@optique/core 1.3.0-dev.2387 → 1.3.0-dev.2390

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.
@@ -494,6 +494,41 @@ function extractRawInputFromState(state) {
494
494
  */
495
495
  const effectfulSchedulingNodesKey = Symbol("@optique/core/dependency-runtime/effectfulSchedulingNodes");
496
496
  /**
497
+ * Opt-in marker for parsers whose {@link effectfulSchedulingNodesKey}
498
+ * hook also defines their explicit-source *collection* scope.
499
+ *
500
+ * A parent construct normally collects explicit source values from its
501
+ * direct children only. A parser carrying this marker (with value
502
+ * `true`) asks the parent to expand it through its scheduling hook
503
+ * before collecting, so command-line source values inside it—such as a
504
+ * `conditional()` discriminator, a committed conditional branch, or a
505
+ * selected `command()` subtree—register into the parent's dependency
506
+ * runtime exactly like a prompt-completed value would. Constructs
507
+ * without the marker (plain nested `object()`, uncommitted exclusive
508
+ * branches) keep their existing scope.
509
+ *
510
+ * @internal
511
+ * @since 1.3.0
512
+ */
513
+ const sourceCollectionExpansionKey = Symbol("@optique/core/dependency-runtime/sourceCollectionExpansion");
514
+ /**
515
+ * Static child parsers reachable for dependency-source estimation.
516
+ *
517
+ * `collectStaticSourceIds()` walks flattened field pairs, which stops at
518
+ * parsers whose children are not field-shaped—a `command()`'s inner
519
+ * parser, a nested `conditional()`'s branches, exclusive alternatives,
520
+ * or a transparent wrapper's inner construct. Such parsers expose their
521
+ * children here so the walk can estimate every source a subtree may
522
+ * provide. The estimate feeds demand-only control dependencies, where
523
+ * an overcount merely completes a discriminator earlier than strictly
524
+ * needed, while an undercount delays an effectful completion to the
525
+ * final pass and starves phase-two contexts of seed values.
526
+ *
527
+ * @internal
528
+ * @since 1.3.0
529
+ */
530
+ const staticSourceScopeKey = Symbol("@optique/core/dependency-runtime/staticSourceScope");
531
+ /**
497
532
  * Forwards effectful scheduling through a shape-preserving wrapper such
498
533
  * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
499
534
  * wrapped parser—a selected exclusive or command branch, or an ordinary
@@ -511,6 +546,11 @@ const effectfulSchedulingNodesKey = Symbol("@optique/core/dependency-runtime/eff
511
546
  * @since 1.3.0
512
547
  */
513
548
  function defineForwardedEffectfulSchedulingNodes(wrapper, inner, adaptState) {
549
+ Object.defineProperty(wrapper, staticSourceScopeKey, {
550
+ value: [inner],
551
+ configurable: true,
552
+ enumerable: false
553
+ });
514
554
  if (inner.dependencyMetadata?.source != null) return;
515
555
  Object.defineProperty(wrapper, effectfulSchedulingNodesKey, {
516
556
  value: ((state, parentPath) => [{
@@ -521,6 +561,11 @@ function defineForwardedEffectfulSchedulingNodes(wrapper, inner, adaptState) {
521
561
  configurable: true,
522
562
  enumerable: false
523
563
  });
564
+ if (inner[sourceCollectionExpansionKey] === true) Object.defineProperty(wrapper, sourceCollectionExpansionKey, {
565
+ value: true,
566
+ configurable: true,
567
+ enumerable: false
568
+ });
524
569
  }
525
570
  /**
526
571
  * Collects the dependency source IDs demanded by consumers among the
@@ -570,12 +615,12 @@ function collectDeferredDemand(state, demanded, visited) {
570
615
  *
571
616
  * - Runs only during real completion (`exec.phase === "complete"`); probe
572
617
  * and suggest phases return immediately without effects.
573
- * - Precedence is structural: a source whose value was registered before
574
- * the pass begins (from CLI state, environment, configuration, or a
575
- * default) or that has already failed is never completed effectfully.
576
- * When several scheduled occurrences share one source, each occurrence
577
- * still completes and re-registers, so the last occurrence wins—the
578
- * same rule as repeated command-line source occurrences.
618
+ * - Precedence is structural per occurrence: an effectful completion
619
+ * returns its own field's command-line or bound value without running
620
+ * the effect, and structural occurrences re-register their extracted
621
+ * values in declaration order. When several scheduled occurrences
622
+ * share one source, the last occurrence wins—the same rule as repeated
623
+ * command-line source occurrences.
579
624
  * - Completion results that are `undefined` or marked `deferred` are
580
625
  * treated as declined and neither registered nor cached.
581
626
  * - A successful result registers its value unless the value is
@@ -618,6 +663,19 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
618
663
  const demandNodes = options?.demandNodes ?? nodes;
619
664
  const demanded = collectDemandedDependencyIds(demandNodes, state, exec.trace);
620
665
  for (const id of demanded) session.demanded.add(id);
666
+ let demandAdded = true;
667
+ while (demandAdded) {
668
+ demandAdded = false;
669
+ for (const node of nodes) {
670
+ if (node.requiresSourceId == null || node.providesSourceIds == null) continue;
671
+ if (session.demanded.has(node.requiresSourceId)) continue;
672
+ for (const provided of node.providesSourceIds) if (session.demanded.has(provided)) {
673
+ session.demanded.add(node.requiresSourceId);
674
+ demandAdded = true;
675
+ break;
676
+ }
677
+ }
678
+ }
621
679
  }
622
680
  if (session != null) {
623
681
  for (const result of session.results.values()) if (!result.success) return {
@@ -625,14 +683,28 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
625
683
  error: result.error
626
684
  };
627
685
  }
628
- const schedulable = nodes.filter((node) => node.parser.dependencyMetadata?.source?.completeSource != null);
629
- if (schedulable.length === 0) return empty;
686
+ const schedulable = nodes.filter((node) => node.parser.dependencyMetadata?.source?.completeSource != null || node.prepare != null);
687
+ if (schedulable.length === 0 && options?.includeStructural !== true) return empty;
630
688
  const completed = [];
631
689
  for (const node of nodes) {
690
+ if (node.prepare != null) {
691
+ const barrierFailure = await node.prepare({
692
+ runtime,
693
+ exec,
694
+ schedule: (barrierNodes) => completeEffectfulSourcesAsync(barrierNodes, state, runtime, exec, {
695
+ isReusable: () => false,
696
+ isCollected: () => true,
697
+ includeStructural: true
698
+ }).then((result$1) => result$1.success ? void 0 : result$1)
699
+ });
700
+ if (barrierFailure != null) return barrierFailure;
701
+ continue;
702
+ }
632
703
  const source = node.parser.dependencyMetadata?.source;
633
704
  if (source == null) continue;
634
705
  if (source.completeSource == null) {
635
- if (source.extractSourceValue == null || (options?.isReusable?.(node) ?? true) === false) continue;
706
+ const collected = options?.isCollected?.(node) ?? options?.isReusable?.(node) ?? true;
707
+ if (source.extractSourceValue == null || collected === false) continue;
636
708
  const extracted = await source.extractSourceValue(node.state);
637
709
  if (extracted?.success === true && extracted.value !== void 0) runtime.registerSource(source.sourceId, extracted.value);
638
710
  continue;
@@ -676,7 +748,12 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
676
748
  }
677
749
  /**
678
750
  * Serializes a scheduling node path into a stable string key, using
679
- * length-prefixed segments so no separator escaping is needed.
751
+ * length-prefixed segments so no separator escaping is needed. Shared
752
+ * with constructs that key run-scoped session entries by path (e.g.,
753
+ * `conditional()`'s prepared branch selections).
754
+ *
755
+ * @internal
756
+ * @since 1.3.0
680
757
  */
681
758
  function serializeSchedulingPath(path) {
682
759
  return path.map(serializePathSegment).join("");
@@ -939,4 +1016,7 @@ exports.fillMissingSourceDefaultsAsync = fillMissingSourceDefaultsAsync;
939
1016
  exports.replayDerivedParser = replayDerivedParser;
940
1017
  exports.replayDerivedParserAsync = replayDerivedParserAsync;
941
1018
  exports.resolveStateWithRuntime = resolveStateWithRuntime;
942
- exports.resolveStateWithRuntimeAsync = resolveStateWithRuntimeAsync;
1019
+ exports.resolveStateWithRuntimeAsync = resolveStateWithRuntimeAsync;
1020
+ exports.serializeSchedulingPath = serializeSchedulingPath;
1021
+ exports.sourceCollectionExpansionKey = sourceCollectionExpansionKey;
1022
+ exports.staticSourceScopeKey = staticSourceScopeKey;
@@ -105,6 +105,63 @@ interface RuntimeNode {
105
105
  * @since 1.0.0
106
106
  */
107
107
  readonly defaultDependencyValues?: readonly unknown[];
108
+ /**
109
+ * A scheduling barrier: a preparation step executed serially at this
110
+ * node's declaration position during the effectful completion pass.
111
+ *
112
+ * A `conditional()` whose branch selection is still unknown installs
113
+ * one after its discriminator node: the preparation resolves the
114
+ * branch from the discriminator's completion, caches the decision in
115
+ * the run-scoped session so final completion reuses it, and schedules
116
+ * the chosen branch's nodes through `ctx.schedule` within the same
117
+ * pass. A failure aborts the pass like a failed effectful
118
+ * completion; `undefined` declines without effect.
119
+ *
120
+ * @since 1.3.0
121
+ */
122
+ readonly prepare?: (ctx: SchedulingBarrierContext) => Promise<{
123
+ readonly success: false;
124
+ readonly error: Message;
125
+ } | undefined>;
126
+ /**
127
+ * Source IDs that the subtree guarded by this barrier can provide.
128
+ * Used by the demand-only pass: when any of them is demanded, the
129
+ * barrier's {@link RuntimeNode.requiresSourceId} becomes demanded as
130
+ * a control dependency, so the guarding discriminator completes in
131
+ * the seed pass even though no consumer demands it directly.
132
+ *
133
+ * @since 1.3.0
134
+ */
135
+ readonly providesSourceIds?: ReadonlySet<symbol>;
136
+ /**
137
+ * The source ID whose completion this barrier's preparation depends
138
+ * on (a `conditional()` discriminator). See
139
+ * {@link RuntimeNode.providesSourceIds}.
140
+ *
141
+ * @since 1.3.0
142
+ */
143
+ readonly requiresSourceId?: symbol;
144
+ }
145
+ /**
146
+ * The context handed to a {@link RuntimeNode.prepare} barrier.
147
+ *
148
+ * @internal
149
+ * @since 1.3.0
150
+ */
151
+ interface SchedulingBarrierContext {
152
+ /** The runtime the current pass registers source values into. */
153
+ readonly runtime: DependencyRuntimeContext;
154
+ /** The execution context of the current pass. */
155
+ readonly exec: ExecutionContext | undefined;
156
+ /**
157
+ * Schedules further (already expanded) nodes within the current pass,
158
+ * at the barrier's position. Results are deduplicated through the
159
+ * run-scoped session and are not cached by the owning construct.
160
+ */
161
+ readonly schedule: (nodes: readonly RuntimeNode[]) => Promise<{
162
+ readonly success: false;
163
+ readonly error: Message;
164
+ } | undefined>;
108
165
  }
109
166
  /**
110
167
  * Dependency runtime context for centralized dependency resolution.
@@ -281,6 +338,41 @@ declare const effectfulSchedulingNodesKey: unique symbol;
281
338
  * @since 1.3.0
282
339
  */
283
340
  type EffectfulSchedulingNodesFn = (state: unknown, parentPath: readonly PropertyKey[] | undefined) => readonly RuntimeNode[];
341
+ /**
342
+ * Opt-in marker for parsers whose {@link effectfulSchedulingNodesKey}
343
+ * hook also defines their explicit-source *collection* scope.
344
+ *
345
+ * A parent construct normally collects explicit source values from its
346
+ * direct children only. A parser carrying this marker (with value
347
+ * `true`) asks the parent to expand it through its scheduling hook
348
+ * before collecting, so command-line source values inside it—such as a
349
+ * `conditional()` discriminator, a committed conditional branch, or a
350
+ * selected `command()` subtree—register into the parent's dependency
351
+ * runtime exactly like a prompt-completed value would. Constructs
352
+ * without the marker (plain nested `object()`, uncommitted exclusive
353
+ * branches) keep their existing scope.
354
+ *
355
+ * @internal
356
+ * @since 1.3.0
357
+ */
358
+ declare const sourceCollectionExpansionKey: unique symbol;
359
+ /**
360
+ * Static child parsers reachable for dependency-source estimation.
361
+ *
362
+ * `collectStaticSourceIds()` walks flattened field pairs, which stops at
363
+ * parsers whose children are not field-shaped—a `command()`'s inner
364
+ * parser, a nested `conditional()`'s branches, exclusive alternatives,
365
+ * or a transparent wrapper's inner construct. Such parsers expose their
366
+ * children here so the walk can estimate every source a subtree may
367
+ * provide. The estimate feeds demand-only control dependencies, where
368
+ * an overcount merely completes a discriminator earlier than strictly
369
+ * needed, while an undercount delays an effectful completion to the
370
+ * final pass and starves phase-two contexts of seed values.
371
+ *
372
+ * @internal
373
+ * @since 1.3.0
374
+ */
375
+ declare const staticSourceScopeKey: unique symbol;
284
376
  /**
285
377
  * Forwards effectful scheduling through a shape-preserving wrapper such
286
378
  * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
@@ -369,6 +461,25 @@ interface CompleteEffectfulSourcesOptions {
369
461
  * running twice, and are skipped when no session is available.
370
462
  */
371
463
  readonly isReusable?: (node: RuntimeNode) => boolean;
464
+ /**
465
+ * Whether a node participated in the owning construct's explicit
466
+ * source collection, and therefore must have its structural value
467
+ * re-registered at its declaration position so registration order
468
+ * follows declaration order across structural and effectful
469
+ * occurrences. This is wider than {@link isReusable}: nodes expanded
470
+ * from an opted-in child (see `sourceCollectionExpansionKey`) are
471
+ * collected but not reusable. Defaults to {@link isReusable}.
472
+ */
473
+ readonly isCollected?: (node: RuntimeNode) => boolean;
474
+ /**
475
+ * Processes structural source nodes even when no node carries an
476
+ * effectful completion or a barrier. A barrier's nested scheduling
477
+ * call sets this: the branch it schedules may hold only structural
478
+ * (command-line) values, which still must register into the pass's
479
+ * runtime, while ordinary construct passes keep the cheap early
480
+ * return.
481
+ */
482
+ readonly includeStructural?: boolean;
372
483
  }
373
484
  /**
374
485
  * Runs effectful source completions (e.g., interactive prompts) serially
@@ -378,12 +489,12 @@ interface CompleteEffectfulSourcesOptions {
378
489
  *
379
490
  * - Runs only during real completion (`exec.phase === "complete"`); probe
380
491
  * and suggest phases return immediately without effects.
381
- * - Precedence is structural: a source whose value was registered before
382
- * the pass begins (from CLI state, environment, configuration, or a
383
- * default) or that has already failed is never completed effectfully.
384
- * When several scheduled occurrences share one source, each occurrence
385
- * still completes and re-registers, so the last occurrence wins—the
386
- * same rule as repeated command-line source occurrences.
492
+ * - Precedence is structural per occurrence: an effectful completion
493
+ * returns its own field's command-line or bound value without running
494
+ * the effect, and structural occurrences re-register their extracted
495
+ * values in declaration order. When several scheduled occurrences
496
+ * share one source, the last occurrence wins—the same rule as repeated
497
+ * command-line source occurrences.
387
498
  * - Completion results that are `undefined` or marked `deferred` are
388
499
  * treated as declined and neither registered nor cached.
389
500
  * - A successful result registers its value unless the value is
@@ -416,6 +527,16 @@ interface CompleteEffectfulSourcesOptions {
416
527
  * @since 1.3.0
417
528
  */
418
529
  declare function completeEffectfulSourcesAsync(nodes: readonly RuntimeNode[], state: unknown, runtime: DependencyRuntimeContext, exec: ExecutionContext | undefined, options?: CompleteEffectfulSourcesOptions): Promise<EffectfulSourceCompletionResult>;
530
+ /**
531
+ * Serializes a scheduling node path into a stable string key, using
532
+ * length-prefixed segments so no separator escaping is needed. Shared
533
+ * with constructs that key run-scoped session entries by path (e.g.,
534
+ * `conditional()`'s prepared branch selections).
535
+ *
536
+ * @internal
537
+ * @since 1.3.0
538
+ */
539
+ declare function serializeSchedulingPath(path: readonly PropertyKey[]): string;
419
540
  /**
420
541
  * Recursively collects dependency source values from {@link DependencySourceState}
421
542
  * objects found in the state tree and registers them in the runtime.
@@ -495,4 +616,4 @@ declare function buildRuntimeNodesFromArray(parsers: ReadonlyArray<{
495
616
  readonly initialState?: unknown;
496
617
  }>, stateArray: readonly unknown[], parentPath?: readonly PropertyKey[]): readonly RuntimeNode[];
497
618
  //#endregion
498
- export { CompleteEffectfulSourcesOptions, DependencyRequest, DependencyResolution, DependencyRuntimeContext, EffectfulSchedulingNodesFn, EffectfulSourceCompletion, EffectfulSourceCompletionResult, ReplayKey, RuntimeNode, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync };
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 };
@@ -105,6 +105,63 @@ interface RuntimeNode {
105
105
  * @since 1.0.0
106
106
  */
107
107
  readonly defaultDependencyValues?: readonly unknown[];
108
+ /**
109
+ * A scheduling barrier: a preparation step executed serially at this
110
+ * node's declaration position during the effectful completion pass.
111
+ *
112
+ * A `conditional()` whose branch selection is still unknown installs
113
+ * one after its discriminator node: the preparation resolves the
114
+ * branch from the discriminator's completion, caches the decision in
115
+ * the run-scoped session so final completion reuses it, and schedules
116
+ * the chosen branch's nodes through `ctx.schedule` within the same
117
+ * pass. A failure aborts the pass like a failed effectful
118
+ * completion; `undefined` declines without effect.
119
+ *
120
+ * @since 1.3.0
121
+ */
122
+ readonly prepare?: (ctx: SchedulingBarrierContext) => Promise<{
123
+ readonly success: false;
124
+ readonly error: Message;
125
+ } | undefined>;
126
+ /**
127
+ * Source IDs that the subtree guarded by this barrier can provide.
128
+ * Used by the demand-only pass: when any of them is demanded, the
129
+ * barrier's {@link RuntimeNode.requiresSourceId} becomes demanded as
130
+ * a control dependency, so the guarding discriminator completes in
131
+ * the seed pass even though no consumer demands it directly.
132
+ *
133
+ * @since 1.3.0
134
+ */
135
+ readonly providesSourceIds?: ReadonlySet<symbol>;
136
+ /**
137
+ * The source ID whose completion this barrier's preparation depends
138
+ * on (a `conditional()` discriminator). See
139
+ * {@link RuntimeNode.providesSourceIds}.
140
+ *
141
+ * @since 1.3.0
142
+ */
143
+ readonly requiresSourceId?: symbol;
144
+ }
145
+ /**
146
+ * The context handed to a {@link RuntimeNode.prepare} barrier.
147
+ *
148
+ * @internal
149
+ * @since 1.3.0
150
+ */
151
+ interface SchedulingBarrierContext {
152
+ /** The runtime the current pass registers source values into. */
153
+ readonly runtime: DependencyRuntimeContext;
154
+ /** The execution context of the current pass. */
155
+ readonly exec: ExecutionContext | undefined;
156
+ /**
157
+ * Schedules further (already expanded) nodes within the current pass,
158
+ * at the barrier's position. Results are deduplicated through the
159
+ * run-scoped session and are not cached by the owning construct.
160
+ */
161
+ readonly schedule: (nodes: readonly RuntimeNode[]) => Promise<{
162
+ readonly success: false;
163
+ readonly error: Message;
164
+ } | undefined>;
108
165
  }
109
166
  /**
110
167
  * Dependency runtime context for centralized dependency resolution.
@@ -281,6 +338,41 @@ declare const effectfulSchedulingNodesKey: unique symbol;
281
338
  * @since 1.3.0
282
339
  */
283
340
  type EffectfulSchedulingNodesFn = (state: unknown, parentPath: readonly PropertyKey[] | undefined) => readonly RuntimeNode[];
341
+ /**
342
+ * Opt-in marker for parsers whose {@link effectfulSchedulingNodesKey}
343
+ * hook also defines their explicit-source *collection* scope.
344
+ *
345
+ * A parent construct normally collects explicit source values from its
346
+ * direct children only. A parser carrying this marker (with value
347
+ * `true`) asks the parent to expand it through its scheduling hook
348
+ * before collecting, so command-line source values inside it—such as a
349
+ * `conditional()` discriminator, a committed conditional branch, or a
350
+ * selected `command()` subtree—register into the parent's dependency
351
+ * runtime exactly like a prompt-completed value would. Constructs
352
+ * without the marker (plain nested `object()`, uncommitted exclusive
353
+ * branches) keep their existing scope.
354
+ *
355
+ * @internal
356
+ * @since 1.3.0
357
+ */
358
+ declare const sourceCollectionExpansionKey: unique symbol;
359
+ /**
360
+ * Static child parsers reachable for dependency-source estimation.
361
+ *
362
+ * `collectStaticSourceIds()` walks flattened field pairs, which stops at
363
+ * parsers whose children are not field-shaped—a `command()`'s inner
364
+ * parser, a nested `conditional()`'s branches, exclusive alternatives,
365
+ * or a transparent wrapper's inner construct. Such parsers expose their
366
+ * children here so the walk can estimate every source a subtree may
367
+ * provide. The estimate feeds demand-only control dependencies, where
368
+ * an overcount merely completes a discriminator earlier than strictly
369
+ * needed, while an undercount delays an effectful completion to the
370
+ * final pass and starves phase-two contexts of seed values.
371
+ *
372
+ * @internal
373
+ * @since 1.3.0
374
+ */
375
+ declare const staticSourceScopeKey: unique symbol;
284
376
  /**
285
377
  * Forwards effectful scheduling through a shape-preserving wrapper such
286
378
  * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
@@ -369,6 +461,25 @@ interface CompleteEffectfulSourcesOptions {
369
461
  * running twice, and are skipped when no session is available.
370
462
  */
371
463
  readonly isReusable?: (node: RuntimeNode) => boolean;
464
+ /**
465
+ * Whether a node participated in the owning construct's explicit
466
+ * source collection, and therefore must have its structural value
467
+ * re-registered at its declaration position so registration order
468
+ * follows declaration order across structural and effectful
469
+ * occurrences. This is wider than {@link isReusable}: nodes expanded
470
+ * from an opted-in child (see `sourceCollectionExpansionKey`) are
471
+ * collected but not reusable. Defaults to {@link isReusable}.
472
+ */
473
+ readonly isCollected?: (node: RuntimeNode) => boolean;
474
+ /**
475
+ * Processes structural source nodes even when no node carries an
476
+ * effectful completion or a barrier. A barrier's nested scheduling
477
+ * call sets this: the branch it schedules may hold only structural
478
+ * (command-line) values, which still must register into the pass's
479
+ * runtime, while ordinary construct passes keep the cheap early
480
+ * return.
481
+ */
482
+ readonly includeStructural?: boolean;
372
483
  }
373
484
  /**
374
485
  * Runs effectful source completions (e.g., interactive prompts) serially
@@ -378,12 +489,12 @@ interface CompleteEffectfulSourcesOptions {
378
489
  *
379
490
  * - Runs only during real completion (`exec.phase === "complete"`); probe
380
491
  * and suggest phases return immediately without effects.
381
- * - Precedence is structural: a source whose value was registered before
382
- * the pass begins (from CLI state, environment, configuration, or a
383
- * default) or that has already failed is never completed effectfully.
384
- * When several scheduled occurrences share one source, each occurrence
385
- * still completes and re-registers, so the last occurrence wins—the
386
- * same rule as repeated command-line source occurrences.
492
+ * - Precedence is structural per occurrence: an effectful completion
493
+ * returns its own field's command-line or bound value without running
494
+ * the effect, and structural occurrences re-register their extracted
495
+ * values in declaration order. When several scheduled occurrences
496
+ * share one source, the last occurrence wins—the same rule as repeated
497
+ * command-line source occurrences.
387
498
  * - Completion results that are `undefined` or marked `deferred` are
388
499
  * treated as declined and neither registered nor cached.
389
500
  * - A successful result registers its value unless the value is
@@ -416,6 +527,16 @@ interface CompleteEffectfulSourcesOptions {
416
527
  * @since 1.3.0
417
528
  */
418
529
  declare function completeEffectfulSourcesAsync(nodes: readonly RuntimeNode[], state: unknown, runtime: DependencyRuntimeContext, exec: ExecutionContext | undefined, options?: CompleteEffectfulSourcesOptions): Promise<EffectfulSourceCompletionResult>;
530
+ /**
531
+ * Serializes a scheduling node path into a stable string key, using
532
+ * length-prefixed segments so no separator escaping is needed. Shared
533
+ * with constructs that key run-scoped session entries by path (e.g.,
534
+ * `conditional()`'s prepared branch selections).
535
+ *
536
+ * @internal
537
+ * @since 1.3.0
538
+ */
539
+ declare function serializeSchedulingPath(path: readonly PropertyKey[]): string;
419
540
  /**
420
541
  * Recursively collects dependency source values from {@link DependencySourceState}
421
542
  * objects found in the state tree and registers them in the runtime.
@@ -495,4 +616,4 @@ declare function buildRuntimeNodesFromArray(parsers: ReadonlyArray<{
495
616
  readonly initialState?: unknown;
496
617
  }>, stateArray: readonly unknown[], parentPath?: readonly PropertyKey[]): readonly RuntimeNode[];
497
618
  //#endregion
498
- export { CompleteEffectfulSourcesOptions, DependencyRequest, DependencyResolution, DependencyRuntimeContext, EffectfulSchedulingNodesFn, EffectfulSourceCompletion, EffectfulSourceCompletionResult, ReplayKey, RuntimeNode, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync };
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 };
@@ -494,6 +494,41 @@ function extractRawInputFromState(state) {
494
494
  */
495
495
  const effectfulSchedulingNodesKey = Symbol("@optique/core/dependency-runtime/effectfulSchedulingNodes");
496
496
  /**
497
+ * Opt-in marker for parsers whose {@link effectfulSchedulingNodesKey}
498
+ * hook also defines their explicit-source *collection* scope.
499
+ *
500
+ * A parent construct normally collects explicit source values from its
501
+ * direct children only. A parser carrying this marker (with value
502
+ * `true`) asks the parent to expand it through its scheduling hook
503
+ * before collecting, so command-line source values inside it—such as a
504
+ * `conditional()` discriminator, a committed conditional branch, or a
505
+ * selected `command()` subtree—register into the parent's dependency
506
+ * runtime exactly like a prompt-completed value would. Constructs
507
+ * without the marker (plain nested `object()`, uncommitted exclusive
508
+ * branches) keep their existing scope.
509
+ *
510
+ * @internal
511
+ * @since 1.3.0
512
+ */
513
+ const sourceCollectionExpansionKey = Symbol("@optique/core/dependency-runtime/sourceCollectionExpansion");
514
+ /**
515
+ * Static child parsers reachable for dependency-source estimation.
516
+ *
517
+ * `collectStaticSourceIds()` walks flattened field pairs, which stops at
518
+ * parsers whose children are not field-shaped—a `command()`'s inner
519
+ * parser, a nested `conditional()`'s branches, exclusive alternatives,
520
+ * or a transparent wrapper's inner construct. Such parsers expose their
521
+ * children here so the walk can estimate every source a subtree may
522
+ * provide. The estimate feeds demand-only control dependencies, where
523
+ * an overcount merely completes a discriminator earlier than strictly
524
+ * needed, while an undercount delays an effectful completion to the
525
+ * final pass and starves phase-two contexts of seed values.
526
+ *
527
+ * @internal
528
+ * @since 1.3.0
529
+ */
530
+ const staticSourceScopeKey = Symbol("@optique/core/dependency-runtime/staticSourceScope");
531
+ /**
497
532
  * Forwards effectful scheduling through a shape-preserving wrapper such
498
533
  * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
499
534
  * wrapped parser—a selected exclusive or command branch, or an ordinary
@@ -511,6 +546,11 @@ const effectfulSchedulingNodesKey = Symbol("@optique/core/dependency-runtime/eff
511
546
  * @since 1.3.0
512
547
  */
513
548
  function defineForwardedEffectfulSchedulingNodes(wrapper, inner, adaptState) {
549
+ Object.defineProperty(wrapper, staticSourceScopeKey, {
550
+ value: [inner],
551
+ configurable: true,
552
+ enumerable: false
553
+ });
514
554
  if (inner.dependencyMetadata?.source != null) return;
515
555
  Object.defineProperty(wrapper, effectfulSchedulingNodesKey, {
516
556
  value: ((state, parentPath) => [{
@@ -521,6 +561,11 @@ function defineForwardedEffectfulSchedulingNodes(wrapper, inner, adaptState) {
521
561
  configurable: true,
522
562
  enumerable: false
523
563
  });
564
+ if (inner[sourceCollectionExpansionKey] === true) Object.defineProperty(wrapper, sourceCollectionExpansionKey, {
565
+ value: true,
566
+ configurable: true,
567
+ enumerable: false
568
+ });
524
569
  }
525
570
  /**
526
571
  * Collects the dependency source IDs demanded by consumers among the
@@ -570,12 +615,12 @@ function collectDeferredDemand(state, demanded, visited) {
570
615
  *
571
616
  * - Runs only during real completion (`exec.phase === "complete"`); probe
572
617
  * and suggest phases return immediately without effects.
573
- * - Precedence is structural: a source whose value was registered before
574
- * the pass begins (from CLI state, environment, configuration, or a
575
- * default) or that has already failed is never completed effectfully.
576
- * When several scheduled occurrences share one source, each occurrence
577
- * still completes and re-registers, so the last occurrence wins—the
578
- * same rule as repeated command-line source occurrences.
618
+ * - Precedence is structural per occurrence: an effectful completion
619
+ * returns its own field's command-line or bound value without running
620
+ * the effect, and structural occurrences re-register their extracted
621
+ * values in declaration order. When several scheduled occurrences
622
+ * share one source, the last occurrence wins—the same rule as repeated
623
+ * command-line source occurrences.
579
624
  * - Completion results that are `undefined` or marked `deferred` are
580
625
  * treated as declined and neither registered nor cached.
581
626
  * - A successful result registers its value unless the value is
@@ -618,6 +663,19 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
618
663
  const demandNodes = options?.demandNodes ?? nodes;
619
664
  const demanded = collectDemandedDependencyIds(demandNodes, state, exec.trace);
620
665
  for (const id of demanded) session.demanded.add(id);
666
+ let demandAdded = true;
667
+ while (demandAdded) {
668
+ demandAdded = false;
669
+ for (const node of nodes) {
670
+ if (node.requiresSourceId == null || node.providesSourceIds == null) continue;
671
+ if (session.demanded.has(node.requiresSourceId)) continue;
672
+ for (const provided of node.providesSourceIds) if (session.demanded.has(provided)) {
673
+ session.demanded.add(node.requiresSourceId);
674
+ demandAdded = true;
675
+ break;
676
+ }
677
+ }
678
+ }
621
679
  }
622
680
  if (session != null) {
623
681
  for (const result of session.results.values()) if (!result.success) return {
@@ -625,14 +683,28 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
625
683
  error: result.error
626
684
  };
627
685
  }
628
- const schedulable = nodes.filter((node) => node.parser.dependencyMetadata?.source?.completeSource != null);
629
- if (schedulable.length === 0) return empty;
686
+ const schedulable = nodes.filter((node) => node.parser.dependencyMetadata?.source?.completeSource != null || node.prepare != null);
687
+ if (schedulable.length === 0 && options?.includeStructural !== true) return empty;
630
688
  const completed = [];
631
689
  for (const node of nodes) {
690
+ if (node.prepare != null) {
691
+ const barrierFailure = await node.prepare({
692
+ runtime,
693
+ exec,
694
+ schedule: (barrierNodes) => completeEffectfulSourcesAsync(barrierNodes, state, runtime, exec, {
695
+ isReusable: () => false,
696
+ isCollected: () => true,
697
+ includeStructural: true
698
+ }).then((result$1) => result$1.success ? void 0 : result$1)
699
+ });
700
+ if (barrierFailure != null) return barrierFailure;
701
+ continue;
702
+ }
632
703
  const source = node.parser.dependencyMetadata?.source;
633
704
  if (source == null) continue;
634
705
  if (source.completeSource == null) {
635
- if (source.extractSourceValue == null || (options?.isReusable?.(node) ?? true) === false) continue;
706
+ const collected = options?.isCollected?.(node) ?? options?.isReusable?.(node) ?? true;
707
+ if (source.extractSourceValue == null || collected === false) continue;
636
708
  const extracted = await source.extractSourceValue(node.state);
637
709
  if (extracted?.success === true && extracted.value !== void 0) runtime.registerSource(source.sourceId, extracted.value);
638
710
  continue;
@@ -676,7 +748,12 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
676
748
  }
677
749
  /**
678
750
  * Serializes a scheduling node path into a stable string key, using
679
- * length-prefixed segments so no separator escaping is needed.
751
+ * length-prefixed segments so no separator escaping is needed. Shared
752
+ * with constructs that key run-scoped session entries by path (e.g.,
753
+ * `conditional()`'s prepared branch selections).
754
+ *
755
+ * @internal
756
+ * @since 1.3.0
680
757
  */
681
758
  function serializeSchedulingPath(path) {
682
759
  return path.map(serializePathSegment).join("");
@@ -921,4 +998,4 @@ function buildRuntimeNodesFromArray(parsers, stateArray, parentPath) {
921
998
  }
922
999
 
923
1000
  //#endregion
924
- export { buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync };
1001
+ export { buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };