@optique/valibot 1.1.0-dev.2096 → 1.1.0-dev.2148

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/README.md CHANGED
@@ -142,6 +142,46 @@ const startDate = argument(
142
142
  ~~~~
143
143
 
144
144
 
145
+ Async schemas
146
+ -------------
147
+
148
+ Use `valibotAsync()` when a schema depends on async validations, and run the
149
+ containing parser with `runAsync()` or `await run()`:
150
+
151
+ ~~~~ typescript
152
+ import { runAsync } from "@optique/run";
153
+ import { object } from "@optique/core/constructs";
154
+ import { option } from "@optique/core/primitives";
155
+ import { valibotAsync } from "@optique/valibot";
156
+ import * as v from "valibot";
157
+
158
+ async function checkProject(value: string): Promise<boolean> {
159
+ return await Promise.resolve(value.startsWith("project-"));
160
+ }
161
+
162
+ const parser = object({
163
+ project: option("--project",
164
+ valibotAsync(
165
+ v.pipeAsync(v.string(), v.checkAsync(checkProject, "Unknown project.")),
166
+ { placeholder: "" },
167
+ ),
168
+ ),
169
+ });
170
+
171
+ const cli = await runAsync(parser);
172
+ ~~~~
173
+
174
+ The `valibot()` helper remains synchronous and rejects schemas that require
175
+ Valibot's async parse path. `valibotAsync()` preserves metavar inference,
176
+ choices, suggestions, formatting, and custom errors. Fallback values from
177
+ `bindEnv()` or `bindConfig()` are validated by the same schema before they are
178
+ accepted.
179
+
180
+ Async validation can run during fallback resolution and other repeated parser
181
+ paths, including shell completion requests. Keep remote checks bounded and
182
+ cached when possible.
183
+
184
+
145
185
  Custom error messages
146
186
  ---------------------
147
187
 
@@ -186,25 +226,30 @@ const port = option("-p",
186
226
  // const port = option("-p", valibot(v.number()));
187
227
  ~~~~
188
228
 
189
- ### Async validations are not supported
229
+ ### `valibot()` is synchronous
190
230
 
191
- Optique's `ValueParser.parse()` is synchronous, so async Valibot features like
192
- async validations cannot be supported:
231
+ The `valibot()` helper returns a sync value parser, so async Valibot features
232
+ like `pipeAsync()` require `valibotAsync()`:
193
233
 
194
234
  ~~~~ typescript
195
235
  import { option } from "@optique/core/primitives";
196
- import { valibot } from "@optique/valibot";
236
+ import { valibot, valibotAsync } from "@optique/valibot";
197
237
  import * as v from "valibot";
198
238
 
199
- // ❌ Not supported
200
- const email = option("--email",
239
+ // ❌ Not supported by valibot()
240
+ const syncEmail = option("--email",
201
241
  valibot(v.pipeAsync(v.string(),
202
242
  v.checkAsync(async (val) => await checkDB(val))),
203
243
  { placeholder: "" }),
204
244
  );
205
- ~~~~
206
245
 
207
- If you need async validation, perform it after parsing the CLI arguments.
246
+ // Use valibotAsync() and runAsync()
247
+ const asyncEmail = option("--email",
248
+ valibotAsync(v.pipeAsync(v.string(),
249
+ v.checkAsync(async (val) => await checkDB(val))),
250
+ { placeholder: "" }),
251
+ );
252
+ ~~~~
208
253
 
209
254
 
210
255
  For more resources
package/dist/index.cjs CHANGED
@@ -285,6 +285,23 @@ function inferChoices(schema) {
285
285
  }
286
286
  return void 0;
287
287
  }
288
+ function validateOptions(functionName, options) {
289
+ if (options == null || typeof options !== "object") throw new TypeError(`${functionName}() requires an options object with a placeholder property.`);
290
+ if (!("placeholder" in options)) throw new TypeError(`${functionName}() options must include a placeholder property.`);
291
+ }
292
+ function formatValue(value, format) {
293
+ if (format) return format(value);
294
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? String(value) : value.toISOString();
295
+ if (typeof value !== "object" || value === null) return String(value);
296
+ if (Array.isArray(value)) return String(value);
297
+ const str = String(value);
298
+ if (str !== "[object Object]") return str;
299
+ const proto = Object.getPrototypeOf(value);
300
+ if (proto === Object.prototype || proto === null) try {
301
+ return JSON.stringify(value) ?? str;
302
+ } catch {}
303
+ return str;
304
+ }
288
305
  /**
289
306
  * Creates a value parser from a Valibot schema.
290
307
  *
@@ -376,8 +393,7 @@ function inferChoices(schema) {
376
393
  * @since 0.7.0
377
394
  */
378
395
  function valibot$1(schema, options) {
379
- if (options == null || typeof options !== "object") throw new TypeError("valibot() requires an options object with a placeholder property.");
380
- if (!("placeholder" in options)) throw new TypeError("valibot() options must include a placeholder property.");
396
+ validateOptions("valibot", options);
381
397
  if (containsAsyncSchema(schema)) throw new TypeError("Async Valibot schemas (e.g., async validations) are not supported by valibot(). Use synchronous schemas instead.");
382
398
  const choices = inferChoices(schema);
383
399
  const metavar = options.metavar ?? inferMetavar(schema);
@@ -413,21 +429,76 @@ function valibot$1(schema, options) {
413
429
  };
414
430
  },
415
431
  format(value) {
416
- if (options.format) return options.format(value);
417
- if (value instanceof Date) return Number.isNaN(value.getTime()) ? String(value) : value.toISOString();
418
- if (typeof value !== "object" || value === null) return String(value);
419
- if (Array.isArray(value)) return String(value);
420
- const str = String(value);
421
- if (str !== "[object Object]") return str;
422
- const proto = Object.getPrototypeOf(value);
423
- if (proto === Object.prototype || proto === null) try {
424
- return JSON.stringify(value) ?? str;
425
- } catch {}
426
- return str;
432
+ return formatValue(value, options.format);
433
+ }
434
+ };
435
+ return parser;
436
+ }
437
+ /**
438
+ * Creates an async value parser from a Valibot schema.
439
+ *
440
+ * This parser validates CLI argument strings with Valibot's async safe-parse
441
+ * path, so schemas using `pipeAsync()`, `checkAsync()`, or other async actions
442
+ * can be reused directly in asynchronous Optique parsers.
443
+ *
444
+ * The metavar, choices, suggestions, formatting, and error customization
445
+ * follow the same rules as {@link valibot}. Because the returned parser runs
446
+ * asynchronously, use it with `parseAsync()`, `run()`, or `runAsync()`.
447
+ *
448
+ * @template T The output type of the Valibot schema.
449
+ * @param schema A Valibot schema to validate input against.
450
+ * @param options Configuration for the parser, including a required
451
+ * `placeholder` value used during deferred prompt resolution.
452
+ * @returns An async value parser that validates inputs using the provided
453
+ * schema.
454
+ *
455
+ * @throws {TypeError} If `options` is missing, not an object, or does not
456
+ * include `placeholder`.
457
+ * @throws {TypeError} If the resolved `metavar` is an empty string.
458
+ * @since 1.1.0
459
+ */
460
+ function valibotAsync(schema, options) {
461
+ validateOptions("valibotAsync", options);
462
+ const syncSchema = schema;
463
+ const choices = inferChoices(syncSchema);
464
+ const metavar = options.metavar ?? inferMetavar(syncSchema);
465
+ (0, __optique_core_nonempty.ensureNonEmptyString)(metavar);
466
+ const parser = {
467
+ mode: "async",
468
+ metavar,
469
+ placeholder: options.placeholder,
470
+ ...choices != null && choices.length > 0 ? {
471
+ choices: Object.freeze(choices),
472
+ async *suggest(prefix) {
473
+ for (const c of choices) if (c.startsWith(prefix)) yield {
474
+ kind: "literal",
475
+ text: c
476
+ };
477
+ }
478
+ } : {},
479
+ async parse(input) {
480
+ const result = await (0, valibot.safeParseAsync)(schema, input);
481
+ if (result.success) return {
482
+ success: true,
483
+ value: result.output
484
+ };
485
+ if (options.errors?.valibotError) return {
486
+ success: false,
487
+ error: typeof options.errors.valibotError === "function" ? options.errors.valibotError(result.issues, input) : options.errors.valibotError
488
+ };
489
+ const firstIssue = result.issues[0];
490
+ return {
491
+ success: false,
492
+ error: __optique_core_message.message`${firstIssue?.message ?? "Validation failed"}`
493
+ };
494
+ },
495
+ format(value) {
496
+ return formatValue(value, options.format);
427
497
  }
428
498
  };
429
499
  return parser;
430
500
  }
