@optique/prompt 1.3.0-dev.2417 → 1.3.0-dev.2418

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/index.cjs CHANGED
@@ -22,10 +22,117 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
 
23
23
  //#endregion
24
24
  const __optique_core_annotations = __toESM(require("@optique/core/annotations"));
25
+ const __optique_core_dependency = __toESM(require("@optique/core/dependency"));
26
+ const __optique_core_message = __toESM(require("@optique/core/message"));
25
27
  const __optique_core_extension = __toESM(require("@optique/core/extension"));
26
28
  const __optique_core_fluent = __toESM(require("@optique/core/fluent"));
27
29
 
28
30
  //#region src/index.ts
31
+ const derivedPromptConfigMarker = Symbol.for("@optique/prompt/derivedPromptConfig");
32
+ /**
33
+ * Checks whether a prompt configuration was created by
34
+ * {@link derivePromptConfig}.
35
+ *
36
+ * @param config The configuration to inspect.
37
+ * @returns `true` for a derived prompt configuration.
38
+ * @since 1.3.0
39
+ */
40
+ function isDerivedPromptConfig(config) {
41
+ return config != null && typeof config === "object" && derivedPromptConfigMarker in config && config[derivedPromptConfigMarker] === true;
42
+ }
43
+ function derivePromptConfig(source, resolver, options) {
44
+ const isTuple = Array.isArray(source);
45
+ const dependencies = isTuple ? source : [source];
46
+ if (dependencies.length === 0) throw new TypeError("derivePromptConfig() requires at least one dependency source.");
47
+ const infos = dependencies.map(__optique_core_dependency.getDependencySourceInfo);
48
+ const singleDefault = options?.defaultValue;
49
+ const defaultValues = isTuple ? options?.defaultValues : singleDefault == null ? void 0 : () => [singleDefault()];
50
+ const resolve = isTuple ? (values, usedDefaults) => resolver(values, { usedDefaults }) : (values, usedDefaults) => resolver(values[0], { usedDefault: usedDefaults[0] });
51
+ return {
52
+ [derivedPromptConfigMarker]: true,
53
+ dependencies,
54
+ dependencyIds: infos.map((info) => info.sourceId),
55
+ dependencyLabels: infos.map((info, index) => info.metavar ?? `dependency #${index + 1}`),
56
+ resolve,
57
+ ...defaultValues == null ? {} : { defaultValues },
58
+ ...options?.when == null ? {} : {
59
+ when: options.when,
60
+ otherwise: options.otherwise
61
+ }
62
+ };
63
+ }
64
+ function describeThrown(error) {
65
+ return error instanceof Error ? error.message : String(error);
66
+ }
67
+ /**
68
+ * Resolves a derived prompt configuration against the dependency runtime.
69
+ *
70
+ * A failed upstream source fails the resolution transitively—the resolver
71
+ * and the adapter never run. A missing source uses the configuration's
72
+ * declared default, or fails when none is declared. Resolver exceptions
73
+ * (synchronous throws and rejections alike) become prompt failures so
74
+ * the scheduler treats them like a cancelled prompt.
75
+ */
76
+ async function resolveDerivedPromptConfig(config, exec, ownSourceId, ownLabel) {
77
+ const runtime = exec?.dependencyRuntime;
78
+ const ids = config.dependencyIds;
79
+ if (runtime != null) {
80
+ const failedIndex = ids.findIndex((id) => runtime.isSourceFailed(id));
81
+ if (failedIndex >= 0) {
82
+ runtime.propagateSourceFailure(ids, ownLabel ?? "prompt", ownSourceId);
83
+ const base = __optique_core_message.message`Cannot resolve prompt configuration: dependency ${config.dependencyLabels[failedIndex]} failed.`;
84
+ const chain = ownSourceId == null ? runtime.getSourceFailureChain(ids[failedIndex]) : void 0;
85
+ return {
86
+ ok: false,
87
+ error: chain == null || chain.length < 2 ? base : __optique_core_message.message`${base} Dependency chain: ${chain.join(" -> ")}.`
88
+ };
89
+ }
90
+ }
91
+ const missing = [];
92
+ const values = ids.map((id, index) => {
93
+ if (runtime?.hasSource(id) === true) return runtime.getSource(id);
94
+ missing.push(index);
95
+ return void 0;
96
+ });
97
+ const usedDefaults = ids.map(() => false);
98
+ if (missing.length > 0) {
99
+ if (config.defaultValues == null) {
100
+ const missingLabels = (0, __optique_core_message.values)(missing.map((index) => config.dependencyLabels[index]));
101
+ return {
102
+ ok: false,
103
+ error: missing.length > 1 ? __optique_core_message.message`Cannot resolve prompt configuration: dependencies ${missingLabels} are not available and no default values are declared.` : __optique_core_message.message`Cannot resolve prompt configuration: dependency ${missingLabels} is not available and no default value is declared.`
104
+ };
105
+ }
106
+ let defaults;
107
+ try {
108
+ defaults = config.defaultValues();
109
+ } catch (error) {
110
+ return {
111
+ ok: false,
112
+ error: __optique_core_message.message`Prompt configuration default evaluation failed: ${describeThrown(error)}`
113
+ };
114
+ }
115
+ if (defaults.length !== ids.length) return {
116
+ ok: false,
117
+ error: __optique_core_message.message`Prompt configuration declared ${String(defaults.length)} default values for ${String(ids.length)} dependencies.`
118
+ };
119
+ for (const index of missing) {
120
+ values[index] = defaults[index];
121
+ usedDefaults[index] = true;
122
+ }
123
+ }
124
+ try {
125
+ return {
126
+ ok: true,
127
+ config: await config.resolve(values, usedDefaults)
128
+ };
129
+ } catch (error) {
130
+ return {
131
+ ok: false,
132
+ error: __optique_core_message.message`Prompt configuration resolution failed: ${describeThrown(error)}`
133
+ };
134
+ }
135
+ }
29
136
  function shouldDeferPrompt(parser, state, exec) {
30
137
  return typeof parser.shouldDeferCompletion === "function" && parser.shouldDeferCompletion(state, exec) === true;
31
138
  }
