@optique/core 1.0.0-dev.1608 → 1.0.0-dev.1616

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.
@@ -879,6 +879,31 @@ async function completeDependencySourceDefaultsAsync(context, parserPairs, regis
879
879
  * mappings from its child `object()` (or nested `merge()`) parsers.
880
880
  * @internal
881
881
  */
882
+ /**
883
+ * Removes entries for duplicate field names from a pre-completed results
884
+ * map. When a child construct's field-parser pairs contain the same field
885
+ * name more than once (e.g., inner merges with overlapping output keys),
886
+ * the flat map collapses branch-specific results. This function strips
887
+ * only the ambiguous entries, preserving cached results for unique fields.
888
+ *
889
+ * Returns `undefined` if the filtered map is empty (no cacheable fields).
890
+ *
891
+ * @see https://github.com/dahlia/optique/issues/762
892
+ * @internal
893
+ */
894
+ function filterDuplicateKeys(preCompleted, pairs) {
895
+ const counts = /* @__PURE__ */ new Map();
896
+ for (const [field] of pairs) counts.set(field, (counts.get(field) ?? 0) + 1);
897
+ let hasDuplicates = false;
898
+ for (const count of counts.values()) if (count > 1) {
899
+ hasDuplicates = true;
900
+ break;
901
+ }
902
+ if (!hasDuplicates) return preCompleted;
903
+ const filtered = /* @__PURE__ */ new Map();
904
+ for (const [field, value] of preCompleted) if ((counts.get(field) ?? 0) <= 1) filtered.set(field, value);
905
+ return filtered.size > 0 ? filtered : void 0;
906
+ }
882
907
  function collectChildFieldParsers(parsers) {
883
908
  const pairs = [];
884
909
  for (const parser of parsers) if (fieldParsersKey in parser) pairs.push(...parser[fieldParsersKey]);
@@ -901,7 +926,14 @@ function collectChildFieldParsers(parsers) {
901
926
  */
902
927
  function preCompleteAndRegisterDependencies(state, fieldParserPairs, registry, exec) {
903
928
  const preCompleted = /* @__PURE__ */ new Map();
929
+ const parentResults = exec?.preCompletedByParser;
904
930
  for (const [field, fieldParser] of fieldParserPairs) {
931
+ const cached = parentResults?.get(field);
932
+ if (cached !== void 0) {
933
+ preCompleted.set(field, cached);
934
+ registerCompletedDependency(cached, registry);
935
+ continue;
936
+ }
905
937
  const fieldState = state[field];
906
938
  if (Array.isArray(fieldState) && fieldState.length === 1 && require_dependency.isPendingDependencySourceState(fieldState[0])) {
907
939
  const completed = fieldParser.complete(fieldState, exec);
@@ -932,7 +964,14 @@ function preCompleteAndRegisterDependencies(state, fieldParserPairs, registry, e
932
964
  */
933
965
  async function preCompleteAndRegisterDependenciesAsync(state, fieldParserPairs, registry, exec) {
934
966
  const preCompleted = /* @__PURE__ */ new Map();
967
+ const parentResults = exec?.preCompletedByParser;
935
968
  for (const [field, fieldParser] of fieldParserPairs) {
969
+ const cached = parentResults?.get(field);
970
+ if (cached !== void 0) {
971
+ preCompleted.set(field, cached);
972
+ registerCompletedDependency(cached, registry);
973
+ continue;
974
+ }
936
975
  const fieldState = state[field];
937
976
  if (Array.isArray(fieldState) && fieldState.length === 1 && require_dependency.isPendingDependencySourceState(fieldState[0])) {
938
977
  const completed = await fieldParser.complete(fieldState, exec);
@@ -1304,7 +1343,12 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1304
1343
  ...exec,
1305
1344
  dependencyRuntime: runtime
1306
1345
  };
1307
- const preCompleted = preCompleteAndRegisterDependencies(state, parserPairs, runtime.registry, childExec);
1346
+ const typedParserPairs = parserPairs;
1347
+ const preCompleted = preCompleteAndRegisterDependencies(state, typedParserPairs, runtime.registry, childExec);
1348
+ const phase3Exec = {
1349
+ ...childExec,
1350
+ preCompletedByParser: void 0
1351
+ };
1308
1352
  const getFieldState = createFieldStateGetter(state);
1309
1353
  const annotatedState = {};
1310
1354
  for (const field of parserKeys) {
@@ -1324,7 +1368,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1324
1368
  if (preCompletedResult !== void 0) valueResult = unwrapCompleteResult(preCompletedResult);
1325
1369
  else {
1326
1370
  const fieldState = resolvedFieldStates[fieldKey];
1327
- valueResult = unwrapCompleteResult(fieldParser.complete(fieldState, childExec));
1371
+ valueResult = unwrapCompleteResult(fieldParser.complete(fieldState, phase3Exec));
1328
1372
  }
1329
1373
  if (valueResult.success) {
1330
1374
  result[fieldKey] = valueResult.value;
@@ -1351,7 +1395,12 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1351
1395
  ...exec,
1352
1396
  dependencyRuntime: runtime
1353
1397
  };
1354
- const preCompleted = await preCompleteAndRegisterDependenciesAsync(state, parserPairs, runtime.registry, childExec);
1398
+ const asyncParserPairs = parserPairs;
1399
+ const preCompleted = await preCompleteAndRegisterDependenciesAsync(state, asyncParserPairs, runtime.registry, childExec);
1400
+ const phase3Exec = {
1401
+ ...childExec,
1402
+ preCompletedByParser: void 0
1403
+ };
1355
1404
  const getFieldState = createFieldStateGetter(state);
1356
1405
  const annotatedState = {};
1357
1406
  for (const field of parserKeys) {
@@ -1371,7 +1420,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1371
1420
  if (preCompletedResult !== void 0) valueResult = unwrapCompleteResult(preCompletedResult);
1372
1421
  else {
1373
1422
  const fieldState = resolvedFieldStates[fieldKey];
1374
- valueResult = unwrapCompleteResult(await fieldParser.complete(fieldState, childExec));
1423
+ valueResult = unwrapCompleteResult(await fieldParser.complete(fieldState, phase3Exec));
1375
1424
  }
1376
1425
  if (valueResult.success) {
1377
1426
  result[fieldKey] = valueResult.value;
@@ -1644,6 +1693,10 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1644
1693
  const tuplePairs = syncParsers.map((p, i) => [String(i), p]);
1645
1694
  const tupleState = Object.fromEntries(stateArray.map((s, i) => [String(i), s]));
1646
1695
  const preCompleted = preCompleteAndRegisterDependencies(tupleState, tuplePairs, runtime.registry, childExec);
1696
+ const phase3Exec = {
1697
+ ...childExec,
1698
+ preCompletedByParser: void 0
1699
+ };
1647
1700
  const resolvedArray = require_dependency_runtime.resolveStateWithRuntime(stateArray, runtime);
1648
1701
  const result = [];
1649
1702
  const deferredKeys = /* @__PURE__ */ new Map();
@@ -1655,7 +1708,7 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1655
1708
  if (preCompletedResult !== void 0) valueResult = unwrapCompleteResult(preCompletedResult);
1656
1709
  else {
1657
1710
  const elementState = prepareStateForCompletion(resolvedArray[i], elementParser);
1658
- valueResult = unwrapCompleteResult(elementParser.complete(elementState, childExec));
1711
+ valueResult = unwrapCompleteResult(elementParser.complete(elementState, phase3Exec));
1659
1712
  }
1660
1713
  if (valueResult.success) {
1661
1714
  result[i] = valueResult.value;
@@ -1686,6 +1739,10 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1686
1739
  const tuplePairs = parsers.map((p, i) => [String(i), p]);
1687
1740
  const tupleState = Object.fromEntries(stateArray.map((s, i) => [String(i), s]));
1688
1741
  const preCompleted = await preCompleteAndRegisterDependenciesAsync(tupleState, tuplePairs, runtime.registry, childExec);
1742
+ const phase3Exec = {
1743
+ ...childExec,
1744
+ preCompletedByParser: void 0
1745
+ };
1689
1746
  const resolvedArray = await require_dependency_runtime.resolveStateWithRuntimeAsync(stateArray, runtime);
1690
1747
  const result = [];
1691
1748
  const deferredKeys = /* @__PURE__ */ new Map();
@@ -1697,7 +1754,7 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1697
1754
  if (preCompletedResult !== void 0) valueResult = unwrapCompleteResult(preCompletedResult);
1698
1755
  else {
1699
1756
  const elementState = prepareStateForCompletion(resolvedArray[i], elementParser);
1700
- valueResult = unwrapCompleteResult(await elementParser.complete(elementState, childExec));
1757
+ valueResult = unwrapCompleteResult(await elementParser.complete(elementState, phase3Exec));
1701
1758
  }
1702
1759
  if (valueResult.success) {
1703
1760
  result[i] = valueResult.value;
@@ -1963,8 +2020,14 @@ function merge(...args) {
1963
2020
  ...exec,
1964
2021
  dependencyRuntime: runtime
1965
2022
  };
1966
- const childFieldPairs = collectChildFieldParsers(syncParsers);
1967
- preCompleteAndRegisterDependencies(state, childFieldPairs, runtime.registry, childExec);
2023
+ const perChildCache = syncParsers.map((parser) => {
2024
+ if (fieldParsersKey in parser) {
2025
+ const pairs = parser[fieldParsersKey];
2026
+ const preCompleted = preCompleteAndRegisterDependencies(state, pairs, runtime.registry, childExec);
2027
+ return filterDuplicateKeys(preCompleted, pairs);
2028
+ }
2029
+ return void 0;
2030
+ });
1968
2031
  const resolvedState = require_dependency_runtime.resolveStateWithRuntime(state, runtime);
1969
2032
  const object$1 = {};
1970
2033
  const deferredKeys = /* @__PURE__ */ new Map();
@@ -1972,7 +2035,11 @@ function merge(...args) {
1972
2035
  for (let i = 0; i < syncParsers.length; i++) {
1973
2036
  const parser = syncParsers[i];
1974
2037
  const parserState = extractCompleteState(parser, resolvedState, i);
1975
- const result = unwrapCompleteResult(parser.complete(parserState, childExec));
2038
+ const cache = perChildCache[i];
2039
+ const result = unwrapCompleteResult(parser.complete(parserState, {
2040
+ ...childExec,
2041
+ preCompletedByParser: cache
2042
+ }));
1976
2043
  if (!result.success) return result;
1977
2044
  const resultValue = result.value;
1978
2045
  for (const field in resultValue) {
@@ -1998,8 +2065,12 @@ function merge(...args) {
1998
2065
  ...exec,
1999
2066
  dependencyRuntime: runtime
2000
2067
  };
2001
- const childFieldPairs = collectChildFieldParsers(parsers);
2002
- await preCompleteAndRegisterDependenciesAsync(state, childFieldPairs, runtime.registry, childExec);
2068
+ const perChildCache = [];
2069
+ for (const parser of parsers) if (fieldParsersKey in parser) {
2070
+ const pairs = parser[fieldParsersKey];
2071
+ const preCompleted = await preCompleteAndRegisterDependenciesAsync(state, pairs, runtime.registry, childExec);
2072
+ perChildCache.push(filterDuplicateKeys(preCompleted, pairs));
2073
+ } else perChildCache.push(void 0);
2003
2074
  const resolvedState = await require_dependency_runtime.resolveStateWithRuntimeAsync(state, runtime);
2004
2075
  const object$1 = {};
2005
2076
  const deferredKeys = /* @__PURE__ */ new Map();
@@ -2007,7 +2078,11 @@ function merge(...args) {
2007
2078
  for (let i = 0; i < parsers.length; i++) {
2008
2079
  const parser = parsers[i];
2009
2080
  const parserState = extractCompleteState(parser, resolvedState, i);
2010
- const result = unwrapCompleteResult(await parser.complete(parserState, childExec));
2081
+ const asyncCache = perChildCache[i];
2082
+ const result = unwrapCompleteResult(await parser.complete(parserState, {
2083
+ ...childExec,
2084
+ preCompletedByParser: asyncCache
2085
+ }));
2011
2086
  if (!result.success) return result;
2012
2087
  const resultValue = result.value;
2013
2088
  for (const field in resultValue) {
@@ -2400,7 +2475,8 @@ function concat(...parsers) {
2400
2475
  const runtime = exec?.dependencyRuntime ?? require_dependency_runtime.createDependencyRuntimeContext(exec?.dependencyRegistry);
2401
2476
  const childExec = {
2402
2477
  ...exec,
2403
- dependencyRuntime: runtime
2478
+ dependencyRuntime: runtime,
2479
+ preCompletedByParser: void 0
2404
2480
  };
2405
2481
  const resolvedArray = require_dependency_runtime.resolveStateWithRuntime(stateArray, runtime);
2406
2482
  const results = [];
@@ -2442,7 +2518,8 @@ function concat(...parsers) {
2442
2518
  const runtime = exec?.dependencyRuntime ?? require_dependency_runtime.createDependencyRuntimeContext(exec?.dependencyRegistry);
2443
2519
  const childExec = {
2444
2520
  ...exec,
2445
- dependencyRuntime: runtime
2521
+ dependencyRuntime: runtime,
2522
+ preCompletedByParser: void 0
2446
2523
  };
2447
2524
  const resolvedArray = await require_dependency_runtime.resolveStateWithRuntimeAsync(stateArray, runtime);
2448
2525
  const results = [];
@@ -879,6 +879,31 @@ async function completeDependencySourceDefaultsAsync(context, parserPairs, regis
879
879
  * mappings from its child `object()` (or nested `merge()`) parsers.
880
880
  * @internal
881
881
  */
882
+ /**
883
+ * Removes entries for duplicate field names from a pre-completed results
884
+ * map. When a child construct's field-parser pairs contain the same field
885
+ * name more than once (e.g., inner merges with overlapping output keys),
886
+ * the flat map collapses branch-specific results. This function strips
887
+ * only the ambiguous entries, preserving cached results for unique fields.
888
+ *
889
+ * Returns `undefined` if the filtered map is empty (no cacheable fields).
890
+ *
891
+ * @see https://github.com/dahlia/optique/issues/762
892
+ * @internal
893
+ */
894
+ function filterDuplicateKeys(preCompleted, pairs) {
895
+ const counts = /* @__PURE__ */ new Map();
896
+ for (const [field] of pairs) counts.set(field, (counts.get(field) ?? 0) + 1);
897
+ let hasDuplicates = false;
898
+ for (const count of counts.values()) if (count > 1) {
899
+ hasDuplicates = true;
900
+ break;
901
+ }
902
+ if (!hasDuplicates) return preCompleted;
903
+ const filtered = /* @__PURE__ */ new Map();
904
+ for (const [field, value] of preCompleted) if ((counts.get(field) ?? 0) <= 1) filtered.set(field, value);
905
+ return filtered.size > 0 ? filtered : void 0;
906
+ }
882
907
  function collectChildFieldParsers(parsers) {
883
908
  const pairs = [];
884
909
  for (const parser of parsers) if (fieldParsersKey in parser) pairs.push(...parser[fieldParsersKey]);
@@ -901,7 +926,14 @@ function collectChildFieldParsers(parsers) {
901
926
  */
902
927
  function preCompleteAndRegisterDependencies(state, fieldParserPairs, registry, exec) {
903
928
  const preCompleted = /* @__PURE__ */ new Map();
929
+ const parentResults = exec?.preCompletedByParser;
904
930
  for (const [field, fieldParser] of fieldParserPairs) {
931
+ const cached = parentResults?.get(field);
932
+ if (cached !== void 0) {
933
+ preCompleted.set(field, cached);
934
+ registerCompletedDependency(cached, registry);
935
+ continue;
936
+ }
905
937
  const fieldState = state[field];
906
938
  if (Array.isArray(fieldState) && fieldState.length === 1 && isPendingDependencySourceState(fieldState[0])) {
907
939
  const completed = fieldParser.complete(fieldState, exec);
@@ -932,7 +964,14 @@ function preCompleteAndRegisterDependencies(state, fieldParserPairs, registry, e
932
964
  */
933
965
  async function preCompleteAndRegisterDependenciesAsync(state, fieldParserPairs, registry, exec) {
934
966
  const preCompleted = /* @__PURE__ */ new Map();
967
+ const parentResults = exec?.preCompletedByParser;
935
968
  for (const [field, fieldParser] of fieldParserPairs) {
969
+ const cached = parentResults?.get(field);
970
+ if (cached !== void 0) {
971
+ preCompleted.set(field, cached);
972
+ registerCompletedDependency(cached, registry);
973
+ continue;
974
+ }
936
975
  const fieldState = state[field];
937
976
  if (Array.isArray(fieldState) && fieldState.length === 1 && isPendingDependencySourceState(fieldState[0])) {
938
977
  const completed = await fieldParser.complete(fieldState, exec);
@@ -1304,7 +1343,12 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1304
1343
  ...exec,
1305
1344
  dependencyRuntime: runtime
1306
1345
  };
1307
- const preCompleted = preCompleteAndRegisterDependencies(state, parserPairs, runtime.registry, childExec);
1346
+ const typedParserPairs = parserPairs;
1347
+ const preCompleted = preCompleteAndRegisterDependencies(state, typedParserPairs, runtime.registry, childExec);
1348
+ const phase3Exec = {
1349
+ ...childExec,
1350
+ preCompletedByParser: void 0
1351
+ };
1308
1352
  const getFieldState = createFieldStateGetter(state);
1309
1353
  const annotatedState = {};
1310
1354
  for (const field of parserKeys) {
@@ -1324,7 +1368,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1324
1368
  if (preCompletedResult !== void 0) valueResult = unwrapCompleteResult(preCompletedResult);
1325
1369
  else {
1326
1370
  const fieldState = resolvedFieldStates[fieldKey];
1327
- valueResult = unwrapCompleteResult(fieldParser.complete(fieldState, childExec));
1371
+ valueResult = unwrapCompleteResult(fieldParser.complete(fieldState, phase3Exec));
1328
1372
  }
1329
1373
  if (valueResult.success) {
1330
1374
  result[fieldKey] = valueResult.value;
@@ -1351,7 +1395,12 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1351
1395
  ...exec,
1352
1396
  dependencyRuntime: runtime
1353
1397
  };
1354
- const preCompleted = await preCompleteAndRegisterDependenciesAsync(state, parserPairs, runtime.registry, childExec);
1398
+ const asyncParserPairs = parserPairs;
1399
+ const preCompleted = await preCompleteAndRegisterDependenciesAsync(state, asyncParserPairs, runtime.registry, childExec);
1400
+ const phase3Exec = {
1401
+ ...childExec,
1402
+ preCompletedByParser: void 0
1403
+ };
1355
1404
  const getFieldState = createFieldStateGetter(state);
1356
1405
  const annotatedState = {};
1357
1406
  for (const field of parserKeys) {
@@ -1371,7 +1420,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1371
1420
  if (preCompletedResult !== void 0) valueResult = unwrapCompleteResult(preCompletedResult);
1372
1421
  else {
1373
1422
  const fieldState = resolvedFieldStates[fieldKey];
1374
- valueResult = unwrapCompleteResult(await fieldParser.complete(fieldState, childExec));
1423
+ valueResult = unwrapCompleteResult(await fieldParser.complete(fieldState, phase3Exec));
1375
1424
  }
1376
1425
  if (valueResult.success) {
1377
1426
  result[fieldKey] = valueResult.value;
@@ -1644,6 +1693,10 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1644
1693
  const tuplePairs = syncParsers.map((p, i) => [String(i), p]);
1645
1694
  const tupleState = Object.fromEntries(stateArray.map((s, i) => [String(i), s]));
1646
1695
  const preCompleted = preCompleteAndRegisterDependencies(tupleState, tuplePairs, runtime.registry, childExec);
1696
+ const phase3Exec = {
1697
+ ...childExec,
1698
+ preCompletedByParser: void 0
1699
+ };
1647
1700
  const resolvedArray = resolveStateWithRuntime(stateArray, runtime);
1648
1701
  const result = [];
1649
1702
  const deferredKeys = /* @__PURE__ */ new Map();
@@ -1655,7 +1708,7 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1655
1708
  if (preCompletedResult !== void 0) valueResult = unwrapCompleteResult(preCompletedResult);
1656
1709
  else {
1657
1710
  const elementState = prepareStateForCompletion(resolvedArray[i], elementParser);
1658
- valueResult = unwrapCompleteResult(elementParser.complete(elementState, childExec));
1711
+ valueResult = unwrapCompleteResult(elementParser.complete(elementState, phase3Exec));
1659
1712
  }
1660
1713
  if (valueResult.success) {
1661
1714
  result[i] = valueResult.value;
@@ -1686,6 +1739,10 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1686
1739
  const tuplePairs = parsers.map((p, i) => [String(i), p]);
1687
1740
  const tupleState = Object.fromEntries(stateArray.map((s, i) => [String(i), s]));
1688
1741
  const preCompleted = await preCompleteAndRegisterDependenciesAsync(tupleState, tuplePairs, runtime.registry, childExec);
1742
+ const phase3Exec = {
1743
+ ...childExec,
1744
+ preCompletedByParser: void 0
1745
+ };
1689
1746
  const resolvedArray = await resolveStateWithRuntimeAsync(stateArray, runtime);
1690
1747
  const result = [];
1691
1748
  const deferredKeys = /* @__PURE__ */ new Map();
@@ -1697,7 +1754,7 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1697
1754
  if (preCompletedResult !== void 0) valueResult = unwrapCompleteResult(preCompletedResult);
1698
1755
  else {
1699
1756
  const elementState = prepareStateForCompletion(resolvedArray[i], elementParser);
1700
- valueResult = unwrapCompleteResult(await elementParser.complete(elementState, childExec));
1757
+ valueResult = unwrapCompleteResult(await elementParser.complete(elementState, phase3Exec));
1701
1758
  }
1702
1759
  if (valueResult.success) {
1703
1760
  result[i] = valueResult.value;
@@ -1963,8 +2020,14 @@ function merge(...args) {
1963
2020
  ...exec,
1964
2021
  dependencyRuntime: runtime
1965
2022
  };
1966
- const childFieldPairs = collectChildFieldParsers(syncParsers);
1967
- preCompleteAndRegisterDependencies(state, childFieldPairs, runtime.registry, childExec);
2023
+ const perChildCache = syncParsers.map((parser) => {
2024
+ if (fieldParsersKey in parser) {
2025
+ const pairs = parser[fieldParsersKey];
2026
+ const preCompleted = preCompleteAndRegisterDependencies(state, pairs, runtime.registry, childExec);
2027
+ return filterDuplicateKeys(preCompleted, pairs);
2028
+ }
2029
+ return void 0;
2030
+ });
1968
2031
  const resolvedState = resolveStateWithRuntime(state, runtime);
1969
2032
  const object$1 = {};
1970
2033
  const deferredKeys = /* @__PURE__ */ new Map();
@@ -1972,7 +2035,11 @@ function merge(...args) {
1972
2035
  for (let i = 0; i < syncParsers.length; i++) {
1973
2036
  const parser = syncParsers[i];
1974
2037
  const parserState = extractCompleteState(parser, resolvedState, i);
1975
- const result = unwrapCompleteResult(parser.complete(parserState, childExec));
2038
+ const cache = perChildCache[i];
2039
+ const result = unwrapCompleteResult(parser.complete(parserState, {
2040
+ ...childExec,
2041
+ preCompletedByParser: cache
2042
+ }));
1976
2043
  if (!result.success) return result;
1977
2044
  const resultValue = result.value;
1978
2045
  for (const field in resultValue) {
@@ -1998,8 +2065,12 @@ function merge(...args) {
1998
2065
  ...exec,
1999
2066
  dependencyRuntime: runtime
2000
2067
  };
2001
- const childFieldPairs = collectChildFieldParsers(parsers);
2002
- await preCompleteAndRegisterDependenciesAsync(state, childFieldPairs, runtime.registry, childExec);
2068
+ const perChildCache = [];
2069
+ for (const parser of parsers) if (fieldParsersKey in parser) {
2070
+ const pairs = parser[fieldParsersKey];
2071
+ const preCompleted = await preCompleteAndRegisterDependenciesAsync(state, pairs, runtime.registry, childExec);
2072
+ perChildCache.push(filterDuplicateKeys(preCompleted, pairs));
2073
+ } else perChildCache.push(void 0);
2003
2074
  const resolvedState = await resolveStateWithRuntimeAsync(state, runtime);
2004
2075
  const object$1 = {};
2005
2076
  const deferredKeys = /* @__PURE__ */ new Map();
@@ -2007,7 +2078,11 @@ function merge(...args) {
2007
2078
  for (let i = 0; i < parsers.length; i++) {
2008
2079
  const parser = parsers[i];
2009
2080
  const parserState = extractCompleteState(parser, resolvedState, i);
2010
- const result = unwrapCompleteResult(await parser.complete(parserState, childExec));
2081
+ const asyncCache = perChildCache[i];
2082
+ const result = unwrapCompleteResult(await parser.complete(parserState, {
2083
+ ...childExec,
2084
+ preCompletedByParser: asyncCache
2085
+ }));
2011
2086
  if (!result.success) return result;
2012
2087
  const resultValue = result.value;
2013
2088
  for (const field in resultValue) {
@@ -2400,7 +2475,8 @@ function concat(...parsers) {
2400
2475
  const runtime = exec?.dependencyRuntime ?? createDependencyRuntimeContext(exec?.dependencyRegistry);
2401
2476
  const childExec = {
2402
2477
  ...exec,
2403
- dependencyRuntime: runtime
2478
+ dependencyRuntime: runtime,
2479
+ preCompletedByParser: void 0
2404
2480
  };
2405
2481
  const resolvedArray = resolveStateWithRuntime(stateArray, runtime);
2406
2482
  const results = [];
@@ -2442,7 +2518,8 @@ function concat(...parsers) {
2442
2518
  const runtime = exec?.dependencyRuntime ?? createDependencyRuntimeContext(exec?.dependencyRegistry);
2443
2519
  const childExec = {
2444
2520
  ...exec,
2445
- dependencyRuntime: runtime
2521
+ dependencyRuntime: runtime,
2522
+ preCompletedByParser: void 0
2446
2523
  };
2447
2524
  const resolvedArray = await resolveStateWithRuntimeAsync(stateArray, runtime);
2448
2525
  const results = [];
@@ -168,6 +168,7 @@ function resolveSingleDeferred(deferred, runtime) {
168
168
  defaultValues: deferred.defaultValues
169
169
  });
170
170
  if (resolution.kind !== "resolved") return deferred.preliminaryResult;
171
+ if (resolution.usedDefaults.every((d) => d)) return deferred.preliminaryResult;
171
172
  const depValue = isMultiDep ? resolution.values : resolution.values[0];
172
173
  const result = deferred.parser[require_dependency.parseWithDependency](deferred.rawInput, depValue);
173
174
  if (result instanceof Promise) return deferred.preliminaryResult;
@@ -266,6 +267,7 @@ async function resolveDeferredInStateAsync(state, runtime, visited = /* @__PURE_
266
267
  defaultValues: deferred.defaultValues
267
268
  });
268
269
  if (resolution.kind !== "resolved") return deferred.preliminaryResult;
270
+ if (resolution.usedDefaults.every((d) => d)) return deferred.preliminaryResult;
269
271
  const depValue = isMultiDep ? resolution.values : resolution.values[0];
270
272
  return Promise.resolve(deferred.parser[require_dependency.parseWithDependency](deferred.rawInput, depValue));
271
273
  }
@@ -168,6 +168,7 @@ function resolveSingleDeferred(deferred, runtime) {
168
168
  defaultValues: deferred.defaultValues
169
169
  });
170
170
  if (resolution.kind !== "resolved") return deferred.preliminaryResult;
171
+ if (resolution.usedDefaults.every((d) => d)) return deferred.preliminaryResult;
171
172
  const depValue = isMultiDep ? resolution.values : resolution.values[0];
172
173
  const result = deferred.parser[parseWithDependency](deferred.rawInput, depValue);
173
174
  if (result instanceof Promise) return deferred.preliminaryResult;
@@ -266,6 +267,7 @@ async function resolveDeferredInStateAsync(state, runtime, visited = /* @__PURE_
266
267
  defaultValues: deferred.defaultValues
267
268
  });
268
269
  if (resolution.kind !== "resolved") return deferred.preliminaryResult;
270
+ if (resolution.usedDefaults.every((d) => d)) return deferred.preliminaryResult;
269
271
  const depValue = isMultiDep ? resolution.values : resolution.values[0];
270
272
  return Promise.resolve(deferred.parser[parseWithDependency](deferred.rawInput, depValue));
271
273
  }
package/dist/parser.cjs CHANGED
@@ -1,5 +1,7 @@
1
1
  const require_annotations = require('./annotations.cjs');
2
2
  const require_message = require('./message.cjs');
3
+ const require_dependency = require('./dependency.cjs');
4
+ const require_dependency_runtime = require('./dependency-runtime.cjs');
3
5
  const require_mode_dispatch = require('./mode-dispatch.cjs');
4
6
  const require_usage = require('./usage.cjs');
5
7
  const require_doc = require('./doc.cjs');
@@ -83,11 +85,15 @@ function parseSync(parser, args, options) {
83
85
  error: require_message.message`Unexpected option or argument: ${context.buffer[0]}.`
84
86
  };
85
87
  } while (context.buffer.length > 0);
88
+ const runtime = require_dependency_runtime.createDependencyRuntimeContext();
89
+ const resolvedState = require_dependency.isDeferredParseState(context.state) ? require_dependency_runtime.resolveStateWithRuntime(context.state, runtime) : context.state;
86
90
  const completeExec = {
87
91
  ...exec,
88
- phase: "complete"
92
+ phase: "complete",
93
+ dependencyRuntime: runtime,
94
+ dependencyRegistry: runtime.registry
89
95
  };
90
- const endResult = parser.complete(context.state, completeExec);
96
+ const endResult = parser.complete(resolvedState, completeExec);
91
97
  return endResult.success ? {
92
98
  success: true,
93
99
  value: shouldUnwrapAnnotatedValue ? require_annotations.unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
@@ -150,11 +156,15 @@ async function parseAsync(parser, args, options) {
150
156
  error: require_message.message`Unexpected option or argument: ${context.buffer[0]}.`
151
157
  };
152
158
  } while (context.buffer.length > 0);
159
+ const runtime = require_dependency_runtime.createDependencyRuntimeContext();
160
+ const resolvedState = require_dependency.isDeferredParseState(context.state) ? await require_dependency_runtime.resolveStateWithRuntimeAsync(context.state, runtime) : context.state;
153
161
  const completeExec = {
154
162
  ...exec,
155
- phase: "complete"
163
+ phase: "complete",
164
+ dependencyRuntime: runtime,
165
+ dependencyRegistry: runtime.registry
156
166
  };
157
- const endResult = await parser.complete(context.state, completeExec);
167
+ const endResult = await parser.complete(resolvedState, completeExec);
158
168
  return endResult.success ? {
159
169
  success: true,
160
170
  value: shouldUnwrapAnnotatedValue ? require_annotations.unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
@@ -241,12 +251,31 @@ function suggestSync(parser, args, options) {
241
251
  });
242
252
  while (context.buffer.length > 0) {
243
253
  const result = parser.parse(context);
244
- if (!result.success) return Array.from(parser.suggest(context, prefix));
254
+ if (!result.success) return Array.from(parser.suggest(withSuggestRuntime(context), prefix));
245
255
  const previousBuffer = context.buffer;
246
256
  context = result.next;
247
257
  if (isBufferUnchanged(previousBuffer, context.buffer)) return [];
248
258
  }
249
- return Array.from(parser.suggest(context, prefix));
259
+ return Array.from(parser.suggest(withSuggestRuntime(context), prefix));
260
+ }
261
+ /**
262
+ * Creates a dependency runtime from the current parser state and returns
263
+ * a context with the populated registry. Used by top-level suggest
264
+ * functions to mirror the construct-owned model where suggest() receives
265
+ * a context with a dependency registry.
266
+ * @internal
267
+ */
268
+ function withSuggestRuntime(context) {
269
+ const runtime = require_dependency_runtime.createDependencyRuntimeContext();
270
+ require_dependency_runtime.collectSourcesFromState(context.state, runtime);
271
+ return {
272
+ ...context,
273
+ dependencyRegistry: runtime.registry,
274
+ exec: context.exec ? {
275
+ ...context.exec,
276
+ dependencyRegistry: runtime.registry
277
+ } : void 0
278
+ };
250
279
  }
251
280
  /**
252
281
  * Generates command-line suggestions based on current parsing state.
@@ -284,16 +313,18 @@ async function suggestAsync(parser, args, options) {
284
313
  while (context.buffer.length > 0) {
285
314
  const result = await parser.parse(context);
286
315
  if (!result.success) {
316
+ const ctx$1 = withSuggestRuntime(context);
287
317
  const suggestions$1 = [];
288
- for await (const suggestion of parser.suggest(context, prefix)) suggestions$1.push(suggestion);
318
+ for await (const suggestion of parser.suggest(ctx$1, prefix)) suggestions$1.push(suggestion);
289
319
  return suggestions$1;
290
320
  }
291
321
  const previousBuffer = context.buffer;
292
322
  context = result.next;
293
323
  if (isBufferUnchanged(previousBuffer, context.buffer)) return [];
294
324
  }
325
+ const ctx = withSuggestRuntime(context);
295
326
  const suggestions = [];
296
- for await (const suggestion of parser.suggest(context, prefix)) suggestions.push(suggestion);
327
+ for await (const suggestion of parser.suggest(ctx, prefix)) suggestions.push(suggestion);
297
328
  return suggestions;
298
329
  }
299
330
  /**
package/dist/parser.d.cts CHANGED
@@ -325,6 +325,25 @@ interface ExecutionContext {
325
325
  * @since 1.0.0
326
326
  */
327
327
  readonly dependencyRuntime?: DependencyRuntimeContext;
328
+ /**
329
+ * Immutable map of pre-completed results from the parent construct's
330
+ * Phase 1, keyed by field name. Each construct passes its own
331
+ * `preCompleteAndRegisterDependencies` results directly to children
332
+ * in Phase 3. Children read it in their own Phase 1 to avoid
333
+ * re-evaluating non-idempotent default thunks, but never write to
334
+ * it — this prevents sibling completions from leaking into each
335
+ * other.
336
+ *
337
+ * Field-name keying naturally handles parser reuse across different
338
+ * fields (e.g., `merge(object({a: shared}), object({b: shared}))`)
339
+ * because each field maps to its own result regardless of whether
340
+ * the underlying parser instance is the same.
341
+ *
342
+ * @see https://github.com/dahlia/optique/issues/762
343
+ * @internal
344
+ * @since 1.0.0
345
+ */
346
+ readonly preCompletedByParser?: ReadonlyMap<string | symbol, unknown>;
328
347
  }
329
348
  /**
330
349
  * The context of the parser, which includes the input buffer and the state.
package/dist/parser.d.ts CHANGED
@@ -325,6 +325,25 @@ interface ExecutionContext {
325
325
  * @since 1.0.0
326
326
  */
327
327
  readonly dependencyRuntime?: DependencyRuntimeContext;
328
+ /**
329
+ * Immutable map of pre-completed results from the parent construct's
330
+ * Phase 1, keyed by field name. Each construct passes its own
331
+ * `preCompleteAndRegisterDependencies` results directly to children
332
+ * in Phase 3. Children read it in their own Phase 1 to avoid
333
+ * re-evaluating non-idempotent default thunks, but never write to
334
+ * it — this prevents sibling completions from leaking into each
335
+ * other.
336
+ *
337
+ * Field-name keying naturally handles parser reuse across different
338
+ * fields (e.g., `merge(object({a: shared}), object({b: shared}))`)
339
+ * because each field maps to its own result regardless of whether
340
+ * the underlying parser instance is the same.
341
+ *
342
+ * @see https://github.com/dahlia/optique/issues/762
343
+ * @internal
344
+ * @since 1.0.0
345
+ */
346
+ readonly preCompletedByParser?: ReadonlyMap<string | symbol, unknown>;
328
347
  }
329
348
  /**
330
349
  * The context of the parser, which includes the input buffer and the state.
package/dist/parser.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { injectAnnotations, isInjectedAnnotationWrapper, unwrapInjectedAnnotationWrapper } from "./annotations.js";
2
2
  import { cloneMessage, message } from "./message.js";
3
+ import { isDeferredParseState } from "./dependency.js";
4
+ import { collectSourcesFromState, createDependencyRuntimeContext, resolveStateWithRuntime, resolveStateWithRuntimeAsync } from "./dependency-runtime.js";
3
5
  import { dispatchByMode } from "./mode-dispatch.js";
4
6
  import { cloneUsage, normalizeUsage } from "./usage.js";
5
7
  import { cloneDocEntry, isDocEntryHidden } from "./doc.js";
@@ -83,11 +85,15 @@ function parseSync(parser, args, options) {
83
85
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
84
86
  };
85
87
  } while (context.buffer.length > 0);
88
+ const runtime = createDependencyRuntimeContext();
89
+ const resolvedState = isDeferredParseState(context.state) ? resolveStateWithRuntime(context.state, runtime) : context.state;
86
90
  const completeExec = {
87
91
  ...exec,
88
- phase: "complete"
92
+ phase: "complete",
93
+ dependencyRuntime: runtime,
94
+ dependencyRegistry: runtime.registry
89
95
  };
90
- const endResult = parser.complete(context.state, completeExec);
96
+ const endResult = parser.complete(resolvedState, completeExec);
91
97
  return endResult.success ? {
92
98
  success: true,
93
99
  value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
@@ -150,11 +156,15 @@ async function parseAsync(parser, args, options) {
150
156
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
151
157
  };
152
158
  } while (context.buffer.length > 0);
159
+ const runtime = createDependencyRuntimeContext();
160
+ const resolvedState = isDeferredParseState(context.state) ? await resolveStateWithRuntimeAsync(context.state, runtime) : context.state;
153
161
  const completeExec = {
154
162
  ...exec,
155
- phase: "complete"
163
+ phase: "complete",
164
+ dependencyRuntime: runtime,
165
+ dependencyRegistry: runtime.registry
156
166
  };
157
- const endResult = await parser.complete(context.state, completeExec);
167
+ const endResult = await parser.complete(resolvedState, completeExec);
158
168
  return endResult.success ? {
159
169
  success: true,
160
170
  value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
@@ -241,12 +251,31 @@ function suggestSync(parser, args, options) {
241
251
  });
242
252
  while (context.buffer.length > 0) {
243
253
  const result = parser.parse(context);
244
- if (!result.success) return Array.from(parser.suggest(context, prefix));
254
+ if (!result.success) return Array.from(parser.suggest(withSuggestRuntime(context), prefix));
245
255
  const previousBuffer = context.buffer;
246
256
  context = result.next;
247
257
  if (isBufferUnchanged(previousBuffer, context.buffer)) return [];
248
258
  }
249
- return Array.from(parser.suggest(context, prefix));
259
+ return Array.from(parser.suggest(withSuggestRuntime(context), prefix));
260
+ }
261
+ /**
262
+ * Creates a dependency runtime from the current parser state and returns
263
+ * a context with the populated registry. Used by top-level suggest
264
+ * functions to mirror the construct-owned model where suggest() receives
265
+ * a context with a dependency registry.
266
+ * @internal
267
+ */
268
+ function withSuggestRuntime(context) {
269
+ const runtime = createDependencyRuntimeContext();
270
+ collectSourcesFromState(context.state, runtime);
271
+ return {
272
+ ...context,
273
+ dependencyRegistry: runtime.registry,
274
+ exec: context.exec ? {
275
+ ...context.exec,
276
+ dependencyRegistry: runtime.registry
277
+ } : void 0
278
+ };
250
279
  }
251
280
  /**
252
281
  * Generates command-line suggestions based on current parsing state.
@@ -284,16 +313,18 @@ async function suggestAsync(parser, args, options) {
284
313
  while (context.buffer.length > 0) {
285
314
  const result = await parser.parse(context);
286
315
  if (!result.success) {
316
+ const ctx$1 = withSuggestRuntime(context);
287
317
  const suggestions$1 = [];
288
- for await (const suggestion of parser.suggest(context, prefix)) suggestions$1.push(suggestion);
318
+ for await (const suggestion of parser.suggest(ctx$1, prefix)) suggestions$1.push(suggestion);
289
319
  return suggestions$1;
290
320
  }
291
321
  const previousBuffer = context.buffer;
292
322
  context = result.next;
293
323
  if (isBufferUnchanged(previousBuffer, context.buffer)) return [];
294
324
  }
325
+ const ctx = withSuggestRuntime(context);
295
326
  const suggestions = [];
296
- for await (const suggestion of parser.suggest(context, prefix)) suggestions.push(suggestion);
327
+ for await (const suggestion of parser.suggest(ctx, prefix)) suggestions.push(suggestion);
297
328
  return suggestions;
298
329
  }
299
330
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1608+66e67b9f",
3
+ "version": "1.0.0-dev.1616+17abc525",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",