@optique/core 0.10.0-dev.293 → 0.10.0-dev.295

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.
@@ -1,5 +1,5 @@
1
1
  import { message, optionName, values } from "./message.js";
2
- import { DependencyId, DependencyRegistry, ParseWithDependency, isDeferredParseState, isDependencySourceState } from "./dependency.js";
2
+ import { DependencyId, DependencyRegistry, ParseWithDependency, WrappedDependencySourceMarker, isDeferredParseState, isDependencySourceState, isPendingDependencySourceState, isWrappedDependencySource } from "./dependency.js";
3
3
  import { extractArgumentMetavars, extractCommandNames, extractOptionNames } from "./usage.js";
4
4
  import { createErrorWithSuggestions, deduplicateSuggestions } from "./suggestion.js";
5
5
 
@@ -541,32 +541,145 @@ async function* suggestObjectAsync(context, prefix, parserPairs) {
541
541
  * @returns The field states with deferred states resolved to their actual values
542
542
  * @internal
543
543
  */
544
+ /**
545
+ * Recursively collects dependency values from DependencySourceState objects
546
+ * found anywhere in the state tree.
547
+ */
548
+ function collectDependencies(state, registry) {
549
+ if (state === null || state === void 0) return;
550
+ if (isDependencySourceState(state)) {
551
+ const depId = state[DependencyId];
552
+ const result = state.result;
553
+ if (result.success) registry.set(depId, result.value);
554
+ return;
555
+ }
556
+ if (Array.isArray(state)) {
557
+ for (const item of state) collectDependencies(item, registry);
558
+ return;
559
+ }
560
+ if (typeof state === "object" && !isDeferredParseState(state)) for (const key of Reflect.ownKeys(state)) collectDependencies(state[key], registry);
561
+ }
562
+ /**
563
+ * Checks if a value is a plain object (created with `{}` or `Object.create(null)`).
564
+ * Class instances like `Temporal.PlainDate`, `URL`, `Date`, etc. return false.
565
+ * This is used to determine whether to recursively traverse an object when
566
+ * resolving deferred parse states - we only want to traverse plain objects
567
+ * that are part of the parser state structure, not user values.
568
+ */
569
+ function isPlainObject(value) {
570
+ if (typeof value !== "object" || value === null) return false;
571
+ const proto = Object.getPrototypeOf(value);
572
+ return proto === Object.prototype || proto === null;
573
+ }
574
+ /**
575
+ * Recursively resolves DeferredParseState objects found anywhere in the state tree.
576
+ * Returns the resolved state (sync version).
577
+ *
578
+ * Only traverses:
579
+ * - DeferredParseState (to resolve it)
580
+ * - DependencySourceState (skipped, kept as-is)
581
+ * - Arrays (to find nested deferred states)
582
+ * - Plain objects (to find nested deferred states in parser state structures)
583
+ *
584
+ * Does NOT traverse class instances (e.g., Temporal.PlainDate, URL) since these
585
+ * are user values that should be preserved as-is.
586
+ */
587
+ function resolveDeferred(state, registry) {
588
+ if (state === null || state === void 0) return state;
589
+ if (isDeferredParseState(state)) {
590
+ const deferredState = state;
591
+ const parser = deferredState.parser;
592
+ const depIds = deferredState.dependencyIds;
593
+ if (depIds && depIds.length > 0) {
594
+ const dependencyValues = [];
595
+ let allDepsAvailable = true;
596
+ for (const depId$1 of depIds) if (registry.has(depId$1)) dependencyValues.push(registry.get(depId$1));
597
+ else {
598
+ allDepsAvailable = false;
599
+ break;
600
+ }
601
+ if (allDepsAvailable) {
602
+ const reParseResult = parser[ParseWithDependency](deferredState.rawInput, dependencyValues);
603
+ if (reParseResult instanceof Promise) return deferredState.preliminaryResult;
604
+ return reParseResult;
605
+ }
606
+ return deferredState.preliminaryResult;
607
+ }
608
+ const depId = deferredState.dependencyId;
609
+ if (registry.has(depId)) {
610
+ const dependencyValue = registry.get(depId);
611
+ const reParseResult = parser[ParseWithDependency](deferredState.rawInput, dependencyValue);
612
+ if (reParseResult instanceof Promise) return deferredState.preliminaryResult;
613
+ return reParseResult;
614
+ }
615
+ return deferredState.preliminaryResult;
616
+ }
617
+ if (isDependencySourceState(state)) return state;
618
+ if (Array.isArray(state)) return state.map((item) => resolveDeferred(item, registry));
619
+ if (isPlainObject(state)) {
620
+ const resolved = {};
621
+ for (const key of Reflect.ownKeys(state)) resolved[key] = resolveDeferred(state[key], registry);
622
+ return resolved;
623
+ }
624
+ return state;
625
+ }
544
626
  function resolveDeferredParseStates(fieldStates) {
545
627
  const registry = new DependencyRegistry();
546
- for (const key of Reflect.ownKeys(fieldStates)) {
547
- const fieldState = fieldStates[key];
548
- if (isDependencySourceState(fieldState)) {
549
- const depId = fieldState[DependencyId];
550
- const result = fieldState.result;
551
- if (result.success) registry.set(depId, result.value);
628
+ collectDependencies(fieldStates, registry);
629
+ return resolveDeferred(fieldStates, registry);
630
+ }
631
+ /**
632
+ * Recursively resolves DeferredParseState objects found anywhere in the state tree.
633
+ * Returns the resolved state (async version).
634
+ *
635
+ * Only traverses:
636
+ * - DeferredParseState (to resolve it)
637
+ * - DependencySourceState (skipped, kept as-is)
638
+ * - Arrays (to find nested deferred states)
639
+ * - Plain objects (to find nested deferred states in parser state structures)
640
+ *
641
+ * Does NOT traverse class instances (e.g., Temporal.PlainDate, URL) since these
642
+ * are user values that should be preserved as-is.
643
+ */
644
+ async function resolveDeferredAsync(state, registry) {
645
+ if (state === null || state === void 0) return state;
646
+ if (isDeferredParseState(state)) {
647
+ const deferredState = state;
648
+ const parser = deferredState.parser;
649
+ const depIds = deferredState.dependencyIds;
650
+ if (depIds && depIds.length > 0) {
651
+ const dependencyValues = [];
652
+ let allDepsAvailable = true;
653
+ for (const depId$1 of depIds) if (registry.has(depId$1)) dependencyValues.push(registry.get(depId$1));
654
+ else {
655
+ allDepsAvailable = false;
656
+ break;
657
+ }
658
+ if (allDepsAvailable) {
659
+ const reParseResult = parser[ParseWithDependency](deferredState.rawInput, dependencyValues);
660
+ return Promise.resolve(reParseResult);
661
+ }
662
+ return deferredState.preliminaryResult;
552
663
  }
553
- }
554
- const resolvedStates = { ...fieldStates };
555
- for (const key of Reflect.ownKeys(fieldStates)) {
556
- const fieldState = fieldStates[key];
557
- if (isDeferredParseState(fieldState)) {
558
- const deferredState = fieldState;
559
- const depId = deferredState.dependencyId;
560
- if (registry.has(depId)) {
561
- const dependencyValue = registry.get(depId);
562
- const parser = deferredState.parser;
563
- const reParseResult = parser[ParseWithDependency](deferredState.rawInput, dependencyValue);
564
- if (reParseResult instanceof Promise) resolvedStates[key] = deferredState.preliminaryResult;
565
- else resolvedStates[key] = reParseResult;
566
- } else resolvedStates[key] = deferredState.preliminaryResult;
664
+ const depId = deferredState.dependencyId;
665
+ if (registry.has(depId)) {
666
+ const dependencyValue = registry.get(depId);
667
+ const reParseResult = parser[ParseWithDependency](deferredState.rawInput, dependencyValue);
668
+ return Promise.resolve(reParseResult);
567
669
  }
670
+ return deferredState.preliminaryResult;
671
+ }
672
+ if (isDependencySourceState(state)) return state;
673
+ if (Array.isArray(state)) return Promise.all(state.map((item) => resolveDeferredAsync(item, registry)));
674
+ if (isPlainObject(state)) {
675
+ const resolved = {};
676
+ const keys = Reflect.ownKeys(state);
677
+ await Promise.all(keys.map(async (key) => {
678
+ resolved[key] = await resolveDeferredAsync(state[key], registry);
679
+ }));
680
+ return resolved;
568
681
  }
569
- return resolvedStates;
682
+ return state;
570
683
  }
571
684
  /**
572
685
  * Async version of resolveDeferredParseStates for async parsers.
@@ -574,29 +687,8 @@ function resolveDeferredParseStates(fieldStates) {
574
687
  */
575
688
  async function resolveDeferredParseStatesAsync(fieldStates) {
576
689
  const registry = new DependencyRegistry();
577
- for (const key of Reflect.ownKeys(fieldStates)) {
578
- const fieldState = fieldStates[key];
579
- if (isDependencySourceState(fieldState)) {
580
- const depId = fieldState[DependencyId];
581
- const result = fieldState.result;
582
- if (result.success) registry.set(depId, result.value);
583
- }
584
- }
585
- const resolvedStates = { ...fieldStates };
586
- for (const key of Reflect.ownKeys(fieldStates)) {
587
- const fieldState = fieldStates[key];
588
- if (isDeferredParseState(fieldState)) {
589
- const deferredState = fieldState;
590
- const depId = deferredState.dependencyId;
591
- if (registry.has(depId)) {
592
- const dependencyValue = registry.get(depId);
593
- const parser = deferredState.parser;
594
- const reParseResult = parser[ParseWithDependency](deferredState.rawInput, dependencyValue);
595
- resolvedStates[key] = await Promise.resolve(reParseResult);
596
- } else resolvedStates[key] = deferredState.preliminaryResult;
597
- }
598
- }
599
- return resolvedStates;
690
+ collectDependencies(fieldStates, registry);
691
+ return await resolveDeferredAsync(fieldStates, registry);
600
692
  }
601
693
  function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
602
694
  const label = typeof labelOrParsers === "string" ? labelOrParsers : void 0;
@@ -756,12 +848,45 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
756
848
  return parseSync(context);
757
849
  },
758
850
  complete(state) {
759
- const resolvedState = isAsync ? state : resolveDeferredParseStates(state);
760
851
  if (!isAsync) {
852
+ const preCompletedState = {};
853
+ for (const field of parserKeys) {
854
+ const fieldKey = field;
855
+ const fieldState = state[fieldKey];
856
+ const fieldParser = parsers[field];
857
+ if (Array.isArray(fieldState) && fieldState.length === 1 && isPendingDependencySourceState(fieldState[0])) {
858
+ const completed = fieldParser.complete(fieldState);
859
+ preCompletedState[fieldKey] = completed;
860
+ } else if (fieldState === void 0 && isPendingDependencySourceState(fieldParser.initialState)) {
861
+ const completed = fieldParser.complete([fieldParser.initialState]);
862
+ preCompletedState[fieldKey] = completed;
863
+ } else if (fieldState === void 0 && isWrappedDependencySource(fieldParser)) {
864
+ const pendingState = fieldParser[WrappedDependencySourceMarker];
865
+ const completed = fieldParser.complete([pendingState]);
866
+ preCompletedState[fieldKey] = completed;
867
+ } else preCompletedState[fieldKey] = fieldState;
868
+ }
869
+ const resolvedState = resolveDeferredParseStates(preCompletedState);
761
870
  const result = {};
762
871
  for (const field of parserKeys) {
763
- const valueResult = parsers[field].complete(resolvedState[field]);
764
- if (valueResult.success) result[field] = valueResult.value;
872
+ const fieldKey = field;
873
+ const fieldResolvedState = resolvedState[fieldKey];
874
+ const fieldParser = parsers[field];
875
+ const originalFieldState = state[fieldKey];
876
+ const wasPreCompletedCase1 = Array.isArray(originalFieldState) && originalFieldState.length === 1 && isPendingDependencySourceState(originalFieldState[0]);
877
+ const wasPreCompletedCase2 = originalFieldState === void 0 && isPendingDependencySourceState(fieldParser.initialState);
878
+ const wasPreCompletedCase3 = originalFieldState === void 0 && isWrappedDependencySource(fieldParser);
879
+ if (isDependencySourceState(fieldResolvedState) && (wasPreCompletedCase1 || wasPreCompletedCase2 || wasPreCompletedCase3)) {
880
+ const depResult = fieldResolvedState.result;
881
+ if (depResult.success) result[fieldKey] = depResult.value;
882
+ else return {
883
+ success: false,
884
+ error: depResult.error
885
+ };
886
+ continue;
887
+ }
888
+ const valueResult = fieldParser.complete(fieldResolvedState);
889
+ if (valueResult.success) result[fieldKey] = valueResult.value;
765
890
  else return {
766
891
  success: false,
767
892
  error: valueResult.error
@@ -773,11 +898,44 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
773
898
  };
774
899
  }
775
900
  return (async () => {
776
- const resolvedState$1 = await resolveDeferredParseStatesAsync(state);
901
+ const preCompletedState = {};
902
+ for (const field of parserKeys) {
903
+ const fieldKey = field;
904
+ const fieldState = state[fieldKey];
905
+ const fieldParser = parsers[field];
906
+ if (Array.isArray(fieldState) && fieldState.length === 1 && isPendingDependencySourceState(fieldState[0])) {
907
+ const completed = await fieldParser.complete(fieldState);
908
+ preCompletedState[fieldKey] = completed;
909
+ } else if (fieldState === void 0 && isPendingDependencySourceState(fieldParser.initialState)) {
910
+ const completed = await fieldParser.complete([fieldParser.initialState]);
911
+ preCompletedState[fieldKey] = completed;
912
+ } else if (fieldState === void 0 && isWrappedDependencySource(fieldParser)) {
913
+ const pendingState = fieldParser[WrappedDependencySourceMarker];
914
+ const completed = await fieldParser.complete([pendingState]);
915
+ preCompletedState[fieldKey] = completed;
916
+ } else preCompletedState[fieldKey] = fieldState;
917
+ }
918
+ const resolvedState = await resolveDeferredParseStatesAsync(preCompletedState);
777
919
  const result = {};
778
920
  for (const field of parserKeys) {
779
- const valueResult = await parsers[field].complete(resolvedState$1[field]);
780
- if (valueResult.success) result[field] = valueResult.value;
921
+ const fieldKey = field;
922
+ const fieldResolvedState = resolvedState[fieldKey];
923
+ const fieldParser = parsers[field];
924
+ const originalFieldState = state[fieldKey];
925
+ const wasPreCompletedCase1 = Array.isArray(originalFieldState) && originalFieldState.length === 1 && isPendingDependencySourceState(originalFieldState[0]);
926
+ const wasPreCompletedCase2 = originalFieldState === void 0 && isPendingDependencySourceState(fieldParser.initialState);
927
+ const wasPreCompletedCase3 = originalFieldState === void 0 && isWrappedDependencySource(fieldParser);
928
+ if (isDependencySourceState(fieldResolvedState) && (wasPreCompletedCase1 || wasPreCompletedCase2 || wasPreCompletedCase3)) {
929
+ const depResult = fieldResolvedState.result;
930
+ if (depResult.success) result[fieldKey] = depResult.value;
931
+ else return {
932
+ success: false,
933
+ error: depResult.error
934
+ };
935
+ continue;
936
+ }
937
+ const valueResult = await fieldParser.complete(fieldResolvedState);
938
+ if (valueResult.success) result[fieldKey] = valueResult.value;
781
939
  else return {
782
940
  success: false,
783
941
  error: valueResult.error
@@ -1220,15 +1378,15 @@ function merge(...args) {
1220
1378
  return parseSync(context);
1221
1379
  },
1222
1380
  complete(state) {
1223
- const extractCompleteState = (parser, index) => {
1381
+ const extractCompleteState = (parser, resolvedState, index) => {
1224
1382
  if (parser.initialState === void 0) {
1225
1383
  const key = `__parser_${index}`;
1226
- if (state && typeof state === "object" && key in state) return state[key];
1384
+ if (resolvedState && typeof resolvedState === "object" && key in resolvedState) return resolvedState[key];
1227
1385
  return void 0;
1228
1386
  } else if (parser.initialState && typeof parser.initialState === "object") {
1229
- if (state && typeof state === "object") {
1387
+ if (resolvedState && typeof resolvedState === "object") {
1230
1388
  const extractedState = {};
1231
- for (const field in parser.initialState) extractedState[field] = field in state ? state[field] : parser.initialState[field];
1389
+ for (const field in parser.initialState) extractedState[field] = field in resolvedState ? resolvedState[field] : parser.initialState[field];
1232
1390
  return extractedState;
1233
1391
  }
1234
1392
  return parser.initialState;
@@ -1236,10 +1394,11 @@ function merge(...args) {
1236
1394
  return parser.initialState;
1237
1395
  };
1238
1396
  if (!isAsync) {
1397
+ const resolvedState = resolveDeferredParseStates(state);
1239
1398
  const object$1 = {};
1240
1399
  for (let i = 0; i < syncParsers.length; i++) {
1241
1400
  const parser = syncParsers[i];
1242
- const parserState = extractCompleteState(parser, i);
1401
+ const parserState = extractCompleteState(parser, resolvedState, i);
1243
1402
  const result = parser.complete(parserState);
1244
1403
  if (!result.success) return result;
1245
1404
  for (const field in result.value) object$1[field] = result.value[field];
@@ -1250,10 +1409,11 @@ function merge(...args) {
1250
1409
  };
1251
1410
  }
1252
1411
  return (async () => {
1412
+ const resolvedState = await resolveDeferredParseStatesAsync(state);
1253
1413
  const object$1 = {};
1254
1414
  for (let i = 0; i < parsers.length; i++) {
1255
1415
  const parser = parsers[i];
1256
- const parserState = extractCompleteState(parser, i);
1416
+ const parserState = extractCompleteState(parser, resolvedState, i);
1257
1417
  const result = await parser.complete(parserState);
1258
1418
  if (!result.success) return result;
1259
1419
  for (const field in result.value) object$1[field] = result.value[field];
@@ -1482,11 +1642,14 @@ function concat(...parsers) {
1482
1642
  };
1483
1643
  };
1484
1644
  const completeSync = (state) => {
1485
- const results = [];
1486
1645
  const stateArray = state;
1646
+ const combinedState = {};
1647
+ for (let i = 0; i < stateArray.length; i++) combinedState[i] = stateArray[i];
1648
+ const resolvedCombinedState = resolveDeferredParseStates(combinedState);
1649
+ const results = [];
1487
1650
  for (let i = 0; i < syncParsers.length; i++) {
1488
1651
  const parser = syncParsers[i];
1489
- const parserState = stateArray[i];
1652
+ const parserState = resolvedCombinedState[i];
1490
1653
  const result = parser.complete(parserState);
1491
1654
  if (!result.success) return result;
1492
1655
  if (Array.isArray(result.value)) results.push(...result.value);
@@ -1498,11 +1661,14 @@ function concat(...parsers) {
1498
1661
  };
1499
1662
  };
1500
1663
  const completeAsync = async (state) => {
1501
- const results = [];
1502
1664
  const stateArray = state;
1665
+ const combinedState = {};
1666
+ for (let i = 0; i < stateArray.length; i++) combinedState[i] = stateArray[i];
1667
+ const resolvedCombinedState = await resolveDeferredParseStatesAsync(combinedState);
1668
+ const results = [];
1503
1669
  for (let i = 0; i < parsers.length; i++) {
1504
1670
  const parser = parsers[i];
1505
- const parserState = stateArray[i];
1671
+ const parserState = resolvedCombinedState[i];
1506
1672
  const result = await parser.complete(parserState);
1507
1673
  if (!result.success) return result;
1508
1674
  if (Array.isArray(result.value)) results.push(...result.value);
@@ -20,6 +20,12 @@ const DerivedValueParserMarker = Symbol.for("@optique/core/dependency/DerivedVal
20
20
  */
21
21
  const DependencyId = Symbol.for("@optique/core/dependency/DependencyId");
22
22
  /**
23
+ * A unique symbol used to store multiple dependency IDs on derived parsers
24
+ * that depend on multiple sources (created via {@link deriveFrom}).
25
+ * @since 0.10.0
26
+ */
27
+ const DependencyIds = Symbol.for("@optique/core/dependency/DependencyIds");
28
+ /**
23
29
  * A unique symbol used to access the parseWithDependency method on derived parsers.
24
30
  * @since 0.10.0
25
31
  */
@@ -181,11 +187,13 @@ function determineFactoryModeForDeriveFrom(options) {
181
187
  return parser.$mode === "async";
182
188
  }
183
189
  function createSyncDerivedFromParser(sourceId, options) {
190
+ const allDependencyIds = options.dependencies.map((dep) => dep[DependencyId]);
184
191
  return {
185
192
  $mode: "sync",
186
193
  metavar: options.metavar,
187
194
  [DerivedValueParserMarker]: true,
188
195
  [DependencyId]: sourceId,
196
+ [DependencyIds]: allDependencyIds,
189
197
  parse(input) {
190
198
  const sourceValues = options.defaultValues();
191
199
  const derivedParser = options.factory(...sourceValues);
@@ -212,11 +220,13 @@ function createSyncDerivedFromParser(sourceId, options) {
212
220
  * factory returns an async parser.
213
221
  */
214
222
  function createAsyncDerivedFromParserFromAsyncFactory(sourceId, options) {
223
+ const allDependencyIds = options.dependencies.map((dep) => dep[DependencyId]);
215
224
  return {
216
225
  $mode: "async",
217
226
  metavar: options.metavar,
218
227
  [DerivedValueParserMarker]: true,
219
228
  [DependencyId]: sourceId,
229
+ [DependencyIds]: allDependencyIds,
220
230
  parse(input) {
221
231
  const sourceValues = options.defaultValues();
222
232
  const derivedParser = options.factory(...sourceValues);
@@ -243,11 +253,13 @@ function createAsyncDerivedFromParserFromAsyncFactory(sourceId, options) {
243
253
  * sources are async but the factory returns a sync parser.
244
254
  */
245
255
  function createAsyncDerivedFromParserFromSyncFactory(sourceId, options) {
256
+ const allDependencyIds = options.dependencies.map((dep) => dep[DependencyId]);
246
257
  return {
247
258
  $mode: "async",
248
259
  metavar: options.metavar,
249
260
  [DerivedValueParserMarker]: true,
250
261
  [DependencyId]: sourceId,
262
+ [DependencyIds]: allDependencyIds,
251
263
  parse(input) {
252
264
  const sourceValues = options.defaultValues();
253
265
  const derivedParser = options.factory(...sourceValues);
@@ -403,11 +415,13 @@ function isDeferredParseState(value) {
403
415
  * @since 0.10.0
404
416
  */
405
417
  function createDeferredParseState(rawInput, parser, preliminaryResult) {
418
+ const multipleIds = DependencyIds in parser ? parser[DependencyIds] : void 0;
406
419
  return {
407
420
  [DeferredParseMarker]: true,
408
421
  rawInput,
409
422
  parser,
410
423
  dependencyId: parser[DependencyId],
424
+ dependencyIds: multipleIds,
411
425
  preliminaryResult
412
426
  };
413
427
  }
@@ -443,6 +457,51 @@ function createDependencySourceState(result, dependencyId) {
443
457
  };
444
458
  }
445
459
  /**
460
+ * A unique symbol used to identify pending dependency source states.
461
+ * @since 0.10.0
462
+ */
463
+ const PendingDependencySourceStateMarker = Symbol.for("@optique/core/dependency/PendingDependencySourceStateMarker");
464
+ /**
465
+ * Checks if a value is a {@link PendingDependencySourceState}.
466
+ *
467
+ * @param value The value to check.
468
+ * @returns `true` if the value is a pending dependency source state.
469
+ * @since 0.10.0
470
+ */
471
+ function isPendingDependencySourceState(value) {
472
+ return typeof value === "object" && value !== null && PendingDependencySourceStateMarker in value && value[PendingDependencySourceStateMarker] === true;
473
+ }
474
+ /**
475
+ * Creates a pending dependency source state.
476
+ *
477
+ * @param dependencyId The dependency ID.
478
+ * @returns A PendingDependencySourceState object.
479
+ * @since 0.10.0
480
+ */
481
+ function createPendingDependencySourceState(dependencyId) {
482
+ return {
483
+ [PendingDependencySourceStateMarker]: true,
484
+ [DependencyId]: dependencyId
485
+ };
486
+ }
487
+ /**
488
+ * A unique symbol used to identify parsers that wrap a dependency source.
489
+ * This is used by withDefault to indicate it contains an inner parser
490
+ * with a PendingDependencySourceState initialState.
491
+ * @since 0.10.0
492
+ */
493
+ const WrappedDependencySourceMarker = Symbol.for("@optique/core/dependency/WrappedDependencySourceMarker");
494
+ /**
495
+ * Checks if a parser wraps a dependency source (has WrappedDependencySourceMarker).
496
+ *
497
+ * @param parser The parser to check.
498
+ * @returns `true` if the parser wraps a dependency source.
499
+ * @since 0.10.0
500
+ */
501
+ function isWrappedDependencySource(parser) {
502
+ return typeof parser === "object" && parser !== null && WrappedDependencySourceMarker in parser;
503
+ }
504
+ /**
446
505
  * A registry for storing resolved dependency values during parsing.
447
506
  * This is used to pass dependency values from DependencySource options
448
507
  * to DerivedValueParser options.
@@ -510,13 +569,17 @@ function formatDependencyError(error) {
510
569
  //#endregion
511
570
  exports.DeferredParseMarker = DeferredParseMarker;
512
571
  exports.DependencyId = DependencyId;
572
+ exports.DependencyIds = DependencyIds;
513
573
  exports.DependencyRegistry = DependencyRegistry;
514
574
  exports.DependencySourceMarker = DependencySourceMarker;
515
575
  exports.DependencySourceStateMarker = DependencySourceStateMarker;
516
576
  exports.DerivedValueParserMarker = DerivedValueParserMarker;
517
577
  exports.ParseWithDependency = ParseWithDependency;
578
+ exports.PendingDependencySourceStateMarker = PendingDependencySourceStateMarker;
579
+ exports.WrappedDependencySourceMarker = WrappedDependencySourceMarker;
518
580
  exports.createDeferredParseState = createDeferredParseState;
519
581
  exports.createDependencySourceState = createDependencySourceState;
582
+ exports.createPendingDependencySourceState = createPendingDependencySourceState;
520
583
  exports.dependency = dependency;
521
584
  exports.deriveFrom = deriveFrom;
522
585
  exports.deriveFromAsync = deriveFromAsync;
@@ -525,4 +588,6 @@ exports.formatDependencyError = formatDependencyError;
525
588
  exports.isDeferredParseState = isDeferredParseState;
526
589
  exports.isDependencySource = isDependencySource;
527
590
  exports.isDependencySourceState = isDependencySourceState;
528
- exports.isDerivedValueParser = isDerivedValueParser;
591
+ exports.isDerivedValueParser = isDerivedValueParser;
592
+ exports.isPendingDependencySourceState = isPendingDependencySourceState;
593
+ exports.isWrappedDependencySource = isWrappedDependencySource;
@@ -24,6 +24,12 @@ declare const DerivedValueParserMarker: unique symbol;
24
24
  * @since 0.10.0
25
25
  */
26
26
  declare const DependencyId: unique symbol;
27
+ /**
28
+ * A unique symbol used to store multiple dependency IDs on derived parsers
29
+ * that depend on multiple sources (created via {@link deriveFrom}).
30
+ * @since 0.10.0
31
+ */
32
+ declare const DependencyIds: unique symbol;
27
33
  /**
28
34
  * A unique symbol used to access the parseWithDependency method on derived parsers.
29
35
  * @since 0.10.0
@@ -308,9 +314,19 @@ interface DerivedValueParser<M extends Mode = "sync", T = unknown, S = unknown>
308
314
  readonly [DerivedValueParserMarker]: true;
309
315
  /**
310
316
  * The unique identifier of the dependency source this parser depends on.
317
+ * For parsers created with {@link deriveFrom} that have multiple dependencies,
318
+ * this is set to the first dependency's ID for backwards compatibility.
311
319
  * @internal
312
320
  */
313
321
  readonly [DependencyId]: symbol;
322
+ /**
323
+ * The unique identifiers of all dependency sources this parser depends on.
324
+ * Present only for parsers created with {@link deriveFrom} that have multiple
325
+ * dependencies. If present, this takes precedence over {@link DependencyId}
326
+ * during dependency resolution.
327
+ * @internal
328
+ */
329
+ readonly [DependencyIds]?: readonly symbol[];
314
330
  /**
315
331
  * Parses the input using the actual dependency value instead of the default.
316
332
  * This method is used during dependency resolution in `complete()`.
@@ -460,9 +476,14 @@ interface DeferredParseState<T = unknown> {
460
476
  */
461
477
  readonly parser: DerivedValueParser<Mode, T, unknown>;
462
478
  /**
463
- * The dependency ID that this parser depends on.
479
+ * The dependency ID that this parser depends on (for single-dependency parsers).
464
480
  */
465
481
  readonly dependencyId: symbol;
482
+ /**
483
+ * The dependency IDs that this parser depends on (for multi-dependency parsers).
484
+ * If present, this is used instead of `dependencyId`.
485
+ */
486
+ readonly dependencyIds?: readonly symbol[];
466
487
  /**
467
488
  * The preliminary parse result using the default dependency value.
468
489
  * This is used as a fallback if dependency resolution is not needed
@@ -535,6 +556,62 @@ declare function isDependencySourceState<T>(value: unknown): value is Dependency
535
556
  * @since 0.10.0
536
557
  */
537
558
  declare function createDependencySourceState<T>(result: ValueParserResult<T>, dependencyId: symbol): DependencySourceState<T>;
559
+ /**
560
+ * A unique symbol used to identify pending dependency source states.
561
+ * @since 0.10.0
562
+ */
563
+ declare const PendingDependencySourceStateMarker: unique symbol;
564
+ /**
565
+ * Represents a pending dependency source state.
566
+ * This is used when a dependency source option was not provided, but its
567
+ * dependency ID still needs to be tracked for later resolution with a
568
+ * default value.
569
+ *
570
+ * @since 0.10.0
571
+ */
572
+ interface PendingDependencySourceState {
573
+ /**
574
+ * Marker to identify this as a pending dependency source state.
575
+ */
576
+ readonly [PendingDependencySourceStateMarker]: true;
577
+ /**
578
+ * The dependency ID of the source.
579
+ */
580
+ readonly [DependencyId]: symbol;
581
+ }
582
+ /**
583
+ * Checks if a value is a {@link PendingDependencySourceState}.
584
+ *
585
+ * @param value The value to check.
586
+ * @returns `true` if the value is a pending dependency source state.
587
+ * @since 0.10.0
588
+ */
589
+ declare function isPendingDependencySourceState(value: unknown): value is PendingDependencySourceState;
590
+ /**
591
+ * Creates a pending dependency source state.
592
+ *
593
+ * @param dependencyId The dependency ID.
594
+ * @returns A PendingDependencySourceState object.
595
+ * @since 0.10.0
596
+ */
597
+ declare function createPendingDependencySourceState(dependencyId: symbol): PendingDependencySourceState;
598
+ /**
599
+ * A unique symbol used to identify parsers that wrap a dependency source.
600
+ * This is used by withDefault to indicate it contains an inner parser
601
+ * with a PendingDependencySourceState initialState.
602
+ * @since 0.10.0
603
+ */
604
+ declare const WrappedDependencySourceMarker: unique symbol;
605
+ /**
606
+ * Checks if a parser wraps a dependency source (has WrappedDependencySourceMarker).
607
+ *
608
+ * @param parser The parser to check.
609
+ * @returns `true` if the parser wraps a dependency source.
610
+ * @since 0.10.0
611
+ */
612
+ declare function isWrappedDependencySource(parser: unknown): parser is {
613
+ [WrappedDependencySourceMarker]: PendingDependencySourceState;
614
+ };
538
615
  /**
539
616
  * Represents a resolved dependency value stored during parsing.
540
617
  * @since 0.10.0
@@ -614,4 +691,4 @@ type DependencyError = {
614
691
  */
615
692
  declare function formatDependencyError(error: DependencyError): Message;
616
693
  //#endregion
617
- export { AnyDependencySource, CombineMode, CombinedDependencyMode, DeferredParseMarker, DeferredParseState, DependencyError, DependencyId, DependencyMode, DependencyRegistry, DependencySource, DependencySourceMarker, DependencySourceState, DependencySourceStateMarker, DependencyValue, DependencyValues, DeriveAsyncOptions, DeriveFromAsyncOptions, DeriveFromOptions, DeriveFromSyncOptions, DeriveOptions, DeriveSyncOptions, DerivedValueParser, DerivedValueParserMarker, ParseWithDependency, ResolvedDependency, createDeferredParseState, createDependencySourceState, dependency, deriveFrom, deriveFromAsync, deriveFromSync, formatDependencyError, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser };
694
+ export { AnyDependencySource, CombineMode, CombinedDependencyMode, DeferredParseMarker, DeferredParseState, DependencyError, DependencyId, DependencyIds, DependencyMode, DependencyRegistry, DependencySource, DependencySourceMarker, DependencySourceState, DependencySourceStateMarker, DependencyValue, DependencyValues, DeriveAsyncOptions, DeriveFromAsyncOptions, DeriveFromOptions, DeriveFromSyncOptions, DeriveOptions, DeriveSyncOptions, DerivedValueParser, DerivedValueParserMarker, ParseWithDependency, PendingDependencySourceState, PendingDependencySourceStateMarker, ResolvedDependency, WrappedDependencySourceMarker, createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, dependency, deriveFrom, deriveFromAsync, deriveFromSync, formatDependencyError, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isPendingDependencySourceState, isWrappedDependencySource };