@optique/core 1.0.0-dev.1616 → 1.0.0-dev.1658
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.
- package/dist/constructs.cjs +937 -557
- package/dist/constructs.js +938 -558
- package/dist/dependency-metadata.cjs +12 -13
- package/dist/dependency-metadata.d.cts +7 -3
- package/dist/dependency-metadata.d.ts +7 -3
- package/dist/dependency-metadata.js +12 -13
- package/dist/dependency-runtime.cjs +370 -13
- package/dist/dependency-runtime.d.cts +28 -2
- package/dist/dependency-runtime.d.ts +28 -2
- package/dist/dependency-runtime.js +365 -14
- package/dist/dependency.cjs +158 -65
- package/dist/dependency.d.cts +34 -8
- package/dist/dependency.d.ts +34 -8
- package/dist/dependency.js +156 -66
- package/dist/facade.cjs +2 -2
- package/dist/facade.js +2 -2
- package/dist/index.cjs +6 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/input-trace.cjs +56 -0
- package/dist/input-trace.d.cts +77 -0
- package/dist/input-trace.d.ts +77 -0
- package/dist/input-trace.js +55 -0
- package/dist/modifiers.cjs +320 -182
- package/dist/modifiers.js +320 -182
- package/dist/parser.cjs +83 -20
- package/dist/parser.d.cts +87 -5
- package/dist/parser.d.ts +87 -5
- package/dist/parser.js +82 -21
- package/dist/primitives.cjs +293 -163
- package/dist/primitives.d.cts +2 -8
- package/dist/primitives.d.ts +2 -8
- package/dist/primitives.js +294 -164
- package/package.json +1 -1
|
@@ -50,12 +50,17 @@ function extractDependencyMetadata(valueParser) {
|
|
|
50
50
|
return void 0;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* Extracts the source parse result from a
|
|
54
|
-
*
|
|
53
|
+
* Extracts the source parse result from a plain source state.
|
|
54
|
+
*
|
|
55
|
+
* Accepts either a `DependencySourceState` or a bare
|
|
56
|
+
* `ValueParserResult`-shaped object, and returns `undefined` for
|
|
57
|
+
* unrelated states. Used as the base `extractSourceValue` for plain
|
|
58
|
+
* dependency sources.
|
|
55
59
|
*/
|
|
56
60
|
function extractFromBareState(state) {
|
|
57
|
-
if (
|
|
58
|
-
return state
|
|
61
|
+
if (require_dependency.isDependencySourceState(state)) return state.result;
|
|
62
|
+
if (state != null && typeof state === "object" && Object.hasOwn(state, "success") && typeof state.success === "boolean" && (state.success && Object.hasOwn(state, "value") || !state.success && Object.hasOwn(state, "error"))) return state;
|
|
63
|
+
return void 0;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* Wraps an inner `extractSourceValue` to unwrap `[innerState]` first.
|
|
@@ -101,19 +106,13 @@ function composeDependencyMetadata(inner, wrapperKind, options) {
|
|
|
101
106
|
}
|
|
102
107
|
case "withDefault": {
|
|
103
108
|
const wrappedExtract = inner.source?.extractSourceValue != null ? unwrapArrayThenExtract(inner.source.extractSourceValue) : void 0;
|
|
104
|
-
|
|
109
|
+
const preservesSourceValue = inner.source?.preservesSourceValue !== false;
|
|
110
|
+
if (inner.source != null) return {
|
|
105
111
|
...inner,
|
|
106
112
|
source: {
|
|
107
113
|
...inner.source,
|
|
108
114
|
...wrappedExtract != null && { extractSourceValue: wrappedExtract },
|
|
109
|
-
getMissingSourceValue: options
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
if (wrappedExtract != null && inner.source != null) return {
|
|
113
|
-
...inner,
|
|
114
|
-
source: {
|
|
115
|
-
...inner.source,
|
|
116
|
-
extractSourceValue: wrappedExtract
|
|
115
|
+
...preservesSourceValue && options?.defaultValue != null && { getMissingSourceValue: options.defaultValue }
|
|
117
116
|
}
|
|
118
117
|
};
|
|
119
118
|
return inner;
|
|
@@ -17,16 +17,20 @@ interface DependencySourceCapability {
|
|
|
17
17
|
/**
|
|
18
18
|
* Extracts the dependency source parse result from the parser's state.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
20
|
+
* The `state` argument is the current parser state for the source-owning
|
|
21
|
+
* parser. Each wrapper composes this method to handle its own state shape:
|
|
21
22
|
* - plain source: reads from `DependencySourceState`
|
|
22
23
|
* - `optional()` / `withDefault()`: unwraps `[innerState]` first
|
|
23
24
|
* - `map()`: reads the pre-transform value from inner state
|
|
24
25
|
*
|
|
25
26
|
* Returns the `ValueParserResult` (which may be successful with any
|
|
26
|
-
* value including `undefined`, or failed),
|
|
27
|
+
* value including `undefined`, or failed), a promise that resolves to the
|
|
28
|
+
* same shape when extraction needs async work, or `undefined` if the state
|
|
27
29
|
* does not contain a source result at all (unpopulated / wrong shape).
|
|
30
|
+
* Callers and wrapper authors must handle both direct and promise-wrapped
|
|
31
|
+
* results when composing `extractSourceValue`.
|
|
28
32
|
*/
|
|
29
|
-
readonly extractSourceValue: (state: unknown) => ValueParserResult<unknown> | undefined;
|
|
33
|
+
readonly extractSourceValue: (state: unknown) => ValueParserResult<unknown> | Promise<ValueParserResult<unknown> | undefined> | undefined;
|
|
30
34
|
/**
|
|
31
35
|
* When present, provides a missing-source value (e.g., from a
|
|
32
36
|
* `withDefault()` wrapper). Called during the `fillMissingSourceDefaults`
|
|
@@ -17,16 +17,20 @@ interface DependencySourceCapability {
|
|
|
17
17
|
/**
|
|
18
18
|
* Extracts the dependency source parse result from the parser's state.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
20
|
+
* The `state` argument is the current parser state for the source-owning
|
|
21
|
+
* parser. Each wrapper composes this method to handle its own state shape:
|
|
21
22
|
* - plain source: reads from `DependencySourceState`
|
|
22
23
|
* - `optional()` / `withDefault()`: unwraps `[innerState]` first
|
|
23
24
|
* - `map()`: reads the pre-transform value from inner state
|
|
24
25
|
*
|
|
25
26
|
* Returns the `ValueParserResult` (which may be successful with any
|
|
26
|
-
* value including `undefined`, or failed),
|
|
27
|
+
* value including `undefined`, or failed), a promise that resolves to the
|
|
28
|
+
* same shape when extraction needs async work, or `undefined` if the state
|
|
27
29
|
* does not contain a source result at all (unpopulated / wrong shape).
|
|
30
|
+
* Callers and wrapper authors must handle both direct and promise-wrapped
|
|
31
|
+
* results when composing `extractSourceValue`.
|
|
28
32
|
*/
|
|
29
|
-
readonly extractSourceValue: (state: unknown) => ValueParserResult<unknown> | undefined;
|
|
33
|
+
readonly extractSourceValue: (state: unknown) => ValueParserResult<unknown> | Promise<ValueParserResult<unknown> | undefined> | undefined;
|
|
30
34
|
/**
|
|
31
35
|
* When present, provides a missing-source value (e.g., from a
|
|
32
36
|
* `withDefault()` wrapper). Called during the `fillMissingSourceDefaults`
|
|
@@ -50,12 +50,17 @@ function extractDependencyMetadata(valueParser) {
|
|
|
50
50
|
return void 0;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* Extracts the source parse result from a
|
|
54
|
-
*
|
|
53
|
+
* Extracts the source parse result from a plain source state.
|
|
54
|
+
*
|
|
55
|
+
* Accepts either a `DependencySourceState` or a bare
|
|
56
|
+
* `ValueParserResult`-shaped object, and returns `undefined` for
|
|
57
|
+
* unrelated states. Used as the base `extractSourceValue` for plain
|
|
58
|
+
* dependency sources.
|
|
55
59
|
*/
|
|
56
60
|
function extractFromBareState(state) {
|
|
57
|
-
if (
|
|
58
|
-
return state
|
|
61
|
+
if (isDependencySourceState(state)) return state.result;
|
|
62
|
+
if (state != null && typeof state === "object" && Object.hasOwn(state, "success") && typeof state.success === "boolean" && (state.success && Object.hasOwn(state, "value") || !state.success && Object.hasOwn(state, "error"))) return state;
|
|
63
|
+
return void 0;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* Wraps an inner `extractSourceValue` to unwrap `[innerState]` first.
|
|
@@ -101,19 +106,13 @@ function composeDependencyMetadata(inner, wrapperKind, options) {
|
|
|
101
106
|
}
|
|
102
107
|
case "withDefault": {
|
|
103
108
|
const wrappedExtract = inner.source?.extractSourceValue != null ? unwrapArrayThenExtract(inner.source.extractSourceValue) : void 0;
|
|
104
|
-
|
|
109
|
+
const preservesSourceValue = inner.source?.preservesSourceValue !== false;
|
|
110
|
+
if (inner.source != null) return {
|
|
105
111
|
...inner,
|
|
106
112
|
source: {
|
|
107
113
|
...inner.source,
|
|
108
114
|
...wrappedExtract != null && { extractSourceValue: wrappedExtract },
|
|
109
|
-
getMissingSourceValue: options
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
if (wrappedExtract != null && inner.source != null) return {
|
|
113
|
-
...inner,
|
|
114
|
-
source: {
|
|
115
|
-
...inner.source,
|
|
116
|
-
extractSourceValue: wrappedExtract
|
|
115
|
+
...preservesSourceValue && options?.defaultValue != null && { getMissingSourceValue: options.defaultValue }
|
|
117
116
|
}
|
|
118
117
|
};
|
|
119
118
|
return inner;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
const require_message = require('./message.cjs');
|
|
1
2
|
const require_dependency = require('./dependency.cjs');
|
|
3
|
+
const require_parser = require('./parser.cjs');
|
|
2
4
|
|
|
3
5
|
//#region src/dependency-runtime.ts
|
|
4
6
|
const symbolIds = /* @__PURE__ */ new WeakMap();
|
|
@@ -18,7 +20,11 @@ var DependencyRuntimeContextImpl = class {
|
|
|
18
20
|
#replayCache = /* @__PURE__ */ new Map();
|
|
19
21
|
#failedSources = /* @__PURE__ */ new Set();
|
|
20
22
|
constructor(registry) {
|
|
21
|
-
|
|
23
|
+
if (registry instanceof FailedAwareRegistry) {
|
|
24
|
+
this.registry = registry.rebindFailedSources(this.#failedSources);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.registry = new FailedAwareRegistry(registry, this.#failedSources);
|
|
22
28
|
}
|
|
23
29
|
registerSource(sourceId, value, _origin) {
|
|
24
30
|
this.registry.set(sourceId, value);
|
|
@@ -48,6 +54,47 @@ var DependencyRuntimeContextImpl = class {
|
|
|
48
54
|
return resolveRequest(this, request);
|
|
49
55
|
}
|
|
50
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Registry wrapper that hides values for sources that have failed.
|
|
59
|
+
*
|
|
60
|
+
* The wrapper lets clones share an underlying registry while maintaining
|
|
61
|
+
* an isolated failed-source view, so later lookups do not reuse stale
|
|
62
|
+
* values after extraction errors.
|
|
63
|
+
*
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
var FailedAwareRegistry = class FailedAwareRegistry {
|
|
67
|
+
#inner;
|
|
68
|
+
#failedSources;
|
|
69
|
+
constructor(inner, failedSources) {
|
|
70
|
+
this.#inner = inner;
|
|
71
|
+
this.#failedSources = failedSources;
|
|
72
|
+
}
|
|
73
|
+
set(id, value) {
|
|
74
|
+
this.#inner.set(id, value);
|
|
75
|
+
this.#failedSources.delete(id);
|
|
76
|
+
}
|
|
77
|
+
get(id) {
|
|
78
|
+
if (this.#failedSources.has(id)) return void 0;
|
|
79
|
+
return this.#inner.get(id);
|
|
80
|
+
}
|
|
81
|
+
has(id) {
|
|
82
|
+
if (this.#failedSources.has(id)) return false;
|
|
83
|
+
return this.#inner.has(id);
|
|
84
|
+
}
|
|
85
|
+
copyFailedSources(target) {
|
|
86
|
+
for (const sourceId of this.#failedSources) target.add(sourceId);
|
|
87
|
+
}
|
|
88
|
+
rebindFailedSources(target) {
|
|
89
|
+
this.copyFailedSources(target);
|
|
90
|
+
return new FailedAwareRegistry(this.#inner, target);
|
|
91
|
+
}
|
|
92
|
+
clone() {
|
|
93
|
+
const failedSources = new Set(this.#failedSources);
|
|
94
|
+
const innerClone = this.#inner.clone();
|
|
95
|
+
return innerClone instanceof FailedAwareRegistry ? innerClone.rebindFailedSources(failedSources) : new FailedAwareRegistry(innerClone, failedSources);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
51
98
|
function resolveRequest(ctx, request) {
|
|
52
99
|
const values = [];
|
|
53
100
|
const usedDefaults = [];
|
|
@@ -55,13 +102,13 @@ function resolveRequest(ctx, request) {
|
|
|
55
102
|
let defaultedCount = 0;
|
|
56
103
|
for (let i = 0; i < request.dependencyIds.length; i++) {
|
|
57
104
|
const id = request.dependencyIds[i];
|
|
58
|
-
if (ctx.
|
|
105
|
+
if (ctx.isSourceFailed(id)) {
|
|
106
|
+
values.push(void 0);
|
|
107
|
+
usedDefaults.push(false);
|
|
108
|
+
} else if (ctx.hasSource(id)) {
|
|
59
109
|
values.push(ctx.getSource(id));
|
|
60
110
|
usedDefaults.push(false);
|
|
61
111
|
resolvedCount++;
|
|
62
|
-
} else if (ctx.isSourceFailed(id)) {
|
|
63
|
-
values.push(void 0);
|
|
64
|
-
usedDefaults.push(false);
|
|
65
112
|
} else if (request.defaultValues != null && i < request.defaultValues.length) {
|
|
66
113
|
values.push(request.defaultValues[i]);
|
|
67
114
|
usedDefaults.push(true);
|
|
@@ -126,11 +173,80 @@ function createDependencyRuntimeContext(registry) {
|
|
|
126
173
|
return new DependencyRuntimeContextImpl(registry ?? new SimpleRegistry());
|
|
127
174
|
}
|
|
128
175
|
/**
|
|
176
|
+
* Creates a stable fingerprint from dependency values.
|
|
177
|
+
*
|
|
178
|
+
* @param values The dependency values to fingerprint.
|
|
179
|
+
* @returns A string fingerprint.
|
|
180
|
+
* @internal
|
|
181
|
+
* @since 1.0.0
|
|
182
|
+
*/
|
|
183
|
+
function createDependencyFingerprint(values) {
|
|
184
|
+
return values.map((v) => {
|
|
185
|
+
const raw = fingerprintValue(v);
|
|
186
|
+
return `${raw.length}:${raw}`;
|
|
187
|
+
}).join("");
|
|
188
|
+
}
|
|
189
|
+
const objectIds = /* @__PURE__ */ new WeakMap();
|
|
190
|
+
let objectIdCounter = 0;
|
|
191
|
+
function fingerprintValue(v) {
|
|
192
|
+
if (v === void 0) return "u:";
|
|
193
|
+
if (v === null) return "n:";
|
|
194
|
+
if (typeof v === "string") return `s:${v}`;
|
|
195
|
+
if (typeof v === "number") {
|
|
196
|
+
if (Object.is(v, -0)) return "d:-0";
|
|
197
|
+
return `d:${v}`;
|
|
198
|
+
}
|
|
199
|
+
if (typeof v === "boolean") return `b:${v}`;
|
|
200
|
+
if (typeof v === "symbol") return `y:${stableSymbolKey(v)}`;
|
|
201
|
+
if (typeof v === "object" || typeof v === "function") {
|
|
202
|
+
let id = objectIds.get(v);
|
|
203
|
+
if (id === void 0) {
|
|
204
|
+
id = objectIdCounter++;
|
|
205
|
+
objectIds.set(v, id);
|
|
206
|
+
}
|
|
207
|
+
return `o:${id}`;
|
|
208
|
+
}
|
|
209
|
+
return `?:${String(v)}`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Creates a {@link ReplayKey} from a path, raw input, and dependency values.
|
|
213
|
+
*
|
|
214
|
+
* @param path The parser path.
|
|
215
|
+
* @param rawInput The raw input string.
|
|
216
|
+
* @param dependencyValues The dependency values.
|
|
217
|
+
* @returns A replay key.
|
|
218
|
+
* @internal
|
|
219
|
+
* @since 1.0.0
|
|
220
|
+
*/
|
|
221
|
+
const parserIds = /* @__PURE__ */ new WeakMap();
|
|
222
|
+
let parserIdCounter = 0;
|
|
223
|
+
/** Get a stable identity string for a replayParse function reference. */
|
|
224
|
+
function getParserFingerprint(replayParse) {
|
|
225
|
+
let id = parserIds.get(replayParse);
|
|
226
|
+
if (id === void 0) {
|
|
227
|
+
id = parserIdCounter++;
|
|
228
|
+
parserIds.set(replayParse, id);
|
|
229
|
+
}
|
|
230
|
+
return `p:${id}`;
|
|
231
|
+
}
|
|
232
|
+
function createReplayKey(path, rawInput, dependencyValues, replayParse) {
|
|
233
|
+
return {
|
|
234
|
+
path,
|
|
235
|
+
rawInput,
|
|
236
|
+
dependencyFingerprint: createDependencyFingerprint(dependencyValues),
|
|
237
|
+
parserFingerprint: replayParse != null ? getParserFingerprint(replayParse) : ""
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
129
241
|
* Collects explicit source values from parser states and registers them
|
|
130
242
|
* in the runtime context.
|
|
131
243
|
*
|
|
132
244
|
* @param nodes The runtime nodes to inspect.
|
|
133
245
|
* @param runtime The dependency runtime context.
|
|
246
|
+
* @throws Propagates synchronous errors thrown by `extractSourceValue()`.
|
|
247
|
+
* @throws {TypeError} If `extractSourceValue()` returns a promise-like result.
|
|
248
|
+
* Use {@link collectExplicitSourceValuesAsync} when async source
|
|
249
|
+
* extraction is expected.
|
|
134
250
|
* @internal
|
|
135
251
|
* @since 1.0.0
|
|
136
252
|
*/
|
|
@@ -140,12 +256,207 @@ function collectExplicitSourceValues(nodes, runtime) {
|
|
|
140
256
|
if (meta?.source == null) continue;
|
|
141
257
|
if (meta.source.extractSourceValue == null) continue;
|
|
142
258
|
const result = meta.source.extractSourceValue(node.state);
|
|
143
|
-
if (result
|
|
144
|
-
|
|
145
|
-
|
|
259
|
+
if (isPromiseLike(result)) throw new TypeError(`collectExplicitSourceValues() received an async extractSourceValue() result for ${String(meta.source.sourceId)}. Use collectExplicitSourceValuesAsync() instead.`);
|
|
260
|
+
registerExplicitSourceValue(meta.source.sourceId, result, runtime);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function registerExplicitSourceValue(sourceId, result, runtime) {
|
|
264
|
+
if (result == null) return;
|
|
265
|
+
if (result.success) runtime.registerSource(sourceId, result.value, "cli");
|
|
266
|
+
else runtime.markSourceFailed(sourceId);
|
|
267
|
+
}
|
|
268
|
+
function isPromiseLike(value) {
|
|
269
|
+
return value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Async version of {@link collectExplicitSourceValues}.
|
|
273
|
+
* Awaits async `extractSourceValue` results instead of rejecting
|
|
274
|
+
* promise-like values as the synchronous variant does.
|
|
275
|
+
*
|
|
276
|
+
* @param nodes The runtime nodes to inspect.
|
|
277
|
+
* @param runtime The dependency runtime context.
|
|
278
|
+
* @throws Propagates errors thrown or rejected by `extractSourceValue()`.
|
|
279
|
+
* @internal
|
|
280
|
+
* @since 1.0.0
|
|
281
|
+
*/
|
|
282
|
+
async function collectExplicitSourceValuesAsync(nodes, runtime) {
|
|
283
|
+
for (const node of nodes) {
|
|
284
|
+
const meta = node.parser.dependencyMetadata;
|
|
285
|
+
if (meta?.source == null) continue;
|
|
286
|
+
if (meta.source.extractSourceValue == null) continue;
|
|
287
|
+
const result = await meta.source.extractSourceValue(node.state);
|
|
288
|
+
registerExplicitSourceValue(meta.source.sourceId, result, runtime);
|
|
146
289
|
}
|
|
147
290
|
}
|
|
148
291
|
/**
|
|
292
|
+
* Fills missing source defaults for source parsers whose state is
|
|
293
|
+
* unpopulated.
|
|
294
|
+
*
|
|
295
|
+
* Returns an array of failures for sources whose default evaluation
|
|
296
|
+
* failed (either threw or returned `{ success: false }`). The caller
|
|
297
|
+
* should propagate these so that dependent parsers see the real error
|
|
298
|
+
* instead of silently treating the source as absent.
|
|
299
|
+
*
|
|
300
|
+
* @param nodes The runtime nodes to inspect.
|
|
301
|
+
* @param runtime The dependency runtime context.
|
|
302
|
+
* @returns Failures from default evaluation (empty if all succeeded).
|
|
303
|
+
* @throws {TypeError} If `getMissingSourceValue()` returns a promise-like
|
|
304
|
+
* result. Use {@link fillMissingSourceDefaultsAsync} when async
|
|
305
|
+
* default extraction is expected.
|
|
306
|
+
* @internal
|
|
307
|
+
* @since 1.0.0
|
|
308
|
+
*/
|
|
309
|
+
function fillMissingSourceDefaults(nodes, runtime) {
|
|
310
|
+
const failures = [];
|
|
311
|
+
for (const node of nodes) {
|
|
312
|
+
const meta = node.parser.dependencyMetadata;
|
|
313
|
+
if (meta?.source == null) continue;
|
|
314
|
+
if (runtime.hasSource(meta.source.sourceId)) continue;
|
|
315
|
+
if (runtime.isSourceFailed(meta.source.sourceId)) continue;
|
|
316
|
+
if (node.matched === true) continue;
|
|
317
|
+
if (!meta.source.preservesSourceValue) continue;
|
|
318
|
+
if (meta.source.getMissingSourceValue == null) continue;
|
|
319
|
+
let result;
|
|
320
|
+
try {
|
|
321
|
+
result = meta.source.getMissingSourceValue();
|
|
322
|
+
} catch (e) {
|
|
323
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
324
|
+
failures.push({
|
|
325
|
+
sourceId: meta.source.sourceId,
|
|
326
|
+
path: node.path,
|
|
327
|
+
error: {
|
|
328
|
+
success: false,
|
|
329
|
+
error: require_message.message`Default value evaluation failed: ${msg}`
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (isPromiseLike(result)) throw new TypeError(`fillMissingSourceDefaults() received an async getMissingSourceValue() result for ${String(meta.source.sourceId)}. Use fillMissingSourceDefaultsAsync() instead.`);
|
|
335
|
+
if (result.success) runtime.registerSource(meta.source.sourceId, result.value, "default");
|
|
336
|
+
else failures.push({
|
|
337
|
+
sourceId: meta.source.sourceId,
|
|
338
|
+
path: node.path,
|
|
339
|
+
error: result
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return failures;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Async version of {@link fillMissingSourceDefaults}.
|
|
346
|
+
* Awaits async `getMissingSourceValue` results.
|
|
347
|
+
*
|
|
348
|
+
* @param nodes The runtime nodes to inspect.
|
|
349
|
+
* @param runtime The dependency runtime context.
|
|
350
|
+
* @returns Failures from default evaluation (empty if all succeeded).
|
|
351
|
+
* @internal
|
|
352
|
+
* @since 1.0.0
|
|
353
|
+
*/
|
|
354
|
+
async function fillMissingSourceDefaultsAsync(nodes, runtime) {
|
|
355
|
+
const failures = [];
|
|
356
|
+
for (const node of nodes) {
|
|
357
|
+
const meta = node.parser.dependencyMetadata;
|
|
358
|
+
if (meta?.source == null) continue;
|
|
359
|
+
if (runtime.hasSource(meta.source.sourceId)) continue;
|
|
360
|
+
if (runtime.isSourceFailed(meta.source.sourceId)) continue;
|
|
361
|
+
if (node.matched === true) continue;
|
|
362
|
+
if (!meta.source.preservesSourceValue) continue;
|
|
363
|
+
if (meta.source.getMissingSourceValue == null) continue;
|
|
364
|
+
let result;
|
|
365
|
+
try {
|
|
366
|
+
result = await meta.source.getMissingSourceValue();
|
|
367
|
+
} catch (e) {
|
|
368
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
369
|
+
failures.push({
|
|
370
|
+
sourceId: meta.source.sourceId,
|
|
371
|
+
path: node.path,
|
|
372
|
+
error: {
|
|
373
|
+
success: false,
|
|
374
|
+
error: require_message.message`Default value evaluation failed: ${msg}`
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (result.success) runtime.registerSource(meta.source.sourceId, result.value, "default");
|
|
380
|
+
else failures.push({
|
|
381
|
+
sourceId: meta.source.sourceId,
|
|
382
|
+
path: node.path,
|
|
383
|
+
error: result
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return failures;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Replays a derived parser with resolved dependency values (sync).
|
|
390
|
+
*
|
|
391
|
+
* Returns `undefined` if dependencies cannot be resolved.
|
|
392
|
+
*
|
|
393
|
+
* @param node The runtime node with derived metadata.
|
|
394
|
+
* @param rawInput The raw input to replay.
|
|
395
|
+
* @param runtime The dependency runtime context.
|
|
396
|
+
* @returns The replay result, or `undefined`.
|
|
397
|
+
* @throws {TypeError} If `replayParse()` returns a promise-like result.
|
|
398
|
+
* Use {@link replayDerivedParserAsync} for async replay.
|
|
399
|
+
* @internal
|
|
400
|
+
* @since 1.0.0
|
|
401
|
+
*/
|
|
402
|
+
function replayDerivedParser(node, rawInput, runtime) {
|
|
403
|
+
const meta = node.parser.dependencyMetadata;
|
|
404
|
+
if (meta?.derived == null) return void 0;
|
|
405
|
+
let defaults = node.defaultDependencyValues;
|
|
406
|
+
if (defaults == null && meta.derived.getDefaultDependencyValues != null) try {
|
|
407
|
+
defaults = meta.derived.getDefaultDependencyValues();
|
|
408
|
+
} catch {
|
|
409
|
+
return void 0;
|
|
410
|
+
}
|
|
411
|
+
const resolution = runtime.resolveDependencies({
|
|
412
|
+
dependencyIds: meta.derived.dependencyIds,
|
|
413
|
+
defaultValues: defaults
|
|
414
|
+
});
|
|
415
|
+
if (resolution.kind === "missing") return void 0;
|
|
416
|
+
if (resolution.kind === "partial") return void 0;
|
|
417
|
+
const key = createReplayKey(node.path, rawInput, resolution.values, meta.derived.replayParse);
|
|
418
|
+
const cached = runtime.getReplayResult(key);
|
|
419
|
+
if (cached != null) return cached;
|
|
420
|
+
const result = meta.derived.replayParse(rawInput, resolution.values);
|
|
421
|
+
if (isPromiseLike(result)) throw new TypeError("replayDerivedParser() received an async replayParse() result. Use replayDerivedParserAsync() instead.");
|
|
422
|
+
runtime.setReplayResult(key, result);
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Replays a derived parser with resolved dependency values (async).
|
|
427
|
+
*
|
|
428
|
+
* Returns `undefined` if dependencies cannot be resolved.
|
|
429
|
+
*
|
|
430
|
+
* @param node The runtime node with derived metadata.
|
|
431
|
+
* @param rawInput The raw input to replay.
|
|
432
|
+
* @param runtime The dependency runtime context.
|
|
433
|
+
* @returns The replay result, or `undefined`.
|
|
434
|
+
* @internal
|
|
435
|
+
* @since 1.0.0
|
|
436
|
+
*/
|
|
437
|
+
async function replayDerivedParserAsync(node, rawInput, runtime) {
|
|
438
|
+
const meta = node.parser.dependencyMetadata;
|
|
439
|
+
if (meta?.derived == null) return void 0;
|
|
440
|
+
let defaults = node.defaultDependencyValues;
|
|
441
|
+
if (defaults == null && meta.derived.getDefaultDependencyValues != null) try {
|
|
442
|
+
defaults = meta.derived.getDefaultDependencyValues();
|
|
443
|
+
} catch {
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
const resolution = runtime.resolveDependencies({
|
|
447
|
+
dependencyIds: meta.derived.dependencyIds,
|
|
448
|
+
defaultValues: defaults
|
|
449
|
+
});
|
|
450
|
+
if (resolution.kind === "missing") return void 0;
|
|
451
|
+
if (resolution.kind === "partial") return void 0;
|
|
452
|
+
const key = createReplayKey(node.path, rawInput, resolution.values, meta.derived.replayParse);
|
|
453
|
+
const cached = runtime.getReplayResult(key);
|
|
454
|
+
if (cached != null) return cached;
|
|
455
|
+
const result = await meta.derived.replayParse(rawInput, resolution.values);
|
|
456
|
+
runtime.setReplayResult(key, result);
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
149
460
|
* Checks if a value is a plain object (not a class instance) for the
|
|
150
461
|
* purpose of recursive state traversal.
|
|
151
462
|
*/
|
|
@@ -171,7 +482,7 @@ function resolveSingleDeferred(deferred, runtime) {
|
|
|
171
482
|
if (resolution.usedDefaults.every((d) => d)) return deferred.preliminaryResult;
|
|
172
483
|
const depValue = isMultiDep ? resolution.values : resolution.values[0];
|
|
173
484
|
const result = deferred.parser[require_dependency.parseWithDependency](deferred.rawInput, depValue);
|
|
174
|
-
if (result
|
|
485
|
+
if (isPromiseLike(result)) throw new TypeError("resolveStateWithRuntime() received an async parseWithDependency() result. Use resolveStateWithRuntimeAsync() instead.");
|
|
175
486
|
return result;
|
|
176
487
|
}
|
|
177
488
|
/**
|
|
@@ -180,8 +491,14 @@ function resolveSingleDeferred(deferred, runtime) {
|
|
|
180
491
|
*
|
|
181
492
|
* This must run BEFORE deferred resolution so that all source values
|
|
182
493
|
* are available when replaying derived parsers.
|
|
494
|
+
*
|
|
495
|
+
* @param state The state tree to traverse.
|
|
496
|
+
* @param runtime The dependency runtime context to populate.
|
|
497
|
+
* @param visited Cycle guard for recursive traversal.
|
|
498
|
+
* @param excludedFields Optional property keys to skip at the current level.
|
|
499
|
+
* This exclusion set is not propagated recursively.
|
|
183
500
|
*/
|
|
184
|
-
function collectSourcesFromState(state, runtime, visited = /* @__PURE__ */ new WeakSet()) {
|
|
501
|
+
function collectSourcesFromState(state, runtime, visited = /* @__PURE__ */ new WeakSet(), excludedFields) {
|
|
185
502
|
if (state == null || typeof state !== "object") return;
|
|
186
503
|
if (visited.has(state)) return;
|
|
187
504
|
visited.add(state);
|
|
@@ -197,7 +514,10 @@ function collectSourcesFromState(state, runtime, visited = /* @__PURE__ */ new W
|
|
|
197
514
|
for (const item of state) collectSourcesFromState(item, runtime, visited);
|
|
198
515
|
return;
|
|
199
516
|
}
|
|
200
|
-
if (typeof state === "object") for (const key of Reflect.ownKeys(state))
|
|
517
|
+
if (typeof state === "object") for (const key of Reflect.ownKeys(state)) {
|
|
518
|
+
if (excludedFields?.has(key)) continue;
|
|
519
|
+
collectSourcesFromState(state[key], runtime, visited);
|
|
520
|
+
}
|
|
201
521
|
}
|
|
202
522
|
/**
|
|
203
523
|
* Recursively resolves all {@link DeferredParseState} objects in a state
|
|
@@ -214,6 +534,9 @@ function collectSourcesFromState(state, runtime, visited = /* @__PURE__ */ new W
|
|
|
214
534
|
* @param state The state tree to resolve.
|
|
215
535
|
* @param runtime The dependency runtime context.
|
|
216
536
|
* @returns The resolved state tree.
|
|
537
|
+
* @throws {TypeError} If a deferred parser returns a promise-like result from
|
|
538
|
+
* `parseWithDependency()`. Use {@link resolveStateWithRuntimeAsync}
|
|
539
|
+
* for async resolution.
|
|
217
540
|
* @internal
|
|
218
541
|
* @since 1.0.0
|
|
219
542
|
*/
|
|
@@ -289,8 +612,9 @@ async function resolveDeferredInStateAsync(state, runtime, visited = /* @__PURE_
|
|
|
289
612
|
*/
|
|
290
613
|
function isMatchedState(fieldState, parser) {
|
|
291
614
|
if (fieldState === void 0) return false;
|
|
292
|
-
|
|
293
|
-
if (require_dependency.isPendingDependencySourceState(
|
|
615
|
+
const innerState = Array.isArray(fieldState) && fieldState.length === 1 ? fieldState[0] : fieldState;
|
|
616
|
+
if (require_dependency.isPendingDependencySourceState(innerState)) return false;
|
|
617
|
+
if (parser[require_parser.unmatchedNonCliDependencySourceStateMarker] === true && innerState != null && typeof innerState === "object" && Object.hasOwn(innerState, "hasCliValue") && innerState.hasCliValue === false) return false;
|
|
294
618
|
if (fieldState === parser.initialState) return false;
|
|
295
619
|
return true;
|
|
296
620
|
}
|
|
@@ -320,11 +644,44 @@ function buildRuntimeNodesFromPairs(pairs, state, parentPath) {
|
|
|
320
644
|
}
|
|
321
645
|
return nodes;
|
|
322
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Builds {@link RuntimeNode}s from a parser array and a state array.
|
|
649
|
+
*
|
|
650
|
+
* Used by `tuple()` and `concat()` constructs.
|
|
651
|
+
*
|
|
652
|
+
* @param parsers The child parsers.
|
|
653
|
+
* @param stateArray The state array (one element per parser).
|
|
654
|
+
* @param parentPath Optional parent path prefix.
|
|
655
|
+
* @returns An array of runtime nodes.
|
|
656
|
+
* @internal
|
|
657
|
+
* @since 1.0.0
|
|
658
|
+
*/
|
|
659
|
+
function buildRuntimeNodesFromArray(parsers, stateArray, parentPath) {
|
|
660
|
+
const prefix = parentPath ?? [];
|
|
661
|
+
const nodes = [];
|
|
662
|
+
for (let i = 0; i < parsers.length; i++) {
|
|
663
|
+
const parser = parsers[i];
|
|
664
|
+
const elemState = i < stateArray.length ? stateArray[i] : void 0;
|
|
665
|
+
nodes.push({
|
|
666
|
+
path: [...prefix, i],
|
|
667
|
+
parser,
|
|
668
|
+
state: elemState,
|
|
669
|
+
matched: isMatchedState(elemState, parser)
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
return nodes;
|
|
673
|
+
}
|
|
323
674
|
|
|
324
675
|
//#endregion
|
|
676
|
+
exports.buildRuntimeNodesFromArray = buildRuntimeNodesFromArray;
|
|
325
677
|
exports.buildRuntimeNodesFromPairs = buildRuntimeNodesFromPairs;
|
|
326
678
|
exports.collectExplicitSourceValues = collectExplicitSourceValues;
|
|
679
|
+
exports.collectExplicitSourceValuesAsync = collectExplicitSourceValuesAsync;
|
|
327
680
|
exports.collectSourcesFromState = collectSourcesFromState;
|
|
328
681
|
exports.createDependencyRuntimeContext = createDependencyRuntimeContext;
|
|
682
|
+
exports.fillMissingSourceDefaults = fillMissingSourceDefaults;
|
|
683
|
+
exports.fillMissingSourceDefaultsAsync = fillMissingSourceDefaultsAsync;
|
|
684
|
+
exports.replayDerivedParser = replayDerivedParser;
|
|
685
|
+
exports.replayDerivedParserAsync = replayDerivedParserAsync;
|
|
329
686
|
exports.resolveStateWithRuntime = resolveStateWithRuntime;
|
|
330
687
|
exports.resolveStateWithRuntimeAsync = resolveStateWithRuntimeAsync;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DependencyRegistryLike } from "./registry-types.cjs";
|
|
2
2
|
import { ValueParserResult } from "./valueparser.cjs";
|
|
3
|
+
import { ParserDependencyMetadata } from "./dependency-metadata.cjs";
|
|
3
4
|
|
|
4
5
|
//#region src/dependency-runtime.d.ts
|
|
5
6
|
|
|
@@ -76,7 +77,32 @@ interface ReplayKey {
|
|
|
76
77
|
* @internal
|
|
77
78
|
* @since 1.0.0
|
|
78
79
|
*/
|
|
79
|
-
|
|
80
|
+
interface RuntimeNode {
|
|
81
|
+
/** Path from root to this parser node. */
|
|
82
|
+
readonly path: readonly PropertyKey[];
|
|
83
|
+
/** The parser (only the metadata field is inspected). */
|
|
84
|
+
readonly parser: {
|
|
85
|
+
readonly dependencyMetadata?: ParserDependencyMetadata;
|
|
86
|
+
};
|
|
87
|
+
/** The parser's current state. */
|
|
88
|
+
readonly state: unknown;
|
|
89
|
+
/**
|
|
90
|
+
* Whether the parser consumed explicit input during parsing.
|
|
91
|
+
* When `true`, the parser's state reflects user-provided input (which
|
|
92
|
+
* may have failed validation). Missing-source defaults must not override
|
|
93
|
+
* explicit parse failures.
|
|
94
|
+
* @since 1.0.0
|
|
95
|
+
*/
|
|
96
|
+
readonly matched?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Snapshotted default dependency values for derived parsers.
|
|
99
|
+
* Constructs should populate this at node creation time (once) to
|
|
100
|
+
* avoid re-evaluating dynamic `getDefaultDependencyValues()` thunks
|
|
101
|
+
* at replay time.
|
|
102
|
+
* @since 1.0.0
|
|
103
|
+
*/
|
|
104
|
+
readonly defaultDependencyValues?: readonly unknown[];
|
|
105
|
+
}
|
|
80
106
|
/**
|
|
81
107
|
* Dependency runtime context for centralized dependency resolution.
|
|
82
108
|
*
|
|
@@ -120,4 +146,4 @@ interface DependencyRuntimeContext {
|
|
|
120
146
|
* @since 1.0.0
|
|
121
147
|
*/
|
|
122
148
|
//#endregion
|
|
123
|
-
export { DependencyRuntimeContext };
|
|
149
|
+
export { DependencyRuntimeContext, RuntimeNode };
|