@optique/zod 1.0.0-dev.1431 → 1.0.0-dev.1436

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
@@ -23,6 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  const __optique_core_message = __toESM(require("@optique/core/message"));
25
25
  const __optique_core_nonempty = __toESM(require("@optique/core/nonempty"));
26
+ const zod = __toESM(require("zod"));
26
27
 
27
28
  //#region src/index.ts
28
29
  /**
@@ -39,6 +40,114 @@ function isZodAsyncError(error) {
39
40
  if (error.message === "Async refinement encountered during synchronous parse operation. Use .parseAsync instead." || error.message === "Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead." || error.message === "Synchronous parse encountered promise.") return true;
40
41
  return false;
41
42
  }
43
+ const BOOL_TRUE_LITERALS = [
44
+ "true",
45
+ "1",
46
+ "yes",
47
+ "on"
48
+ ];
49
+ const BOOL_FALSE_LITERALS = [
50
+ "false",
51
+ "0",
52
+ "no",
53
+ "off"
54
+ ];
55
+ /**
56
+ * Analyzes whether the given Zod schema represents a boolean type,
57
+ * unwrapping all known Zod wrappers. Also determines whether it is
58
+ * safe to expose `choices` and `suggest()` — wrappers that can narrow
59
+ * the accepted domain (effects, catch) suppress choice exposure.
60
+ */
61
+ function analyzeBooleanSchema(schema) {
62
+ const result = analyzeBooleanInner(schema, true, /* @__PURE__ */ new WeakSet());
63
+ if (!result.isBoolean) return {
64
+ isBoolean: false,
65
+ exposeChoices: false,
66
+ isCoerced: false
67
+ };
68
+ return result;
69
+ }
70
+ function analyzeBooleanInner(schema, canExposeChoices, visited) {
71
+ if (visited.has(schema)) return {
72
+ isBoolean: false,
73
+ exposeChoices: false,
74
+ isCoerced: false
75
+ };
76
+ visited.add(schema);
77
+ const def = schema._def;
78
+ if (!def) return {
79
+ isBoolean: false,
80
+ exposeChoices: false,
81
+ isCoerced: false
82
+ };
83
+ const typeName = def.typeName ?? def.type;
84
+ if (typeName === "ZodBoolean" || typeName === "boolean") {
85
+ const hasCustomChecks = Array.isArray(def.checks) && def.checks.some((c) => c.kind === "custom" || c.type === "custom" || c._zod?.def?.check === "custom");
86
+ return {
87
+ isBoolean: true,
88
+ exposeChoices: canExposeChoices && !hasCustomChecks,
89
+ isCoerced: def.coerce === true
90
+ };
91
+ }
92
+ if (typeName === "ZodOptional" || typeName === "optional" || typeName === "ZodNullable" || typeName === "nullable" || typeName === "ZodDefault" || typeName === "default" || typeName === "ZodReadonly" || typeName === "readonly" || typeName === "prefault" || typeName === "nonoptional") {
93
+ const innerType = def.innerType;
94
+ if (innerType != null) return analyzeBooleanInner(innerType, canExposeChoices, visited);
95
+ }
96
+ if (typeName === "ZodLazy" || typeName === "lazy") {
97
+ if (typeof def.getter === "function") return analyzeBooleanInner(def.getter(), canExposeChoices, visited);
98
+ }
99
+ if (typeName === "ZodEffects" || typeName === "effects") {
100
+ if (def.effect?.type === "preprocess") return {
101
+ isBoolean: false,
102
+ exposeChoices: false,
103
+ isCoerced: false
104
+ };
105
+ const innerSchema = def.schema;
106
+ if (innerSchema != null) return analyzeBooleanInner(innerSchema, false, visited);
107
+ }
108
+ if (typeName === "ZodCatch" || typeName === "catch") {
109
+ const innerType = def.innerType;
110
+ if (innerType != null) return analyzeBooleanInner(innerType, false, visited);
111
+ }
112
+ if (typeName === "ZodBranded" || typeName === "branded") {
113
+ const innerType = def.innerType ?? (typeof def.type === "object" && def.type != null ? def.type : void 0);
114
+ if (innerType != null) return analyzeBooleanInner(innerType, false, visited);
115
+ }
116
+ if (typeName === "pipe" || typeName === "ZodPipeline") {
117
+ const inSchema = def.in;
118
+ if (inSchema != null) return analyzeBooleanInner(inSchema, false, visited);
119
+ const innerType = def.innerType;
120
+ if (innerType != null) return analyzeBooleanInner(innerType, false, visited);
121
+ }
122
+ if (typeName === "pipeline") {
123
+ const innerType = def.innerType;
124
+ if (innerType != null) return analyzeBooleanInner(innerType, false, visited);
125
+ }
126
+ return {
127
+ isBoolean: false,
128
+ exposeChoices: false,
129
+ isCoerced: false
130
+ };
131
+ }
132
+ /**
133
+ * Pre-converts a CLI string input to an actual boolean value using
134
+ * CLI-friendly literals (true/false, 1/0, yes/no, on/off).
135
+ */
136
+ function preConvertBoolean(input) {
137
+ const normalized = input.trim().toLowerCase();
138
+ if (BOOL_TRUE_LITERALS.includes(normalized)) return {
139
+ success: true,
140
+ value: true
141
+ };
142
+ if (BOOL_FALSE_LITERALS.includes(normalized)) return {
143
+ success: true,
144
+ value: false
145
+ };
146
+ return {
147
+ success: false,
148
+ error: __optique_core_message.message`Invalid Boolean value: ${input}. Expected one of ${(0, __optique_core_message.valueSet)([...BOOL_TRUE_LITERALS, ...BOOL_FALSE_LITERALS], { locale: "en-US" })}.`
149
+ };
150
+ }
42
151
  /**
43
152
  * Infers an appropriate metavar string from a Zod schema.
44
153
  *
@@ -249,14 +358,86 @@ function inferChoices(schema) {
249
358
  * operations that cannot be executed synchronously.
250
359
  * @since 0.7.0
251
360
  */