@@ -60,6 +167,7 @@ function hasSourceBindingMarker(state) {
60
167
  return state != null && typeof state === "object" && "hasCliValue" in state && Object.getOwnPropertySymbols(state).length > 0;
61
168
  }
62
169
  function readDefaultValue(adapter, config) {
170
+ if (isDerivedPromptConfig(config)) return void 0;
63
171
  if (adapter.getDefaultValue != null) return adapter.getDefaultValue(config);
64
172
  if (config != null && typeof config === "object" && "default" in config) return config.default;
65
173
  return void 0;
@@ -82,7 +190,8 @@ function unwrapCompleteResult(result) {
82
190
  * @typeParam TConfig Prompt configuration accepted by the adapter.
83
191
  * @param adapter Library-specific prompt executor.
84
192
  * @returns A `prompt(parser, config)` wrapper that always produces an async
85
- * parser.
193
+ * parser. The configuration may be a static `TConfig` or a
194
+ * {@link DerivedPromptConfig} whose resolver returns `TConfig`.
86
195
  * @since 1.2.0
87
196
  */
88
197
  function createPromptAdapter(adapter) {
@@ -131,12 +240,19 @@ function createPromptAdapter(adapter) {
131
240
  if (cliStateIsInjectedAnnotationWrapper && requiresSourceBindingForAnnotationWrapper) return hasNestedSourceBinding;
132
241
  return shouldAttemptInnerCompletion(cliState, state) || hasNestedSourceBinding;
133
242
  }
134
- async function executePrompt() {
243
+ async function executePrompt(exec) {
135
244
  if (config.when != null && !await config.when()) return {
136
245
  success: true,
137
246
  value: config.otherwise
138
247
  };
139
- return adapter.execute(config);
248
+ if (!isDerivedPromptConfig(config)) return adapter.execute(config);
249
+ const source = promptedParser.dependencyMetadata?.source;
250
+ const resolved = await resolveDerivedPromptConfig(config, exec, source?.sourceId, source?.metavar);
251
+ if (!resolved.ok) return {
252
+ success: false,
253
+ error: resolved.error
254
+ };
255
+ return adapter.execute(resolved.config);
140
256
  }
141
257
  const parserInheritsAnnotations = (0, __optique_core_extension.getTraits)(parser).inheritsAnnotations === true;
142
258
  const promptedParser = {
@@ -247,13 +363,15 @@ function createPromptAdapter(adapter) {
247
363
  return Promise.resolve(cached);
248
364
  }
249
365
  if (session.policy === "demand-only" && !session.demanded.has(sourceId)) return Promise.resolve(deferredPromptResult(readPlaceholder()));
250
- return executePrompt().then((result) => {
366
+ if (session.policy === "demand-only" && isDerivedPromptConfig(config) && config.dependencyIds.some((id) => exec?.dependencyRuntime?.hasSource(id) !== true && exec?.dependencyRuntime?.isSourceFailed(id) !== true)) return Promise.resolve(deferredPromptResult(readPlaceholder()));
367
+ return executePrompt(exec).then((result) => {
251
368
  session.results.set(cacheKey, result);
252
369
  session.effectfulSources.add(sourceId);
253
370
  return result;
254
371
  });
255
372
  }
256
- return executePrompt();
373
+ if (session?.policy === "demand-only" && isDerivedPromptConfig(config)) return Promise.resolve(deferredPromptResult(readPlaceholder()));
374
+ return executePrompt(exec);
257
375
  };
258
376
  const hasDeferHook = typeof parser.shouldDeferCompletion === "function";
259
377
  const decideFromParse = (parseResult) => {
@@ -337,8 +455,12 @@ function createPromptAdapter(adapter) {
337
455
  },
338
456
  completeSource: source.preservesSourceValue === false ? void 0 : (state, exec) => promptedParser.complete(state, exec)
339
457
  }));
340
- if (dependencyMetadata != null) Object.defineProperty(promptedParser, "dependencyMetadata", {
341
- value: dependencyMetadata,
458
+ const composedMetadata = isDerivedPromptConfig(config) ? {
459
+ ...dependencyMetadata ?? {},
460
+ completion: { dependencyIds: config.dependencyIds }
461
+ } : dependencyMetadata;
462
+ if (composedMetadata != null) Object.defineProperty(promptedParser, "dependencyMetadata", {
463
+ value: composedMetadata,
342
464
  configurable: true,
343
465
  enumerable: false
344
466
  });
@@ -347,4 +469,6 @@ function createPromptAdapter(adapter) {
347
469
  }
348
470
 
349
471
  //#endregion
350
- exports.createPromptAdapter = createPromptAdapter;
472
+ exports.createPromptAdapter = createPromptAdapter;
473
+ exports.derivePromptConfig = derivePromptConfig;
474
+ exports.isDerivedPromptConfig = isDerivedPromptConfig;
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import { AnyDependencySource, DependencyValue, DependencyValues } from "@optique/core/dependency";
1
2
  import { FluentParser } from "@optique/core/fluent";
2
3
  import { Mode, Parser } from "@optique/core/parser";
3
4
  import { ValueParserResult } from "@optique/core/valueparser";
@@ -57,6 +58,175 @@ interface PromptAdapter<TConfig> {
57
58
  */
58
59
  readonly getDefaultValue?: (config: TConfig) => unknown;
59
60
  }
61
+ declare const derivedPromptConfigMarker: unique symbol;
62
+ /**
63
+ * Context passed to a single-dependency prompt configuration resolver.
64
+ *
65
+ * @since 1.3.0
66
+ */
67
+ interface DerivePromptConfigContext {
68
+ /**
69
+ * Whether the dependency value came from the `defaultValue` declared by
70
+ * this derived prompt configuration rather than a published source
71
+ * value. Source-level fallbacks such as `withDefault()` publish real
72
+ * values and are not reported here.
73
+ */
74
+ readonly usedDefault: boolean;
75
+ }
76
+ /**
77
+ * Context passed to a multi-dependency prompt configuration resolver.
78
+ *
79
+ * @typeParam Deps Tuple of dependency sources the resolver reads.
80
+ * @since 1.3.0
81
+ */
82
+ interface DerivePromptConfigsContext<Deps extends readonly AnyDependencySource[]> {
83
+ /**
84
+ * For each dependency position, whether the value came from the
85
+ * `defaultValues` declared by this derived prompt configuration rather
86
+ * than a published source value.
87
+ */
88
+ readonly usedDefaults: { readonly [K in keyof Deps]: boolean };
89
+ }
90
+ /**
91
+ * Options for a single-dependency {@link derivePromptConfig} call.
92
+ *
93
+ * @typeParam TDefault Value type of the dependency source.
94
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
95
+ * @since 1.3.0
96
+ */
97
+ type DerivePromptConfigOptions<TDefault, TOtherwise> = {
98
+ /**
99
+ * Lazily evaluated fallback used when the dependency source has not
100
+ * published a value. Without it, an unresolved dependency fails the
101
+ * prompt instead of running the resolver.
102
+ */
103
+ readonly defaultValue?: () => TDefault;
104
+ } & ({
105
+ readonly when?: never;
106
+ readonly otherwise?: never;
107
+ } | {
108
+ readonly when: () => boolean | Promise<boolean>;
109
+ readonly otherwise: TOtherwise;
110
+ });
111
+ /**
112
+ * Options for a multi-dependency {@link derivePromptConfig} call.
113
+ *
114
+ * @typeParam TDefaults Tuple of dependency source value types.
115
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
116
+ * @since 1.3.0
117
+ */
118
+ type DerivePromptConfigsOptions<TDefaults extends readonly unknown[], TOtherwise> = {
119
+ /**
120
+ * Lazily evaluated fallbacks used for dependency sources that have
121
+ * not published a value. The thunk must return one value per
122
+ * dependency. Without it, an unresolved dependency fails the prompt
123
+ * instead of running the resolver.
124
+ */
125
+ readonly defaultValues?: () => TDefaults;
126
+ } & ({
127
+ readonly when?: never;
128
+ readonly otherwise?: never;
129
+ } | {
130
+ readonly when: () => boolean | Promise<boolean>;
131
+ readonly otherwise: TOtherwise;
132
+ });
133
+ /**
134
+ * A prompt configuration derived from dependency source values, created
135
+ * by {@link derivePromptConfig}.
136
+ *
137
+ * The resolver runs during the real completion phase, immediately before
138
+ * the adapter executes, and never during probes, help, or suggestions.
139
+ * Generated documentation therefore cannot reflect a derived
140
+ * configuration and falls back to the wrapped parser's static metadata.
141
+ *
142
+ * @typeParam TConfig Adapter configuration produced by the resolver.
143
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
144
+ * @since 1.3.0
145
+ */
146
+ interface DerivedPromptConfig<TConfig, TOtherwise = never> {
147
+ readonly [derivedPromptConfigMarker]: true;
148
+ /** The dependency sources the resolver reads, in declaration order. */
149
+ readonly dependencies: readonly AnyDependencySource[];
150
+ /** Snapshot of the dependency source identities. @internal */
151
+ readonly dependencyIds: readonly symbol[];
152
+ /** Diagnostic labels matching {@link dependencyIds}. @internal */
153
+ readonly dependencyLabels: readonly string[];
154
+ /**
155
+ * Resolves the adapter configuration from dependency values. Receives
156
+ * one value and one used-default flag per dependency position.
157
+ * @internal
158
+ */
159
+ readonly resolve: (values: readonly unknown[], usedDefaults: readonly boolean[]) => TConfig | Promise<TConfig>;
160
+ /**
161
+ * Lazily evaluated fallbacks, one per dependency position.
162
+ * @internal
163
+ */
164
+ readonly defaultValues?: () => readonly unknown[];
165
+ /** Runtime condition, evaluated before the resolver. */
166
+ readonly when?: () => boolean | Promise<boolean>;
167
+ /** Value produced when {@link when} returns `false`. */
168
+ readonly otherwise?: TOtherwise;
169
+ }
170
+ /**
171
+ * Checks whether a prompt configuration was created by
172
+ * {@link derivePromptConfig}.
173
+ *
174
+ * @param config The configuration to inspect.
175
+ * @returns `true` for a derived prompt configuration.
176
+ * @since 1.3.0
177
+ */
178
+ declare function isDerivedPromptConfig(config: unknown): config is DerivedPromptConfig<unknown, unknown>;
179
+ /**
180
+ * Derives a prompt configuration from one dependency source value.
181
+ *
182
+ * The resolver may return the configuration synchronously or
183
+ * asynchronously. It runs only during the real completion phase, after
184
+ * the named source has published its value—whether that value came from
185
+ * the command line, a source binding, or another prompt.
186
+ *
187
+ * @typeParam D The dependency source type.
188
+ * @typeParam TConfig Adapter configuration produced by the resolver.
189
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
190
+ * @param source The dependency source the resolver reads.
191
+ * @param resolver Produces the adapter configuration from the source
192
+ * value.
193
+ * @param options Optional declared default and runtime condition.
194
+ * @returns A derived configuration accepted by `prompt()` wrappers.
195
+ * @throws {TypeError} If `source` is not a dependency source.
196
+ * @since 1.3.0
197
+ */
198
+ declare function derivePromptConfig<D extends AnyDependencySource, TConfig, const TOtherwise = never>(source: D, resolver: (value: DependencyValue<D>, context: DerivePromptConfigContext) => TConfig | Promise<TConfig>, options?: DerivePromptConfigOptions<DependencyValue<D>, TOtherwise>): DerivedPromptConfig<TConfig, TOtherwise>;
199
+ /**
200
+ * Derives a prompt configuration from multiple dependency source values.
201
+ *
202
+ * The resolver receives the values as a tuple matching the declaration
203
+ * order of `sources`. Evaluation order among prompts follows the
204
+ * dependency graph, not surrounding object or tuple field order.
205
+ *
206
+ * @typeParam Deps Tuple of dependency sources the resolver reads.
207
+ * @typeParam TConfig Adapter configuration produced by the resolver.
208
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
209
+ * @param sources The dependency sources, at least one.
210
+ * @param resolver Produces the adapter configuration from the source
211
+ * values.
212
+ * @param options Optional declared defaults and runtime condition.
213
+ * @returns A derived configuration accepted by `prompt()` wrappers.
214
+ * @throws {TypeError} If `sources` is empty or contains a value that is
215
+ * not a dependency source.
216
+ * @since 1.3.0
217
+ */
218
+ declare function derivePromptConfig<const Deps extends readonly [AnyDependencySource, ...AnyDependencySource[]], TConfig, const TOtherwise = never>(sources: Deps, resolver: (values: DependencyValues<Deps>, context: DerivePromptConfigsContext<Deps>) => TConfig | Promise<TConfig>, options?: DerivePromptConfigsOptions<DependencyValues<Deps>, TOtherwise>): DerivedPromptConfig<TConfig, TOtherwise>;
219
+ /**
220
+ * Prompt configuration accepted by a generated `prompt()` wrapper: either
221
+ * a static adapter configuration (optionally with a runtime condition) or
222
+ * a configuration derived from dependency sources via
223
+ * {@link derivePromptConfig}.
224
+ *
225
+ * @typeParam TConfig Prompt configuration accepted by the adapter.
226
+ * @typeParam TValue Value type produced by the wrapped parser.
227
+ * @since 1.3.0
228
+ */
229
+ type PromptConfigInput<TConfig, TValue> = (TConfig & PromptCondition<TValue>) | DerivedPromptConfig<TConfig, NoInfer<TValue>>;
60
230
  /**
61
231
  * Creates a `prompt()` parser wrapper for a prompt library adapter.
62
232
  *
@@ -68,9 +238,10 @@ interface PromptAdapter<TConfig> {
68
238
  * @typeParam TConfig Prompt configuration accepted by the adapter.
69
239
  * @param adapter Library-specific prompt executor.
70
240
  * @returns A `prompt(parser, config)` wrapper that always produces an async
71
- * parser.
241
+ * parser. The configuration may be a static `TConfig` or a
242
+ * {@link DerivedPromptConfig} whose resolver returns `TConfig`.
72
243
  * @since 1.2.0
73
244
  */
74
- declare function createPromptAdapter<TConfig>(adapter: PromptAdapter<TConfig>): <M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, config: TConfig & PromptCondition<TValue>) => FluentParser<"async", TValue, TState>;
245
+ declare function createPromptAdapter<TConfig>(adapter: PromptAdapter<TConfig>): <M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, config: PromptConfigInput<TConfig, TValue>) => FluentParser<"async", TValue, TState>;
75
246
  //#endregion
76
- export { PromptAdapter, PromptCondition, createPromptAdapter };
247
+ export { DerivePromptConfigContext, DerivePromptConfigOptions, DerivePromptConfigsContext, DerivePromptConfigsOptions, DerivedPromptConfig, PromptAdapter, PromptCondition, PromptConfigInput, createPromptAdapter, derivePromptConfig, isDerivedPromptConfig };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { AnyDependencySource, DependencyValue, DependencyValues } from "@optique/core/dependency";
1
2
  import { FluentParser } from "@optique/core/fluent";
2
3
  import { Mode, Parser } from "@optique/core/parser";
3
4
  import { ValueParserResult } from "@optique/core/valueparser";
@@ -57,6 +58,175 @@ interface PromptAdapter<TConfig> {
57
58
  */
58
59
  readonly getDefaultValue?: (config: TConfig) => unknown;
59
60
  }
61
+ declare const derivedPromptConfigMarker: unique symbol;
62
+ /**
63
+ * Context passed to a single-dependency prompt configuration resolver.
64
+ *
65
+ * @since 1.3.0
66
+ */
67
+ interface DerivePromptConfigContext {
68
+ /**
69
+ * Whether the dependency value came from the `defaultValue` declared by
70
+ * this derived prompt configuration rather than a published source
71
+ * value. Source-level fallbacks such as `withDefault()` publish real
72
+ * values and are not reported here.
73
+ */
74
+ readonly usedDefault: boolean;
75
+ }
76
+ /**
77
+ * Context passed to a multi-dependency prompt configuration resolver.
78
+ *
79
+ * @typeParam Deps Tuple of dependency sources the resolver reads.
80
+ * @since 1.3.0
81
+ */
82
+ interface DerivePromptConfigsContext<Deps extends readonly AnyDependencySource[]> {
83
+ /**
84
+ * For each dependency position, whether the value came from the
85
+ * `defaultValues` declared by this derived prompt configuration rather
86
+ * than a published source value.
87
+ */
88
+ readonly usedDefaults: { readonly [K in keyof Deps]: boolean };
89
+ }
90
+ /**
91
+ * Options for a single-dependency {@link derivePromptConfig} call.
92
+ *
93
+ * @typeParam TDefault Value type of the dependency source.
94
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
95
+ * @since 1.3.0
96
+ */
97
+ type DerivePromptConfigOptions<TDefault, TOtherwise> = {
98
+ /**
99
+ * Lazily evaluated fallback used when the dependency source has not
100
+ * published a value. Without it, an unresolved dependency fails the
101
+ * prompt instead of running the resolver.
102
+ */
103
+ readonly defaultValue?: () => TDefault;
104
+ } & ({
105
+ readonly when?: never;
106
+ readonly otherwise?: never;
107
+ } | {
108
+ readonly when: () => boolean | Promise<boolean>;
109
+ readonly otherwise: TOtherwise;
110
+ });
111
+ /**
112
+ * Options for a multi-dependency {@link derivePromptConfig} call.
113
+ *
114
+ * @typeParam TDefaults Tuple of dependency source value types.
115
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
116
+ * @since 1.3.0
117
+ */
118
+ type DerivePromptConfigsOptions<TDefaults extends readonly unknown[], TOtherwise> = {
119
+ /**
120
+ * Lazily evaluated fallbacks used for dependency sources that have
121
+ * not published a value. The thunk must return one value per
122
+ * dependency. Without it, an unresolved dependency fails the prompt
123
+ * instead of running the resolver.
124
+ */
125
+ readonly defaultValues?: () => TDefaults;
126
+ } & ({
127
+ readonly when?: never;
128
+ readonly otherwise?: never;
129
+ } | {
130
+ readonly when: () => boolean | Promise<boolean>;
131
+ readonly otherwise: TOtherwise;
132
+ });
133
+ /**
134
+ * A prompt configuration derived from dependency source values, created
135
+ * by {@link derivePromptConfig}.
136
+ *
137
+ * The resolver runs during the real completion phase, immediately before
138
+ * the adapter executes, and never during probes, help, or suggestions.
139
+ * Generated documentation therefore cannot reflect a derived
140
+ * configuration and falls back to the wrapped parser's static metadata.
141
+ *
142
+ * @typeParam TConfig Adapter configuration produced by the resolver.
143
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
144
+ * @since 1.3.0
145
+ */
146
+ interface DerivedPromptConfig<TConfig, TOtherwise = never> {
147
+ readonly [derivedPromptConfigMarker]: true;
148
+ /** The dependency sources the resolver reads, in declaration order. */
149
+ readonly dependencies: readonly AnyDependencySource[];
150
+ /** Snapshot of the dependency source identities. @internal */
151
+ readonly dependencyIds: readonly symbol[];
152
+ /** Diagnostic labels matching {@link dependencyIds}. @internal */
153
+ readonly dependencyLabels: readonly string[];
154
+ /**
155
+ * Resolves the adapter configuration from dependency values. Receives
156
+ * one value and one used-default flag per dependency position.
157
+ * @internal
158
+ */
159
+ readonly resolve: (values: readonly unknown[], usedDefaults: readonly boolean[]) => TConfig | Promise<TConfig>;
160
+ /**
161
+ * Lazily evaluated fallbacks, one per dependency position.
162
+ * @internal
163
+ */
164
+ readonly defaultValues?: () => readonly unknown[];
165
+ /** Runtime condition, evaluated before the resolver. */
166
+ readonly when?: () => boolean | Promise<boolean>;
167
+ /** Value produced when {@link when} returns `false`. */
168
+ readonly otherwise?: TOtherwise;
169
+ }
170
+ /**
171
+ * Checks whether a prompt configuration was created by
172
+ * {@link derivePromptConfig}.
173
+ *
174
+ * @param config The configuration to inspect.
175
+ * @returns `true` for a derived prompt configuration.
176
+ * @since 1.3.0
177
+ */
178
+ declare function isDerivedPromptConfig(config: unknown): config is DerivedPromptConfig<unknown, unknown>;
179
+ /**
180
+ * Derives a prompt configuration from one dependency source value.
181
+ *
182
+ * The resolver may return the configuration synchronously or
183
+ * asynchronously. It runs only during the real completion phase, after
184
+ * the named source has published its value—whether that value came from
185
+ * the command line, a source binding, or another prompt.
186
+ *
187
+ * @typeParam D The dependency source type.
188
+ * @typeParam TConfig Adapter configuration produced by the resolver.
189
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
190
+ * @param source The dependency source the resolver reads.
191
+ * @param resolver Produces the adapter configuration from the source
192
+ * value.
193
+ * @param options Optional declared default and runtime condition.
194
+ * @returns A derived configuration accepted by `prompt()` wrappers.
195
+ * @throws {TypeError} If `source` is not a dependency source.
196
+ * @since 1.3.0
197
+ */
198
+ declare function derivePromptConfig<D extends AnyDependencySource, TConfig, const TOtherwise = never>(source: D, resolver: (value: DependencyValue<D>, context: DerivePromptConfigContext) => TConfig | Promise<TConfig>, options?: DerivePromptConfigOptions<DependencyValue<D>, TOtherwise>): DerivedPromptConfig<TConfig, TOtherwise>;
199
+ /**
200
+ * Derives a prompt configuration from multiple dependency source values.
201
+ *
202
+ * The resolver receives the values as a tuple matching the declaration
203
+ * order of `sources`. Evaluation order among prompts follows the
204
+ * dependency graph, not surrounding object or tuple field order.
205
+ *
206
+ * @typeParam Deps Tuple of dependency sources the resolver reads.
207
+ * @typeParam TConfig Adapter configuration produced by the resolver.
208
+ * @typeParam TOtherwise Value type returned when `when` skips the prompt.
209
+ * @param sources The dependency sources, at least one.
210
+ * @param resolver Produces the adapter configuration from the source
211
+ * values.
212
+ * @param options Optional declared defaults and runtime condition.
213
+ * @returns A derived configuration accepted by `prompt()` wrappers.
214
+ * @throws {TypeError} If `sources` is empty or contains a value that is
215
+ * not a dependency source.
216
+ * @since 1.3.0
217
+ */
218
+ declare function derivePromptConfig<const Deps extends readonly [AnyDependencySource, ...AnyDependencySource[]], TConfig, const TOtherwise = never>(sources: Deps, resolver: (values: DependencyValues<Deps>, context: DerivePromptConfigsContext<Deps>) => TConfig | Promise<TConfig>, options?: DerivePromptConfigsOptions<DependencyValues<Deps>, TOtherwise>): DerivedPromptConfig<TConfig, TOtherwise>;
219
+ /**
220
+ * Prompt configuration accepted by a generated `prompt()` wrapper: either
221
+ * a static adapter configuration (optionally with a runtime condition) or
222
+ * a configuration derived from dependency sources via
223
+ * {@link derivePromptConfig}.
224
+ *
225
+ * @typeParam TConfig Prompt configuration accepted by the adapter.
226
+ * @typeParam TValue Value type produced by the wrapped parser.
227
+ * @since 1.3.0
228
+ */
229
+ type PromptConfigInput<TConfig, TValue> = (TConfig & PromptCondition<TValue>) | DerivedPromptConfig<TConfig, NoInfer<TValue>>;
60
230
  /**
61
231
  * Creates a `prompt()` parser wrapper for a prompt library adapter.
62
232
  *
@@ -68,9 +238,10 @@ interface PromptAdapter<TConfig> {
68
238
  * @typeParam TConfig Prompt configuration accepted by the adapter.
69
239
  * @param adapter Library-specific prompt executor.
70
240
  * @returns A `prompt(parser, config)` wrapper that always produces an async
71
- * parser.
241
+ * parser. The configuration may be a static `TConfig` or a
242
+ * {@link DerivedPromptConfig} whose resolver returns `TConfig`.
72
243
  * @since 1.2.0
73
244
  */
74
- declare function createPromptAdapter<TConfig>(adapter: PromptAdapter<TConfig>): <M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, config: TConfig & PromptCondition<TValue>) => FluentParser<"async", TValue, TState>;
245
+ declare function createPromptAdapter<TConfig>(adapter: PromptAdapter<TConfig>): <M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, config: PromptConfigInput<TConfig, TValue>) => FluentParser<"async", TValue, TState>;
75
246
  //#endregion
76
- export { PromptAdapter, PromptCondition, createPromptAdapter };
247
+ export { DerivePromptConfigContext, DerivePromptConfigOptions, DerivePromptConfigsContext, DerivePromptConfigsOptions, DerivedPromptConfig, PromptAdapter, PromptCondition, PromptConfigInput, createPromptAdapter, derivePromptConfig, isDerivedPromptConfig };
package/dist/index.js CHANGED
@@ -1,8 +1,115 @@
1
1
  import { getAnnotations } from "@optique/core/annotations";
2
+ import { getDependencySourceInfo } from "@optique/core/dependency";
3
+ import { message, values } from "@optique/core/message";
2
4
  import { defineTraits, delegateSuggestNodes, getTraits, inheritAnnotations, injectAnnotations, mapSourceMetadata, unwrapInjectedAnnotationState, withAnnotationView } from "@optique/core/extension";
3
5
  import { fluent } from "@optique/core/fluent";
4
6
 
5
7
  //#region src/index.ts
8
+ const derivedPromptConfigMarker = Symbol.for("@optique/prompt/derivedPromptConfig");
9
+ /**
10
+ * Checks whether a prompt configuration was created by
11
+ * {@link derivePromptConfig}.
12
+ *
13
+ * @param config The configuration to inspect.
14
+ * @returns `true` for a derived prompt configuration.
15
+ * @since 1.3.0
16
+ */
17
+ function isDerivedPromptConfig(config) {
18
+ return config != null && typeof config === "object" && derivedPromptConfigMarker in config && config[derivedPromptConfigMarker] === true;
19
+ }
20
+ function derivePromptConfig(source, resolver, options) {
21
+ const isTuple = Array.isArray(source);
22
+ const dependencies = isTuple ? source : [source];
23
+ if (dependencies.length === 0) throw new TypeError("derivePromptConfig() requires at least one dependency source.");
24
+ const infos = dependencies.map(getDependencySourceInfo);
25
+ const singleDefault = options?.defaultValue;
26
+ const defaultValues = isTuple ? options?.defaultValues : singleDefault == null ? void 0 : () => [singleDefault()];
27
+ const resolve = isTuple ? (values$1, usedDefaults) => resolver(values$1, { usedDefaults }) : (values$1, usedDefaults) => resolver(values$1[0], { usedDefault: usedDefaults[0] });
28
+ return {
29
+ [derivedPromptConfigMarker]: true,
30
+ dependencies,
31
+ dependencyIds: infos.map((info) => info.sourceId),
32
+ dependencyLabels: infos.map((info, index) => info.metavar ?? `dependency #${index + 1}`),
33
+ resolve,
34
+ ...defaultValues == null ? {} : { defaultValues },
35
+ ...options?.when == null ? {} : {
36
+ when: options.when,
37
+ otherwise: options.otherwise
38
+ }
39
+ };
40
+ }
41
+ function describeThrown(error) {
42
+ return error instanceof Error ? error.message : String(error);
43
+ }
44
+ /**
45
+ * Resolves a derived prompt configuration against the dependency runtime.
46
+ *
47
+ * A failed upstream source fails the resolution transitively—the resolver
48
+ * and the adapter never run. A missing source uses the configuration's
49
+ * declared default, or fails when none is declared. Resolver exceptions
50
+ * (synchronous throws and rejections alike) become prompt failures so
51
+ * the scheduler treats them like a cancelled prompt.
52
+ */
53
+ async function resolveDerivedPromptConfig(config, exec, ownSourceId, ownLabel) {
54
+ const runtime = exec?.dependencyRuntime;
55
+ const ids = config.dependencyIds;
56
+ if (runtime != null) {
57
+ const failedIndex = ids.findIndex((id) => runtime.isSourceFailed(id));
58
+ if (failedIndex >= 0) {
59
+ runtime.propagateSourceFailure(ids, ownLabel ?? "prompt", ownSourceId);
60
+ const base = message`Cannot resolve prompt configuration: dependency ${config.dependencyLabels[failedIndex]} failed.`;
61
+ const chain = ownSourceId == null ? runtime.getSourceFailureChain(ids[failedIndex]) : void 0;
62
+ return {
63
+ ok: false,
64
+ error: chain == null || chain.length < 2 ? base : message`${base} Dependency chain: ${chain.join(" -> ")}.`
65
+ };
66
+ }
67
+ }
68
+ const missing = [];
69
+ const values$1 = ids.map((id, index) => {
70
+ if (runtime?.hasSource(id) === true) return runtime.getSource(id);
71
+ missing.push(index);
72
+ return void 0;
73
+ });
74
+ const usedDefaults = ids.map(() => false);
75
+ if (missing.length > 0) {
76
+ if (config.defaultValues == null) {
77
+ const missingLabels = values(missing.map((index) => config.dependencyLabels[index]));
78
+ return {
79
+ ok: false,
80
+ error: missing.length > 1 ? message`Cannot resolve prompt configuration: dependencies ${missingLabels} are not available and no default values are declared.` : message`Cannot resolve prompt configuration: dependency ${missingLabels} is not available and no default value is declared.`
81
+ };
82
+ }
83
+ let defaults;
84
+ try {
85
+ defaults = config.defaultValues();
86
+ } catch (error) {
87
+ return {
88
+ ok: false,
89
+ error: message`Prompt configuration default evaluation failed: ${describeThrown(error)}`
90
+ };
91
+ }
92
+ if (defaults.length !== ids.length) return {
93
+ ok: false,
94
+ error: message`Prompt configuration declared ${String(defaults.length)} default values for ${String(ids.length)} dependencies.`
95
+ };
96
+ for (const index of missing) {
97
+ values$1[index] = defaults[index];
98
+ usedDefaults[index] = true;
99
+ }
100
+ }
101
+ try {
102
+ return {
103
+ ok: true,
104
+ config: await config.resolve(values$1, usedDefaults)
105
+ };
106
+ } catch (error) {
107
+ return {
108
+ ok: false,
109
+ error: message`Prompt configuration resolution failed: ${describeThrown(error)}`
110
+ };
111
+ }
112
+ }
6
113
  function shouldDeferPrompt(parser, state, exec) {
7
114
  return typeof parser.shouldDeferCompletion === "function" && parser.shouldDeferCompletion(state, exec) === true;
8
115
  }
@@ -37,6 +144,7 @@ function hasSourceBindingMarker(state) {
37
144
  return state != null && typeof state === "object" && "hasCliValue" in state && Object.getOwnPropertySymbols(state).length > 0;
38
145
  }
39
146
  function readDefaultValue(adapter, config) {
147
+ if (isDerivedPromptConfig(config)) return void 0;
40
148
  if (adapter.getDefaultValue != null) return adapter.getDefaultValue(config);
41
149
  if (config != null && typeof config === "object" && "default" in config) return config.default;
42
150
  return void 0;
@@ -59,7 +167,8 @@ function unwrapCompleteResult(result) {
59
167
  * @typeParam TConfig Prompt configuration accepted by the adapter.
60
168
  * @param adapter Library-specific prompt executor.
61
169
  * @returns A `prompt(parser, config)` wrapper that always produces an async
62
- * parser.
170
+ * parser. The configuration may be a static `TConfig` or a
171
+ * {@link DerivedPromptConfig} whose resolver returns `TConfig`.
63
172
  * @since 1.2.0
64
173
  */
65
174
  function createPromptAdapter(adapter) {
@@ -108,12 +217,19 @@ function createPromptAdapter(adapter) {
108
217
  if (cliStateIsInjectedAnnotationWrapper && requiresSourceBindingForAnnotationWrapper) return hasNestedSourceBinding;
109
218
  return shouldAttemptInnerCompletion(cliState, state) || hasNestedSourceBinding;
110
219
  }
111
- async function executePrompt() {
220
+ async function executePrompt(exec) {
112
221
  if (config.when != null && !await config.when()) return {
113
222
  success: true,
114
223
  value: config.otherwise
115
224
  };
116
- return adapter.execute(config);
225
+ if (!isDerivedPromptConfig(config)) return adapter.execute(config);
226
+ const source = promptedParser.dependencyMetadata?.source;
227
+ const resolved = await resolveDerivedPromptConfig(config, exec, source?.sourceId, source?.metavar);
228
+ if (!resolved.ok) return {
229
+ success: false,
230
+ error: resolved.error
231
+ };
232
+ return adapter.execute(resolved.config);
117
233
  }
118
234
  const parserInheritsAnnotations = getTraits(parser).inheritsAnnotations === true;
119
235
  const promptedParser = {
@@ -224,13 +340,15 @@ function createPromptAdapter(adapter) {
224
340
  return Promise.resolve(cached);
225
341
  }
226
342
  if (session.policy === "demand-only" && !session.demanded.has(sourceId)) return Promise.resolve(deferredPromptResult(readPlaceholder()));
227
- return executePrompt().then((result) => {
343
+ if (session.policy === "demand-only" && isDerivedPromptConfig(config) && config.dependencyIds.some((id) => exec?.dependencyRuntime?.hasSource(id) !== true && exec?.dependencyRuntime?.isSourceFailed(id) !== true)) return Promise.resolve(deferredPromptResult(readPlaceholder()));
344
+ return executePrompt(exec).then((result) => {
228
345
  session.results.set(cacheKey, result);
229
346
  session.effectfulSources.add(sourceId);
230
347
  return result;
231
348
  });
232
349
  }
233
- return executePrompt();
350
+ if (session?.policy === "demand-only" && isDerivedPromptConfig(config)) return Promise.resolve(deferredPromptResult(readPlaceholder()));
351
+ return executePrompt(exec);
234
352
  };
235
353
  const hasDeferHook = typeof parser.shouldDeferCompletion === "function";
236
354
  const decideFromParse = (parseResult) => {
@@ -314,8 +432,12 @@ function createPromptAdapter(adapter) {
314
432
  },
315
433
  completeSource: source.preservesSourceValue === false ? void 0 : (state, exec) => promptedParser.complete(state, exec)
316
434
  }));
317
- if (dependencyMetadata != null) Object.defineProperty(promptedParser, "dependencyMetadata", {
318
- value: dependencyMetadata,
435
+ const composedMetadata = isDerivedPromptConfig(config) ? {
436
+ ...dependencyMetadata ?? {},
437
+ completion: { dependencyIds: config.dependencyIds }
438
+ } : dependencyMetadata;
439
+ if (composedMetadata != null) Object.defineProperty(promptedParser, "dependencyMetadata", {
440
+ value: composedMetadata,
319
441
  configurable: true,
320
442
  enumerable: false
321
443
  });
@@ -324,4 +446,4 @@ function createPromptAdapter(adapter) {
324
446
  }
325
447
 
326
448
  //#endregion
327
- export { createPromptAdapter };
449
+ export { createPromptAdapter, derivePromptConfig, isDerivedPromptConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/prompt",
3
- "version": "1.3.0-dev.2417",
3
+ "version": "1.3.0-dev.2418",
4
4
  "description": "Generic prompt adapter support for Optique",
5
5
  "keywords": [
6
6
  "CLI",
@@ -60,12 +60,12 @@
60
60
  },
61
61
  "sideEffects": false,
62
62
  "dependencies": {
63
- "@optique/core": "1.3.0-dev.2417+6b19e980"
63
+ "@optique/core": "1.3.0-dev.2418+afa5cda1"
64
64
  },
65
65
  "devDependencies": {
66
- "@optique/config": "1.3.0-dev.2417+6b19e980",
67
- "@optique/env": "1.3.0-dev.2417+6b19e980",
68
- "@optique/run": "1.3.0-dev.2417+6b19e980",
66
+ "@optique/config": "1.3.0-dev.2418+afa5cda1",
67
+ "@optique/env": "1.3.0-dev.2418+afa5cda1",
68
+ "@optique/run": "1.3.0-dev.2418+afa5cda1",
69
69
  "@types/node": "^24.0.0",
70
70
  "fast-check": "^4.7.0",
71
71
  "tsdown": "^0.13.0",