@meltstudio/config-loader 2.0.1 → 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 CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  A type-safe configuration loader for Node.js. Define your schema once, load from YAML or JSON files, `.env` files, environment variables, and CLI arguments — and get a fully typed result with zero manual type annotations.
4
4
 
5
+ > **Upgrading from v1?** v1.x is deprecated. v2 includes breaking changes to the public API, object schema syntax, and requires Node.js >= 20. Install the latest version with `npm install @meltstudio/config-loader@latest` or `yarn add @meltstudio/config-loader@latest`.
6
+
7
+ **[Full documentation](https://meltstudio.github.io/config-loader/)**
8
+
5
9
  ## Why config-loader?
6
10
 
7
11
  Most config libraries give you `Record<string, unknown>` and leave you to cast or validate manually. config-loader infers TypeScript types directly from your schema definition:
@@ -56,7 +60,8 @@ No separate interface to maintain. No `as` casts. The types flow from the schema
56
60
  - **Priority resolution** — CLI > process.env > `.env` files > Config files > Defaults
57
61
  - **`.env` file support** — load environment variables from `.env` files with automatic line tracking
58
62
  - **Nested objects and arrays** — deeply nested configs with full type safety
59
- - **Structured errors** — typed `ConfigLoadError` with per-field error details instead of `process.exit(1)`
63
+ - **Structured errors** — typed `ConfigLoadError` with per-field error details and warnings
64
+ - **Strict mode** — promote warnings to errors for production safety
60
65
  - **Default values** — static or computed (via functions)
61
66
  - **Multiple files / directory loading** — load from a list of files or an entire directory
62
67
 
@@ -274,7 +279,7 @@ Use `loadExtended()` instead of `load()` to get each value wrapped in a `ConfigN
274
279
  ```typescript
275
280
  import c from "@meltstudio/config-loader";
276
281
 
277
- const extended = c
282
+ const { data, warnings } = c
278
283
  .schema({
279
284
  port: c.number({ required: true, env: "PORT" }),
280
285
  host: c.string({ defaultValue: "localhost" }),
@@ -285,7 +290,12 @@ const extended = c
285
290
  files: "./config.yaml",
286
291
  });
287
292
 
288
- // Each leaf is a ConfigNode with:
293
+ // `warnings` is a string[] of non-fatal issues (e.g. type coercions, unused env mappings)
294
+ if (warnings.length > 0) {
295
+ warnings.forEach((w) => console.warn(w));
296
+ }
297
+
298
+ // Each leaf in `data` is a ConfigNode with:
289
299
  // {
290
300
  // value: 3000,
291
301
  // path: "port",
@@ -296,9 +306,9 @@ const extended = c
296
306
  // line: 5 | null, // source line (1-based) for YAML, JSON, and .env files; null for env/args/default
297
307
  // column: 3 | null // source column (1-based) for YAML, JSON, and .env files; null for env/args/default
298
308
  // }
299
- console.log(extended.port.value); // 3000
300
- console.log(extended.port.sourceType); // "env"
301
- console.log(extended.port.variableName); // "PORT"
309
+ console.log(data.port.value); // 3000
310
+ console.log(data.port.sourceType); // "env"
311
+ console.log(data.port.variableName); // "PORT"
302
312
  ```
303
313
 
304
314
  This is useful for debugging configuration resolution, building admin UIs that show where each setting originated, or auditing which sources are active.
@@ -326,12 +336,23 @@ try {
326
336
  }
327
337
  ```
328
338
 
329
- For CLI tools that prefer the old exit-on-error behavior:
339
+ Warnings (non-fatal issues like type coercions) are never printed to the console. Use `loadExtended()` to access them, or they are included in `ConfigLoadError.warnings` when errors occur.
340
+
341
+ ### Strict Mode
342
+
343
+ Enable `strict: true` to promote all warnings to errors, causing `ConfigLoadError` to be thrown for any ambiguous or lossy configuration:
330
344
 
331
345
  ```typescript
332
- .load({ env: true, args: true, files: "./config.yaml", exitOnError: true })
346
+ .load({
347
+ env: true,
348
+ args: false,
349
+ files: "./config.yaml",
350
+ strict: true,
351
+ })
333
352
  ```
334
353
 
354
+ This is useful in production environments where you want to catch type coercions, null values, and other ambiguous config early rather than silently accepting them.
355
+
335
356
  ## CLI Arguments
336
357
 
337
358
  Set `cli: true` on an option to allow overriding via command line:
@@ -413,6 +434,182 @@ The `.env` parser supports:
413
434
 
414
435
  When using `loadExtended()`, values from `.env` files have `sourceType: "envFile"` with `file`, `line`, and `column` metadata pointing to the `.env` file location.
415
436
 
437
+ ## Common Patterns
438
+
439
+ ### Load from YAML with env overrides
440
+
441
+ ```typescript
442
+ import c from "@meltstudio/config-loader";
443
+
444
+ const config = c
445
+ .schema({
446
+ port: c.number({ required: true, env: "PORT", defaultValue: 3000 }),
447
+ host: c.string({ required: true, env: "HOST", defaultValue: "localhost" }),
448
+ })
449
+ .load({ env: true, args: false, files: "./config.yaml" });
450
+ ```
451
+
452
+ ### Strict mode for production
453
+
454
+ ```typescript
455
+ const config = c
456
+ .schema({
457
+ port: c.number({ required: true, env: "PORT" }),
458
+ dbUrl: c.string({ required: true, env: "DATABASE_URL" }),
459
+ })
460
+ .load({
461
+ env: true,
462
+ args: false,
463
+ files: "./config.yaml",
464
+ strict: true, // any type coercion or ambiguity throws an error
465
+ });
466
+ ```
467
+
468
+ ### Catch and inspect errors
469
+
470
+ ```typescript
471
+ import c, { ConfigLoadError } from "@meltstudio/config-loader";
472
+
473
+ try {
474
+ const config = c
475
+ .schema({ port: c.number({ required: true }) })
476
+ .load({ env: false, args: false, files: "./config.yaml" });
477
+ } catch (err) {
478
+ if (err instanceof ConfigLoadError) {
479
+ for (const entry of err.errors) {
480
+ console.error(`[${entry.kind}] ${entry.path}: ${entry.message}`);
481
+ }
482
+ // err.warnings contains non-fatal issues
483
+ }
484
+ }
485
+ ```
486
+
487
+ ### Load from a directory of config files
488
+
489
+ ```typescript
490
+ const config = c
491
+ .schema({
492
+ port: c.number({ required: true }),
493
+ host: c.string({ required: true }),
494
+ })
495
+ .load({ env: false, args: false, dir: "./config.d/" });
496
+ // All YAML/JSON files in the directory are loaded and merged (sorted by filename)
497
+ ```
498
+
499
+ ### Access source metadata with loadExtended
500
+
501
+ ```typescript
502
+ const { data, warnings } = c
503
+ .schema({
504
+ port: c.number({ required: true, env: "PORT" }),
505
+ })
506
+ .loadExtended({ env: true, args: false, files: "./config.yaml" });
507
+
508
+ const portNode = data.port; // ConfigNode
509
+ console.log(portNode.value); // 3000
510
+ console.log(portNode.sourceType); // "env" | "file" | "default" | "args" | "envFile"
511
+ console.log(portNode.file); // "./config.yaml" or null
512
+ console.log(portNode.line); // source line number or null
513
+ ```
514
+
515
+ ### Combine .env files with process.env
516
+
517
+ ```typescript
518
+ const config = c
519
+ .schema({
520
+ apiKey: c.string({ required: true, env: "API_KEY" }),
521
+ debug: c.bool({ env: "DEBUG", defaultValue: false }),
522
+ })
523
+ .load({
524
+ env: true, // reads process.env
525
+ args: false,
526
+ envFile: ["./.env", "./.env.local"], // .env.local overrides .env
527
+ });
528
+ // Priority: process.env > .env.local > .env > defaults
529
+ ```
530
+
531
+ ## Common Mistakes
532
+
533
+ ### Forgetting `item` in `c.object()`
534
+
535
+ ```typescript
536
+ // WRONG — fields are passed directly
537
+ c.object({ host: c.string(), port: c.number() });
538
+
539
+ // CORRECT — fields must be inside `item`
540
+ c.object({ item: { host: c.string(), port: c.number() } });
541
+ ```
542
+
543
+ ### Setting `env` on an option but not enabling env loading
544
+
545
+ ```typescript
546
+ // WRONG — env: "PORT" is set but env loading is disabled
547
+ c.schema({ port: c.number({ env: "PORT" }) }).load({
548
+ env: false,
549
+ args: false,
550
+ files: "./config.yaml",
551
+ });
552
+ // This emits a warning: "Options [port] have env mappings but env loading is disabled"
553
+
554
+ // CORRECT — set env: true in load options
555
+ c.schema({ port: c.number({ env: "PORT" }) }).load({
556
+ env: true,
557
+ args: false,
558
+ files: "./config.yaml",
559
+ });
560
+ ```
561
+
562
+ ### Expecting `.env` files to work without `envFile`
563
+
564
+ ```typescript
565
+ // WRONG — .env files are not loaded by default
566
+ c.schema({ key: c.string({ env: "API_KEY" }) }).load({
567
+ env: true,
568
+ args: false,
569
+ });
570
+ // This only reads process.env, not .env files
571
+
572
+ // CORRECT — explicitly pass envFile
573
+ c.schema({ key: c.string({ env: "API_KEY" }) }).load({
574
+ env: true,
575
+ args: false,
576
+ envFile: "./.env",
577
+ });
578
+ ```
579
+
580
+ ### Not catching `ConfigLoadError`
581
+
582
+ ```typescript
583
+ // WRONG — unhandled error crashes the process with an unhelpful stack trace
584
+ const config = c
585
+ .schema({ port: c.number({ required: true }) })
586
+ .load({ env: false, args: false });
587
+
588
+ // CORRECT — catch and handle structured errors
589
+ try {
590
+ const config = c
591
+ .schema({ port: c.number({ required: true }) })
592
+ .load({ env: false, args: false });
593
+ } catch (err) {
594
+ if (err instanceof ConfigLoadError) {
595
+ console.error(err.errors); // structured error details
596
+ }
597
+ }
598
+ ```
599
+
600
+ ### Using `required: true` on nested fields without the parent object
601
+
602
+ If the parent object is entirely absent from all sources, child `required` fields will still trigger errors. Use `required` on the parent `c.object()` only if the entire subtree must be present.
603
+
604
+ ## Documentation for AI Agents
605
+
606
+ This project provides machine-readable documentation for AI coding agents at the docs site:
607
+
608
+ - **[llms.txt](https://meltstudio.github.io/config-loader/llms.txt)** — structured index of documentation pages
609
+ - **[llms-full.txt](https://meltstudio.github.io/config-loader/llms-full.txt)** — full documentation in a single Markdown file
610
+
611
+ These files follow the [llms.txt standard](https://llmstxt.org/) and are generated automatically at build time. They are designed to be consumed by AI tools like Claude Code, Cursor, GitHub Copilot, and other LLM-based development assistants.
612
+
416
613
  ## License
417
614
 
418
615
  This package is licensed under the Apache License 2.0. See the [LICENSE](./LICENSE) file for details.
package/dist/index.d.ts CHANGED
@@ -14,19 +14,30 @@ interface EnvFileResult {
14
14
  filePath: string;
15
15
  }
16
16
 
17
+ /** A single configuration validation error with optional location metadata. */
17
18
  interface ConfigErrorEntry {
19
+ /** Human-readable error description. */
18
20
  message: string;
21
+ /** Dot-separated path to the offending config key (e.g. `"db.port"`). */
19
22
  path?: string;
23
+ /** The source where the error originated (e.g. file path, `"env"`, `"cli"`). */
20
24
  source?: string;
21
- kind?: "required" | "type_conversion" | "invalid_path" | "invalid_state" | "file_validation";
25
+ /** Classification of the error. */
26
+ kind?: "required" | "type_conversion" | "invalid_path" | "invalid_state" | "file_validation" | "null_value" | "strict";
27
+ /** Line number in the config file where the error occurred, if applicable. */
22
28
  line?: number;
29
+ /** Column number in the config file where the error occurred, if applicable. */
23
30
  column?: number;
24
31
  }
32
+ /** Thrown when configuration loading fails validation. Contains structured error entries and warnings. */
25
33
  declare class ConfigLoadError extends Error {
34
+ /** All validation errors that caused the load to fail. */
26
35
  readonly errors: ConfigErrorEntry[];
36
+ /** Non-fatal warnings collected during loading. */
27
37
  readonly warnings: string[];
28
38
  constructor(errors: ConfigErrorEntry[], warnings: string[]);
29
39
  }
40
+ /** Thrown when a configuration file cannot be read or parsed. */
30
41
  declare class ConfigFileError extends ConfigLoadError {
31
42
  constructor(message: string);
32
43
  }
@@ -93,21 +104,37 @@ declare class PrimitiveOption<T extends PrimitiveKind = PrimitiveKind> extends O
93
104
  type NodeTree = {
94
105
  [key: string]: NodeTree | ConfigNode;
95
106
  };
107
+ /** Result returned by `SettingsBuilder.loadExtended()`, including raw node data and warnings. */
108
+ type ExtendedResult = {
109
+ /** Tree of `ConfigNode` objects preserving source metadata for each resolved value. */
110
+ data: NodeTree;
111
+ /** Non-fatal warnings collected during loading. */
112
+ warnings: string[];
113
+ };
96
114
  type RecursivePartial<T> = {
97
115
  [K in keyof T]?: RecursivePartial<T[K]>;
98
116
  };
117
+ /** Configuration sources passed to `SettingsBuilder.load()` / `loadExtended()`. Priority: CLI > Env > Files > Defaults. */
99
118
  type SettingsSources<T> = {
119
+ /** Whether to read values from `process.env`. */
100
120
  env: boolean;
121
+ /** Whether to parse CLI arguments via Commander. */
101
122
  args: boolean;
123
+ /** YAML/JSON file path(s) to load, or `false` to skip. */
102
124
  files?: string | string[] | false;
125
+ /** Directory to scan for config files, or `false` to skip. */
103
126
  dir?: string | false;
127
+ /** `.env` file path(s) to load, or `false` to skip. */
104
128
  envFile?: string | string[] | false;
129
+ /** Partial default values applied at the lowest priority. */
105
130
  defaults?: RecursivePartial<T>;
106
- exitOnError?: boolean;
131
+ /** When `true`, unknown keys in config files cause errors. */
132
+ strict?: boolean;
107
133
  };
108
134
  type OptionKind = "boolean" | "string" | "number" | "array" | "object";
109
135
  type PrimitiveKind = Extract<OptionKind, "boolean" | "string" | "number">;
110
136
  type TypeOfPrimitiveKind<T extends PrimitiveKind> = T extends "boolean" ? boolean : T extends "string" ? string : T extends "number" ? number : never;
137
+ /** Recursively infers the plain TypeScript type from a schema definition. Maps option nodes to their resolved value types. */
111
138
  type SchemaValue<T extends OptionBase | Node> = T extends OptionBase ? T extends ArrayOption<OptionTypes> ? SchemaValue<T["item"]>[] : T extends ObjectOption<infer R> ? {
112
139
  [K in keyof R]: SchemaValue<R[K]>;
113
140
  } : T extends PrimitiveOption<infer R> ? TypeOfPrimitiveKind<R> : never : T extends Node ? {
@@ -146,29 +173,68 @@ declare class ObjectOption<T extends Node = Node> extends OptionBase<"object"> {
146
173
 
147
174
  type OptionTypes = PrimitiveOption | ArrayOption<OptionTypes> | ObjectOption<Node>;
148
175
 
176
+ /** Fluent builder that takes a schema and resolves configuration from multiple sources. */
149
177
  declare class SettingsBuilder<T extends Node> {
150
178
  private readonly schema;
151
179
  constructor(schema: T);
180
+ /**
181
+ * Loads and validates configuration, returning a fully-typed plain object.
182
+ * @param sources - Which sources to read (env, args, files, etc.).
183
+ * @returns The resolved configuration object matching the schema type.
184
+ * @throws {ConfigLoadError} If validation fails (missing required fields, type errors, etc.).
185
+ */
152
186
  load(sources: SettingsSources<SchemaValue<T>>): SchemaValue<T>;
153
- loadExtended(sources: SettingsSources<SchemaValue<T>>): NodeTree;
187
+ /**
188
+ * Loads configuration and returns raw node data with source metadata alongside warnings.
189
+ * @param sources - Which sources to read (env, args, files, etc.).
190
+ * @returns An `ExtendedResult` containing the node tree and any warnings.
191
+ * @throws {ConfigLoadError} If validation fails.
192
+ */
193
+ loadExtended(sources: SettingsSources<SchemaValue<T>>): ExtendedResult;
154
194
  }
155
195
 
196
+ /** Options for configuring a primitive (`string`, `number`, `bool`) schema field. */
156
197
  interface OptionPropsArgs<T> {
198
+ /** Whether the field must be present in at least one source. */
157
199
  required?: boolean;
200
+ /** Environment variable name to read from, or `null` to disable. */
158
201
  env?: string | null;
202
+ /** Whether to expose this field as a CLI argument via Commander. */
159
203
  cli?: boolean;
204
+ /** Static default value or factory function returning one. */
160
205
  defaultValue?: T | (() => T);
206
+ /** Help text shown in CLI `--help` output. */
161
207
  help?: string;
162
208
  }
209
+ /** Options for configuring an `array` schema field. */
163
210
  interface ArrayOptionPropsArgs<T extends OptionTypes> {
211
+ /** Whether the field must be present in at least one source. */
164
212
  required?: boolean;
213
+ /** Schema definition for each item in the array. */
165
214
  item: T;
215
+ /** Static default value or factory function returning one. */
166
216
  defaultValue?: SchemaValue<T>[] | (() => SchemaValue<T>[]);
167
217
  }
218
+ /** Options for configuring a nested `object` schema field. */
168
219
  interface ObjectOptionPropsArgs<T extends Node> {
220
+ /** Whether the field must be present in at least one source. */
169
221
  required?: boolean;
222
+ /** Schema definition for the nested object's shape. */
170
223
  item: T;
171
224
  }
225
+ /**
226
+ * Config-loader entry point. Provides factory functions to define a typed configuration schema.
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * import c from "@meltstudio/config-loader";
231
+ *
232
+ * const config = c.schema({
233
+ * port: c.number({ env: "PORT", defaultValue: 3000 }),
234
+ * host: c.string({ env: "HOST" }),
235
+ * }).load({ env: true, args: false });
236
+ * ```
237
+ */
172
238
  declare const option: {
173
239
  string: (opts?: OptionPropsArgs<string>) => PrimitiveOption<"string">;
174
240
  number: (opts?: OptionPropsArgs<number>) => PrimitiveOption<"number">;
@@ -178,4 +244,4 @@ declare const option: {
178
244
  schema: <T extends Node>(theSchema: T) => SettingsBuilder<T>;
179
245
  };
180
246
 
181
- export { type ConfigErrorEntry, ConfigFileError, ConfigLoadError, option as default };
247
+ export { type ConfigErrorEntry, ConfigFileError, ConfigLoadError, type ExtendedResult, option as default };
package/dist/index.js CHANGED
@@ -41,7 +41,9 @@ var import_commander = require("commander");
41
41
 
42
42
  // src/errors.ts
43
43
  var ConfigLoadError = class extends Error {
44
+ /** All validation errors that caused the load to fail. */
44
45
  errors;
46
+ /** Non-fatal warnings collected during loading. */
45
47
  warnings;
46
48
  constructor(errors, warnings) {
47
49
  const message = `Configuration loading failed with ${errors.length} error${errors.length === 1 ? "" : "s"}`;
@@ -286,7 +288,7 @@ var OptionBase = class {
286
288
  if (this.params.env && env) {
287
289
  if (this.params.env in env) {
288
290
  const val = env[this.params.env];
289
- if (val) {
291
+ if (val !== void 0) {
290
292
  const envFileSource = findEnvFileSource(
291
293
  this.params.env,
292
294
  val,
@@ -448,6 +450,12 @@ var OptionBase = class {
448
450
  );
449
451
  return val.toString();
450
452
  }
453
+ if (typeof val === "boolean") {
454
+ errors?.warnings.push(
455
+ `The option ${ident} is stated as a string but is provided as a boolean`
456
+ );
457
+ return val.toString();
458
+ }
451
459
  errors?.errors.push({
452
460
  message: `Cannot convert value '${valueToString(
453
461
  val
@@ -460,10 +468,11 @@ var OptionBase = class {
460
468
  }
461
469
  if (this.params.kind === "boolean") {
462
470
  if (typeof val !== "boolean" && typeof val !== "object") {
463
- if ([1, "1", "true"].includes(val)) {
471
+ const normalized = typeof val === "string" ? val.toLowerCase() : val;
472
+ if ([1, "1", "true", "yes"].includes(normalized)) {
464
473
  return true;
465
474
  }
466
- if ([0, "0", "false"].includes(val)) {
475
+ if ([0, "0", "false", "no"].includes(normalized)) {
467
476
  return false;
468
477
  }
469
478
  }
@@ -521,13 +530,33 @@ var OptionBase = class {
521
530
  });
522
531
  return new InvalidValue();
523
532
  }
524
- if (val == null) {
533
+ if (val === void 0) {
534
+ return new InvalidValue();
535
+ }
536
+ if (val === null) {
537
+ errors?.errors.push({
538
+ message: `Option '${path2.join(".")}' is null \u2014 expected an object to traverse into`,
539
+ kind: "null_value"
540
+ });
525
541
  return new InvalidValue();
526
542
  }
527
543
  return this.findInObject(val, rest, errors);
528
544
  }
529
545
  if (path2.length === 1) {
530
546
  const val = obj[path2[0]];
547
+ if (val === null) {
548
+ if (this.params.required) {
549
+ errors?.errors.push({
550
+ message: `Option '${path2.join(".")}' is null \u2014 expected a ${this.params.kind}`,
551
+ kind: "null_value"
552
+ });
553
+ } else {
554
+ errors?.warnings.push(
555
+ `Option '${path2.join(".")}' is null and will be treated as unset`
556
+ );
557
+ }
558
+ return new InvalidValue();
559
+ }
531
560
  if (!Array.isArray(val) && typeof val === "object" && val || typeof val === "string" || typeof val === "number" || typeof val === "boolean" || typeof val === "undefined") {
532
561
  return val;
533
562
  }
@@ -754,8 +783,23 @@ var Settings = class {
754
783
  this.envFileResults = envFileResults;
755
784
  this.envData = mergedEnvData;
756
785
  }
786
+ checkEnvMappingsWithoutEnvLoading() {
787
+ if (this.sources.env || this.sources.envFile) return;
788
+ const envMappedOptions = [];
789
+ this.traverseOptions(this.schema, [], (node, path2) => {
790
+ if (node.params.env) {
791
+ envMappedOptions.push(path2.join("."));
792
+ }
793
+ });
794
+ if (envMappedOptions.length > 0) {
795
+ this.errors.warnings.push(
796
+ `Options [${envMappedOptions.join(", ")}] have env mappings but env loading is disabled. Set 'env: true' in load options to read from environment variables.`
797
+ );
798
+ }
799
+ }
757
800
  load() {
758
801
  this.validateAndLoadFiles();
802
+ this.checkEnvMappingsWithoutEnvLoading();
759
803
  if (this.sources.env) {
760
804
  this.envData = { ...process.env };
761
805
  }
@@ -780,18 +824,13 @@ var Settings = class {
780
824
  errors: this.errors
781
825
  })
782
826
  );
783
- if (this.errors.warnings.length > 0) {
784
- for (let index = 0; index < this.errors.warnings.length; index += 1) {
785
- console.warn(`[Warning]: ${this.errors.warnings[index]}`);
827
+ if (this.sources.strict && this.errors.warnings.length > 0) {
828
+ for (const warning of this.errors.warnings) {
829
+ this.errors.errors.push({ message: warning, kind: "strict" });
786
830
  }
831
+ this.errors.warnings = [];
787
832
  }
788
833
  if (this.errors.errors.length > 0) {
789
- if (this.sources.exitOnError) {
790
- for (let index = 0; index < this.errors.errors.length; index += 1) {
791
- console.error(`[Error]: ${this.errors.errors[index].message}`);
792
- }
793
- process.exit(1);
794
- }
795
834
  throw new ConfigLoadError(
796
835
  [...this.errors.errors],
797
836
  [...this.errors.warnings]
@@ -922,8 +961,7 @@ var Settings = class {
922
961
  return node.value;
923
962
  }
924
963
  return Object.entries(node).reduce(
925
- (acc, item) => {
926
- const [key, value] = item;
964
+ (acc, [key, value]) => {
927
965
  acc[key] = this.getValuesFromTree(value);
928
966
  return acc;
929
967
  },
@@ -938,6 +976,9 @@ var Settings = class {
938
976
  getExtended() {
939
977
  return this.optionsTree;
940
978
  }
979
+ getWarnings() {
980
+ return [...this.errors.warnings];
981
+ }
941
982
  };
942
983
  var settings_default = Settings;
943
984
 
@@ -947,13 +988,28 @@ var SettingsBuilder = class {
947
988
  constructor(schema2) {
948
989
  this.schema = schema2;
949
990
  }
991
+ /**
992
+ * Loads and validates configuration, returning a fully-typed plain object.
993
+ * @param sources - Which sources to read (env, args, files, etc.).
994
+ * @returns The resolved configuration object matching the schema type.
995
+ * @throws {ConfigLoadError} If validation fails (missing required fields, type errors, etc.).
996
+ */
950
997
  load(sources) {
951
998
  const settings = new settings_default(this.schema, sources);
952
999
  return settings.get();
953
1000
  }
1001
+ /**
1002
+ * Loads configuration and returns raw node data with source metadata alongside warnings.
1003
+ * @param sources - Which sources to read (env, args, files, etc.).
1004
+ * @returns An `ExtendedResult` containing the node tree and any warnings.
1005
+ * @throws {ConfigLoadError} If validation fails.
1006
+ */
954
1007
  loadExtended(sources) {
955
1008
  const settings = new settings_default(this.schema, sources);
956
- return settings.getExtended();
1009
+ return {
1010
+ data: settings.getExtended(),
1011
+ warnings: settings.getWarnings()
1012
+ };
957
1013
  }
958
1014
  };
959
1015
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meltstudio/config-loader",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "Melt Studio's tool for loading configurations into a Node.js application.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -53,6 +53,7 @@
53
53
  "@types/jest": "^30.0.0",
54
54
  "@types/js-yaml": "^4.0.5",
55
55
  "@types/node": "^22.0.0",
56
+ "docusaurus-plugin-llms": "^0.3.0",
56
57
  "eslint": "^10.0.0",
57
58
  "eslint-config-prettier": "^10.0.0",
58
59
  "eslint-plugin-jest": "^29.0.0",