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

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.
@@ -3,6 +3,32 @@ const require_internal_dependency = require('./internal/dependency.cjs');
3
3
  const require_internal_parser = require('./internal/parser.cjs');
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,182 @@ 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
+ function includeSourceFailureChain(error, sourceId, runtime) {
545
+ const chain = runtime.getSourceFailureChain(sourceId);
546
+ return chain == null || chain.length < 2 ? error : require_message.message`${error} Dependency chain: ${chain.join(" -> ")}.`;
547
+ }
548
+ function formatDependencyNodeMetavar(node) {
549
+ return node.parser.dependencyMetadata?.derived?.metavar ?? node.parser.dependencyMetadata?.source?.metavar ?? (node.path.map(String).join(".") || "<root>");
550
+ }
551
+ function formatDependencyNodeLabel(node) {
552
+ const metavar = node.parser.dependencyMetadata?.derived?.metavar ?? node.parser.dependencyMetadata?.source?.metavar;
553
+ const path = node.path.map(String).join(".") || "<root>";
554
+ return metavar == null ? path : `${metavar} (${path})`;
290
555
  }
291
556
  /**
292
557
  * Fills missing source defaults for source parsers whose state is
@@ -339,6 +604,7 @@ function fillMissingSourceDefaults(nodes, runtime) {
339
604
  error: result
340
605
  });
341
606
  }
607
+ resolveDerivedSourceValues(nodes, runtime);
342
608
  return failures;
343
609
  }
344
610
  /**
@@ -383,6 +649,7 @@ async function fillMissingSourceDefaultsAsync(nodes, runtime) {
383
649
  error: result
384
650
  });
385
651
  }
652
+ await resolveDerivedSourceValuesAsync(nodes, runtime);
386
653
  return failures;
387
654
  }
388
655
  /**
@@ -414,6 +681,10 @@ function replayDerivedParser(node, rawInput, runtime) {
414
681
  });
415
682
  if (resolution.kind === "missing") return void 0;
416
683
  if (resolution.kind === "partial") return void 0;
684
+ if (resolution.usedDefaults.every((usedDefault) => usedDefault)) {
685
+ const preliminary = extractPreliminaryResultFromState(node.state);
686
+ if (preliminary != null) return preliminary;
687
+ }
417
688
  const key = createReplayKey(node.path, rawInput, resolution.values, meta.derived.replayParse);
418
689
  const cached = runtime.getReplayResult(key);
419
690
  if (cached != null) return cached;
@@ -449,6 +720,10 @@ async function replayDerivedParserAsync(node, rawInput, runtime) {
449
720
  });
450
721
  if (resolution.kind === "missing") return void 0;
451
722
  if (resolution.kind === "partial") return void 0;
723
+ if (resolution.usedDefaults.every((usedDefault) => usedDefault)) {
724
+ const preliminary = extractPreliminaryResultFromState(node.state);
725
+ if (preliminary != null) return preliminary;
726
+ }
452
727
  const key = createReplayKey(node.path, rawInput, resolution.values, meta.derived.replayParse);
453
728
  const cached = runtime.getReplayResult(key);
454
729
  if (cached != null) return cached;
@@ -471,10 +746,60 @@ async function replayDerivedParserAsync(node, rawInput, runtime) {
471
746
  * @since 1.0.0
472
747
  */
