@optique/core 1.3.0-dev.2379 → 1.3.0-dev.2381

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,6 +1,9 @@
1
+ import { Message } from "./message.js";
1
2
  import { DependencyRegistryLike } from "./registry-types.js";
2
3
  import { ValueParserResult } from "./valueparser.js";
3
4
  import { ParserDependencyMetadata } from "./dependency-metadata.js";
5
+ import { InputTrace } from "./input-trace.js";
6
+ import { ExecutionContext } from "./internal/parser.js";
4
7
 
5
8
  //#region src/dependency-runtime.d.ts
6
9
 
@@ -255,6 +258,164 @@ declare function replayDerivedParserAsync(node: RuntimeNode, rawInput: string, r
255
258
  * @since 1.0.0
256
259
  */
257
260
  declare function extractRawInputFromState(state: unknown): string | undefined;
261
+ /**
262
+ * Internal parser hook for constructs whose effectful scheduling nodes
263
+ * cannot be derived from flattened field pairs alone.
264
+ *
265
+ * `merge()` installs it so a parent's scheduling expansion uses the same
266
+ * child-indexed paths, declaration order, and duplicate-field exclusion
267
+ * as the merge's own scheduling pass; `or()`/`longestMatch()` install it
268
+ * to expose the committed branch; `command()` installs it to expose its
269
+ * inner parser once the command has matched. The returned node paths
270
+ * must match the execution paths used when the same parsers complete, so
271
+ * the run-scoped completion cache lines up.
272
+ *
273
+ * @internal
274
+ * @since 1.3.0
275
+ */
276
+ declare const effectfulSchedulingNodesKey: unique symbol;
277
+ /**
278
+ * The shape of the {@link effectfulSchedulingNodesKey} hook.
279
+ *
280
+ * @internal
281
+ * @since 1.3.0
282
+ */
283
+ type EffectfulSchedulingNodesFn = (state: unknown, parentPath: readonly PropertyKey[] | undefined) => readonly RuntimeNode[];
284
+ /**
285
+ * Forwards effectful scheduling through a shape-preserving wrapper such
286
+ * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
287
+ * wrapped parser—a selected exclusive or command branch, or an ordinary
288
+ * construct with nested effectful sources—stays visible to a parent
289
+ * construct's scheduling expansion.
290
+ *
291
+ * The installed hook simply re-exposes the inner parser as a node with
292
+ * the wrapper's own state shape unwrapped (`adaptState`, e.g. the
293
+ * `[innerState]` array used by `optional()`/`withDefault()`); the
294
+ * expansion then applies its ordinary rules to the inner parser, whether
295
+ * it carries its own hook, flattened field pairs, or source metadata.
296
+ * Wrappers are path-transparent, so the node keeps the wrapper's path.
297
+ *
298
+ * @internal
299
+ * @since 1.3.0
300
+ */
301
+ declare function defineForwardedEffectfulSchedulingNodes(wrapper: object, inner: {
302
+ readonly dependencyMetadata?: ParserDependencyMetadata;
303
+ }, adaptState?: (state: unknown) => unknown): void;
304
+ /**
305
+ * A completed effectful source result to be reused by the owning
306
+ * construct's final completion phase, keyed by the node's last path
307
+ * segment (its field key or tuple index).
308
+ *
309
+ * @internal
310
+ * @since 1.3.0
311
+ */
312
+ interface EffectfulSourceCompletion {
313
+ /** The node's field key or index (its last path segment). */
314
+ readonly key: PropertyKey;
315
+ /** The effectful completion result. */
316
+ readonly result: ValueParserResult<unknown>;
317
+ }
318
+ /**
319
+ * The result of scheduling effectful source completions.
320
+ *
321
+ * @internal
322
+ * @since 1.3.0
323
+ */
324
+ type EffectfulSourceCompletionResult = {
325
+ readonly success: true;
326
+ readonly completed: readonly EffectfulSourceCompletion[];
327
+ } | {
328
+ readonly success: false;
329
+ readonly error: Message;
330
+ };
331
+ /**
332
+ * Collects the dependency source IDs demanded by consumers among the
333
+ * given nodes and state subtree.
334
+ *
335
+ * A consumer demands its sources when it has raw input evidence: either a
336
+ * trace entry recorded at its path during parsing, or a legacy
337
+ * `DeferredParseState` embedded in the state subtree. Consumers without
338
+ * raw input never replay against real dependency values, so their sources
339
+ * are not demanded.
340
+ *
341
+ * @param nodes The direct-child runtime nodes of the owning construct.
342
+ * @param state The construct's state subtree (for legacy deferred states).
343
+ * @param trace The input trace recorded during parsing.
344
+ * @returns The set of demanded dependency source IDs.
345
+ * @internal
346
+ * @since 1.3.0
347
+ */
348
+ declare function collectDemandedDependencyIds(nodes: readonly RuntimeNode[], state: unknown, trace: InputTrace | undefined): ReadonlySet<symbol>;
349
+ /**
350
+ * Options for {@link completeEffectfulSourcesAsync}.
351
+ *
352
+ * @internal
353
+ * @since 1.3.0
354
+ */
355
+ interface CompleteEffectfulSourcesOptions {
356
+ /**
357
+ * Nodes used for demand detection instead of the scheduled nodes.
358
+ * Constructs whose parse-time trace paths differ from their scheduling
359
+ * node paths (e.g., `merge()`, which records child-indexed paths but
360
+ * schedules flattened field nodes) pass path-corrected nodes here.
361
+ */
362
+ readonly demandNodes?: readonly RuntimeNode[];
363
+ /**
364
+ * Whether a node's completion result may be cached by the owning
365
+ * construct for reuse in its final completion phase. Defaults to
366
+ * treating every node as reusable. Non-reusable completions (nodes
367
+ * expanded from nested children, or sources whose field value differs
368
+ * from the source value) rely on the run-scoped session cache to avoid
369
+ * running twice, and are skipped when no session is available.
370
+ */
371
+ readonly isReusable?: (node: RuntimeNode) => boolean;
372
+ }
373
+ /**
374
+ * Runs effectful source completions (e.g., interactive prompts) serially
375
+ * in declaration order for source nodes whose value is not yet registered.
376
+ *
377
+ * This is the scheduling half of the `completeSource` capability contract:
378
+ *
379
+ * - Runs only during real completion (`exec.phase === "complete"`); probe
380
+ * 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.
387
+ * - Completion results that are `undefined` or marked `deferred` are
388
+ * treated as declined and neither registered nor cached.
389
+ * - A successful result registers its value unless the value is
390
+ * `undefined`. When the node is reusable (a direct child whose field
391
+ * value is the source value), the result is also returned for reuse by
392
+ * the owning construct so the node is not completed twice; otherwise the
393
+ * effectful parser's own run-scoped session cache prevents a second
394
+ * execution, so nodes that are not reusable are only scheduled when a
395
+ * session is present. For a source behind a transform such as `map()`
396
+ * (`preservesSourceValue: false`), the `completeSource` contract still
397
+ * yields the pre-transform source value, so registration stays correct
398
+ * while the field's final value is produced separately.
399
+ * - A failed result (e.g., a cancelled prompt) marks the source as failed
400
+ * and aborts immediately—later effectful completions do not run.
401
+ *
402
+ * When the run-scoped session policy is `"demand-only"` (the phase-two
403
+ * seed pass), the demanded source IDs from
404
+ * {@link collectDemandedDependencyIds} are added to the session before any
405
+ * completion runs, letting effectful parsers defer when no phase-one
406
+ * consumer demands their value.
407
+ *
408
+ * @param nodes The runtime nodes to schedule, in declaration order.
409
+ * @param state The construct's state subtree (for demand detection).
410
+ * @param runtime The dependency runtime context.
411
+ * @param exec The execution context of the owning construct.
412
+ * @param options Scheduling options.
413
+ * @returns The scheduling result: completed nodes for reuse, or the first
414
+ * failure.
415
+ * @internal
416
+ * @since 1.3.0
417
+ */
418
+ declare function completeEffectfulSourcesAsync(nodes: readonly RuntimeNode[], state: unknown, runtime: DependencyRuntimeContext, exec: ExecutionContext | undefined, options?: CompleteEffectfulSourcesOptions): Promise<EffectfulSourceCompletionResult>;
258
419
  /**
259
420
  * Recursively collects dependency source values from {@link DependencySourceState}
260
421
  * objects found in the state tree and registers them in the runtime.
@@ -334,4 +495,4 @@ declare function buildRuntimeNodesFromArray(parsers: ReadonlyArray<{
334
495
  readonly initialState?: unknown;
335
496
  }>, stateArray: readonly unknown[], parentPath?: readonly PropertyKey[]): readonly RuntimeNode[];
336
497
  //#endregion
337
- export { DependencyRequest, DependencyResolution, DependencyRuntimeContext, ReplayKey, RuntimeNode, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync };
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 };
@@ -478,6 +478,218 @@ function extractRawInputFromState(state) {
478
478
  return void 0;
479
479
  }
480
480
  /**
481
+ * Internal parser hook for constructs whose effectful scheduling nodes
482
+ * cannot be derived from flattened field pairs alone.
483
+ *
484
+ * `merge()` installs it so a parent's scheduling expansion uses the same
485
+ * child-indexed paths, declaration order, and duplicate-field exclusion
486
+ * as the merge's own scheduling pass; `or()`/`longestMatch()` install it
487
+ * to expose the committed branch; `command()` installs it to expose its
488
+ * inner parser once the command has matched. The returned node paths
489
+ * must match the execution paths used when the same parsers complete, so
490
+ * the run-scoped completion cache lines up.
491
+ *
492
+ * @internal
493
+ * @since 1.3.0
494
+ */
495
+ const effectfulSchedulingNodesKey = Symbol("@optique/core/dependency-runtime/effectfulSchedulingNodes");
496
+ /**
497
+ * Forwards effectful scheduling through a shape-preserving wrapper such
498
+ * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
499
+ * wrapped parser—a selected exclusive or command branch, or an ordinary
500
+ * construct with nested effectful sources—stays visible to a parent
501
+ * construct's scheduling expansion.
502
+ *
503
+ * The installed hook simply re-exposes the inner parser as a node with
504
+ * the wrapper's own state shape unwrapped (`adaptState`, e.g. the
505
+ * `[innerState]` array used by `optional()`/`withDefault()`); the
506
+ * expansion then applies its ordinary rules to the inner parser, whether
507
+ * it carries its own hook, flattened field pairs, or source metadata.
508
+ * Wrappers are path-transparent, so the node keeps the wrapper's path.
509
+ *
510
+ * @internal
511
+ * @since 1.3.0
512
+ */
513
+ function defineForwardedEffectfulSchedulingNodes(wrapper, inner, adaptState) {
514
+ if (inner.dependencyMetadata?.source != null) return;
515
+ Object.defineProperty(wrapper, effectfulSchedulingNodesKey, {
516
+ value: ((state, parentPath) => [{
517
+ path: parentPath ?? [],
518
+ parser: inner,
519
+ state: adaptState == null ? state : adaptState(state)
520
+ }]),
521
+ configurable: true,
522
+ enumerable: false
523
+ });
524
+ }
525
+ /**
526
+ * Collects the dependency source IDs demanded by consumers among the
527
+ * given nodes and state subtree.
528
+ *
529
+ * A consumer demands its sources when it has raw input evidence: either a
530
+ * trace entry recorded at its path during parsing, or a legacy
531
+ * `DeferredParseState` embedded in the state subtree. Consumers without
532
+ * raw input never replay against real dependency values, so their sources
533
+ * are not demanded.
534
+ *
535
+ * @param nodes The direct-child runtime nodes of the owning construct.
536
+ * @param state The construct's state subtree (for legacy deferred states).
537
+ * @param trace The input trace recorded during parsing.
538
+ * @returns The set of demanded dependency source IDs.
539
+ * @internal
540
+ * @since 1.3.0
541
+ */
542
+ function collectDemandedDependencyIds(nodes, state, trace) {
543
+ const demanded = /* @__PURE__ */ new Set();
544
+ for (const node of nodes) {
545
+ const derived = node.parser.dependencyMetadata?.derived;
546
+ if (derived == null) continue;
547
+ const hasRawInput = trace?.get(node.path)?.rawInput != null || extractRawInputFromState(node.state) != null;
548
+ if (!hasRawInput) continue;
549
+ for (const id of derived.dependencyIds) demanded.add(id);
550
+ }
551
+ collectDeferredDemand(state, demanded, /* @__PURE__ */ new WeakSet());
552
+ return demanded;
553
+ }
554
+ function collectDeferredDemand(state, demanded, visited) {
555
+ if (state == null || typeof state !== "object") return;
556
+ if (visited.has(state)) return;
557
+ visited.add(state);
558
+ if (isDeferredParseState(state)) {
559
+ const ids = state.dependencyIds != null && state.dependencyIds.length > 0 ? state.dependencyIds : [state.dependencyId];
560
+ for (const id of ids) demanded.add(id);
561
+ return;
562
+ }
563
+ for (const key of Reflect.ownKeys(state)) collectDeferredDemand(state[key], demanded, visited);
564
+ }
565
+ /**
566
+ * Runs effectful source completions (e.g., interactive prompts) serially
567
+ * in declaration order for source nodes whose value is not yet registered.
568
+ *
569
+ * This is the scheduling half of the `completeSource` capability contract:
570
+ *
571
+ * - Runs only during real completion (`exec.phase === "complete"`); probe
572
+ * 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.
579
+ * - Completion results that are `undefined` or marked `deferred` are
580
+ * treated as declined and neither registered nor cached.
581
+ * - A successful result registers its value unless the value is
582
+ * `undefined`. When the node is reusable (a direct child whose field
583
+ * value is the source value), the result is also returned for reuse by
584
+ * the owning construct so the node is not completed twice; otherwise the
585
+ * effectful parser's own run-scoped session cache prevents a second
586
+ * execution, so nodes that are not reusable are only scheduled when a
587
+ * session is present. For a source behind a transform such as `map()`
588
+ * (`preservesSourceValue: false`), the `completeSource` contract still
589
+ * yields the pre-transform source value, so registration stays correct
590
+ * while the field's final value is produced separately.
591
+ * - A failed result (e.g., a cancelled prompt) marks the source as failed
592
+ * and aborts immediately—later effectful completions do not run.
593
+ *
594
+ * When the run-scoped session policy is `"demand-only"` (the phase-two
595
+ * seed pass), the demanded source IDs from
596
+ * {@link collectDemandedDependencyIds} are added to the session before any
597
+ * completion runs, letting effectful parsers defer when no phase-one
598
+ * consumer demands their value.
599
+ *
600
+ * @param nodes The runtime nodes to schedule, in declaration order.
601
+ * @param state The construct's state subtree (for demand detection).
602
+ * @param runtime The dependency runtime context.
603
+ * @param exec The execution context of the owning construct.
604
+ * @param options Scheduling options.
605
+ * @returns The scheduling result: completed nodes for reuse, or the first
606
+ * failure.
607
+ * @internal
608
+ * @since 1.3.0
609
+ */
610
+ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, options) {
611
+ const empty = {
612
+ success: true,
613
+ completed: []
614
+ };
615
+ if (exec == null || exec.phase !== "complete") return empty;
616
+ const session = exec.effectfulCompletionSession;
617
+ if (session?.policy === "demand-only") {
618
+ const demandNodes = options?.demandNodes ?? nodes;
619
+ const demanded = collectDemandedDependencyIds(demandNodes, state, exec.trace);
620
+ for (const id of demanded) session.demanded.add(id);
621
+ }
622
+ if (session != null) {
623
+ for (const result of session.results.values()) if (!result.success) return {
624
+ success: false,
625
+ error: result.error
626
+ };
627
+ }
628
+ const schedulable = nodes.filter((node) => node.parser.dependencyMetadata?.source?.completeSource != null);
629
+ if (schedulable.length === 0) return empty;
630
+ const preexisting = /* @__PURE__ */ new Set();
631
+ for (const node of schedulable) {
632
+ const source = node.parser.dependencyMetadata?.source;
633
+ if (source != null && runtime.hasSource(source.sourceId) && session?.effectfulSources.has(source.sourceId) !== true) preexisting.add(source.sourceId);
634
+ }
635
+ const completed = [];
636
+ for (const node of schedulable) {
637
+ const source = node.parser.dependencyMetadata?.source;
638
+ if (source?.completeSource == null) continue;
639
+ const pathKey = serializeSchedulingPath(node.path);
640
+ const priorResult = session?.completedByPath.get(pathKey);
641
+ if (priorResult != null) {
642
+ if (source.preservesSourceValue && (options?.isReusable?.(node) ?? true)) completed.push({
643
+ key: node.path[node.path.length - 1],
644
+ result: priorResult
645
+ });
646
+ continue;
647
+ }
648
+ if (preexisting.has(source.sourceId)) continue;
649
+ const reusable = source.preservesSourceValue && (options?.isReusable?.(node) ?? true);
650
+ if (!reusable && session == null) continue;
651
+ const childExec = {
652
+ ...exec,
653
+ path: node.path
654
+ };
655
+ const result = await source.completeSource(node.state, childExec);
656
+ if (result == null) continue;
657
+ if (!result.success) {
658
+ runtime.markSourceFailed(source.sourceId);
659
+ return {
660
+ success: false,
661
+ error: result.error
662
+ };
663
+ }
664
+ if (result.deferred === true) continue;
665
+ session?.completedByPath.set(pathKey, result);
666
+ if (reusable) completed.push({
667
+ key: node.path[node.path.length - 1],
668
+ result
669
+ });
670
+ if (result.value !== void 0) runtime.registerSource(source.sourceId, result.value);
671
+ }
672
+ return {
673
+ success: true,
674
+ completed
675
+ };
676
+ }
677
+ /**
678
+ * Serializes a scheduling node path into a stable string key, using
679
+ * length-prefixed segments so no separator escaping is needed.
680
+ */
681
+ function serializeSchedulingPath(path) {
682
+ return path.map((segment) => {
683
+ if (typeof segment === "symbol") {
684
+ const key = stableSymbolKey(segment);
685
+ return `y${key.length}:${key}`;
686
+ }
687
+ const tag = typeof segment === "number" ? "n" : "s";
688
+ const text = String(segment);
689
+ return `${tag}${text.length}:${text}`;
690
+ }).join("");
691
+ }
692
+ /**
481
693
  * Checks if a value is a plain object (not a class instance) for the
482
694
  * purpose of recursive state traversal.
483
695
  */
