@optique/core 1.3.0-dev.2398 → 1.3.0-dev.2401

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,8 +1,34 @@
1
1
  import { message } from "./message.js";
2
- import { dependencyId, isDeferredParseState, isDependencySourceState, isPendingDependencySourceState, parseWithDependency } from "./internal/dependency.js";
2
+ import { dependencyId, getSnapshottedDefaultDependencyValues, isDeferredParseState, isDependencySourceState, isPendingDependencySourceState, parseWithDependency } from "./internal/dependency.js";
3
3
  import { unmatchedNonCliDependencySourceStateMarker } from "./internal/parser.js";
4
4
 
5
5
  //#region src/dependency-runtime.ts
6
+ /**
7
+ * Stores the raw token parsed by a derived value parser on structural parser
8
+ * states that can safely carry an in-band annotation.
9
+ *
10
+ * The execution trace remains the canonical diagnostic record. This state
11
+ * marker lets construct-independent dependency resolution replay a derived
12
+ * source before downstream fields complete.
13
+ * @internal
14
+ * @since 1.3.0
15
+ */
16
+ const derivedRawInputKey = Symbol("@optique/core/dependency-runtime/derivedRawInput");
17
+ const derivedRawInputs = /* @__PURE__ */ new WeakMap();
18
+ /**
19
+ * Records a derived parser's raw token without modifying its parse result.
20
+ *
21
+ * Parse results may be frozen or carry class private state, so primitives keep
22
+ * their original identity and associate replay metadata out of band.
23
+ *
24
+ * @param state The original value parser result.
25
+ * @param rawInput The token parsed into that result.
26
+ * @internal
27
+ * @since 1.3.0
28
+ */
29
+ function recordDerivedRawInput(state, rawInput) {
30
+ derivedRawInputs.set(state, rawInput);
31
+ }
6
32
  const symbolIds = /* @__PURE__ */ new WeakMap();
7
33
  let symbolCounter = 0;