473
748
  function extractRawInputFromState(state) {
749
+ return extractRawInputFromStateInner(state, /* @__PURE__ */ new Set());
750
+ }
751
+ function extractRawInputFromStateInner(state, visited) {
474
752
  if (state == null) return void 0;
475
753
  if (typeof state !== "object") return void 0;
754
+ if (visited.has(state)) return void 0;
755
+ visited.add(state);
756
+ const recordedRawInput = getRecordedDerivedRawInput(state);
757
+ if (recordedRawInput != null) return recordedRawInput;
476
758
  if (require_internal_dependency.isDeferredParseState(state)) return state.rawInput;
477
- if (Array.isArray(state) && state.length === 1 && require_internal_dependency.isDeferredParseState(state[0])) return state[0].rawInput;
759
+ if (Array.isArray(state)) {
760
+ for (let index = state.length - 1; index >= 0; index--) {
761
+ const rawInput = extractRawInputFromStateInner(state[index], visited);
762
+ if (rawInput != null) return rawInput;
763
+ }
764
+ return void 0;
765
+ }
766
+ const nested = /* @__PURE__ */ new Set();
767
+ for (const value of Object.values(state)) {
768
+ const rawInput = extractRawInputFromStateInner(value, visited);
769
+ if (rawInput != null) nested.add(rawInput);
770
+ }
771
+ return nested.size === 1 ? nested.values().next().value : void 0;
772
+ }
773
+ function extractPreliminaryResultFromState(state) {
774
+ return extractPreliminaryResultFromStateInner(state, /* @__PURE__ */ new Set());
775
+ }
776
+ function extractPreliminaryResultFromStateInner(state, visited) {
777
+ if (state == null || typeof state !== "object") return void 0;
778
+ if (visited.has(state)) return void 0;
779
+ visited.add(state);
780
+ if (require_internal_dependency.isDeferredParseState(state)) return state.preliminaryResult;
781
+ if (getRecordedDerivedRawInput(state) != null && "success" in state && typeof state.success === "boolean") {
782
+ if (state.success === true && "value" in state) return state;
783
+ if (state.success === false && "error" in state) return state;
784
+ }
785
+ if (Array.isArray(state)) {
786
+ for (let index = state.length - 1; index >= 0; index--) {
787
+ const result = extractPreliminaryResultFromStateInner(state[index], visited);
788
+ if (result != null) return result;
789
+ }
790
+ return void 0;
791
+ }
792
+ const nested = /* @__PURE__ */ new Set();
793
+ for (const value of Object.values(state)) {
794
+ const result = extractPreliminaryResultFromStateInner(value, visited);
795
+ if (result != null) nested.add(result);
796
+ }
797
+ return nested.size === 1 ? nested.values().next().value : void 0;
798
+ }
799
+ function getRecordedDerivedRawInput(state) {
800
+ const recorded = derivedRawInputs.get(state);
801
+ if (recorded != null) return recorded;
802
+ if (derivedRawInputKey in state && typeof state[derivedRawInputKey] === "string") return state[derivedRawInputKey];
478
803
  return void 0;
479
804
  }
