valrs 0.1.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 +197 -0
- package/dist/compiler.d.ts +195 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +349 -0
- package/dist/compiler.js.map +1 -0
- package/dist/error.d.ts +415 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +619 -0
- package/dist/error.js.map +1 -0
- package/dist/factory.d.ts +107 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +135 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/primitives.d.ts +99 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +315 -0
- package/dist/primitives.js.map +1 -0
- package/dist/schema.d.ts +710 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +1179 -0
- package/dist/schema.js.map +1 -0
- package/dist/streaming.d.ts +159 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +692 -0
- package/dist/streaming.js.map +1 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/v.d.ts +1382 -0
- package/dist/v.d.ts.map +1 -0
- package/dist/v.js +2396 -0
- package/dist/v.js.map +1 -0
- package/dist/wasm.d.ts +86 -0
- package/dist/wasm.d.ts.map +1 -0
- package/dist/wasm.js +87 -0
- package/dist/wasm.js.map +1 -0
- package/package.json +89 -0
package/dist/v.js
ADDED
|
@@ -0,0 +1,2396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod-compatible fluent API for valrs.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a `v` namespace with schema builders that mirror
|
|
5
|
+
* Zod's API while maintaining Standard Schema compliance.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { v } from 'valrs';
|
|
10
|
+
*
|
|
11
|
+
* // Create schemas
|
|
12
|
+
* const userSchema = v.object({
|
|
13
|
+
* name: v.string(),
|
|
14
|
+
* age: v.number(),
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Parse data
|
|
18
|
+
* const user = userSchema.parse({ name: 'Alice', age: 30 });
|
|
19
|
+
*
|
|
20
|
+
* // Infer types
|
|
21
|
+
* type User = v.infer<typeof userSchema>;
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import { ValSchema, ValArray, ValUnion, ValIntersection, ValPreprocessed, } from './schema';
|
|
25
|
+
import { StringSchema, NumberSchema, BooleanSchema, Int32Schema, Int64Schema, Uint32Schema, Uint64Schema, Float32Schema, Float64Schema, } from './primitives';
|
|
26
|
+
import { success, fail } from './factory';
|
|
27
|
+
import { stream, streamLines, createMockStream, createChunkedStream, } from './streaming';
|
|
28
|
+
export { ValSchema } from './schema';
|
|
29
|
+
export { isSafeParseSuccess, isSafeParseError } from './schema';
|
|
30
|
+
// Error types and functions (Phase 7)
|
|
31
|
+
import { ValError as ValErrorClass, setErrorMap as setErrorMapFn, getErrorMap as getErrorMapFn, resetErrorMap as resetErrorMapFn, getTypeName, createInvalidTypeIssue, createTooSmallIssue, createTooBigIssue, createInvalidStringIssue, createInvalidEnumValueIssue, createInvalidUnionIssue, createUnrecognizedKeysIssue, createCustomIssue, createInvalidLiteralIssue, createNotMultipleOfIssue, createNotFiniteIssue, createInvalidDateIssue, } from './error';
|
|
32
|
+
// Re-export error functions
|
|
33
|
+
export const ValError = ValErrorClass;
|
|
34
|
+
export const setErrorMap = setErrorMapFn;
|
|
35
|
+
export const getErrorMap = getErrorMapFn;
|
|
36
|
+
export const resetErrorMap = resetErrorMapFn;
|
|
37
|
+
export { getTypeName, createInvalidTypeIssue, createTooSmallIssue, createTooBigIssue, createInvalidStringIssue, createInvalidEnumValueIssue, createInvalidUnionIssue, createUnrecognizedKeysIssue, createCustomIssue, createInvalidLiteralIssue, createNotMultipleOfIssue, createNotFiniteIssue, createInvalidDateIssue, };
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Primitive Schema Classes
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Validation Issue Creation
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Creates a validation issue with a Zod-compatible error code.
|
|
46
|
+
* Issues include the code and any additional metadata for proper error formatting.
|
|
47
|
+
*/
|
|
48
|
+
function createIssue(code, message, metadata) {
|
|
49
|
+
return {
|
|
50
|
+
issues: [{ code, message, ...metadata }],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Creates an invalid_type issue for type validation failures.
|
|
55
|
+
*/
|
|
56
|
+
function createTypeIssue(expected, received, message) {
|
|
57
|
+
const receivedType = received === null ? 'null'
|
|
58
|
+
: received === undefined ? 'undefined'
|
|
59
|
+
: Array.isArray(received) ? 'array'
|
|
60
|
+
: typeof received;
|
|
61
|
+
return {
|
|
62
|
+
issues: [{
|
|
63
|
+
code: 'invalid_type',
|
|
64
|
+
expected,
|
|
65
|
+
received: receivedType,
|
|
66
|
+
message: message ?? `Expected ${expected}, received ${receivedType}`,
|
|
67
|
+
}],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Email regex - simplified but covers most common cases */
|
|
71
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
72
|
+
/** URL regex - supports http, https, ftp protocols */
|
|
73
|
+
const URL_REGEX = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i;
|
|
74
|
+
/** UUID v4 regex */
|
|
75
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
76
|
+
/** CUID regex (original format) */
|
|
77
|
+
const CUID_REGEX = /^c[a-z0-9]{24}$/;
|
|
78
|
+
/** CUID2 regex (newer format, variable length) */
|
|
79
|
+
const CUID2_REGEX = /^[a-z][a-z0-9]{23,}$/;
|
|
80
|
+
/** ULID regex (26 characters, Crockford Base32) */
|
|
81
|
+
const ULID_REGEX = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
82
|
+
/** ISO 8601 datetime regex */
|
|
83
|
+
const DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;
|
|
84
|
+
/** IPv4 regex */
|
|
85
|
+
const IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
|
|
86
|
+
/** IPv6 regex (simplified) */
|
|
87
|
+
const IPV6_REGEX = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}$|^[0-9a-fA-F]{1,4}::(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,2}:(?:[0-9a-fA-F]{1,4}:){0,4}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,3}:(?:[0-9a-fA-F]{1,4}:){0,3}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,4}:(?:[0-9a-fA-F]{1,4}:){0,2}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,5}:(?:[0-9a-fA-F]{1,4}:)?[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$/;
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// ValString Class
|
|
90
|
+
// ============================================================================
|
|
91
|
+
/**
|
|
92
|
+
* Schema for string values with validation and transformation methods.
|
|
93
|
+
*
|
|
94
|
+
* Supports chainable methods like `.min()`, `.max()`, `.email()`, `.url()`, etc.
|
|
95
|
+
* Each method returns a new schema instance (immutable).
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const emailSchema = v.string().email();
|
|
100
|
+
* const usernameSchema = v.string().min(3).max(20);
|
|
101
|
+
* const normalizedSchema = v.string().trim().toLowerCase();
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export class ValString extends ValSchema {
|
|
105
|
+
validators;
|
|
106
|
+
transforms;
|
|
107
|
+
constructor(validators = [], transforms = []) {
|
|
108
|
+
const baseValidate = StringSchema['~standard'].validate;
|
|
109
|
+
// Capture validators and transforms for use in closure
|
|
110
|
+
const capturedValidators = validators;
|
|
111
|
+
const capturedTransforms = transforms;
|
|
112
|
+
// Create a wrapped validation function that runs all validators and transforms
|
|
113
|
+
const wrappedValidate = (value) => {
|
|
114
|
+
// First, validate base type
|
|
115
|
+
const baseResult = baseValidate(value);
|
|
116
|
+
if (baseResult.issues !== undefined) {
|
|
117
|
+
return baseResult;
|
|
118
|
+
}
|
|
119
|
+
// At this point we know baseResult.value is defined
|
|
120
|
+
const baseValue = baseResult.value;
|
|
121
|
+
// Apply transforms first
|
|
122
|
+
let transformedValue = baseValue;
|
|
123
|
+
for (let i = 0; i < capturedTransforms.length; i++) {
|
|
124
|
+
const transform = capturedTransforms[i];
|
|
125
|
+
if (transform !== undefined) {
|
|
126
|
+
transformedValue = transform(transformedValue);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Then run all validators on the transformed value
|
|
130
|
+
for (let i = 0; i < capturedValidators.length; i++) {
|
|
131
|
+
const validator = capturedValidators[i];
|
|
132
|
+
if (validator !== undefined) {
|
|
133
|
+
const issue = validator(transformedValue);
|
|
134
|
+
if (issue !== null) {
|
|
135
|
+
return issue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return success(transformedValue);
|
|
140
|
+
};
|
|
141
|
+
super(wrappedValidate, (target) => StringSchema['~standard'].jsonSchema.input({ target }), (target) => StringSchema['~standard'].jsonSchema.output({ target }));
|
|
142
|
+
this.validators = validators;
|
|
143
|
+
this.transforms = transforms;
|
|
144
|
+
this._hasTransforms = validators.length > 0 || transforms.length > 0;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Creates a copy of this schema with additional validators/transforms.
|
|
148
|
+
*/
|
|
149
|
+
clone(additionalValidators = [], additionalTransforms = []) {
|
|
150
|
+
return new ValString([...this.validators, ...additionalValidators], [...this.transforms, ...additionalTransforms]);
|
|
151
|
+
}
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Length Validation Methods
|
|
154
|
+
// ============================================================================
|
|
155
|
+
/**
|
|
156
|
+
* Requires string to be at least `length` characters.
|
|
157
|
+
*/
|
|
158
|
+
min(length, message) {
|
|
159
|
+
return this.clone([
|
|
160
|
+
(v) => v.length >= length
|
|
161
|
+
? null
|
|
162
|
+
: createIssue('too_small', message ?? `String must be at least ${length} character(s)`, {
|
|
163
|
+
type: 'string',
|
|
164
|
+
minimum: length,
|
|
165
|
+
inclusive: true,
|
|
166
|
+
}),
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Requires string to be at most `length` characters.
|
|
171
|
+
*/
|
|
172
|
+
max(length, message) {
|
|
173
|
+
return this.clone([
|
|
174
|
+
(v) => v.length <= length
|
|
175
|
+
? null
|
|
176
|
+
: createIssue('too_big', message ?? `String must be at most ${length} character(s)`, {
|
|
177
|
+
type: 'string',
|
|
178
|
+
maximum: length,
|
|
179
|
+
inclusive: true,
|
|
180
|
+
}),
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Requires string to be exactly `len` characters.
|
|
185
|
+
*/
|
|
186
|
+
length(len, message) {
|
|
187
|
+
return this.clone([
|
|
188
|
+
(v) => v.length === len
|
|
189
|
+
? null
|
|
190
|
+
: createIssue('too_small', message ?? `String must be exactly ${len} character(s)`, {
|
|
191
|
+
type: 'string',
|
|
192
|
+
minimum: len,
|
|
193
|
+
inclusive: true,
|
|
194
|
+
exact: true,
|
|
195
|
+
}),
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Format Validation Methods
|
|
200
|
+
// ============================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Validates that the string is a valid email address.
|
|
203
|
+
*/
|
|
204
|
+
email(message) {
|
|
205
|
+
return this.clone([
|
|
206
|
+
(v) => EMAIL_REGEX.test(v)
|
|
207
|
+
? null
|
|
208
|
+
: createIssue('invalid_email', message ?? 'Invalid email address'),
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Validates that the string is a valid URL.
|
|
213
|
+
*/
|
|
214
|
+
url(message) {
|
|
215
|
+
return this.clone([
|
|
216
|
+
(v) => URL_REGEX.test(v)
|
|
217
|
+
? null
|
|
218
|
+
: createIssue('invalid_url', message ?? 'Invalid URL'),
|
|
219
|
+
]);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Validates that the string is a valid UUID.
|
|
223
|
+
*/
|
|
224
|
+
uuid(message) {
|
|
225
|
+
return this.clone([
|
|
226
|
+
(v) => UUID_REGEX.test(v)
|
|
227
|
+
? null
|
|
228
|
+
: createIssue('invalid_uuid', message ?? 'Invalid UUID'),
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Validates that the string is a valid CUID.
|
|
233
|
+
*/
|
|
234
|
+
cuid(message) {
|
|
235
|
+
return this.clone([
|
|
236
|
+
(v) => CUID_REGEX.test(v)
|
|
237
|
+
? null
|
|
238
|
+
: createIssue('invalid_cuid', message ?? 'Invalid CUID'),
|
|
239
|
+
]);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Validates that the string is a valid CUID2.
|
|
243
|
+
*/
|
|
244
|
+
cuid2(message) {
|
|
245
|
+
return this.clone([
|
|
246
|
+
(v) => CUID2_REGEX.test(v)
|
|
247
|
+
? null
|
|
248
|
+
: createIssue('invalid_cuid2', message ?? 'Invalid CUID2'),
|
|
249
|
+
]);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Validates that the string is a valid ULID.
|
|
253
|
+
*/
|
|
254
|
+
ulid(message) {
|
|
255
|
+
return this.clone([
|
|
256
|
+
(v) => ULID_REGEX.test(v)
|
|
257
|
+
? null
|
|
258
|
+
: createIssue('invalid_ulid', message ?? 'Invalid ULID'),
|
|
259
|
+
]);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Validates that the string matches the provided regex pattern.
|
|
263
|
+
*/
|
|
264
|
+
regex(pattern, message) {
|
|
265
|
+
return this.clone([
|
|
266
|
+
(v) => pattern.test(v)
|
|
267
|
+
? null
|
|
268
|
+
: createIssue('invalid_regex', message ?? 'String does not match pattern'),
|
|
269
|
+
]);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Validates that the string is a valid ISO 8601 datetime.
|
|
273
|
+
*/
|
|
274
|
+
datetime(message) {
|
|
275
|
+
return this.clone([
|
|
276
|
+
(v) => DATETIME_REGEX.test(v)
|
|
277
|
+
? null
|
|
278
|
+
: createIssue('invalid_datetime', message ?? 'Invalid datetime format'),
|
|
279
|
+
]);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Validates that the string is a valid IP address (v4 or v6).
|
|
283
|
+
*/
|
|
284
|
+
ip(message) {
|
|
285
|
+
return this.clone([
|
|
286
|
+
(v) => IPV4_REGEX.test(v) || IPV6_REGEX.test(v)
|
|
287
|
+
? null
|
|
288
|
+
: createIssue('invalid_ip', message ?? 'Invalid IP address'),
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Content Validation Methods
|
|
293
|
+
// ============================================================================
|
|
294
|
+
/**
|
|
295
|
+
* Validates that the string includes the specified substring.
|
|
296
|
+
*/
|
|
297
|
+
includes(needle, message) {
|
|
298
|
+
return this.clone([
|
|
299
|
+
(v) => v.includes(needle)
|
|
300
|
+
? null
|
|
301
|
+
: createIssue('invalid_includes', message ?? `String must include "${needle}"`),
|
|
302
|
+
]);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Validates that the string starts with the specified prefix.
|
|
306
|
+
*/
|
|
307
|
+
startsWith(prefix, message) {
|
|
308
|
+
return this.clone([
|
|
309
|
+
(v) => v.startsWith(prefix)
|
|
310
|
+
? null
|
|
311
|
+
: createIssue('invalid_starts_with', message ?? `String must start with "${prefix}"`),
|
|
312
|
+
]);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Validates that the string ends with the specified suffix.
|
|
316
|
+
*/
|
|
317
|
+
endsWith(suffix, message) {
|
|
318
|
+
return this.clone([
|
|
319
|
+
(v) => v.endsWith(suffix)
|
|
320
|
+
? null
|
|
321
|
+
: createIssue('invalid_ends_with', message ?? `String must end with "${suffix}"`),
|
|
322
|
+
]);
|
|
323
|
+
}
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// Transform Methods
|
|
326
|
+
// ============================================================================
|
|
327
|
+
/**
|
|
328
|
+
* Trims whitespace from both ends of the string.
|
|
329
|
+
*/
|
|
330
|
+
trim() {
|
|
331
|
+
return this.clone([], [(v) => v.trim()]);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Converts the string to lowercase.
|
|
335
|
+
*/
|
|
336
|
+
toLowerCase() {
|
|
337
|
+
return this.clone([], [(v) => v.toLowerCase()]);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Converts the string to uppercase.
|
|
341
|
+
*/
|
|
342
|
+
toUpperCase() {
|
|
343
|
+
return this.clone([], [(v) => v.toUpperCase()]);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// ============================================================================
|
|
347
|
+
// ValNumber Class
|
|
348
|
+
// ============================================================================
|
|
349
|
+
/**
|
|
350
|
+
* Schema for number values with validation methods.
|
|
351
|
+
*
|
|
352
|
+
* Supports chainable methods like `.gt()`, `.gte()`, `.int()`, `.positive()`, etc.
|
|
353
|
+
* Each method returns a new schema instance (immutable).
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```typescript
|
|
357
|
+
* const ageSchema = v.number().int().positive();
|
|
358
|
+
* const priceSchema = v.number().gte(0).lte(1000);
|
|
359
|
+
* const percentSchema = v.number().gte(0).lte(100);
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
export class ValNumber extends ValSchema {
|
|
363
|
+
validators;
|
|
364
|
+
constructor(validators = []) {
|
|
365
|
+
const baseValidate = NumberSchema['~standard'].validate;
|
|
366
|
+
// Capture validators for use in closure
|
|
367
|
+
const capturedValidators = validators;
|
|
368
|
+
// Create a wrapped validation function that runs all validators
|
|
369
|
+
const wrappedValidate = (value) => {
|
|
370
|
+
// First, validate base type
|
|
371
|
+
const baseResult = baseValidate(value);
|
|
372
|
+
if (baseResult.issues !== undefined) {
|
|
373
|
+
return baseResult;
|
|
374
|
+
}
|
|
375
|
+
// At this point we know baseResult.value is defined
|
|
376
|
+
const validatedValue = baseResult.value;
|
|
377
|
+
// Then run all validators
|
|
378
|
+
for (let i = 0; i < capturedValidators.length; i++) {
|
|
379
|
+
const validator = capturedValidators[i];
|
|
380
|
+
if (validator !== undefined) {
|
|
381
|
+
const issue = validator(validatedValue);
|
|
382
|
+
if (issue !== null) {
|
|
383
|
+
return issue;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return success(validatedValue);
|
|
388
|
+
};
|
|
389
|
+
super(wrappedValidate, (target) => NumberSchema['~standard'].jsonSchema.input({ target }), (target) => NumberSchema['~standard'].jsonSchema.output({ target }));
|
|
390
|
+
this.validators = validators;
|
|
391
|
+
this._hasTransforms = validators.length > 0;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Creates a copy of this schema with additional validators.
|
|
395
|
+
*/
|
|
396
|
+
clone(additionalValidators) {
|
|
397
|
+
return new ValNumber([...this.validators, ...additionalValidators]);
|
|
398
|
+
}
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Comparison Methods
|
|
401
|
+
// ============================================================================
|
|
402
|
+
/**
|
|
403
|
+
* Requires number to be greater than `value`.
|
|
404
|
+
*/
|
|
405
|
+
gt(value, message) {
|
|
406
|
+
return this.clone([
|
|
407
|
+
(v) => v > value
|
|
408
|
+
? null
|
|
409
|
+
: createIssue('too_small', message ?? `Number must be greater than ${value}`, {
|
|
410
|
+
type: 'number',
|
|
411
|
+
minimum: value,
|
|
412
|
+
inclusive: false,
|
|
413
|
+
}),
|
|
414
|
+
]);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Requires number to be greater than or equal to `value`.
|
|
418
|
+
*/
|
|
419
|
+
gte(value, message) {
|
|
420
|
+
return this.clone([
|
|
421
|
+
(v) => v >= value
|
|
422
|
+
? null
|
|
423
|
+
: createIssue('too_small', message ?? `Number must be greater than or equal to ${value}`, {
|
|
424
|
+
type: 'number',
|
|
425
|
+
minimum: value,
|
|
426
|
+
inclusive: true,
|
|
427
|
+
}),
|
|
428
|
+
]);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Alias for `gte()`.
|
|
432
|
+
*/
|
|
433
|
+
min(value, message) {
|
|
434
|
+
return this.gte(value, message);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Requires number to be less than `value`.
|
|
438
|
+
*/
|
|
439
|
+
lt(value, message) {
|
|
440
|
+
return this.clone([
|
|
441
|
+
(v) => v < value
|
|
442
|
+
? null
|
|
443
|
+
: createIssue('too_big', message ?? `Number must be less than ${value}`, {
|
|
444
|
+
type: 'number',
|
|
445
|
+
maximum: value,
|
|
446
|
+
inclusive: false,
|
|
447
|
+
}),
|
|
448
|
+
]);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Requires number to be less than or equal to `value`.
|
|
452
|
+
*/
|
|
453
|
+
lte(value, message) {
|
|
454
|
+
return this.clone([
|
|
455
|
+
(v) => v <= value
|
|
456
|
+
? null
|
|
457
|
+
: createIssue('too_big', message ?? `Number must be less than or equal to ${value}`, {
|
|
458
|
+
type: 'number',
|
|
459
|
+
maximum: value,
|
|
460
|
+
inclusive: true,
|
|
461
|
+
}),
|
|
462
|
+
]);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Alias for `lte()`.
|
|
466
|
+
*/
|
|
467
|
+
max(value, message) {
|
|
468
|
+
return this.lte(value, message);
|
|
469
|
+
}
|
|
470
|
+
// ============================================================================
|
|
471
|
+
// Integer and Sign Methods
|
|
472
|
+
// ============================================================================
|
|
473
|
+
/**
|
|
474
|
+
* Requires number to be an integer.
|
|
475
|
+
*/
|
|
476
|
+
int(message) {
|
|
477
|
+
return this.clone([
|
|
478
|
+
(v) => Number.isInteger(v)
|
|
479
|
+
? null
|
|
480
|
+
: createIssue('invalid_type', message ?? 'Number must be an integer'),
|
|
481
|
+
]);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Requires number to be positive (> 0).
|
|
485
|
+
*/
|
|
486
|
+
positive(message) {
|
|
487
|
+
return this.clone([
|
|
488
|
+
(v) => v > 0
|
|
489
|
+
? null
|
|
490
|
+
: createIssue('too_small', message ?? 'Number must be positive', {
|
|
491
|
+
type: 'number',
|
|
492
|
+
minimum: 0,
|
|
493
|
+
inclusive: false,
|
|
494
|
+
}),
|
|
495
|
+
]);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Requires number to be non-negative (>= 0).
|
|
499
|
+
*/
|
|
500
|
+
nonnegative(message) {
|
|
501
|
+
return this.clone([
|
|
502
|
+
(v) => v >= 0
|
|
503
|
+
? null
|
|
504
|
+
: createIssue('too_small', message ?? 'Number must be non-negative', {
|
|
505
|
+
type: 'number',
|
|
506
|
+
minimum: 0,
|
|
507
|
+
inclusive: true,
|
|
508
|
+
}),
|
|
509
|
+
]);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Requires number to be negative (< 0).
|
|
513
|
+
*/
|
|
514
|
+
negative(message) {
|
|
515
|
+
return this.clone([
|
|
516
|
+
(v) => v < 0
|
|
517
|
+
? null
|
|
518
|
+
: createIssue('too_big', message ?? 'Number must be negative', {
|
|
519
|
+
type: 'number',
|
|
520
|
+
maximum: 0,
|
|
521
|
+
inclusive: false,
|
|
522
|
+
}),
|
|
523
|
+
]);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Requires number to be non-positive (<= 0).
|
|
527
|
+
*/
|
|
528
|
+
nonpositive(message) {
|
|
529
|
+
return this.clone([
|
|
530
|
+
(v) => v <= 0
|
|
531
|
+
? null
|
|
532
|
+
: createIssue('too_big', message ?? 'Number must be non-positive', {
|
|
533
|
+
type: 'number',
|
|
534
|
+
maximum: 0,
|
|
535
|
+
inclusive: true,
|
|
536
|
+
}),
|
|
537
|
+
]);
|
|
538
|
+
}
|
|
539
|
+
// ============================================================================
|
|
540
|
+
// Other Validation Methods
|
|
541
|
+
// ============================================================================
|
|
542
|
+
/**
|
|
543
|
+
* Requires number to be a multiple of `value`.
|
|
544
|
+
*
|
|
545
|
+
* Uses tolerance-based comparison to handle floating point precision issues.
|
|
546
|
+
*/
|
|
547
|
+
multipleOf(value, message) {
|
|
548
|
+
return this.clone([
|
|
549
|
+
(v) => {
|
|
550
|
+
// Handle floating point precision by checking if remainder is close to 0 or close to value
|
|
551
|
+
const remainder = Math.abs(v % value);
|
|
552
|
+
const isMultiple = remainder < Number.EPSILON * 100 || Math.abs(remainder - Math.abs(value)) < Number.EPSILON * 100;
|
|
553
|
+
return isMultiple
|
|
554
|
+
? null
|
|
555
|
+
: createIssue('not_multiple_of', message ?? `Number must be a multiple of ${value}`, {
|
|
556
|
+
multipleOf: value,
|
|
557
|
+
});
|
|
558
|
+
},
|
|
559
|
+
]);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Alias for `multipleOf()`.
|
|
563
|
+
*/
|
|
564
|
+
step(value, message) {
|
|
565
|
+
return this.multipleOf(value, message);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Requires number to be finite (not Infinity or -Infinity).
|
|
569
|
+
*/
|
|
570
|
+
finite(message) {
|
|
571
|
+
return this.clone([
|
|
572
|
+
(v) => Number.isFinite(v)
|
|
573
|
+
? null
|
|
574
|
+
: createIssue('not_finite', message ?? 'Number must be finite'),
|
|
575
|
+
]);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Requires number to be a safe integer (within Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER).
|
|
579
|
+
*/
|
|
580
|
+
safe(message) {
|
|
581
|
+
return this.clone([
|
|
582
|
+
(v) => Number.isSafeInteger(v)
|
|
583
|
+
? null
|
|
584
|
+
: createIssue('not_safe', message ?? 'Number must be a safe integer'),
|
|
585
|
+
]);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Schema for bigint values.
|
|
590
|
+
*
|
|
591
|
+
* Validates that the input is a JavaScript BigInt.
|
|
592
|
+
*/
|
|
593
|
+
export class ValBigInt extends ValSchema {
|
|
594
|
+
constructor() {
|
|
595
|
+
super((value) => {
|
|
596
|
+
if (typeof value !== 'bigint') {
|
|
597
|
+
return createTypeIssue('bigint', value);
|
|
598
|
+
}
|
|
599
|
+
return success(value);
|
|
600
|
+
}, (_target) => ({
|
|
601
|
+
type: 'integer',
|
|
602
|
+
description: 'A BigInt value represented as a JSON integer',
|
|
603
|
+
}));
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Schema for boolean values.
|
|
608
|
+
*/
|
|
609
|
+
export class ValBoolean extends ValSchema {
|
|
610
|
+
constructor() {
|
|
611
|
+
super(BooleanSchema['~standard'].validate, (target) => BooleanSchema['~standard'].jsonSchema.input({ target }), (target) => BooleanSchema['~standard'].jsonSchema.output({ target }));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Schema for Date values.
|
|
616
|
+
*
|
|
617
|
+
* Validates that the input is a valid Date object (not Invalid Date).
|
|
618
|
+
*/
|
|
619
|
+
export class ValDate extends ValSchema {
|
|
620
|
+
constructor() {
|
|
621
|
+
super((value) => {
|
|
622
|
+
if (!(value instanceof Date)) {
|
|
623
|
+
return createTypeIssue('date', value);
|
|
624
|
+
}
|
|
625
|
+
if (Number.isNaN(value.getTime())) {
|
|
626
|
+
return createIssue('invalid_date', 'Invalid date');
|
|
627
|
+
}
|
|
628
|
+
return success(value);
|
|
629
|
+
}, (_target) => ({
|
|
630
|
+
type: 'string',
|
|
631
|
+
format: 'date-time',
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Schema for undefined values.
|
|
637
|
+
*/
|
|
638
|
+
export class ValUndefined extends ValSchema {
|
|
639
|
+
constructor() {
|
|
640
|
+
super((value) => {
|
|
641
|
+
if (value !== undefined) {
|
|
642
|
+
return createTypeIssue('undefined', value);
|
|
643
|
+
}
|
|
644
|
+
return success(value);
|
|
645
|
+
}, (_target) => ({
|
|
646
|
+
not: {},
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
isOptional() {
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Schema for null values.
|
|
655
|
+
*/
|
|
656
|
+
export class ValNull extends ValSchema {
|
|
657
|
+
constructor() {
|
|
658
|
+
super((value) => {
|
|
659
|
+
if (value !== null) {
|
|
660
|
+
return createTypeIssue('null', value);
|
|
661
|
+
}
|
|
662
|
+
return success(value);
|
|
663
|
+
}, (_target) => ({
|
|
664
|
+
type: 'null',
|
|
665
|
+
}));
|
|
666
|
+
}
|
|
667
|
+
isNullable() {
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Schema for void (undefined).
|
|
673
|
+
*
|
|
674
|
+
* Alias for undefined schema, matching Zod's behavior.
|
|
675
|
+
*/
|
|
676
|
+
export class ValVoid extends ValSchema {
|
|
677
|
+
constructor() {
|
|
678
|
+
super((value) => {
|
|
679
|
+
if (value !== undefined) {
|
|
680
|
+
return createTypeIssue('void', value);
|
|
681
|
+
}
|
|
682
|
+
return success(value);
|
|
683
|
+
}, (_target) => ({
|
|
684
|
+
not: {},
|
|
685
|
+
}));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Schema that accepts any value.
|
|
690
|
+
*
|
|
691
|
+
* Use sparingly - prefer more specific schemas when possible.
|
|
692
|
+
*/
|
|
693
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
694
|
+
export class ValAny extends ValSchema {
|
|
695
|
+
constructor() {
|
|
696
|
+
super(
|
|
697
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
698
|
+
(value) => success(value), (_target) => ({}));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Schema that accepts any value as unknown.
|
|
703
|
+
*
|
|
704
|
+
* Unlike `any`, `unknown` requires type narrowing before use.
|
|
705
|
+
*/
|
|
706
|
+
export class ValUnknown extends ValSchema {
|
|
707
|
+
constructor() {
|
|
708
|
+
super((value) => success(value), (_target) => ({}));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Schema that never validates successfully.
|
|
713
|
+
*
|
|
714
|
+
* Useful for exhaustive type checking and unreachable code paths.
|
|
715
|
+
*/
|
|
716
|
+
export class ValNever extends ValSchema {
|
|
717
|
+
constructor() {
|
|
718
|
+
super((value) => createTypeIssue('never', value), (_target) => ({
|
|
719
|
+
not: {},
|
|
720
|
+
}));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// ============================================================================
|
|
724
|
+
// Integer Schema Classes (for Phase 2 extensibility)
|
|
725
|
+
// ============================================================================
|
|
726
|
+
/**
|
|
727
|
+
* Schema for 32-bit signed integers.
|
|
728
|
+
*/
|
|
729
|
+
export class ValInt32 extends ValSchema {
|
|
730
|
+
constructor() {
|
|
731
|
+
super(Int32Schema['~standard'].validate, (target) => Int32Schema['~standard'].jsonSchema.input({ target }), (target) => Int32Schema['~standard'].jsonSchema.output({ target }));
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Schema for 64-bit signed integers.
|
|
736
|
+
*/
|
|
737
|
+
export class ValInt64 extends ValSchema {
|
|
738
|
+
constructor() {
|
|
739
|
+
super(Int64Schema['~standard'].validate, (target) => Int64Schema['~standard'].jsonSchema.input({ target }), (target) => Int64Schema['~standard'].jsonSchema.output({ target }));
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Schema for 32-bit unsigned integers.
|
|
744
|
+
*/
|
|
745
|
+
export class ValUint32 extends ValSchema {
|
|
746
|
+
constructor() {
|
|
747
|
+
super(Uint32Schema['~standard'].validate, (target) => Uint32Schema['~standard'].jsonSchema.input({ target }), (target) => Uint32Schema['~standard'].jsonSchema.output({ target }));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Schema for 64-bit unsigned integers.
|
|
752
|
+
*/
|
|
753
|
+
export class ValUint64 extends ValSchema {
|
|
754
|
+
constructor() {
|
|
755
|
+
super(Uint64Schema['~standard'].validate, (target) => Uint64Schema['~standard'].jsonSchema.input({ target }), (target) => Uint64Schema['~standard'].jsonSchema.output({ target }));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Schema for 32-bit floating point numbers.
|
|
760
|
+
*/
|
|
761
|
+
export class ValFloat32 extends ValSchema {
|
|
762
|
+
constructor() {
|
|
763
|
+
super(Float32Schema['~standard'].validate, (target) => Float32Schema['~standard'].jsonSchema.input({ target }), (target) => Float32Schema['~standard'].jsonSchema.output({ target }));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Schema for 64-bit floating point numbers.
|
|
768
|
+
*/
|
|
769
|
+
export class ValFloat64 extends ValSchema {
|
|
770
|
+
constructor() {
|
|
771
|
+
super(Float64Schema['~standard'].validate, (target) => Float64Schema['~standard'].jsonSchema.input({ target }), (target) => Float64Schema['~standard'].jsonSchema.output({ target }));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
// ============================================================================
|
|
775
|
+
// ValObject Class
|
|
776
|
+
// ============================================================================
|
|
777
|
+
/**
|
|
778
|
+
* Schema for object values with a defined shape.
|
|
779
|
+
*
|
|
780
|
+
* Supports validation of each property, unknown key handling, and various
|
|
781
|
+
* transformation methods like pick, omit, partial, extend, etc.
|
|
782
|
+
*
|
|
783
|
+
* @template T - The shape definition (record of property schemas)
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* ```typescript
|
|
787
|
+
* const User = v.object({
|
|
788
|
+
* name: v.string(),
|
|
789
|
+
* age: v.number().int().positive(),
|
|
790
|
+
* email: v.string().email().optional(),
|
|
791
|
+
* });
|
|
792
|
+
*
|
|
793
|
+
* type User = v.infer<typeof User>;
|
|
794
|
+
* // { name: string; age: number; email?: string }
|
|
795
|
+
*
|
|
796
|
+
* User.parse({ name: 'Alice', age: 30 });
|
|
797
|
+
* ```
|
|
798
|
+
*/
|
|
799
|
+
export class ValObject extends ValSchema {
|
|
800
|
+
/**
|
|
801
|
+
* The shape definition containing all property schemas.
|
|
802
|
+
*/
|
|
803
|
+
shape;
|
|
804
|
+
unknownKeyMode;
|
|
805
|
+
catchallSchema;
|
|
806
|
+
constructor(shape, unknownKeyMode = 'strip', catchallSchema = null) {
|
|
807
|
+
const validateFn = (value) => {
|
|
808
|
+
// Check if value is an object
|
|
809
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
810
|
+
return createTypeIssue('object', value);
|
|
811
|
+
}
|
|
812
|
+
const input = value;
|
|
813
|
+
const result = {};
|
|
814
|
+
const issues = [];
|
|
815
|
+
// Track which keys we've processed
|
|
816
|
+
const processedKeys = new Set();
|
|
817
|
+
// Validate each defined property
|
|
818
|
+
for (const key of Object.keys(shape)) {
|
|
819
|
+
processedKeys.add(key);
|
|
820
|
+
const propertySchema = shape[key];
|
|
821
|
+
if (propertySchema === undefined)
|
|
822
|
+
continue;
|
|
823
|
+
const propertyValue = input[key];
|
|
824
|
+
// Check if property is missing (not present in input)
|
|
825
|
+
if (!(key in input)) {
|
|
826
|
+
// If the property schema accepts undefined, use undefined
|
|
827
|
+
// Otherwise, report a missing required property
|
|
828
|
+
if (propertySchema.isOptional()) {
|
|
829
|
+
// Don't include in result - it's optional and not provided
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
issues.push({ code: 'invalid_type', expected: 'string', received: 'undefined', message: 'Required', path: [key] });
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const propertyResult = propertySchema['~standard'].validate(propertyValue);
|
|
838
|
+
if (propertyResult.issues !== undefined) {
|
|
839
|
+
for (const issue of propertyResult.issues) {
|
|
840
|
+
const issueWithCode = issue;
|
|
841
|
+
issues.push({
|
|
842
|
+
code: issueWithCode.code ?? 'custom',
|
|
843
|
+
message: issue.message,
|
|
844
|
+
path: [key, ...(issue.path ?? [])],
|
|
845
|
+
...Object.fromEntries(Object.entries(issueWithCode).filter(([k]) => !['code', 'message', 'path'].includes(k))),
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
result[key] = propertyResult.value;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
// Handle unknown keys
|
|
854
|
+
for (const key of Object.keys(input)) {
|
|
855
|
+
if (processedKeys.has(key))
|
|
856
|
+
continue;
|
|
857
|
+
if (unknownKeyMode === 'strict') {
|
|
858
|
+
issues.push({ code: 'unrecognized_keys', keys: [key], message: `Unrecognized key(s) in object: '${key}'`, path: [] });
|
|
859
|
+
}
|
|
860
|
+
else if (unknownKeyMode === 'passthrough') {
|
|
861
|
+
result[key] = input[key];
|
|
862
|
+
}
|
|
863
|
+
else if (catchallSchema !== null) {
|
|
864
|
+
// Validate unknown keys with catchall schema
|
|
865
|
+
const catchallResult = catchallSchema['~standard'].validate(input[key]);
|
|
866
|
+
if (catchallResult.issues !== undefined) {
|
|
867
|
+
for (const issue of catchallResult.issues) {
|
|
868
|
+
const issueWithCode = issue;
|
|
869
|
+
issues.push({
|
|
870
|
+
code: issueWithCode.code ?? 'custom',
|
|
871
|
+
message: issue.message,
|
|
872
|
+
path: [key, ...(issue.path ?? [])],
|
|
873
|
+
...Object.fromEntries(Object.entries(issueWithCode).filter(([k]) => !['code', 'message', 'path'].includes(k))),
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
result[key] = catchallResult.value;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
// 'strip' mode: just don't include the key
|
|
882
|
+
}
|
|
883
|
+
if (issues.length > 0) {
|
|
884
|
+
return { issues };
|
|
885
|
+
}
|
|
886
|
+
return success(result);
|
|
887
|
+
};
|
|
888
|
+
const inputJsonSchemaFn = (target) => {
|
|
889
|
+
const properties = {};
|
|
890
|
+
const required = [];
|
|
891
|
+
for (const key of Object.keys(shape)) {
|
|
892
|
+
const propertySchema = shape[key];
|
|
893
|
+
if (propertySchema === undefined)
|
|
894
|
+
continue;
|
|
895
|
+
properties[key] = propertySchema['~standard'].jsonSchema.input({ target });
|
|
896
|
+
if (!propertySchema.isOptional()) {
|
|
897
|
+
required.push(key);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
const jsonSchema = {
|
|
901
|
+
type: 'object',
|
|
902
|
+
properties,
|
|
903
|
+
};
|
|
904
|
+
if (required.length > 0) {
|
|
905
|
+
jsonSchema['required'] = required;
|
|
906
|
+
}
|
|
907
|
+
if (unknownKeyMode === 'strict') {
|
|
908
|
+
jsonSchema['additionalProperties'] = false;
|
|
909
|
+
}
|
|
910
|
+
else if (catchallSchema !== null) {
|
|
911
|
+
jsonSchema['additionalProperties'] = catchallSchema['~standard'].jsonSchema.input({ target });
|
|
912
|
+
}
|
|
913
|
+
return jsonSchema;
|
|
914
|
+
};
|
|
915
|
+
super(validateFn, inputJsonSchemaFn);
|
|
916
|
+
this.shape = shape;
|
|
917
|
+
this.unknownKeyMode = unknownKeyMode;
|
|
918
|
+
this.catchallSchema = catchallSchema;
|
|
919
|
+
this._hasTransforms = Object.values(shape).some(s => s.hasTransforms());
|
|
920
|
+
}
|
|
921
|
+
// ============================================================================
|
|
922
|
+
// Object Transformation Methods
|
|
923
|
+
// ============================================================================
|
|
924
|
+
/**
|
|
925
|
+
* Extends the object schema with additional properties.
|
|
926
|
+
*
|
|
927
|
+
* @param augmentation - Additional property schemas to add
|
|
928
|
+
* @returns A new object schema with the combined shape
|
|
929
|
+
*
|
|
930
|
+
* @example
|
|
931
|
+
* ```typescript
|
|
932
|
+
* const User = v.object({ name: v.string() });
|
|
933
|
+
* const Admin = User.extend({ role: v.string() });
|
|
934
|
+
* // { name: string; role: string }
|
|
935
|
+
* ```
|
|
936
|
+
*/
|
|
937
|
+
extend(augmentation) {
|
|
938
|
+
return new ValObject({ ...this.shape, ...augmentation }, this.unknownKeyMode, this.catchallSchema);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Merges this object schema with another object schema.
|
|
942
|
+
*
|
|
943
|
+
* Properties from the other schema override properties in this schema.
|
|
944
|
+
*
|
|
945
|
+
* @param other - Another object schema to merge with
|
|
946
|
+
* @returns A new object schema with the merged shape
|
|
947
|
+
*
|
|
948
|
+
* @example
|
|
949
|
+
* ```typescript
|
|
950
|
+
* const A = v.object({ a: v.string(), shared: v.number() });
|
|
951
|
+
* const B = v.object({ b: v.boolean(), shared: v.string() });
|
|
952
|
+
* const Merged = A.merge(B);
|
|
953
|
+
* // { a: string; b: boolean; shared: string }
|
|
954
|
+
* ```
|
|
955
|
+
*/
|
|
956
|
+
merge(other) {
|
|
957
|
+
return new ValObject({ ...this.shape, ...other.shape }, this.unknownKeyMode, this.catchallSchema);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Creates a new schema with only the specified keys.
|
|
961
|
+
*
|
|
962
|
+
* @param mask - An object with keys to pick (values should be true)
|
|
963
|
+
* @returns A new object schema with only the picked keys
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* ```typescript
|
|
967
|
+
* const User = v.object({ name: v.string(), age: v.number(), email: v.string() });
|
|
968
|
+
* const NameOnly = User.pick({ name: true });
|
|
969
|
+
* // { name: string }
|
|
970
|
+
* ```
|
|
971
|
+
*/
|
|
972
|
+
pick(mask) {
|
|
973
|
+
const newShape = {};
|
|
974
|
+
for (const key of Object.keys(mask)) {
|
|
975
|
+
if (key in this.shape) {
|
|
976
|
+
newShape[key] = this.shape[key];
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return new ValObject(newShape, this.unknownKeyMode, this.catchallSchema);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Creates a new schema without the specified keys.
|
|
983
|
+
*
|
|
984
|
+
* @param mask - An object with keys to omit (values should be true)
|
|
985
|
+
* @returns A new object schema without the omitted keys
|
|
986
|
+
*
|
|
987
|
+
* @example
|
|
988
|
+
* ```typescript
|
|
989
|
+
* const User = v.object({ name: v.string(), age: v.number(), email: v.string() });
|
|
990
|
+
* const WithoutEmail = User.omit({ email: true });
|
|
991
|
+
* // { name: string; age: number }
|
|
992
|
+
* ```
|
|
993
|
+
*/
|
|
994
|
+
omit(mask) {
|
|
995
|
+
const newShape = {};
|
|
996
|
+
const keysToOmit = new Set(Object.keys(mask));
|
|
997
|
+
for (const key of Object.keys(this.shape)) {
|
|
998
|
+
if (!keysToOmit.has(key)) {
|
|
999
|
+
newShape[key] = this.shape[key];
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return new ValObject(newShape, this.unknownKeyMode, this.catchallSchema);
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Makes all properties optional.
|
|
1006
|
+
*
|
|
1007
|
+
* @returns A new object schema where all properties are optional
|
|
1008
|
+
*
|
|
1009
|
+
* @example
|
|
1010
|
+
* ```typescript
|
|
1011
|
+
* const User = v.object({ name: v.string(), age: v.number() });
|
|
1012
|
+
* const PartialUser = User.partial();
|
|
1013
|
+
* // { name?: string; age?: number }
|
|
1014
|
+
* ```
|
|
1015
|
+
*/
|
|
1016
|
+
partial() {
|
|
1017
|
+
const newShape = {};
|
|
1018
|
+
for (const key of Object.keys(this.shape)) {
|
|
1019
|
+
const schema = this.shape[key];
|
|
1020
|
+
if (schema !== undefined) {
|
|
1021
|
+
newShape[key] = schema.optional();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return new ValObject(newShape, this.unknownKeyMode, this.catchallSchema);
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Makes all properties deeply optional (recursive).
|
|
1028
|
+
*
|
|
1029
|
+
* For nested objects, this recursively applies partial().
|
|
1030
|
+
*
|
|
1031
|
+
* @returns A new object schema where all properties are deeply optional
|
|
1032
|
+
*
|
|
1033
|
+
* @example
|
|
1034
|
+
* ```typescript
|
|
1035
|
+
* const User = v.object({
|
|
1036
|
+
* name: v.string(),
|
|
1037
|
+
* address: v.object({ city: v.string(), zip: v.string() }),
|
|
1038
|
+
* });
|
|
1039
|
+
* const DeepPartialUser = User.deepPartial();
|
|
1040
|
+
* // { name?: string; address?: { city?: string; zip?: string } }
|
|
1041
|
+
* ```
|
|
1042
|
+
*/
|
|
1043
|
+
deepPartial() {
|
|
1044
|
+
const newShape = {};
|
|
1045
|
+
for (const key of Object.keys(this.shape)) {
|
|
1046
|
+
const schema = this.shape[key];
|
|
1047
|
+
if (schema === undefined)
|
|
1048
|
+
continue;
|
|
1049
|
+
if (schema instanceof ValObject) {
|
|
1050
|
+
// Recursively apply deepPartial to nested objects
|
|
1051
|
+
newShape[key] = schema.deepPartial().optional();
|
|
1052
|
+
}
|
|
1053
|
+
else {
|
|
1054
|
+
newShape[key] = schema.optional();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return new ValObject(newShape, this.unknownKeyMode, this.catchallSchema);
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Makes all properties required (removes optional).
|
|
1061
|
+
*
|
|
1062
|
+
* @returns A new object schema where all properties are required
|
|
1063
|
+
*
|
|
1064
|
+
* @example
|
|
1065
|
+
* ```typescript
|
|
1066
|
+
* const PartialUser = v.object({ name: v.string().optional(), age: v.number().optional() });
|
|
1067
|
+
* const User = PartialUser.required();
|
|
1068
|
+
* // { name: string; age: number }
|
|
1069
|
+
* ```
|
|
1070
|
+
*/
|
|
1071
|
+
required() {
|
|
1072
|
+
const newShape = {};
|
|
1073
|
+
for (const key of Object.keys(this.shape)) {
|
|
1074
|
+
const schema = this.shape[key];
|
|
1075
|
+
if (schema === undefined)
|
|
1076
|
+
continue;
|
|
1077
|
+
// Create a wrapper that rejects undefined
|
|
1078
|
+
newShape[key] = new ValSchema((value) => {
|
|
1079
|
+
if (value === undefined) {
|
|
1080
|
+
return fail('Required');
|
|
1081
|
+
}
|
|
1082
|
+
return schema['~standard'].validate(value);
|
|
1083
|
+
}, (target) => schema['~standard'].jsonSchema.input({ target }), (target) => schema['~standard'].jsonSchema.output({ target }));
|
|
1084
|
+
}
|
|
1085
|
+
return new ValObject(newShape, this.unknownKeyMode, this.catchallSchema);
|
|
1086
|
+
}
|
|
1087
|
+
// ============================================================================
|
|
1088
|
+
// Unknown Key Handling Methods
|
|
1089
|
+
// ============================================================================
|
|
1090
|
+
/**
|
|
1091
|
+
* Allows unknown keys to pass through without validation.
|
|
1092
|
+
*
|
|
1093
|
+
* @returns A new object schema that preserves unknown keys
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```typescript
|
|
1097
|
+
* const User = v.object({ name: v.string() }).passthrough();
|
|
1098
|
+
* User.parse({ name: 'Alice', extra: 'value' });
|
|
1099
|
+
* // { name: 'Alice', extra: 'value' }
|
|
1100
|
+
* ```
|
|
1101
|
+
*/
|
|
1102
|
+
passthrough() {
|
|
1103
|
+
return new ValObject(this.shape, 'passthrough', null);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Rejects any unknown keys (throws validation error).
|
|
1107
|
+
*
|
|
1108
|
+
* @returns A new object schema that rejects unknown keys
|
|
1109
|
+
*
|
|
1110
|
+
* @example
|
|
1111
|
+
* ```typescript
|
|
1112
|
+
* const User = v.object({ name: v.string() }).strict();
|
|
1113
|
+
* User.parse({ name: 'Alice', extra: 'value' }); // throws
|
|
1114
|
+
* ```
|
|
1115
|
+
*/
|
|
1116
|
+
strict() {
|
|
1117
|
+
return new ValObject(this.shape, 'strict', null);
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Silently removes unknown keys (default behavior).
|
|
1121
|
+
*
|
|
1122
|
+
* @returns A new object schema that strips unknown keys
|
|
1123
|
+
*
|
|
1124
|
+
* @example
|
|
1125
|
+
* ```typescript
|
|
1126
|
+
* const User = v.object({ name: v.string() }).strip();
|
|
1127
|
+
* User.parse({ name: 'Alice', extra: 'value' });
|
|
1128
|
+
* // { name: 'Alice' }
|
|
1129
|
+
* ```
|
|
1130
|
+
*/
|
|
1131
|
+
strip() {
|
|
1132
|
+
return new ValObject(this.shape, 'strip', null);
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Validates unknown keys with the provided schema.
|
|
1136
|
+
*
|
|
1137
|
+
* @param schema - Schema to validate unknown keys with
|
|
1138
|
+
* @returns A new object schema that validates unknown keys
|
|
1139
|
+
*
|
|
1140
|
+
* @example
|
|
1141
|
+
* ```typescript
|
|
1142
|
+
* const Metadata = v.object({ id: v.string() }).catchall(v.number());
|
|
1143
|
+
* Metadata.parse({ id: 'abc', count: 42, score: 100 });
|
|
1144
|
+
* // { id: 'abc', count: 42, score: 100 }
|
|
1145
|
+
* ```
|
|
1146
|
+
*/
|
|
1147
|
+
catchall(schema) {
|
|
1148
|
+
return new ValObject(this.shape, 'strip', schema);
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Returns a schema that validates to a union of the object's literal keys.
|
|
1152
|
+
*
|
|
1153
|
+
* @returns A schema for the object's keys as literals
|
|
1154
|
+
*
|
|
1155
|
+
* @example
|
|
1156
|
+
* ```typescript
|
|
1157
|
+
* const User = v.object({ name: v.string(), age: v.number() });
|
|
1158
|
+
* const UserKey = User.keyof();
|
|
1159
|
+
* UserKey.parse('name'); // 'name'
|
|
1160
|
+
* UserKey.parse('age'); // 'age'
|
|
1161
|
+
* UserKey.parse('email'); // throws
|
|
1162
|
+
* ```
|
|
1163
|
+
*/
|
|
1164
|
+
keyof() {
|
|
1165
|
+
const keys = Object.keys(this.shape);
|
|
1166
|
+
return new ValLiteral(keys);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
// ============================================================================
|
|
1170
|
+
// ValLiteral Class (for keyof)
|
|
1171
|
+
// ============================================================================
|
|
1172
|
+
/**
|
|
1173
|
+
* Schema for literal union values.
|
|
1174
|
+
*
|
|
1175
|
+
* Used by object.keyof() to create a schema that validates to one of the
|
|
1176
|
+
* object's keys.
|
|
1177
|
+
*
|
|
1178
|
+
* @template T - The union of literal values
|
|
1179
|
+
*/
|
|
1180
|
+
export class ValLiteral extends ValSchema {
|
|
1181
|
+
values;
|
|
1182
|
+
constructor(values) {
|
|
1183
|
+
const validateFn = (value) => {
|
|
1184
|
+
if (typeof value !== 'string') {
|
|
1185
|
+
return fail(`Expected one of: ${values.join(', ')}`);
|
|
1186
|
+
}
|
|
1187
|
+
if (!values.includes(value)) {
|
|
1188
|
+
return fail(`Expected one of: ${values.join(', ')}`);
|
|
1189
|
+
}
|
|
1190
|
+
return success(value);
|
|
1191
|
+
};
|
|
1192
|
+
const jsonSchemaFn = (_target) => ({
|
|
1193
|
+
type: 'string',
|
|
1194
|
+
enum: values,
|
|
1195
|
+
});
|
|
1196
|
+
super(validateFn, jsonSchemaFn);
|
|
1197
|
+
this.values = values;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
// Re-export ValArray, ValUnion, ValIntersection from schema.ts
|
|
1201
|
+
export { ValArray, ValUnion, ValIntersection } from './schema';
|
|
1202
|
+
/**
|
|
1203
|
+
* Schema for tuple values with fixed element types.
|
|
1204
|
+
*
|
|
1205
|
+
* @template T - The array of element schemas
|
|
1206
|
+
*
|
|
1207
|
+
* @example
|
|
1208
|
+
* ```typescript
|
|
1209
|
+
* const schema = v.tuple([v.string(), v.number()]);
|
|
1210
|
+
* schema.parse(['hello', 42]); // ['hello', 42]
|
|
1211
|
+
*
|
|
1212
|
+
* type Tuple = v.infer<typeof schema>; // [string, number]
|
|
1213
|
+
* ```
|
|
1214
|
+
*/
|
|
1215
|
+
export class ValTuple extends ValSchema {
|
|
1216
|
+
items;
|
|
1217
|
+
constructor(items, restSchema = null) {
|
|
1218
|
+
const validateFn = (value) => {
|
|
1219
|
+
if (!Array.isArray(value)) {
|
|
1220
|
+
return createTypeIssue('array', value);
|
|
1221
|
+
}
|
|
1222
|
+
const minLength = items.length;
|
|
1223
|
+
if (restSchema === null && value.length !== minLength) {
|
|
1224
|
+
return createIssue('too_small', `Expected tuple of length ${minLength}`, { type: 'array', minimum: minLength, inclusive: true, exact: true });
|
|
1225
|
+
}
|
|
1226
|
+
if (restSchema !== null && value.length < minLength) {
|
|
1227
|
+
return createIssue('too_small', `Expected at least ${minLength} elements`, { type: 'array', minimum: minLength, inclusive: true });
|
|
1228
|
+
}
|
|
1229
|
+
const result = [];
|
|
1230
|
+
const issues = [];
|
|
1231
|
+
// Validate fixed elements
|
|
1232
|
+
for (let i = 0; i < items.length; i++) {
|
|
1233
|
+
const elementSchema = items[i];
|
|
1234
|
+
if (elementSchema === undefined)
|
|
1235
|
+
continue;
|
|
1236
|
+
const elementResult = elementSchema['~standard'].validate(value[i]);
|
|
1237
|
+
if (elementResult.issues !== undefined) {
|
|
1238
|
+
for (const issue of elementResult.issues) {
|
|
1239
|
+
const issueWithCode = issue;
|
|
1240
|
+
issues.push({
|
|
1241
|
+
code: issueWithCode.code ?? 'custom',
|
|
1242
|
+
message: issue.message,
|
|
1243
|
+
path: [i, ...(issue.path ?? [])],
|
|
1244
|
+
...Object.fromEntries(Object.entries(issueWithCode).filter(([k]) => !['code', 'message', 'path'].includes(k))),
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
result.push(elementResult.value);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
// Validate rest elements
|
|
1253
|
+
if (restSchema !== null) {
|
|
1254
|
+
for (let i = items.length; i < value.length; i++) {
|
|
1255
|
+
const elementResult = restSchema['~standard'].validate(value[i]);
|
|
1256
|
+
if (elementResult.issues !== undefined) {
|
|
1257
|
+
for (const issue of elementResult.issues) {
|
|
1258
|
+
const issueWithCode = issue;
|
|
1259
|
+
issues.push({
|
|
1260
|
+
code: issueWithCode.code ?? 'custom',
|
|
1261
|
+
message: issue.message,
|
|
1262
|
+
path: [i, ...(issue.path ?? [])],
|
|
1263
|
+
...Object.fromEntries(Object.entries(issueWithCode).filter(([k]) => !['code', 'message', 'path'].includes(k))),
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
else {
|
|
1268
|
+
result.push(elementResult.value);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
if (issues.length > 0) {
|
|
1273
|
+
return { issues };
|
|
1274
|
+
}
|
|
1275
|
+
return success(result);
|
|
1276
|
+
};
|
|
1277
|
+
const inputJsonSchemaFn = (target) => {
|
|
1278
|
+
const prefixItems = items.map((s) => s['~standard'].jsonSchema.input({ target }));
|
|
1279
|
+
const schema = {
|
|
1280
|
+
type: 'array',
|
|
1281
|
+
prefixItems,
|
|
1282
|
+
minItems: items.length,
|
|
1283
|
+
};
|
|
1284
|
+
if (restSchema === null) {
|
|
1285
|
+
schema['maxItems'] = items.length;
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
schema['items'] = restSchema['~standard'].jsonSchema.input({ target });
|
|
1289
|
+
}
|
|
1290
|
+
return schema;
|
|
1291
|
+
};
|
|
1292
|
+
const outputJsonSchemaFn = (target) => {
|
|
1293
|
+
const prefixItems = items.map((s) => s['~standard'].jsonSchema.output({ target }));
|
|
1294
|
+
const schema = {
|
|
1295
|
+
type: 'array',
|
|
1296
|
+
prefixItems,
|
|
1297
|
+
minItems: items.length,
|
|
1298
|
+
};
|
|
1299
|
+
if (restSchema === null) {
|
|
1300
|
+
schema['maxItems'] = items.length;
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
schema['items'] = restSchema['~standard'].jsonSchema.output({ target });
|
|
1304
|
+
}
|
|
1305
|
+
return schema;
|
|
1306
|
+
};
|
|
1307
|
+
super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
|
|
1308
|
+
this.items = items;
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Adds a rest element type to the tuple.
|
|
1312
|
+
*
|
|
1313
|
+
* @example
|
|
1314
|
+
* ```typescript
|
|
1315
|
+
* const schema = v.tuple([v.string()]).rest(v.number());
|
|
1316
|
+
* schema.parse(['hello', 1, 2, 3]); // ['hello', 1, 2, 3]
|
|
1317
|
+
*
|
|
1318
|
+
* type T = v.infer<typeof schema>; // [string, ...number[]]
|
|
1319
|
+
* ```
|
|
1320
|
+
*/
|
|
1321
|
+
rest(restSchema) {
|
|
1322
|
+
return new ValTuple(this.items, restSchema);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
// ============================================================================
|
|
1326
|
+
// ValDiscriminatedUnion Class
|
|
1327
|
+
// ============================================================================
|
|
1328
|
+
/**
|
|
1329
|
+
* Schema for discriminated union types (tagged unions).
|
|
1330
|
+
*
|
|
1331
|
+
* Provides better error messages by identifying the discriminator first.
|
|
1332
|
+
*
|
|
1333
|
+
* @example
|
|
1334
|
+
* ```typescript
|
|
1335
|
+
* const schema = v.discriminatedUnion('type', [
|
|
1336
|
+
* v.object({ type: v.literal('a'), value: v.string() }),
|
|
1337
|
+
* v.object({ type: v.literal('b'), count: v.number() }),
|
|
1338
|
+
* ]);
|
|
1339
|
+
*
|
|
1340
|
+
* schema.parse({ type: 'a', value: 'hello' }); // OK
|
|
1341
|
+
* schema.parse({ type: 'b', count: 42 }); // OK
|
|
1342
|
+
* ```
|
|
1343
|
+
*/
|
|
1344
|
+
export class ValDiscriminatedUnion extends ValSchema {
|
|
1345
|
+
discriminator;
|
|
1346
|
+
options;
|
|
1347
|
+
constructor(discriminator, options) {
|
|
1348
|
+
const validateFn = (value) => {
|
|
1349
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
1350
|
+
return createTypeIssue('object', value);
|
|
1351
|
+
}
|
|
1352
|
+
const input = value;
|
|
1353
|
+
const discriminatorValue = input[discriminator];
|
|
1354
|
+
if (discriminatorValue === undefined) {
|
|
1355
|
+
return createIssue('invalid_union_discriminator', `Missing discriminator property "${discriminator}"`, { options: [] });
|
|
1356
|
+
}
|
|
1357
|
+
// Find matching variant
|
|
1358
|
+
for (const option of options) {
|
|
1359
|
+
const discriminatorSchema = option.shape[discriminator];
|
|
1360
|
+
if (discriminatorSchema === undefined)
|
|
1361
|
+
continue;
|
|
1362
|
+
const discriminatorResult = discriminatorSchema['~standard'].validate(discriminatorValue);
|
|
1363
|
+
if (discriminatorResult.issues === undefined) {
|
|
1364
|
+
// This variant's discriminator matches, validate the full object
|
|
1365
|
+
const result = option['~standard'].validate(value);
|
|
1366
|
+
return result;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
// Build list of expected discriminator values for error message
|
|
1370
|
+
const expectedValues = [];
|
|
1371
|
+
for (const option of options) {
|
|
1372
|
+
const discSchema = option.shape[discriminator];
|
|
1373
|
+
if (discSchema instanceof ValLiteralValue) {
|
|
1374
|
+
const val = discSchema.value;
|
|
1375
|
+
if (typeof val === 'string' || typeof val === 'number') {
|
|
1376
|
+
expectedValues.push(val);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return createIssue('invalid_union_discriminator', `Invalid discriminator value. Expected one of: ${expectedValues.join(', ')}, got: ${String(discriminatorValue)}`, { options: expectedValues });
|
|
1381
|
+
};
|
|
1382
|
+
const inputJsonSchemaFn = (target) => ({
|
|
1383
|
+
oneOf: options.map((o) => o['~standard'].jsonSchema.input({ target })),
|
|
1384
|
+
discriminator: { propertyName: discriminator },
|
|
1385
|
+
});
|
|
1386
|
+
const outputJsonSchemaFn = (target) => ({
|
|
1387
|
+
oneOf: options.map((o) => o['~standard'].jsonSchema.output({ target })),
|
|
1388
|
+
discriminator: { propertyName: discriminator },
|
|
1389
|
+
});
|
|
1390
|
+
super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
|
|
1391
|
+
this.discriminator = discriminator;
|
|
1392
|
+
this.options = options;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
// ============================================================================
|
|
1396
|
+
// ValRecord Class
|
|
1397
|
+
// ============================================================================
|
|
1398
|
+
/**
|
|
1399
|
+
* Schema for record/dictionary values with string keys.
|
|
1400
|
+
*
|
|
1401
|
+
* @template K - Key schema (must be string)
|
|
1402
|
+
* @template V - Value schema
|
|
1403
|
+
*
|
|
1404
|
+
* @example
|
|
1405
|
+
* ```typescript
|
|
1406
|
+
* const schema = v.record(v.string());
|
|
1407
|
+
* schema.parse({ a: 'hello', b: 'world' }); // OK
|
|
1408
|
+
*
|
|
1409
|
+
* const typedRecord = v.record(v.string(), v.number());
|
|
1410
|
+
* typedRecord.parse({ count: 42, score: 100 }); // OK
|
|
1411
|
+
* ```
|
|
1412
|
+
*/
|
|
1413
|
+
export class ValRecord extends ValSchema {
|
|
1414
|
+
keySchema;
|
|
1415
|
+
valueSchema;
|
|
1416
|
+
constructor(keySchema, valueSchema) {
|
|
1417
|
+
const validateFn = (value) => {
|
|
1418
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
1419
|
+
return createTypeIssue('object', value);
|
|
1420
|
+
}
|
|
1421
|
+
const input = value;
|
|
1422
|
+
const result = {};
|
|
1423
|
+
const issues = [];
|
|
1424
|
+
for (const key of Object.keys(input)) {
|
|
1425
|
+
// Validate key
|
|
1426
|
+
const keyResult = keySchema['~standard'].validate(key);
|
|
1427
|
+
if (keyResult.issues !== undefined) {
|
|
1428
|
+
for (const issue of keyResult.issues) {
|
|
1429
|
+
const issueWithCode = issue;
|
|
1430
|
+
issues.push({
|
|
1431
|
+
code: issueWithCode.code ?? 'custom',
|
|
1432
|
+
message: `Invalid key "${key}": ${issue.message}`,
|
|
1433
|
+
path: [key],
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
// Validate value
|
|
1439
|
+
const valueResult = valueSchema['~standard'].validate(input[key]);
|
|
1440
|
+
if (valueResult.issues !== undefined) {
|
|
1441
|
+
for (const issue of valueResult.issues) {
|
|
1442
|
+
const issueWithCode = issue;
|
|
1443
|
+
issues.push({
|
|
1444
|
+
code: issueWithCode.code ?? 'custom',
|
|
1445
|
+
message: issue.message,
|
|
1446
|
+
path: [key, ...(issue.path ?? [])],
|
|
1447
|
+
...Object.fromEntries(Object.entries(issueWithCode).filter(([k]) => !['code', 'message', 'path'].includes(k))),
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
result[keyResult.value] = valueResult.value;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (issues.length > 0) {
|
|
1456
|
+
return { issues };
|
|
1457
|
+
}
|
|
1458
|
+
return success(result);
|
|
1459
|
+
};
|
|
1460
|
+
const inputJsonSchemaFn = (target) => ({
|
|
1461
|
+
type: 'object',
|
|
1462
|
+
additionalProperties: valueSchema['~standard'].jsonSchema.input({ target }),
|
|
1463
|
+
});
|
|
1464
|
+
const outputJsonSchemaFn = (target) => ({
|
|
1465
|
+
type: 'object',
|
|
1466
|
+
additionalProperties: valueSchema['~standard'].jsonSchema.output({ target }),
|
|
1467
|
+
});
|
|
1468
|
+
super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
|
|
1469
|
+
this.keySchema = keySchema;
|
|
1470
|
+
this.valueSchema = valueSchema;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
// ============================================================================
|
|
1474
|
+
// ValMap Class
|
|
1475
|
+
// ============================================================================
|
|
1476
|
+
/**
|
|
1477
|
+
* Schema for JavaScript Map objects.
|
|
1478
|
+
*
|
|
1479
|
+
* @template K - Key schema
|
|
1480
|
+
* @template V - Value schema
|
|
1481
|
+
*
|
|
1482
|
+
* @example
|
|
1483
|
+
* ```typescript
|
|
1484
|
+
* const schema = v.map(v.string(), v.number());
|
|
1485
|
+
* const map = new Map([['a', 1], ['b', 2]]);
|
|
1486
|
+
* schema.parse(map); // Map { 'a' => 1, 'b' => 2 }
|
|
1487
|
+
* ```
|
|
1488
|
+
*/
|
|
1489
|
+
export class ValMap extends ValSchema {
|
|
1490
|
+
keySchema;
|
|
1491
|
+
valueSchema;
|
|
1492
|
+
constructor(keySchema, valueSchema) {
|
|
1493
|
+
const validateFn = (value) => {
|
|
1494
|
+
if (!(value instanceof Map)) {
|
|
1495
|
+
return createTypeIssue('map', value);
|
|
1496
|
+
}
|
|
1497
|
+
const result = new Map();
|
|
1498
|
+
const issues = [];
|
|
1499
|
+
let index = 0;
|
|
1500
|
+
for (const [k, v] of value.entries()) {
|
|
1501
|
+
const keyResult = keySchema['~standard'].validate(k);
|
|
1502
|
+
if (keyResult.issues !== undefined) {
|
|
1503
|
+
for (const issue of keyResult.issues) {
|
|
1504
|
+
const issueWithCode = issue;
|
|
1505
|
+
issues.push({
|
|
1506
|
+
code: issueWithCode.code ?? 'custom',
|
|
1507
|
+
message: `Invalid key at index ${index}: ${issue.message}`,
|
|
1508
|
+
path: [index, 'key', ...(issue.path ?? [])],
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
const valueResult = valueSchema['~standard'].validate(v);
|
|
1513
|
+
if (valueResult.issues !== undefined) {
|
|
1514
|
+
for (const issue of valueResult.issues) {
|
|
1515
|
+
const issueWithCode = issue;
|
|
1516
|
+
issues.push({
|
|
1517
|
+
code: issueWithCode.code ?? 'custom',
|
|
1518
|
+
message: issue.message,
|
|
1519
|
+
path: [index, 'value', ...(issue.path ?? [])],
|
|
1520
|
+
...Object.fromEntries(Object.entries(issueWithCode).filter(([k]) => !['code', 'message', 'path'].includes(k))),
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if (keyResult.issues === undefined && valueResult.issues === undefined) {
|
|
1525
|
+
result.set(keyResult.value, valueResult.value);
|
|
1526
|
+
}
|
|
1527
|
+
index++;
|
|
1528
|
+
}
|
|
1529
|
+
if (issues.length > 0) {
|
|
1530
|
+
return { issues };
|
|
1531
|
+
}
|
|
1532
|
+
return success(result);
|
|
1533
|
+
};
|
|
1534
|
+
const jsonSchemaFn = (target) => ({
|
|
1535
|
+
type: 'array',
|
|
1536
|
+
items: {
|
|
1537
|
+
type: 'array',
|
|
1538
|
+
prefixItems: [
|
|
1539
|
+
keySchema['~standard'].jsonSchema.input({ target }),
|
|
1540
|
+
valueSchema['~standard'].jsonSchema.input({ target }),
|
|
1541
|
+
],
|
|
1542
|
+
minItems: 2,
|
|
1543
|
+
maxItems: 2,
|
|
1544
|
+
},
|
|
1545
|
+
description: 'Map represented as array of [key, value] tuples',
|
|
1546
|
+
});
|
|
1547
|
+
super(validateFn, jsonSchemaFn);
|
|
1548
|
+
this.keySchema = keySchema;
|
|
1549
|
+
this.valueSchema = valueSchema;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
// ============================================================================
|
|
1553
|
+
// ValSet Class
|
|
1554
|
+
// ============================================================================
|
|
1555
|
+
/**
|
|
1556
|
+
* Schema for JavaScript Set objects.
|
|
1557
|
+
*
|
|
1558
|
+
* @template V - Value schema
|
|
1559
|
+
*
|
|
1560
|
+
* @example
|
|
1561
|
+
* ```typescript
|
|
1562
|
+
* const schema = v.set(v.string());
|
|
1563
|
+
* schema.parse(new Set(['a', 'b', 'c'])); // Set { 'a', 'b', 'c' }
|
|
1564
|
+
* ```
|
|
1565
|
+
*/
|
|
1566
|
+
export class ValSet extends ValSchema {
|
|
1567
|
+
valueSchema;
|
|
1568
|
+
constructor(valueSchema) {
|
|
1569
|
+
const validateFn = (value) => {
|
|
1570
|
+
if (!(value instanceof Set)) {
|
|
1571
|
+
return createTypeIssue('set', value);
|
|
1572
|
+
}
|
|
1573
|
+
const result = new Set();
|
|
1574
|
+
const issues = [];
|
|
1575
|
+
let index = 0;
|
|
1576
|
+
for (const item of value) {
|
|
1577
|
+
const itemResult = valueSchema['~standard'].validate(item);
|
|
1578
|
+
if (itemResult.issues !== undefined) {
|
|
1579
|
+
for (const issue of itemResult.issues) {
|
|
1580
|
+
const issueWithCode = issue;
|
|
1581
|
+
issues.push({
|
|
1582
|
+
code: issueWithCode.code ?? 'custom',
|
|
1583
|
+
message: issue.message,
|
|
1584
|
+
path: [index, ...(issue.path ?? [])],
|
|
1585
|
+
...Object.fromEntries(Object.entries(issueWithCode).filter(([k]) => !['code', 'message', 'path'].includes(k))),
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
else {
|
|
1590
|
+
result.add(itemResult.value);
|
|
1591
|
+
}
|
|
1592
|
+
index++;
|
|
1593
|
+
}
|
|
1594
|
+
if (issues.length > 0) {
|
|
1595
|
+
return { issues };
|
|
1596
|
+
}
|
|
1597
|
+
return success(result);
|
|
1598
|
+
};
|
|
1599
|
+
const jsonSchemaFn = (target) => ({
|
|
1600
|
+
type: 'array',
|
|
1601
|
+
items: valueSchema['~standard'].jsonSchema.input({ target }),
|
|
1602
|
+
uniqueItems: true,
|
|
1603
|
+
description: 'Set represented as array with unique items',
|
|
1604
|
+
});
|
|
1605
|
+
super(validateFn, jsonSchemaFn);
|
|
1606
|
+
this.valueSchema = valueSchema;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Schema for a single literal value.
|
|
1611
|
+
*
|
|
1612
|
+
* @template T - The literal type
|
|
1613
|
+
*
|
|
1614
|
+
* @example
|
|
1615
|
+
* ```typescript
|
|
1616
|
+
* const schema = v.literal('hello');
|
|
1617
|
+
* schema.parse('hello'); // 'hello'
|
|
1618
|
+
* schema.parse('world'); // throws
|
|
1619
|
+
*
|
|
1620
|
+
* type Hello = v.infer<typeof schema>; // 'hello'
|
|
1621
|
+
* ```
|
|
1622
|
+
*/
|
|
1623
|
+
export class ValLiteralValue extends ValSchema {
|
|
1624
|
+
value;
|
|
1625
|
+
constructor(literalValue) {
|
|
1626
|
+
const validateFn = (value) => {
|
|
1627
|
+
if (value !== literalValue) {
|
|
1628
|
+
return fail(`Expected literal ${JSON.stringify(literalValue)}`);
|
|
1629
|
+
}
|
|
1630
|
+
return success(value);
|
|
1631
|
+
};
|
|
1632
|
+
const jsonSchemaFn = (_target) => {
|
|
1633
|
+
if (literalValue === null) {
|
|
1634
|
+
return { type: 'null' };
|
|
1635
|
+
}
|
|
1636
|
+
if (literalValue === undefined) {
|
|
1637
|
+
return { not: {} };
|
|
1638
|
+
}
|
|
1639
|
+
return { const: literalValue };
|
|
1640
|
+
};
|
|
1641
|
+
super(validateFn, jsonSchemaFn);
|
|
1642
|
+
this.value = literalValue;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
// ============================================================================
|
|
1646
|
+
// ValEnum Class
|
|
1647
|
+
// ============================================================================
|
|
1648
|
+
/**
|
|
1649
|
+
* Schema for enum values (one of a fixed set of string literals).
|
|
1650
|
+
*
|
|
1651
|
+
* @template T - Tuple of allowed string values
|
|
1652
|
+
*
|
|
1653
|
+
* @example
|
|
1654
|
+
* ```typescript
|
|
1655
|
+
* const RoleSchema = v.enum(['admin', 'user', 'guest']);
|
|
1656
|
+
* RoleSchema.parse('admin'); // 'admin'
|
|
1657
|
+
* RoleSchema.parse('other'); // throws
|
|
1658
|
+
*
|
|
1659
|
+
* type Role = v.infer<typeof RoleSchema>; // 'admin' | 'user' | 'guest'
|
|
1660
|
+
* ```
|
|
1661
|
+
*/
|
|
1662
|
+
export class ValEnum extends ValSchema {
|
|
1663
|
+
options;
|
|
1664
|
+
/** Enum-like object mapping values to themselves */
|
|
1665
|
+
enum;
|
|
1666
|
+
constructor(values) {
|
|
1667
|
+
const validateFn = (value) => {
|
|
1668
|
+
if (typeof value !== 'string') {
|
|
1669
|
+
return fail(`Expected one of: ${values.join(', ')}`);
|
|
1670
|
+
}
|
|
1671
|
+
if (!values.includes(value)) {
|
|
1672
|
+
return fail(`Expected one of: ${values.join(', ')}, got: "${value}"`);
|
|
1673
|
+
}
|
|
1674
|
+
return success(value);
|
|
1675
|
+
};
|
|
1676
|
+
const jsonSchemaFn = (_target) => ({
|
|
1677
|
+
type: 'string',
|
|
1678
|
+
enum: [...values],
|
|
1679
|
+
});
|
|
1680
|
+
super(validateFn, jsonSchemaFn);
|
|
1681
|
+
this.options = values;
|
|
1682
|
+
// Create enum-like object
|
|
1683
|
+
this.enum = Object.fromEntries(values.map((v) => [v, v]));
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Schema for TypeScript native enums.
|
|
1688
|
+
*
|
|
1689
|
+
* @template T - The enum type
|
|
1690
|
+
*
|
|
1691
|
+
* @example
|
|
1692
|
+
* ```typescript
|
|
1693
|
+
* enum Status { Active, Inactive }
|
|
1694
|
+
* const schema = v.nativeEnum(Status);
|
|
1695
|
+
* schema.parse(Status.Active); // 0
|
|
1696
|
+
* schema.parse('Active'); // throws (numeric enum)
|
|
1697
|
+
* ```
|
|
1698
|
+
*/
|
|
1699
|
+
export class ValNativeEnum extends ValSchema {
|
|
1700
|
+
enumObject;
|
|
1701
|
+
constructor(enumObj) {
|
|
1702
|
+
// Get the actual values from the enum
|
|
1703
|
+
const values = Object.values(enumObj).filter((v) => typeof v === 'number' || typeof v === 'string');
|
|
1704
|
+
// For numeric enums, TypeScript creates reverse mappings, so we need to filter
|
|
1705
|
+
const numericValues = values.filter((v) => typeof v === 'number');
|
|
1706
|
+
const actualValues = numericValues.length > 0 ? numericValues : values;
|
|
1707
|
+
const validateFn = (value) => {
|
|
1708
|
+
if (!actualValues.includes(value)) {
|
|
1709
|
+
return fail(`Invalid enum value. Expected one of: ${actualValues.join(', ')}`);
|
|
1710
|
+
}
|
|
1711
|
+
return success(value);
|
|
1712
|
+
};
|
|
1713
|
+
const jsonSchemaFn = (_target) => ({
|
|
1714
|
+
enum: actualValues,
|
|
1715
|
+
});
|
|
1716
|
+
super(validateFn, jsonSchemaFn);
|
|
1717
|
+
this.enumObject = enumObj;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
// ============================================================================
|
|
1721
|
+
// Object Builder Function
|
|
1722
|
+
// ============================================================================
|
|
1723
|
+
/**
|
|
1724
|
+
* Creates an object schema from a shape definition.
|
|
1725
|
+
*
|
|
1726
|
+
* @param shape - An object mapping property names to their schemas
|
|
1727
|
+
* @returns A new object schema
|
|
1728
|
+
*
|
|
1729
|
+
* @example
|
|
1730
|
+
* ```typescript
|
|
1731
|
+
* const User = v.object({
|
|
1732
|
+
* name: v.string(),
|
|
1733
|
+
* age: v.number().int().positive(),
|
|
1734
|
+
* email: v.string().email().optional(),
|
|
1735
|
+
* });
|
|
1736
|
+
*
|
|
1737
|
+
* type User = v.infer<typeof User>;
|
|
1738
|
+
* // { name: string; age: number; email?: string }
|
|
1739
|
+
*
|
|
1740
|
+
* const user = User.parse({ name: 'Alice', age: 30 });
|
|
1741
|
+
* ```
|
|
1742
|
+
*/
|
|
1743
|
+
export function object(shape) {
|
|
1744
|
+
return new ValObject(shape);
|
|
1745
|
+
}
|
|
1746
|
+
// ============================================================================
|
|
1747
|
+
// Schema Builder Functions
|
|
1748
|
+
// ============================================================================
|
|
1749
|
+
/**
|
|
1750
|
+
* Creates a string schema.
|
|
1751
|
+
*
|
|
1752
|
+
* @returns A new string schema
|
|
1753
|
+
*
|
|
1754
|
+
* @example
|
|
1755
|
+
* ```typescript
|
|
1756
|
+
* const nameSchema = v.string();
|
|
1757
|
+
* nameSchema.parse('Alice'); // 'Alice'
|
|
1758
|
+
* nameSchema.parse(123); // throws ValError
|
|
1759
|
+
* ```
|
|
1760
|
+
*/
|
|
1761
|
+
export function string() {
|
|
1762
|
+
return new ValString();
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Creates a number schema.
|
|
1766
|
+
*
|
|
1767
|
+
* @returns A new number schema
|
|
1768
|
+
*
|
|
1769
|
+
* @example
|
|
1770
|
+
* ```typescript
|
|
1771
|
+
* const ageSchema = v.number();
|
|
1772
|
+
* ageSchema.parse(25); // 25
|
|
1773
|
+
* ageSchema.parse('25'); // throws ValError
|
|
1774
|
+
* ```
|
|
1775
|
+
*/
|
|
1776
|
+
export function number() {
|
|
1777
|
+
return new ValNumber();
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Creates a bigint schema.
|
|
1781
|
+
*
|
|
1782
|
+
* @returns A new bigint schema
|
|
1783
|
+
*
|
|
1784
|
+
* @example
|
|
1785
|
+
* ```typescript
|
|
1786
|
+
* const bigSchema = v.bigint();
|
|
1787
|
+
* bigSchema.parse(123n); // 123n
|
|
1788
|
+
* bigSchema.parse(123); // throws ValError
|
|
1789
|
+
* ```
|
|
1790
|
+
*/
|
|
1791
|
+
export function bigint() {
|
|
1792
|
+
return new ValBigInt();
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Creates a boolean schema.
|
|
1796
|
+
*
|
|
1797
|
+
* @returns A new boolean schema
|
|
1798
|
+
*
|
|
1799
|
+
* @example
|
|
1800
|
+
* ```typescript
|
|
1801
|
+
* const flagSchema = v.boolean();
|
|
1802
|
+
* flagSchema.parse(true); // true
|
|
1803
|
+
* flagSchema.parse('true'); // throws ValError
|
|
1804
|
+
* ```
|
|
1805
|
+
*/
|
|
1806
|
+
function booleanFn() {
|
|
1807
|
+
return new ValBoolean();
|
|
1808
|
+
}
|
|
1809
|
+
// Export as 'boolean' (reserved word workaround)
|
|
1810
|
+
export { booleanFn as boolean };
|
|
1811
|
+
/**
|
|
1812
|
+
* Creates a date schema.
|
|
1813
|
+
*
|
|
1814
|
+
* @returns A new date schema
|
|
1815
|
+
*
|
|
1816
|
+
* @example
|
|
1817
|
+
* ```typescript
|
|
1818
|
+
* const dateSchema = v.date();
|
|
1819
|
+
* dateSchema.parse(new Date()); // Date object
|
|
1820
|
+
* dateSchema.parse('2024-01-01'); // throws ValError
|
|
1821
|
+
* ```
|
|
1822
|
+
*/
|
|
1823
|
+
export function date() {
|
|
1824
|
+
return new ValDate();
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Creates an undefined schema.
|
|
1828
|
+
*
|
|
1829
|
+
* @returns A new undefined schema
|
|
1830
|
+
*/
|
|
1831
|
+
function undefinedFn() {
|
|
1832
|
+
return new ValUndefined();
|
|
1833
|
+
}
|
|
1834
|
+
// Export as 'undefined' (reserved word workaround)
|
|
1835
|
+
export { undefinedFn as undefined };
|
|
1836
|
+
/**
|
|
1837
|
+
* Creates a null schema.
|
|
1838
|
+
*
|
|
1839
|
+
* @returns A new null schema
|
|
1840
|
+
*/
|
|
1841
|
+
function nullFn() {
|
|
1842
|
+
return new ValNull();
|
|
1843
|
+
}
|
|
1844
|
+
// Export as 'null' (reserved word workaround)
|
|
1845
|
+
export { nullFn as null };
|
|
1846
|
+
/**
|
|
1847
|
+
* Creates a void schema (alias for undefined).
|
|
1848
|
+
*
|
|
1849
|
+
* @returns A new void schema
|
|
1850
|
+
*/
|
|
1851
|
+
function voidFn() {
|
|
1852
|
+
return new ValVoid();
|
|
1853
|
+
}
|
|
1854
|
+
// Export as 'void' (reserved word workaround)
|
|
1855
|
+
export { voidFn as void };
|
|
1856
|
+
/**
|
|
1857
|
+
* Creates an any schema.
|
|
1858
|
+
*
|
|
1859
|
+
* Use sparingly - prefer more specific schemas when possible.
|
|
1860
|
+
*
|
|
1861
|
+
* @returns A new any schema
|
|
1862
|
+
*/
|
|
1863
|
+
export function any() {
|
|
1864
|
+
return new ValAny();
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Creates an unknown schema.
|
|
1868
|
+
*
|
|
1869
|
+
* Unlike `any`, `unknown` requires type narrowing before use.
|
|
1870
|
+
*
|
|
1871
|
+
* @returns A new unknown schema
|
|
1872
|
+
*/
|
|
1873
|
+
export function unknown() {
|
|
1874
|
+
return new ValUnknown();
|
|
1875
|
+
}
|
|
1876
|
+
/**
|
|
1877
|
+
* Creates a never schema.
|
|
1878
|
+
*
|
|
1879
|
+
* Useful for exhaustive type checking and unreachable code paths.
|
|
1880
|
+
*
|
|
1881
|
+
* @returns A new never schema
|
|
1882
|
+
*/
|
|
1883
|
+
export function never() {
|
|
1884
|
+
return new ValNever();
|
|
1885
|
+
}
|
|
1886
|
+
// ============================================================================
|
|
1887
|
+
// Integer Schema Builder Functions
|
|
1888
|
+
// ============================================================================
|
|
1889
|
+
/**
|
|
1890
|
+
* Creates a 32-bit signed integer schema.
|
|
1891
|
+
*
|
|
1892
|
+
* Range: -2,147,483,648 to 2,147,483,647
|
|
1893
|
+
*/
|
|
1894
|
+
export function int32() {
|
|
1895
|
+
return new ValInt32();
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Creates a 64-bit signed integer schema.
|
|
1899
|
+
*
|
|
1900
|
+
* Note: JavaScript numbers can only safely represent integers up to 2^53 - 1.
|
|
1901
|
+
*/
|
|
1902
|
+
export function int64() {
|
|
1903
|
+
return new ValInt64();
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Creates a 32-bit unsigned integer schema.
|
|
1907
|
+
*
|
|
1908
|
+
* Range: 0 to 4,294,967,295
|
|
1909
|
+
*/
|
|
1910
|
+
export function uint32() {
|
|
1911
|
+
return new ValUint32();
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Creates a 64-bit unsigned integer schema.
|
|
1915
|
+
*/
|
|
1916
|
+
export function uint64() {
|
|
1917
|
+
return new ValUint64();
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Creates a 32-bit floating point schema.
|
|
1921
|
+
*/
|
|
1922
|
+
export function float32() {
|
|
1923
|
+
return new ValFloat32();
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Creates a 64-bit floating point schema.
|
|
1927
|
+
*/
|
|
1928
|
+
export function float64() {
|
|
1929
|
+
return new ValFloat64();
|
|
1930
|
+
}
|
|
1931
|
+
// ============================================================================
|
|
1932
|
+
// Phase 4 Builder Functions
|
|
1933
|
+
// ============================================================================
|
|
1934
|
+
/**
|
|
1935
|
+
* Creates an array schema for the given element type.
|
|
1936
|
+
*
|
|
1937
|
+
* @param element - The schema for array elements
|
|
1938
|
+
* @returns A new array schema
|
|
1939
|
+
*
|
|
1940
|
+
* @example
|
|
1941
|
+
* ```typescript
|
|
1942
|
+
* const schema = v.array(v.string());
|
|
1943
|
+
* schema.parse(['a', 'b', 'c']); // ['a', 'b', 'c']
|
|
1944
|
+
*
|
|
1945
|
+
* const withMin = v.array(v.number()).min(1);
|
|
1946
|
+
* withMin.parse([]); // throws
|
|
1947
|
+
* withMin.parse([1, 2]); // [1, 2]
|
|
1948
|
+
* ```
|
|
1949
|
+
*/
|
|
1950
|
+
export function array(element) {
|
|
1951
|
+
return new ValArray(element);
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Creates a tuple schema with fixed element types.
|
|
1955
|
+
*
|
|
1956
|
+
* @param items - Array of schemas for each tuple element
|
|
1957
|
+
* @returns A new tuple schema
|
|
1958
|
+
*
|
|
1959
|
+
* @example
|
|
1960
|
+
* ```typescript
|
|
1961
|
+
* const schema = v.tuple([v.string(), v.number()]);
|
|
1962
|
+
* schema.parse(['hello', 42]); // ['hello', 42]
|
|
1963
|
+
*
|
|
1964
|
+
* const withRest = v.tuple([v.string()]).rest(v.number());
|
|
1965
|
+
* withRest.parse(['hello', 1, 2, 3]); // ['hello', 1, 2, 3]
|
|
1966
|
+
* ```
|
|
1967
|
+
*/
|
|
1968
|
+
export function tuple(items) {
|
|
1969
|
+
return new ValTuple(items);
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Creates a union schema (one of multiple types).
|
|
1973
|
+
*
|
|
1974
|
+
* @param options - Array of schemas to union
|
|
1975
|
+
* @returns A new union schema
|
|
1976
|
+
*
|
|
1977
|
+
* @example
|
|
1978
|
+
* ```typescript
|
|
1979
|
+
* const schema = v.union([v.string(), v.number()]);
|
|
1980
|
+
* schema.parse('hello'); // 'hello'
|
|
1981
|
+
* schema.parse(42); // 42
|
|
1982
|
+
* schema.parse(true); // throws
|
|
1983
|
+
* ```
|
|
1984
|
+
*/
|
|
1985
|
+
export function union(options) {
|
|
1986
|
+
return new ValUnion(options);
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Creates a discriminated union schema (tagged union).
|
|
1990
|
+
*
|
|
1991
|
+
* Uses a discriminator property to efficiently match variants.
|
|
1992
|
+
* Provides better error messages than regular unions.
|
|
1993
|
+
*
|
|
1994
|
+
* @param discriminator - The property name used to discriminate variants
|
|
1995
|
+
* @param options - Array of object schemas with the discriminator
|
|
1996
|
+
* @returns A new discriminated union schema
|
|
1997
|
+
*
|
|
1998
|
+
* @example
|
|
1999
|
+
* ```typescript
|
|
2000
|
+
* const schema = v.discriminatedUnion('type', [
|
|
2001
|
+
* v.object({ type: v.literal('a'), value: v.string() }),
|
|
2002
|
+
* v.object({ type: v.literal('b'), count: v.number() }),
|
|
2003
|
+
* ]);
|
|
2004
|
+
*
|
|
2005
|
+
* schema.parse({ type: 'a', value: 'hello' }); // OK
|
|
2006
|
+
* schema.parse({ type: 'b', count: 42 }); // OK
|
|
2007
|
+
* schema.parse({ type: 'c' }); // throws with helpful error
|
|
2008
|
+
* ```
|
|
2009
|
+
*/
|
|
2010
|
+
export function discriminatedUnion(discriminator, options) {
|
|
2011
|
+
return new ValDiscriminatedUnion(discriminator, options);
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Creates an intersection schema (all types must match).
|
|
2015
|
+
*
|
|
2016
|
+
* @param left - First schema
|
|
2017
|
+
* @param right - Second schema
|
|
2018
|
+
* @returns A new intersection schema
|
|
2019
|
+
*
|
|
2020
|
+
* @example
|
|
2021
|
+
* ```typescript
|
|
2022
|
+
* const A = v.object({ a: v.string() });
|
|
2023
|
+
* const B = v.object({ b: v.number() });
|
|
2024
|
+
* const AB = v.intersection(A, B);
|
|
2025
|
+
*
|
|
2026
|
+
* AB.parse({ a: 'hello', b: 42 }); // { a: 'hello', b: 42 }
|
|
2027
|
+
* ```
|
|
2028
|
+
*/
|
|
2029
|
+
export function intersection(left, right) {
|
|
2030
|
+
return new ValIntersection(left, right);
|
|
2031
|
+
}
|
|
2032
|
+
export function record(keyOrValue, maybeValue) {
|
|
2033
|
+
if (maybeValue === undefined) {
|
|
2034
|
+
// Single argument: just value schema, use string for keys
|
|
2035
|
+
return new ValRecord(new ValString(), keyOrValue);
|
|
2036
|
+
}
|
|
2037
|
+
// Two arguments: key schema and value schema
|
|
2038
|
+
return new ValRecord(keyOrValue, maybeValue);
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Creates a Map schema.
|
|
2042
|
+
*
|
|
2043
|
+
* @param keySchema - Schema for map keys
|
|
2044
|
+
* @param valueSchema - Schema for map values
|
|
2045
|
+
* @returns A new Map schema
|
|
2046
|
+
*
|
|
2047
|
+
* @example
|
|
2048
|
+
* ```typescript
|
|
2049
|
+
* const schema = v.map(v.string(), v.number());
|
|
2050
|
+
* schema.parse(new Map([['a', 1], ['b', 2]])); // OK
|
|
2051
|
+
* ```
|
|
2052
|
+
*/
|
|
2053
|
+
export function map(keySchema, valueSchema) {
|
|
2054
|
+
return new ValMap(keySchema, valueSchema);
|
|
2055
|
+
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Creates a Set schema.
|
|
2058
|
+
*
|
|
2059
|
+
* @param valueSchema - Schema for set values
|
|
2060
|
+
* @returns A new Set schema
|
|
2061
|
+
*
|
|
2062
|
+
* @example
|
|
2063
|
+
* ```typescript
|
|
2064
|
+
* const schema = v.set(v.string());
|
|
2065
|
+
* schema.parse(new Set(['a', 'b', 'c'])); // OK
|
|
2066
|
+
* ```
|
|
2067
|
+
*/
|
|
2068
|
+
export function set(valueSchema) {
|
|
2069
|
+
return new ValSet(valueSchema);
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Creates a literal schema for a single value.
|
|
2073
|
+
*
|
|
2074
|
+
* @param value - The literal value to match
|
|
2075
|
+
* @returns A new literal schema
|
|
2076
|
+
*
|
|
2077
|
+
* @example
|
|
2078
|
+
* ```typescript
|
|
2079
|
+
* const hello = v.literal('hello');
|
|
2080
|
+
* hello.parse('hello'); // 'hello'
|
|
2081
|
+
* hello.parse('world'); // throws
|
|
2082
|
+
*
|
|
2083
|
+
* const fortyTwo = v.literal(42);
|
|
2084
|
+
* fortyTwo.parse(42); // 42
|
|
2085
|
+
*
|
|
2086
|
+
* type Hello = v.infer<typeof hello>; // 'hello'
|
|
2087
|
+
* ```
|
|
2088
|
+
*/
|
|
2089
|
+
export function literal(value) {
|
|
2090
|
+
return new ValLiteralValue(value);
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Creates an enum schema for a fixed set of string values.
|
|
2094
|
+
*
|
|
2095
|
+
* @param values - Tuple of allowed string values
|
|
2096
|
+
* @returns A new enum schema with an `enum` property
|
|
2097
|
+
*
|
|
2098
|
+
* @example
|
|
2099
|
+
* ```typescript
|
|
2100
|
+
* const Role = v.enum(['admin', 'user', 'guest']);
|
|
2101
|
+
* Role.parse('admin'); // 'admin'
|
|
2102
|
+
* Role.parse('other'); // throws
|
|
2103
|
+
*
|
|
2104
|
+
* type Role = v.infer<typeof Role>; // 'admin' | 'user' | 'guest'
|
|
2105
|
+
*
|
|
2106
|
+
* // Access values like an enum
|
|
2107
|
+
* Role.enum.admin; // 'admin'
|
|
2108
|
+
* ```
|
|
2109
|
+
*/
|
|
2110
|
+
function enumFn(values) {
|
|
2111
|
+
return new ValEnum(values);
|
|
2112
|
+
}
|
|
2113
|
+
// Export as 'enum' (reserved word workaround)
|
|
2114
|
+
export { enumFn as enum };
|
|
2115
|
+
/**
|
|
2116
|
+
* Creates a schema for a TypeScript native enum.
|
|
2117
|
+
*
|
|
2118
|
+
* @param enumObject - The TypeScript enum object
|
|
2119
|
+
* @returns A new native enum schema
|
|
2120
|
+
*
|
|
2121
|
+
* @example
|
|
2122
|
+
* ```typescript
|
|
2123
|
+
* enum Status { Active, Inactive }
|
|
2124
|
+
* const schema = v.nativeEnum(Status);
|
|
2125
|
+
*
|
|
2126
|
+
* schema.parse(Status.Active); // 0
|
|
2127
|
+
* schema.parse(Status.Inactive); // 1
|
|
2128
|
+
* schema.parse(2); // throws
|
|
2129
|
+
* ```
|
|
2130
|
+
*/
|
|
2131
|
+
export function nativeEnum(enumObject) {
|
|
2132
|
+
return new ValNativeEnum(enumObject);
|
|
2133
|
+
}
|
|
2134
|
+
// ============================================================================
|
|
2135
|
+
// Utility Functions
|
|
2136
|
+
// ============================================================================
|
|
2137
|
+
/**
|
|
2138
|
+
* Wraps an existing Standard Schema into a ValSchema.
|
|
2139
|
+
*
|
|
2140
|
+
* Useful for integrating schemas from other Standard Schema compliant libraries.
|
|
2141
|
+
*
|
|
2142
|
+
* @param schema - A Standard Schema compliant object
|
|
2143
|
+
* @returns A ValSchema instance with parse/safeParse methods
|
|
2144
|
+
*
|
|
2145
|
+
* @example
|
|
2146
|
+
* ```typescript
|
|
2147
|
+
* import { StringSchema } from 'valrs';
|
|
2148
|
+
*
|
|
2149
|
+
* const wrapped = v.wrap(StringSchema);
|
|
2150
|
+
* wrapped.parse('hello'); // 'hello'
|
|
2151
|
+
* ```
|
|
2152
|
+
*/
|
|
2153
|
+
export function wrap(schema) {
|
|
2154
|
+
const std = schema['~standard'];
|
|
2155
|
+
// Check if schema has JSON Schema support
|
|
2156
|
+
if (std.jsonSchema !== undefined) {
|
|
2157
|
+
const inputFn = (target) => std.jsonSchema.input({ target });
|
|
2158
|
+
const outputFn = (target) => std.jsonSchema.output({ target });
|
|
2159
|
+
return new ValSchema(std.validate, inputFn, outputFn);
|
|
2160
|
+
}
|
|
2161
|
+
// Fallback for schemas without JSON Schema
|
|
2162
|
+
return new ValSchema(std.validate, () => ({}));
|
|
2163
|
+
}
|
|
2164
|
+
// ============================================================================
|
|
2165
|
+
// Preprocess Function
|
|
2166
|
+
// ============================================================================
|
|
2167
|
+
/**
|
|
2168
|
+
* Preprocesses input before passing to the schema.
|
|
2169
|
+
*
|
|
2170
|
+
* Useful for coercing input types before validation.
|
|
2171
|
+
*
|
|
2172
|
+
* @param preprocessFn - Function to transform the raw input
|
|
2173
|
+
* @param schema - Schema to validate after preprocessing
|
|
2174
|
+
* @returns A new schema that preprocesses then validates
|
|
2175
|
+
*
|
|
2176
|
+
* @example
|
|
2177
|
+
* ```typescript
|
|
2178
|
+
* const schema = v.preprocess(
|
|
2179
|
+
* (val) => (typeof val === 'string' ? parseInt(val, 10) : val),
|
|
2180
|
+
* v.number()
|
|
2181
|
+
* );
|
|
2182
|
+
*
|
|
2183
|
+
* schema.parse('42'); // 42
|
|
2184
|
+
* schema.parse(42); // 42
|
|
2185
|
+
* ```
|
|
2186
|
+
*/
|
|
2187
|
+
export function preprocess(preprocessFn, schema) {
|
|
2188
|
+
return new ValPreprocessed(preprocessFn, schema);
|
|
2189
|
+
}
|
|
2190
|
+
// ============================================================================
|
|
2191
|
+
// Coerce Namespace
|
|
2192
|
+
// ============================================================================
|
|
2193
|
+
/**
|
|
2194
|
+
* Schema that coerces input to string using String().
|
|
2195
|
+
*/
|
|
2196
|
+
class ValCoerceString extends ValSchema {
|
|
2197
|
+
constructor() {
|
|
2198
|
+
super((value) => {
|
|
2199
|
+
// Handle null and undefined specially
|
|
2200
|
+
if (value === null) {
|
|
2201
|
+
return success('null');
|
|
2202
|
+
}
|
|
2203
|
+
if (value === undefined) {
|
|
2204
|
+
return success('undefined');
|
|
2205
|
+
}
|
|
2206
|
+
return success(String(value));
|
|
2207
|
+
}, (_target) => ({ type: 'string' }));
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Schema that coerces input to number using Number().
|
|
2212
|
+
*/
|
|
2213
|
+
class ValCoerceNumber extends ValSchema {
|
|
2214
|
+
constructor() {
|
|
2215
|
+
super((value) => {
|
|
2216
|
+
const coerced = Number(value);
|
|
2217
|
+
if (Number.isNaN(coerced)) {
|
|
2218
|
+
return fail('Expected a value that coerces to a valid number');
|
|
2219
|
+
}
|
|
2220
|
+
return success(coerced);
|
|
2221
|
+
}, (_target) => ({ type: 'number' }));
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Schema that coerces input to boolean.
|
|
2226
|
+
*
|
|
2227
|
+
* Truthy values become true, falsy values become false.
|
|
2228
|
+
*/
|
|
2229
|
+
class ValCoerceBoolean extends ValSchema {
|
|
2230
|
+
constructor() {
|
|
2231
|
+
super((value) => {
|
|
2232
|
+
return success(Boolean(value));
|
|
2233
|
+
}, (_target) => ({ type: 'boolean' }));
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Schema that coerces input to bigint using BigInt().
|
|
2238
|
+
*/
|
|
2239
|
+
class ValCoerceBigInt extends ValSchema {
|
|
2240
|
+
constructor() {
|
|
2241
|
+
super((value) => {
|
|
2242
|
+
try {
|
|
2243
|
+
// Handle string and number inputs
|
|
2244
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
2245
|
+
return success(BigInt(value));
|
|
2246
|
+
}
|
|
2247
|
+
if (typeof value === 'bigint') {
|
|
2248
|
+
return success(value);
|
|
2249
|
+
}
|
|
2250
|
+
return fail('Expected a value that coerces to bigint');
|
|
2251
|
+
}
|
|
2252
|
+
catch {
|
|
2253
|
+
return fail('Expected a value that coerces to bigint');
|
|
2254
|
+
}
|
|
2255
|
+
}, (_target) => ({ type: 'integer', description: 'Coerced BigInt' }));
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Schema that coerces input to Date using new Date().
|
|
2260
|
+
*/
|
|
2261
|
+
class ValCoerceDate extends ValSchema {
|
|
2262
|
+
constructor() {
|
|
2263
|
+
super((value) => {
|
|
2264
|
+
// Already a Date
|
|
2265
|
+
if (value instanceof Date) {
|
|
2266
|
+
if (Number.isNaN(value.getTime())) {
|
|
2267
|
+
return fail('Invalid Date');
|
|
2268
|
+
}
|
|
2269
|
+
return success(value);
|
|
2270
|
+
}
|
|
2271
|
+
// String or number input
|
|
2272
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
2273
|
+
const date = new Date(value);
|
|
2274
|
+
if (Number.isNaN(date.getTime())) {
|
|
2275
|
+
return fail('Invalid Date');
|
|
2276
|
+
}
|
|
2277
|
+
return success(date);
|
|
2278
|
+
}
|
|
2279
|
+
return fail('Expected a value that coerces to Date');
|
|
2280
|
+
}, (_target) => ({ type: 'string', format: 'date-time' }));
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Coercion schema builders.
|
|
2285
|
+
*
|
|
2286
|
+
* These schemas attempt to convert input values to the target type
|
|
2287
|
+
* before validation. Similar to Zod's coerce namespace.
|
|
2288
|
+
*
|
|
2289
|
+
* @example
|
|
2290
|
+
* ```typescript
|
|
2291
|
+
* v.coerce.string().parse(42); // '42'
|
|
2292
|
+
* v.coerce.number().parse('42'); // 42
|
|
2293
|
+
* v.coerce.boolean().parse(1); // true
|
|
2294
|
+
* v.coerce.date().parse('2024-01-01'); // Date object
|
|
2295
|
+
* ```
|
|
2296
|
+
*/
|
|
2297
|
+
export const coerce = {
|
|
2298
|
+
/**
|
|
2299
|
+
* Coerces any value to string using String().
|
|
2300
|
+
*/
|
|
2301
|
+
string: () => new ValCoerceString(),
|
|
2302
|
+
/**
|
|
2303
|
+
* Coerces any value to number using Number().
|
|
2304
|
+
* Fails if the result is NaN.
|
|
2305
|
+
*/
|
|
2306
|
+
number: () => new ValCoerceNumber(),
|
|
2307
|
+
/**
|
|
2308
|
+
* Coerces any value to boolean using Boolean().
|
|
2309
|
+
*/
|
|
2310
|
+
boolean: () => new ValCoerceBoolean(),
|
|
2311
|
+
/**
|
|
2312
|
+
* Coerces any value to bigint using BigInt().
|
|
2313
|
+
* Fails if the value cannot be converted.
|
|
2314
|
+
*/
|
|
2315
|
+
bigint: () => new ValCoerceBigInt(),
|
|
2316
|
+
/**
|
|
2317
|
+
* Coerces any value to Date using new Date().
|
|
2318
|
+
* Fails if the result is an Invalid Date.
|
|
2319
|
+
*/
|
|
2320
|
+
date: () => new ValCoerceDate(),
|
|
2321
|
+
};
|
|
2322
|
+
// ============================================================================
|
|
2323
|
+
// Phase 6: Streaming Validation Exports
|
|
2324
|
+
// ============================================================================
|
|
2325
|
+
// Re-export streaming functions for tree-shaking
|
|
2326
|
+
export { stream, streamLines, createMockStream, createChunkedStream };
|
|
2327
|
+
// ============================================================================
|
|
2328
|
+
// Default Export - The v Namespace
|
|
2329
|
+
// ============================================================================
|
|
2330
|
+
/**
|
|
2331
|
+
* The main valrs namespace, providing a Zod-compatible API.
|
|
2332
|
+
*
|
|
2333
|
+
* @example
|
|
2334
|
+
* ```typescript
|
|
2335
|
+
* import { v } from 'valrs';
|
|
2336
|
+
*
|
|
2337
|
+
* const schema = v.string();
|
|
2338
|
+
* schema.parse('hello');
|
|
2339
|
+
*
|
|
2340
|
+
* type MyType = v.infer<typeof schema>;
|
|
2341
|
+
* ```
|
|
2342
|
+
*/
|
|
2343
|
+
export const v = {
|
|
2344
|
+
// Primitive builders
|
|
2345
|
+
string,
|
|
2346
|
+
number,
|
|
2347
|
+
bigint,
|
|
2348
|
+
boolean: booleanFn,
|
|
2349
|
+
date,
|
|
2350
|
+
undefined: undefinedFn,
|
|
2351
|
+
null: nullFn,
|
|
2352
|
+
void: voidFn,
|
|
2353
|
+
any,
|
|
2354
|
+
unknown,
|
|
2355
|
+
never,
|
|
2356
|
+
// Composite types
|
|
2357
|
+
object,
|
|
2358
|
+
array,
|
|
2359
|
+
tuple,
|
|
2360
|
+
// Union and intersection
|
|
2361
|
+
union,
|
|
2362
|
+
discriminatedUnion,
|
|
2363
|
+
intersection,
|
|
2364
|
+
// Collections
|
|
2365
|
+
record,
|
|
2366
|
+
map,
|
|
2367
|
+
set,
|
|
2368
|
+
// Literals and enums
|
|
2369
|
+
literal,
|
|
2370
|
+
enum: enumFn,
|
|
2371
|
+
nativeEnum,
|
|
2372
|
+
// Integer types
|
|
2373
|
+
int32,
|
|
2374
|
+
int64,
|
|
2375
|
+
uint32,
|
|
2376
|
+
uint64,
|
|
2377
|
+
float32,
|
|
2378
|
+
float64,
|
|
2379
|
+
// Transform and refinement utilities
|
|
2380
|
+
preprocess,
|
|
2381
|
+
coerce,
|
|
2382
|
+
// Utilities
|
|
2383
|
+
wrap,
|
|
2384
|
+
// Streaming validation (Phase 6)
|
|
2385
|
+
stream,
|
|
2386
|
+
streamLines,
|
|
2387
|
+
createMockStream,
|
|
2388
|
+
createChunkedStream,
|
|
2389
|
+
// Error formatting (Phase 7)
|
|
2390
|
+
setErrorMap: setErrorMapFn,
|
|
2391
|
+
getErrorMap: getErrorMapFn,
|
|
2392
|
+
resetErrorMap: resetErrorMapFn,
|
|
2393
|
+
};
|
|
2394
|
+
// Also export individual functions for tree-shaking
|
|
2395
|
+
export default v;
|
|
2396
|
+
//# sourceMappingURL=v.js.map
|