Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | /**
* EnvOverrideLayer - Environment Variable Configuration Overrides
*
* Processes QUIQR_* environment variables and applies them as config overrides.
* Environment variables take precedence over file-based configuration.
*/
import { standardEnvMappings, } from '@quiqr/types';
/**
* EnvOverrideLayer handles environment variable configuration
*/
export class EnvOverrideLayer {
prefix;
mappings;
overrides;
constructor(prefix = 'QUIQR_', customMappings) {
this.prefix = prefix;
this.mappings = customMappings || standardEnvMappings;
this.overrides = new Map();
this.loadFromEnvironment();
}
/**
* Load overrides from current environment
*/
loadFromEnvironment() {
this.overrides.clear();
// Process standard mappings
for (const mapping of this.mappings) {
const envVarName = `${this.prefix}${mapping.envVar}`;
const rawValue = process.env[envVarName];
if (rawValue !== undefined) {
const value = this.transformValue(rawValue, mapping.transform);
this.overrides.set(mapping.configPath, {
configPath: mapping.configPath,
value,
envVar: envVarName,
});
}
}
// Also process any QUIQR_* vars that follow the convention
// Format: QUIQR_SECTION_KEY or QUIQR_SECTION_SUBSECTION_KEY
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith(this.prefix) && value !== undefined) {
const pathParts = key.slice(this.prefix.length).toLowerCase().split('_');
if (pathParts.length >= 2) {
const configPath = pathParts.join('.');
// Skip if already handled by explicit mapping
if (!this.overrides.has(configPath)) {
// Auto-detect type
const transformedValue = this.autoTransform(value);
this.overrides.set(configPath, {
configPath,
value: transformedValue,
envVar: key,
});
}
}
}
}
}
/**
* Get all current overrides
*/
getOverrides() {
return Array.from(this.overrides.values());
}
/**
* Get override for a specific config path
*/
getOverride(configPath) {
return this.overrides.get(configPath);
}
/**
* Check if a config path has an env override
*/
hasOverride(configPath) {
return this.overrides.has(configPath);
}
/**
* Get the override value for a config path
*/
getValue(configPath) {
return this.overrides.get(configPath)?.value;
}
/**
* Apply overrides to a config object
* Modifies the object in place and returns paths that were overridden
*/
applyOverrides(config) {
const appliedPaths = [];
for (const override of this.overrides.values()) {
this.setNestedValue(config, override.configPath, override.value);
appliedPaths.push(override.configPath);
}
return appliedPaths;
}
/**
* Transform a string value based on the specified type
*/
transformValue(value, transform) {
switch (transform) {
case 'string':
return value;
case 'number':
const num = Number(value);
return isNaN(num) ? value : num;
case 'boolean':
return value.toLowerCase() === 'true' || value === '1';
case 'json':
try {
return JSON.parse(value);
}
catch {
return value;
}
default:
return value;
}
}
/**
* Auto-detect and transform a value
*/
autoTransform(value) {
// Check for boolean
if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
return value.toLowerCase() === 'true';
}
// Check for number
const num = Number(value);
if (!isNaN(num) && value.trim() !== '') {
return num;
}
// Check for JSON
if ((value.startsWith('{') && value.endsWith('}')) ||
(value.startsWith('[') && value.endsWith(']'))) {
try {
return JSON.parse(value);
}
catch {
// Not valid JSON, return as string
}
}
return value;
}
/**
* Set a nested value in an object using dot notation
*/
setNestedValue(obj, path, value) {
const parts = path.split('.');
let current = obj;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (!(part in current) || typeof current[part] !== 'object') {
current[part] = {};
}
current = current[part];
}
current[parts[parts.length - 1]] = value;
}
}
|