480
805
  /**
@@ -658,6 +983,7 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
658
983
  completed: []
659
984
  };
660
985
  if (exec == null || exec.phase !== "complete") return empty;
986
+ registerRuntimeSourceMetadata(nodes, runtime);
661
987
  const session = exec.effectfulCompletionSession;
662
988
  if (session?.policy === "demand-only") {
663
989
  const demandNodes = options?.demandNodes ?? nodes;
@@ -667,6 +993,12 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
667
993
  while (demandAdded) {
668
994
  demandAdded = false;
669
995
  for (const node of nodes) {
996
+ const metadata = node.parser.dependencyMetadata;
997
+ if (metadata?.source != null && metadata.derived != null && session.demanded.has(metadata.source.sourceId)) for (const dependencySourceId of metadata.derived.dependencyIds) {
998
+ if (session.demanded.has(dependencySourceId)) continue;
999
+ session.demanded.add(dependencySourceId);
1000
+ demandAdded = true;
1001
+ }
670
1002
  if (node.requiresSourceId == null || node.providesSourceIds == null) continue;
671
1003
  if (session.demanded.has(node.requiresSourceId)) continue;
672
1004
  for (const provided of node.providesSourceIds) if (session.demanded.has(provided)) {
@@ -686,7 +1018,7 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
686
1018
  const schedulable = nodes.filter((node) => node.parser.dependencyMetadata?.source?.completeSource != null || node.prepare != null);
687
1019
  if (schedulable.length === 0 && options?.includeStructural !== true) return empty;
688
1020
  const completed = [];
689
- for (const node of nodes) {
1021
+ for (const node of orderDependencyNodes(nodes)) {
690
1022
  if (node.prepare != null) {
691
1023
  const barrierFailure = await node.prepare({
692
1024
  runtime,
@@ -703,6 +1035,28 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
703
1035
  const source = node.parser.dependencyMetadata?.source;
704
1036
  if (source == null) continue;
705
1037
  if (source.completeSource == null) {
1038
+ const derived = node.parser.dependencyMetadata?.derived;
1039
+ const rawInput = getNodeRawInput(node);
1040
+ if (derived != null && rawInput != null) {
1041
+ if (runtime.propagateSourceFailure(derived.dependencyIds, formatDependencyNodeMetavar(node), source.sourceId)) continue;
1042
+ const replayed = await replayDerivedParserAsync(node, rawInput, runtime);
1043
+ if (replayed == null) continue;
1044
+ if (!replayed.success) {
1045
+ runtime.markSourceFailed(source.sourceId);
1046
+ propagateRuntimeSourceFailures(nodes, runtime);
1047
+ return {
1048
+ success: false,
1049
+ error: includeSourceFailureChain(replayed.error, source.sourceId, runtime)
1050
+ };
1051
+ }
1052
+ if (replayed.deferred === true) continue;
1053
+ runtime.registerSource(source.sourceId, replayed.value);
1054
+ if (source.preservesSourceValue && (options?.isReusable?.(node) ?? true)) completed.push({
1055
+ key: node.path[node.path.length - 1],
1056
+ result: replayed
1057
+ });
1058
+ continue;
1059
+ }
706
1060
  const collected = options?.isCollected?.(node) ?? options?.isReusable?.(node) ?? true;
707
1061
  if (source.extractSourceValue == null || collected === false) continue;
708
1062
  const extracted = await source.extractSourceValue(node.state);
@@ -728,9 +1082,10 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
728
1082
  if (result == null) continue;
729
1083
  if (!result.success) {
730
1084
  runtime.markSourceFailed(source.sourceId);
1085
+ propagateRuntimeSourceFailures(nodes, runtime);
731
1086
  return {
732
1087
  success: false,
733
- error: result.error
1088
+ error: includeSourceFailureChain(result.error, source.sourceId, runtime)
734
1089
  };
735
1090
  }
736
1091
  if (result.deferred === true) continue;
@@ -960,11 +1315,15 @@ function buildRuntimeNodesFromPairs(pairs, state, parentPath) {
960
1315
  const nodes = [];
961
1316
  for (const [field, parser] of pairs) {
962
1317
  const fieldState = Object.hasOwn(state, field) ? state[field] : void 0;
1318
+ const rawInput = extractRawInputFromState(fieldState);
1319
+ const defaultDependencyValues = getDefaultDependencySnapshot(fieldState);
963
1320
  nodes.push({
964
1321
  path: [...prefix, field],
965
1322
  parser,
966
1323
  state: fieldState,
967
- matched: isMatchedState(fieldState, parser)
1324
+ matched: isMatchedState(fieldState, parser),
1325
+ ...rawInput != null ? { rawInput } : {},
1326
+ ...defaultDependencyValues != null ? { defaultDependencyValues } : {}
968
1327
  });
969
1328
  }
970
1329
  return nodes;
@@ -987,15 +1346,42 @@ function buildRuntimeNodesFromArray(parsers, stateArray, parentPath) {
987
1346
  for (let i = 0; i < parsers.length; i++) {
988
1347
  const parser = parsers[i];
989
1348
  const elemState = i < stateArray.length ? stateArray[i] : void 0;
1349
+ const rawInput = extractRawInputFromState(elemState);
1350
+ const defaultDependencyValues = getDefaultDependencySnapshot(elemState);
990
1351
  nodes.push({
991
1352
  path: [...prefix, i],
992
1353
  parser,
993
1354
  state: elemState,
994
- matched: isMatchedState(elemState, parser)
1355
+ matched: isMatchedState(elemState, parser),
1356
+ ...rawInput != null ? { rawInput } : {},
1357
+ ...defaultDependencyValues != null ? { defaultDependencyValues } : {}
995
1358
  });
996
1359
  }
997
1360
  return nodes;
998
1361
  }
1362
+ function getDefaultDependencySnapshot(state) {
1363
+ return getDefaultDependencySnapshotInner(state, /* @__PURE__ */ new Set());
1364
+ }
1365
+ function getDefaultDependencySnapshotInner(state, visited) {
1366
+ if (state == null || typeof state !== "object") return void 0;
1367
+ if (visited.has(state)) return void 0;
1368
+ visited.add(state);
1369
+ const direct = require_internal_dependency.getSnapshottedDefaultDependencyValues(state);
1370
+ if (direct != null) return direct;
1371
+ if (Array.isArray(state)) {
1372
+ for (let index = state.length - 1; index >= 0; index--) {
1373
+ const snapshot = getDefaultDependencySnapshotInner(state[index], visited);
1374
+ if (snapshot != null) return snapshot;
1375
+ }
1376
+ return void 0;
1377
+ }
1378
+ const nested = [];
1379
+ for (const value of Object.values(state)) {
1380
+ const snapshot = getDefaultDependencySnapshotInner(value, visited);
1381
+ if (snapshot != null) nested.push(snapshot);
1382
+ }
1383
+ return nested.length === 1 ? nested[0] : void 0;
1384
+ }
999
1385
 
