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

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.
@@ -0,0 +1,328 @@
1
+ const require_dependency = require('./dependency.cjs');
2
+
3
+ //#region src/dependency-runtime.ts
4
+ const symbolIds = /* @__PURE__ */ new WeakMap();
5
+ let symbolCounter = 0;
6
+ function stableSymbolKey(sym) {
7
+ const registeredKey = Symbol.keyFor(sym);
8
+ if (registeredKey !== void 0) return `reg:${registeredKey}`;
9
+ let id = symbolIds.get(sym);
10
+ if (id === void 0) {
11
+ id = `sym:${symbolCounter++}`;
12
+ symbolIds.set(sym, id);
13
+ }
14
+ return id;
15
+ }
16
+ var DependencyRuntimeContextImpl = class {
17
+ registry;
18
+ #replayCache = /* @__PURE__ */ new Map();
19
+ #failedSources = /* @__PURE__ */ new Set();
20
+ constructor(registry) {
21
+ this.registry = registry;
22
+ }
23
+ registerSource(sourceId, value, _origin) {
24
+ this.registry.set(sourceId, value);
25
+ }
26
+ hasSource(sourceId) {
27
+ return this.registry.has(sourceId);
28
+ }
29
+ getSource(sourceId) {
30
+ return this.registry.get(sourceId);
31
+ }
32
+ resolveDependencies(request) {
33
+ return resolveRequest(this, request);
34
+ }
35
+ getReplayResult(key) {
36
+ return this.#replayCache.get(serializeReplayKey(key));
37
+ }
38
+ setReplayResult(key, result) {
39
+ this.#replayCache.set(serializeReplayKey(key), result);
40
+ }
41
+ markSourceFailed(sourceId) {
42
+ this.#failedSources.add(sourceId);
43
+ }
44
+ isSourceFailed(sourceId) {
45
+ return this.#failedSources.has(sourceId);
46
+ }
47
+ getSuggestionDependencies(request) {
48
+ return resolveRequest(this, request);
49
+ }
50
+ };
51
+ function resolveRequest(ctx, request) {
52
+ const values = [];
53
+ const usedDefaults = [];
54
+ let resolvedCount = 0;
55
+ let defaultedCount = 0;
56
+ for (let i = 0; i < request.dependencyIds.length; i++) {
57
+ const id = request.dependencyIds[i];
58
+ if (ctx.hasSource(id)) {
59
+ values.push(ctx.getSource(id));
60
+ usedDefaults.push(false);
61
+ resolvedCount++;
62
+ } else if (ctx.isSourceFailed(id)) {
63
+ values.push(void 0);
64
+ usedDefaults.push(false);
65
+ } else if (request.defaultValues != null && i < request.defaultValues.length) {
66
+ values.push(request.defaultValues[i]);
67
+ usedDefaults.push(true);
68
+ defaultedCount++;
69
+ } else {
70
+ values.push(void 0);
71
+ usedDefaults.push(false);
72
+ }
73
+ }
74
+ const total = request.dependencyIds.length;
75
+ const foundOrDefaulted = resolvedCount + defaultedCount;
76
+ let kind;
77
+ if (foundOrDefaulted === total) kind = "resolved";
78
+ else if (resolvedCount === 0 && defaultedCount === 0) kind = "missing";
79
+ else kind = "partial";
80
+ return {
81
+ kind,
82
+ values,
83
+ usedDefaults
84
+ };
85
+ }
86
+ /** Length-prefix a segment so that no delimiter escaping is needed. */
87
+ function lengthPrefix(s) {
88
+ return `${s.length}:${s}`;
89
+ }
90
+ function serializePathSegment(p) {
91
+ if (typeof p === "string") return lengthPrefix(`s${p}`);
92
+ if (typeof p === "number") return lengthPrefix(`n${p}`);
93
+ return lengthPrefix(`y${stableSymbolKey(p)}`);
94
+ }
95
+ function serializeReplayKey(key) {
96
+ const pathStr = key.path.map(serializePathSegment).join("");
97
+ return `${pathStr}\x01${lengthPrefix(key.rawInput)}\x01${key.dependencyFingerprint}\x01${key.parserFingerprint}`;
98
+ }
99
+ /** Minimal registry implementation for standalone use. */
100
+ var SimpleRegistry = class SimpleRegistry {
101
+ #map = /* @__PURE__ */ new Map();
102
+ set(id, value) {
103
+ this.#map.set(id, value);
104
+ }
105
+ get(id) {
106
+ return this.#map.get(id);
107
+ }
108
+ has(id) {
109
+ return this.#map.has(id);
110
+ }
111
+ clone() {
112
+ const copy = new SimpleRegistry();
113
+ for (const [k, v] of this.#map) copy.set(k, v);
114
+ return copy;
115
+ }
116
+ };
117
+ /**
118
+ * Creates a new {@link DependencyRuntimeContext}.
119
+ *
120
+ * @param registry Optional existing registry to wrap for bridge interop.
121
+ * @returns A new runtime context.
122
+ * @internal
123
+ * @since 1.0.0
124
+ */
125
+ function createDependencyRuntimeContext(registry) {
126
+ return new DependencyRuntimeContextImpl(registry ?? new SimpleRegistry());
127
+ }
128
+ /**
129
+ * Collects explicit source values from parser states and registers them
130
+ * in the runtime context.
131
+ *
132
+ * @param nodes The runtime nodes to inspect.
133
+ * @param runtime The dependency runtime context.
134
+ * @internal
135
+ * @since 1.0.0
136
+ */
137
+ function collectExplicitSourceValues(nodes, runtime) {
138
+ for (const node of nodes) {
139
+ const meta = node.parser.dependencyMetadata;
140
+ if (meta?.source == null) continue;
141
+ if (meta.source.extractSourceValue == null) continue;
142
+ const result = meta.source.extractSourceValue(node.state);
143
+ if (result == null) continue;
144
+ if (result.success) runtime.registerSource(meta.source.sourceId, result.value, "cli");
145
+ else runtime.markSourceFailed(meta.source.sourceId);
146
+ }
147
+ }
148
+ /**
149
+ * Checks if a value is a plain object (not a class instance) for the
150
+ * purpose of recursive state traversal.
151
+ */
152
+ function isPlainObject(value) {
153
+ if (typeof value !== "object" || value === null) return false;
154
+ const proto = Object.getPrototypeOf(value);
155
+ return proto === Object.prototype || proto === null;
156
+ }
157
+ /**
158
+ * Resolves a single {@link DeferredParseState} using the dependency runtime.
159
+ *
160
+ * Returns the replay result if all dependencies are available, or the
161
+ * preliminary result if dependencies are missing.
162
+ */
163
+ function resolveSingleDeferred(deferred, runtime) {
164
+ const isMultiDep = deferred.dependencyIds != null && deferred.dependencyIds.length > 0;
165
+ const depIds = isMultiDep ? deferred.dependencyIds : [deferred.dependencyId];
166
+ const resolution = runtime.resolveDependencies({
167
+ dependencyIds: depIds,
168
+ defaultValues: deferred.defaultValues
169
+ });
170
+ if (resolution.kind !== "resolved") return deferred.preliminaryResult;
171
+ const depValue = isMultiDep ? resolution.values : resolution.values[0];
172
+ const result = deferred.parser[require_dependency.parseWithDependency](deferred.rawInput, depValue);
173
+ if (result instanceof Promise) return deferred.preliminaryResult;
174
+ return result;
175
+ }
176
+ /**
177
+ * Recursively collects dependency source values from {@link DependencySourceState}
178
+ * objects found in the state tree and registers them in the runtime.
179
+ *
180
+ * This must run BEFORE deferred resolution so that all source values
181
+ * are available when replaying derived parsers.
182
+ */
183
+ function collectSourcesFromState(state, runtime, visited = /* @__PURE__ */ new WeakSet()) {
184
+ if (state == null || typeof state !== "object") return;
185
+ if (visited.has(state)) return;
186
+ visited.add(state);
187
+ if (require_dependency.isDependencySourceState(state)) {
188
+ const depId = state[require_dependency.dependencyId];
189
+ const result = state.result;
190
+ if (depId != null && result.success) runtime.registerSource(depId, result.value, "cli");
191
+ else if (depId != null) runtime.markSourceFailed(depId);
192
+ return;
193
+ }
194
+ if (require_dependency.isDeferredParseState(state)) return;
195
+ if (Array.isArray(state)) {
196
+ for (const item of state) collectSourcesFromState(item, runtime, visited);
197
+ return;
198
+ }
199
+ if (typeof state === "object") for (const key of Reflect.ownKeys(state)) collectSourcesFromState(state[key], runtime, visited);
200
+ }
201
+ /**
202
+ * Recursively resolves all {@link DeferredParseState} objects in a state
203
+ * tree using the dependency runtime (sync).
204
+ *
205
+ * Performs a two-pass traversal:
206
+ * 1. Collect all {@link DependencySourceState} values into the runtime.
207
+ * 2. Resolve all {@link DeferredParseState} using the populated runtime.
208
+ *
209
+ * This replaces the old `resolveDeferredParseStates` with runtime-based
210
+ * resolution. Only traverses plain objects and arrays; class instances
211
+ * and primitives are returned as-is.
212
+ *
213
+ * @param state The state tree to resolve.
214
+ * @param runtime The dependency runtime context.
215
+ * @returns The resolved state tree.
216
+ * @internal
217
+ * @since 1.0.0
218
+ */
219
+ function resolveStateWithRuntime(state, runtime) {
220
+ collectSourcesFromState(state, runtime);
221
+ return resolveDeferredInState(state, runtime);
222
+ }
223
+ /** Pass 2 helper: recursively replace DeferredParseState with resolved values. */
224
+ function resolveDeferredInState(state, runtime, visited = /* @__PURE__ */ new WeakSet()) {
225
+ if (state == null) return state;
226
+ if (typeof state === "object") {
227
+ if (visited.has(state)) return state;
228
+ visited.add(state);
229
+ }
230
+ if (require_dependency.isDeferredParseState(state)) return resolveSingleDeferred(state, runtime);
231
+ if (require_dependency.isDependencySourceState(state)) return state;
232
+ if (Array.isArray(state)) return state.map((item) => resolveDeferredInState(item, runtime, visited));
233
+ if (isPlainObject(state)) {
234
+ const resolved = Object.create(Object.getPrototypeOf(state));
235
+ for (const key of Reflect.ownKeys(state)) resolved[key] = resolveDeferredInState(state[key], runtime, visited);
236
+ return resolved;
237
+ }
238
+ return state;
239
+ }
240
+ /**
241
+ * Async version of {@link resolveStateWithRuntime}.
242
+ *
243
+ * @param state The state tree to resolve.
244
+ * @param runtime The dependency runtime context.
245
+ * @returns The resolved state tree.
246
+ * @internal
247
+ * @since 1.0.0
248
+ */
249
+ function resolveStateWithRuntimeAsync(state, runtime) {
250
+ collectSourcesFromState(state, runtime);
251
+ return resolveDeferredInStateAsync(state, runtime);
252
+ }
253
+ /** Async pass 2 helper. */
254
+ async function resolveDeferredInStateAsync(state, runtime, visited = /* @__PURE__ */ new WeakSet()) {
255
+ if (state == null) return state;
256
+ if (typeof state === "object") {
257
+ if (visited.has(state)) return state;
258
+ visited.add(state);
259
+ }
260
+ if (require_dependency.isDeferredParseState(state)) {
261
+ const deferred = state;
262
+ const isMultiDep = deferred.dependencyIds != null && deferred.dependencyIds.length > 0;
263
+ const depIds = isMultiDep ? deferred.dependencyIds : [deferred.dependencyId];
264
+ const resolution = runtime.resolveDependencies({
265
+ dependencyIds: depIds,
266
+ defaultValues: deferred.defaultValues
267
+ });
268
+ if (resolution.kind !== "resolved") return deferred.preliminaryResult;
269
+ const depValue = isMultiDep ? resolution.values : resolution.values[0];
270
+ return Promise.resolve(deferred.parser[require_dependency.parseWithDependency](deferred.rawInput, depValue));
271
+ }
272
+ if (require_dependency.isDependencySourceState(state)) return state;
273
+ if (Array.isArray(state)) return Promise.all(state.map((item) => resolveDeferredInStateAsync(item, runtime, visited)));
274
+ if (isPlainObject(state)) {
275
+ const resolved = Object.create(Object.getPrototypeOf(state));
276
+ const keys = Reflect.ownKeys(state);
277
+ await Promise.all(keys.map(async (key) => {
278
+ resolved[key] = await resolveDeferredInStateAsync(state[key], runtime, visited);
279
+ }));
280
+ return resolved;
281
+ }
282
+ return state;
283
+ }
284
+ /**
285
+ * Determines whether a parser state represents an explicit match (the user
286
+ * provided input) rather than an initial/pending state.
287
+ */
288
+ function isMatchedState(fieldState, parser) {
289
+ if (fieldState === void 0) return false;
290
+ if (Array.isArray(fieldState) && fieldState.length === 1 && require_dependency.isPendingDependencySourceState(fieldState[0])) return false;
291
+ if (require_dependency.isPendingDependencySourceState(fieldState)) return false;
292
+ if (fieldState === parser.initialState) return false;
293
+ return true;
294
+ }
295
+ /**
296
+ * Builds {@link RuntimeNode}s from field→parser pairs and a state record.
297
+ *
298
+ * Used by `object()` and `merge()` constructs.
299
+ *
300
+ * @param pairs Field→parser pairs.
301
+ * @param state The state record keyed by field name.
302
+ * @param parentPath Optional parent path prefix.
303
+ * @returns An array of runtime nodes.
304
+ * @internal
305
+ * @since 1.0.0
306
+ */
307
+ function buildRuntimeNodesFromPairs(pairs, state, parentPath) {
308
+ const prefix = parentPath ?? [];
309
+ const nodes = [];
310
+ for (const [field, parser] of pairs) {
311
+ const fieldState = Object.hasOwn(state, field) ? state[field] : void 0;
312
+ nodes.push({
313
+ path: [...prefix, field],
314
+ parser,
315
+ state: fieldState,
316
+ matched: isMatchedState(fieldState, parser)
317
+ });
318
+ }
319
+ return nodes;
320
+ }
321
+
322
+ //#endregion
323
+ exports.buildRuntimeNodesFromPairs = buildRuntimeNodesFromPairs;
324
+ exports.collectExplicitSourceValues = collectExplicitSourceValues;
325
+ exports.collectSourcesFromState = collectSourcesFromState;
326
+ exports.createDependencyRuntimeContext = createDependencyRuntimeContext;
327
+ exports.resolveStateWithRuntime = resolveStateWithRuntime;
328
+ exports.resolveStateWithRuntimeAsync = resolveStateWithRuntimeAsync;
@@ -0,0 +1,123 @@
1
+ import { DependencyRegistryLike } from "./registry-types.cjs";
2
+ import { ValueParserResult } from "./valueparser.cjs";
3
+
4
+ //#region src/dependency-runtime.d.ts
5
+
6
+ /**
7
+ * The origin of a dependency source value.
8
+ *
9
+ * @internal
10
+ * @since 1.0.0
11
+ */
12
+ type DependencyValueOrigin = "cli" | "default" | "config" | "env" | "prompt" | "derived-precomplete";
13
+ /**
14
+ * A request to resolve one or more dependency values.
15
+ *
16
+ * @internal
17
+ * @since 1.0.0
18
+ */
19
+ interface DependencyRequest {
20
+ /** The dependency source IDs to resolve. */
21
+ readonly dependencyIds: readonly symbol[];
22
+ /** Optional default values (one per ID) for missing sources. */
23
+ readonly defaultValues?: readonly unknown[];
24
+ }
25
+ /**
26
+ * The result of a dependency resolution request.
27
+ *
28
+ * @internal
29
+ * @since 1.0.0
30
+ */
31
+ interface DependencyResolution {
32
+ /**
33
+ * - `"resolved"`: all dependency values are available.
34
+ * - `"partial"`: some are available, some are missing.
35
+ * - `"missing"`: none are available.
36
+ */
37
+ readonly kind: "resolved" | "partial" | "missing";
38
+ /** The resolved values (one per requested ID, `undefined` for missing). */
39
+ readonly values: readonly unknown[];
40
+ /** For each position, whether the value came from a default. */
41
+ readonly usedDefaults: readonly boolean[];
42
+ }
43
+ /**
44
+ * A failure that occurred while evaluating a missing-source default.
45
+ * Returned by `fillMissingSourceDefaults()` so the caller can propagate
46
+ * the error instead of silently treating the source as absent.
47
+ *
48
+ * @internal
49
+ * @since 1.0.0
50
+ */
51
+
52
+ /**
53
+ * A key for caching replayed parse results.
54
+ *
55
+ * @internal
56
+ * @since 1.0.0
57
+ */
58
+ interface ReplayKey {
59
+ /** Path from root to the parser node. */
60
+ readonly path: readonly PropertyKey[];
61
+ /** The raw input string that was parsed. */
62
+ readonly rawInput: string;
63
+ /** A stable fingerprint of the dependency values used. */
64
+ readonly dependencyFingerprint: string;
65
+ /**
66
+ * A per-parser identity string that disambiguates different derived
67
+ * parsers sharing the same path (e.g., alternative branches).
68
+ * @since 1.0.0
69
+ */
70
+ readonly parserFingerprint: string;
71
+ }
72
+ /**
73
+ * A runtime node representing a child parser's position, metadata, and state.
74
+ * Used as input to the shared runtime helpers.
75
+ *
76
+ * @internal
77
+ * @since 1.0.0
78
+ */
79
+
80
+ /**
81
+ * Dependency runtime context for centralized dependency resolution.
82
+ *
83
+ * @internal
84
+ * @since 1.0.0
85
+ */
86
+ interface DependencyRuntimeContext {
87
+ /** The underlying registry (for bridge interop). */
88
+ readonly registry: DependencyRegistryLike;
89
+ /** Register a source value with its origin. */
90
+ registerSource(sourceId: symbol, value: unknown, origin: DependencyValueOrigin): void;
91
+ /** Check if a source has been registered. */
92
+ hasSource(sourceId: symbol): boolean;
93
+ /** Get a registered source value. */
94
+ getSource(sourceId: symbol): unknown;
95
+ /** Resolve dependency values for a request. */
96
+ resolveDependencies(request: DependencyRequest): DependencyResolution;
97
+ /** Get a cached replay result. */
98
+ getReplayResult(key: ReplayKey): ValueParserResult<unknown> | undefined;
99
+ /** Cache a replay result. */
100
+ setReplayResult(key: ReplayKey, result: ValueParserResult<unknown>): void;
101
+ /**
102
+ * Mark a source as explicitly failed (user provided input that did
103
+ * not pass validation). Derived parsers should not fall back to
104
+ * defaults for failed sources.
105
+ */
106
+ markSourceFailed(sourceId: symbol): void;
107
+ /**
108
+ * Check if a source was explicitly attempted but failed validation.
109
+ */
110
+ isSourceFailed(sourceId: symbol): boolean;
111
+ /** Resolve dependencies for suggestions (same semantics as resolve). */
112
+ getSuggestionDependencies(request: DependencyRequest): DependencyResolution;
113
+ }
114
+ /**
115
+ * Creates a new {@link DependencyRuntimeContext}.
116
+ *
117
+ * @param registry Optional existing registry to wrap for bridge interop.
118
+ * @returns A new runtime context.
119
+ * @internal
120
+ * @since 1.0.0
121
+ */
122
+ //#endregion
123
+ export { DependencyRuntimeContext };
@@ -0,0 +1,123 @@
1
+ import { DependencyRegistryLike } from "./registry-types.js";
2
+ import { ValueParserResult } from "./valueparser.js";
3
+
4
+ //#region src/dependency-runtime.d.ts
5
+
6
+ /**
7
+ * The origin of a dependency source value.
8
+ *
9
+ * @internal
10
+ * @since 1.0.0
11
+ */
12
+ type DependencyValueOrigin = "cli" | "default" | "config" | "env" | "prompt" | "derived-precomplete";
13
+ /**
14
+ * A request to resolve one or more dependency values.
15
+ *
16
+ * @internal
17
+ * @since 1.0.0
18
+ */
19
+ interface DependencyRequest {
20
+ /** The dependency source IDs to resolve. */
21
+ readonly dependencyIds: readonly symbol[];
22
+ /** Optional default values (one per ID) for missing sources. */
23
+ readonly defaultValues?: readonly unknown[];
24
+ }
25
+ /**
26
+ * The result of a dependency resolution request.
27
+ *
28
+ * @internal
29
+ * @since 1.0.0
30
+ */
31
+ interface DependencyResolution {
32
+ /**
33
+ * - `"resolved"`: all dependency values are available.
34
+ * - `"partial"`: some are available, some are missing.
35
+ * - `"missing"`: none are available.
36
+ */
37
+ readonly kind: "resolved" | "partial" | "missing";
38
+ /** The resolved values (one per requested ID, `undefined` for missing). */
39
+ readonly values: readonly unknown[];
40
+ /** For each position, whether the value came from a default. */
41
+ readonly usedDefaults: readonly boolean[];
42
+ }
43
+ /**
44
+ * A failure that occurred while evaluating a missing-source default.
45
+ * Returned by `fillMissingSourceDefaults()` so the caller can propagate
46
+ * the error instead of silently treating the source as absent.
47
+ *
48
+ * @internal
49
+ * @since 1.0.0
50
+ */
51
+
52
+ /**
53
+ * A key for caching replayed parse results.
54
+ *
55
+ * @internal
56
+ * @since 1.0.0
57
+ */
58
+ interface ReplayKey {
59
+ /** Path from root to the parser node. */
60
+ readonly path: readonly PropertyKey[];
61
+ /** The raw input string that was parsed. */
62
+ readonly rawInput: string;
63
+ /** A stable fingerprint of the dependency values used. */
64
+ readonly dependencyFingerprint: string;
65
+ /**
66
+ * A per-parser identity string that disambiguates different derived
67
+ * parsers sharing the same path (e.g., alternative branches).
68
+ * @since 1.0.0
69
+ */
70
+ readonly parserFingerprint: string;
71
+ }
72
+ /**
73
+ * A runtime node representing a child parser's position, metadata, and state.
74
+ * Used as input to the shared runtime helpers.
75
+ *
76
+ * @internal
77
+ * @since 1.0.0
78
+ */
79
+
80
+ /**
81
+ * Dependency runtime context for centralized dependency resolution.
82
+ *
83
+ * @internal
84
+ * @since 1.0.0
85
+ */
86
+ interface DependencyRuntimeContext {
87
+ /** The underlying registry (for bridge interop). */
88
+ readonly registry: DependencyRegistryLike;
89
+ /** Register a source value with its origin. */
90
+ registerSource(sourceId: symbol, value: unknown, origin: DependencyValueOrigin): void;
91
+ /** Check if a source has been registered. */
92
+ hasSource(sourceId: symbol): boolean;
93
+ /** Get a registered source value. */
94
+ getSource(sourceId: symbol): unknown;
95
+ /** Resolve dependency values for a request. */
96
+ resolveDependencies(request: DependencyRequest): DependencyResolution;
97
+ /** Get a cached replay result. */
98
+ getReplayResult(key: ReplayKey): ValueParserResult<unknown> | undefined;
99
+ /** Cache a replay result. */
100
+ setReplayResult(key: ReplayKey, result: ValueParserResult<unknown>): void;
101
+ /**
102
+ * Mark a source as explicitly failed (user provided input that did
103
+ * not pass validation). Derived parsers should not fall back to
104
+ * defaults for failed sources.
105
+ */
106
+ markSourceFailed(sourceId: symbol): void;
107
+ /**
108
+ * Check if a source was explicitly attempted but failed validation.
109
+ */
110
+ isSourceFailed(sourceId: symbol): boolean;
111
+ /** Resolve dependencies for suggestions (same semantics as resolve). */
112
+ getSuggestionDependencies(request: DependencyRequest): DependencyResolution;
113
+ }
114
+ /**
115
+ * Creates a new {@link DependencyRuntimeContext}.
116
+ *
117
+ * @param registry Optional existing registry to wrap for bridge interop.
118
+ * @returns A new runtime context.
119
+ * @internal
120
+ * @since 1.0.0
121
+ */
122
+ //#endregion
123
+ export { DependencyRuntimeContext };