252
- function zod(schema, options = {}) {
361
+ function zod$1(schema, options = {}) {
253
362
  const choices = inferChoices(schema);
254
- const metavar = options.metavar ?? inferMetavar(schema);
363
+ const boolInfo = analyzeBooleanSchema(schema);
364
+ const metavar = options.metavar ?? (boolInfo.isBoolean ? "BOOLEAN" : inferMetavar(schema));
255
365
  (0, __optique_core_nonempty.ensureNonEmptyString)(metavar);
366
+ function doSafeParse(input, rawInput) {
367
+ let result;
368
+ try {
369
+ result = schema.safeParse(input);
370
+ } catch (error) {
371
+ if (error instanceof Error && isZodAsyncError(error)) throw new TypeError("Async Zod schemas (e.g., async refinements) are not supported by zod(). Use synchronous schemas instead.");
372
+ throw error;
373
+ }
374
+ if (result.success) return {
375
+ success: true,
376
+ value: result.data
377
+ };
378
+ if (options.errors?.zodError) return {
379
+ success: false,
380
+ error: typeof options.errors.zodError === "function" ? options.errors.zodError(result.error, rawInput) : options.errors.zodError
381
+ };
382
+ const zodModule = schema;
383
+ if (typeof zodModule.constructor?.prettifyError === "function") try {
384
+ const pretty = zodModule.constructor.prettifyError(result.error);
385
+ return {
386
+ success: false,
387
+ error: __optique_core_message.message`${pretty}`
388
+ };
389
+ } catch {}
390
+ const firstError = result.error.issues[0];
391
+ return {
392
+ success: false,
393
+ error: __optique_core_message.message`${firstError?.message ?? "Validation failed"}`
394
+ };
395
+ }
396
+ /**
397
+ * Handles a failed boolean literal pre-conversion.
398
+ *
399
+ * - *Non-coerced* (`z.boolean()`): falls through to `doSafeParse`
400
+ * so that catch/default, custom errors, and async detection all
401
+ * work. This is safe because `safeParse(string)` fails at the
402
+ * type level before any refinements execute.
403
+ * - *Coerced* (`z.coerce.boolean()`): runs the lazy async probe
404
+ * (if not yet completed), then returns the pre-conversion error
405
+ * or delegates to the custom `zodError` callback.
406
+ */
407
+ function handleBooleanLiteralError(boolResult, rawInput) {
408
+ if (!boolInfo.isCoerced) return doSafeParse(rawInput, rawInput);
409
+ if (options.errors?.zodError) {
410
+ if (typeof options.errors.zodError !== "function") return {
411
+ success: false,
412
+ error: options.errors.zodError
413
+ };
414
+ const zodError = new zod.ZodError([{
415
+ code: "invalid_type",
416
+ expected: "boolean",
417
+ message: `Invalid Boolean value: ${rawInput}`,
418
+ path: []
419
+ }]);
420
+ return {
421
+ success: false,
422
+ error: options.errors.zodError(zodError, rawInput)
423
+ };
424
+ }
425
+ return boolResult;
426
+ }
256
427
  const parser = {
257
428
  $mode: "sync",
258
429
  metavar,
259
- ...choices != null && choices.length > 0 ? {
430
+ ...boolInfo.exposeChoices ? {
431
+ choices: Object.freeze([true, false]),
432
+ suggest(prefix) {
433
+ const allLiterals = [...BOOL_TRUE_LITERALS, ...BOOL_FALSE_LITERALS];
434
+ const normalizedPrefix = prefix.toLowerCase();
435
+ return allLiterals.filter((lit) => lit.startsWith(normalizedPrefix)).map((lit) => ({
436
+ kind: "literal",
437
+ text: lit
438
+ }));
439
+ }
440
+ } : choices != null && choices.length > 0 ? {
260
441
  choices: Object.freeze(choices),
261
442
  *suggest(prefix) {
262
443
  for (const c of choices) if (c.startsWith(prefix)) yield {
@@ -266,34 +447,12 @@ function zod(schema, options = {}) {
266
447
  }
267
448
  } : {},
268
449
  parse(input) {
269
- let result;
270
- try {
271
- result = schema.safeParse(input);
272
- } catch (error) {
273
- if (error instanceof Error && isZodAsyncError(error)) throw new TypeError("Async Zod schemas (e.g., async refinements) are not supported by zod(). Use synchronous schemas instead.");
274
- throw error;
450
+ if (boolInfo.isBoolean) {
451
+ const boolResult = preConvertBoolean(input);
452
+ if (!boolResult.success) return handleBooleanLiteralError(boolResult, input);
453
+ return doSafeParse(boolResult.value, input);
275
454
  }
276
- if (result.success) return {
277
- success: true,
278
- value: result.data
279
- };
280
- if (options.errors?.zodError) return {
281
- success: false,
282
- error: typeof options.errors.zodError === "function" ? options.errors.zodError(result.error, input) : options.errors.zodError
283
- };
284
- const zodModule = schema;
285
- if (typeof zodModule.constructor?.prettifyError === "function") try {
286
- const pretty = zodModule.constructor.prettifyError(result.error);
287
- return {
288
- success: false,
289
- error: __optique_core_message.message`${pretty}`
290
- };
291
- } catch {}
292
- const firstError = result.error.issues[0];
293
- return {
294
- success: false,
295
- error: __optique_core_message.message`${firstError?.message ?? "Validation failed"}`
296
- };
455
+ return doSafeParse(input, input);
297
456
  },
298
457
  format(value) {
299
458
  if (options.format) return options.format(value);
@@ -313,4 +472,4 @@ function zod(schema, options = {}) {
313
472
  }
314
473
 
315
474
  //#endregion
316
- exports.zod = zod;
475
+ exports.zod = zod$1;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Message } from "@optique/core/message";
2
- import { NonEmptyString, ValueParser } from "@optique/core/valueparser";
3
2
  import { z } from "zod";
3
+ import { NonEmptyString, ValueParser } from "@optique/core/valueparser";
4
4
 
5
5
  //#region src/index.d.ts
6
6
 
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { message } from "@optique/core/message";
1
+ import { message, valueSet } from "@optique/core/message";
2
2
  import { ensureNonEmptyString } from "@optique/core/nonempty";
3
+ import { ZodError } from "zod";
3
4
 
4
5
  //#region src/index.ts
5
6
  /**
@@ -16,6 +17,114 @@ function isZodAsyncError(error) {
16
17
  if (error.message === "Async refinement encountered during synchronous parse operation. Use .parseAsync instead." || error.message === "Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead." || error.message === "Synchronous parse encountered promise.") return true;
17
18
  return false;
18
19
  }
20
+ const BOOL_TRUE_LITERALS = [
21
+ "true",
22
+ "1",
23
+ "yes",
24
+ "on"
25
+ ];
26
+ const BOOL_FALSE_LITERALS = [
27
+ "false",
28
+ "0",
29
+ "no",
30
+ "off"
31
+ ];
32
+ /**
33
+ * Analyzes whether the given Zod schema represents a boolean type,
34
+ * unwrapping all known Zod wrappers. Also determines whether it is
35
+ * safe to expose `choices` and `suggest()` — wrappers that can narrow
36
+ * the accepted domain (effects, catch) suppress choice exposure.
37
+ */
38
+ function analyzeBooleanSchema(schema) {
39
+ const result = analyzeBooleanInner(schema, true, /* @__PURE__ */ new WeakSet());
40
+ if (!result.isBoolean) return {
41
+ isBoolean: false,
42
+ exposeChoices: false,
43
+ isCoerced: false
44
+ };
45
+ return result;
46
+ }
47
+ function analyzeBooleanInner(schema, canExposeChoices, visited) {
48
+ if (visited.has(schema)) return {
49
+ isBoolean: false,
50
+ exposeChoices: false,
51
+ isCoerced: false
52
+ };
53
+ visited.add(schema);
54
+ const def = schema._def;
55
+ if (!def) return {
56
+ isBoolean: false,
57
+ exposeChoices: false,
58
+ isCoerced: false
59
+ };
60
+ const typeName = def.typeName ?? def.type;
61
+ if (typeName === "ZodBoolean" || typeName === "boolean") {
62
+ const hasCustomChecks = Array.isArray(def.checks) && def.checks.some((c) => c.kind === "custom" || c.type === "custom" || c._zod?.def?.check === "custom");
63
+ return {
64
+ isBoolean: true,
65
+ exposeChoices: canExposeChoices && !hasCustomChecks,
66
+ isCoerced: def.coerce === true
67
+ };
68
+ }
69
+ if (typeName === "ZodOptional" || typeName === "optional" || typeName === "ZodNullable" || typeName === "nullable" || typeName === "ZodDefault" || typeName === "default" || typeName === "ZodReadonly" || typeName === "readonly" || typeName === "prefault" || typeName === "nonoptional") {
70
+ const innerType = def.innerType;
71
+ if (innerType != null) return analyzeBooleanInner(innerType, canExposeChoices, visited);
72
+ }
73
+ if (typeName === "ZodLazy" || typeName === "lazy") {
74
+ if (typeof def.getter === "function") return analyzeBooleanInner(def.getter(), canExposeChoices, visited);
75
+ }
76
+ if (typeName === "ZodEffects" || typeName === "effects") {
77
+ if (def.effect?.type === "preprocess") return {
78
+ isBoolean: false,
79
+ exposeChoices: false,
80
+ isCoerced: false
81
+ };
82
+ const innerSchema = def.schema;
83
+ if (innerSchema != null) return analyzeBooleanInner(innerSchema, false, visited);
84
+ }
85
+ if (typeName === "ZodCatch" || typeName === "catch") {
86
+ const innerType = def.innerType;
87
+ if (innerType != null) return analyzeBooleanInner(innerType, false, visited);
88
+ }
89
+ if (typeName === "ZodBranded" || typeName === "branded") {
90
+ const innerType = def.innerType ?? (typeof def.type === "object" && def.type != null ? def.type : void 0);
91
+ if (innerType != null) return analyzeBooleanInner(innerType, false, visited);
92
+ }
93
+ if (typeName === "pipe" || typeName === "ZodPipeline") {
94
+ const inSchema = def.in;
95
+ if (inSchema != null) return analyzeBooleanInner(inSchema, false, visited);
96
+ const innerType = def.innerType;
97
+ if (innerType != null) return analyzeBooleanInner(innerType, false, visited);
98
+ }
99
+ if (typeName === "pipeline") {
100
+ const innerType = def.innerType;
101
+ if (innerType != null) return analyzeBooleanInner(innerType, false, visited);
102
+ }
103
+ return {
104
+ isBoolean: false,
105
+ exposeChoices: false,
106
+ isCoerced: false
107
+ };
108
+ }
109
+ /**
110
+ * Pre-converts a CLI string input to an actual boolean value using
111
+ * CLI-friendly literals (true/false, 1/0, yes/no, on/off).
112
+ */
113
+ function preConvertBoolean(input) {
114
+ const normalized = input.trim().toLowerCase();
115
+ if (BOOL_TRUE_LITERALS.includes(normalized)) return {
116
+ success: true,
117
+ value: true
118
+ };
119
+ if (BOOL_FALSE_LITERALS.includes(normalized)) return {
120
+ success: true,
121
+ value: false
122
+ };
123
+ return {
124
+ success: false,
125
+ error: message`Invalid Boolean value: ${input}. Expected one of ${valueSet([...BOOL_TRUE_LITERALS, ...BOOL_FALSE_LITERALS], { locale: "en-US" })}.`
126
+ };
127
+ }
19
128
  /**
20
129
  * Infers an appropriate metavar string from a Zod schema.
21
130
  *
@@ -228,12 +337,84 @@ function inferChoices(schema) {
228
337
  */
229
338
  function zod(schema, options = {}) {
230
339
  const choices = inferChoices(schema);
231
- const metavar = options.metavar ?? inferMetavar(schema);
340
+ const boolInfo = analyzeBooleanSchema(schema);
341
+ const metavar = options.metavar ?? (boolInfo.isBoolean ? "BOOLEAN" : inferMetavar(schema));
232
342
  ensureNonEmptyString(metavar);
343
+ function doSafeParse(input, rawInput) {
344
+ let result;
345
+ try {
346
+ result = schema.safeParse(input);
347
+ } catch (error) {
348
+ if (error instanceof Error && isZodAsyncError(error)) throw new TypeError("Async Zod schemas (e.g., async refinements) are not supported by zod(). Use synchronous schemas instead.");
349
+ throw error;
350
+ }
351
+ if (result.success) return {
352
+ success: true,
353
+ value: result.data
354
+ };
355
+ if (options.errors?.zodError) return {
356
+ success: false,
357
+ error: typeof options.errors.zodError === "function" ? options.errors.zodError(result.error, rawInput) : options.errors.zodError
358
+ };
359
+ const zodModule = schema;
360
+ if (typeof zodModule.constructor?.prettifyError === "function") try {
361
+ const pretty = zodModule.constructor.prettifyError(result.error);
362
+ return {
363
+ success: false,
364
+ error: message`${pretty}`
365
+ };
366
+ } catch {}
367
+ const firstError = result.error.issues[0];
368
+ return {
369
+ success: false,
370
+ error: message`${firstError?.message ?? "Validation failed"}`
371
+ };
372
+ }
373
+ /**
374
+ * Handles a failed boolean literal pre-conversion.
375
+ *
376
+ * - *Non-coerced* (`z.boolean()`): falls through to `doSafeParse`
377
+ * so that catch/default, custom errors, and async detection all
378
+ * work. This is safe because `safeParse(string)` fails at the
379
+ * type level before any refinements execute.
380
+ * - *Coerced* (`z.coerce.boolean()`): runs the lazy async probe
381
+ * (if not yet completed), then returns the pre-conversion error
382
+ * or delegates to the custom `zodError` callback.
383
+ */
384
+ function handleBooleanLiteralError(boolResult, rawInput) {
385
+ if (!boolInfo.isCoerced) return doSafeParse(rawInput, rawInput);
386
+ if (options.errors?.zodError) {
387
+ if (typeof options.errors.zodError !== "function") return {
388
+ success: false,
389
+ error: options.errors.zodError
390
+ };
391
+ const zodError = new ZodError([{
392
+ code: "invalid_type",
393
+ expected: "boolean",
394
+ message: `Invalid Boolean value: ${rawInput}`,
395
+ path: []
396
+ }]);
397
+ return {
398
+ success: false,
399
+ error: options.errors.zodError(zodError, rawInput)
400
+ };
401
+ }
402
+ return boolResult;
403
+ }
233
404
  const parser = {
234
405
  $mode: "sync",
235
406
  metavar,
236
- ...choices != null && choices.length > 0 ? {
407
+ ...boolInfo.exposeChoices ? {
408
+ choices: Object.freeze([true, false]),
409
+ suggest(prefix) {
410
+ const allLiterals = [...BOOL_TRUE_LITERALS, ...BOOL_FALSE_LITERALS];
411
+ const normalizedPrefix = prefix.toLowerCase();
412
+ return allLiterals.filter((lit) => lit.startsWith(normalizedPrefix)).map((lit) => ({
413
+ kind: "literal",
414
+ text: lit
415
+ }));
416
+ }
417
+ } : choices != null && choices.length > 0 ? {
237
418
  choices: Object.freeze(choices),
238
419
  *suggest(prefix) {
239
420
  for (const c of choices) if (c.startsWith(prefix)) yield {
@@ -243,34 +424,12 @@ function zod(schema, options = {}) {
243
424
  }
244
425
  } : {},
245
426
  parse(input) {
246
- let result;
247
- try {
248
- result = schema.safeParse(input);
249
- } catch (error) {
250
- if (error instanceof Error && isZodAsyncError(error)) throw new TypeError("Async Zod schemas (e.g., async refinements) are not supported by zod(). Use synchronous schemas instead.");
251
- throw error;
427
+ if (boolInfo.isBoolean) {
428
+ const boolResult = preConvertBoolean(input);
429
+ if (!boolResult.success) return handleBooleanLiteralError(boolResult, input);
430
+ return doSafeParse(boolResult.value, input);
252
431
  }
253
- if (result.success) return {
254
- success: true,
255
- value: result.data
256
- };
257
- if (options.errors?.zodError) return {
258
- success: false,
259
- error: typeof options.errors.zodError === "function" ? options.errors.zodError(result.error, input) : options.errors.zodError
260
- };
261
- const zodModule = schema;
262
- if (typeof zodModule.constructor?.prettifyError === "function") try {
263
- const pretty = zodModule.constructor.prettifyError(result.error);
264
- return {
265
- success: false,
266
- error: message`${pretty}`
267
- };
268
- } catch {}
269
- const firstError = result.error.issues[0];
270
- return {
271
- success: false,
272
- error: message`${firstError?.message ?? "Validation failed"}`
273
- };
432
+ return doSafeParse(input, input);
274
433
  },
275
434
  format(value) {
276
435
  if (options.format) return options.format(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/zod",
3
- "version": "1.0.0-dev.1431+53a2612f",
3
+ "version": "1.0.0-dev.1436+4ad8f445",
4
4
  "description": "Zod value parsers for Optique",
5
5
  "keywords": [
6
6
  "CLI",
@@ -57,7 +57,7 @@
57
57
  "zod": "^3.25.0 || ^4.0.0"
58
58
  },
59
59
  "dependencies": {
60
- "@optique/core": "1.0.0-dev.1431+53a2612f"
60
+ "@optique/core": "1.0.0-dev.1436+4ad8f445"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/node": "^20.19.9",