1000
1386
  //#endregion
1001
1387
  exports.buildRuntimeNodesFromArray = buildRuntimeNodesFromArray;
@@ -1009,12 +1395,17 @@ exports.createDependencyFingerprint = createDependencyFingerprint;
1009
1395
  exports.createDependencyRuntimeContext = createDependencyRuntimeContext;
1010
1396
  exports.createReplayKey = createReplayKey;
1011
1397
  exports.defineForwardedEffectfulSchedulingNodes = defineForwardedEffectfulSchedulingNodes;
1398
+ exports.derivedRawInputKey = derivedRawInputKey;
1012
1399
  exports.effectfulSchedulingNodesKey = effectfulSchedulingNodesKey;
1013
1400
  exports.extractRawInputFromState = extractRawInputFromState;
1014
1401
  exports.fillMissingSourceDefaults = fillMissingSourceDefaults;
1015
1402
  exports.fillMissingSourceDefaultsAsync = fillMissingSourceDefaultsAsync;
1403
+ exports.orderDependencyNodes = orderDependencyNodes;
1404
+ exports.recordDerivedRawInput = recordDerivedRawInput;
1016
1405
  exports.replayDerivedParser = replayDerivedParser;
1017
1406
  exports.replayDerivedParserAsync = replayDerivedParserAsync;
1407
+ exports.resolveDerivedSourceValues = resolveDerivedSourceValues;
1408
+ exports.resolveDerivedSourceValuesAsync = resolveDerivedSourceValuesAsync;
1018
1409
  exports.resolveStateWithRuntime = resolveStateWithRuntime;
1019
1410
  exports.resolveStateWithRuntimeAsync = resolveStateWithRuntimeAsync;
1020
1411
  exports.serializeSchedulingPath = serializeSchedulingPath;
@@ -7,6 +7,29 @@ import { ExecutionContext } from "./internal/parser.cjs";
7
7
 
8
8
  //#region src/dependency-runtime.d.ts
9
9
 
10
+ /**
11
+ * Stores the raw token parsed by a derived value parser on structural parser
12
+ * states that can safely carry an in-band annotation.
13
+ *
14
+ * The execution trace remains the canonical diagnostic record. This state
15
+ * marker lets construct-independent dependency resolution replay a derived
16
+ * source before downstream fields complete.
17
+ * @internal
18
+ * @since 1.3.0
19
+ */
20
+ declare const derivedRawInputKey: unique symbol;
21
+ /**
22
+ * Records a derived parser's raw token without modifying its parse result.
23
+ *
24
+ * Parse results may be frozen or carry class private state, so primitives keep
25
+ * their original identity and associate replay metadata out of band.
26
+ *
27
+ * @param state The original value parser result.
28
+ * @param rawInput The token parsed into that result.
29
+ * @internal
30
+ * @since 1.3.0
31
+ */
32
+ declare function recordDerivedRawInput(state: object, rawInput: string): void;
10
33
  /**
11
34
  * A request to resolve one or more dependency values.
12
35
  *
@@ -89,6 +112,8 @@ interface RuntimeNode {
89
112
  };
90
113
  /** The parser's current state. */
91
114
  readonly state: unknown;
115
+ /** Raw input captured for a derived parser, when this node matched. */
116
+ readonly rawInput?: string;
92
117
  /**
93
118
  * Whether the parser consumed explicit input during parsing.
94
119
  * When `true`, the parser's state reflects user-provided input (which
@@ -142,6 +167,20 @@ interface RuntimeNode {
142
167
  */
143
168
  readonly requiresSourceId?: symbol;
144
169
  }
