@optique/core 1.0.0-dev.1778 → 1.0.0-dev.1786

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.
@@ -126,9 +126,14 @@ interface SourceContext<TRequiredOptions = void> {
126
126
  *
127
127
  * 1. *First call*: `parsed` is `undefined`. Static contexts should return
128
128
  * their annotations, while dynamic contexts should return an empty object.
129
- * 2. *Second call*: `parsed` contains the result from the first parse pass.
130
- * Dynamic contexts can use this to load external data (e.g., reading
131
- * a config file whose path was determined in the first pass).
129
+ * 2. *Second call*: `parsed` contains the first pass result, or a
130
+ * best-effort partial value extracted from parser state when the first
131
+ * pass reached a usable intermediate state but still did not complete
132
+ * successfully. Dynamic contexts can use this to load external data
133
+ * (e.g., reading a config file whose path was determined in the first
134
+ * pass). Deferred or otherwise unresolved fields may be `undefined`.
135
+ * If the runner cannot extract a usable value at all, this second call
136
+ * is skipped and the original parse failure is reported instead.
132
137
  *
133
138
  * @param parsed Optional parsed result from a previous parse pass.
134
139
  * Static contexts can ignore this parameter.
package/dist/context.d.ts CHANGED
@@ -126,9 +126,14 @@ interface SourceContext<TRequiredOptions = void> {
126
126
  *
127
127
  * 1. *First call*: `parsed` is `undefined`. Static contexts should return
128
128
  * their annotations, while dynamic contexts should return an empty object.
129
- * 2. *Second call*: `parsed` contains the result from the first parse pass.
130
- * Dynamic contexts can use this to load external data (e.g., reading
131
- * a config file whose path was determined in the first pass).
129
+ * 2. *Second call*: `parsed` contains the first pass result, or a
130
+ * best-effort partial value extracted from parser state when the first
131
+ * pass reached a usable intermediate state but still did not complete
132
+ * successfully. Dynamic contexts can use this to load external data
133
+ * (e.g., reading a config file whose path was determined in the first
134
+ * pass). Deferred or otherwise unresolved fields may be `undefined`.
135
+ * If the runner cannot extract a usable value at all, this second call
136
+ * is skipped and the original parse failure is reported instead.
132
137
  *
133
138
  * @param parsed Optional parsed result from a previous parse pass.
134
139
  * Static contexts can ignore this parameter.
package/dist/facade.cjs CHANGED
@@ -5,10 +5,13 @@ const require_validate = require('./validate.cjs');
5
5
  const require_usage = require('./usage.cjs');
6
6
  const require_doc = require('./doc.cjs');
7
7
  const require_mode_dispatch = require('./mode-dispatch.cjs');
8
+ const require_input_trace = require('./input-trace.cjs');
9
+ const require_phase2_seed = require('./phase2-seed.cjs');
8
10
  const require_modifiers = require('./modifiers.cjs');
9
11
  const require_valueparser = require('./valueparser.cjs');
10
12
  const require_primitives = require('./primitives.cjs');
11
13
  const require_parser = require('./parser.cjs');
14
+ const require_dependency_runtime = require('./dependency-runtime.cjs');
12
15
  const require_constructs = require('./constructs.cjs');
13
16
 
14
17
  //#region src/facade.ts
@@ -89,6 +92,60 @@ function prepareParsedForContexts(parsed, deferred, deferredKeys) {
89
92
  function withPreparedParsedForContext(context, preparedParsed, run) {
90
93
  return run(finalizeParsedForContext(context, preparedParsed));
91
94
  }
95
+ function isBufferUnchanged(previous, current) {
96
+ return current.length > 0 && current.length === previous.length && current.every((item, i) => item === previous[i]);
97
+ }
98
+ function createPhase2SeedExec(parser, context) {
99
+ const exec = {
100
+ usage: parser.usage,
101
+ phase: "parse",
102
+ path: [],
103
+ trace: require_input_trace.createInputTrace()
104
+ };
105
+ const runtime = require_dependency_runtime.createDependencyRuntimeContext();
106
+ return {
107
+ ...exec,
108
+ phase: "complete",
109
+ dependencyRuntime: runtime,
110
+ dependencyRegistry: runtime.registry,
111
+ trace: context.exec?.trace ?? context.trace ?? exec.trace
112
+ };
113
+ }
114
+ function createPhase2SeedContext(parser, args) {
115
+ const exec = {
116
+ usage: parser.usage,
117
+ phase: "parse",
118
+ path: [],
119
+ trace: require_input_trace.createInputTrace()
120
+ };
121
+ return require_parser.createParserContext({
122
+ buffer: args,
123
+ state: parser.initialState,
124
+ optionsTerminated: false
125
+ }, exec);
126
+ }
127
+ function extractPhase2SeedSync(parser, args) {
128
+ let context = createPhase2SeedContext(parser, args);
129
+ do {
130
+ const result = parser.parse(context);
131
+ if (!result.success) return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
132
+ const previousBuffer = context.buffer;
133
+ context = result.next;
134
+ if (isBufferUnchanged(previousBuffer, context.buffer)) return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
135
+ } while (context.buffer.length > 0);
136
+ return require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
137
+ }
138
+ async function extractPhase2SeedAsync(parser, args) {
139
+ let context = createPhase2SeedContext(parser, args);
140
+ do {
141
+ const result = await parser.parse(context);
142
+ if (!result.success) return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
143
+ const previousBuffer = context.buffer;
144
+ context = result.next;
145
+ if (isBufferUnchanged(previousBuffer, context.buffer)) return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
146
+ } while (context.buffer.length > 0);
147
+ return await require_phase2_seed.completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
148
+ }
92
149
  /**
93
150
  * Creates help parsers based on the sub-config.
94
151
  */
@@ -1197,30 +1254,13 @@ async function runWithBody(parser, programName, contexts, args, options) {
1197
1254
  return Promise.resolve(runParser(augmentedParser, programName, args, options));
1198
1255
  }
1199
1256
  const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1200
- let firstPassResult;
1201
- let firstPassDeferred;
1202
- let firstPassDeferredKeys;
1203
- let firstPassFailed = false;
1204
- try {
1205
- if (parser.$mode === "async") firstPassResult = await require_parser.parseAsync(augmentedParser1, args);
1206
- else firstPassResult = require_parser.parseSync(augmentedParser1, args);
1207
- if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
1208
- const result = firstPassResult;
1209
- if (result.success) {
1210
- firstPassResult = result.value;
1211
- firstPassDeferred = result.deferred;
1212
- firstPassDeferredKeys = result.deferredKeys;
1213
- } else firstPassFailed = true;
1214
- }
1215
- } catch {
1216
- firstPassFailed = true;
1217
- }
1218
- if (firstPassFailed) {
1257
+ const firstPassSeed = await require_mode_dispatch.dispatchByMode(parser.$mode, () => extractPhase2SeedSync(augmentedParser1, args), () => extractPhase2SeedAsync(augmentedParser1, args));
1258
+ if (firstPassSeed == null) {
1219
1259
  const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1220
1260
  if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1221
1261
  return Promise.resolve(runParser(augmentedParser, programName, args, options));
1222
1262
  }
1223
- const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassResult, ctxOptions, firstPassDeferred, firstPassDeferredKeys);
1263
+ const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
1224
1264
  const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1225
1265
  const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1226
1266
  if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
@@ -1236,13 +1276,19 @@ async function runWithBody(parser, programName, contexts, args, options) {
1236
1276
  *
1237
1277
  * 1. *Phase 1*: Collect annotations from all contexts (static contexts return
1238
1278
  * their data, dynamic contexts may return empty).
1239
- * 2. *First parse*: Parse with Phase 1 annotations.
1279
+ * 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
1280
+ * successfully, its value becomes the phase-two input. If the parser
1281
+ * reaches a usable intermediate state but still does not complete
1282
+ * successfully, the runner extracts a best-effort seed from that state
1283
+ * instead.
1240
1284
  * 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with the first
1241
- * parse result.
1285
+ * pass value. Deferred or otherwise unresolved fields in `parsed` may be
1286
+ * `undefined`.
1242
1287
  * 4. *Second parse*: Parse again with merged annotations from both phases.
1243
1288
  *
1244
- * If all contexts are static (no dynamic contexts), the second parse is skipped
1245
- * for optimization.
1289
+ * If all contexts are static (no dynamic contexts), the second parse is
1290
+ * skipped for optimization. Phase 2 is also skipped when the first pass does
1291
+ * not yield any usable seed at all.
1246
1292
  *
1247
1293
  * @template TParser The parser type.
1248
1294
  * @template THelp Return type when help is shown.
@@ -1317,20 +1363,12 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
1317
1363
  return runParser(augmentedParser, programName, args, options);
1318
1364
  }
1319
1365
  const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1320
- let firstPassResult;
1321
- let firstPassDeferred;
1322
- let firstPassDeferredKeys;
1323
- try {
1324
- const result = require_parser.parseSync(augmentedParser1, args);
1325
- if (result.success) {
1326
- firstPassResult = result.value;
1327
- firstPassDeferred = result.deferred;
1328
- firstPassDeferredKeys = result.deferredKeys;
1329
- } else return runParser(augmentedParser1, programName, args, options);
1330
- } catch {
1331
- return runParser(augmentedParser1, programName, args, options);
1366
+ const firstPassSeed = extractPhase2SeedSync(augmentedParser1, args);
1367
+ if (firstPassSeed == null) {
1368
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1369
+ return runParser(augmentedParser, programName, args, options);
1332
1370
  }
1333
- const { annotationsList: phase2AnnotationsList } = collectAnnotationsSync(contexts, firstPassResult, ctxOptions, firstPassDeferred, firstPassDeferredKeys);
1371
+ const { annotationsList: phase2AnnotationsList } = collectAnnotationsSync(contexts, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
1334
1372
  const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1335
1373
  const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1336
1374
  return runParser(augmentedParser2, programName, args, options);
@@ -1339,7 +1377,9 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
1339
1377
  * Runs a synchronous parser with multiple source contexts.
1340
1378
  *
1341
1379
  * This is the sync-only variant of {@link runWith}. All contexts must return
1342
- * annotations synchronously (not Promises).
1380
+ * annotations synchronously (not Promises). It uses the same two-phase
1381
+ * best-effort seed extraction as {@link runWith} when dynamic contexts are
1382
+ * present.
1343
1383
  *
1344
1384
  * @template TParser The sync parser type.
1345
1385
  * @template THelp Return type when help is shown.
@@ -1415,10 +1455,25 @@ function runWithAsync(parser, programName, contexts, options) {
1415
1455
  */
1416
1456
  function injectAnnotationsIntoParser(parser, annotations) {
1417
1457
  const newInitialState = require_annotations.injectAnnotations(parser.initialState, annotations);
1418
- return {
1419
- ...parser,
1420
- initialState: newInitialState
1458
+ const descriptors = { ...Object.getOwnPropertyDescriptors(parser) };
1459
+ const initialState = descriptors.initialState;
1460
+ descriptors.initialState = initialState == null ? {
1461
+ value: newInitialState,
1462
+ writable: true,
1463
+ enumerable: true,
1464
+ configurable: true
1465
+ } : "get" in initialState || "set" in initialState ? {
1466
+ value: newInitialState,
1467
+ writable: true,
1468
+ enumerable: initialState.enumerable ?? true,
1469
+ configurable: initialState.configurable ?? true
1470
+ } : {
1471
+ value: newInitialState,
1472
+ writable: initialState.writable ?? true,
1473
+ enumerable: initialState.enumerable ?? true,
1474
+ configurable: initialState.configurable ?? true
1421
1475
  };
1476
+ return Object.create(Object.getPrototypeOf(parser), descriptors);
1422
1477
  }
1423
1478
 
1424
1479
  //#endregion
package/dist/facade.d.cts CHANGED
@@ -412,13 +412,19 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
412
412
  *
413
413
  * 1. *Phase 1*: Collect annotations from all contexts (static contexts return
414
414
  * their data, dynamic contexts may return empty).
415
- * 2. *First parse*: Parse with Phase 1 annotations.
415
+ * 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
416
+ * successfully, its value becomes the phase-two input. If the parser
417
+ * reaches a usable intermediate state but still does not complete
418
+ * successfully, the runner extracts a best-effort seed from that state
419
+ * instead.
416
420
  * 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with the first
417
- * parse result.
421
+ * pass value. Deferred or otherwise unresolved fields in `parsed` may be
422
+ * `undefined`.
418
423
  * 4. *Second parse*: Parse again with merged annotations from both phases.
419
424
  *
420
- * If all contexts are static (no dynamic contexts), the second parse is skipped
421
- * for optimization.
425
+ * If all contexts are static (no dynamic contexts), the second parse is
426
+ * skipped for optimization. Phase 2 is also skipped when the first pass does
427
+ * not yield any usable seed at all.
422
428
  *
423
429
  * @template TParser The parser type.
424
430
  * @template THelp Return type when help is shown.
@@ -460,7 +466,9 @@ declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContex
460
466
  * Runs a synchronous parser with multiple source contexts.
461
467
  *
462
468
  * This is the sync-only variant of {@link runWith}. All contexts must return
463
- * annotations synchronously (not Promises).
469
+ * annotations synchronously (not Promises). It uses the same two-phase
470
+ * best-effort seed extraction as {@link runWith} when dynamic contexts are
471
+ * present.
464
472
  *
465
473
  * @template TParser The sync parser type.
466
474
  * @template THelp Return type when help is shown.
package/dist/facade.d.ts CHANGED
@@ -412,13 +412,19 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
412
412
  *
413
413
  * 1. *Phase 1*: Collect annotations from all contexts (static contexts return
414
414
  * their data, dynamic contexts may return empty).
415
- * 2. *First parse*: Parse with Phase 1 annotations.
415
+ * 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
416
+ * successfully, its value becomes the phase-two input. If the parser
417
+ * reaches a usable intermediate state but still does not complete
418
+ * successfully, the runner extracts a best-effort seed from that state
419
+ * instead.
416
420
  * 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with the first
417
- * parse result.
421
+ * pass value. Deferred or otherwise unresolved fields in `parsed` may be
422
+ * `undefined`.
418
423
  * 4. *Second parse*: Parse again with merged annotations from both phases.
419
424
  *
420
- * If all contexts are static (no dynamic contexts), the second parse is skipped
421
- * for optimization.
425
+ * If all contexts are static (no dynamic contexts), the second parse is
426
+ * skipped for optimization. Phase 2 is also skipped when the first pass does
427
+ * not yield any usable seed at all.
422
428
  *
423
429
  * @template TParser The parser type.
424
430
  * @template THelp Return type when help is shown.
@@ -460,7 +466,9 @@ declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContex
460
466
  * Runs a synchronous parser with multiple source contexts.
461
467
  *
462
468
  * This is the sync-only variant of {@link runWith}. All contexts must return
463
- * annotations synchronously (not Promises).
469
+ * annotations synchronously (not Promises). It uses the same two-phase
470
+ * best-effort seed extraction as {@link runWith} when dynamic contexts are
471
+ * present.
464
472
  *
465
473
  * @template TParser The sync parser type.
466
474
  * @template THelp Return type when help is shown.
package/dist/facade.js CHANGED
@@ -5,10 +5,13 @@ import { validateCommandNames, validateContextIds, validateMetaNameCollisions, v
5
5
  import { extractCommandNames, extractLiteralValues, extractOptionNames, formatUsage } from "./usage.js";
6
6
  import { formatDocPage } from "./doc.js";
7
7
  import { dispatchByMode } from "./mode-dispatch.js";
8
+ import { createInputTrace } from "./input-trace.js";
9
+ import { completeOrExtractPhase2Seed } from "./phase2-seed.js";
8
10
  import { multiple, optional, withDefault } from "./modifiers.js";
9
11
  import { string } from "./valueparser.js";
10
12
  import { argument, command, constant, flag, option } from "./primitives.js";
11
- import { getDocPage, parseAsync, parseSync, suggest, suggestAsync } from "./parser.js";
13
+ import { createParserContext, getDocPage, parseAsync, parseSync, suggest, suggestAsync } from "./parser.js";
14
+ import { createDependencyRuntimeContext } from "./dependency-runtime.js";
12
15
  import { group, longestMatch, object } from "./constructs.js";
13
16
 
14
17
  //#region src/facade.ts
@@ -89,6 +92,60 @@ function prepareParsedForContexts(parsed, deferred, deferredKeys) {
89
92
  function withPreparedParsedForContext(context, preparedParsed, run) {
90
93
  return run(finalizeParsedForContext(context, preparedParsed));
91
94
  }
95
+ function isBufferUnchanged(previous, current) {
96
+ return current.length > 0 && current.length === previous.length && current.every((item, i) => item === previous[i]);
97
+ }
98
+ function createPhase2SeedExec(parser, context) {
99
+ const exec = {
100
+ usage: parser.usage,
101
+ phase: "parse",
102
+ path: [],
103
+ trace: createInputTrace()
104
+ };
105
+ const runtime = createDependencyRuntimeContext();
106
+ return {
107
+ ...exec,
108
+ phase: "complete",
109
+ dependencyRuntime: runtime,
110
+ dependencyRegistry: runtime.registry,
111
+ trace: context.exec?.trace ?? context.trace ?? exec.trace
112
+ };
113
+ }
114
+ function createPhase2SeedContext(parser, args) {
115
+ const exec = {
116
+ usage: parser.usage,
117
+ phase: "parse",
118
+ path: [],
119
+ trace: createInputTrace()
120
+ };
121
+ return createParserContext({
122
+ buffer: args,
123
+ state: parser.initialState,
124
+ optionsTerminated: false
125
+ }, exec);
126
+ }
127
+ function extractPhase2SeedSync(parser, args) {
128
+ let context = createPhase2SeedContext(parser, args);
129
+ do {
130
+ const result = parser.parse(context);
131
+ if (!result.success) return completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
132
+ const previousBuffer = context.buffer;
133
+ context = result.next;
134
+ if (isBufferUnchanged(previousBuffer, context.buffer)) return completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
135
+ } while (context.buffer.length > 0);
136
+ return completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
137
+ }
138
+ async function extractPhase2SeedAsync(parser, args) {
139
+ let context = createPhase2SeedContext(parser, args);
140
+ do {
141
+ const result = await parser.parse(context);
142
+ if (!result.success) return await completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
143
+ const previousBuffer = context.buffer;
144
+ context = result.next;
145
+ if (isBufferUnchanged(previousBuffer, context.buffer)) return await completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
146
+ } while (context.buffer.length > 0);
147
+ return await completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
148
+ }
92
149
  /**
93
150
  * Creates help parsers based on the sub-config.
94
151
  */
@@ -1197,30 +1254,13 @@ async function runWithBody(parser, programName, contexts, args, options) {
1197
1254
  return Promise.resolve(runParser(augmentedParser, programName, args, options));
1198
1255
  }
1199
1256
  const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1200
- let firstPassResult;
1201
- let firstPassDeferred;
1202
- let firstPassDeferredKeys;
1203
- let firstPassFailed = false;
1204
- try {
1205
- if (parser.$mode === "async") firstPassResult = await parseAsync(augmentedParser1, args);
1206
- else firstPassResult = parseSync(augmentedParser1, args);
1207
- if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
1208
- const result = firstPassResult;
1209
- if (result.success) {
1210
- firstPassResult = result.value;
1211
- firstPassDeferred = result.deferred;
1212
- firstPassDeferredKeys = result.deferredKeys;
1213
- } else firstPassFailed = true;
1214
- }
1215
- } catch {
1216
- firstPassFailed = true;
1217
- }
1218
- if (firstPassFailed) {
1257
+ const firstPassSeed = await dispatchByMode(parser.$mode, () => extractPhase2SeedSync(augmentedParser1, args), () => extractPhase2SeedAsync(augmentedParser1, args));
1258
+ if (firstPassSeed == null) {
1219
1259
  const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1220
1260
  if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1221
1261
  return Promise.resolve(runParser(augmentedParser, programName, args, options));
1222
1262
  }
1223
- const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassResult, ctxOptions, firstPassDeferred, firstPassDeferredKeys);
1263
+ const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
1224
1264
  const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1225
1265
  const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1226
1266
  if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
@@ -1236,13 +1276,19 @@ async function runWithBody(parser, programName, contexts, args, options) {
1236
1276
  *
1237
1277
  * 1. *Phase 1*: Collect annotations from all contexts (static contexts return
1238
1278
  * their data, dynamic contexts may return empty).
1239
- * 2. *First parse*: Parse with Phase 1 annotations.
1279
+ * 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
1280
+ * successfully, its value becomes the phase-two input. If the parser
1281
+ * reaches a usable intermediate state but still does not complete
1282
+ * successfully, the runner extracts a best-effort seed from that state
1283
+ * instead.
1240
1284
  * 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with the first
1241
- * parse result.
1285
+ * pass value. Deferred or otherwise unresolved fields in `parsed` may be
1286
+ * `undefined`.
1242
1287
  * 4. *Second parse*: Parse again with merged annotations from both phases.
1243
1288
  *
1244
- * If all contexts are static (no dynamic contexts), the second parse is skipped
1245
- * for optimization.
1289
+ * If all contexts are static (no dynamic contexts), the second parse is
1290
+ * skipped for optimization. Phase 2 is also skipped when the first pass does
1291
+ * not yield any usable seed at all.
1246
1292
  *
1247
1293
  * @template TParser The parser type.
1248
1294
  * @template THelp Return type when help is shown.
@@ -1317,20 +1363,12 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
1317
1363
  return runParser(augmentedParser, programName, args, options);
1318
1364
  }
1319
1365
  const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1320
- let firstPassResult;
1321
- let firstPassDeferred;
1322
- let firstPassDeferredKeys;
1323
- try {
1324
- const result = parseSync(augmentedParser1, args);
1325
- if (result.success) {
1326
- firstPassResult = result.value;
1327
- firstPassDeferred = result.deferred;
1328
- firstPassDeferredKeys = result.deferredKeys;
1329
- } else return runParser(augmentedParser1, programName, args, options);
1330
- } catch {
1331
- return runParser(augmentedParser1, programName, args, options);
1366
+ const firstPassSeed = extractPhase2SeedSync(augmentedParser1, args);
1367
+ if (firstPassSeed == null) {
1368
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1369
+ return runParser(augmentedParser, programName, args, options);
1332
1370
  }
1333
- const { annotationsList: phase2AnnotationsList } = collectAnnotationsSync(contexts, firstPassResult, ctxOptions, firstPassDeferred, firstPassDeferredKeys);
1371
+ const { annotationsList: phase2AnnotationsList } = collectAnnotationsSync(contexts, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
1334
1372
  const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1335
1373
  const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1336
1374
  return runParser(augmentedParser2, programName, args, options);
@@ -1339,7 +1377,9 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
1339
1377
  * Runs a synchronous parser with multiple source contexts.
1340
1378
  *
1341
1379
  * This is the sync-only variant of {@link runWith}. All contexts must return
1342
- * annotations synchronously (not Promises).
1380
+ * annotations synchronously (not Promises). It uses the same two-phase
1381
+ * best-effort seed extraction as {@link runWith} when dynamic contexts are
1382
+ * present.
1343
1383
  *
1344
1384
  * @template TParser The sync parser type.
1345
1385
  * @template THelp Return type when help is shown.
@@ -1415,10 +1455,25 @@ function runWithAsync(parser, programName, contexts, options) {
1415
1455
  */
1416
1456
  function injectAnnotationsIntoParser(parser, annotations) {
1417
1457
  const newInitialState = injectAnnotations(parser.initialState, annotations);
1418
- return {
1419
- ...parser,
1420
- initialState: newInitialState
1458
+ const descriptors = { ...Object.getOwnPropertyDescriptors(parser) };
1459
+ const initialState = descriptors.initialState;
1460
+ descriptors.initialState = initialState == null ? {
1461
+ value: newInitialState,
1462
+ writable: true,
1463
+ enumerable: true,
1464
+ configurable: true
1465
+ } : "get" in initialState || "set" in initialState ? {
1466
+ value: newInitialState,
1467
+ writable: true,
1468
+ enumerable: initialState.enumerable ?? true,
1469
+ configurable: initialState.configurable ?? true
1470
+ } : {
1471
+ value: newInitialState,
1472
+ writable: initialState.writable ?? true,
1473
+ enumerable: initialState.enumerable ?? true,
1474
+ configurable: initialState.configurable ?? true
1421
1475
  };
1476
+ return Object.create(Object.getPrototypeOf(parser), descriptors);
1422
1477
  }
1423
1478
 
1424
1479
  //#endregion
@@ -2,6 +2,7 @@ const require_annotations = require('./annotations.cjs');
2
2
  const require_message = require('./message.cjs');
3
3
  const require_mode_dispatch = require('./mode-dispatch.cjs');
4
4
  const require_dependency_metadata = require('./dependency-metadata.cjs');
5
+ const require_phase2_seed = require('./phase2-seed.cjs');
5
6
  const require_parser = require('./parser.cjs');
6
7
 
7
8
  //#region src/modifiers.ts
@@ -79,6 +80,10 @@ function unwrapMultipleItemState(state) {
79
80
  function isPromiseLike(value) {
80
81
  return value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
81
82
  }
83
+ function extractOptionalLikePhase2Seed(parser, state, exec) {
84
+ if (!Array.isArray(state) && !(state != null && typeof state === "object")) return require_mode_dispatch.wrapForMode(parser.$mode, null);
85
+ return require_phase2_seed.completeOrExtractPhase2Seed(parser, normalizeOptionalLikeInnerState(state, parser.initialState, parser), exec);
86
+ }
82
87
  /**
83
88
  * Computes the inner state to pass through to the wrapped parser inside
84
89
  * {@link optional} / {@link withDefault}. When the outer state is an
@@ -251,6 +256,9 @@ function optional(parser) {
251
256
  state: innerState
252
257
  }] : []);
253
258
  },
259
+ [require_phase2_seed.extractPhase2SeedKey](state, exec) {
260
+ return extractOptionalLikePhase2Seed(parser, state, exec);
261
+ },
254
262
  parse(context) {
255
263
  return require_mode_dispatch.dispatchByMode(parser.$mode, () => parseOptionalStyleSync(context, syncParser), () => parseOptionalStyleAsync(context, parser));
256
264
  },
@@ -416,6 +424,9 @@ function withDefault(parser, defaultValue, options) {
416
424
  state: innerState
417
425
  }] : []);
418
426
  },
427
+ [require_phase2_seed.extractPhase2SeedKey](state, exec) {
428
+ return extractOptionalLikePhase2Seed(parser, state, exec);
429
+ },
419
430
  parse(context) {
420
431
  return require_mode_dispatch.dispatchByMode(parser.$mode, () => parseOptionalStyleSync(context, syncParser), () => parseOptionalStyleAsync(context, parser));
421
432
  },
@@ -656,6 +667,23 @@ function map(parser, transform) {
656
667
  ...parser,
657
668
  $valueType: [],
658
669
  complete,
670
+ [require_phase2_seed.extractPhase2SeedKey](state, exec) {
671
+ return require_mode_dispatch.mapModeValue(parser.$mode, require_phase2_seed.completeOrExtractPhase2Seed(parser, state, exec), (seed) => {
672
+ if (seed == null) return null;
673
+ if (seed.deferred) try {
674
+ return {
675
+ value: transform(seed.value),
676
+ deferred: true
677
+ };
678
+ } catch {
679
+ return {
680
+ value: void 0,
681
+ deferred: true
682
+ };
683
+ }
684
+ return { value: transform(seed.value) };
685
+ });
686
+ },
659
687
  getSuggestRuntimeNodes(state, path) {
660
688
  if (mappedParser.dependencyMetadata?.source != null) return [{
661
689
  path,
@@ -795,6 +823,26 @@ function multiple(parser, options = {}) {
795
823
  });
796
824
  }
797
825
  };
826
+ const extractPhase2SeedSyncWithUnwrappedFallback = (state, exec) => {
827
+ try {
828
+ const seed = require_phase2_seed.completeOrExtractPhase2Seed(syncParser, state, exec);
829
+ if (seed == null && require_annotations.isInjectedAnnotationWrapper(state)) return require_phase2_seed.completeOrExtractPhase2Seed(syncParser, unwrapInjectedWrapper(state), exec);
830
+ return seed;
831
+ } catch (error) {
832
+ if (!require_annotations.isInjectedAnnotationWrapper(state)) throw error;
833
+ return require_phase2_seed.completeOrExtractPhase2Seed(syncParser, unwrapInjectedWrapper(state), exec);
834
+ }
835
+ };
836
+ const extractPhase2SeedAsyncWithUnwrappedFallback = async (state, exec) => {
837
+ try {
838
+ const seed = await require_phase2_seed.completeOrExtractPhase2Seed(parser, state, exec);
839
+ if (seed == null && require_annotations.isInjectedAnnotationWrapper(state)) return await require_phase2_seed.completeOrExtractPhase2Seed(parser, unwrapInjectedWrapper(state), exec);
840
+ return seed;
841
+ } catch (error) {
842
+ if (!require_annotations.isInjectedAnnotationWrapper(state)) throw error;
843
+ return await require_phase2_seed.completeOrExtractPhase2Seed(parser, unwrapInjectedWrapper(state), exec);
844
+ }
845
+ };
798
846
  const getInnerSuggestRuntimeNodes = (state, path) => parser.getSuggestRuntimeNodes?.(state, path) ?? (parser.dependencyMetadata?.source != null ? [{
799
847
  path,
800
848
  parser,
@@ -1023,6 +1071,53 @@ function multiple(parser, options = {}) {
1023
1071
  return validateMultipleResult(values, deferredIndices, hasDeferred);
1024
1072
  });
1025
1073
  },
1074
+ [require_phase2_seed.extractPhase2SeedKey](state, exec) {
1075
+ return require_mode_dispatch.dispatchByMode(parser.$mode, () => {
1076
+ const values = [];
1077
+ const deferredIndices = /* @__PURE__ */ new Map();
1078
+ let hasDeferred = false;
1079
+ let hasAnySeed = false;
1080
+ for (let i = 0; i < state.length; i++) {
1081
+ const seed = extractPhase2SeedSyncWithUnwrappedFallback(state[i], withChildExecPath(exec, i));
1082
+ if (seed == null) continue;
1083
+ hasAnySeed = true;
1084
+ values[i] = seed.value;
1085
+ if (seed.deferred) if (seed.deferredKeys) deferredIndices.set(i, seed.deferredKeys);
1086
+ else if (seed.value == null || typeof seed.value !== "object") deferredIndices.set(i, null);
1087
+ else hasDeferred = true;
1088
+ }
1089
+ if (!hasAnySeed) return null;
1090
+ return {
1091
+ value: values,
1092
+ ...deferredIndices.size > 0 || hasDeferred ? {
1093
+ deferred: true,
1094
+ ...deferredIndices.size > 0 ? { deferredKeys: deferredIndices } : {}
1095
+ } : {}
1096
+ };
1097
+ }, async () => {
1098
+ const values = [];
1099
+ const deferredIndices = /* @__PURE__ */ new Map();
1100
+ let hasDeferred = false;
1101
+ let hasAnySeed = false;
1102
+ for (let i = 0; i < state.length; i++) {
1103
+ const seed = await extractPhase2SeedAsyncWithUnwrappedFallback(state[i], withChildExecPath(exec, i));
1104
+ if (seed == null) continue;
1105
+ hasAnySeed = true;
1106
+ values[i] = seed.value;
1107
+ if (seed.deferred) if (seed.deferredKeys) deferredIndices.set(i, seed.deferredKeys);
1108
+ else if (seed.value == null || typeof seed.value !== "object") deferredIndices.set(i, null);
1109
+ else hasDeferred = true;
1110
+ }
1111
+ if (!hasAnySeed) return null;
1112
+ return {
1113
+ value: values,
1114
+ ...deferredIndices.size > 0 || hasDeferred ? {
1115
+ deferred: true,
1116
+ ...deferredIndices.size > 0 ? { deferredKeys: deferredIndices } : {}
1117
+ } : {}
1118
+ };
1119
+ });
1120
+ },
1026
1121
  suggest(context, prefix) {
1027
1122
  const currentItemState = context.state.at(-1);
1028
1123
  const canExtendCurrent = currentItemState != null && !isTerminalMultipleItemState(currentItemState);
@@ -1365,6 +1460,13 @@ function nonEmpty(parser) {
1365
1460
  configurable: true,
1366
1461
  enumerable: false
1367
1462
  });
1463
+ Object.defineProperty(nonEmptyParser, require_phase2_seed.extractPhase2SeedKey, {
1464
+ value(state, exec) {
1465
+ return require_phase2_seed.extractPhase2Seed(parser, state, exec);
1466
+ },
1467
+ configurable: true,
1468
+ enumerable: false
1469
+ });
1368
1470
  if (typeof parser.normalizeValue === "function") Object.defineProperty(nonEmptyParser, "normalizeValue", {
1369
1471
  value: parser.normalizeValue.bind(parser),
1370
1472
  configurable: true,