@meltstudio/config-loader 3.0.1 → 3.1.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 +69 -0
- package/dist/index.d.ts +37 -2
- package/dist/index.js +37 -0
- 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
|
+
- **Schema validation** — optional per-field validation via [Standard Schema](https://github.com/standard-schema/standard-schema) (Zod, Valibot, ArkType, or custom)
|
|
64
65
|
- **Strict mode** — promote warnings to errors for production safety
|
|
65
66
|
- **Default values** — static or computed (via functions)
|
|
66
67
|
- **Multiple files / directory loading** — load from a list of files or an entire directory
|
|
@@ -252,6 +253,74 @@ c.array({
|
|
|
252
253
|
}); // { name: string; age: number }[]
|
|
253
254
|
```
|
|
254
255
|
|
|
256
|
+
## Validation
|
|
257
|
+
|
|
258
|
+
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.
|
|
259
|
+
|
|
260
|
+
Validation runs **after** type coercion, so validators see the final typed value (e.g., the number `3000`, not the string `"3000"` from an env var).
|
|
261
|
+
|
|
262
|
+
### With Zod
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import c from "@meltstudio/config-loader";
|
|
266
|
+
import { z } from "zod";
|
|
267
|
+
|
|
268
|
+
const config = c
|
|
269
|
+
.schema({
|
|
270
|
+
port: c.number({
|
|
271
|
+
required: true,
|
|
272
|
+
env: "PORT",
|
|
273
|
+
validate: z.number().min(1).max(65535),
|
|
274
|
+
}),
|
|
275
|
+
host: c.string({
|
|
276
|
+
required: true,
|
|
277
|
+
validate: z.string().url(),
|
|
278
|
+
}),
|
|
279
|
+
env: c.string({
|
|
280
|
+
defaultValue: "development",
|
|
281
|
+
validate: z.enum(["development", "staging", "production"]),
|
|
282
|
+
}),
|
|
283
|
+
})
|
|
284
|
+
.load({ env: true, args: false, files: "./config.yaml" });
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### With a custom validator
|
|
288
|
+
|
|
289
|
+
Any object with a `~standard.validate()` method works:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
const portValidator = {
|
|
293
|
+
"~standard": {
|
|
294
|
+
version: 1,
|
|
295
|
+
vendor: "my-app",
|
|
296
|
+
validate(value: unknown) {
|
|
297
|
+
if (typeof value === "number" && value >= 1 && value <= 65535) {
|
|
298
|
+
return { value };
|
|
299
|
+
}
|
|
300
|
+
return { issues: [{ message: "must be a valid port (1-65535)" }] };
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
c.number({ required: true, env: "PORT", validate: portValidator });
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Validation errors are collected alongside other config errors and thrown as `ConfigLoadError` with `kind: "validation"`:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
try {
|
|
312
|
+
const config = c.schema({ ... }).load({ ... });
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (err instanceof ConfigLoadError) {
|
|
315
|
+
for (const entry of err.errors) {
|
|
316
|
+
if (entry.kind === "validation") {
|
|
317
|
+
console.error(`Validation: ${entry.path} — ${entry.message}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
255
324
|
## Loading Sources
|
|
256
325
|
|
|
257
326
|
```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ interface ConfigErrorEntry {
|
|
|
23
23
|
/** The source where the error originated (e.g. file path, `"env"`, `"cli"`). */
|
|
24
24
|
source?: string;
|
|
25
25
|
/** Classification of the error. */
|
|
26
|
-
kind?: "required" | "type_conversion" | "invalid_path" | "invalid_state" | "file_validation" | "null_value" | "strict";
|
|
26
|
+
kind?: "required" | "type_conversion" | "invalid_path" | "invalid_state" | "file_validation" | "null_value" | "strict" | "validation";
|
|
27
27
|
/** Line number in the config file where the error occurred, if applicable. */
|
|
28
28
|
line?: number;
|
|
29
29
|
/** Column number in the config file where the error occurred, if applicable. */
|
|
@@ -61,6 +61,7 @@ interface OptionClassParams<T extends OptionKind> {
|
|
|
61
61
|
cli: boolean;
|
|
62
62
|
help: string;
|
|
63
63
|
defaultValue?: TypedDefaultValue<T>;
|
|
64
|
+
validate?: StandardSchemaV1;
|
|
64
65
|
}
|
|
65
66
|
declare class OptionBase<T extends OptionKind = OptionKind> {
|
|
66
67
|
readonly params: OptionClassParams<T>;
|
|
@@ -79,6 +80,8 @@ declare class OptionBase<T extends OptionKind = OptionKind> {
|
|
|
79
80
|
} | undefined;
|
|
80
81
|
} | null;
|
|
81
82
|
}, envFileResults?: EnvFileResult[], errors?: OptionErrors): ConfigNode | null;
|
|
83
|
+
private resolveValue;
|
|
84
|
+
private runValidation;
|
|
82
85
|
private resolveFromFileData;
|
|
83
86
|
checkType(val: Value, path: Path, sourceOfVal: string, errors?: OptionErrors): Value;
|
|
84
87
|
protected findInObject(obj: ConfigFileData, path: Path, errors?: OptionErrors): Value | ArrayValue;
|
|
@@ -149,11 +152,36 @@ interface ConfigFileData extends ConfigFileStructure<ConfigFileData> {
|
|
|
149
152
|
type ArrayValue = Array<string | number | boolean | ConfigFileData>;
|
|
150
153
|
declare class InvalidValue {
|
|
151
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* A single issue returned by a Standard Schema validator.
|
|
157
|
+
* Mirrors the Standard Schema v1 spec (https://github.com/standard-schema/standard-schema).
|
|
158
|
+
*/
|
|
159
|
+
interface StandardSchemaIssue {
|
|
160
|
+
message: string;
|
|
161
|
+
path?: ReadonlyArray<PropertyKey>;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Minimal Standard Schema v1 interface for value validation.
|
|
165
|
+
* Any object with a `~standard.validate()` method is accepted — this covers
|
|
166
|
+
* Zod 3.24+, Valibot 1.0+, ArkType 2.1+, and custom validators.
|
|
167
|
+
*/
|
|
168
|
+
interface StandardSchemaV1<Output = unknown> {
|
|
169
|
+
"~standard": {
|
|
170
|
+
version: 1;
|
|
171
|
+
vendor: string;
|
|
172
|
+
validate(value: unknown): {
|
|
173
|
+
value: Output;
|
|
174
|
+
} | {
|
|
175
|
+
issues: ReadonlyArray<StandardSchemaIssue>;
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
}
|
|
152
179
|
|
|
153
180
|
interface ArrayOptionClassParams<T extends OptionTypes> {
|
|
154
181
|
required: boolean;
|
|
155
182
|
defaultValue?: SchemaValue<T>[] | (() => SchemaValue<T>[]);
|
|
156
183
|
item: T;
|
|
184
|
+
validate?: StandardSchemaV1;
|
|
157
185
|
}
|
|
158
186
|
declare class ArrayOption<T extends OptionTypes> extends OptionBase<"array"> {
|
|
159
187
|
item: T;
|
|
@@ -165,6 +193,7 @@ declare class ArrayOption<T extends OptionTypes> extends OptionBase<"array"> {
|
|
|
165
193
|
interface ObjectOptionClassParams<T extends Node> {
|
|
166
194
|
required: boolean;
|
|
167
195
|
item: T;
|
|
196
|
+
validate?: StandardSchemaV1;
|
|
168
197
|
}
|
|
169
198
|
declare class ObjectOption<T extends Node = Node> extends OptionBase<"object"> {
|
|
170
199
|
item: T;
|
|
@@ -205,6 +234,8 @@ interface OptionPropsArgs<T> {
|
|
|
205
234
|
defaultValue?: T | (() => T);
|
|
206
235
|
/** Help text shown in CLI `--help` output. */
|
|
207
236
|
help?: string;
|
|
237
|
+
/** Standard Schema validator run after type coercion. Accepts Zod, Valibot, ArkType, or any Standard Schema v1 implementation. */
|
|
238
|
+
validate?: StandardSchemaV1;
|
|
208
239
|
}
|
|
209
240
|
/** Options for configuring an `array` schema field. */
|
|
210
241
|
interface ArrayOptionPropsArgs<T extends OptionTypes> {
|
|
@@ -214,6 +245,8 @@ interface ArrayOptionPropsArgs<T extends OptionTypes> {
|
|
|
214
245
|
item: T;
|
|
215
246
|
/** Static default value or factory function returning one. */
|
|
216
247
|
defaultValue?: SchemaValue<T>[] | (() => SchemaValue<T>[]);
|
|
248
|
+
/** Standard Schema validator run on the resolved array. Accepts Zod, Valibot, ArkType, or any Standard Schema v1 implementation. */
|
|
249
|
+
validate?: StandardSchemaV1;
|
|
217
250
|
}
|
|
218
251
|
/** Options for configuring a nested `object` schema field. */
|
|
219
252
|
interface ObjectOptionPropsArgs<T extends Node> {
|
|
@@ -221,6 +254,8 @@ interface ObjectOptionPropsArgs<T extends Node> {
|
|
|
221
254
|
required?: boolean;
|
|
222
255
|
/** Schema definition for the nested object's shape. */
|
|
223
256
|
item: T;
|
|
257
|
+
/** Standard Schema validator run on the resolved object. Accepts Zod, Valibot, ArkType, or any Standard Schema v1 implementation. */
|
|
258
|
+
validate?: StandardSchemaV1;
|
|
224
259
|
}
|
|
225
260
|
/**
|
|
226
261
|
* Config-loader entry point. Provides factory functions to define a typed configuration schema.
|
|
@@ -244,4 +279,4 @@ declare const option: {
|
|
|
244
279
|
schema: <T extends Node>(theSchema: T) => SettingsBuilder<T>;
|
|
245
280
|
};
|
|
246
281
|
|
|
247
|
-
export { type ConfigErrorEntry, ConfigFileError, ConfigLoadError, type ExtendedResult, option as default };
|
|
282
|
+
export { type ConfigErrorEntry, ConfigFileError, ConfigLoadError, type ExtendedResult, type StandardSchemaV1, option as default };
|
package/dist/index.js
CHANGED
|
@@ -272,6 +272,22 @@ var OptionBase = class {
|
|
|
272
272
|
this.params = params;
|
|
273
273
|
}
|
|
274
274
|
getValue(sourceFile, env, args, path2, defaultValues, objectFromArray, envFileResults, errors) {
|
|
275
|
+
const resolved = this.resolveValue(
|
|
276
|
+
sourceFile,
|
|
277
|
+
env,
|
|
278
|
+
args,
|
|
279
|
+
path2,
|
|
280
|
+
defaultValues,
|
|
281
|
+
objectFromArray,
|
|
282
|
+
envFileResults,
|
|
283
|
+
errors
|
|
284
|
+
);
|
|
285
|
+
if (resolved && this.params.validate) {
|
|
286
|
+
this.runValidation(resolved, path2, errors);
|
|
287
|
+
}
|
|
288
|
+
return resolved;
|
|
289
|
+
}
|
|
290
|
+
resolveValue(sourceFile, env, args, path2, defaultValues, objectFromArray, envFileResults, errors) {
|
|
275
291
|
const ident = path2.join(".");
|
|
276
292
|
if (this.params.cli && args) {
|
|
277
293
|
if (ident in args) {
|
|
@@ -413,6 +429,27 @@ var OptionBase = class {
|
|
|
413
429
|
}
|
|
414
430
|
return null;
|
|
415
431
|
}
|
|
432
|
+
runValidation(node, path2, errors) {
|
|
433
|
+
const validator = this.params.validate;
|
|
434
|
+
if (!validator) return;
|
|
435
|
+
const value = node.value;
|
|
436
|
+
if (valueIsInvalid(value)) return;
|
|
437
|
+
const result = validator["~standard"].validate(value);
|
|
438
|
+
if ("issues" in result && result.issues) {
|
|
439
|
+
const ident = path2.join(".");
|
|
440
|
+
const source = node.file ?? node.variableName ?? node.argName ?? node.sourceType;
|
|
441
|
+
for (const issue of result.issues) {
|
|
442
|
+
errors?.errors.push({
|
|
443
|
+
message: `Validation failed for '${ident}': ${issue.message}`,
|
|
444
|
+
path: ident,
|
|
445
|
+
source,
|
|
446
|
+
kind: "validation",
|
|
447
|
+
line: node.line ?? void 0,
|
|
448
|
+
column: node.column ?? void 0
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
416
453
|
resolveFromFileData(data, file, sourceMap, path2, ident, errors) {
|
|
417
454
|
const val = this.findInObject(data, path2, errors);
|
|
418
455
|
const loc = lookupLocation(sourceMap, path2);
|
package/package.json
CHANGED