@optique/valibot 1.0.0-dev.908 → 1.0.0
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 +21 -8
- package/dist/index.cjs +247 -14
- package/dist/index.d.cts +39 -9
- package/dist/index.d.ts +39 -9
- package/dist/index.js +247 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -45,7 +45,9 @@ import * as v from "valibot";
|
|
|
45
45
|
|
|
46
46
|
const cli = run(
|
|
47
47
|
object({
|
|
48
|
-
email: option("--email",
|
|
48
|
+
email: option("--email",
|
|
49
|
+
valibot(v.pipe(v.string(), v.email()), { placeholder: "" }),
|
|
50
|
+
),
|
|
49
51
|
}),
|
|
50
52
|
);
|
|
51
53
|
|
|
@@ -73,7 +75,9 @@ import { option } from "@optique/core/primitives";
|
|
|
73
75
|
import { valibot } from "@optique/valibot";
|
|
74
76
|
import * as v from "valibot";
|
|
75
77
|
|
|
76
|
-
const email = option("--email",
|
|
78
|
+
const email = option("--email",
|
|
79
|
+
valibot(v.pipe(v.string(), v.email()), { placeholder: "" }),
|
|
80
|
+
);
|
|
77
81
|
~~~~
|
|
78
82
|
|
|
79
83
|
### URL validation
|
|
@@ -83,7 +87,9 @@ import { option } from "@optique/core/primitives";
|
|
|
83
87
|
import { valibot } from "@optique/valibot";
|
|
84
88
|
import * as v from "valibot";
|
|
85
89
|
|
|
86
|
-
const url = option("--url",
|
|
90
|
+
const url = option("--url",
|
|
91
|
+
valibot(v.pipe(v.string(), v.url()), { placeholder: "" }),
|
|
92
|
+
);
|
|
87
93
|
~~~~
|
|
88
94
|
|
|
89
95
|
### Port numbers with range validation
|
|
@@ -105,7 +111,7 @@ const port = option("-p", "--port",
|
|
|
105
111
|
v.integer(),
|
|
106
112
|
v.minValue(1024),
|
|
107
113
|
v.maxValue(65535)
|
|
108
|
-
))
|
|
114
|
+
), { placeholder: 0 }),
|
|
109
115
|
);
|
|
110
116
|
~~~~
|
|
111
117
|
|
|
@@ -117,7 +123,8 @@ import { valibot } from "@optique/valibot";
|
|
|
117
123
|
import * as v from "valibot";
|
|
118
124
|
|
|
119
125
|
const logLevel = option("--log-level",
|
|
120
|
-
valibot(v.picklist(["debug", "info", "warn", "error"])
|
|
126
|
+
valibot(v.picklist(["debug", "info", "warn", "error"]),
|
|
127
|
+
{ placeholder: "debug" }),
|
|
121
128
|
);
|
|
122
129
|
~~~~
|
|
123
130
|
|
|
@@ -129,7 +136,8 @@ import { valibot } from "@optique/valibot";
|
|
|
129
136
|
import * as v from "valibot";
|
|
130
137
|
|
|
131
138
|
const startDate = argument(
|
|
132
|
-
valibot(v.pipe(v.string(), v.transform((s: string) => new Date(s)))
|
|
139
|
+
valibot(v.pipe(v.string(), v.transform((s: string) => new Date(s))),
|
|
140
|
+
{ placeholder: new Date(0) }),
|
|
133
141
|
);
|
|
134
142
|
~~~~
|
|
135
143
|
|
|
@@ -146,6 +154,7 @@ import { message } from "@optique/core/message";
|
|
|
146
154
|
import * as v from "valibot";
|
|
147
155
|
|
|
148
156
|
const email = option("--email", valibot(v.pipe(v.string(), v.email()), {
|
|
157
|
+
placeholder: "",
|
|
149
158
|
metavar: "EMAIL",
|
|
150
159
|
errors: {
|
|
151
160
|
valibotError: (issues, input) =>
|
|
@@ -169,7 +178,9 @@ import { valibot } from "@optique/valibot";
|
|
|
169
178
|
import * as v from "valibot";
|
|
170
179
|
|
|
171
180
|
// ✅ Correct
|
|
172
|
-
const port = option("-p",
|
|
181
|
+
const port = option("-p",
|
|
182
|
+
valibot(v.pipe(v.string(), v.transform(Number)), { placeholder: 0 }),
|
|
183
|
+
);
|
|
173
184
|
|
|
174
185
|
// ❌ Won't work (CLI arguments are always strings)
|
|
175
186
|
// const port = option("-p", valibot(v.number()));
|
|
@@ -187,7 +198,9 @@ import * as v from "valibot";
|
|
|
187
198
|
|
|
188
199
|
// ❌ Not supported
|
|
189
200
|
const email = option("--email",
|
|
190
|
-
valibot(v.pipeAsync(v.string(),
|
|
201
|
+
valibot(v.pipeAsync(v.string(),
|
|
202
|
+
v.checkAsync(async (val) => await checkDB(val))),
|
|
203
|
+
{ placeholder: "" }),
|
|
191
204
|
);
|
|
192
205
|
~~~~
|
|
193
206
|
|
package/dist/index.cjs
CHANGED
|
@@ -22,10 +22,153 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
24
|
const __optique_core_message = __toESM(require("@optique/core/message"));
|
|
25
|
+
const __optique_core_nonempty = __toESM(require("@optique/core/nonempty"));
|
|
25
26
|
const valibot = __toESM(require("valibot"));
|
|
26
27
|
|
|
27
28
|
//#region src/index.ts
|
|
28
29
|
/**
|
|
30
|
+
* Valibot transformation action types that are known to be non-rejecting and
|
|
31
|
+
* type-preserving (they transform a string into a string without ever adding
|
|
32
|
+
* issues). All other transformation types (transform, raw_transform,
|
|
33
|
+
* parse_json, to_number, to_boolean, etc.) may reject input or change the
|
|
34
|
+
* value type.
|
|
35
|
+
*/
|
|
36
|
+
const SAFE_TRANSFORMATION_TYPES = new Set([
|
|
37
|
+
"trim",
|
|
38
|
+
"to_lower_case",
|
|
39
|
+
"to_upper_case",
|
|
40
|
+
"normalize",
|
|
41
|
+
"to_min_value",
|
|
42
|
+
"to_max_value",
|
|
43
|
+
"trim_start",
|
|
44
|
+
"trim_end",
|
|
45
|
+
"readonly",
|
|
46
|
+
"brand",
|
|
47
|
+
"flavor"
|
|
48
|
+
]);
|
|
49
|
+
/**
|
|
50
|
+
* Checks whether a schema synchronously accepts every possible input value.
|
|
51
|
+
* This includes:
|
|
52
|
+
* - `v.unknown()`, `v.any()` (accept everything regardless of input type)
|
|
53
|
+
* - Bare `v.string()` without a pipe (accepts every string)
|
|
54
|
+
* - Any wrapper with a `wrapped` field pointing to a catch-all schema
|
|
55
|
+
* (e.g., `v.optional()`, `v.nullable()`, `v.nonOptional()`, etc.)
|
|
56
|
+
*
|
|
57
|
+
* Piped schemas are considered catch-all only when the base type is
|
|
58
|
+
* `string`/`unknown`/`any` and every pipe action is a non-rejecting
|
|
59
|
+
* transformation (not a validation or nested schema).
|
|
60
|
+
*
|
|
61
|
+
* @param afterTransform When true, only type-agnostic catch-alls
|
|
62
|
+
* (`v.unknown()`, `v.any()`) are recognized. String-based catch-alls
|
|
63
|
+
* are not trusted since the input type may no longer be a string.
|
|
64
|
+
*/
|
|
65
|
+
function isCatchAllSchema(schema, afterTransform = false) {
|
|
66
|
+
const s = schema;
|
|
67
|
+
if (s.async) return false;
|
|
68
|
+
if ("fallback" in s) return true;
|
|
69
|
+
if (s.type === "unknown" || s.type === "any") {
|
|
70
|
+
if (!s.pipe) return true;
|
|
71
|
+
return s.pipe.slice(1).every((action) => {
|
|
72
|
+
const a = action;
|
|
73
|
+
if (a.kind === "validation") return false;
|
|
74
|
+
if (a.kind === "schema") return isCatchAllSchema(action, afterTransform);
|
|
75
|
+
return SAFE_TRANSFORMATION_TYPES.has(a.type ?? "");
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (!afterTransform && s.type === "string") {
|
|
79
|
+
if (!s.pipe) return true;
|
|
80
|
+
return s.pipe.slice(1).every((action) => {
|
|
81
|
+
const a = action;
|
|
82
|
+
if (a.kind === "validation") return false;
|
|
83
|
+
if (a.kind === "schema") return isCatchAllSchema(action, afterTransform);
|
|
84
|
+
return SAFE_TRANSFORMATION_TYPES.has(a.type ?? "");
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (s.wrapped && !s.pipe) return isCatchAllSchema(s.wrapped, afterTransform);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Recursively checks whether a Valibot schema contains any async parts
|
|
92
|
+
* (e.g., `pipeAsync`, `checkAsync`). Wrapper schemas such as `optional()`,
|
|
93
|
+
* `nullable()`, `nullish()`, and `union()` keep `async === false` on the
|
|
94
|
+
* outer layer even when they wrap async inner schemas, so a shallow check
|
|
95
|
+
* on the top-level `async` property is not sufficient.
|
|
96
|
+
*
|
|
97
|
+
* Known limitations:
|
|
98
|
+
* - `v.variant()` arms are treated like union arms, but the catch-all
|
|
99
|
+
* detection does not recognize object-shaped variant arms. Variant
|
|
100
|
+
* schemas with async arms after a broad discriminator will be
|
|
101
|
+
* conservatively rejected.
|
|
102
|
+
* - `v.lazy()` schemas are not inspected because the getter depends on
|
|
103
|
+
* actual parse input, making static analysis unreliable.
|
|
104
|
+
*
|
|
105
|
+
* @param afterTransform When true, a preceding `v.transform()` may have
|
|
106
|
+
* changed the value type. Container members become reachable and
|
|
107
|
+
* string-based union catch-all arms are no longer trusted.
|
|
108
|
+
*/
|
|
109
|
+
function containsAsyncSchema(schema, visited = /* @__PURE__ */ new WeakMap(), afterTransform = false) {
|
|
110
|
+
const prev = visited.get(schema);
|
|
111
|
+
if (prev !== void 0 && (prev || !afterTransform)) return false;
|
|
112
|
+
visited.set(schema, (prev ?? false) || afterTransform);
|
|
113
|
+
const s = schema;
|
|
114
|
+
if (s.async) return true;
|
|
115
|
+
if (s.wrapped && !s.pipe) return containsAsyncSchema(s.wrapped, visited, afterTransform);
|
|
116
|
+
if (s.options && Array.isArray(s.options)) {
|
|
117
|
+
if (s.type === "union") for (const option of s.options) {
|
|
118
|
+
if (typeof option !== "object" || option == null) continue;
|
|
119
|
+
if (isCatchAllSchema(option, afterTransform)) break;
|
|
120
|
+
if (containsAsyncSchema(option, visited, afterTransform)) return true;
|
|
121
|
+
}
|
|
122
|
+
else if (s.type === "variant") {
|
|
123
|
+
if (afterTransform) {
|
|
124
|
+
for (const option of s.options) if (typeof option === "object" && option != null) {
|
|
125
|
+
if (containsAsyncSchema(option, visited, true)) return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else for (const option of s.options) if (typeof option === "object" && option != null) {
|
|
129
|
+
if (containsAsyncSchema(option, visited, afterTransform)) return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (s.pipe && Array.isArray(s.pipe)) {
|
|
133
|
+
let seenTransform = afterTransform;
|
|
134
|
+
for (const action of s.pipe) {
|
|
135
|
+
if (action.async) return true;
|
|
136
|
+
const a = action;
|
|
137
|
+
if (a.kind === "transformation" && !SAFE_TRANSFORMATION_TYPES.has(a.type ?? "")) seenTransform = true;
|
|
138
|
+
if (a.kind === "schema") {
|
|
139
|
+
if (containsAsyncSchema(action, visited, seenTransform)) return true;
|
|
140
|
+
if (!seenTransform) {
|
|
141
|
+
const innerPipe = action.pipe;
|
|
142
|
+
if (innerPipe && Array.isArray(innerPipe)) for (const innerAction of innerPipe) {
|
|
143
|
+
const ia = innerAction;
|
|
144
|
+
if (ia.kind === "transformation" && !SAFE_TRANSFORMATION_TYPES.has(ia.type ?? "")) {
|
|
145
|
+
seenTransform = true;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (afterTransform) {
|
|
154
|
+
if (s.entries) {
|
|
155
|
+
for (const entry of Object.values(s.entries)) if (containsAsyncSchema(entry, visited, true)) return true;
|
|
156
|
+
}
|
|
157
|
+
if (s.item && containsAsyncSchema(s.item, visited, true)) return true;
|
|
158
|
+
if (s.items && Array.isArray(s.items)) {
|
|
159
|
+
for (const item of s.items) if (containsAsyncSchema(item, visited, true)) return true;
|
|
160
|
+
}
|
|
161
|
+
if (s.key && typeof s.key === "object" && containsAsyncSchema(s.key, visited, true)) return true;
|
|
162
|
+
if (s.value && containsAsyncSchema(s.value, visited, true)) return true;
|
|
163
|
+
if (s.rest && containsAsyncSchema(s.rest, visited, true)) return true;
|
|
164
|
+
if (s.type === "promise") {
|
|
165
|
+
const promiseInner = schema.message;
|
|
166
|
+
if (typeof promiseInner === "object" && promiseInner != null && "kind" in promiseInner && containsAsyncSchema(promiseInner, visited, true)) return true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
29
172
|
* Infers an appropriate metavar string from a Valibot schema.
|
|
30
173
|
*
|
|
31
174
|
* This function analyzes the Valibot schema's internal structure to determine
|
|
@@ -83,8 +226,15 @@ function inferMetavar(schema) {
|
|
|
83
226
|
if (schemaType === "boolean") return "BOOLEAN";
|
|
84
227
|
if (schemaType === "date") return "DATE";
|
|
85
228
|
if (schemaType === "picklist") return "CHOICE";
|
|
86
|
-
if (schemaType === "literal")
|
|
87
|
-
|
|
229
|
+
if (schemaType === "literal") {
|
|
230
|
+
if (inferChoices(schema) != null) return "CHOICE";
|
|
231
|
+
return "VALUE";
|
|
232
|
+
}
|
|
233
|
+
if (schemaType === "union") {
|
|
234
|
+
if (inferChoices(schema) != null) return "CHOICE";
|
|
235
|
+
return "VALUE";
|
|
236
|
+
}
|
|
237
|
+
if (schemaType === "variant") return "VALUE";
|
|
88
238
|
if (schemaType === "optional" || schemaType === "nullable" || schemaType === "nullish") {
|
|
89
239
|
const wrapped = internalSchema.wrapped;
|
|
90
240
|
if (wrapped) return inferMetavar(wrapped);
|
|
@@ -92,6 +242,50 @@ function inferMetavar(schema) {
|
|
|
92
242
|
return "VALUE";
|
|
93
243
|
}
|
|
94
244
|
/**
|
|
245
|
+
* Extracts valid choices from a Valibot schema that represents a fixed set of
|
|
246
|
+
* values (picklist, literal, or union of literals).
|
|
247
|
+
*
|
|
248
|
+
* @param schema A Valibot schema to analyze.
|
|
249
|
+
* @returns An array of string representations of valid choices, or `undefined`
|
|
250
|
+
* if the schema does not represent a fixed set of values.
|
|
251
|
+
*/
|
|
252
|
+
function inferChoices(schema) {
|
|
253
|
+
const internalSchema = schema;
|
|
254
|
+
const schemaType = internalSchema.type;
|
|
255
|
+
if (!schemaType) return void 0;
|
|
256
|
+
if (schemaType === "picklist") {
|
|
257
|
+
const options = internalSchema.options;
|
|
258
|
+
if (Array.isArray(options)) {
|
|
259
|
+
const result = [];
|
|
260
|
+
for (const opt of options) if (typeof opt === "string") result.push(opt);
|
|
261
|
+
else return void 0;
|
|
262
|
+
return result.length > 0 ? result : void 0;
|
|
263
|
+
}
|
|
264
|
+
return void 0;
|
|
265
|
+
}
|
|
266
|
+
if (schemaType === "literal") {
|
|
267
|
+
const value = internalSchema.literal;
|
|
268
|
+
if (typeof value === "string") return [value];
|
|
269
|
+
return void 0;
|
|
270
|
+
}
|
|
271
|
+
if (schemaType === "union") {
|
|
272
|
+
const options = internalSchema.options;
|
|
273
|
+
if (!Array.isArray(options)) return void 0;
|
|
274
|
+
const allChoices = /* @__PURE__ */ new Set();
|
|
275
|
+
for (const opt of options) if (typeof opt === "object" && opt != null && "type" in opt) {
|
|
276
|
+
const sub = inferChoices(opt);
|
|
277
|
+
if (sub == null) return void 0;
|
|
278
|
+
for (const choice of sub) allChoices.add(choice);
|
|
279
|
+
} else return void 0;
|
|
280
|
+
return allChoices.size > 0 ? [...allChoices] : void 0;
|
|
281
|
+
}
|
|
282
|
+
if (schemaType === "optional" || schemaType === "nullable" || schemaType === "nullish") {
|
|
283
|
+
const wrapped = internalSchema.wrapped;
|
|
284
|
+
if (wrapped) return inferChoices(wrapped);
|
|
285
|
+
}
|
|
286
|
+
return void 0;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
95
289
|
* Creates a value parser from a Valibot schema.
|
|
96
290
|
*
|
|
97
291
|
* This parser validates CLI argument strings using Valibot schemas, enabling
|
|
@@ -108,7 +302,8 @@ function inferMetavar(schema) {
|
|
|
108
302
|
*
|
|
109
303
|
* @template T The output type of the Valibot schema.
|
|
110
304
|
* @param schema A Valibot schema to validate input against.
|
|
111
|
-
* @param options
|
|
305
|
+
* @param options Configuration for the parser, including a required
|
|
306
|
+
* `placeholder` value used during deferred prompt resolution.
|
|
112
307
|
* @returns A value parser that validates inputs using the provided schema.
|
|
113
308
|
*
|
|
114
309
|
* @example Basic string validation
|
|
@@ -117,7 +312,9 @@ function inferMetavar(schema) {
|
|
|
117
312
|
* import { valibot } from "@optique/valibot";
|
|
118
313
|
* import { option } from "@optique/core/primitives";
|
|
119
314
|
*
|
|
120
|
-
* const email = option("--email",
|
|
315
|
+
* const email = option("--email",
|
|
316
|
+
* valibot(v.pipe(v.string(), v.email()), { placeholder: "" }),
|
|
317
|
+
* );
|
|
121
318
|
* ```
|
|
122
319
|
*
|
|
123
320
|
* @example Number validation with pipeline
|
|
@@ -134,8 +331,8 @@ function inferMetavar(schema) {
|
|
|
134
331
|
* v.number(),
|
|
135
332
|
* v.integer(),
|
|
136
333
|
* v.minValue(1024),
|
|
137
|
-
* v.maxValue(65535)
|
|
138
|
-
* ))
|
|
334
|
+
* v.maxValue(65535),
|
|
335
|
+
* ), { placeholder: 1024 }),
|
|
139
336
|
* );
|
|
140
337
|
* ```
|
|
141
338
|
*
|
|
@@ -146,7 +343,9 @@ function inferMetavar(schema) {
|
|
|
146
343
|
* import { option } from "@optique/core/primitives";
|
|
147
344
|
*
|
|
148
345
|
* const logLevel = option("--log-level",
|
|
149
|
-
* valibot(v.picklist(["debug", "info", "warn", "error"])
|
|
346
|
+
* valibot(v.picklist(["debug", "info", "warn", "error"]), {
|
|
347
|
+
* placeholder: "debug",
|
|
348
|
+
* }),
|
|
150
349
|
* );
|
|
151
350
|
* ```
|
|
152
351
|
*
|
|
@@ -159,23 +358,46 @@ function inferMetavar(schema) {
|
|
|
159
358
|
*
|
|
160
359
|
* const email = option("--email",
|
|
161
360
|
* valibot(v.pipe(v.string(), v.email()), {
|
|
361
|
+
* placeholder: "",
|
|
162
362
|
* metavar: "EMAIL",
|
|
163
363
|
* errors: {
|
|
164
364
|
* valibotError: (issues, input) =>
|
|
165
365
|
* message`Please provide a valid email address, got ${input}.`
|
|
166
|
-
* }
|
|
167
|
-
* })
|
|
366
|
+
* },
|
|
367
|
+
* }),
|
|
168
368
|
* );
|
|
169
369
|
* ```
|
|
170
370
|
*
|
|
371
|
+
* @throws {TypeError} If `options` is missing, not an object, or does not
|
|
372
|
+
* include `placeholder`.
|
|
373
|
+
* @throws {TypeError} If the resolved `metavar` is an empty string.
|
|
374
|
+
* @throws {TypeError} If the schema contains async validations that cannot be
|
|
375
|
+
* executed synchronously.
|
|
171
376
|
* @since 0.7.0
|
|
172
377
|
*/
|
|
173
|
-
function valibot$1(schema, options
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
378
|
+
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.");
|
|
381
|
+
if (containsAsyncSchema(schema)) throw new TypeError("Async Valibot schemas (e.g., async validations) are not supported by valibot(). Use synchronous schemas instead.");
|
|
382
|
+
const choices = inferChoices(schema);
|
|
383
|
+
const metavar = options.metavar ?? inferMetavar(schema);
|
|
384
|
+
(0, __optique_core_nonempty.ensureNonEmptyString)(metavar);
|
|
385
|
+
const parser = {
|
|
386
|
+
mode: "sync",
|
|
387
|
+
metavar,
|
|
388
|
+
placeholder: options.placeholder,
|
|
389
|
+
...choices != null && choices.length > 0 ? {
|
|
390
|
+
choices: Object.freeze(choices),
|
|
391
|
+
*suggest(prefix) {
|
|
392
|
+
for (const c of choices) if (c.startsWith(prefix)) yield {
|
|
393
|
+
kind: "literal",
|
|
394
|
+
text: c
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
} : {},
|
|
177
398
|
parse(input) {
|
|
178
399
|
const result = (0, valibot.safeParse)(schema, input);
|
|
400
|
+
if (typeof result.typed !== "boolean") throw new TypeError("Async Valibot schemas (e.g., async validations) are not supported by valibot(). Use synchronous schemas instead.");
|
|
179
401
|
if (result.success) return {
|
|
180
402
|
success: true,
|
|
181
403
|
value: result.output
|
|
@@ -191,9 +413,20 @@ function valibot$1(schema, options = {}) {
|
|
|
191
413
|
};
|
|
192
414
|
},
|
|
193
415
|
format(value) {
|
|
194
|
-
return
|
|
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;
|
|
195
427
|
}
|
|
196
428
|
};
|
|
429
|
+
return parser;
|
|
197
430
|
}
|
|
198
431
|
|
|
199
432
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -8,7 +8,7 @@ import * as v from "valibot";
|
|
|
8
8
|
* Options for creating a Valibot value parser.
|
|
9
9
|
* @since 0.7.0
|
|
10
10
|
*/
|
|
11
|
-
interface ValibotParserOptions {
|
|
11
|
+
interface ValibotParserOptions<T = unknown> {
|
|
12
12
|
/**
|
|
13
13
|
* The metavariable name for this parser. This is used in help messages to
|
|
14
14
|
* indicate what kind of value this parser expects. Usually a single
|
|
@@ -16,6 +16,25 @@ interface ValibotParserOptions {
|
|
|
16
16
|
* @default `"VALUE"`
|
|
17
17
|
*/
|
|
18
18
|
readonly metavar?: NonEmptyString;
|
|
19
|
+
/**
|
|
20
|
+
* A phase-one stand-in value of type `T` used during deferred prompt
|
|
21
|
+
* resolution. Because the output type of a Valibot schema cannot be
|
|
22
|
+
* inferred to a concrete default, callers must provide this explicitly.
|
|
23
|
+
* @since 1.0.0
|
|
24
|
+
*/
|
|
25
|
+
readonly placeholder: T;
|
|
26
|
+
/**
|
|
27
|
+
* Custom formatter for displaying parsed values in help messages.
|
|
28
|
+
* When not provided, the default formatter is used: primitives use
|
|
29
|
+
* `String()`, valid `Date` values use `.toISOString()`, and plain
|
|
30
|
+
* objects use `JSON.stringify()`. All other objects (arrays, class
|
|
31
|
+
* instances, etc.) use `String()`.
|
|
32
|
+
*
|
|
33
|
+
* @param value The parsed value to format.
|
|
34
|
+
* @returns A string representation of the value.
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
*/
|
|
37
|
+
readonly format?: (value: T) => string;
|
|
19
38
|
/**
|
|
20
39
|
* Custom error messages for Valibot validation failures.
|
|
21
40
|
*/
|
|
@@ -46,7 +65,8 @@ interface ValibotParserOptions {
|
|
|
46
65
|
*
|
|
47
66
|
* @template T The output type of the Valibot schema.
|
|
48
67
|
* @param schema A Valibot schema to validate input against.
|
|
49
|
-
* @param options
|
|
68
|
+
* @param options Configuration for the parser, including a required
|
|
69
|
+
* `placeholder` value used during deferred prompt resolution.
|
|
50
70
|
* @returns A value parser that validates inputs using the provided schema.
|
|
51
71
|
*
|
|
52
72
|
* @example Basic string validation
|
|
@@ -55,7 +75,9 @@ interface ValibotParserOptions {
|
|
|
55
75
|
* import { valibot } from "@optique/valibot";
|
|
56
76
|
* import { option } from "@optique/core/primitives";
|
|
57
77
|
*
|
|
58
|
-
* const email = option("--email",
|
|
78
|
+
* const email = option("--email",
|
|
79
|
+
* valibot(v.pipe(v.string(), v.email()), { placeholder: "" }),
|
|
80
|
+
* );
|
|
59
81
|
* ```
|
|
60
82
|
*
|
|
61
83
|
* @example Number validation with pipeline
|
|
@@ -72,8 +94,8 @@ interface ValibotParserOptions {
|
|
|
72
94
|
* v.number(),
|
|
73
95
|
* v.integer(),
|
|
74
96
|
* v.minValue(1024),
|
|
75
|
-
* v.maxValue(65535)
|
|
76
|
-
* ))
|
|
97
|
+
* v.maxValue(65535),
|
|
98
|
+
* ), { placeholder: 1024 }),
|
|
77
99
|
* );
|
|
78
100
|
* ```
|
|
79
101
|
*
|
|
@@ -84,7 +106,9 @@ interface ValibotParserOptions {
|
|
|
84
106
|
* import { option } from "@optique/core/primitives";
|
|
85
107
|
*
|
|
86
108
|
* const logLevel = option("--log-level",
|
|
87
|
-
* valibot(v.picklist(["debug", "info", "warn", "error"])
|
|
109
|
+
* valibot(v.picklist(["debug", "info", "warn", "error"]), {
|
|
110
|
+
* placeholder: "debug",
|
|
111
|
+
* }),
|
|
88
112
|
* );
|
|
89
113
|
* ```
|
|
90
114
|
*
|
|
@@ -97,17 +121,23 @@ interface ValibotParserOptions {
|
|
|
97
121
|
*
|
|
98
122
|
* const email = option("--email",
|
|
99
123
|
* valibot(v.pipe(v.string(), v.email()), {
|
|
124
|
+
* placeholder: "",
|
|
100
125
|
* metavar: "EMAIL",
|
|
101
126
|
* errors: {
|
|
102
127
|
* valibotError: (issues, input) =>
|
|
103
128
|
* message`Please provide a valid email address, got ${input}.`
|
|
104
|
-
* }
|
|
105
|
-
* })
|
|
129
|
+
* },
|
|
130
|
+
* }),
|
|
106
131
|
* );
|
|
107
132
|
* ```
|
|
108
133
|
*
|
|
134
|
+
* @throws {TypeError} If `options` is missing, not an object, or does not
|
|
135
|
+
* include `placeholder`.
|
|
136
|
+
* @throws {TypeError} If the resolved `metavar` is an empty string.
|
|
137
|
+
* @throws {TypeError} If the schema contains async validations that cannot be
|
|
138
|
+
* executed synchronously.
|
|
109
139
|
* @since 0.7.0
|
|
110
140
|
*/
|
|
111
|
-
declare function valibot<T>(schema: v.BaseSchema<unknown, T, v.BaseIssue<unknown>>, options
|
|
141
|
+
declare function valibot<T>(schema: v.BaseSchema<unknown, T, v.BaseIssue<unknown>>, options: ValibotParserOptions<T>): ValueParser<"sync", T>;
|
|
112
142
|
//#endregion
|
|
113
143
|
export { ValibotParserOptions, valibot };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { NonEmptyString, ValueParser } from "@optique/core/valueparser";
|
|
|
8
8
|
* Options for creating a Valibot value parser.
|
|
9
9
|
* @since 0.7.0
|
|
10
10
|
*/
|
|
11
|
-
interface ValibotParserOptions {
|
|
11
|
+
interface ValibotParserOptions<T = unknown> {
|
|
12
12
|
/**
|
|
13
13
|
* The metavariable name for this parser. This is used in help messages to
|
|
14
14
|
* indicate what kind of value this parser expects. Usually a single
|
|
@@ -16,6 +16,25 @@ interface ValibotParserOptions {
|
|
|
16
16
|
* @default `"VALUE"`
|
|
17
17
|
*/
|
|
18
18
|
readonly metavar?: NonEmptyString;
|
|
19
|
+
/**
|
|
20
|
+
* A phase-one stand-in value of type `T` used during deferred prompt
|
|
21
|
+
* resolution. Because the output type of a Valibot schema cannot be
|
|
22
|
+
* inferred to a concrete default, callers must provide this explicitly.
|
|
23
|
+
* @since 1.0.0
|
|
24
|
+
*/
|
|
25
|
+
readonly placeholder: T;
|
|
26
|
+
/**
|
|
27
|
+
* Custom formatter for displaying parsed values in help messages.
|
|
28
|
+
* When not provided, the default formatter is used: primitives use
|
|
29
|
+
* `String()`, valid `Date` values use `.toISOString()`, and plain
|
|
30
|
+
* objects use `JSON.stringify()`. All other objects (arrays, class
|
|
31
|
+
* instances, etc.) use `String()`.
|
|
32
|
+
*
|
|
33
|
+
* @param value The parsed value to format.
|
|
34
|
+
* @returns A string representation of the value.
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
*/
|
|
37
|
+
readonly format?: (value: T) => string;
|
|
19
38
|
/**
|
|
20
39
|
* Custom error messages for Valibot validation failures.
|
|
21
40
|
*/
|
|
@@ -46,7 +65,8 @@ interface ValibotParserOptions {
|
|
|
46
65
|
*
|
|
47
66
|
* @template T The output type of the Valibot schema.
|
|
48
67
|
* @param schema A Valibot schema to validate input against.
|
|
49
|
-
* @param options
|
|
68
|
+
* @param options Configuration for the parser, including a required
|
|
69
|
+
* `placeholder` value used during deferred prompt resolution.
|
|
50
70
|
* @returns A value parser that validates inputs using the provided schema.
|
|
51
71
|
*
|
|
52
72
|
* @example Basic string validation
|
|
@@ -55,7 +75,9 @@ interface ValibotParserOptions {
|
|
|
55
75
|
* import { valibot } from "@optique/valibot";
|
|
56
76
|
* import { option } from "@optique/core/primitives";
|
|
57
77
|
*
|
|
58
|
-
* const email = option("--email",
|
|
78
|
+
* const email = option("--email",
|
|
79
|
+
* valibot(v.pipe(v.string(), v.email()), { placeholder: "" }),
|
|
80
|
+
* );
|
|
59
81
|
* ```
|
|
60
82
|
*
|
|
61
83
|
* @example Number validation with pipeline
|
|
@@ -72,8 +94,8 @@ interface ValibotParserOptions {
|
|
|
72
94
|
* v.number(),
|
|
73
95
|
* v.integer(),
|
|
74
96
|
* v.minValue(1024),
|
|
75
|
-
* v.maxValue(65535)
|
|
76
|
-
* ))
|
|
97
|
+
* v.maxValue(65535),
|
|
98
|
+
* ), { placeholder: 1024 }),
|
|
77
99
|
* );
|
|
78
100
|
* ```
|
|
79
101
|
*
|
|
@@ -84,7 +106,9 @@ interface ValibotParserOptions {
|
|
|
84
106
|
* import { option } from "@optique/core/primitives";
|
|
85
107
|
*
|
|
86
108
|
* const logLevel = option("--log-level",
|
|
87
|
-
* valibot(v.picklist(["debug", "info", "warn", "error"])
|
|
109
|
+
* valibot(v.picklist(["debug", "info", "warn", "error"]), {
|
|
110
|
+
* placeholder: "debug",
|
|
111
|
+
* }),
|
|
88
112
|
* );
|
|
89
113
|
* ```
|
|
90
114
|
*
|
|
@@ -97,17 +121,23 @@ interface ValibotParserOptions {
|
|
|
97
121
|
*
|
|
98
122
|
* const email = option("--email",
|
|
99
123
|
* valibot(v.pipe(v.string(), v.email()), {
|
|
124
|
+
* placeholder: "",
|
|
100
125
|
* metavar: "EMAIL",
|
|
101
126
|
* errors: {
|
|
102
127
|
* valibotError: (issues, input) =>
|
|
103
128
|
* message`Please provide a valid email address, got ${input}.`
|
|
104
|
-
* }
|
|
105
|
-
* })
|
|
129
|
+
* },
|
|
130
|
+
* }),
|
|
106
131
|
* );
|
|
107
132
|
* ```
|
|
108
133
|
*
|
|
134
|
+
* @throws {TypeError} If `options` is missing, not an object, or does not
|
|
135
|
+
* include `placeholder`.
|
|
136
|
+
* @throws {TypeError} If the resolved `metavar` is an empty string.
|
|
137
|
+
* @throws {TypeError} If the schema contains async validations that cannot be
|
|
138
|
+
* executed synchronously.
|
|
109
139
|
* @since 0.7.0
|
|
110
140
|
*/
|
|
111
|
-
declare function valibot<T>(schema: v.BaseSchema<unknown, T, v.BaseIssue<unknown>>, options
|
|
141
|
+
declare function valibot<T>(schema: v.BaseSchema<unknown, T, v.BaseIssue<unknown>>, options: ValibotParserOptions<T>): ValueParser<"sync", T>;
|
|
112
142
|
//#endregion
|
|
113
143
|
export { ValibotParserOptions, valibot };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,151 @@
|
|
|
1
1
|
import { message } from "@optique/core/message";
|
|
2
|
+
import { ensureNonEmptyString } from "@optique/core/nonempty";
|
|
2
3
|
import { safeParse } from "valibot";
|
|
3
4
|
|
|
4
5
|
//#region src/index.ts
|
|
5
6
|
/**
|
|
7
|
+
* Valibot transformation action types that are known to be non-rejecting and
|
|
8
|
+
* type-preserving (they transform a string into a string without ever adding
|
|
9
|
+
* issues). All other transformation types (transform, raw_transform,
|
|
10
|
+
* parse_json, to_number, to_boolean, etc.) may reject input or change the
|
|
11
|
+
* value type.
|
|
12
|
+
*/
|
|
13
|
+
const SAFE_TRANSFORMATION_TYPES = new Set([
|
|
14
|
+
"trim",
|
|
15
|
+
"to_lower_case",
|
|
16
|
+
"to_upper_case",
|
|
17
|
+
"normalize",
|
|
18
|
+
"to_min_value",
|
|
19
|
+
"to_max_value",
|
|
20
|
+
"trim_start",
|
|
21
|
+
"trim_end",
|
|
22
|
+
"readonly",
|
|
23
|
+
"brand",
|
|
24
|
+
"flavor"
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Checks whether a schema synchronously accepts every possible input value.
|
|
28
|
+
* This includes:
|
|
29
|
+
* - `v.unknown()`, `v.any()` (accept everything regardless of input type)
|
|
30
|
+
* - Bare `v.string()` without a pipe (accepts every string)
|
|
31
|
+
* - Any wrapper with a `wrapped` field pointing to a catch-all schema
|
|
32
|
+
* (e.g., `v.optional()`, `v.nullable()`, `v.nonOptional()`, etc.)
|
|
33
|
+
*
|
|
34
|
+
* Piped schemas are considered catch-all only when the base type is
|
|
35
|
+
* `string`/`unknown`/`any` and every pipe action is a non-rejecting
|
|
36
|
+
* transformation (not a validation or nested schema).
|
|
37
|
+
*
|
|
38
|
+
* @param afterTransform When true, only type-agnostic catch-alls
|
|
39
|
+
* (`v.unknown()`, `v.any()`) are recognized. String-based catch-alls
|
|
40
|
+
* are not trusted since the input type may no longer be a string.
|
|
41
|
+
*/
|
|
42
|
+
function isCatchAllSchema(schema, afterTransform = false) {
|
|
43
|
+
const s = schema;
|
|
44
|
+
if (s.async) return false;
|
|
45
|
+
if ("fallback" in s) return true;
|
|
46
|
+
if (s.type === "unknown" || s.type === "any") {
|
|
47
|
+
if (!s.pipe) return true;
|
|
48
|
+
return s.pipe.slice(1).every((action) => {
|
|
49
|
+
const a = action;
|
|
50
|
+
if (a.kind === "validation") return false;
|
|
51
|
+
if (a.kind === "schema") return isCatchAllSchema(action, afterTransform);
|
|
52
|
+
return SAFE_TRANSFORMATION_TYPES.has(a.type ?? "");
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (!afterTransform && s.type === "string") {
|
|
56
|
+
if (!s.pipe) return true;
|
|
57
|
+
return s.pipe.slice(1).every((action) => {
|
|
58
|
+
const a = action;
|
|
59
|
+
if (a.kind === "validation") return false;
|
|
60
|
+
if (a.kind === "schema") return isCatchAllSchema(action, afterTransform);
|
|
61
|
+
return SAFE_TRANSFORMATION_TYPES.has(a.type ?? "");
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (s.wrapped && !s.pipe) return isCatchAllSchema(s.wrapped, afterTransform);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Recursively checks whether a Valibot schema contains any async parts
|
|
69
|
+
* (e.g., `pipeAsync`, `checkAsync`). Wrapper schemas such as `optional()`,
|
|
70
|
+
* `nullable()`, `nullish()`, and `union()` keep `async === false` on the
|
|
71
|
+
* outer layer even when they wrap async inner schemas, so a shallow check
|
|
72
|
+
* on the top-level `async` property is not sufficient.
|
|
73
|
+
*
|
|
74
|
+
* Known limitations:
|
|
75
|
+
* - `v.variant()` arms are treated like union arms, but the catch-all
|
|
76
|
+
* detection does not recognize object-shaped variant arms. Variant
|
|
77
|
+
* schemas with async arms after a broad discriminator will be
|
|
78
|
+
* conservatively rejected.
|
|
79
|
+
* - `v.lazy()` schemas are not inspected because the getter depends on
|
|
80
|
+
* actual parse input, making static analysis unreliable.
|
|
81
|
+
*
|
|
82
|
+
* @param afterTransform When true, a preceding `v.transform()` may have
|
|
83
|
+
* changed the value type. Container members become reachable and
|
|
84
|
+
* string-based union catch-all arms are no longer trusted.
|
|
85
|
+
*/
|
|
86
|
+
function containsAsyncSchema(schema, visited = /* @__PURE__ */ new WeakMap(), afterTransform = false) {
|
|
87
|
+
const prev = visited.get(schema);
|
|
88
|
+
if (prev !== void 0 && (prev || !afterTransform)) return false;
|
|
89
|
+
visited.set(schema, (prev ?? false) || afterTransform);
|
|
90
|
+
const s = schema;
|
|
91
|
+
if (s.async) return true;
|
|
92
|
+
if (s.wrapped && !s.pipe) return containsAsyncSchema(s.wrapped, visited, afterTransform);
|
|
93
|
+
if (s.options && Array.isArray(s.options)) {
|
|
94
|
+
if (s.type === "union") for (const option of s.options) {
|
|
95
|
+
if (typeof option !== "object" || option == null) continue;
|
|
96
|
+
if (isCatchAllSchema(option, afterTransform)) break;
|
|
97
|
+
if (containsAsyncSchema(option, visited, afterTransform)) return true;
|
|
98
|
+
}
|
|
99
|
+
else if (s.type === "variant") {
|
|
100
|
+
if (afterTransform) {
|
|
101
|
+
for (const option of s.options) if (typeof option === "object" && option != null) {
|
|
102
|
+
if (containsAsyncSchema(option, visited, true)) return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else for (const option of s.options) if (typeof option === "object" && option != null) {
|
|
106
|
+
if (containsAsyncSchema(option, visited, afterTransform)) return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (s.pipe && Array.isArray(s.pipe)) {
|
|
110
|
+
let seenTransform = afterTransform;
|
|
111
|
+
for (const action of s.pipe) {
|
|
112
|
+
if (action.async) return true;
|
|
113
|
+
const a = action;
|
|
114
|
+
if (a.kind === "transformation" && !SAFE_TRANSFORMATION_TYPES.has(a.type ?? "")) seenTransform = true;
|
|
115
|
+
if (a.kind === "schema") {
|
|
116
|
+
if (containsAsyncSchema(action, visited, seenTransform)) return true;
|
|
117
|
+
if (!seenTransform) {
|
|
118
|
+
const innerPipe = action.pipe;
|
|
119
|
+
if (innerPipe && Array.isArray(innerPipe)) for (const innerAction of innerPipe) {
|
|
120
|
+
const ia = innerAction;
|
|
121
|
+
if (ia.kind === "transformation" && !SAFE_TRANSFORMATION_TYPES.has(ia.type ?? "")) {
|
|
122
|
+
seenTransform = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (afterTransform) {
|
|
131
|
+
if (s.entries) {
|
|
132
|
+
for (const entry of Object.values(s.entries)) if (containsAsyncSchema(entry, visited, true)) return true;
|
|
133
|
+
}
|
|
134
|
+
if (s.item && containsAsyncSchema(s.item, visited, true)) return true;
|
|
135
|
+
if (s.items && Array.isArray(s.items)) {
|
|
136
|
+
for (const item of s.items) if (containsAsyncSchema(item, visited, true)) return true;
|
|
137
|
+
}
|
|
138
|
+
if (s.key && typeof s.key === "object" && containsAsyncSchema(s.key, visited, true)) return true;
|
|
139
|
+
if (s.value && containsAsyncSchema(s.value, visited, true)) return true;
|
|
140
|
+
if (s.rest && containsAsyncSchema(s.rest, visited, true)) return true;
|
|
141
|
+
if (s.type === "promise") {
|
|
142
|
+
const promiseInner = schema.message;
|
|
143
|
+
if (typeof promiseInner === "object" && promiseInner != null && "kind" in promiseInner && containsAsyncSchema(promiseInner, visited, true)) return true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
6
149
|
* Infers an appropriate metavar string from a Valibot schema.
|
|
7
150
|
*
|
|
8
151
|
* This function analyzes the Valibot schema's internal structure to determine
|
|
@@ -60,8 +203,15 @@ function inferMetavar(schema) {
|
|
|
60
203
|
if (schemaType === "boolean") return "BOOLEAN";
|
|
61
204
|
if (schemaType === "date") return "DATE";
|
|
62
205
|
if (schemaType === "picklist") return "CHOICE";
|
|
63
|
-
if (schemaType === "literal")
|
|
64
|
-
|
|
206
|
+
if (schemaType === "literal") {
|
|
207
|
+
if (inferChoices(schema) != null) return "CHOICE";
|
|
208
|
+
return "VALUE";
|
|
209
|
+
}
|
|
210
|
+
if (schemaType === "union") {
|
|
211
|
+
if (inferChoices(schema) != null) return "CHOICE";
|
|
212
|
+
return "VALUE";
|
|
213
|
+
}
|
|
214
|
+
if (schemaType === "variant") return "VALUE";
|
|
65
215
|
if (schemaType === "optional" || schemaType === "nullable" || schemaType === "nullish") {
|
|
66
216
|
const wrapped = internalSchema.wrapped;
|
|
67
217
|
if (wrapped) return inferMetavar(wrapped);
|
|
@@ -69,6 +219,50 @@ function inferMetavar(schema) {
|
|
|
69
219
|
return "VALUE";
|
|
70
220
|
}
|
|
71
221
|
/**
|
|
222
|
+
* Extracts valid choices from a Valibot schema that represents a fixed set of
|
|
223
|
+
* values (picklist, literal, or union of literals).
|
|
224
|
+
*
|
|
225
|
+
* @param schema A Valibot schema to analyze.
|
|
226
|
+
* @returns An array of string representations of valid choices, or `undefined`
|
|
227
|
+
* if the schema does not represent a fixed set of values.
|
|
228
|
+
*/
|
|
229
|
+
function inferChoices(schema) {
|
|
230
|
+
const internalSchema = schema;
|
|
231
|
+
const schemaType = internalSchema.type;
|
|
232
|
+
if (!schemaType) return void 0;
|
|
233
|
+
if (schemaType === "picklist") {
|
|
234
|
+
const options = internalSchema.options;
|
|
235
|
+
if (Array.isArray(options)) {
|
|
236
|
+
const result = [];
|
|
237
|
+
for (const opt of options) if (typeof opt === "string") result.push(opt);
|
|
238
|
+
else return void 0;
|
|
239
|
+
return result.length > 0 ? result : void 0;
|
|
240
|
+
}
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
if (schemaType === "literal") {
|
|
244
|
+
const value = internalSchema.literal;
|
|
245
|
+
if (typeof value === "string") return [value];
|
|
246
|
+
return void 0;
|
|
247
|
+
}
|
|
248
|
+
if (schemaType === "union") {
|
|
249
|
+
const options = internalSchema.options;
|
|
250
|
+
if (!Array.isArray(options)) return void 0;
|
|
251
|
+
const allChoices = /* @__PURE__ */ new Set();
|
|
252
|
+
for (const opt of options) if (typeof opt === "object" && opt != null && "type" in opt) {
|
|
253
|
+
const sub = inferChoices(opt);
|
|
254
|
+
if (sub == null) return void 0;
|
|
255
|
+
for (const choice of sub) allChoices.add(choice);
|
|
256
|
+
} else return void 0;
|
|
257
|
+
return allChoices.size > 0 ? [...allChoices] : void 0;
|
|
258
|
+
}
|
|
259
|
+
if (schemaType === "optional" || schemaType === "nullable" || schemaType === "nullish") {
|
|
260
|
+
const wrapped = internalSchema.wrapped;
|
|
261
|
+
if (wrapped) return inferChoices(wrapped);
|
|
262
|
+
}
|
|
263
|
+
return void 0;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
72
266
|
* Creates a value parser from a Valibot schema.
|
|
73
267
|
*
|
|
74
268
|
* This parser validates CLI argument strings using Valibot schemas, enabling
|
|
@@ -85,7 +279,8 @@ function inferMetavar(schema) {
|
|
|
85
279
|
*
|
|
86
280
|
* @template T The output type of the Valibot schema.
|
|
87
281
|
* @param schema A Valibot schema to validate input against.
|
|
88
|
-
* @param options
|
|
282
|
+
* @param options Configuration for the parser, including a required
|
|
283
|
+
* `placeholder` value used during deferred prompt resolution.
|
|
89
284
|
* @returns A value parser that validates inputs using the provided schema.
|
|
90
285
|
*
|
|
91
286
|
* @example Basic string validation
|
|
@@ -94,7 +289,9 @@ function inferMetavar(schema) {
|
|
|
94
289
|
* import { valibot } from "@optique/valibot";
|
|
95
290
|
* import { option } from "@optique/core/primitives";
|
|
96
291
|
*
|
|
97
|
-
* const email = option("--email",
|
|
292
|
+
* const email = option("--email",
|
|
293
|
+
* valibot(v.pipe(v.string(), v.email()), { placeholder: "" }),
|
|
294
|
+
* );
|
|
98
295
|
* ```
|
|
99
296
|
*
|
|
100
297
|
* @example Number validation with pipeline
|
|
@@ -111,8 +308,8 @@ function inferMetavar(schema) {
|
|
|
111
308
|
* v.number(),
|
|
112
309
|
* v.integer(),
|
|
113
310
|
* v.minValue(1024),
|
|
114
|
-
* v.maxValue(65535)
|
|
115
|
-
* ))
|
|
311
|
+
* v.maxValue(65535),
|
|
312
|
+
* ), { placeholder: 1024 }),
|
|
116
313
|
* );
|
|
117
314
|
* ```
|
|
118
315
|
*
|
|
@@ -123,7 +320,9 @@ function inferMetavar(schema) {
|
|
|
123
320
|
* import { option } from "@optique/core/primitives";
|
|
124
321
|
*
|
|
125
322
|
* const logLevel = option("--log-level",
|
|
126
|
-
* valibot(v.picklist(["debug", "info", "warn", "error"])
|
|
323
|
+
* valibot(v.picklist(["debug", "info", "warn", "error"]), {
|
|
324
|
+
* placeholder: "debug",
|
|
325
|
+
* }),
|
|
127
326
|
* );
|
|
128
327
|
* ```
|
|
129
328
|
*
|
|
@@ -136,23 +335,46 @@ function inferMetavar(schema) {
|
|
|
136
335
|
*
|
|
137
336
|
* const email = option("--email",
|
|
138
337
|
* valibot(v.pipe(v.string(), v.email()), {
|
|
338
|
+
* placeholder: "",
|
|
139
339
|
* metavar: "EMAIL",
|
|
140
340
|
* errors: {
|
|
141
341
|
* valibotError: (issues, input) =>
|
|
142
342
|
* message`Please provide a valid email address, got ${input}.`
|
|
143
|
-
* }
|
|
144
|
-
* })
|
|
343
|
+
* },
|
|
344
|
+
* }),
|
|
145
345
|
* );
|
|
146
346
|
* ```
|
|
147
347
|
*
|
|
348
|
+
* @throws {TypeError} If `options` is missing, not an object, or does not
|
|
349
|
+
* include `placeholder`.
|
|
350
|
+
* @throws {TypeError} If the resolved `metavar` is an empty string.
|
|
351
|
+
* @throws {TypeError} If the schema contains async validations that cannot be
|
|
352
|
+
* executed synchronously.
|
|
148
353
|
* @since 0.7.0
|
|
149
354
|
*/
|
|
150
|
-
function valibot(schema, options
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
355
|
+
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.");
|
|
358
|
+
if (containsAsyncSchema(schema)) throw new TypeError("Async Valibot schemas (e.g., async validations) are not supported by valibot(). Use synchronous schemas instead.");
|
|
359
|
+
const choices = inferChoices(schema);
|
|
360
|
+
const metavar = options.metavar ?? inferMetavar(schema);
|
|
361
|
+
ensureNonEmptyString(metavar);
|
|
362
|
+
const parser = {
|
|
363
|
+
mode: "sync",
|
|
364
|
+
metavar,
|
|
365
|
+
placeholder: options.placeholder,
|
|
366
|
+
...choices != null && choices.length > 0 ? {
|
|
367
|
+
choices: Object.freeze(choices),
|
|
368
|
+
*suggest(prefix) {
|
|
369
|
+
for (const c of choices) if (c.startsWith(prefix)) yield {
|
|
370
|
+
kind: "literal",
|
|
371
|
+
text: c
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
} : {},
|
|
154
375
|
parse(input) {
|
|
155
376
|
const result = safeParse(schema, input);
|
|
377
|
+
if (typeof result.typed !== "boolean") throw new TypeError("Async Valibot schemas (e.g., async validations) are not supported by valibot(). Use synchronous schemas instead.");
|
|
156
378
|
if (result.success) return {
|
|
157
379
|
success: true,
|
|
158
380
|
value: result.output
|
|
@@ -168,9 +390,20 @@ function valibot(schema, options = {}) {
|
|
|
168
390
|
};
|
|
169
391
|
},
|
|
170
392
|
format(value) {
|
|
171
|
-
return
|
|
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;
|
|
172
404
|
}
|
|
173
405
|
};
|
|
406
|
+
return parser;
|
|
174
407
|
}
|
|
175
408
|
|
|
176
409
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/valibot",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
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.0.0
|
|
60
|
+
"@optique/core": "1.0.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "^20.19.9",
|