8
34
  function stableSymbolKey(sym) {
@@ -19,6 +45,8 @@ var DependencyRuntimeContextImpl = class {
19
45
  registry;
20
46
  #replayCache = /* @__PURE__ */ new Map();
21
47
  #failedSources = /* @__PURE__ */ new Set();
48
+ #sourceMetadata = /* @__PURE__ */ new Map();
49
+ #sourceFailures = /* @__PURE__ */ new Map();
22
50
  constructor(registry) {
23
51
  if (registry instanceof FailedAwareRegistry) {
24
52
  this.registry = registry.rebindFailedSources(this.#failedSources);
@@ -28,6 +56,7 @@ var DependencyRuntimeContextImpl = class {
28
56
  }
29
57
  registerSource(sourceId, value) {
30
58
  this.registry.set(sourceId, value);
59
+ this.#sourceFailures.delete(sourceId);
31
60
  }
32
61
  hasSource(sourceId) {
33
62
  return this.registry.has(sourceId);
@@ -46,6 +75,45 @@ var DependencyRuntimeContextImpl = class {
46
75
  }
47
76
  markSourceFailed(sourceId) {
48
77
  this.#failedSources.add(sourceId);
78
+ const lineage = this.#getSourceLineage(sourceId, /* @__PURE__ */ new Set());
79
+ const failure = {
80
+ chain: lineage.labels,
81
+ participants: lineage.sourceIds,
82
+ diagnosticChain: lineage.labels
83
+ };
84
+ this.#sourceFailures.set(sourceId, failure);
85
+ this.#promoteDiagnosticChain(failure);
86
+ }
87
+ registerSourceMetadata(sourceId, label, dependencyIds = []) {
88
+ this.#sourceMetadata.set(sourceId, {
89
+ label,
90
+ dependencyIds
91
+ });
92
+ }
93
+ propagateSourceFailure(dependencyIds, label, sourceId) {
94
+ const upstream = dependencyIds.filter((id) => this.isSourceFailed(id)).map((id) => this.#sourceFailures.get(id) ?? {
95
+ chain: [this.#getSourceLabel(id)],
96
+ participants: [id],
97
+ diagnosticChain: [this.#getSourceLabel(id)]
98
+ }).sort((left, right) => right.chain.length - left.chain.length)[0];
99
+ if (upstream == null) return false;
100
+ const chain = upstream.chain.at(-1) === label ? upstream.chain : [...upstream.chain, label];
101
+ const participants = sourceId == null || upstream.participants.includes(sourceId) ? upstream.participants : [...upstream.participants, sourceId];
102
+ const failure = {
103
+ chain,
104
+ participants,
105
+ diagnosticChain: chain
106
+ };
107
+ if (sourceId != null) {
108
+ this.#failedSources.add(sourceId);
109
+ this.#sourceFailures.set(sourceId, failure);
110
+ }
111
+ this.#promoteDiagnosticChain(failure);
112
+ return true;
113
+ }
114
+ getSourceFailureChain(sourceId) {
115
+ if (!this.#failedSources.has(sourceId)) return void 0;
116
+ return this.#sourceFailures.get(sourceId)?.diagnosticChain;
49
117
  }
50
118
  isSourceFailed(sourceId) {
51
119
  return this.#failedSources.has(sourceId);
@@ -53,6 +121,31 @@ var DependencyRuntimeContextImpl = class {
53
121
  getSuggestionDependencies(request) {
54
122
  return resolveRequest(this, request);
55
123
  }
124
+ #getSourceLabel(sourceId) {
125
+ return this.#sourceMetadata.get(sourceId)?.label ?? sourceId.description ?? String(sourceId);
126
+ }
127
+ #getSourceLineage(sourceId, visited) {
128
+ if (visited.has(sourceId)) return {
129
+ labels: [this.#getSourceLabel(sourceId)],
130
+ sourceIds: [sourceId]
131
+ };
132
+ visited.add(sourceId);
133
+ const metadata = this.#sourceMetadata.get(sourceId);
134
+ const upstream = (metadata?.dependencyIds ?? []).map((id) => this.#getSourceLineage(id, new Set(visited))).sort((left, right) => right.labels.length - left.labels.length)[0];
135
+ return upstream == null ? {
136
+ labels: [this.#getSourceLabel(sourceId)],
137
+ sourceIds: [sourceId]
138
+ } : {
139
+ labels: [...upstream.labels, this.#getSourceLabel(sourceId)],
140
+ sourceIds: [...upstream.sourceIds, sourceId]
141
+ };
142
+ }
143
+ #promoteDiagnosticChain(failure) {
144
+ for (const sourceId of failure.participants) {
145
+ const current = this.#sourceFailures.get(sourceId);
146
+ if (current != null && current.diagnosticChain.length < failure.diagnosticChain.length) current.diagnosticChain = failure.diagnosticChain;
147
+ }
148
+ }
56
149
  };
57
150
  /**
58
151
  * Registry wrapper that hides values for sources that have failed.
@@ -251,14 +344,17 @@ function createReplayKey(path, rawInput, dependencyValues, replayParse) {
251
344
  * @since 1.0.0
252
345
  */
253
346
  function collectExplicitSourceValues(nodes, runtime) {
347
+ registerRuntimeSourceMetadata(nodes, runtime);
254
348
  for (const node of nodes) {
255
349
  const meta = node.parser.dependencyMetadata;
256
350
  if (meta?.source == null) continue;
257
351
  if (meta.source.extractSourceValue == null) continue;
352
+ if (meta.derived != null && getNodeRawInput(node) != null) continue;
258
353
  const result = meta.source.extractSourceValue(node.state);
259
354
  if (isPromiseLike(result)) throw new TypeError(`collectExplicitSourceValues() received an async extractSourceValue() result for ${String(meta.source.sourceId)}. Use collectExplicitSourceValuesAsync() instead.`);
260
355
  registerExplicitSourceValue(meta.source.sourceId, result, runtime);
261
356
  }
357
+ resolveDerivedSourceValues(nodes, runtime);
262
358
  }
263
359
  function registerExplicitSourceValue(sourceId, result, runtime) {
264
360
  if (result == null) return;
@@ -280,13 +376,192 @@ function isPromiseLike(value) {
280
376
  * @since 1.0.0
281
377
  */
282
378
  async function collectExplicitSourceValuesAsync(nodes, runtime) {
379
+ registerRuntimeSourceMetadata(nodes, runtime);
283
380
  for (const node of nodes) {
284
381
  const meta = node.parser.dependencyMetadata;
285
382
  if (meta?.source == null) continue;
286
383
  if (meta.source.extractSourceValue == null) continue;
384
+ if (meta.derived != null && getNodeRawInput(node) != null) continue;
287
385
  const result = await meta.source.extractSourceValue(node.state);
288
386
  registerExplicitSourceValue(meta.source.sourceId, result, runtime);
289
387
  }
388
+ await resolveDerivedSourceValuesAsync(nodes, runtime);
389
+ }
390
+ /**
391
+ * Orders runtime nodes so every in-scope provider precedes a derived source
392
+ * that consumes it. Independent nodes retain declaration order.
393
+ *
394
+ * Missing providers create no edge because the consumer may use its declared
395
+ * default. Scheduling barriers act as providers for the source IDs their
396
+ * selected subtree may expose and depend on their discriminator source.
397
+ *
398
+ * @param nodes Runtime nodes in declaration order.
399
+ * @returns The same nodes in stable dependency order.
400
+ * @throws {TypeError} If active provider edges contain a cycle.
401
+ * @internal
402
+ * @since 1.3.0
403
+ */
404
+ function orderDependencyNodes(nodes) {
405
+ const providers = /* @__PURE__ */ new Map();
406
+ const addProvider = (sourceId, node) => {
407
+ const existing = providers.get(sourceId);
408
+ if (existing == null) providers.set(sourceId, [node]);
409
+ else existing.push(node);
410
+ };
411
+ for (const node of nodes) {
412
+ const source = node.parser.dependencyMetadata?.source;
413
+ if (source != null) addProvider(source.sourceId, node);
414
+ for (const sourceId of node.providesSourceIds ?? []) addProvider(sourceId, node);
415
+ }
416
+ const outgoing = /* @__PURE__ */ new Map();
417
+ const indegree = /* @__PURE__ */ new Map();
418
+ for (const node of nodes) indegree.set(node, 0);
419
+ const addEdge = (provider, consumer) => {
420
+ const edges = outgoing.get(provider) ?? /* @__PURE__ */ new Set();
421
+ if (edges.has(consumer)) return;
422
+ edges.add(consumer);
423
+ outgoing.set(provider, edges);
424
+ indegree.set(consumer, (indegree.get(consumer) ?? 0) + 1);
425
+ };
426
+ for (const node of nodes) {
427
+ const derived = node.parser.dependencyMetadata?.derived;
428
+ if (derived != null) for (const dependencySourceId of derived.dependencyIds) for (const provider of providers.get(dependencySourceId) ?? []) addEdge(provider, node);
429
+ if (node.requiresSourceId != null) {
430
+ for (const provider of providers.get(node.requiresSourceId) ?? []) if (provider !== node) addEdge(provider, node);
431
+ }
432
+ }
433
+ const declarationOrder = new Map(nodes.map((node, index) => [node, index]));
434
+ const ready = nodes.filter((node) => indegree.get(node) === 0);
435
+ const ordered = [];
436
+ while (ready.length > 0) {
437
+ ready.sort((left, right) => declarationOrder.get(left) - declarationOrder.get(right));
438
+ const node = ready.shift();
439
+ ordered.push(node);
440
+ for (const consumer of outgoing.get(node) ?? []) {
441
+ const next = (indegree.get(consumer) ?? 0) - 1;
442
+ indegree.set(consumer, next);
443
+ if (next === 0) ready.push(consumer);
444
+ }
445
+ }
446
+ if (ordered.length !== nodes.length) {
447
+ const cycle = nodes.filter((node) => (indegree.get(node) ?? 0) > 0);
448
+ const labels = cycle.map(formatDependencyNodeLabel).join(" -> ");
449
+ throw new TypeError(`Circular dependency detected among derived sources: ${labels}.`);
450
+ }
451
+ return ordered;
452
+ }
453
+ /** Resolves and publishes matched derived sources in stable dependency order. */
454
+ function resolveDerivedSourceValues(nodes, runtime, options) {
455
+ resolveDerivedSourceValuesInOrder(orderDependencyNodes(nodes), nodes, runtime, (node, rawInput) => replayDerivedParser(node, rawInput, runtime), options);
456
+ }
457
+ /** Async version of {@link resolveDerivedSourceValues}. */
458
+ async function resolveDerivedSourceValuesAsync(nodes, runtime, options) {
459
+ await resolveDerivedSourceValuesInOrderAsync(orderDependencyNodes(nodes), nodes, runtime, options);
460
+ }
461
+ function resolveDerivedSourceValuesInOrder(ordered, allNodes, runtime, replay, options) {
462
+ const providers = collectSourceProviders(allNodes);
463
+ const settled = /* @__PURE__ */ new Set();
464
+ for (const node of ordered) {
465
+ const metadata = node.parser.dependencyMetadata;
466
+ const rawInput = getNodeRawInput(node);
467
+ if (metadata?.derived == null) continue;
468
+ const label = formatDependencyNodeMetavar(node);
469
+ if (runtime.propagateSourceFailure(metadata.derived.dependencyIds, label, metadata.source?.sourceId)) {
470
+ if (metadata.source != null) settled.add(node);
471
+ continue;
472
+ }
473
+ if (metadata.source == null || rawInput == null) continue;
474
+ if (hasPendingProvider(node, providers, settled, runtime, options)) continue;
475
+ settleDerivedSource(node, rawInput, runtime, replay);
476
+ settled.add(node);
477
+ }
478
+ }
479
+ async function resolveDerivedSourceValuesInOrderAsync(ordered, allNodes, runtime, options) {
480
+ const providers = collectSourceProviders(allNodes);
481
+ const settled = /* @__PURE__ */ new Set();
482
+ for (const node of ordered) {
483
+ const metadata = node.parser.dependencyMetadata;
484
+ const rawInput = getNodeRawInput(node);
485
+ if (metadata?.derived == null) continue;
486
+ const label = formatDependencyNodeMetavar(node);
487
+ if (runtime.propagateSourceFailure(metadata.derived.dependencyIds, label, metadata.source?.sourceId)) {
488
+ if (metadata.source != null) settled.add(node);
489
+ continue;
490
+ }
491
+ if (metadata.source == null || rawInput == null) continue;
492
+ if (hasPendingProvider(node, providers, settled, runtime, options)) continue;
493
+ const result = await replayDerivedParserAsync(node, rawInput, runtime);
494
+ publishDerivedSourceResult(metadata.source.sourceId, result, runtime);
495
+ settled.add(node);
496
+ }
497
+ }
498
+ function settleDerivedSource(node, rawInput, runtime, replay) {
499
+ const metadata = node.parser.dependencyMetadata;
500
+ publishDerivedSourceResult(metadata.source.sourceId, replay(node, rawInput), runtime);
501
+ }
502
+ function publishDerivedSourceResult(sourceId, result, runtime) {
503
+ if (result == null || result.success && result.deferred === true) return;
504
+ if (!result.success) runtime.markSourceFailed(sourceId);
505
+ else runtime.registerSource(sourceId, result.value);
506
+ }
507
+ function collectSourceProviders(nodes) {
508
+ const providers = /* @__PURE__ */ new Map();
509
+ for (const node of nodes) {
510
+ const sourceId = node.parser.dependencyMetadata?.source?.sourceId;
511
+ if (sourceId == null) continue;
512
+ const existing = providers.get(sourceId);
513
+ if (existing == null) providers.set(sourceId, [node]);
514
+ else existing.push(node);
515
+ }
516
+ return providers;
517
+ }
518
+ function hasPendingProvider(node, providers, settled, runtime, options) {
519
+ const derived = node.parser.dependencyMetadata.derived;
520
+ return derived.dependencyIds.some((sourceId) => (providers.get(sourceId) ?? []).some((provider) => {
521
+ if (provider === node || runtime.hasSource(sourceId) || runtime.isSourceFailed(sourceId)) return false;
522
+ const metadata = provider.parser.dependencyMetadata;
523
+ if (metadata?.derived != null && getNodeRawInput(provider) != null) return !settled.has(provider);
524
+ return options?.effectfulProviders !== "inactive" && metadata?.source?.completeSource != null;
525
+ }));
526
+ }
527
+ function getNodeRawInput(node) {
528
+ return node.rawInput ?? extractRawInputFromState(node.state);
529
+ }
530
+ function registerRuntimeSourceMetadata(nodes, runtime) {
531
+ for (const node of nodes) {
532
+ const metadata = node.parser.dependencyMetadata;
533
+ if (metadata?.source == null) continue;
534
+ runtime.registerSourceMetadata(metadata.source.sourceId, formatDependencyNodeMetavar(node), metadata.derived?.dependencyIds);
535
+ }
536
+ }
537
+ function propagateRuntimeSourceFailures(nodes, runtime) {
538
+ for (const node of orderDependencyNodes(nodes)) {
539
+ const metadata = node.parser.dependencyMetadata;
540
+ if (metadata?.derived == null) continue;
541
+ runtime.propagateSourceFailure(metadata.derived.dependencyIds, formatDependencyNodeMetavar(node), metadata.source?.sourceId);
542
+ }
543
+ }
544
+ /**
545
+ * Appends the recorded dependency chain to a source failure.
546
+ *
547
+ * @param error The source failure to annotate.
548
+ * @param sourceId The identifier of the failed source.
549
+ * @param runtime The dependency runtime that recorded the failure chain.
550
+ * @returns The annotated failure, or the original failure when no chain exists.
551
+ * @internal
552
+ * @since 1.3.0
553
+ */
554
+ function includeSourceFailureChain(error, sourceId, runtime) {
555
+ const chain = runtime.getSourceFailureChain(sourceId);
556
+ return chain == null || chain.length < 2 ? error : message`${error} Dependency chain: ${chain.join(" -> ")}.`;
557
+ }
558
+ function formatDependencyNodeMetavar(node) {
559
+ return node.parser.dependencyMetadata?.derived?.metavar ?? node.parser.dependencyMetadata?.source?.metavar ?? (node.path.map(String).join(".") || "<root>");
560
+ }
561
+ function formatDependencyNodeLabel(node) {
562
+ const metavar = node.parser.dependencyMetadata?.derived?.metavar ?? node.parser.dependencyMetadata?.source?.metavar;
563
+ const path = node.path.map(String).join(".") || "<root>";
564
+ return metavar == null ? path : `${metavar} (${path})`;
290
565
  }
291
566
  /**
292
567
  * Fills missing source defaults for source parsers whose state is
@@ -339,6 +614,7 @@ function fillMissingSourceDefaults(nodes, runtime) {
339
614
  error: result
340
615
  });
341
616
  }
617
+ resolveDerivedSourceValues(nodes, runtime);
342
618
  return failures;
343
619
  }
344
620
  /**
@@ -383,6 +659,7 @@ async function fillMissingSourceDefaultsAsync(nodes, runtime) {
383
659
  error: result
384
660
  });
385
661
  }
662
+ await resolveDerivedSourceValuesAsync(nodes, runtime);
386
663
  return failures;
387
664
  }
388
665
  /**
@@ -414,6 +691,10 @@ function replayDerivedParser(node, rawInput, runtime) {
414
691
  });
415
692
  if (resolution.kind === "missing") return void 0;
416
693
  if (resolution.kind === "partial") return void 0;
694
+ if (resolution.usedDefaults.every((usedDefault) => usedDefault)) {
695
+ const preliminary = extractPreliminaryResultFromState(node.state);
696
+ if (preliminary != null) return preliminary;
697
+ }
417
698
  const key = createReplayKey(node.path, rawInput, resolution.values, meta.derived.replayParse);
418
699
  const cached = runtime.getReplayResult(key);
419
700
  if (cached != null) return cached;
@@ -449,6 +730,10 @@ async function replayDerivedParserAsync(node, rawInput, runtime) {
449
730
  });
450
731
  if (resolution.kind === "missing") return void 0;
451
732
  if (resolution.kind === "partial") return void 0;
733
+ if (resolution.usedDefaults.every((usedDefault) => usedDefault)) {
734
+ const preliminary = extractPreliminaryResultFromState(node.state);
735
+ if (preliminary != null) return preliminary;
736
+ }
452
737
  const key = createReplayKey(node.path, rawInput, resolution.values, meta.derived.replayParse);
453
738
  const cached = runtime.getReplayResult(key);
454
739
  if (cached != null) return cached;
@@ -471,10 +756,60 @@ async function replayDerivedParserAsync(node, rawInput, runtime) {
471
756
  * @since 1.0.0
472
757
  */
473
758
  function extractRawInputFromState(state) {
759
+ return extractRawInputFromStateInner(state, /* @__PURE__ */ new Set());
760
+ }
761
+ function extractRawInputFromStateInner(state, visited) {
474
762
  if (state == null) return void 0;
475
763
  if (typeof state !== "object") return void 0;
764
+ if (visited.has(state)) return void 0;
765
+ visited.add(state);
766
+ const recordedRawInput = getRecordedDerivedRawInput(state);
767
+ if (recordedRawInput != null) return recordedRawInput;
476
768
  if (isDeferredParseState(state)) return state.rawInput;
477
- if (Array.isArray(state) && state.length === 1 && isDeferredParseState(state[0])) return state[0].rawInput;
769
+ if (Array.isArray(state)) {
770
+ for (let index = state.length - 1; index >= 0; index--) {
771
+ const rawInput = extractRawInputFromStateInner(state[index], visited);
772
+ if (rawInput != null) return rawInput;
773
+ }
774
+ return void 0;
775
+ }
776
+ const nested = /* @__PURE__ */ new Set();
777
+ for (const value of Object.values(state)) {
778
+ const rawInput = extractRawInputFromStateInner(value, visited);
779
+ if (rawInput != null) nested.add(rawInput);
780
+ }
781
+ return nested.size === 1 ? nested.values().next().value : void 0;
782
+ }
783
+ function extractPreliminaryResultFromState(state) {
784
+ return extractPreliminaryResultFromStateInner(state, /* @__PURE__ */ new Set());
785
+ }
786
+ function extractPreliminaryResultFromStateInner(state, visited) {
787
+ if (state == null || typeof state !== "object") return void 0;
788
+ if (visited.has(state)) return void 0;
789
+ visited.add(state);
790
+ if (isDeferredParseState(state)) return state.preliminaryResult;
791
+ if (getRecordedDerivedRawInput(state) != null && "success" in state && typeof state.success === "boolean") {
792
+ if (state.success === true && "value" in state) return state;
793
+ if (state.success === false && "error" in state) return state;
794
+ }
795
+ if (Array.isArray(state)) {
796
+ for (let index = state.length - 1; index >= 0; index--) {
797
+ const result = extractPreliminaryResultFromStateInner(state[index], visited);
798
+ if (result != null) return result;
799
+ }
800
+ return void 0;
801
+ }
802
+ const nested = /* @__PURE__ */ new Set();
803
+ for (const value of Object.values(state)) {
804
+ const result = extractPreliminaryResultFromStateInner(value, visited);
805
+ if (result != null) nested.add(result);
806
+ }
807
+ return nested.size === 1 ? nested.values().next().value : void 0;
808
+ }
809
+ function getRecordedDerivedRawInput(state) {
810
+ const recorded = derivedRawInputs.get(state);
811
+ if (recorded != null) return recorded;
812
+ if (derivedRawInputKey in state && typeof state[derivedRawInputKey] === "string") return state[derivedRawInputKey];
478
813
  return void 0;
479
814
  }
480
815
  /**
@@ -658,6 +993,7 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
658
993
  completed: []
659
994
  };
660
995
  if (exec == null || exec.phase !== "complete") return empty;
996
+ registerRuntimeSourceMetadata(nodes, runtime);
661
997
  const session = exec.effectfulCompletionSession;
662
998
  if (session?.policy === "demand-only") {
663
999
  const demandNodes = options?.demandNodes ?? nodes;
@@ -667,6 +1003,12 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
667
1003
  while (demandAdded) {
668
1004
  demandAdded = false;
669
1005
  for (const node of nodes) {
1006
+ const metadata = node.parser.dependencyMetadata;
1007
+ if (metadata?.source != null && metadata.derived != null && session.demanded.has(metadata.source.sourceId)) for (const dependencySourceId of metadata.derived.dependencyIds) {
1008
+ if (session.demanded.has(dependencySourceId)) continue;
1009
+ session.demanded.add(dependencySourceId);
1010
+ demandAdded = true;
1011
+ }
670
1012
  if (node.requiresSourceId == null || node.providesSourceIds == null) continue;
671
1013
  if (session.demanded.has(node.requiresSourceId)) continue;
672
1014
  for (const provided of node.providesSourceIds) if (session.demanded.has(provided)) {
@@ -686,7 +1028,7 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
686
1028
  const schedulable = nodes.filter((node) => node.parser.dependencyMetadata?.source?.completeSource != null || node.prepare != null);
687
1029
  if (schedulable.length === 0 && options?.includeStructural !== true) return empty;
688
1030
  const completed = [];
689
- for (const node of nodes) {
1031
+ for (const node of orderDependencyNodes(nodes)) {
690
1032
  if (node.prepare != null) {
691
1033
  const barrierFailure = await node.prepare({
692
1034
  runtime,
@@ -703,6 +1045,28 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
703
1045
  const source = node.parser.dependencyMetadata?.source;
704
1046
  if (source == null) continue;
705
1047
  if (source.completeSource == null) {
1048
+ const derived = node.parser.dependencyMetadata?.derived;
1049
+ const rawInput = getNodeRawInput(node);
1050
+ if (derived != null && rawInput != null) {
1051
+ if (runtime.propagateSourceFailure(derived.dependencyIds, formatDependencyNodeMetavar(node), source.sourceId)) continue;
1052
+ const replayed = await replayDerivedParserAsync(node, rawInput, runtime);
1053
+ if (replayed == null) continue;
1054
+ if (!replayed.success) {
1055
+ runtime.markSourceFailed(source.sourceId);
1056
+ propagateRuntimeSourceFailures(nodes, runtime);
1057
+ return {
1058
+ success: false,
1059
+ error: includeSourceFailureChain(replayed.error, source.sourceId, runtime)
1060
+ };
1061
+ }
1062
+ if (replayed.deferred === true) continue;
1063
+ runtime.registerSource(source.sourceId, replayed.value);
1064
+ if (source.preservesSourceValue && (options?.isReusable?.(node) ?? true)) completed.push({
1065
+ key: node.path[node.path.length - 1],
1066
+ result: replayed
1067
+ });
1068
+ continue;
1069
+ }
706
1070
  const collected = options?.isCollected?.(node) ?? options?.isReusable?.(node) ?? true;
707
1071
  if (source.extractSourceValue == null || collected === false) continue;
708
1072
  const extracted = await source.extractSourceValue(node.state);
@@ -728,9 +1092,10 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
728
1092
  if (result == null) continue;
729
1093
  if (!result.success) {
730
1094
  runtime.markSourceFailed(source.sourceId);
1095
+ propagateRuntimeSourceFailures(nodes, runtime);
731
1096
  return {
732
1097
  success: false,
733
- error: result.error
1098
+ error: includeSourceFailureChain(result.error, source.sourceId, runtime)
734
1099
  };
735
1100
  }
736
1101
  if (result.deferred === true) continue;
@@ -960,11 +1325,15 @@ function buildRuntimeNodesFromPairs(pairs, state, parentPath) {
960
1325
  const nodes = [];
961
1326
  for (const [field, parser] of pairs) {
962
1327
  const fieldState = Object.hasOwn(state, field) ? state[field] : void 0;
1328
+ const rawInput = extractRawInputFromState(fieldState);
1329
+ const defaultDependencyValues = getDefaultDependencySnapshot(fieldState);
963
1330
  nodes.push({
964
1331
  path: [...prefix, field],
965
1332
  parser,
966
1333
  state: fieldState,
967
- matched: isMatchedState(fieldState, parser)
1334
+ matched: isMatchedState(fieldState, parser),
1335
+ ...rawInput != null ? { rawInput } : {},
1336
+ ...defaultDependencyValues != null ? { defaultDependencyValues } : {}
968
1337
  });
969
1338
  }
970
1339
  return nodes;
@@ -987,15 +1356,42 @@ function buildRuntimeNodesFromArray(parsers, stateArray, parentPath) {
987
1356
  for (let i = 0; i < parsers.length; i++) {
988
1357
  const parser = parsers[i];
989
1358
  const elemState = i < stateArray.length ? stateArray[i] : void 0;
1359
+ const rawInput = extractRawInputFromState(elemState);
1360
+ const defaultDependencyValues = getDefaultDependencySnapshot(elemState);
990
1361
  nodes.push({
991
1362
  path: [...prefix, i],
992
1363
  parser,
993
1364
  state: elemState,
994
- matched: isMatchedState(elemState, parser)
1365
+ matched: isMatchedState(elemState, parser),
1366
+ ...rawInput != null ? { rawInput } : {},
1367
+ ...defaultDependencyValues != null ? { defaultDependencyValues } : {}
995
1368
  });
996
1369
  }
997
1370
  return nodes;
998
1371
  }
1372
+ function getDefaultDependencySnapshot(state) {
1373
+ return getDefaultDependencySnapshotInner(state, /* @__PURE__ */ new Set());
1374
+ }
1375
+ function getDefaultDependencySnapshotInner(state, visited) {
1376
+ if (state == null || typeof state !== "object") return void 0;
1377
+ if (visited.has(state)) return void 0;
1378
+ visited.add(state);
1379
+ const direct = getSnapshottedDefaultDependencyValues(state);
1380
+ if (direct != null) return direct;
1381
+ if (Array.isArray(state)) {
1382
+ for (let index = state.length - 1; index >= 0; index--) {
1383
+ const snapshot = getDefaultDependencySnapshotInner(state[index], visited);
1384
+ if (snapshot != null) return snapshot;
1385
+ }
1386
+ return void 0;
1387
+ }
1388
+ const nested = [];
1389
+ for (const value of Object.values(state)) {
1390
+ const snapshot = getDefaultDependencySnapshotInner(value, visited);
1391
+ if (snapshot != null) nested.push(snapshot);
1392
+ }
1393
+ return nested.length === 1 ? nested[0] : void 0;
1394
+ }
999
1395
 
1000
1396
  //#endregion
1001
- export { buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };
1397
+ export { buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, derivedRawInputKey, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, includeSourceFailureChain, orderDependencyNodes, recordDerivedRawInput, replayDerivedParser, replayDerivedParserAsync, resolveDerivedSourceValues, resolveDerivedSourceValuesAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };
@@ -9,6 +9,16 @@ const require_message = require('../message.cjs');
9
9
  */
10
10
  const dependencySourceMarker = Symbol.for("@optique/core/dependency/dependencySourceMarker");
11
11
  /**
12
+ * A unique symbol used to store a dependency source's own identity.
13
+ *
14
+ * This is distinct from {@link dependencyId}, which stores an upstream
15
+ * reference on a derived parser. A parser wrapped with `dependency()` after
16
+ * derivation carries both values.
17
+ * @internal
18
+ * @since 1.3.0
19
+ */
20
+ const dependencySourceId = Symbol.for("@optique/core/dependency/dependencySourceId");
21
+ /**
12
22
  * A unique symbol used to identify derived value parsers at compile time.
13
23
  * This marker is used to distinguish {@link DerivedValueParser} from regular
14
24
  * {@link ValueParser} instances.
@@ -66,6 +76,8 @@ const suggestWithDependency = Symbol.for("@optique/core/dependency/suggestWithDe
66
76
  * A dependency source wraps an existing value parser and enables creating
67
77
  * derived parsers that depend on the parsed value. This is useful for
68
78
  * scenarios where one option's valid values depend on another option's value.
79
+ * A derived parser can itself become a source by wrapping it with
80
+ * `dependency()`, allowing dependency chains of any depth.
69
81
  *
70
82
  * @template M The execution mode of the value parser.
71
83
  * @template T The type of value the parser produces.
@@ -87,15 +99,23 @@ const suggestWithDependency = Symbol.for("@optique/core/dependency/suggestWithDe
87
99
  * factory: (dir) => gitBranch({ dir }),
88
100
  * defaultValue: () => process.cwd(),
89
101
  * });
102
+ *
103
+ * // A derived parser can provide the next dependency level.
104
+ * const branchSource = dependency(branchParser);
105
+ * const commitParser = branchSource.deriveSync({
106
+ * metavar: "COMMIT",
107
+ * factory: (branch) => gitCommit({ branch }),
108
+ * defaultValue: () => "main",
109
+ * });
90
110
  * ```
91
111
  * @since 0.10.0
92
112
  */
93
113
  function dependency(parser) {
94
114
  const id = Symbol();
95
- const result = {
96
- ...parser,
115
+ const result = Object.create(Object.getPrototypeOf(parser), Object.getOwnPropertyDescriptors(parser));
116
+ Object.defineProperties(result, Object.getOwnPropertyDescriptors({
97
117
  [dependencySourceMarker]: true,
98
- [dependencyId]: id,
118
+ [dependencySourceId]: id,
99
119
  derive(options) {
100
120
  if (options.mode !== "sync" && options.mode !== "async") throw new TypeError("derive() requires an explicit mode field (\"sync\" or \"async\").");
101
121
  return createDerivedValueParser(id, parser, options, options.mode);
@@ -107,7 +127,7 @@ function dependency(parser) {
107
127
  deriveAsync(options) {
108
128
  return createAsyncDerivedParserFromAsyncFactory(id, options);
109
129
  }
110
- };
130
+ }));
111
131
  return result;
112
132
  }
113
133
  /**
@@ -166,7 +186,7 @@ function isDerivedValueParser(parser) {
166
186
  function deriveFrom(options) {
167
187
  if (options.mode !== "sync" && options.mode !== "async") throw new TypeError("deriveFrom() requires an explicit mode field (\"sync\" or \"async\").");
168
188
  const depsAsync = options.dependencies.some((dep) => dep.mode === "async");
169
- const sourceId = options.dependencies.length > 0 ? options.dependencies[0][dependencyId] : Symbol();
189
+ const sourceId = options.dependencies.length > 0 ? options.dependencies[0][dependencySourceId] : Symbol();
170
190
  const factoryReturnsAsync = options.mode === "async";
171
191
  const isAsync = depsAsync || factoryReturnsAsync;
172
192
  if (isAsync) {
@@ -191,7 +211,7 @@ function deriveFrom(options) {
191
211
  */
192
212
  function deriveFromSync(options) {
193
213
  const depsAsync = options.dependencies.some((dep) => dep.mode === "async");
194
- const sourceId = options.dependencies.length > 0 ? options.dependencies[0][dependencyId] : Symbol();
214
+ const sourceId = options.dependencies.length > 0 ? options.dependencies[0][dependencySourceId] : Symbol();
195
215
  if (depsAsync) return createAsyncDerivedFromParserFromSyncFactory(sourceId, options);
196
216
  return createSyncDerivedFromParser(sourceId, options);
197
217
  }
@@ -210,7 +230,7 @@ function deriveFromSync(options) {
210
230
  * @since 0.10.0
211
231
  */
212
232
  function deriveFromAsync(options) {
213
- const sourceId = options.dependencies.length > 0 ? options.dependencies[0][dependencyId] : Symbol();
233
+ const sourceId = options.dependencies.length > 0 ? options.dependencies[0][dependencySourceId] : Symbol();
214
234
  return createAsyncDerivedFromParserFromAsyncFactory(sourceId, options);
215
235
  }
216
236
  function isAsyncModeParser(parser) {
@@ -282,7 +302,7 @@ async function parseDerivedResultWithSnapshotAsync(parser, input, sourceValues)
282
302
  return attachDefaultDependencySnapshot(await parseDerivedResultAsync(parser, input), snapshot);
283
303
  }
284
304
  function createSyncDerivedFromParser(sourceId, options) {
285
- const alldependencyIds = options.dependencies.map((dep) => dep[dependencyId]);
305
+ const alldependencyIds = options.dependencies.map((dep) => dep[dependencySourceId]);
286
306
  return {
287
307
  mode: "sync",
288
308
  metavar: options.metavar,
@@ -387,7 +407,7 @@ function createSyncDerivedFromParser(sourceId, options) {
387
407
  * factory returns an async parser.
388
408
  */
389
409
  function createAsyncDerivedFromParserFromAsyncFactory(sourceId, options) {
390
- const alldependencyIds = options.dependencies.map((dep) => dep[dependencyId]);
410
+ const alldependencyIds = options.dependencies.map((dep) => dep[dependencySourceId]);
391
411
  return {
392
412
  mode: "async",
393
413
  metavar: options.metavar,
@@ -475,7 +495,7 @@ function createAsyncDerivedFromParserFromAsyncFactory(sourceId, options) {
475
495
  * sources are async but the factory returns a sync parser.
476
496
  */
477
497
  function createAsyncDerivedFromParserFromSyncFactory(sourceId, options) {
478
- const alldependencyIds = options.dependencies.map((dep) => dep[dependencyId]);
498
+ const alldependencyIds = options.dependencies.map((dep) => dep[dependencySourceId]);
479
499
  return {
480
500
  mode: "async",
481
501
  metavar: options.metavar,
@@ -1089,6 +1109,7 @@ exports.deferredParseMarker = deferredParseMarker;
1089
1109
  exports.dependency = dependency;
1090
1110
  exports.dependencyId = dependencyId;
1091
1111
  exports.dependencyIds = dependencyIds;
1112
+ exports.dependencySourceId = dependencySourceId;
1092
1113
  exports.dependencySourceMarker = dependencySourceMarker;
1093
1114
  exports.dependencySourceStateMarker = dependencySourceStateMarker;
1094
1115
  exports.deriveFrom = deriveFrom;