@@ -717,4 +929,4 @@ function buildRuntimeNodesFromArray(parsers, stateArray, parentPath) {
717
929
  }
718
930
 
719
931
  //#endregion
720
- export { buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync };
932
+ export { buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, replayDerivedParser, replayDerivedParserAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync };
package/dist/facade.cjs CHANGED
@@ -116,7 +116,17 @@ function createParseExec(parser) {
116
116
  function getCommandPath(exec) {
117
117
  return exec?.commandPath ?? [];
118
118
  }
119
- function createCompleteExec(exec, context) {
119
+ const effectfulSessionOptionsKey = Symbol("@optique/core/facade/effectfulSessionOptionsKey");
120
+ function getEffectfulSessionOption(options) {
121
+ return options[effectfulSessionOptionsKey];
122
+ }
123
+ function withEffectfulSessionOption(options, session) {
124
+ return {
125
+ ...options,
126
+ [effectfulSessionOptionsKey]: session
127
+ };
128
+ }
129
+ function createCompleteExec(exec, context, session) {
120
130
  const runtime = require_dependency_runtime.createDependencyRuntimeContext();
121
131
  return {
122
132
  ...exec,
@@ -124,10 +134,11 @@ function createCompleteExec(exec, context) {
124
134
  dependencyRuntime: runtime,
125
135
  dependencyRegistry: runtime.registry,
126
136
  commandPath: getCommandPath(context.exec) ?? exec.commandPath,
127
- trace: context.exec?.trace ?? context.trace ?? exec.trace
137
+ trace: context.exec?.trace ?? context.trace ?? exec.trace,
138
+ effectfulCompletionSession: session ?? require_internal_parser.createEffectfulCompletionSession()
128
139
  };
129
140
  }
130
- function attemptParseSync(parser, args, mode = "complete") {
141
+ function attemptParseSync(parser, args, mode = "complete", session) {
131
142
  const shouldUnwrapAnnotatedValue = require_internal_annotations.isInjectedAnnotationWrapper(parser.initialState);
132
143
  const exec = createParseExec(parser);
133
144
  let context = require_internal_parser.createParserContext({
@@ -166,7 +177,7 @@ function attemptParseSync(parser, args, mode = "complete") {
166
177
  kind: "success",
167
178
  value: void 0
168
179
  };
169
- const endResult = parser.complete(context.state, createCompleteExec(exec, context));
180
+ const endResult = parser.complete(context.state, createCompleteExec(exec, context, session));
170
181
  if (!endResult.success) return {
171
182
  kind: "failure",
172
183
  error: endResult.error,
@@ -180,7 +191,7 @@ function attemptParseSync(parser, args, mode = "complete") {
180
191
  value: shouldUnwrapAnnotatedValue ? require_internal_annotations.unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value
181
192
  };
182
193
  }
183
- async function attemptParseAsync(parser, args, mode = "complete") {
194
+ async function attemptParseAsync(parser, args, mode = "complete", session) {
184
195
  const shouldUnwrapAnnotatedValue = require_internal_annotations.isInjectedAnnotationWrapper(parser.initialState);
185
196
  const exec = createParseExec(parser);
186
197
  let context = require_internal_parser.createParserContext({
@@ -219,7 +230,7 @@ async function attemptParseAsync(parser, args, mode = "complete") {
219
230
  kind: "success",
220
231
  value: void 0
221
232
  };
222
- const endResult = await parser.complete(context.state, createCompleteExec(exec, context));
233
+ const endResult = await parser.complete(context.state, createCompleteExec(exec, context, session));
223
234
  if (!endResult.success) return {
224
235
  kind: "failure",
225
236
  error: endResult.error,
@@ -233,7 +244,7 @@ async function attemptParseAsync(parser, args, mode = "complete") {
233
244
  value: shouldUnwrapAnnotatedValue ? require_internal_annotations.unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value
234
245
  };
235
246
  }
236
- function createPhase2SeedExec(parser, context) {
247
+ function createPhase2SeedExec(parser, context, session) {
237
248
  const exec = {
238
249
  usage: parser.usage,
239
250
  phase: "parse",
@@ -248,7 +259,8 @@ function createPhase2SeedExec(parser, context) {
248
259
  dependencyRuntime: runtime,
249
260
  dependencyRegistry: runtime.registry,
250
261
  commandPath: getCommandPath(context.exec),
251
- trace: context.exec?.trace ?? context.trace ?? exec.trace
262
+ trace: context.exec?.trace ?? context.trace ?? exec.trace,
263
+ effectfulCompletionSession: session ?? require_internal_parser.createEffectfulCompletionSession()
252
264
  };
253
265
  }
254
266
  function createPhase2SeedContext(parser, args) {
@@ -265,27 +277,27 @@ function createPhase2SeedContext(parser, args) {
265
277
  optionsTerminated: false
266
278
  }, exec);
267
279
  }
268
- function extractPhase2SeedSync(parser, args) {
280
+ function extractPhase2SeedSync(parser, args, session) {
269
281
  let context = createPhase2SeedContext(parser, args);
270
282
  do {
271
283
  const result = parser.parse(context);
272
- if (!result.success) return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
284
+ if (!result.success) return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context, session));
273
285
  const previousBuffer = context.buffer;
274
286
  context = result.next;
275
- if (isBufferUnchanged(previousBuffer, context.buffer)) return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
287
+ if (isBufferUnchanged(previousBuffer, context.buffer)) return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context, session));
276
288
  } while (context.buffer.length > 0);
277
- return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
289
+ return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context, session));
278
290
  }
279
- async function extractPhase2SeedAsync(parser, args) {
291
+ async function extractPhase2SeedAsync(parser, args, session) {
280
292
  let context = createPhase2SeedContext(parser, args);
281
293
  do {
282
294
  const result = await parser.parse(context);
283
- if (!result.success) return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
295
+ if (!result.success) return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context, session));
284
296
  const previousBuffer = context.buffer;
285
297
  context = result.next;
286
- if (isBufferUnchanged(previousBuffer, context.buffer)) return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
298
+ if (isBufferUnchanged(previousBuffer, context.buffer)) return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context, session));
287
299
  } while (context.buffer.length > 0);
288
- return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
300
+ return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context, session));
289
301
  }
290
302
  function getMetaCommandAliases(names) {
291
303
  const [, firstAlias, ...restAliases] = names;
@@ -1246,7 +1258,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1246
1258
  };
1247
1259
  const parserMode = parser.mode;
1248
1260
  return require_mode_dispatch.dispatchByMode(parserMode, () => {
1249
- const attempted = attemptParseSync(parser, args);
1261
+ const attempted = attemptParseSync(parser, args, "complete", getEffectfulSessionOption(options));
1250
1262
  const classified = attempted.kind === "success" ? {
1251
1263
  type: "success",
1252
1264
  value: attempted.value
@@ -1255,7 +1267,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1255
1267
  if (handled instanceof Promise) throw new RunParserError("Synchronous parser returned async result.");
1256
1268
  return handled;
1257
1269
  }, async () => {
1258
- const attempted = await attemptParseAsync(parser, args);
1270
+ const attempted = await attemptParseAsync(parser, args, "complete", getEffectfulSessionOption(options));
1259
1271
  const classified = attempted.kind === "success" ? {
1260
1272
  type: "success",
1261
1273
  value: attempted.value
@@ -1603,16 +1615,24 @@ async function runWithBody(parser, programName, contexts, args, options) {
1603
1615
  if (parser.mode === "async") return runParser(augmentedParser1, programName, args, options);
1604
1616
  return Promise.resolve(runParser(augmentedParser1, programName, args, options));
1605
1617
  }
1606
- const firstPassSeed = await require_mode_dispatch.dispatchByMode(parser.mode, () => extractPhase2SeedSync(augmentedParser1, args), () => extractPhase2SeedAsync(augmentedParser1, args));
1618
+ const seedSession = require_internal_parser.createEffectfulCompletionSession("demand-only");
1619
+ const finalSession = {
1620
+ ...seedSession,
1621
+ policy: "eager",
1622
+ effectfulSources: /* @__PURE__ */ new Set(),
1623
+ completedByPath: /* @__PURE__ */ new Map()
1624
+ };
1625
+ const sessionOptions = withEffectfulSessionOption(options, finalSession);
1626
+ const firstPassSeed = await require_mode_dispatch.dispatchByMode(parser.mode, () => extractPhase2SeedSync(augmentedParser1, args, seedSession), () => extractPhase2SeedAsync(augmentedParser1, args, seedSession));
1607
1627
  if (firstPassSeed == null) {
1608
1628
  const fallbackParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1609
- if (parser.mode === "async") return runParser(fallbackParser, programName, args, options);
1610
- return Promise.resolve(runParser(fallbackParser, programName, args, options));
1629
+ if (parser.mode === "async") return runParser(fallbackParser, programName, args, sessionOptions);
1630
+ return Promise.resolve(runParser(fallbackParser, programName, args, sessionOptions));
1611
1631
  }
1612
1632
  const { annotations: finalAnnotations } = await collectFinalAnnotations(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
1613
1633
  const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1614
- if (parser.mode === "async") return runParser(augmentedParser2, programName, args, options);
1615
- return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1634
+ if (parser.mode === "async") return runParser(augmentedParser2, programName, args, sessionOptions);
1635
+ return Promise.resolve(runParser(augmentedParser2, programName, args, sessionOptions));
1616
1636
  }
1617
1637
  /**
1618
1638
  * Runs a parser with multiple source contexts.
@@ -1718,14 +1738,22 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
1718
1738
  }
1719
1739
  const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1720
1740
  if (!needsTwoPhase) return runParser(augmentedParser1, programName, args, options);
1721
- const firstPassSeed = extractPhase2SeedSync(augmentedParser1, args);
1741
+ const seedSession = require_internal_parser.createEffectfulCompletionSession("demand-only");
1742
+ const finalSession = {
1743
+ ...seedSession,
1744
+ policy: "eager",
1745
+ effectfulSources: /* @__PURE__ */ new Set(),
1746
+ completedByPath: /* @__PURE__ */ new Map()
1747
+ };
1748
+ const sessionOptions = withEffectfulSessionOption(options, finalSession);
1749
+ const firstPassSeed = extractPhase2SeedSync(augmentedParser1, args, seedSession);
1722
1750
  if (firstPassSeed == null) {
1723
1751
  const fallbackParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1724
- return runParser(fallbackParser, programName, args, options);
1752
+ return runParser(fallbackParser, programName, args, sessionOptions);
1725
1753
  }
1726
1754
  const { annotations: finalAnnotations } = collectFinalAnnotationsSync(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
1727
1755
  const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1728
- return runParser(augmentedParser2, programName, args, options);
1756
+ return runParser(augmentedParser2, programName, args, sessionOptions);
1729
1757
  }
1730
1758
  /**
1731
1759
  * Runs a synchronous parser with multiple source contexts.