170
+ /**
171
+ * Options for resolving matched derived source values.
172
+ *
173
+ * @internal
174
+ * @since 1.3.0
175
+ */
176
+ interface ResolveDerivedSourceValuesOptions {
177
+ /**
178
+ * Whether an unpopulated effectful source can still provide a value later.
179
+ * Suggestion generation sets this to `"inactive"` because it never runs
180
+ * effects and must let downstream parsers use declared dependency defaults.
181
+ */
182
+ readonly effectfulProviders?: "pending" | "inactive";
183
+ }
145
184
  /**
146
185
  * The context handed to a {@link RuntimeNode.prepare} barrier.
147
186
  *
@@ -190,6 +229,15 @@ interface DependencyRuntimeContext {
190
229
  * defaults for failed sources.
191
230
  */
192
231
  markSourceFailed(sourceId: symbol): void;
232
+ /** Register a source's diagnostic label and upstream dependencies. */
233
+ registerSourceMetadata(sourceId: symbol, label: string, dependencyIds?: readonly symbol[]): void;
234
+ /**
235
+ * Propagate a failed upstream source through one derived dependency edge.
236
+ * Returns whether any upstream source had failed.
237
+ */
238
+ propagateSourceFailure(dependencyIds: readonly symbol[], label: string, sourceId?: symbol): boolean;
239
+ /** Return the most informative dependency chain for a failed source. */
240
+ getSourceFailureChain(sourceId: symbol): readonly string[] | undefined;
193
241
  /**
194
242
  * Check if a source was explicitly attempted but failed validation.
195
243
  */
@@ -242,6 +290,25 @@ declare function collectExplicitSourceValues(nodes: readonly RuntimeNode[], runt
242
290
  * @since 1.0.0
243
291
  */
244
292
  declare function collectExplicitSourceValuesAsync(nodes: readonly RuntimeNode[], runtime: DependencyRuntimeContext): Promise<void>;
293
+ /**
294
+ * Orders runtime nodes so every in-scope provider precedes a derived source
295
+ * that consumes it. Independent nodes retain declaration order.
296
+ *
297
+ * Missing providers create no edge because the consumer may use its declared
298
+ * default. Scheduling barriers act as providers for the source IDs their
299
+ * selected subtree may expose and depend on their discriminator source.
300
+ *
301
+ * @param nodes Runtime nodes in declaration order.
302
+ * @returns The same nodes in stable dependency order.
303
+ * @throws {TypeError} If active provider edges contain a cycle.
304
+ * @internal
305
+ * @since 1.3.0
306
+ */
307
+ declare function orderDependencyNodes(nodes: readonly RuntimeNode[]): readonly RuntimeNode[];
308
+ /** Resolves and publishes matched derived sources in stable dependency order. */
309
+ declare function resolveDerivedSourceValues(nodes: readonly RuntimeNode[], runtime: DependencyRuntimeContext, options?: ResolveDerivedSourceValuesOptions): void;
310
+ /** Async version of {@link resolveDerivedSourceValues}. */
311
+ declare function resolveDerivedSourceValuesAsync(nodes: readonly RuntimeNode[], runtime: DependencyRuntimeContext, options?: ResolveDerivedSourceValuesOptions): Promise<void>;
245
312
  /**
246
313
  * Fills missing source defaults for source parsers whose state is
247
314
  * unpopulated.
@@ -616,4 +683,4 @@ declare function buildRuntimeNodesFromArray(parsers: ReadonlyArray<{
616
683
  readonly initialState?: unknown;
617
684
  }>, stateArray: readonly unknown[], parentPath?: readonly PropertyKey[]): readonly RuntimeNode[];
618
685
  //#endregion
619
- export { CompleteEffectfulSourcesOptions, DependencyRequest, DependencyResolution, DependencyRuntimeContext, EffectfulSchedulingNodesFn, EffectfulSourceCompletion, EffectfulSourceCompletionResult, ReplayKey, RuntimeNode, SchedulingBarrierContext, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };
686
+ export { CompleteEffectfulSourcesOptions, DependencyRequest, DependencyResolution, DependencyRuntimeContext, EffectfulSchedulingNodesFn, EffectfulSourceCompletion, EffectfulSourceCompletionResult, ReplayKey, ResolveDerivedSourceValuesOptions, RuntimeNode, SchedulingBarrierContext, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, derivedRawInputKey, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, orderDependencyNodes, recordDerivedRawInput, replayDerivedParser, replayDerivedParserAsync, resolveDerivedSourceValues, resolveDerivedSourceValuesAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };