@bymax-one/nest-config 1.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/CHANGELOG.md +106 -0
- package/LICENSE +21 -0
- package/README.md +572 -0
- package/dist/index.cjs +34 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/internal/index.cjs +423 -0
- package/dist/internal/index.d.cts +571 -0
- package/dist/internal/index.d.ts +571 -0
- package/dist/internal/index.mjs +415 -0
- package/dist/testing/index.cjs +171 -0
- package/dist/testing/index.d.cts +88 -0
- package/dist/testing/index.d.ts +88 -0
- package/dist/testing/index.mjs +168 -0
- package/package.json +161 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
+
import { DynamicModule } from '@nestjs/common';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @fileoverview Foundational schema shape types for the two-level environment
|
|
7
|
+
* configuration convention: top-level keys are namespaces (Zod object schemas)
|
|
8
|
+
* whose leaves are the individual environment-derived values. Everything the
|
|
9
|
+
* package builds (the `defineEnv` factory, the source-name mapping, the
|
|
10
|
+
* validator, and the `ConfigService` dot-path accessor) is expressed in terms
|
|
11
|
+
* of these types.
|
|
12
|
+
* @layer Types
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A single environment-derived leaf schema.
|
|
17
|
+
*
|
|
18
|
+
* Any Zod type may be a leaf (`z.string()`, `z.coerce.number()`, `z.enum(...)`,
|
|
19
|
+
* and so on). Leaves live one level below a namespace and never at the top
|
|
20
|
+
* level of a shape.
|
|
21
|
+
*/
|
|
22
|
+
type EnvLeaf = z.ZodType;
|
|
23
|
+
/**
|
|
24
|
+
* A configuration namespace: a Zod object schema that groups related leaves
|
|
25
|
+
* under one top-level key (for example `server`, `database`, or `auth`).
|
|
26
|
+
*/
|
|
27
|
+
type EnvNamespace = z.ZodObject;
|
|
28
|
+
/**
|
|
29
|
+
* The shape accepted by `defineEnv`.
|
|
30
|
+
*
|
|
31
|
+
* Encodes the two-level convention as a record of namespace keys, each mapped
|
|
32
|
+
* to a Zod object schema. A bare leaf at the top level is intentionally not
|
|
33
|
+
* assignable, which keeps the namespace boundary explicit and greppable.
|
|
34
|
+
*/
|
|
35
|
+
type EnvShape = Record<string, EnvNamespace>;
|
|
36
|
+
/**
|
|
37
|
+
* The parsed, validated output type produced by a shape.
|
|
38
|
+
*
|
|
39
|
+
* Mirrors what Zod infers for `z.object(shape)`, so applied coercions and
|
|
40
|
+
* defaults are reflected exactly. Used to type the `infer` phantom accessor on
|
|
41
|
+
* the schema returned by `defineEnv`.
|
|
42
|
+
*
|
|
43
|
+
* @typeParam TShape - The env shape whose composed output is inferred.
|
|
44
|
+
*/
|
|
45
|
+
type EnvOutput<TShape extends EnvShape> = z.infer<z.ZodObject<TShape>>;
|
|
46
|
+
/**
|
|
47
|
+
* The schema returned by `defineEnv`.
|
|
48
|
+
*
|
|
49
|
+
* The composed Zod object augmented with a phantom `infer` accessor that
|
|
50
|
+
* carries the inferred configuration type for ergonomic extraction via
|
|
51
|
+
* `typeof schema.infer`. The property exists only in the type system; reading
|
|
52
|
+
* it at runtime is not part of the contract.
|
|
53
|
+
*
|
|
54
|
+
* @typeParam TShape - The env shape the schema was composed from.
|
|
55
|
+
*/
|
|
56
|
+
type EnvSchema<TShape extends EnvShape = EnvShape> = z.ZodObject<TShape> & {
|
|
57
|
+
/** Phantom accessor exposing the inferred config type; use `typeof schema.infer`. */
|
|
58
|
+
readonly infer: EnvOutput<TShape>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* The union of every valid `namespace.leaf` dot-path for a config object.
|
|
62
|
+
*
|
|
63
|
+
* Given a parsed config type whose top-level keys are namespaces and whose
|
|
64
|
+
* second-level keys are leaves, this resolves to the union of `` `${namespace}.${leaf}` ``
|
|
65
|
+
* strings. Inference is deliberately fixed at exactly two levels (namespace and
|
|
66
|
+
* leaf) with no recursive conditional types, so the compiler cost stays flat
|
|
67
|
+
* even for large schemas. Deeper nesting stays typed through `getAll()` but is
|
|
68
|
+
* intentionally not addressable through a dot-path; this is a documented limit
|
|
69
|
+
* of the two-level convention, not an oversight.
|
|
70
|
+
*
|
|
71
|
+
* @typeParam TConfig - The parsed, two-level configuration object type.
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* type AppConfig = { server: { port: number }; database: { url: string } };
|
|
75
|
+
* type Paths = Path<AppConfig>; // 'server.port' | 'database.url'
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
type Path<TConfig> = {
|
|
79
|
+
[Namespace in keyof TConfig & string]: TConfig[Namespace] extends Record<string, unknown> ? {
|
|
80
|
+
[Leaf in keyof TConfig[Namespace] & string]: `${Namespace}.${Leaf}`;
|
|
81
|
+
}[keyof TConfig[Namespace] & string] : never;
|
|
82
|
+
}[keyof TConfig & string];
|
|
83
|
+
/**
|
|
84
|
+
* The leaf value type addressed by a `namespace.leaf` path.
|
|
85
|
+
*
|
|
86
|
+
* Splits the path on its single dot and walks exactly the two fixed levels to
|
|
87
|
+
* resolve the leaf type, so `PathValue<AppConfig, 'server.port'>` is `number`.
|
|
88
|
+
* The split is a single template-literal inference with no recursion, matching
|
|
89
|
+
* the flat-cost guarantee of {@link Path}.
|
|
90
|
+
*
|
|
91
|
+
* @typeParam TConfig - The parsed, two-level configuration object type.
|
|
92
|
+
* @typeParam TPath - A path drawn from {@link Path}<TConfig>.
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* type AppConfig = { server: { port: number }; database: { url: string } };
|
|
96
|
+
* type Port = PathValue<AppConfig, 'server.port'>; // number
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
type PathValue<TConfig, TPath extends Path<TConfig>> = TPath extends `${infer Namespace}.${infer Leaf}` ? Namespace extends keyof TConfig ? Leaf extends keyof TConfig[Namespace] ? TConfig[Namespace][Leaf] : never : never : never;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @fileoverview Value-free error model for the configuration validation
|
|
103
|
+
* pipeline: the frozen issue-code catalog, the ConfigIssue shape, and the
|
|
104
|
+
* BymaxConfigValidationError aggregate. The error carries every violation as a
|
|
105
|
+
* structured, immutable issue list and renders a multi-line report that never
|
|
106
|
+
* echoes a raw source value.
|
|
107
|
+
* @layer Error
|
|
108
|
+
*/
|
|
109
|
+
/**
|
|
110
|
+
* Frozen catalog of stable, machine-readable configuration error codes.
|
|
111
|
+
*
|
|
112
|
+
* `VALIDATION` is the top-level code carried by the thrown error; the remaining
|
|
113
|
+
* three classify individual issues. Exported once and referenced everywhere so
|
|
114
|
+
* no code string is ever inlined at a use site.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* if (issue.code === ConfigErrorCode.MISSING) {
|
|
119
|
+
* // handle an absent required variable
|
|
120
|
+
* }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
declare const ConfigErrorCode: Readonly<{
|
|
124
|
+
/** Top-level error code: one or more schema violations were collected. */
|
|
125
|
+
readonly VALIDATION: "BYMAX_CONFIG_VALIDATION";
|
|
126
|
+
/** Issue code: a required variable is absent from the source. */
|
|
127
|
+
readonly MISSING: "BYMAX_CONFIG_MISSING";
|
|
128
|
+
/** Issue code: a variable is present but violates its constraint. */
|
|
129
|
+
readonly INVALID: "BYMAX_CONFIG_INVALID";
|
|
130
|
+
/** Issue code: strict mode found a source variable matching no declared leaf. */
|
|
131
|
+
readonly UNKNOWN_KEY: "BYMAX_CONFIG_UNKNOWN_KEY";
|
|
132
|
+
}>;
|
|
133
|
+
/** The stable top-level code carried by {@link BymaxConfigValidationError}. */
|
|
134
|
+
type ConfigValidationCode = typeof ConfigErrorCode.VALIDATION;
|
|
135
|
+
/** Machine-readable code classifying a single {@link ConfigIssue}. */
|
|
136
|
+
type ConfigIssueCode = typeof ConfigErrorCode.MISSING | typeof ConfigErrorCode.INVALID | typeof ConfigErrorCode.UNKNOWN_KEY;
|
|
137
|
+
/**
|
|
138
|
+
* A single configuration violation, described entirely without any raw value.
|
|
139
|
+
*
|
|
140
|
+
* Every field is value-free by contract: `message` states the expected
|
|
141
|
+
* constraint, never the received input, so an issue can be logged or serialized
|
|
142
|
+
* without leaking a secret.
|
|
143
|
+
*/
|
|
144
|
+
interface ConfigIssue {
|
|
145
|
+
/** Nested config path, e.g. `database.url`; the namespace alone (e.g. `database`) for an unknown-key issue. */
|
|
146
|
+
readonly path: string;
|
|
147
|
+
/** Resolved environment variable name, e.g. `DATABASE_URL`. */
|
|
148
|
+
readonly variable: string;
|
|
149
|
+
/** Stable machine-readable classification. */
|
|
150
|
+
readonly code: ConfigIssueCode;
|
|
151
|
+
/** Human-readable constraint description, value-free. */
|
|
152
|
+
readonly message: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Aggregated, value-free configuration validation error.
|
|
156
|
+
*
|
|
157
|
+
* Thrown once at bootstrap when the source fails the schema. It carries every
|
|
158
|
+
* violation in an immutable, structured {@link ConfigIssue} list and a
|
|
159
|
+
* human-readable message that lists the offending variables and their expected
|
|
160
|
+
* constraints, never their received values.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* try {
|
|
165
|
+
* validateEnv(schema, source);
|
|
166
|
+
* } catch (error) {
|
|
167
|
+
* if (error instanceof BymaxConfigValidationError) {
|
|
168
|
+
* for (const issue of error.issues) report(issue.variable, issue.code);
|
|
169
|
+
* }
|
|
170
|
+
* }
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
declare class BymaxConfigValidationError extends Error {
|
|
174
|
+
/** The stable top-level error code. */
|
|
175
|
+
readonly code: ConfigValidationCode;
|
|
176
|
+
/** The immutable, aggregated list of collected violations. */
|
|
177
|
+
readonly issues: ReadonlyArray<ConfigIssue>;
|
|
178
|
+
/**
|
|
179
|
+
* Build the aggregated error from a list of value-free issues.
|
|
180
|
+
*
|
|
181
|
+
* @param issues - Every collected violation; copied and frozen so neither the
|
|
182
|
+
* caller nor consumer code can mutate the reported list.
|
|
183
|
+
*/
|
|
184
|
+
constructor(issues: ReadonlyArray<ConfigIssue>);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @fileoverview Public options contract for the BymaxConfigModule dynamic
|
|
189
|
+
* module: the schema to validate against, an optional raw source (defaulting to
|
|
190
|
+
* the process environment inside the provider factory), an optional value-free
|
|
191
|
+
* observability hook invoked before the fail-fast throw, and an optional strict
|
|
192
|
+
* unknown-key flag. This file declares only types and carries no runtime code.
|
|
193
|
+
* @layer Types
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Registration options for {@link BymaxConfigModule}.
|
|
198
|
+
*
|
|
199
|
+
* Passed to `forRoot` directly or produced by a `forRootAsync` factory. Only the
|
|
200
|
+
* schema is required; the remaining fields tune the source, observability, and
|
|
201
|
+
* strictness of the single bootstrap-time validation.
|
|
202
|
+
*
|
|
203
|
+
* @typeParam TSchema - The schema produced by `defineEnv`, carrying the inferred
|
|
204
|
+
* configuration type.
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* BymaxConfigModule.forRoot({ schema: envSchema, strict: true });
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
interface BymaxConfigModuleOptions<TSchema extends EnvSchema = EnvSchema> {
|
|
211
|
+
/** Schema produced by `defineEnv`. Required. */
|
|
212
|
+
readonly schema: TSchema;
|
|
213
|
+
/**
|
|
214
|
+
* Raw source record. Defaults to the process environment in the provider
|
|
215
|
+
* factory. Injectable for tests and tooling; a supplied source is used
|
|
216
|
+
* verbatim, with no environment fallback or merge.
|
|
217
|
+
*/
|
|
218
|
+
readonly source?: Record<string, string | undefined>;
|
|
219
|
+
/**
|
|
220
|
+
* Observability hook invoked once with the structured, value-free issue list
|
|
221
|
+
* immediately before the validation error is thrown. It is observability
|
|
222
|
+
* only and cannot suppress the failure: the error still propagates even when
|
|
223
|
+
* the hook itself throws.
|
|
224
|
+
*/
|
|
225
|
+
readonly onValidationError?: (issues: ReadonlyArray<ConfigIssue>) => void;
|
|
226
|
+
/**
|
|
227
|
+
* When true, source variables that match a declared namespace prefix but no
|
|
228
|
+
* declared leaf produce `BYMAX_CONFIG_UNKNOWN_KEY` issues. Defaults to false.
|
|
229
|
+
*/
|
|
230
|
+
readonly strict?: boolean;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Extra registration options that shape the module without being injected.
|
|
235
|
+
*
|
|
236
|
+
* `setExtras` keeps these out of the {@link BYMAX_CONFIG_OPTIONS} provider, so a
|
|
237
|
+
* consumer of the options token never sees `isGlobal`; it only influences the
|
|
238
|
+
* generated `DynamicModule`.
|
|
239
|
+
*/
|
|
240
|
+
interface BymaxConfigModuleExtras {
|
|
241
|
+
/**
|
|
242
|
+
* Register the module globally by mapping to `DynamicModule.global`. The
|
|
243
|
+
* builder applies the `true` default before the transform runs, so this is
|
|
244
|
+
* required at the extras level; consumers see it optional through the
|
|
245
|
+
* generated `OPTIONS_TYPE` (a `Partial` of the extras). Defaults to true.
|
|
246
|
+
*/
|
|
247
|
+
readonly isGlobal: boolean;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The generated dynamic-module artifacts.
|
|
251
|
+
*
|
|
252
|
+
* `MODULE_OPTIONS_TOKEN` is the {@link BYMAX_CONFIG_OPTIONS} Symbol (bound
|
|
253
|
+
* through `optionsInjectionToken`), so the config provider factory and the
|
|
254
|
+
* generated options provider share one token. `OPTIONS_TYPE` and
|
|
255
|
+
* `ASYNC_OPTIONS_TYPE` type the `forRoot` / `forRootAsync` parameters, including
|
|
256
|
+
* the `isGlobal` extra.
|
|
257
|
+
*/
|
|
258
|
+
declare const ConfigurableModuleClass: _nestjs_common.ConfigurableModuleCls<BymaxConfigModuleOptions<EnvSchema>, "forRoot", "create", BymaxConfigModuleExtras>;
|
|
259
|
+
declare const OPTIONS_TYPE: BymaxConfigModuleOptions<EnvSchema> & Partial<BymaxConfigModuleExtras>;
|
|
260
|
+
declare const ASYNC_OPTIONS_TYPE: _nestjs_common.ConfigurableModuleAsyncOptions<BymaxConfigModuleOptions<EnvSchema>, "create"> & Partial<BymaxConfigModuleExtras>;
|
|
261
|
+
/** Parameter type of `BymaxConfigModule.forRoot`: the options plus the `isGlobal` extra. */
|
|
262
|
+
type BymaxConfigForRootOptions = typeof OPTIONS_TYPE;
|
|
263
|
+
/** Parameter type of `BymaxConfigModule.forRootAsync`: async options plus the `isGlobal` extra. */
|
|
264
|
+
type BymaxConfigForRootAsyncOptions = typeof ASYNC_OPTIONS_TYPE;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @fileoverview BymaxConfigModule: the dynamic module consumers import once via
|
|
268
|
+
* `forRoot` / `forRootAsync`. It extends the generated ConfigurableModuleClass,
|
|
269
|
+
* inheriting the `isGlobal` extra mapping, and augments each returned definition
|
|
270
|
+
* with the validated, deep-frozen BYMAX_CONFIG provider and the typed
|
|
271
|
+
* ConfigService accessor, so the config is produced (and validation runs) as
|
|
272
|
+
* part of module resolution and both surfaces are exported for consumers.
|
|
273
|
+
* @layer Module
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* NestJS dynamic module exposing typed, validated environment configuration.
|
|
278
|
+
*
|
|
279
|
+
* Import it once (typically in `AppModule`) with `forRoot` for a static schema
|
|
280
|
+
* or `forRootAsync` when the source is resolved through other providers. It is
|
|
281
|
+
* global by default; pass `isGlobal: false` to scope it to a submodule. Either
|
|
282
|
+
* entry point registers and exports the frozen {@link BYMAX_CONFIG} provider.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```typescript
|
|
286
|
+
* @Module({ imports: [BymaxConfigModule.forRoot({ schema: envSchema })] })
|
|
287
|
+
* export class AppModule {}
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
declare class BymaxConfigModule extends ConfigurableModuleClass {
|
|
291
|
+
/**
|
|
292
|
+
* Register the module synchronously from a static schema.
|
|
293
|
+
*
|
|
294
|
+
* @param options - The module options plus the optional `isGlobal` extra.
|
|
295
|
+
* @returns The dynamic module augmented with the frozen config provider.
|
|
296
|
+
*/
|
|
297
|
+
static forRoot(options: BymaxConfigForRootOptions): DynamicModule;
|
|
298
|
+
/**
|
|
299
|
+
* Register the module asynchronously, resolving options through a factory.
|
|
300
|
+
*
|
|
301
|
+
* @param options - The async options with `useFactory` and explicit `inject`.
|
|
302
|
+
* @returns The dynamic module augmented with the frozen config provider.
|
|
303
|
+
*/
|
|
304
|
+
static forRootAsync(options: BymaxConfigForRootAsyncOptions): DynamicModule;
|
|
305
|
+
/**
|
|
306
|
+
* Append the config provider and typed accessor to a generated definition.
|
|
307
|
+
*
|
|
308
|
+
* Registers the frozen {@link BYMAX_CONFIG} provider and the {@link ConfigService}
|
|
309
|
+
* accessor (which injects that token), then exports both so consumers can
|
|
310
|
+
* inject either the raw frozen object or the typed service.
|
|
311
|
+
*
|
|
312
|
+
* @param definition - The dynamic module produced by the builder base class.
|
|
313
|
+
* @returns A definition that provides and exports {@link BYMAX_CONFIG} and
|
|
314
|
+
* {@link ConfigService}.
|
|
315
|
+
*/
|
|
316
|
+
private static withConfigProvider;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @fileoverview ConfigService: the typed, injectable accessor over the frozen
|
|
321
|
+
* BYMAX_CONFIG object. It is the recommended consumption surface for the
|
|
322
|
+
* validated configuration, offering compile-time dot-path inference (`get`),
|
|
323
|
+
* the frozen root (`getAll`), and a declared-path presence check (`has`). The
|
|
324
|
+
* config is injected explicitly through the module-local Symbol token, so the
|
|
325
|
+
* service carries no decorator metadata and needs no `emitDecoratorMetadata`.
|
|
326
|
+
* @layer Service
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Typed accessor over the validated, deep-frozen configuration object.
|
|
331
|
+
*
|
|
332
|
+
* Wraps the frozen config registered under {@link BYMAX_CONFIG} and exposes it
|
|
333
|
+
* through dot-path access limited to the two-level `namespace.leaf` convention.
|
|
334
|
+
* Because validation completed at bootstrap, every declared leaf either passed
|
|
335
|
+
* validation or received its default (or the process never started), so `get`
|
|
336
|
+
* never throws for a declared path.
|
|
337
|
+
*
|
|
338
|
+
* @typeParam TConfig - The parsed, two-level configuration object type.
|
|
339
|
+
* @example
|
|
340
|
+
* ```typescript
|
|
341
|
+
* @Injectable()
|
|
342
|
+
* export class InvoiceService {
|
|
343
|
+
* public constructor(
|
|
344
|
+
* @Inject(ConfigService) private readonly config: ConfigService<AppConfig>,
|
|
345
|
+
* ) {}
|
|
346
|
+
*
|
|
347
|
+
* public connect(): Connection {
|
|
348
|
+
* return connect(this.config.get('database.url'), this.config.get('server.port'));
|
|
349
|
+
* }
|
|
350
|
+
* }
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
declare class ConfigService<TConfig> {
|
|
354
|
+
private readonly config;
|
|
355
|
+
/** Precomputed `namespace.leaf` to value lookup, built once at construction. */
|
|
356
|
+
private readonly leaves;
|
|
357
|
+
/**
|
|
358
|
+
* Bind the accessor to the frozen configuration object.
|
|
359
|
+
*
|
|
360
|
+
* Builds a flat `namespace.leaf` lookup once so `get`/`has` are constant-time
|
|
361
|
+
* and never rebuild a map per call. Iteration over entries (rather than dynamic
|
|
362
|
+
* bracket indexing) avoids introducing a non-literal property sink.
|
|
363
|
+
*
|
|
364
|
+
* @param config - The validated, deep-frozen config injected via {@link BYMAX_CONFIG}.
|
|
365
|
+
*/
|
|
366
|
+
constructor(config: Readonly<TConfig>);
|
|
367
|
+
/**
|
|
368
|
+
* Read a leaf value by its `namespace.leaf` dot-path.
|
|
369
|
+
*
|
|
370
|
+
* The return type is inferred from the path, so `get('server.port')` is typed
|
|
371
|
+
* `number` with no cast. Never throws for a declared path: validation ran once
|
|
372
|
+
* at bootstrap. An optional leaf without a default may still resolve to
|
|
373
|
+
* `undefined` when its type includes it.
|
|
374
|
+
*
|
|
375
|
+
* @typeParam TPath - A declared path drawn from {@link Path}<TConfig>.
|
|
376
|
+
* @param path - The two-level dot-path to resolve.
|
|
377
|
+
* @returns The leaf value, typed as {@link PathValue}<TConfig, TPath>.
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* const port = config.get('server.port'); // number
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
get<TPath extends Path<TConfig>>(path: TPath): PathValue<TConfig, TPath>;
|
|
384
|
+
/**
|
|
385
|
+
* Return the whole validated configuration as its deep-frozen root.
|
|
386
|
+
*
|
|
387
|
+
* Yields the same frozen reference the module registered, fully typed for
|
|
388
|
+
* arbitrary depth, which is the escape hatch for nesting deeper than the
|
|
389
|
+
* two-level dot-path inference covers.
|
|
390
|
+
*
|
|
391
|
+
* @returns The frozen configuration root.
|
|
392
|
+
* @example
|
|
393
|
+
* ```typescript
|
|
394
|
+
* const everything = config.getAll(); // Readonly<AppConfig>
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
getAll(): Readonly<TConfig>;
|
|
398
|
+
/**
|
|
399
|
+
* Report whether a declared path resolves to a defined value.
|
|
400
|
+
*
|
|
401
|
+
* Returns `false` when the leaf resolved to `undefined` (an optional leaf
|
|
402
|
+
* without a default), distinguishing "declared but unset" from "declared and
|
|
403
|
+
* set" without a truthiness pitfall on falsy values such as `0` or `''`.
|
|
404
|
+
*
|
|
405
|
+
* @param path - The two-level dot-path to check.
|
|
406
|
+
* @returns True when the resolved value is not `undefined`.
|
|
407
|
+
* @example
|
|
408
|
+
* ```typescript
|
|
409
|
+
* if (config.has('redis.url')) connectRedis(config.get('redis.url'));
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
has(path: Path<TConfig>): boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Resolve a two-level dot-path to its raw leaf value.
|
|
415
|
+
*
|
|
416
|
+
* Looks the path up in the precomputed `namespace.leaf` map, so an unset leaf
|
|
417
|
+
* (or, defensively, an unexpected path) yields `undefined` instead of throwing.
|
|
418
|
+
*
|
|
419
|
+
* @param path - The declared two-level dot-path.
|
|
420
|
+
* @returns The leaf value as `unknown`, or `undefined` when the leaf is unset.
|
|
421
|
+
*/
|
|
422
|
+
private resolve;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @fileoverview Symbol dependency-injection tokens for the configuration
|
|
427
|
+
* module. Both tokens are module-local Symbols, never strings and never
|
|
428
|
+
* registered in the global symbol registry, so their identity is unique across
|
|
429
|
+
* realms and cannot collide with another provider's token. Every injection site
|
|
430
|
+
* references these constants through an explicit `@Inject(...)`.
|
|
431
|
+
* @layer Constants
|
|
432
|
+
*/
|
|
433
|
+
/**
|
|
434
|
+
* Injection token for the resolved {@link BymaxConfigModuleOptions}.
|
|
435
|
+
*
|
|
436
|
+
* The `forRoot` value provider and the `forRootAsync` factory both register the
|
|
437
|
+
* options object under this token; the config provider factory injects it to
|
|
438
|
+
* read the schema, source, hook, and strict flag.
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```typescript
|
|
442
|
+
* constructor(@Inject(BYMAX_CONFIG_OPTIONS) options: BymaxConfigModuleOptions) {}
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
declare const BYMAX_CONFIG_OPTIONS: unique symbol;
|
|
446
|
+
/**
|
|
447
|
+
* Injection token for the deep-frozen, validated configuration object.
|
|
448
|
+
*
|
|
449
|
+
* The provider factory registers the parsed, frozen config under this token and
|
|
450
|
+
* the module exports it, so consumers (and the typed accessor) can inject the
|
|
451
|
+
* validated config directly for factory-style wiring.
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* ```typescript
|
|
455
|
+
* constructor(@Inject(BYMAX_CONFIG) config: Readonly<AppConfig>) {}
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
declare const BYMAX_CONFIG: unique symbol;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* @fileoverview The `defineEnv` factory: a thin, typed wrapper over
|
|
462
|
+
* `z.object(...)` that establishes the two-level namespace convention of the
|
|
463
|
+
* package and exposes the inferred configuration type through a phantom
|
|
464
|
+
* accessor. It composes the caller's schemas as-is and never rewrites, clones,
|
|
465
|
+
* or wraps them, so coercion and defaults stay explicit consumer choices.
|
|
466
|
+
* @layer Utility
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Compose environment namespaces into a single typed schema.
|
|
471
|
+
*
|
|
472
|
+
* Wraps the passed shape in `z.object(...)` and returns it augmented with the
|
|
473
|
+
* `infer` phantom accessor, so the configuration type is extracted ergonomically
|
|
474
|
+
* via `typeof schema.infer`. The caller's namespace and leaf schemas are reused
|
|
475
|
+
* unchanged: `defineEnv` documents and encourages `z.coerce.*` for the
|
|
476
|
+
* string-shaped environment, but never injects coercion or defaults itself.
|
|
477
|
+
*
|
|
478
|
+
* @typeParam TShape - The two-level shape: namespace keys mapped to Zod objects.
|
|
479
|
+
* @param shape - Top-level namespaces, each a Zod object of leaf schemas.
|
|
480
|
+
* @returns The composed Zod object schema carrying the `infer` type helper.
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* import { defineEnv } from '@bymax-one/nest-config';
|
|
484
|
+
* import { z } from 'zod';
|
|
485
|
+
*
|
|
486
|
+
* export const envSchema = defineEnv({
|
|
487
|
+
* server: z.object({
|
|
488
|
+
* port: z.coerce.number().int().min(1).max(65535).default(3000),
|
|
489
|
+
* }),
|
|
490
|
+
* database: z.object({ url: z.url() }),
|
|
491
|
+
* });
|
|
492
|
+
*
|
|
493
|
+
* export type AppConfig = typeof envSchema.infer;
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
declare function defineEnv<TShape extends EnvShape>(shape: TShape): EnvSchema<TShape>;
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* @fileoverview The BYMAX_CONFIG provider factory: the fail-fast heart of the
|
|
500
|
+
* module. It resolves the source (defaulting to `process.env`, using a custom
|
|
501
|
+
* source verbatim), runs the single-pass validator, and on success returns the
|
|
502
|
+
* deep-frozen typed config. On failure it invokes the value-free observability
|
|
503
|
+
* hook exactly once before rethrowing the aggregated error; the hook is
|
|
504
|
+
* observability only and can never suppress or replace the failure, so a
|
|
505
|
+
* misconfigured process is stopped before any consumer provider is constructed.
|
|
506
|
+
* @layer Provider
|
|
507
|
+
*/
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Validate the source against the schema, freeze the result, or fail fast.
|
|
511
|
+
*
|
|
512
|
+
* Resolves the source to the supplied record or `process.env`, runs exactly one
|
|
513
|
+
* validation pass, and returns the deep-frozen typed output. On a validation
|
|
514
|
+
* failure the hook runs once with the issues and the aggregated error is
|
|
515
|
+
* rethrown unchanged, aborting module resolution before any consumer provider
|
|
516
|
+
* is instantiated.
|
|
517
|
+
*
|
|
518
|
+
* @typeParam TShape - The two-level schema shape.
|
|
519
|
+
* @param options - The resolved module options (schema, source, hook, strict).
|
|
520
|
+
* @returns The validated, deep-frozen configuration output.
|
|
521
|
+
* @throws {BymaxConfigValidationError} When the source fails the schema.
|
|
522
|
+
* @example
|
|
523
|
+
* ```typescript
|
|
524
|
+
* const config = createValidatedConfig({ schema: envSchema });
|
|
525
|
+
* config.server.port; // typed, validated, frozen
|
|
526
|
+
* ```
|
|
527
|
+
*/
|
|
528
|
+
declare function createValidatedConfig<TShape extends EnvShape>(options: Omit<BymaxConfigModuleOptions, 'schema'> & {
|
|
529
|
+
readonly schema: EnvSchema<TShape>;
|
|
530
|
+
}): Readonly<EnvOutput<TShape>>;
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* @fileoverview Deterministic mapping between nested config paths and flat
|
|
534
|
+
* environment variable names. Each leaf resolves to exactly one variable:
|
|
535
|
+
* SCREAMING_SNAKE_CASE of the joined path (`database.url` reads `DATABASE_URL`),
|
|
536
|
+
* unless the leaf declares a `meta({ env })` override, which wins. This is an
|
|
537
|
+
* internal module consumed by the validator; it is not part of the public API.
|
|
538
|
+
* @layer Utility
|
|
539
|
+
*/
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* The resolved binding between one config leaf and its source variable name.
|
|
543
|
+
*/
|
|
544
|
+
interface SourceBinding {
|
|
545
|
+
/** Nested config path, e.g. `database.url`. */
|
|
546
|
+
readonly path: string;
|
|
547
|
+
/** Resolved environment variable name, e.g. `DATABASE_URL`. */
|
|
548
|
+
readonly variable: string;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Resolve the source variable name of every leaf in a `defineEnv` schema.
|
|
552
|
+
*
|
|
553
|
+
* Walks the two-level schema (namespaces then leaves) and yields one binding
|
|
554
|
+
* per leaf. The variable name is the `meta({ env })` override when present,
|
|
555
|
+
* otherwise the SCREAMING_SNAKE_CASE derivation of the joined path. The result
|
|
556
|
+
* is total: every declared leaf maps to exactly one variable, in declaration
|
|
557
|
+
* order.
|
|
558
|
+
*
|
|
559
|
+
* @typeParam TShape - The two-level shape the schema was composed from.
|
|
560
|
+
* @param schema - A schema produced by `defineEnv`.
|
|
561
|
+
* @returns The ordered list of leaf-to-variable bindings, typed `readonly`.
|
|
562
|
+
* @example
|
|
563
|
+
* ```typescript
|
|
564
|
+
* const schema = defineEnv({ database: z.object({ url: z.url() }) });
|
|
565
|
+
* resolveSourceNames(schema);
|
|
566
|
+
* // => [{ path: 'database.url', variable: 'DATABASE_URL' }]
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
declare function resolveSourceNames<TShape extends EnvShape = EnvShape>(schema: EnvSchema<TShape>): readonly SourceBinding[];
|
|
570
|
+
|
|
571
|
+
export { BYMAX_CONFIG, BYMAX_CONFIG_OPTIONS, BymaxConfigModule, type BymaxConfigModuleOptions, BymaxConfigValidationError, ConfigErrorCode, type ConfigIssue, type ConfigIssueCode, ConfigService, type ConfigValidationCode, type EnvLeaf, type EnvNamespace, type EnvOutput, type EnvSchema, type EnvShape, type Path, type PathValue, type SourceBinding, createValidatedConfig, defineEnv, resolveSourceNames };
|