@meltstudio/config-loader 3.2.0 → 3.4.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 +40 -0
- package/dist/index.d.ts +41 -5
- package/dist/index.js +50 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ No separate interface to maintain. No `as` casts. The types flow from the schema
|
|
|
61
61
|
- **`.env` file support** — load environment variables from `.env` files with automatic line tracking
|
|
62
62
|
- **Nested objects and arrays** — deeply nested configs with full type safety
|
|
63
63
|
- **Structured errors** — typed `ConfigLoadError` with per-field error details and warnings
|
|
64
|
+
- **Enum constraints** — restrict values to a fixed set with `oneOf`, with full type narrowing
|
|
64
65
|
- **Schema validation** — optional per-field validation via [Standard Schema](https://github.com/standard-schema/standard-schema) (Zod, Valibot, ArkType, or custom)
|
|
65
66
|
- **Strict mode** — promote warnings to errors for production safety
|
|
66
67
|
- **Default values** — static or computed (via functions)
|
|
@@ -253,6 +254,45 @@ c.array({
|
|
|
253
254
|
}); // { name: string; age: number }[]
|
|
254
255
|
```
|
|
255
256
|
|
|
257
|
+
## Enum Constraints (`oneOf`)
|
|
258
|
+
|
|
259
|
+
Use `oneOf` to restrict a field to a fixed set of allowed values. The check runs after type coercion and before any `validate` schema:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
const config = c
|
|
263
|
+
.schema({
|
|
264
|
+
env: c.string({
|
|
265
|
+
env: "NODE_ENV",
|
|
266
|
+
defaultValue: "development",
|
|
267
|
+
oneOf: ["development", "staging", "production"],
|
|
268
|
+
}),
|
|
269
|
+
logLevel: c.number({
|
|
270
|
+
env: "LOG_LEVEL",
|
|
271
|
+
defaultValue: 1,
|
|
272
|
+
oneOf: [0, 1, 2, 3],
|
|
273
|
+
}),
|
|
274
|
+
})
|
|
275
|
+
.load({ env: true, args: false });
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
If a value is not in the allowed set, a `ConfigLoadError` is thrown with `kind: "validation"`.
|
|
279
|
+
|
|
280
|
+
### Type Narrowing
|
|
281
|
+
|
|
282
|
+
When `oneOf` is provided, the inferred type is automatically narrowed to the union of the allowed values:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const config = c
|
|
286
|
+
.schema({
|
|
287
|
+
env: c.string({ oneOf: ["dev", "staging", "prod"] }),
|
|
288
|
+
})
|
|
289
|
+
.load({ env: false, args: false });
|
|
290
|
+
|
|
291
|
+
// config.env is typed as "dev" | "staging" | "prod", not string
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
When used with `cli: true`, the `--help` output automatically lists the allowed values.
|
|
295
|
+
|
|
256
296
|
## Validation
|
|
257
297
|
|
|
258
298
|
Add per-field validation using the `validate` option. config-loader accepts any [Standard Schema v1](https://github.com/standard-schema/standard-schema) implementation — including **Zod**, **Valibot**, and **ArkType** — or a custom validator.
|
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ interface OptionClassParams<T extends OptionKind> {
|
|
|
61
61
|
cli: boolean;
|
|
62
62
|
help: string;
|
|
63
63
|
defaultValue?: TypedDefaultValue<T>;
|
|
64
|
+
oneOf?: ReadonlyArray<string | number | boolean>;
|
|
64
65
|
validate?: StandardSchemaV1;
|
|
65
66
|
}
|
|
66
67
|
declare class OptionBase<T extends OptionKind = OptionKind> {
|
|
@@ -81,6 +82,7 @@ declare class OptionBase<T extends OptionKind = OptionKind> {
|
|
|
81
82
|
} | null;
|
|
82
83
|
}, envFileResults?: EnvFileResult[], errors?: OptionErrors): ConfigNode | null;
|
|
83
84
|
private resolveValue;
|
|
85
|
+
private runOneOfCheck;
|
|
84
86
|
private runValidation;
|
|
85
87
|
private resolveFromFileData;
|
|
86
88
|
checkType(val: Value, path: Path, sourceOfVal: string, errors?: OptionErrors): Value;
|
|
@@ -101,7 +103,7 @@ declare class ConfigNode {
|
|
|
101
103
|
constructor(value: Value | ArrayValue, path: string, sourceType: SourceTypes, file: string | null, variableName: string | null, argName: string | null, line?: number | null, column?: number | null);
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
declare class PrimitiveOption<T extends PrimitiveKind = PrimitiveKind
|
|
106
|
+
declare class PrimitiveOption<T extends PrimitiveKind = PrimitiveKind, Narrowed = TypeOfPrimitiveKind<T>> extends OptionBase<T> {
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
type NodeTree = {
|
|
@@ -140,7 +142,7 @@ type TypeOfPrimitiveKind<T extends PrimitiveKind> = T extends "boolean" ? boolea
|
|
|
140
142
|
/** Recursively infers the plain TypeScript type from a schema definition. Maps option nodes to their resolved value types. */
|
|
141
143
|
type SchemaValue<T extends OptionBase | Node> = T extends OptionBase ? T extends ArrayOption<OptionTypes> ? SchemaValue<T["item"]>[] : T extends ObjectOption<infer R> ? {
|
|
142
144
|
[K in keyof R]: SchemaValue<R[K]>;
|
|
143
|
-
} : T extends PrimitiveOption<infer
|
|
145
|
+
} : T extends PrimitiveOption<infer _R, infer Narrowed> ? Narrowed : never : T extends Node ? {
|
|
144
146
|
[K in keyof T]: SchemaValue<T[K]>;
|
|
145
147
|
} : never;
|
|
146
148
|
type Path = Array<string | number>;
|
|
@@ -260,6 +262,8 @@ interface OptionPropsArgs<T> {
|
|
|
260
262
|
defaultValue?: T | (() => T);
|
|
261
263
|
/** Help text shown in CLI `--help` output. */
|
|
262
264
|
help?: string;
|
|
265
|
+
/** Restrict the value to a fixed set of allowed values. Checked after type coercion, before `validate`. */
|
|
266
|
+
oneOf?: readonly T[];
|
|
263
267
|
/** Standard Schema validator run after type coercion. Accepts Zod, Valibot, ArkType, or any Standard Schema v1 implementation. */
|
|
264
268
|
validate?: StandardSchemaV1;
|
|
265
269
|
}
|
|
@@ -283,6 +287,38 @@ interface ObjectOptionPropsArgs<T extends Node> {
|
|
|
283
287
|
/** Standard Schema validator run on the resolved object. Accepts Zod, Valibot, ArkType, or any Standard Schema v1 implementation. */
|
|
284
288
|
validate?: StandardSchemaV1;
|
|
285
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Creates a string configuration option.
|
|
292
|
+
* @param opts - Option configuration (env, cli, required, defaultValue, help, oneOf).
|
|
293
|
+
* @returns A `PrimitiveOption<"string">` for use in a schema.
|
|
294
|
+
* @example
|
|
295
|
+
* c.string({ env: "HOST", defaultValue: "localhost" })
|
|
296
|
+
* c.string({ env: "NODE_ENV", oneOf: ["development", "staging", "production"] })
|
|
297
|
+
*/
|
|
298
|
+
declare function string<const V extends readonly string[]>(opts: OptionPropsArgs<string> & {
|
|
299
|
+
oneOf: V;
|
|
300
|
+
}): PrimitiveOption<"string", V[number]>;
|
|
301
|
+
declare function string(opts?: OptionPropsArgs<string>): PrimitiveOption<"string">;
|
|
302
|
+
/**
|
|
303
|
+
* Creates a number configuration option. String values from env/CLI are coerced to numbers.
|
|
304
|
+
* @param opts - Option configuration (env, cli, required, defaultValue, help, oneOf).
|
|
305
|
+
* @returns A `PrimitiveOption<"number">` for use in a schema.
|
|
306
|
+
* @example
|
|
307
|
+
* c.number({ env: "PORT", defaultValue: 3000 })
|
|
308
|
+
* c.number({ env: "LOG_LEVEL", oneOf: [0, 1, 2, 3] })
|
|
309
|
+
*/
|
|
310
|
+
declare function number<const V extends readonly number[]>(opts: OptionPropsArgs<number> & {
|
|
311
|
+
oneOf: V;
|
|
312
|
+
}): PrimitiveOption<"number", V[number]>;
|
|
313
|
+
declare function number(opts?: OptionPropsArgs<number>): PrimitiveOption<"number">;
|
|
314
|
+
/**
|
|
315
|
+
* Creates a boolean configuration option. String values `"true"`/`"false"` are coerced.
|
|
316
|
+
* @param opts - Option configuration (env, cli, required, defaultValue, help).
|
|
317
|
+
* @returns A `PrimitiveOption<"boolean">` for use in a schema.
|
|
318
|
+
* @example
|
|
319
|
+
* c.bool({ env: "DEBUG", defaultValue: false })
|
|
320
|
+
*/
|
|
321
|
+
declare function bool(opts?: OptionPropsArgs<boolean>): PrimitiveOption<"boolean">;
|
|
286
322
|
/**
|
|
287
323
|
* Config-loader entry point. Provides factory functions to define a typed configuration schema.
|
|
288
324
|
*
|
|
@@ -297,9 +333,9 @@ interface ObjectOptionPropsArgs<T extends Node> {
|
|
|
297
333
|
* ```
|
|
298
334
|
*/
|
|
299
335
|
declare const option: {
|
|
300
|
-
string:
|
|
301
|
-
number:
|
|
302
|
-
bool:
|
|
336
|
+
string: typeof string;
|
|
337
|
+
number: typeof number;
|
|
338
|
+
bool: typeof bool;
|
|
303
339
|
array: <T extends OptionTypes>(opts: ArrayOptionPropsArgs<T>) => ArrayOption<T>;
|
|
304
340
|
object: <T extends Node>(opts: ObjectOptionPropsArgs<T>) => ObjectOption<T>;
|
|
305
341
|
schema: <T extends Node>(theSchema: T) => SettingsBuilder<T>;
|
package/dist/index.js
CHANGED
|
@@ -292,6 +292,10 @@ var OptionBase = class {
|
|
|
292
292
|
envFileResults,
|
|
293
293
|
errors
|
|
294
294
|
);
|
|
295
|
+
if (resolved && this.params.oneOf) {
|
|
296
|
+
const passed = this.runOneOfCheck(resolved, path2, errors);
|
|
297
|
+
if (!passed) return resolved;
|
|
298
|
+
}
|
|
295
299
|
if (resolved && this.params.validate) {
|
|
296
300
|
this.runValidation(resolved, path2, errors);
|
|
297
301
|
}
|
|
@@ -455,6 +459,27 @@ var OptionBase = class {
|
|
|
455
459
|
}
|
|
456
460
|
return null;
|
|
457
461
|
}
|
|
462
|
+
runOneOfCheck(node, path2, errors) {
|
|
463
|
+
const allowed = this.params.oneOf;
|
|
464
|
+
if (!allowed) return true;
|
|
465
|
+
const value = node.value;
|
|
466
|
+
if (valueIsInvalid(value)) return true;
|
|
467
|
+
if (!allowed.includes(value)) {
|
|
468
|
+
const ident = path2.join(".");
|
|
469
|
+
const source = node.file ?? node.variableName ?? node.argName ?? node.sourceType;
|
|
470
|
+
const allowedStr = allowed.map((v) => `'${String(v)}'`).join(", ");
|
|
471
|
+
errors?.errors.push({
|
|
472
|
+
message: `Value '${typeof value === "object" ? JSON.stringify(value) : String(value)}' for '${ident}' is not one of: ${allowedStr}.`,
|
|
473
|
+
path: ident,
|
|
474
|
+
source,
|
|
475
|
+
kind: "validation",
|
|
476
|
+
line: node.line ?? void 0,
|
|
477
|
+
column: node.column ?? void 0
|
|
478
|
+
});
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
458
483
|
runValidation(node, path2, errors) {
|
|
459
484
|
const validator = this.params.validate;
|
|
460
485
|
if (!validator) return;
|
|
@@ -654,6 +679,19 @@ var OptionBase = class {
|
|
|
654
679
|
var ObjectOption = class extends OptionBase {
|
|
655
680
|
item;
|
|
656
681
|
constructor(params) {
|
|
682
|
+
if (!params.item) {
|
|
683
|
+
const hasOptionValues = Object.values(params).some(
|
|
684
|
+
(v) => v instanceof OptionBase
|
|
685
|
+
);
|
|
686
|
+
if (hasOptionValues) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
"Invalid c.object() call: schema fields were passed directly instead of wrapped in { item: { ... } }. Use c.object({ item: { host: c.string() } }) instead of c.object({ host: c.string() })."
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
throw new Error(
|
|
692
|
+
"Invalid c.object() call: missing required 'item' property. Use c.object({ item: { host: c.string(), port: c.number() } })."
|
|
693
|
+
);
|
|
694
|
+
}
|
|
657
695
|
super({
|
|
658
696
|
kind: "object",
|
|
659
697
|
env: null,
|
|
@@ -1021,7 +1059,12 @@ var Settings = class {
|
|
|
1021
1059
|
addArg(node, path2 = []) {
|
|
1022
1060
|
if (node.params.cli) {
|
|
1023
1061
|
const ident = path2.join(".");
|
|
1024
|
-
|
|
1062
|
+
let help = node.params.help;
|
|
1063
|
+
if (node.params.oneOf) {
|
|
1064
|
+
const allowed = node.params.oneOf.map(String).join(", ");
|
|
1065
|
+
help = help ? `${help} (one of: ${allowed})` : `one of: ${allowed}`;
|
|
1066
|
+
}
|
|
1067
|
+
this.program.option(`--${ident} <value>`, help);
|
|
1025
1068
|
}
|
|
1026
1069
|
}
|
|
1027
1070
|
getValuesFromTree(node) {
|
|
@@ -1212,27 +1255,27 @@ var DEFAULTS = {
|
|
|
1212
1255
|
cli: false,
|
|
1213
1256
|
help: ""
|
|
1214
1257
|
};
|
|
1215
|
-
|
|
1258
|
+
function string(opts) {
|
|
1216
1259
|
return new PrimitiveOption({
|
|
1217
1260
|
kind: "string",
|
|
1218
1261
|
...DEFAULTS,
|
|
1219
1262
|
...opts
|
|
1220
1263
|
});
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1264
|
+
}
|
|
1265
|
+
function number(opts) {
|
|
1223
1266
|
return new PrimitiveOption({
|
|
1224
1267
|
kind: "number",
|
|
1225
1268
|
...DEFAULTS,
|
|
1226
1269
|
...opts
|
|
1227
1270
|
});
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1271
|
+
}
|
|
1272
|
+
function bool(opts) {
|
|
1230
1273
|
return new PrimitiveOption({
|
|
1231
1274
|
kind: "boolean",
|
|
1232
1275
|
...DEFAULTS,
|
|
1233
1276
|
...opts
|
|
1234
1277
|
});
|
|
1235
|
-
}
|
|
1278
|
+
}
|
|
1236
1279
|
var array = (opts) => {
|
|
1237
1280
|
return new ArrayOption({
|
|
1238
1281
|
...DEFAULTS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meltstudio/config-loader",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "Type-safe configuration loader with full TypeScript inference. Load from YAML, JSON, .env, environment variables, and CLI args.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|