431
501
 
432
502
  //#endregion
433
- exports.valibot = valibot$1;
503
+ exports.valibot = valibot$1;
504
+ exports.valibotAsync = valibotAsync;
package/dist/index.d.cts CHANGED
@@ -139,5 +139,30 @@ interface ValibotParserOptions<T = unknown> {
139
139
  * @since 0.7.0
140
140
  */
141
141
  declare function valibot<T>(schema: v.BaseSchema<unknown, T, v.BaseIssue<unknown>>, options: ValibotParserOptions<T>): ValueParser<"sync", T>;
142
+ type AnyValibotSchema<T> = v.BaseSchema<unknown, T, v.BaseIssue<unknown>> | v.BaseSchemaAsync<unknown, T, v.BaseIssue<unknown>>;
143
+ /**
144
+ * Creates an async value parser from a Valibot schema.
145
+ *
146
+ * This parser validates CLI argument strings with Valibot's async safe-parse
147
+ * path, so schemas using `pipeAsync()`, `checkAsync()`, or other async actions
148
+ * can be reused directly in asynchronous Optique parsers.
149
+ *
150
+ * The metavar, choices, suggestions, formatting, and error customization
151
+ * follow the same rules as {@link valibot}. Because the returned parser runs
152
+ * asynchronously, use it with `parseAsync()`, `run()`, or `runAsync()`.
153
+ *
154
+ * @template T The output type of the Valibot schema.
155
+ * @param schema A Valibot schema to validate input against.
156
+ * @param options Configuration for the parser, including a required
157
+ * `placeholder` value used during deferred prompt resolution.
158
+ * @returns An async value parser that validates inputs using the provided
159
+ * schema.
160
+ *
161
+ * @throws {TypeError} If `options` is missing, not an object, or does not
162
+ * include `placeholder`.
163
+ * @throws {TypeError} If the resolved `metavar` is an empty string.
164
+ * @since 1.1.0
165
+ */
166
+ declare function valibotAsync<T>(schema: AnyValibotSchema<T>, options: ValibotParserOptions<T>): ValueParser<"async", T>;
142
167
  //#endregion
143
- export { ValibotParserOptions, valibot };
168
+ export { ValibotParserOptions, valibot, valibotAsync };
package/dist/index.d.ts CHANGED
@@ -139,5 +139,30 @@ interface ValibotParserOptions<T = unknown> {
139
139
  * @since 0.7.0
140
140
  */
141
141
  declare function valibot<T>(schema: v.BaseSchema<unknown, T, v.BaseIssue<unknown>>, options: ValibotParserOptions<T>): ValueParser<"sync", T>;
142
+ type AnyValibotSchema<T> = v.BaseSchema<unknown, T, v.BaseIssue<unknown>> | v.BaseSchemaAsync<unknown, T, v.BaseIssue<unknown>>;
143
+ /**
144
+ * Creates an async value parser from a Valibot schema.
145
+ *
146
+ * This parser validates CLI argument strings with Valibot's async safe-parse
147
+ * path, so schemas using `pipeAsync()`, `checkAsync()`, or other async actions
148
+ * can be reused directly in asynchronous Optique parsers.
149
+ *
150
+ * The metavar, choices, suggestions, formatting, and error customization
151
+ * follow the same rules as {@link valibot}. Because the returned parser runs
152
+ * asynchronously, use it with `parseAsync()`, `run()`, or `runAsync()`.
153
+ *
154
+ * @template T The output type of the Valibot schema.
155
+ * @param schema A Valibot schema to validate input against.
156
+ * @param options Configuration for the parser, including a required
157
+ * `placeholder` value used during deferred prompt resolution.
158
+ * @returns An async value parser that validates inputs using the provided
159
+ * schema.
160
+ *
161
+ * @throws {TypeError} If `options` is missing, not an object, or does not
162
+ * include `placeholder`.
163
+ * @throws {TypeError} If the resolved `metavar` is an empty string.
164
+ * @since 1.1.0
165
+ */
166
+ declare function valibotAsync<T>(schema: AnyValibotSchema<T>, options: ValibotParserOptions<T>): ValueParser<"async", T>;
142
167
  //#endregion
143
- export { ValibotParserOptions, valibot };
168
+ export { ValibotParserOptions, valibot, valibotAsync };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { message } from "@optique/core/message";
2
2
  import { ensureNonEmptyString } from "@optique/core/nonempty";
3
- import { safeParse } from "valibot";
3
+ import { safeParse, safeParseAsync } from "valibot";
4
4
 
5
5
  //#region src/index.ts
6
6
  /**
@@ -262,6 +262,23 @@ function inferChoices(schema) {
262
262
  }
263
263
  return void 0;
264
264
  }
265
+ function validateOptions(functionName, options) {
266
+ if (options == null || typeof options !== "object") throw new TypeError(`${functionName}() requires an options object with a placeholder property.`);
267
+ if (!("placeholder" in options)) throw new TypeError(`${functionName}() options must include a placeholder property.`);
268
+ }
269
+ function formatValue(value, format) {
270
+ if (format) return format(value);
271
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? String(value) : value.toISOString();
272
+ if (typeof value !== "object" || value === null) return String(value);
273
+ if (Array.isArray(value)) return String(value);
274
+ const str = String(value);
275
+ if (str !== "[object Object]") return str;
276
+ const proto = Object.getPrototypeOf(value);
277
+ if (proto === Object.prototype || proto === null) try {
278
+ return JSON.stringify(value) ?? str;
279
+ } catch {}
280
+ return str;
281
+ }
265
282
  /**
266
283
  * Creates a value parser from a Valibot schema.
267
284
  *
@@ -353,8 +370,7 @@ function inferChoices(schema) {
353
370
  * @since 0.7.0
354
371
  */
355
372
  function valibot(schema, options) {
356
- if (options == null || typeof options !== "object") throw new TypeError("valibot() requires an options object with a placeholder property.");
357
- if (!("placeholder" in options)) throw new TypeError("valibot() options must include a placeholder property.");
373
+ validateOptions("valibot", options);
358
374
  if (containsAsyncSchema(schema)) throw new TypeError("Async Valibot schemas (e.g., async validations) are not supported by valibot(). Use synchronous schemas instead.");
359
375
  const choices = inferChoices(schema);
360
376
  const metavar = options.metavar ?? inferMetavar(schema);
@@ -390,21 +406,75 @@ function valibot(schema, options) {
390
406
  };
391
407
  },
392
408
  format(value) {
393
- if (options.format) return options.format(value);
394
- if (value instanceof Date) return Number.isNaN(value.getTime()) ? String(value) : value.toISOString();
395
- if (typeof value !== "object" || value === null) return String(value);
396
- if (Array.isArray(value)) return String(value);
397
- const str = String(value);
398
- if (str !== "[object Object]") return str;
399
- const proto = Object.getPrototypeOf(value);
400
- if (proto === Object.prototype || proto === null) try {
401
- return JSON.stringify(value) ?? str;
402
- } catch {}
403
- return str;
409
+ return formatValue(value, options.format);
410
+ }
411
+ };
412
+ return parser;
413
+ }
414
+ /**
415
+ * Creates an async value parser from a Valibot schema.
416
+ *
417
+ * This parser validates CLI argument strings with Valibot's async safe-parse
418
+ * path, so schemas using `pipeAsync()`, `checkAsync()`, or other async actions
419
+ * can be reused directly in asynchronous Optique parsers.
420
+ *
421
+ * The metavar, choices, suggestions, formatting, and error customization
422
+ * follow the same rules as {@link valibot}. Because the returned parser runs
423
+ * asynchronously, use it with `parseAsync()`, `run()`, or `runAsync()`.
424
+ *
425
+ * @template T The output type of the Valibot schema.
426
+ * @param schema A Valibot schema to validate input against.
427
+ * @param options Configuration for the parser, including a required
428
+ * `placeholder` value used during deferred prompt resolution.
429
+ * @returns An async value parser that validates inputs using the provided
430
+ * schema.
431
+ *
432
+ * @throws {TypeError} If `options` is missing, not an object, or does not
433
+ * include `placeholder`.
434
+ * @throws {TypeError} If the resolved `metavar` is an empty string.
435
+ * @since 1.1.0
436
+ */
437
+ function valibotAsync(schema, options) {
438
+ validateOptions("valibotAsync", options);
439
+ const syncSchema = schema;
440
+ const choices = inferChoices(syncSchema);
441
+ const metavar = options.metavar ?? inferMetavar(syncSchema);
442
+ ensureNonEmptyString(metavar);
443
+ const parser = {
444
+ mode: "async",
445
+ metavar,
446
+ placeholder: options.placeholder,
447
+ ...choices != null && choices.length > 0 ? {
448
+ choices: Object.freeze(choices),
449
+ async *suggest(prefix) {
450
+ for (const c of choices) if (c.startsWith(prefix)) yield {
451
+ kind: "literal",
452
+ text: c
453
+ };
454
+ }
455
+ } : {},
456
+ async parse(input) {
457
+ const result = await safeParseAsync(schema, input);
458
+ if (result.success) return {
459
+ success: true,
460
+ value: result.output
461
+ };
462
+ if (options.errors?.valibotError) return {
463
+ success: false,
464
+ error: typeof options.errors.valibotError === "function" ? options.errors.valibotError(result.issues, input) : options.errors.valibotError
465
+ };
466
+ const firstIssue = result.issues[0];
467
+ return {
468
+ success: false,
469
+ error: message`${firstIssue?.message ?? "Validation failed"}`
470
+ };
471
+ },
472
+ format(value) {
473
+ return formatValue(value, options.format);
404
474
  }
405
475
  };
406
476
  return parser;
407
477
  }
408
478
 
409
479
  //#endregion
410
- export { valibot };
480
+ export { valibot, valibotAsync };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/valibot",
3
- "version": "1.1.0-dev.2096",
3
+ "version": "1.1.0-dev.2148",
4
4
  "description": "Valibot value parsers for Optique",
5
5
  "keywords": [
6
6
  "CLI",
@@ -57,7 +57,7 @@
57
57
  "valibot": "^1.2.0"
58
58
  },
59
59
  "dependencies": {
60
- "@optique/core": "1.1.0-dev.2096+8eda4929"
60
+ "@optique/core": "1.1.0-dev.2148+ab56ac96"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/node": "^24.0.0",
@@ -68,9 +68,9 @@
68
68
  "scripts": {
69
69
  "build": "tsdown",
70
70
  "prepublish": "tsdown",
71
- "test": "node --experimental-transform-types --test",
71
+ "test": "node --test",
72
72
  "test:bun": "bun test",
73
73
  "test:deno": "deno test",
74
- "test-all": "tsdown && node --experimental-transform-types --test && bun test && deno test"
74
+ "test-all": "tsdown && node --test && bun test && deno test"
75
75
  }
76
76
  }