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/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