@optique/prompt 1.4.0-dev.2723 → 1.4.0-dev.2726

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
@@ -94,7 +94,7 @@ function describeThrown(error) {
94
94
  * (synchronous throws and rejections alike) become prompt failures so
95
95
  * the scheduler treats them like a cancelled prompt.
96
96
  */
97
- async function resolveDerivedPromptConfig(config, exec, ownSourceId, ownLabel, signal) {
97
+ async function resolveDerivedPromptConfig(config, exec, ownSourceId, ownLabel, signal, adapter) {
98
98
  const runtime = exec?.dependencyRuntime;
99
99
  const ids = config.dependencyIds;
100
100
  if (runtime != null) {
@@ -142,16 +142,35 @@ async function resolveDerivedPromptConfig(config, exec, ownSourceId, ownLabel, s
142
142
  usedDefaults[index] = true;
143
143
  }
144
144
  }
145
+ const context = signal === void 0 ? {} : { signal };
146
+ let pending;
147
+ let resolverFailure;
148
+ let disposeWork;
149
+ const work = () => {
150
+ if (pending == null) {
151
+ pending = racePromptWorkWithAbort(signal, () => Promise.resolve(config.resolve(values, usedDefaults, context)), (dispose) => {
152
+ disposeWork = dispose;
153
+ });
154
+ pending.then(void 0, (error) => {
155
+ resolverFailure = { error };
156
+ });
157
+ }
158
+ return pending;
159
+ };
145
160
  try {
146
161
  return {
147
162
  ok: true,
148
- config: await config.resolve(values, usedDefaults, signal === void 0 ? {} : { signal })
163
+ config: await (adapter.whilePending == null ? work() : adapter.whilePending(work, context))
149
164
  };
150
165
  } catch (error) {
166
+ throwIfPromptAborted(signal);
167
+ if (resolverFailure == null || !Object.is(resolverFailure.error, error)) throw error;
151
168
  return {
152
169
  ok: false,
153
170
  error: __optique_core_message.message`Prompt configuration resolution failed: ${describeThrown(error)}`
154
171
  };
172
+ } finally {
173
+ disposeWork?.();
155
174
  }
156
175
  }
157
176
  function shouldDeferPrompt(parser, state, exec) {
@@ -203,7 +222,7 @@ function unwrapCompleteResult(result) {
203
222
  function throwIfPromptAborted(signal) {
204
223
  if (signal?.aborted === true) throw signal.reason;
205
224
  }
206
- function racePromptWorkWithAbort(signal, work) {
225
+ function racePromptWorkWithAbort(signal, work, registerCleanup) {
207
226
  if (signal == null) try {
208
227
  return work();
209
228
  } catch (error) {
@@ -217,6 +236,7 @@ function racePromptWorkWithAbort(signal, work) {
217
236
  };
218
237
  const cleanup = () => signal.removeEventListener("abort", onAbort);
219
238
  signal.addEventListener("abort", onAbort, { once: true });
239
+ registerCleanup?.(cleanup);
220
240
  let outcome;
221
241
  try {
222
242
  outcome = work();
@@ -309,7 +329,7 @@ function createPromptAdapter(adapter) {
309
329
  if (!isDerivedPromptConfig(config)) resolvedConfig = config;
310
330
  else {
311
331
  const source = promptedParser.dependencyMetadata?.source;
312
- const resolved = await racePromptWorkWithAbort(signal, () => resolveDerivedPromptConfig(config, exec, source?.sourceId, source?.metavar, signal));
332
+ const resolved = await racePromptWorkWithAbort(signal, () => resolveDerivedPromptConfig(config, exec, source?.sourceId, source?.metavar, signal, adapter));
313
333
  if (!resolved.ok) return {
314
334
  success: false,
315
335
  error: resolved.error
package/dist/index.d.cts CHANGED
@@ -76,6 +76,15 @@ interface PromptExecutionContext {
76
76
  /** Signal supplied through the prompt's shared options, when present. */
77
77
  readonly signal?: AbortSignal;
78
78
  }
79
+ /**
80
+ * Context for an adapter's pending derived configuration indicator.
81
+ *
82
+ * @since 1.4.0
83
+ */
84
+ interface PromptPendingContext {
85
+ /** Signal supplied through the prompt's shared options, when present. */
86
+ readonly signal?: AbortSignal;
87
+ }
79
88
  /**
80
89
  * Prompt adapter used by {@link createPromptAdapter}.
81
90
  *
@@ -89,6 +98,27 @@ interface PromptExecutionContext {
89
98
  * @since 1.2.0
90
99
  */
91
100
  interface PromptAdapter<TConfig> {
101
+ /**
102
+ * Presents a pending state while a derived configuration resolver runs.
103
+ *
104
+ * Called only after dependencies and declared defaults are available, never
105
+ * for static configurations or skipped prompt fallbacks. Invoke `work()`
106
+ * and preserve its value or rejection. Repeated calls share one promise;
107
+ * `work()` never throws synchronously. Resolver failures become parse
108
+ * failures after this hook sees the rejection. The hook's own errors
109
+ * propagate unchanged, with cancellation taking precedence.
110
+ *
111
+ * Finish the indicator before returning. Clean it up on abort even when the
112
+ * resolver ignores the signal, and avoid terminal writes on late settlement.
113
+ *
114
+ * @typeParam T Configuration produced by the resolver.
115
+ * @param work Runs the resolver, rejecting promptly on abort.
116
+ * @param context Optional abort signal for this resolution.
117
+ * @returns The configuration produced by `work()`.
118
+ * @throws Any resolver, cancellation, or indicator failure.
119
+ * @since 1.4.0
120
+ */
121
+ readonly whilePending?: <T>(work: () => Promise<T>, context: PromptPendingContext) => Promise<T>;
92
122
  /**
93
123
  * Executes the library-specific prompt.
94
124
  *
@@ -359,4 +389,4 @@ type PromptConfigInput<TConfig, TValue> = (TConfig & PromptCondition<TValue>) |
359
389
  */
360
390
  declare function createPromptAdapter<TConfig>(adapter: PromptAdapter<TConfig>): <M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, config: PromptConfigInput<TConfig, TValue>, options?: PromptOptions<NoInfer<TValue>>) => FluentParser<"async", TValue, TState>;
361
391
  //#endregion
362
- export { DerivePromptConfigContext, DerivePromptConfigNoDepsContext, DerivePromptConfigNoDepsOptions, DerivePromptConfigOptions, DerivePromptConfigsContext, DerivePromptConfigsOptions, DerivedPromptConfig, PromptAdapter, PromptCondition, PromptConfigInput, PromptExecutionContext, PromptOptions, PromptValidator, createPromptAdapter, derivePromptConfig, isDerivedPromptConfig };
392
+ export { DerivePromptConfigContext, DerivePromptConfigNoDepsContext, DerivePromptConfigNoDepsOptions, DerivePromptConfigOptions, DerivePromptConfigsContext, DerivePromptConfigsOptions, DerivedPromptConfig, PromptAdapter, PromptCondition, PromptConfigInput, PromptExecutionContext, PromptOptions, PromptPendingContext, PromptValidator, createPromptAdapter, derivePromptConfig, isDerivedPromptConfig };
package/dist/index.d.ts CHANGED
@@ -76,6 +76,15 @@ interface PromptExecutionContext {
76
76
  /** Signal supplied through the prompt's shared options, when present. */
77
77
  readonly signal?: AbortSignal;
78
78
  }
79
+ /**
80
+ * Context for an adapter's pending derived configuration indicator.
81
+ *
82
+ * @since 1.4.0
83
+ */
84
+ interface PromptPendingContext {
85
+ /** Signal supplied through the prompt's shared options, when present. */
86
+ readonly signal?: AbortSignal;
87
+ }
79
88
  /**
80
89
  * Prompt adapter used by {@link createPromptAdapter}.
81
90
  *
@@ -89,6 +98,27 @@ interface PromptExecutionContext {
89
98
  * @since 1.2.0
90
99
  */
91
100
  interface PromptAdapter<TConfig> {
101
+ /**
102
+ * Presents a pending state while a derived configuration resolver runs.
103
+ *
104
+ * Called only after dependencies and declared defaults are available, never
105
+ * for static configurations or skipped prompt fallbacks. Invoke `work()`
106
+ * and preserve its value or rejection. Repeated calls share one promise;
107
+ * `work()` never throws synchronously. Resolver failures become parse
108
+ * failures after this hook sees the rejection. The hook's own errors
109
+ * propagate unchanged, with cancellation taking precedence.
110
+ *
111
+ * Finish the indicator before returning. Clean it up on abort even when the
112
+ * resolver ignores the signal, and avoid terminal writes on late settlement.
113
+ *
114
+ * @typeParam T Configuration produced by the resolver.
115
+ * @param work Runs the resolver, rejecting promptly on abort.
116
+ * @param context Optional abort signal for this resolution.
117
+ * @returns The configuration produced by `work()`.
118
+ * @throws Any resolver, cancellation, or indicator failure.
119
+ * @since 1.4.0
120
+ */
121
+ readonly whilePending?: <T>(work: () => Promise<T>, context: PromptPendingContext) => Promise<T>;
92
122
  /**
93
123
  * Executes the library-specific prompt.
94
124
  *
@@ -359,4 +389,4 @@ type PromptConfigInput<TConfig, TValue> = (TConfig & PromptCondition<TValue>) |
359
389
  */
360
390
  declare function createPromptAdapter<TConfig>(adapter: PromptAdapter<TConfig>): <M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, config: PromptConfigInput<TConfig, TValue>, options?: PromptOptions<NoInfer<TValue>>) => FluentParser<"async", TValue, TState>;
361
391
  //#endregion
362
- export { DerivePromptConfigContext, DerivePromptConfigNoDepsContext, DerivePromptConfigNoDepsOptions, DerivePromptConfigOptions, DerivePromptConfigsContext, DerivePromptConfigsOptions, DerivedPromptConfig, PromptAdapter, PromptCondition, PromptConfigInput, PromptExecutionContext, PromptOptions, PromptValidator, createPromptAdapter, derivePromptConfig, isDerivedPromptConfig };
392
+ export { DerivePromptConfigContext, DerivePromptConfigNoDepsContext, DerivePromptConfigNoDepsOptions, DerivePromptConfigOptions, DerivePromptConfigsContext, DerivePromptConfigsOptions, DerivedPromptConfig, PromptAdapter, PromptCondition, PromptConfigInput, PromptExecutionContext, PromptOptions, PromptPendingContext, PromptValidator, createPromptAdapter, derivePromptConfig, isDerivedPromptConfig };
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ function describeThrown(error) {
71
71
  * (synchronous throws and rejections alike) become prompt failures so
72
72
  * the scheduler treats them like a cancelled prompt.
73
73
  */
74
- async function resolveDerivedPromptConfig(config, exec, ownSourceId, ownLabel, signal) {
74
+ async function resolveDerivedPromptConfig(config, exec, ownSourceId, ownLabel, signal, adapter) {
75
75
  const runtime = exec?.dependencyRuntime;
76
76
  const ids = config.dependencyIds;
77
77
  if (runtime != null) {
@@ -119,16 +119,35 @@ async function resolveDerivedPromptConfig(config, exec, ownSourceId, ownLabel, s
119
119
  usedDefaults[index] = true;
120
120
  }
121
121
  }
122
+ const context = signal === void 0 ? {} : { signal };
123
+ let pending;
124
+ let resolverFailure;
125
+ let disposeWork;
126
+ const work = () => {
127
+ if (pending == null) {
128
+ pending = racePromptWorkWithAbort(signal, () => Promise.resolve(config.resolve(values$1, usedDefaults, context)), (dispose) => {
129
+ disposeWork = dispose;
130
+ });
131
+ pending.then(void 0, (error) => {
132
+ resolverFailure = { error };
133
+ });
134
+ }
135
+ return pending;
136
+ };
122
137
  try {
123
138
  return {
124
139
  ok: true,
125
- config: await config.resolve(values$1, usedDefaults, signal === void 0 ? {} : { signal })
140
+ config: await (adapter.whilePending == null ? work() : adapter.whilePending(work, context))
126
141
  };
127
142
  } catch (error) {
143
+ throwIfPromptAborted(signal);
144
+ if (resolverFailure == null || !Object.is(resolverFailure.error, error)) throw error;
128
145
  return {
129
146
  ok: false,
130
147
  error: message`Prompt configuration resolution failed: ${describeThrown(error)}`
131
148
  };
149
+ } finally {
150
+ disposeWork?.();
132
151
  }
133
152
  }
134
153
  function shouldDeferPrompt(parser, state, exec) {
@@ -180,7 +199,7 @@ function unwrapCompleteResult(result) {
180
199
  function throwIfPromptAborted(signal) {
181
200
  if (signal?.aborted === true) throw signal.reason;
182
201
  }
183
- function racePromptWorkWithAbort(signal, work) {
202
+ function racePromptWorkWithAbort(signal, work, registerCleanup) {
184
203
  if (signal == null) try {
185
204
  return work();
186
205
  } catch (error) {
@@ -194,6 +213,7 @@ function racePromptWorkWithAbort(signal, work) {
194
213
  };
195
214
  const cleanup = () => signal.removeEventListener("abort", onAbort);
196
215
  signal.addEventListener("abort", onAbort, { once: true });
216
+ registerCleanup?.(cleanup);
197
217
  let outcome;
198
218
  try {
199
219
  outcome = work();
@@ -286,7 +306,7 @@ function createPromptAdapter(adapter) {
286
306
  if (!isDerivedPromptConfig(config)) resolvedConfig = config;
287
307
  else {
288
308
  const source = promptedParser.dependencyMetadata?.source;
289
- const resolved = await racePromptWorkWithAbort(signal, () => resolveDerivedPromptConfig(config, exec, source?.sourceId, source?.metavar, signal));
309
+ const resolved = await racePromptWorkWithAbort(signal, () => resolveDerivedPromptConfig(config, exec, source?.sourceId, source?.metavar, signal, adapter));
290
310
  if (!resolved.ok) return {
291
311
  success: false,
292
312
  error: resolved.error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/prompt",
3
- "version": "1.4.0-dev.2723",
3
+ "version": "1.4.0-dev.2726",
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.4.0-dev.2723+56e838af"
63
+ "@optique/core": "1.4.0-dev.2726+dfa3c577"
64
64
  },
65
65
  "devDependencies": {
66
- "@optique/config": "1.4.0-dev.2723+56e838af",
67
- "@optique/env": "1.4.0-dev.2723+56e838af",
68
- "@optique/run": "1.4.0-dev.2723+56e838af",
66
+ "@optique/config": "1.4.0-dev.2726+dfa3c577",
67
+ "@optique/env": "1.4.0-dev.2726+dfa3c577",
68
+ "@optique/run": "1.4.0-dev.2726+dfa3c577",
69
69
  "@types/node": "^24.0.0",
70
70
  "fast-check": "^4.7.0",
71
71
  "tsdown": "^0.13.0",