@epsilon-asi/foundation 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/decorators/environment/defaultSystem.d.ts +6 -0
- package/build/decorators/environment/defaultSystem.js +12 -0
- package/build/decorators/environment/dotenv.d.ts +48 -0
- package/build/decorators/environment/dotenv.js +77 -0
- package/build/decorators/environment/errors.d.ts +12 -0
- package/build/decorators/environment/errors.js +13 -0
- package/build/decorators/environment/index.d.ts +7 -164
- package/build/decorators/environment/index.js +19 -221
- package/build/decorators/environment/parse.d.ts +9 -0
- package/build/decorators/environment/parse.js +88 -0
- package/build/decorators/environment/system.d.ts +4 -0
- package/build/decorators/environment/system.js +133 -0
- package/build/decorators/environment/types.d.ts +160 -0
- package/build/decorators/environment/types.js +2 -0
- package/build/files/index.d.ts +11 -0
- package/build/files/index.js +36 -0
- package/build/files/init.d.ts +33 -0
- package/build/files/init.js +62 -0
- package/build/files/layered.d.ts +44 -0
- package/build/files/layered.js +133 -0
- package/build/files/load.d.ts +9 -0
- package/build/files/load.js +33 -0
- package/build/files/parse.d.ts +5 -0
- package/build/files/parse.js +27 -0
- package/build/files/save.d.ts +5 -0
- package/build/files/save.js +30 -0
- package/build/files/serialize.d.ts +6 -0
- package/build/files/serialize.js +24 -0
- package/build/files/types.d.ts +133 -0
- package/build/files/types.js +18 -0
- package/build/files/util.d.ts +42 -0
- package/build/files/util.js +495 -0
- package/package.json +22 -6
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toEnvKey = toEnvKey;
|
|
4
|
+
exports.parseEnvValue = parseEnvValue;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
function toEnvKey(propertyName) {
|
|
7
|
+
return propertyName
|
|
8
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
9
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
10
|
+
.replace(/[-\s]+/g, '_')
|
|
11
|
+
.toUpperCase();
|
|
12
|
+
}
|
|
13
|
+
function parseEnvValue(args) {
|
|
14
|
+
const { envName, propertyName, rawValue, metadata, emptyStringAsMissing } = args;
|
|
15
|
+
const missing = rawValue === undefined || (emptyStringAsMissing && rawValue.trim() === '');
|
|
16
|
+
if (missing) {
|
|
17
|
+
if ('default' in metadata)
|
|
18
|
+
return metadata.default;
|
|
19
|
+
if (metadata.required === false)
|
|
20
|
+
return undefined;
|
|
21
|
+
throw new errors_1.EnvParseError(`Missing required environment variable "${envName}".`, {
|
|
22
|
+
envName,
|
|
23
|
+
propertyName,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
switch (metadata.type) {
|
|
27
|
+
case 'string':
|
|
28
|
+
return rawValue;
|
|
29
|
+
case 'number':
|
|
30
|
+
return parseNumberValue({
|
|
31
|
+
envName,
|
|
32
|
+
propertyName,
|
|
33
|
+
rawValue,
|
|
34
|
+
metadata,
|
|
35
|
+
});
|
|
36
|
+
case 'boolean':
|
|
37
|
+
return parseBooleanValue({
|
|
38
|
+
envName,
|
|
39
|
+
propertyName,
|
|
40
|
+
rawValue,
|
|
41
|
+
});
|
|
42
|
+
default:
|
|
43
|
+
return assertNever(metadata);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function parseNumberValue(args) {
|
|
47
|
+
const { envName, propertyName, rawValue, metadata } = args;
|
|
48
|
+
const value = Number(rawValue);
|
|
49
|
+
if (!Number.isFinite(value)) {
|
|
50
|
+
throw new errors_1.EnvParseError(`Environment variable "${envName}" must be a finite number. Received "${rawValue}".`, { envName, propertyName });
|
|
51
|
+
}
|
|
52
|
+
if (metadata.integer === true && !Number.isInteger(value)) {
|
|
53
|
+
throw new errors_1.EnvParseError(`Environment variable "${envName}" must be an integer. Received "${rawValue}".`, { envName, propertyName });
|
|
54
|
+
}
|
|
55
|
+
if (metadata.min !== undefined && value < metadata.min) {
|
|
56
|
+
throw new errors_1.EnvParseError(`Environment variable "${envName}" must be >= ${metadata.min}. Received "${rawValue}".`, { envName, propertyName });
|
|
57
|
+
}
|
|
58
|
+
if (metadata.max !== undefined && value > metadata.max) {
|
|
59
|
+
throw new errors_1.EnvParseError(`Environment variable "${envName}" must be <= ${metadata.max}. Received "${rawValue}".`, { envName, propertyName });
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
function parseBooleanValue(args) {
|
|
64
|
+
const { envName, propertyName, rawValue } = args;
|
|
65
|
+
const normalized = rawValue.trim().toLowerCase();
|
|
66
|
+
if (isTruthyEnvValue(normalized))
|
|
67
|
+
return true;
|
|
68
|
+
if (isFalsyEnvValue(normalized))
|
|
69
|
+
return false;
|
|
70
|
+
throw new errors_1.EnvParseError(`Environment variable "${envName}" must be a boolean. Received "${rawValue}".`, { envName, propertyName });
|
|
71
|
+
}
|
|
72
|
+
function isTruthyEnvValue(value) {
|
|
73
|
+
return value === 'true'
|
|
74
|
+
|| value === '1'
|
|
75
|
+
|| value === 'yes'
|
|
76
|
+
|| value === 'y'
|
|
77
|
+
|| value === 'on';
|
|
78
|
+
}
|
|
79
|
+
function isFalsyEnvValue(value) {
|
|
80
|
+
return value === 'false'
|
|
81
|
+
|| value === '0'
|
|
82
|
+
|| value === 'no'
|
|
83
|
+
|| value === 'n'
|
|
84
|
+
|| value === 'off';
|
|
85
|
+
}
|
|
86
|
+
function assertNever(value) {
|
|
87
|
+
throw new errors_1.EnvParseError(`Unsupported env metadata: ${JSON.stringify(value)}`);
|
|
88
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RegistryMetadataSnapshot } from '../factory';
|
|
2
|
+
import type { EnvConfigMetadata, EnvSystem } from './types';
|
|
3
|
+
export declare function createEnvSystem(): EnvSystem;
|
|
4
|
+
export declare function getEnvConfigMetadata(snapshot: RegistryMetadataSnapshot): EnvConfigMetadata | undefined;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createEnvSystem = createEnvSystem;
|
|
4
|
+
exports.getEnvConfigMetadata = getEnvConfigMetadata;
|
|
5
|
+
const factory_1 = require("../factory");
|
|
6
|
+
const dotenv_1 = require("./dotenv");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const parse_1 = require("./parse");
|
|
9
|
+
function createEnvSystem() {
|
|
10
|
+
const registry = (0, factory_1.createDecoratorRegistry)({
|
|
11
|
+
namespace: 'env',
|
|
12
|
+
});
|
|
13
|
+
const RawEnvConfig = registry.class('envConfig');
|
|
14
|
+
const Env = ((metadata) => {
|
|
15
|
+
return metadata === undefined ? RawEnvConfig() : RawEnvConfig(metadata);
|
|
16
|
+
});
|
|
17
|
+
const EnvConfig = Env;
|
|
18
|
+
/**
|
|
19
|
+
* Broad scoped contract because this system decorates arbitrary env classes.
|
|
20
|
+
* The public EnvVar.string/number/boolean helper return types enforce the actual
|
|
21
|
+
* decorated property type.
|
|
22
|
+
*/
|
|
23
|
+
const Scoped = registry.for();
|
|
24
|
+
const RawEnvVar = Scoped.property('envVar');
|
|
25
|
+
const EnvVar = ((metadata) => RawEnvVar(metadata));
|
|
26
|
+
EnvVar.string = (name, options = {}) => {
|
|
27
|
+
return RawEnvVar(Object.assign(Object.assign({}, options), { name, type: 'string' }));
|
|
28
|
+
};
|
|
29
|
+
EnvVar.number = (name, options = {}) => {
|
|
30
|
+
return RawEnvVar(Object.assign(Object.assign({}, options), { name, type: 'number' }));
|
|
31
|
+
};
|
|
32
|
+
EnvVar.boolean = (name, options = {}) => {
|
|
33
|
+
return RawEnvVar(Object.assign(Object.assign({}, options), { name, type: 'boolean' }));
|
|
34
|
+
};
|
|
35
|
+
function describeEnv(target, options = {}) {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
const includeInherited = (_a = options.includeInherited) !== null && _a !== void 0 ? _a : true;
|
|
38
|
+
const snapshot = registry.read(target, { includeInherited });
|
|
39
|
+
const classMetadata = getEnvConfigMetadata(snapshot);
|
|
40
|
+
const prefix = (_b = classMetadata === null || classMetadata === void 0 ? void 0 : classMetadata.prefix) !== null && _b !== void 0 ? _b : '';
|
|
41
|
+
return Object.freeze(snapshot.propertyRecords('envVar').map((record) => {
|
|
42
|
+
var _a;
|
|
43
|
+
if (record.memberName === undefined) {
|
|
44
|
+
throw new errors_1.EnvParseError('EnvVar metadata record is missing memberName.');
|
|
45
|
+
}
|
|
46
|
+
if (typeof record.memberName === 'symbol') {
|
|
47
|
+
throw new errors_1.EnvParseError(`EnvVar does not support symbol member keys: ${String(record.memberName)}.`);
|
|
48
|
+
}
|
|
49
|
+
const metadata = record.metadata;
|
|
50
|
+
const propertyName = record.memberName;
|
|
51
|
+
const envName = prefix + ((_a = metadata.name) !== null && _a !== void 0 ? _a : (0, parse_1.toEnvKey)(propertyName));
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
propertyName,
|
|
54
|
+
envName,
|
|
55
|
+
static: record.static,
|
|
56
|
+
private: record.private,
|
|
57
|
+
metadata,
|
|
58
|
+
});
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
function loadEnv(target, options = {}) {
|
|
62
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
63
|
+
const source = (_a = options.source) !== null && _a !== void 0 ? _a : getDefaultEnvSource();
|
|
64
|
+
const requireStatic = (_b = options.requireStatic) !== null && _b !== void 0 ? _b : true;
|
|
65
|
+
const emptyStringAsMissing = (_c = options.emptyStringAsMissing) !== null && _c !== void 0 ? _c : true;
|
|
66
|
+
const includeInherited = (_d = options.includeInherited) !== null && _d !== void 0 ? _d : true;
|
|
67
|
+
const snapshot = registry.read(target, { includeInherited });
|
|
68
|
+
const definitions = describeEnv(target, { includeInherited });
|
|
69
|
+
const values = {};
|
|
70
|
+
const dotenvOptions = (0, dotenv_1.resolveDotenvOptions)((_e = getEnvConfigMetadata(snapshot)) === null || _e === void 0 ? void 0 : _e.dotenv, options.dotenv);
|
|
71
|
+
const dotenv = dotenvOptions === undefined ? undefined : (0, dotenv_1.readDotenvFiles)(dotenvOptions);
|
|
72
|
+
const dotenvWins = (_f = dotenvOptions === null || dotenvOptions === void 0 ? void 0 : dotenvOptions.override) !== null && _f !== void 0 ? _f : true;
|
|
73
|
+
const lookup = (envName) => {
|
|
74
|
+
var _a;
|
|
75
|
+
const fromDotenv = dotenv === null || dotenv === void 0 ? void 0 : dotenv.values[envName];
|
|
76
|
+
return dotenvWins ? fromDotenv !== null && fromDotenv !== void 0 ? fromDotenv : source[envName] : (_a = source[envName]) !== null && _a !== void 0 ? _a : fromDotenv;
|
|
77
|
+
};
|
|
78
|
+
for (const definition of definitions) {
|
|
79
|
+
if (requireStatic && !definition.static) {
|
|
80
|
+
throw new errors_1.EnvParseError(`@EnvVar field "${definition.propertyName}" must be static when using loadEnv().`, {
|
|
81
|
+
envName: definition.envName,
|
|
82
|
+
propertyName: definition.propertyName,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (definition.private) {
|
|
86
|
+
throw new errors_1.EnvParseError(`@EnvVar field "${definition.propertyName}" cannot be private.`, {
|
|
87
|
+
envName: definition.envName,
|
|
88
|
+
propertyName: definition.propertyName,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const rawValue = lookup(definition.envName);
|
|
92
|
+
const parsedValue = (0, parse_1.parseEnvValue)({
|
|
93
|
+
envName: definition.envName,
|
|
94
|
+
propertyName: definition.propertyName,
|
|
95
|
+
rawValue,
|
|
96
|
+
metadata: definition.metadata,
|
|
97
|
+
emptyStringAsMissing,
|
|
98
|
+
});
|
|
99
|
+
Object.defineProperty(target, definition.propertyName, {
|
|
100
|
+
configurable: true,
|
|
101
|
+
enumerable: true,
|
|
102
|
+
writable: true,
|
|
103
|
+
value: parsedValue,
|
|
104
|
+
});
|
|
105
|
+
values[definition.propertyName] = parsedValue;
|
|
106
|
+
}
|
|
107
|
+
return Object.freeze({
|
|
108
|
+
target,
|
|
109
|
+
definitions,
|
|
110
|
+
values: Object.freeze(Object.assign({}, values)),
|
|
111
|
+
snapshot,
|
|
112
|
+
dotenvFiles: (_g = dotenv === null || dotenv === void 0 ? void 0 : dotenv.files) !== null && _g !== void 0 ? _g : Object.freeze([]),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return Object.freeze({
|
|
116
|
+
registry,
|
|
117
|
+
Env,
|
|
118
|
+
EnvConfig,
|
|
119
|
+
EnvVar,
|
|
120
|
+
loadEnv,
|
|
121
|
+
describeEnv,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function getEnvConfigMetadata(snapshot) {
|
|
125
|
+
const configRecords = snapshot.classRecords('envConfig');
|
|
126
|
+
const lastRecord = configRecords[configRecords.length - 1];
|
|
127
|
+
return lastRecord === null || lastRecord === void 0 ? void 0 : lastRecord.metadata;
|
|
128
|
+
}
|
|
129
|
+
function getDefaultEnvSource() {
|
|
130
|
+
var _a, _b;
|
|
131
|
+
const maybeGlobal = globalThis;
|
|
132
|
+
return (_b = (_a = maybeGlobal.process) === null || _a === void 0 ? void 0 : _a.env) !== null && _b !== void 0 ? _b : {};
|
|
133
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { ClassDecoratorDefinition, Constructor, DecoratorRegistry, PropertyDecoratorDefinition, RegistryMetadataSnapshot } from '../factory';
|
|
2
|
+
import type { DotenvSetting } from './dotenv';
|
|
3
|
+
export type EnvPrimitive = string | number | boolean;
|
|
4
|
+
export type EnvPrimitiveOrUndefined = EnvPrimitive | undefined;
|
|
5
|
+
export type EnvBooleanTrueValue = 'true' | '1' | 'yes' | 'y' | 'on';
|
|
6
|
+
export type EnvBooleanFalseValue = 'false' | '0' | 'no' | 'n' | 'off';
|
|
7
|
+
export type EnvValueKind = 'string' | 'number' | 'boolean';
|
|
8
|
+
export interface EnvConfigMetadata {
|
|
9
|
+
/**
|
|
10
|
+
* Optional prefix applied to every variable lookup.
|
|
11
|
+
*
|
|
12
|
+
* Example:
|
|
13
|
+
* prefix: 'APP_'
|
|
14
|
+
* @EnvVar.string('PORT')
|
|
15
|
+
* final lookup key: APP_PORT
|
|
16
|
+
*/
|
|
17
|
+
prefix?: string;
|
|
18
|
+
/** Optional human-readable description for docs/debug output. */
|
|
19
|
+
description?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Dotenv files this class loads. On by default: loadEnv() reads '.env' from the working
|
|
22
|
+
* directory (skipping it when missing) and its values take precedence over process.env.
|
|
23
|
+
*
|
|
24
|
+
* Pass a path, a list of paths (later files win) or DotenvOptions to change that,
|
|
25
|
+
* or `false` to opt this class out. loadEnv({ dotenv }) is merged over this setting.
|
|
26
|
+
*/
|
|
27
|
+
dotenv?: DotenvSetting;
|
|
28
|
+
}
|
|
29
|
+
export type StringEnvVarMetadata = {
|
|
30
|
+
type: 'string';
|
|
31
|
+
name?: string;
|
|
32
|
+
required?: boolean;
|
|
33
|
+
default?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
};
|
|
36
|
+
export type NumberEnvVarMetadata = {
|
|
37
|
+
type: 'number';
|
|
38
|
+
name?: string;
|
|
39
|
+
required?: boolean;
|
|
40
|
+
default?: number;
|
|
41
|
+
min?: number;
|
|
42
|
+
max?: number;
|
|
43
|
+
integer?: boolean;
|
|
44
|
+
description?: string;
|
|
45
|
+
};
|
|
46
|
+
export type BooleanEnvVarMetadata = {
|
|
47
|
+
type: 'boolean';
|
|
48
|
+
name?: string;
|
|
49
|
+
required?: boolean;
|
|
50
|
+
default?: boolean;
|
|
51
|
+
description?: string;
|
|
52
|
+
};
|
|
53
|
+
export type EnvVarMetadata = StringEnvVarMetadata | NumberEnvVarMetadata | BooleanEnvVarMetadata;
|
|
54
|
+
export type EnvDecoratorSchema = {
|
|
55
|
+
class: {
|
|
56
|
+
envConfig: ClassDecoratorDefinition<EnvConfigMetadata | undefined>;
|
|
57
|
+
};
|
|
58
|
+
property: {
|
|
59
|
+
envVar: PropertyDecoratorDefinition<EnvVarMetadata, EnvPrimitiveOrUndefined>;
|
|
60
|
+
};
|
|
61
|
+
method: {};
|
|
62
|
+
accessor: {};
|
|
63
|
+
};
|
|
64
|
+
export type EnvSource = Record<string, string | undefined>;
|
|
65
|
+
export interface LoadEnvOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Defaults to globalThis.process.env when available.
|
|
68
|
+
* Pass this in tests to avoid mutating or depending on the real process env.
|
|
69
|
+
* Dotenv files are still layered over it; add `dotenv: false` to isolate fully.
|
|
70
|
+
*/
|
|
71
|
+
source?: EnvSource;
|
|
72
|
+
/**
|
|
73
|
+
* Dotenv files layered over `source`. On by default: reads '.env' from the working
|
|
74
|
+
* directory, skips it when missing, and its values take precedence over `source`.
|
|
75
|
+
*
|
|
76
|
+
* Merged over the class-level @Env({ dotenv }) setting. Pass `false` to turn it off
|
|
77
|
+
* for this call, or `{ override: false }` to let `source` win instead.
|
|
78
|
+
*/
|
|
79
|
+
dotenv?: DotenvSetting;
|
|
80
|
+
/**
|
|
81
|
+
* Whether @EnvVar fields must be static.
|
|
82
|
+
*
|
|
83
|
+
* Defaults to true because loadEnv mutates the class constructor in place.
|
|
84
|
+
*/
|
|
85
|
+
requireStatic?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Whether empty strings and whitespace-only strings should be treated as missing.
|
|
88
|
+
*
|
|
89
|
+
* Defaults to true.
|
|
90
|
+
*/
|
|
91
|
+
emptyStringAsMissing?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Whether inherited metadata should be loaded.
|
|
94
|
+
*
|
|
95
|
+
* Defaults to true.
|
|
96
|
+
*/
|
|
97
|
+
includeInherited?: boolean;
|
|
98
|
+
}
|
|
99
|
+
export interface DescribeEnvOptions {
|
|
100
|
+
/** Defaults to true. */
|
|
101
|
+
includeInherited?: boolean;
|
|
102
|
+
}
|
|
103
|
+
export interface EnvVariableDefinition {
|
|
104
|
+
readonly propertyName: string;
|
|
105
|
+
readonly envName: string;
|
|
106
|
+
readonly static: boolean;
|
|
107
|
+
readonly private: boolean;
|
|
108
|
+
readonly metadata: EnvVarMetadata;
|
|
109
|
+
}
|
|
110
|
+
export interface EnvLoadResult<TClass extends Constructor> {
|
|
111
|
+
readonly target: TClass;
|
|
112
|
+
readonly definitions: readonly EnvVariableDefinition[];
|
|
113
|
+
readonly values: Readonly<Record<string, EnvPrimitiveOrUndefined>>;
|
|
114
|
+
readonly snapshot: RegistryMetadataSnapshot;
|
|
115
|
+
/** Absolute paths of the dotenv files that were read, in load order. */
|
|
116
|
+
readonly dotenvFiles: readonly string[];
|
|
117
|
+
}
|
|
118
|
+
export interface EnvConfigDecorator {
|
|
119
|
+
(metadata?: EnvConfigMetadata): <TClass extends Constructor>(value: TClass, context: ClassDecoratorContext<TClass>) => void;
|
|
120
|
+
}
|
|
121
|
+
export interface EnvVarDecoratorApi {
|
|
122
|
+
/**
|
|
123
|
+
* Generic form:
|
|
124
|
+
*
|
|
125
|
+
* @EnvVar({ name: 'PORT', type: 'number' })
|
|
126
|
+
* static port: number;
|
|
127
|
+
*/
|
|
128
|
+
(metadata: StringEnvVarMetadata): (value: undefined, context: ClassFieldDecoratorContext<any, string | undefined>) => void;
|
|
129
|
+
(metadata: NumberEnvVarMetadata): (value: undefined, context: ClassFieldDecoratorContext<any, number | undefined>) => void;
|
|
130
|
+
(metadata: BooleanEnvVarMetadata): (value: undefined, context: ClassFieldDecoratorContext<any, boolean | undefined>) => void;
|
|
131
|
+
/**
|
|
132
|
+
* @EnvVar.string('API_KEY')
|
|
133
|
+
* static apiKey: string;
|
|
134
|
+
*/
|
|
135
|
+
string(name?: string, options?: Omit<StringEnvVarMetadata, 'type' | 'name'>): (value: undefined, context: ClassFieldDecoratorContext<any, string | undefined>) => void;
|
|
136
|
+
/**
|
|
137
|
+
* @EnvVar.number('PORT', { default: 3000, integer: true })
|
|
138
|
+
* static port: number;
|
|
139
|
+
*/
|
|
140
|
+
number(name?: string, options?: Omit<NumberEnvVarMetadata, 'type' | 'name'>): (value: undefined, context: ClassFieldDecoratorContext<any, number | undefined>) => void;
|
|
141
|
+
/**
|
|
142
|
+
* @EnvVar.boolean('DEBUG', { default: false })
|
|
143
|
+
* static debug: boolean;
|
|
144
|
+
*/
|
|
145
|
+
boolean(name?: string, options?: Omit<BooleanEnvVarMetadata, 'type' | 'name'>): (value: undefined, context: ClassFieldDecoratorContext<any, boolean | undefined>) => void;
|
|
146
|
+
}
|
|
147
|
+
export interface EnvSystem {
|
|
148
|
+
readonly registry: DecoratorRegistry<EnvDecoratorSchema>;
|
|
149
|
+
/** Preferred class decorator name. */
|
|
150
|
+
readonly Env: EnvConfigDecorator;
|
|
151
|
+
/** Alias for Env, for users who prefer a more explicit name. */
|
|
152
|
+
readonly EnvConfig: EnvConfigDecorator;
|
|
153
|
+
readonly EnvVar: EnvVarDecoratorApi;
|
|
154
|
+
/**
|
|
155
|
+
* Mutates the provided class in place by assigning parsed values to static fields.
|
|
156
|
+
*/
|
|
157
|
+
loadEnv<TClass extends Constructor>(target: TClass, options?: LoadEnvOptions): EnvLoadResult<TClass>;
|
|
158
|
+
/** Reads env metadata without mutating the class. */
|
|
159
|
+
describeEnv<TClass extends Constructor>(target: TClass, options?: DescribeEnvOptions): readonly EnvVariableDefinition[];
|
|
160
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createSchemaFile, interpolateEnv, interpolateEnvHook } from "./init";
|
|
2
|
+
import { loadFile, loadFileSync, safeLoadFile, safeLoadFileSync } from "./load";
|
|
3
|
+
import { parseStringSync } from "./parse";
|
|
4
|
+
import { parseString } from "./parse";
|
|
5
|
+
import { serialize, serializeAsync } from "./serialize";
|
|
6
|
+
import { saveFile, saveFileSync } from "./save";
|
|
7
|
+
import { SchemaFileError } from "./types";
|
|
8
|
+
import { deepMerge } from "./util";
|
|
9
|
+
import { createLayeredFile, discoverLayers, discoverLayersSync, loadLayeredFile, loadLayeredFileSync, loadMergedFiles, loadMergedFilesSync, safeLoadLayeredFile, safeLoadLayeredFileSync } from "./layered";
|
|
10
|
+
export type { DiscoverOptions, ErrorFormatter, FileLayer, LayeredLoadOptions, LayeredResult, LayerOrigin, FileFormat, LoadOptions, ParseOptions, ParseParams, PreprocessContext, SafeResult, SaveOptions, SchemaFileErrorKind, SerializeOptions, SerializeStrategy, } from "./types";
|
|
11
|
+
export { createSchemaFile, interpolateEnv, interpolateEnvHook, loadFile, loadFileSync, safeLoadFile, safeLoadFileSync, serialize, serializeAsync, parseString, parseStringSync, saveFileSync, saveFile, SchemaFileError, deepMerge, createLayeredFile, discoverLayers, discoverLayersSync, loadLayeredFile, loadLayeredFileSync, loadMergedFiles, loadMergedFilesSync, safeLoadLayeredFile, safeLoadLayeredFileSync, };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeLoadLayeredFileSync = exports.safeLoadLayeredFile = exports.loadMergedFilesSync = exports.loadMergedFiles = exports.loadLayeredFileSync = exports.loadLayeredFile = exports.discoverLayersSync = exports.discoverLayers = exports.createLayeredFile = exports.deepMerge = exports.SchemaFileError = exports.saveFile = exports.saveFileSync = exports.parseStringSync = exports.parseString = exports.serializeAsync = exports.serialize = exports.safeLoadFileSync = exports.safeLoadFile = exports.loadFileSync = exports.loadFile = exports.interpolateEnvHook = exports.interpolateEnv = exports.createSchemaFile = void 0;
|
|
4
|
+
const init_1 = require("./init");
|
|
5
|
+
Object.defineProperty(exports, "createSchemaFile", { enumerable: true, get: function () { return init_1.createSchemaFile; } });
|
|
6
|
+
Object.defineProperty(exports, "interpolateEnv", { enumerable: true, get: function () { return init_1.interpolateEnv; } });
|
|
7
|
+
Object.defineProperty(exports, "interpolateEnvHook", { enumerable: true, get: function () { return init_1.interpolateEnvHook; } });
|
|
8
|
+
const load_1 = require("./load");
|
|
9
|
+
Object.defineProperty(exports, "loadFile", { enumerable: true, get: function () { return load_1.loadFile; } });
|
|
10
|
+
Object.defineProperty(exports, "loadFileSync", { enumerable: true, get: function () { return load_1.loadFileSync; } });
|
|
11
|
+
Object.defineProperty(exports, "safeLoadFile", { enumerable: true, get: function () { return load_1.safeLoadFile; } });
|
|
12
|
+
Object.defineProperty(exports, "safeLoadFileSync", { enumerable: true, get: function () { return load_1.safeLoadFileSync; } });
|
|
13
|
+
const parse_1 = require("./parse");
|
|
14
|
+
Object.defineProperty(exports, "parseStringSync", { enumerable: true, get: function () { return parse_1.parseStringSync; } });
|
|
15
|
+
const parse_2 = require("./parse");
|
|
16
|
+
Object.defineProperty(exports, "parseString", { enumerable: true, get: function () { return parse_2.parseString; } });
|
|
17
|
+
const serialize_1 = require("./serialize");
|
|
18
|
+
Object.defineProperty(exports, "serialize", { enumerable: true, get: function () { return serialize_1.serialize; } });
|
|
19
|
+
Object.defineProperty(exports, "serializeAsync", { enumerable: true, get: function () { return serialize_1.serializeAsync; } });
|
|
20
|
+
const save_1 = require("./save");
|
|
21
|
+
Object.defineProperty(exports, "saveFile", { enumerable: true, get: function () { return save_1.saveFile; } });
|
|
22
|
+
Object.defineProperty(exports, "saveFileSync", { enumerable: true, get: function () { return save_1.saveFileSync; } });
|
|
23
|
+
const types_1 = require("./types");
|
|
24
|
+
Object.defineProperty(exports, "SchemaFileError", { enumerable: true, get: function () { return types_1.SchemaFileError; } });
|
|
25
|
+
const util_1 = require("./util");
|
|
26
|
+
Object.defineProperty(exports, "deepMerge", { enumerable: true, get: function () { return util_1.deepMerge; } });
|
|
27
|
+
const layered_1 = require("./layered");
|
|
28
|
+
Object.defineProperty(exports, "createLayeredFile", { enumerable: true, get: function () { return layered_1.createLayeredFile; } });
|
|
29
|
+
Object.defineProperty(exports, "discoverLayers", { enumerable: true, get: function () { return layered_1.discoverLayers; } });
|
|
30
|
+
Object.defineProperty(exports, "discoverLayersSync", { enumerable: true, get: function () { return layered_1.discoverLayersSync; } });
|
|
31
|
+
Object.defineProperty(exports, "loadLayeredFile", { enumerable: true, get: function () { return layered_1.loadLayeredFile; } });
|
|
32
|
+
Object.defineProperty(exports, "loadLayeredFileSync", { enumerable: true, get: function () { return layered_1.loadLayeredFileSync; } });
|
|
33
|
+
Object.defineProperty(exports, "loadMergedFiles", { enumerable: true, get: function () { return layered_1.loadMergedFiles; } });
|
|
34
|
+
Object.defineProperty(exports, "loadMergedFilesSync", { enumerable: true, get: function () { return layered_1.loadMergedFilesSync; } });
|
|
35
|
+
Object.defineProperty(exports, "safeLoadLayeredFile", { enumerable: true, get: function () { return layered_1.safeLoadLayeredFile; } });
|
|
36
|
+
Object.defineProperty(exports, "safeLoadLayeredFileSync", { enumerable: true, get: function () { return layered_1.safeLoadLayeredFileSync; } });
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { LoadOptions, ParseOptions, SaveOptions, SerializeOptions } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Bind a schema and default options once, reuse everywhere.
|
|
5
|
+
* Per-call options are shallow-merged over the defaults.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createSchemaFile<S extends z.ZodType>(schema: S, defaults?: {
|
|
8
|
+
load?: LoadOptions;
|
|
9
|
+
save?: SaveOptions;
|
|
10
|
+
}): {
|
|
11
|
+
schema: S;
|
|
12
|
+
load: (filePath: string, o?: LoadOptions) => Promise<z.core.output<S>>;
|
|
13
|
+
loadSync: (filePath: string, o?: LoadOptions) => z.core.output<S>;
|
|
14
|
+
safeLoad: (filePath: string, o?: LoadOptions) => Promise<import("./types").SafeResult<z.core.output<S>>>;
|
|
15
|
+
safeLoadSync: (filePath: string, o?: LoadOptions) => import("./types").SafeResult<z.core.output<S>>;
|
|
16
|
+
parse: (text: string, o?: ParseOptions) => Promise<z.core.output<S>>;
|
|
17
|
+
save: (filePath: string, value: z.output<S>, o?: SaveOptions) => Promise<string>;
|
|
18
|
+
saveSync: (filePath: string, value: z.output<S>, o?: SaveOptions) => string;
|
|
19
|
+
serialize: (value: z.output<S>, o: SerializeOptions) => Promise<string>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Recursively replace ${VAR} and ${VAR:-fallback} in string values.
|
|
23
|
+
* For a `preprocess` hook use interpolateEnvHook(): a hook's second argument is the
|
|
24
|
+
* PreprocessContext, which this function would read as `env`. Note results are strings,
|
|
25
|
+
* so use z.coerce.* in the schema for numeric/boolean fields filled from env vars.
|
|
26
|
+
*/
|
|
27
|
+
export declare function interpolateEnv(value: unknown, env?: Record<string, string | undefined>, options?: {
|
|
28
|
+
strict?: boolean;
|
|
29
|
+
}): unknown;
|
|
30
|
+
/** A `preprocess` hook that runs interpolateEnv over the parsed data: { preprocess: interpolateEnvHook() }. */
|
|
31
|
+
export declare function interpolateEnvHook(env?: Record<string, string | undefined>, options?: {
|
|
32
|
+
strict?: boolean;
|
|
33
|
+
}): (raw: unknown) => unknown;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSchemaFile = createSchemaFile;
|
|
4
|
+
exports.interpolateEnv = interpolateEnv;
|
|
5
|
+
exports.interpolateEnvHook = interpolateEnvHook;
|
|
6
|
+
const util_1 = require("./util");
|
|
7
|
+
const serialize_1 = require("./serialize");
|
|
8
|
+
const load_1 = require("./load");
|
|
9
|
+
const save_1 = require("./save");
|
|
10
|
+
const parse_1 = require("./parse");
|
|
11
|
+
/**
|
|
12
|
+
* Bind a schema and default options once, reuse everywhere.
|
|
13
|
+
* Per-call options are shallow-merged over the defaults.
|
|
14
|
+
*/
|
|
15
|
+
function createSchemaFile(schema, defaults = {}) {
|
|
16
|
+
const l = (o) => (Object.assign(Object.assign({}, defaults.load), o));
|
|
17
|
+
const s = (o) => (Object.assign(Object.assign({}, defaults.save), o));
|
|
18
|
+
return {
|
|
19
|
+
schema,
|
|
20
|
+
load: (filePath, o) => (0, load_1.loadFile)(filePath, schema, l(o)),
|
|
21
|
+
loadSync: (filePath, o) => (0, load_1.loadFileSync)(filePath, schema, l(o)),
|
|
22
|
+
safeLoad: (filePath, o) => (0, load_1.safeLoadFile)(filePath, schema, l(o)),
|
|
23
|
+
safeLoadSync: (filePath, o) => (0, load_1.safeLoadFileSync)(filePath, schema, l(o)),
|
|
24
|
+
parse: (text, o) => (0, parse_1.parseString)(schema, text, l(o)),
|
|
25
|
+
save: (filePath, value, o) => (0, save_1.saveFile)(filePath, schema, value, s(o)),
|
|
26
|
+
saveSync: (filePath, value, o) => (0, save_1.saveFileSync)(filePath, schema, value, s(o)),
|
|
27
|
+
serialize: (value, o) => (0, serialize_1.serializeAsync)(schema, value, Object.assign(Object.assign({}, defaults.save), o)),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Recursively replace ${VAR} and ${VAR:-fallback} in string values.
|
|
32
|
+
* For a `preprocess` hook use interpolateEnvHook(): a hook's second argument is the
|
|
33
|
+
* PreprocessContext, which this function would read as `env`. Note results are strings,
|
|
34
|
+
* so use z.coerce.* in the schema for numeric/boolean fields filled from env vars.
|
|
35
|
+
*/
|
|
36
|
+
function interpolateEnv(value, env = process.env, options = {}) {
|
|
37
|
+
const pattern = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
38
|
+
const walk = (v) => {
|
|
39
|
+
if (typeof v === "string") {
|
|
40
|
+
return v.replace(pattern, (_m, name, fallback) => {
|
|
41
|
+
const found = env[name];
|
|
42
|
+
if (found !== undefined && found !== "")
|
|
43
|
+
return found;
|
|
44
|
+
if (fallback !== undefined)
|
|
45
|
+
return fallback;
|
|
46
|
+
if (options.strict)
|
|
47
|
+
throw new Error(`Environment variable "${name}" is not set`);
|
|
48
|
+
return "";
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(v))
|
|
52
|
+
return v.map(walk);
|
|
53
|
+
if ((0, util_1.isPlainObject)(v))
|
|
54
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
|
|
55
|
+
return v;
|
|
56
|
+
};
|
|
57
|
+
return walk(value);
|
|
58
|
+
}
|
|
59
|
+
/** A `preprocess` hook that runs interpolateEnv over the parsed data: { preprocess: interpolateEnvHook() }. */
|
|
60
|
+
function interpolateEnvHook(env = process.env, options = {}) {
|
|
61
|
+
return (raw) => interpolateEnv(raw, env, options);
|
|
62
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { DiscoverOptions, FileLayer, LayeredLoadOptions, LayeredResult, LoadOptions, SafeResult } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Find every existing `fileName` (a name or a relative path such as `.tool/config.yaml`) from
|
|
5
|
+
* `startDir` upwards, then among `fallbacks`. Results are ordered highest precedence first.
|
|
6
|
+
*/
|
|
7
|
+
export declare function discoverLayers(fileName: string, options?: DiscoverOptions): Promise<FileLayer[]>;
|
|
8
|
+
export declare function discoverLayersSync(fileName: string, options?: DiscoverOptions): FileLayer[];
|
|
9
|
+
/**
|
|
10
|
+
* Load several JSON/YAML files, deep-merge them and validate the result once.
|
|
11
|
+
* `filePaths` is highest precedence first. Each file is read raw (no schema defaults), so a
|
|
12
|
+
* lower layer's value is never masked by a default; defaults apply only to keys no layer sets.
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadMergedFiles<S extends z.ZodType>(filePaths: readonly string[], schema: S, options?: LoadOptions): Promise<z.output<S>>;
|
|
15
|
+
export declare function loadMergedFilesSync<S extends z.ZodType>(filePaths: readonly string[], schema: S, options?: LoadOptions): z.output<S>;
|
|
16
|
+
/**
|
|
17
|
+
* Discover every `fileName` layer (see discoverLayers), merge them and validate.
|
|
18
|
+
* Precedence: start directory > nearer ancestors > farther ancestors > fallbacks, in order.
|
|
19
|
+
* With no layers found, the schema validates `{}`, so its defaults apply.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadLayeredFile<S extends z.ZodType>(fileName: string, schema: S, options?: LayeredLoadOptions): Promise<LayeredResult<z.output<S>>>;
|
|
22
|
+
/** Synchronous variant. Fails if the schema contains async refinements/transforms. */
|
|
23
|
+
export declare function loadLayeredFileSync<S extends z.ZodType>(fileName: string, schema: S, options?: LayeredLoadOptions): LayeredResult<z.output<S>>;
|
|
24
|
+
/** Like loadLayeredFile, but returns a result object instead of throwing SchemaFileError. */
|
|
25
|
+
export declare function safeLoadLayeredFile<S extends z.ZodType>(fileName: string, schema: S, options?: LayeredLoadOptions): Promise<SafeResult<LayeredResult<z.output<S>>>>;
|
|
26
|
+
export declare function safeLoadLayeredFileSync<S extends z.ZodType>(fileName: string, schema: S, options?: LayeredLoadOptions): SafeResult<LayeredResult<z.output<S>>>;
|
|
27
|
+
/**
|
|
28
|
+
* Bind a schema, file name and default options once, reuse everywhere.
|
|
29
|
+
* Per-call options are shallow-merged over the defaults, e.g. `config.load({startDir})`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const Config = createLayeredFile(ConfigSchema, "tool.yaml", {fallbacks: ["~/.tool/tool.yaml"]});
|
|
33
|
+
* const {value, layers} = await Config.load();
|
|
34
|
+
*/
|
|
35
|
+
export declare function createLayeredFile<S extends z.ZodType>(schema: S, fileName: string, defaults?: LayeredLoadOptions): {
|
|
36
|
+
schema: S;
|
|
37
|
+
fileName: string;
|
|
38
|
+
discover: (options?: DiscoverOptions) => Promise<FileLayer[]>;
|
|
39
|
+
discoverSync: (options?: DiscoverOptions) => FileLayer[];
|
|
40
|
+
load: (options?: LayeredLoadOptions) => Promise<LayeredResult<z.core.output<S>>>;
|
|
41
|
+
loadSync: (options?: LayeredLoadOptions) => LayeredResult<z.core.output<S>>;
|
|
42
|
+
safeLoad: (options?: LayeredLoadOptions) => Promise<SafeResult<LayeredResult<z.core.output<S>>>>;
|
|
43
|
+
safeLoadSync: (options?: LayeredLoadOptions) => SafeResult<LayeredResult<z.core.output<S>>>;
|
|
44
|
+
};
|