@pawells/config 2.3.0 → 3.0.0
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/README.md +252 -142
- package/dist/errors.d.ts +24 -32
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +35 -37
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +265 -46
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +669 -138
- package/dist/manager.js.map +1 -0
- package/dist/provider.d.ts +280 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +108 -0
- package/dist/provider.js.map +1 -0
- package/dist/schema.factory.d.ts +30 -37
- package/dist/schema.factory.d.ts.map +1 -1
- package/dist/schema.factory.js +61 -93
- package/dist/schema.factory.js.map +1 -0
- package/dist/secret.d.ts +13 -2
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +32 -6
- package/dist/secret.js.map +1 -0
- package/package.json +63 -62
- package/CHANGELOG.md +0 -19
package/dist/manager.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { writeFileSync } from 'node:fs';
|
|
2
1
|
import { z } from 'zod/v4';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { GetErrorMessage } from '@pawells/typescript-common';
|
|
3
|
+
import { ConfigRegistrationError, ConfigNotRegisteredError, ConfigNotSetError, ConfigValidationError } from './errors.js';
|
|
4
|
+
import { IsMarkedSecret, traverseSchemaToBase } from './secret.js';
|
|
5
5
|
/**
|
|
6
6
|
* Zod schema for all supported configuration value types.
|
|
7
7
|
* Accepts string, number, boolean, Date, string[], number[], boolean[], and undefined — nullable and optional.
|
|
@@ -14,7 +14,7 @@ export const CONFIG_VALUES_TYPES_SCHEMA = z.union([
|
|
|
14
14
|
z.array(z.string()),
|
|
15
15
|
z.array(z.number()),
|
|
16
16
|
z.array(z.boolean()),
|
|
17
|
-
z.undefined()
|
|
17
|
+
z.undefined()
|
|
18
18
|
]).nullable().optional();
|
|
19
19
|
/**
|
|
20
20
|
* Asserts that a value conforms to the supported configuration value types.
|
|
@@ -37,8 +37,7 @@ export function AssertConfigValueType(value) {
|
|
|
37
37
|
* @internal
|
|
38
38
|
*/
|
|
39
39
|
function GetFieldDescription(schema) {
|
|
40
|
-
|
|
41
|
-
while (current != null) {
|
|
40
|
+
for (const current of traverseSchemaToBase(schema)) {
|
|
42
41
|
try {
|
|
43
42
|
const meta = z.globalRegistry.get(current);
|
|
44
43
|
if (typeof meta?.description === 'string') {
|
|
@@ -48,225 +47,757 @@ function GetFieldDescription(schema) {
|
|
|
48
47
|
catch {
|
|
49
48
|
break;
|
|
50
49
|
}
|
|
51
|
-
current = current._def?.innerType;
|
|
52
50
|
}
|
|
53
51
|
return undefined;
|
|
54
52
|
}
|
|
55
53
|
/**
|
|
56
|
-
*
|
|
57
|
-
* Arrays are JSON-stringified; Dates use ISO 8601; all others use String().
|
|
58
|
-
* Returns '' for null or undefined.
|
|
54
|
+
* Non-exported internal state class holding all configuration data and logic.
|
|
59
55
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @internal
|
|
63
|
-
*/
|
|
64
|
-
function SerializeConfigValue(value) {
|
|
65
|
-
if (value === null || value === undefined)
|
|
66
|
-
return '';
|
|
67
|
-
if (Array.isArray(value))
|
|
68
|
-
return JSON.stringify(value);
|
|
69
|
-
if (value instanceof Date)
|
|
70
|
-
return value.toISOString();
|
|
71
|
-
return String(value);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Runtime configuration manager with Zod schema validation.
|
|
75
|
-
* Provides a singleton instance to register and retrieve typed configuration values.
|
|
56
|
+
* All instance methods mirror the public ConfigManager and ScopedConfigManager APIs.
|
|
57
|
+
* This class exists to avoid code duplication between the static and instance facades.
|
|
76
58
|
*
|
|
77
|
-
* @
|
|
78
|
-
* ConfigManager.Register('DATABASE_URL', z.string().url(), 'postgresql://localhost/mydb');
|
|
79
|
-
* ConfigManager.Set('DEFAULT', 'DATABASE_URL', 'postgresql://localhost/mydb');
|
|
80
|
-
* const url = ConfigManager.Get('DATABASE_URL');
|
|
59
|
+
* @internal
|
|
81
60
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
61
|
+
class CoreConfigState {
|
|
62
|
+
_Schemas = new Map();
|
|
63
|
+
_DataDefaults = new Map();
|
|
64
|
+
_DataOverrides = new Map();
|
|
65
|
+
/**
|
|
66
|
+
* Raw (unvalidated) values collected from all registered providers.
|
|
67
|
+
* Stored so that schemas registered after providers can still receive provider values.
|
|
68
|
+
* Later-registered providers overwrite earlier ones for the same key.
|
|
69
|
+
*/
|
|
70
|
+
_providerRawData = new Map();
|
|
71
|
+
/**
|
|
72
|
+
* Validated provider values, ready for merging into the resolved config.
|
|
73
|
+
* Populated from _providerRawData whenever a schema is registered or a provider is added.
|
|
74
|
+
*/
|
|
75
|
+
_providerValues = new Map();
|
|
76
|
+
/**
|
|
77
|
+
* Maps env-var prefix strings to their section names for save-entry construction.
|
|
78
|
+
* Example: 'KEYCLOAK_' → 'KEYCLOAK'
|
|
79
|
+
*/
|
|
80
|
+
_namespaces = new Map();
|
|
81
|
+
/**
|
|
82
|
+
* Cache for the resolved _Data map to avoid rebuilding on every access.
|
|
83
|
+
* Invalidated whenever data is mutated (Set, Register, Reset, RegisterProvider).
|
|
84
|
+
*/
|
|
85
|
+
_dataCache = null;
|
|
86
|
+
/**
|
|
87
|
+
* Cache for parsed configuration values, keyed by configuration key.
|
|
88
|
+
* Avoids re-parsing the same value on repeated Get() calls.
|
|
89
|
+
*/
|
|
90
|
+
_parsedCache = new Map();
|
|
91
|
+
/**
|
|
92
|
+
* Cache for schema metadata (isSecret and description).
|
|
93
|
+
* Populated at Register() time to avoid traversing the schema chain on every Save() call.
|
|
94
|
+
*/
|
|
95
|
+
_schemaMetaCache = new Map();
|
|
96
|
+
/**
|
|
97
|
+
* Optional handler for provider validation warnings.
|
|
98
|
+
* If set, called when a provider value fails schema validation.
|
|
99
|
+
* If not set, validation failures are silent (no-op).
|
|
100
|
+
*/
|
|
101
|
+
_validationWarningHandler;
|
|
102
|
+
/**
|
|
103
|
+
* Lookup table: key → { section, field }
|
|
104
|
+
* Built at Register() time to optimize Save() performance.
|
|
105
|
+
*/
|
|
106
|
+
_keyLookup = new Map();
|
|
107
|
+
/**
|
|
108
|
+
* Resolved configuration data, computed from defaults, provider values, and overrides.
|
|
109
|
+
*/
|
|
110
|
+
get _Data() {
|
|
111
|
+
if (this._dataCache !== null) {
|
|
112
|
+
return this._dataCache;
|
|
113
|
+
}
|
|
90
114
|
const resolved = new Map(this._DataDefaults);
|
|
115
|
+
for (const [key, value] of this._providerValues) {
|
|
116
|
+
resolved.set(key, value);
|
|
117
|
+
}
|
|
91
118
|
for (const [key, value] of this._DataOverrides) {
|
|
92
119
|
resolved.set(key, value);
|
|
93
120
|
}
|
|
121
|
+
this._dataCache = resolved;
|
|
94
122
|
return resolved;
|
|
95
123
|
}
|
|
96
124
|
/**
|
|
97
|
-
* Reset
|
|
98
|
-
* @internal
|
|
125
|
+
* Reset this state (for testing).
|
|
99
126
|
*/
|
|
100
|
-
|
|
127
|
+
Reset() {
|
|
101
128
|
this._Schemas.clear();
|
|
102
129
|
this._DataDefaults.clear();
|
|
130
|
+
this._providerRawData.clear();
|
|
131
|
+
this._providerValues.clear();
|
|
103
132
|
this._DataOverrides.clear();
|
|
133
|
+
this._namespaces.clear();
|
|
134
|
+
this._dataCache = null;
|
|
135
|
+
this._parsedCache.clear();
|
|
136
|
+
this._schemaMetaCache.clear();
|
|
137
|
+
this._keyLookup.clear();
|
|
138
|
+
this._validationWarningHandler = undefined;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Set an optional handler for provider validation warnings.
|
|
142
|
+
* When a provider value fails schema validation, if a handler is set,
|
|
143
|
+
* it will be called with the key and provider name.
|
|
144
|
+
* If no handler is set, validation failures are silent.
|
|
145
|
+
*
|
|
146
|
+
* @param handler - Function to call on validation failure, or undefined to clear
|
|
147
|
+
*/
|
|
148
|
+
SetValidationWarningHandler(handler) {
|
|
149
|
+
this._validationWarningHandler = handler;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Register a configuration namespace for use when building save entries.
|
|
153
|
+
*
|
|
154
|
+
* Records the mapping from `prefix` to `sectionName` so that
|
|
155
|
+
* {@link Save} can split fully-qualified keys into section and field
|
|
156
|
+
* components (e.g. `KEYCLOAK_HOST` → section `KEYCLOAK`, field `HOST`).
|
|
157
|
+
*
|
|
158
|
+
* @param name - Human-readable namespace name (e.g. `'Keycloak'`)
|
|
159
|
+
* @param prefix - Derived environment variable prefix (e.g. `'KEYCLOAK_'`)
|
|
160
|
+
*/
|
|
161
|
+
RegisterNamespace(name, prefix) {
|
|
162
|
+
this._namespaces.set(prefix, name.toUpperCase());
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Save all registered configuration values via a provider.
|
|
166
|
+
*
|
|
167
|
+
* Builds a {@link ConfigSaveEntry} for every registered schema key, then
|
|
168
|
+
* delegates formatting and file I/O to `provider.Save()`.
|
|
169
|
+
*
|
|
170
|
+
* In template mode (`useCurrentValues: false`, the default), each entry
|
|
171
|
+
* carries the registered default value. In current-values mode
|
|
172
|
+
* (`useCurrentValues: true`), each entry carries the fully resolved live
|
|
173
|
+
* value (DEFAULT → provider values → OVERRIDE). The `isSecret` flag is
|
|
174
|
+
* set for fields marked with {@link Secret}; providers are expected to
|
|
175
|
+
* redact those appropriately in template mode.
|
|
176
|
+
*
|
|
177
|
+
* @param provider - A {@link IConfigProvider} that handles the write
|
|
178
|
+
* @param options - Output path and save mode
|
|
179
|
+
* @returns - A promise that resolves when the save operation completes
|
|
180
|
+
* @remarks In `useCurrentValues` mode, keys that cannot be resolved (not set, not registered, or failing validation) are written as blank/undefined without throwing.
|
|
181
|
+
*/
|
|
182
|
+
async Save(provider, options) {
|
|
183
|
+
const useCurrentValues = options.useCurrentValues ?? false;
|
|
184
|
+
const entries = [];
|
|
185
|
+
for (const [key] of this._Schemas) {
|
|
186
|
+
const meta = this._schemaMetaCache.get(key);
|
|
187
|
+
const isSecret = meta?.isSecret ?? false;
|
|
188
|
+
const description = meta?.description;
|
|
189
|
+
const lookup = this._keyLookup.get(key);
|
|
190
|
+
const section = lookup?.section ?? '';
|
|
191
|
+
const field = lookup?.field ?? key;
|
|
192
|
+
let value;
|
|
193
|
+
if (useCurrentValues) {
|
|
194
|
+
try {
|
|
195
|
+
value = this.Get(key);
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
if (e instanceof ConfigNotSetError
|
|
199
|
+
|| e instanceof ConfigNotRegisteredError) {
|
|
200
|
+
value = undefined;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
throw e;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
value = this._DataDefaults.get(key);
|
|
209
|
+
}
|
|
210
|
+
entries.push({ key, section, field, value, isSecret, description });
|
|
211
|
+
}
|
|
212
|
+
await provider.Save(entries, options);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Register a configuration value provider.
|
|
216
|
+
*
|
|
217
|
+
* Immediately calls `provider.Load()` to obtain all key/value pairs from the
|
|
218
|
+
* provider. All raw values are stored in the internal raw-data cache so that
|
|
219
|
+
* schemas registered after this call can still receive provider values.
|
|
220
|
+
* For any key that already has a registered schema, the raw value is validated
|
|
221
|
+
* and the validated result is stored in the provider values tier.
|
|
222
|
+
*
|
|
223
|
+
* Provider values occupy the middle precedence tier: they override registered
|
|
224
|
+
* defaults but are themselves overridden by explicit {@link Set} calls.
|
|
225
|
+
* When multiple providers supply the same key, the last-registered provider wins.
|
|
226
|
+
*
|
|
227
|
+
* @param provider - The {@link IConfigProvider} implementation to register
|
|
228
|
+
* @returns - A promise that resolves when registration completes
|
|
229
|
+
*/
|
|
230
|
+
async RegisterProvider(provider) {
|
|
231
|
+
const values = await provider.Load();
|
|
232
|
+
for (const [key, rawValue] of Object.entries(values)) {
|
|
233
|
+
// Always cache raw value — needed for schemas registered after this provider
|
|
234
|
+
this._providerRawData.set(key, rawValue);
|
|
235
|
+
// If schema already registered, validate and cache the typed value now
|
|
236
|
+
const schema = this._Schemas.get(key);
|
|
237
|
+
if (schema === undefined)
|
|
238
|
+
continue;
|
|
239
|
+
const result = schema.safeParse(rawValue);
|
|
240
|
+
if (!result.success) {
|
|
241
|
+
if (this._validationWarningHandler) {
|
|
242
|
+
this._validationWarningHandler(key, provider.Name);
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
// Safe: safeParse succeeds only if result.data matches schema's inferred type,
|
|
247
|
+
// which was validated at Register to be TConfigValueTypes
|
|
248
|
+
this._providerValues.set(key, result.data);
|
|
249
|
+
}
|
|
250
|
+
this._dataCache = null;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Register a synchronous configuration provider.
|
|
254
|
+
* Use this only in contexts that cannot `await`. Most code should prefer
|
|
255
|
+
* `RegisterProvider()` with an async provider.
|
|
256
|
+
*
|
|
257
|
+
* Immediately calls `provider.LoadSync()` to obtain all key/value pairs from the
|
|
258
|
+
* provider. All raw values are stored in the internal raw-data cache so that
|
|
259
|
+
* schemas registered after this call can still receive provider values.
|
|
260
|
+
* For any key that already has a registered schema, the raw value is validated
|
|
261
|
+
* and the validated result is stored in the provider values tier.
|
|
262
|
+
*
|
|
263
|
+
* @param provider - The {@link ISyncConfigProvider} implementation to register
|
|
264
|
+
*/
|
|
265
|
+
RegisterSyncProvider(provider) {
|
|
266
|
+
const values = provider.LoadSync();
|
|
267
|
+
for (const [key, rawValue] of Object.entries(values)) {
|
|
268
|
+
// Always cache raw value — needed for schemas registered after this provider
|
|
269
|
+
this._providerRawData.set(key, rawValue);
|
|
270
|
+
// If schema already registered, validate and cache the typed value now
|
|
271
|
+
const schema = this._Schemas.get(key);
|
|
272
|
+
if (schema === undefined)
|
|
273
|
+
continue;
|
|
274
|
+
const result = schema.safeParse(rawValue);
|
|
275
|
+
if (!result.success) {
|
|
276
|
+
if (this._validationWarningHandler) {
|
|
277
|
+
this._validationWarningHandler(key, provider.Name);
|
|
278
|
+
}
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
this._providerValues.set(key, result.data);
|
|
282
|
+
}
|
|
283
|
+
this._dataCache = null;
|
|
104
284
|
}
|
|
105
|
-
constructor() { }
|
|
106
285
|
/**
|
|
107
286
|
* Register a configuration schema.
|
|
287
|
+
*
|
|
108
288
|
* @param key - Unique configuration key
|
|
109
289
|
* @param schema - Zod schema for runtime validation
|
|
110
290
|
* @param defaultValue - Initial value for the configuration key, must satisfy the schema
|
|
111
|
-
* @throws {
|
|
112
|
-
* @
|
|
113
|
-
* ConfigManager.Register('PORT', z.coerce.number().positive(), 3000);
|
|
114
|
-
* ConfigManager.Register('JWT_SECRET', z.string().min(32), 'default-secret');
|
|
291
|
+
* @throws {ConfigRegistrationError} If key is already registered with a different schema
|
|
292
|
+
* @throws {ConfigValidationError} If defaultValue does not match the schema
|
|
115
293
|
*/
|
|
116
|
-
|
|
294
|
+
Register(key, schema, defaultValue) {
|
|
117
295
|
// Ensure key is unique, but it's fine when the schemas match.
|
|
118
296
|
if (this._Schemas.has(key) && this._Schemas.get(key) !== schema)
|
|
119
|
-
throw new
|
|
297
|
+
throw new ConfigRegistrationError(key);
|
|
120
298
|
const result = schema.safeParse(defaultValue);
|
|
121
|
-
if (!result.success)
|
|
122
|
-
|
|
299
|
+
if (!result.success) {
|
|
300
|
+
const isSecret = IsMarkedSecret(schema);
|
|
301
|
+
const message = isSecret ? 'Default value does not match the provided schema (value redacted for security).' : 'Default value does not match the provided schema.';
|
|
302
|
+
const options = isSecret ? undefined : { cause: result.error };
|
|
303
|
+
throw new ConfigValidationError(key, message, options);
|
|
304
|
+
}
|
|
123
305
|
const parsed = result.data;
|
|
306
|
+
// Deep-clone mutable types to prevent caller mutation
|
|
307
|
+
const clonedDefault = structuredClone(parsed);
|
|
124
308
|
this._Schemas.set(key, schema);
|
|
125
|
-
|
|
309
|
+
// Safe: clonedDefault is the parsed result of schema.safeParse(), which succeeded
|
|
310
|
+
this._DataDefaults.set(key, clonedDefault);
|
|
311
|
+
this._dataCache = null;
|
|
312
|
+
this._parsedCache.delete(key);
|
|
313
|
+
// Cache schema metadata for use in Save()
|
|
314
|
+
this._schemaMetaCache.set(key, {
|
|
315
|
+
isSecret: IsMarkedSecret(schema),
|
|
316
|
+
description: GetFieldDescription(schema)
|
|
317
|
+
});
|
|
318
|
+
// Precompute section/field lookup for Save() optimization
|
|
319
|
+
let section = '';
|
|
320
|
+
let field = key;
|
|
321
|
+
for (const [prefix, sectionName] of this._namespaces) {
|
|
322
|
+
if (key.startsWith(prefix)) {
|
|
323
|
+
section = sectionName;
|
|
324
|
+
field = key.slice(prefix.length);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
this._keyLookup.set(key, { section, field });
|
|
329
|
+
// Apply any provider value already loaded for this key
|
|
330
|
+
if (this._providerRawData.has(key)) {
|
|
331
|
+
const rawProviderValue = this._providerRawData.get(key);
|
|
332
|
+
const providerResult = schema.safeParse(rawProviderValue);
|
|
333
|
+
if (providerResult.success) {
|
|
334
|
+
// Safe: safeParse succeeds only if providerResult.data matches schema's type
|
|
335
|
+
this._providerValues.set(key, providerResult.data);
|
|
336
|
+
this._dataCache = null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
126
339
|
}
|
|
127
340
|
/**
|
|
128
341
|
* Set a configuration value and validate against its schema.
|
|
342
|
+
*
|
|
129
343
|
* @param key - Configuration key
|
|
130
344
|
* @param value - Value to set and validate
|
|
131
345
|
* @param target - Whether to set the default store or the override store; defaults to `'OVERRIDE'`
|
|
132
|
-
* @throws {
|
|
133
|
-
* @throws {
|
|
134
|
-
* @
|
|
135
|
-
*
|
|
136
|
-
* manager.set('JWT_SECRET', process.env.SECRET);
|
|
346
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered for key
|
|
347
|
+
* @throws {ConfigValidationError} If validation fails
|
|
348
|
+
* @remarks
|
|
349
|
+
* When validation fails for a field marked with `Secret()`, the error message and error cause are sanitized to prevent secret values from appearing in error logs or stack traces.
|
|
137
350
|
*/
|
|
138
|
-
|
|
351
|
+
Set(key, value, target = 'OVERRIDE') {
|
|
139
352
|
const schema = this._Schemas.get(key);
|
|
140
353
|
if (!schema)
|
|
141
|
-
throw new
|
|
354
|
+
throw new ConfigNotRegisteredError(key);
|
|
142
355
|
try {
|
|
143
356
|
const parsed = schema.parse(value);
|
|
144
|
-
|
|
357
|
+
// Safe: Register validates that this schema produces TConfigValueTypes at runtime
|
|
145
358
|
if (target === 'DEFAULT') {
|
|
146
359
|
this._DataDefaults.set(key, parsed);
|
|
147
360
|
}
|
|
148
361
|
else {
|
|
149
362
|
this._DataOverrides.set(key, parsed);
|
|
150
363
|
}
|
|
364
|
+
this._dataCache = null;
|
|
365
|
+
this._parsedCache.delete(key);
|
|
151
366
|
}
|
|
152
367
|
catch (cause) {
|
|
153
|
-
|
|
368
|
+
const isSecret = this._schemaMetaCache.get(key)?.isSecret ?? false;
|
|
369
|
+
const message = isSecret ? 'value failed validation (value redacted for security)' : GetErrorMessage(cause);
|
|
370
|
+
const options = isSecret ? undefined : (cause instanceof Error ? cause : undefined);
|
|
371
|
+
throw new ConfigValidationError(key, message, options ? { cause: options } : undefined);
|
|
154
372
|
}
|
|
155
373
|
}
|
|
156
374
|
/**
|
|
157
375
|
* Retrieve a configuration value by key.
|
|
376
|
+
*
|
|
158
377
|
* Returns the value parsed by its registered schema.
|
|
378
|
+
*
|
|
159
379
|
* @param key - Configuration key
|
|
160
380
|
* @param source - Optional — filter to a specific store (`'DEFAULT'` or `'OVERRIDE'`); omit to return the resolved value (overrides take precedence over defaults)
|
|
161
381
|
* @returns The typed configuration value
|
|
162
|
-
* @throws {
|
|
163
|
-
* @throws {
|
|
164
|
-
* @throws {
|
|
165
|
-
* @example
|
|
166
|
-
* const port = manager.get('PORT'); // Returns number, guaranteed by schema
|
|
167
|
-
* const secret = manager.get('JWT_SECRET'); // Returns string
|
|
382
|
+
* @throws {ConfigNotSetError} If value was not set
|
|
383
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered
|
|
384
|
+
* @throws {ConfigValidationError} If validation fails on retrieval
|
|
168
385
|
*/
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
386
|
+
Get(key, source) {
|
|
387
|
+
let dataSource;
|
|
388
|
+
switch (source) {
|
|
389
|
+
case 'DEFAULT':
|
|
390
|
+
dataSource = this._DataDefaults;
|
|
391
|
+
break;
|
|
392
|
+
case 'OVERRIDE':
|
|
393
|
+
dataSource = this._DataOverrides;
|
|
394
|
+
break;
|
|
395
|
+
default:
|
|
396
|
+
dataSource = this._Data;
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
if (!dataSource.has(key))
|
|
400
|
+
throw new ConfigNotSetError(key);
|
|
401
|
+
// Safe: has() guard above guarantees presence; stored values match schema types
|
|
402
|
+
const value = dataSource.get(key);
|
|
403
|
+
// Only use parsed cache for resolved (non-source-filtered) values
|
|
404
|
+
if (source === undefined && this._parsedCache.has(key)) {
|
|
405
|
+
// has() guard above guarantees presence — use type assertion instead of ! (ESLint: no-non-null-assertion)
|
|
406
|
+
return this._parsedCache.get(key);
|
|
407
|
+
}
|
|
173
408
|
const schema = this.GetSchema(key);
|
|
174
409
|
try {
|
|
175
|
-
// TODO: This will re-parse the value on every retrieval, which may have performance implications. Consider caching parsed values if this becomes an issue.
|
|
176
|
-
// TODO: Also consider how to handle complex types that may not be easily represented as strings in ENV or CLI (e.g. arrays, objects). We may need to support custom parsing logic or conventions for these cases.
|
|
177
410
|
const rvalue = schema.parse(value);
|
|
178
|
-
|
|
411
|
+
// Safe: Register validates that this schema produces TConfigValueTypes at runtime
|
|
412
|
+
// Cache the parsed result (only for resolved values, not source-filtered)
|
|
413
|
+
if (source === undefined) {
|
|
414
|
+
this._parsedCache.set(key, rvalue);
|
|
415
|
+
}
|
|
179
416
|
return rvalue;
|
|
180
417
|
}
|
|
181
418
|
catch (cause) {
|
|
182
|
-
|
|
419
|
+
const isSecret = this._schemaMetaCache.get(key)?.isSecret ?? false;
|
|
420
|
+
if (isSecret) {
|
|
421
|
+
throw new ConfigValidationError(key, 'value failed validation (value redacted for security)');
|
|
422
|
+
}
|
|
423
|
+
throw new ConfigValidationError(key, GetErrorMessage(cause), cause instanceof Error ? { cause } : undefined);
|
|
183
424
|
}
|
|
184
425
|
}
|
|
185
426
|
/**
|
|
186
427
|
* Retrieve the schema for a configuration key.
|
|
428
|
+
*
|
|
187
429
|
* @param key - Configuration key
|
|
188
430
|
* @returns The Zod schema for this configuration
|
|
189
|
-
* @throws {
|
|
190
|
-
* @example
|
|
191
|
-
* const schema = manager.getSchema('PORT');
|
|
192
|
-
* const parsed = schema.safeParse(value);
|
|
431
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered for key
|
|
193
432
|
*/
|
|
194
|
-
|
|
433
|
+
GetSchema(key) {
|
|
195
434
|
const schema = this._Schemas.get(key);
|
|
196
435
|
if (!schema)
|
|
197
|
-
throw new
|
|
436
|
+
throw new ConfigNotRegisteredError(key);
|
|
198
437
|
return schema;
|
|
199
438
|
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Runtime configuration manager with Zod schema validation.
|
|
442
|
+
* Provides a singleton instance to register and retrieve typed configuration values.
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```typescript
|
|
446
|
+
* ConfigManager.Register('DATABASE_URL', z.string().url(), 'postgresql://localhost/mydb');
|
|
447
|
+
* ConfigManager.Set('DATABASE_URL', 'postgresql://localhost/mydb');
|
|
448
|
+
* const url = ConfigManager.Get('DATABASE_URL');
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
export class ConfigManager {
|
|
452
|
+
static _state = new CoreConfigState();
|
|
453
|
+
/**
|
|
454
|
+
* Reset the singleton instance (for testing).
|
|
455
|
+
*
|
|
456
|
+
* @internal
|
|
457
|
+
*/
|
|
458
|
+
static Reset() {
|
|
459
|
+
this._state.Reset();
|
|
460
|
+
}
|
|
200
461
|
/**
|
|
201
|
-
*
|
|
462
|
+
* Set an optional handler for provider validation warnings.
|
|
202
463
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* In current-values mode, the live resolved value from `Get(key)` is used; keys that are
|
|
207
|
-
* unset or produce a configuration error are emitted as commented-out blank lines (`# KEY=`).
|
|
464
|
+
* When a provider value fails schema validation, if a handler is set,
|
|
465
|
+
* it will be called with the key and provider name. If no handler is set,
|
|
466
|
+
* validation failures are silent (no-op).
|
|
208
467
|
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
468
|
+
* @param handler - Function to call on validation failure, or undefined to clear
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* ConfigManager.SetValidationWarningHandler((key, providerName) => {
|
|
472
|
+
* console.warn(`Provider ${providerName} failed for key ${key}`);
|
|
473
|
+
* });
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
static SetValidationWarningHandler(handler) {
|
|
477
|
+
this._state.SetValidationWarningHandler(handler);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Register a configuration namespace for use when building save entries.
|
|
211
481
|
*
|
|
212
|
-
*
|
|
213
|
-
* @
|
|
214
|
-
*
|
|
215
|
-
* @param options.path - When provided, write the generated string to this file path in UTF-8.
|
|
216
|
-
* @returns - The generated `.env` content as a string.
|
|
482
|
+
* Records the mapping from `prefix` to `sectionName` so that
|
|
483
|
+
* {@link Save} can split fully-qualified keys into section and field
|
|
484
|
+
* components (e.g. `KEYCLOAK_HOST` → section `KEYCLOAK`, field `HOST`).
|
|
217
485
|
*
|
|
486
|
+
* Called automatically by `RegisterConfigSchema` — applications do not
|
|
487
|
+
* normally need to call this directly.
|
|
488
|
+
*
|
|
489
|
+
* @param name - Human-readable namespace name (e.g. `'Keycloak'`)
|
|
490
|
+
* @param prefix - Derived environment variable prefix (e.g. `'KEYCLOAK_'`)
|
|
218
491
|
* @example
|
|
219
492
|
* ```typescript
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* // "APP_HOST=localhost\nAPP_PORT=3000\nAPP_SECRET_KEY="
|
|
493
|
+
* ConfigManager.RegisterNamespace('Keycloak', 'KEYCLOAK_');
|
|
494
|
+
* // KEYCLOAK_HOST → section='KEYCLOAK', field='HOST'
|
|
223
495
|
* ```
|
|
496
|
+
*/
|
|
497
|
+
static RegisterNamespace(name, prefix) {
|
|
498
|
+
this._state.RegisterNamespace(name, prefix);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Save all registered configuration values via a provider.
|
|
502
|
+
*
|
|
503
|
+
* Builds a {@link ConfigSaveEntry} for every registered schema key, then
|
|
504
|
+
* delegates formatting and file I/O to `provider.Save()`.
|
|
224
505
|
*
|
|
506
|
+
* In template mode (`useCurrentValues: false`, the default), each entry
|
|
507
|
+
* carries the registered default value. In current-values mode
|
|
508
|
+
* (`useCurrentValues: true`), each entry carries the fully resolved live
|
|
509
|
+
* value (DEFAULT → provider values → OVERRIDE). The `isSecret` flag is
|
|
510
|
+
* set for fields marked with {@link Secret}; providers are expected to
|
|
511
|
+
* redact those appropriately in template mode.
|
|
512
|
+
*
|
|
513
|
+
* @param provider - An {@link IConfigProvider} that handles the write
|
|
514
|
+
* @param options - Output path and save mode
|
|
515
|
+
* @returns - A promise that resolves when the save operation completes
|
|
516
|
+
* @remarks In `useCurrentValues` mode, keys that cannot be resolved (not set, not registered, or failing validation) are written as blank/undefined without throwing.
|
|
225
517
|
* @example
|
|
226
518
|
* ```typescript
|
|
227
|
-
* //
|
|
228
|
-
* ConfigManager.
|
|
519
|
+
* // Write .env.example (template, secrets blank)
|
|
520
|
+
* await ConfigManager.Save(envProvider, { path: '.env.example' });
|
|
521
|
+
*
|
|
522
|
+
* // Snapshot current runtime values
|
|
523
|
+
* await ConfigManager.Save(envProvider, { path: '.env', useCurrentValues: true });
|
|
229
524
|
* ```
|
|
230
525
|
*/
|
|
231
|
-
static
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
526
|
+
static async Save(provider, options) {
|
|
527
|
+
await this._state.Save(provider, options);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Register a configuration value provider with the manager.
|
|
531
|
+
*
|
|
532
|
+
* Immediately calls `provider.Load()` to obtain all key/value pairs from the
|
|
533
|
+
* provider. All raw values are stored in the internal raw-data cache so that
|
|
534
|
+
* schemas registered after this call can still receive provider values.
|
|
535
|
+
* For any key that already has a registered schema, the raw value is validated
|
|
536
|
+
* and the validated result is stored in the provider values tier.
|
|
537
|
+
*
|
|
538
|
+
* Provider values occupy the middle precedence tier: they override registered
|
|
539
|
+
* defaults but are themselves overridden by explicit {@link Set} calls.
|
|
540
|
+
* When multiple providers supply the same key, the last-registered provider wins.
|
|
541
|
+
*
|
|
542
|
+
* @param provider - An {@link IConfigProvider} implementation to register
|
|
543
|
+
* @returns - A promise that resolves when registration completes
|
|
544
|
+
* @example
|
|
545
|
+
* ```typescript
|
|
546
|
+
* import { ConfigEnvironmentProvider } from '@pawells/config-provider-env';
|
|
547
|
+
* import { ConfigJSONProvider } from '@pawells/config-provider-json';
|
|
548
|
+
*
|
|
549
|
+
* // Register before importing any schema modules
|
|
550
|
+
* await ConfigManager.RegisterProvider(await ConfigEnvironmentProvider.Register({ path: '.env' }));
|
|
551
|
+
* await ConfigManager.RegisterProvider(await ConfigJSONProvider.Register({ path: './config.json' }));
|
|
552
|
+
* ```
|
|
553
|
+
*/
|
|
554
|
+
static async RegisterProvider(provider) {
|
|
555
|
+
await this._state.RegisterProvider(provider);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Register a synchronous configuration provider with the manager.
|
|
559
|
+
* Use this only in contexts that cannot `await`. Most code should prefer
|
|
560
|
+
* `RegisterProvider()` with an async provider.
|
|
561
|
+
*
|
|
562
|
+
* Immediately calls `provider.LoadSync()` to obtain all key/value pairs from the
|
|
563
|
+
* provider. All raw values are stored in the internal raw-data cache so that
|
|
564
|
+
* schemas registered after this call can still receive provider values.
|
|
565
|
+
* For any key that already has a registered schema, the raw value is validated
|
|
566
|
+
* and the validated result is stored in the provider values tier.
|
|
567
|
+
*
|
|
568
|
+
* @param provider - An {@link ISyncConfigProvider} implementation to register
|
|
569
|
+
* @example
|
|
570
|
+
* ```typescript
|
|
571
|
+
* class MemoryProvider implements ISyncConfigProvider {
|
|
572
|
+
* readonly name = 'memory';
|
|
573
|
+
* LoadSync() {
|
|
574
|
+
* return { MY_KEY: 'value' };
|
|
575
|
+
* }
|
|
576
|
+
* }
|
|
577
|
+
* ConfigManager.RegisterSyncProvider(new MemoryProvider());
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
static RegisterSyncProvider(provider) {
|
|
581
|
+
this._state.RegisterSyncProvider(provider);
|
|
582
|
+
}
|
|
583
|
+
constructor() { }
|
|
584
|
+
/**
|
|
585
|
+
* Register a configuration schema.
|
|
586
|
+
*
|
|
587
|
+
* @param key - Unique configuration key
|
|
588
|
+
* @param schema - Zod schema for runtime validation
|
|
589
|
+
* @param defaultValue - Initial value for the configuration key, must satisfy the schema
|
|
590
|
+
* @throws {ConfigRegistrationError} If key is already registered with a different schema
|
|
591
|
+
* @throws {ConfigValidationError} If defaultValue does not match the schema
|
|
592
|
+
* @example
|
|
593
|
+
* ```typescript
|
|
594
|
+
* ConfigManager.Register('PORT', z.coerce.number().positive(), 3000);
|
|
595
|
+
* ConfigManager.Register('JWT_SECRET', z.string().min(32), 'default-secret');
|
|
596
|
+
* ```
|
|
597
|
+
*/
|
|
598
|
+
static Register(key, schema, defaultValue) {
|
|
599
|
+
this._state.Register(key, schema, defaultValue);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Set a configuration value and validate against its schema.
|
|
603
|
+
*
|
|
604
|
+
* @param key - Configuration key
|
|
605
|
+
* @param value - Value to set and validate
|
|
606
|
+
* @param target - Whether to set the default store or the override store; defaults to `'OVERRIDE'`
|
|
607
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered for key
|
|
608
|
+
* @throws {ConfigValidationError} If validation fails
|
|
609
|
+
* @remarks
|
|
610
|
+
* When validation fails for a field marked with `Secret()`, the error message and error cause are sanitized to prevent secret values from appearing in error logs or stack traces.
|
|
611
|
+
* @example
|
|
612
|
+
* ```typescript
|
|
613
|
+
* ConfigManager.Set('PORT', 3000);
|
|
614
|
+
* ConfigManager.Set('JWT_SECRET', process.env.SECRET);
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
static Set(key, value, target = 'OVERRIDE') {
|
|
618
|
+
this._state.Set(key, value, target);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Retrieve a configuration value by key.
|
|
622
|
+
*
|
|
623
|
+
* Returns the value parsed by its registered schema.
|
|
624
|
+
*
|
|
625
|
+
* @param key - Configuration key
|
|
626
|
+
* @param source - Optional — filter to a specific store (`'DEFAULT'` or `'OVERRIDE'`); omit to return the resolved value (overrides take precedence over defaults)
|
|
627
|
+
* @returns The typed configuration value
|
|
628
|
+
* @throws {ConfigNotSetError} If value was not set
|
|
629
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered
|
|
630
|
+
* @throws {ConfigValidationError} If validation fails on retrieval
|
|
631
|
+
* @example
|
|
632
|
+
* ```typescript
|
|
633
|
+
* const port = ConfigManager.Get('PORT'); // Returns number, guaranteed by schema
|
|
634
|
+
* const secret = ConfigManager.Get('JWT_SECRET'); // Returns string
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
static Get(key, source) {
|
|
638
|
+
return this._state.Get(key, source);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Retrieve the schema for a configuration key.
|
|
642
|
+
*
|
|
643
|
+
* @param key - Configuration key
|
|
644
|
+
* @returns The Zod schema for this configuration
|
|
645
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered for key
|
|
646
|
+
* @example
|
|
647
|
+
* ```typescript
|
|
648
|
+
* const schema = ConfigManager.GetSchema('PORT');
|
|
649
|
+
* const parsed = schema.safeParse(value);
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
static GetSchema(key) {
|
|
653
|
+
return this._state.GetSchema(key);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Instance-based configuration manager for test isolation and multi-tenant scenarios.
|
|
658
|
+
*
|
|
659
|
+
* Unlike the static singleton {@link ConfigManager}, `ScopedConfigManager` maintains
|
|
660
|
+
* independent state in instance fields. This enables isolated configuration contexts
|
|
661
|
+
* without affecting the global singleton or other instances.
|
|
662
|
+
*
|
|
663
|
+
* The public API mirrors `ConfigManager` exactly, but as instance methods instead of
|
|
664
|
+
* static methods. Use this when you need:
|
|
665
|
+
* - Test isolation: each test gets its own config instance
|
|
666
|
+
* - Multi-tenant scenarios: separate configs per tenant
|
|
667
|
+
* - Feature-gating: isolated experimental configs
|
|
668
|
+
*
|
|
669
|
+
* @example
|
|
670
|
+
* ```typescript
|
|
671
|
+
* // Two independent configurations
|
|
672
|
+
* const config1 = new ScopedConfigManager();
|
|
673
|
+
* const config2 = new ScopedConfigManager();
|
|
674
|
+
*
|
|
675
|
+
* config1.Register('PORT', z.coerce.number(), 3000);
|
|
676
|
+
* config2.Register('PORT', z.coerce.number(), 4000);
|
|
677
|
+
*
|
|
678
|
+
* config1.Get('PORT'); // 3000
|
|
679
|
+
* config2.Get('PORT'); // 4000 — independent state
|
|
680
|
+
* ```
|
|
681
|
+
*/
|
|
682
|
+
export class ScopedConfigManager {
|
|
683
|
+
_state;
|
|
684
|
+
constructor() {
|
|
685
|
+
this._state = new CoreConfigState();
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Reset this instance (for testing).
|
|
689
|
+
*/
|
|
690
|
+
Reset() {
|
|
691
|
+
this._state.Reset();
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Set an optional handler for provider validation warnings.
|
|
695
|
+
*
|
|
696
|
+
* When a provider value fails schema validation, if a handler is set,
|
|
697
|
+
* it will be called with the key and provider name. If no handler is set,
|
|
698
|
+
* validation failures are silent (no-op).
|
|
699
|
+
*
|
|
700
|
+
* @param handler - Function to call on validation failure, or undefined to clear
|
|
701
|
+
*/
|
|
702
|
+
SetValidationWarningHandler(handler) {
|
|
703
|
+
this._state.SetValidationWarningHandler(handler);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Register a configuration namespace for use when building save entries.
|
|
707
|
+
*
|
|
708
|
+
* Records the mapping from `prefix` to `sectionName` so that
|
|
709
|
+
* {@link Save} can split fully-qualified keys into section and field
|
|
710
|
+
* components (e.g. `KEYCLOAK_HOST` → section `KEYCLOAK`, field `HOST`).
|
|
711
|
+
*
|
|
712
|
+
* @param name - Human-readable namespace name (e.g. `'Keycloak'`)
|
|
713
|
+
* @param prefix - Derived environment variable prefix (e.g. `'KEYCLOAK_'`)
|
|
714
|
+
*/
|
|
715
|
+
RegisterNamespace(name, prefix) {
|
|
716
|
+
this._state.RegisterNamespace(name, prefix);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Save all registered configuration values via a provider.
|
|
720
|
+
*
|
|
721
|
+
* Builds a {@link ConfigSaveEntry} for every registered schema key, then
|
|
722
|
+
* delegates formatting and file I/O to `provider.Save()`.
|
|
723
|
+
*
|
|
724
|
+
* @param provider - An {@link IConfigProvider} that handles the write
|
|
725
|
+
* @param options - Output path and save mode
|
|
726
|
+
* @returns - A promise that resolves when the save operation completes
|
|
727
|
+
*/
|
|
728
|
+
async Save(provider, options) {
|
|
729
|
+
await this._state.Save(provider, options);
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Register a configuration value provider with this instance.
|
|
733
|
+
*
|
|
734
|
+
* @param provider - An {@link IConfigProvider} implementation to register
|
|
735
|
+
* @returns - A promise that resolves when registration completes
|
|
736
|
+
*/
|
|
737
|
+
async RegisterProvider(provider) {
|
|
738
|
+
await this._state.RegisterProvider(provider);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Register a synchronous configuration provider with this instance.
|
|
742
|
+
*
|
|
743
|
+
* Use this only in contexts that cannot `await`. Most code should prefer
|
|
744
|
+
* `RegisterProvider()` with an async provider.
|
|
745
|
+
*
|
|
746
|
+
* @param provider - An {@link ISyncConfigProvider} implementation to register
|
|
747
|
+
*/
|
|
748
|
+
RegisterSyncProvider(provider) {
|
|
749
|
+
this._state.RegisterSyncProvider(provider);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Register a configuration schema.
|
|
753
|
+
*
|
|
754
|
+
* @param key - Unique configuration key
|
|
755
|
+
* @param schema - Zod schema for runtime validation
|
|
756
|
+
* @param defaultValue - Initial value for the configuration key, must satisfy the schema
|
|
757
|
+
* @throws {ConfigRegistrationError} If key is already registered with a different schema
|
|
758
|
+
* @throws {ConfigValidationError} If defaultValue does not match the schema
|
|
759
|
+
*/
|
|
760
|
+
Register(key, schema, defaultValue) {
|
|
761
|
+
this._state.Register(key, schema, defaultValue);
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Set a configuration value and validate against its schema.
|
|
765
|
+
*
|
|
766
|
+
* @param key - Configuration key
|
|
767
|
+
* @param value - Value to set and validate
|
|
768
|
+
* @param target - Whether to set the default store or the override store; defaults to `'OVERRIDE'`
|
|
769
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered for key
|
|
770
|
+
* @throws {ConfigValidationError} If validation fails
|
|
771
|
+
* @remarks
|
|
772
|
+
* When validation fails for a field marked with `Secret()`, the error message and error cause are sanitized to prevent secret values from appearing in error logs or stack traces.
|
|
773
|
+
*/
|
|
774
|
+
Set(key, value, target = 'OVERRIDE') {
|
|
775
|
+
this._state.Set(key, value, target);
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Retrieve a configuration value by key.
|
|
779
|
+
*
|
|
780
|
+
* Returns the value parsed by its registered schema.
|
|
781
|
+
*
|
|
782
|
+
* @param key - Configuration key
|
|
783
|
+
* @param source - Optional — filter to a specific store (`'DEFAULT'` or `'OVERRIDE'`); omit to return the resolved value (overrides take precedence over defaults)
|
|
784
|
+
* @returns The typed configuration value
|
|
785
|
+
* @throws {ConfigNotSetError} If value was not set
|
|
786
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered
|
|
787
|
+
* @throws {ConfigValidationError} If validation fails on retrieval
|
|
788
|
+
*/
|
|
789
|
+
Get(key, source) {
|
|
790
|
+
return this._state.Get(key, source);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Retrieve the schema for a configuration key.
|
|
794
|
+
*
|
|
795
|
+
* @param key - Configuration key
|
|
796
|
+
* @returns The Zod schema for this configuration
|
|
797
|
+
* @throws {ConfigNotRegisteredError} If schema is not registered for key
|
|
798
|
+
*/
|
|
799
|
+
GetSchema(key) {
|
|
800
|
+
return this._state.GetSchema(key);
|
|
271
801
|
}
|
|
272
802
|
}
|
|
803
|
+
//# sourceMappingURL=manager.js.map
|