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/schema.js ADDED
@@ -0,0 +1,1179 @@
1
+ /**
2
+ * Base schema class providing the fluent API wrapper around Standard Schema.
3
+ *
4
+ * This module provides the `ValSchema` base class that all schema types extend.
5
+ * It wraps the internal Standard Schema implementation and exposes Zod-like methods.
6
+ */
7
+ import { isValidationSuccess } from './types';
8
+ import { ValError } from './error';
9
+ import { VENDOR, VERSION, success } from './factory';
10
+ import { compileSchema } from './compiler';
11
+ /**
12
+ * Type guard to check if a SafeParseResult is successful.
13
+ */
14
+ export function isSafeParseSuccess(result) {
15
+ return result.success === true;
16
+ }
17
+ /**
18
+ * Type guard to check if a SafeParseResult is a failure.
19
+ */
20
+ export function isSafeParseError(result) {
21
+ return result.success === false;
22
+ }
23
+ /**
24
+ * Base schema class that provides the Zod-compatible API.
25
+ *
26
+ * All schema types extend this class, inheriting methods like `parse()`,
27
+ * `safeParse()`, and maintaining Standard Schema compliance via `~standard`.
28
+ *
29
+ * @template Input - The expected input type
30
+ * @template Output - The validated output type (defaults to Input)
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const schema = v.string();
35
+ *
36
+ * // Throws ValError on failure
37
+ * const value = schema.parse('hello');
38
+ *
39
+ * // Returns { success, data } or { success, error }
40
+ * const result = schema.safeParse('hello');
41
+ * if (result.success) {
42
+ * console.log(result.data);
43
+ * }
44
+ * ```
45
+ */
46
+ export class ValSchema {
47
+ /**
48
+ * Standard Schema interface for interoperability.
49
+ *
50
+ * This property makes ValSchema compatible with any library that
51
+ * supports the Standard Schema specification.
52
+ */
53
+ '~standard';
54
+ /**
55
+ * Compiled validator for fast boolean checks.
56
+ * Lazily initialized on first check() call.
57
+ */
58
+ _compiledCheck;
59
+ /**
60
+ * Whether this schema or any of its children contain transforms.
61
+ * When true, validation must go through WASM to apply transforms.
62
+ */
63
+ _hasTransforms = false;
64
+ /**
65
+ * Returns whether this schema contains transforms.
66
+ * Used internally to determine validation path.
67
+ */
68
+ hasTransforms() {
69
+ return this._hasTransforms;
70
+ }
71
+ /**
72
+ * Creates a new ValSchema wrapping a validation function.
73
+ *
74
+ * @param validateFn - Function that validates input values
75
+ * @param inputJsonSchemaFn - Function that generates JSON Schema for input type
76
+ * @param outputJsonSchemaFn - Function that generates JSON Schema for output type
77
+ */
78
+ constructor(validateFn, inputJsonSchemaFn, outputJsonSchemaFn) {
79
+ const outputFn = outputJsonSchemaFn ?? inputJsonSchemaFn;
80
+ this['~standard'] = {
81
+ version: VERSION,
82
+ vendor: VENDOR,
83
+ validate: validateFn,
84
+ jsonSchema: {
85
+ input: (options) => inputJsonSchemaFn(options.target),
86
+ output: (options) => outputFn(options.target),
87
+ },
88
+ };
89
+ }
90
+ /**
91
+ * Parses the input and returns the validated value.
92
+ *
93
+ * Throws a `ValError` if validation fails.
94
+ *
95
+ * @param data - The value to validate
96
+ * @returns The validated value with the correct type
97
+ * @throws {ValError} If validation fails
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const name = v.string().parse('Alice'); // 'Alice'
102
+ * const age = v.number().parse('42'); // throws ValError
103
+ * ```
104
+ */
105
+ parse(data) {
106
+ const result = this['~standard'].validate(data);
107
+ if (isValidationSuccess(result)) {
108
+ return result.value;
109
+ }
110
+ throw new ValError(result.issues);
111
+ }
112
+ /**
113
+ * Parses the input and returns a result object.
114
+ *
115
+ * Never throws. Returns `{ success: true, data }` on success,
116
+ * or `{ success: false, error }` on failure.
117
+ *
118
+ * @param data - The value to validate
119
+ * @returns A result object indicating success or failure
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const result = v.string().safeParse(input);
124
+ *
125
+ * if (result.success) {
126
+ * console.log(result.data); // The validated string
127
+ * } else {
128
+ * console.log(result.error.issues); // Array of validation issues
129
+ * }
130
+ * ```
131
+ */
132
+ safeParse(data) {
133
+ // Fast path: use JS-compiled check for non-transform schemas
134
+ if (!this._hasTransforms && this.check(data)) {
135
+ return {
136
+ success: true,
137
+ data: data,
138
+ };
139
+ }
140
+ // Full validation path (WASM) for transforms or when check fails
141
+ const result = this['~standard'].validate(data);
142
+ if (isValidationSuccess(result)) {
143
+ return {
144
+ success: true,
145
+ data: result.value,
146
+ };
147
+ }
148
+ return {
149
+ success: false,
150
+ error: new ValError(result.issues),
151
+ };
152
+ }
153
+ /**
154
+ * Alias for `safeParse()` that matches Standard Schema naming.
155
+ */
156
+ validate(data) {
157
+ return this.safeParse(data);
158
+ }
159
+ /**
160
+ * Returns the JSON Schema for this schema's input type.
161
+ *
162
+ * @param target - The JSON Schema version (e.g., 'draft-2020-12', 'draft-07', 'openapi-3.0')
163
+ * @returns A JSON Schema object
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * const schema = v.string();
168
+ * const jsonSchema = schema.toJsonSchema('draft-2020-12');
169
+ * // { type: 'string' }
170
+ * ```
171
+ */
172
+ toJsonSchema(target = 'draft-2020-12') {
173
+ return this['~standard'].jsonSchema.input({ target });
174
+ }
175
+ /**
176
+ * Fast boolean check using JS-compiled validator.
177
+ *
178
+ * Returns true if the data passes validation, false otherwise.
179
+ * Does NOT apply transforms - use safeParse() for that.
180
+ *
181
+ * @param data - The value to check
182
+ * @returns true if valid, false otherwise
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const schema = v.string();
187
+ * schema.check('hello'); // true
188
+ * schema.check(42); // false
189
+ * ```
190
+ */
191
+ check(data) {
192
+ if (!this._compiledCheck) {
193
+ try {
194
+ const jsonSchema = this.toJsonSchema();
195
+ this._compiledCheck = compileSchema(jsonSchema);
196
+ }
197
+ catch {
198
+ // Fallback: always return true if compilation fails
199
+ this._compiledCheck = () => true;
200
+ }
201
+ }
202
+ return this._compiledCheck(data);
203
+ }
204
+ /**
205
+ * Checks if the schema is optional.
206
+ *
207
+ * Override in subclasses that support optionality.
208
+ */
209
+ isOptional() {
210
+ return false;
211
+ }
212
+ /**
213
+ * Checks if the schema is nullable.
214
+ *
215
+ * Override in subclasses that support nullability.
216
+ */
217
+ isNullable() {
218
+ return false;
219
+ }
220
+ /**
221
+ * Makes the schema accept undefined values.
222
+ *
223
+ * @returns A new schema that accepts T | undefined
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * const schema = v.string().optional();
228
+ * schema.parse(undefined); // undefined
229
+ * schema.parse('hello'); // 'hello'
230
+ * ```
231
+ */
232
+ optional() {
233
+ return new ValOptional(this);
234
+ }
235
+ /**
236
+ * Makes the schema accept null values.
237
+ *
238
+ * @returns A new schema that accepts T | null
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const schema = v.string().nullable();
243
+ * schema.parse(null); // null
244
+ * schema.parse('hello'); // 'hello'
245
+ * ```
246
+ */
247
+ nullable() {
248
+ return new ValNullable(this);
249
+ }
250
+ /**
251
+ * Makes the schema accept null or undefined values.
252
+ *
253
+ * @returns A new schema that accepts T | null | undefined
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * const schema = v.string().nullish();
258
+ * schema.parse(null); // null
259
+ * schema.parse(undefined); // undefined
260
+ * schema.parse('hello'); // 'hello'
261
+ * ```
262
+ */
263
+ nullish() {
264
+ return new ValNullish(this);
265
+ }
266
+ /**
267
+ * Provides a default value when the input is undefined.
268
+ *
269
+ * @param defaultValue - The default value or a function that returns it
270
+ * @returns A new schema that uses the default when input is undefined
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * const schema = v.string().default('anonymous');
275
+ * schema.parse(undefined); // 'anonymous'
276
+ * schema.parse('hello'); // 'hello'
277
+ * ```
278
+ */
279
+ default(defaultValue) {
280
+ return new ValDefault(this, defaultValue);
281
+ }
282
+ /**
283
+ * Provides a fallback value when parsing fails.
284
+ *
285
+ * @param catchValue - The fallback value or a function that returns it
286
+ * @returns A new schema that uses the fallback on parse error
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * const schema = v.number().catch(0);
291
+ * schema.parse('not a number'); // 0
292
+ * schema.parse(42); // 42
293
+ * ```
294
+ */
295
+ catch(catchValue) {
296
+ return new ValCatch(this, catchValue);
297
+ }
298
+ /**
299
+ * Creates an array schema with this schema as the element type.
300
+ *
301
+ * This is an alternative syntax for `v.array(schema)`.
302
+ *
303
+ * @returns A new array schema
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * const schema = v.string().array();
308
+ * schema.parse(['a', 'b', 'c']); // ['a', 'b', 'c']
309
+ *
310
+ * type Arr = v.infer<typeof schema>; // string[]
311
+ * ```
312
+ */
313
+ array() {
314
+ return new ValArray(this);
315
+ }
316
+ /**
317
+ * Creates a union of this schema with another schema.
318
+ *
319
+ * @param other - The other schema to union with
320
+ * @returns A new union schema
321
+ *
322
+ * @example
323
+ * ```typescript
324
+ * const schema = v.string().or(v.number());
325
+ * schema.parse('hello'); // 'hello'
326
+ * schema.parse(42); // 42
327
+ *
328
+ * type T = v.infer<typeof schema>; // string | number
329
+ * ```
330
+ */
331
+ or(other) {
332
+ return new ValUnion([this, other]);
333
+ }
334
+ /**
335
+ * Creates an intersection of this schema with another schema.
336
+ *
337
+ * @param other - The other schema to intersect with
338
+ * @returns A new intersection schema
339
+ *
340
+ * @example
341
+ * ```typescript
342
+ * const A = v.object({ a: v.string() });
343
+ * const B = v.object({ b: v.number() });
344
+ * const AB = A.and(B);
345
+ *
346
+ * type AB = v.infer<typeof AB>; // { a: string } & { b: number }
347
+ * ```
348
+ */
349
+ and(other) {
350
+ return new ValIntersection(this, other);
351
+ }
352
+ // ============================================================================
353
+ // Transform and Refinement Methods
354
+ // ============================================================================
355
+ /**
356
+ * Transforms the output value after validation.
357
+ *
358
+ * Changes the output type of the schema.
359
+ *
360
+ * @param fn - Transform function that receives validated value
361
+ * @returns A new schema with transformed output type
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const schema = v.string().transform(s => parseInt(s, 10));
366
+ * schema.parse('42'); // 42 (number)
367
+ *
368
+ * type Input = v.input<typeof schema>; // string
369
+ * type Output = v.output<typeof schema>; // number
370
+ * ```
371
+ */
372
+ transform(fn) {
373
+ return new ValTransformed(this, fn);
374
+ }
375
+ /**
376
+ * Adds a custom validation refinement.
377
+ *
378
+ * Does not change the output type.
379
+ *
380
+ * @param predicate - Function that returns true if valid, false otherwise
381
+ * @param messageOrOptions - Error message or options object
382
+ * @returns A new schema with the refinement
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * const schema = v.string().refine(
387
+ * s => s.length > 0,
388
+ * 'Required'
389
+ * );
390
+ *
391
+ * const schemaWithPath = v.string().refine(
392
+ * s => s.includes('@'),
393
+ * { message: 'Must contain @', path: ['email'] }
394
+ * );
395
+ * ```
396
+ */
397
+ refine(predicate, messageOrOptions = 'Invalid value') {
398
+ const options = typeof messageOrOptions === 'string'
399
+ ? { message: messageOrOptions }
400
+ : messageOrOptions;
401
+ return new ValRefined(this, predicate, options);
402
+ }
403
+ /**
404
+ * Adds advanced refinement that can add multiple issues.
405
+ *
406
+ * Provides a context object for adding validation issues.
407
+ *
408
+ * @param fn - Refinement function with context
409
+ * @returns A new schema with the refinement
410
+ *
411
+ * @example
412
+ * ```typescript
413
+ * const schema = v.string().superRefine((val, ctx) => {
414
+ * if (val.length < 5) {
415
+ * ctx.addIssue({
416
+ * code: 'custom',
417
+ * message: 'Too short',
418
+ * path: [],
419
+ * });
420
+ * }
421
+ * if (val.length > 100) {
422
+ * ctx.addIssue({
423
+ * code: 'custom',
424
+ * message: 'Too long',
425
+ * path: [],
426
+ * });
427
+ * }
428
+ * });
429
+ * ```
430
+ */
431
+ superRefine(fn) {
432
+ return new ValSuperRefined(this, fn);
433
+ }
434
+ /**
435
+ * Pipes the output of this schema into another schema.
436
+ *
437
+ * Useful for chaining transforms with additional validation.
438
+ *
439
+ * @param schema - The schema to pipe into
440
+ * @returns A new schema that validates with both schemas
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * const schema = v.string()
445
+ * .transform(s => parseInt(s, 10))
446
+ * .pipe(v.number().positive());
447
+ *
448
+ * schema.parse('42'); // 42
449
+ * schema.parse('-5'); // throws (not positive)
450
+ * ```
451
+ */
452
+ pipe(schema) {
453
+ return new ValPiped(this, schema);
454
+ }
455
+ // ============================================================================
456
+ // Async Parsing Methods
457
+ // ============================================================================
458
+ /**
459
+ * Asynchronously parses the input value.
460
+ *
461
+ * Required when using async transforms or refinements.
462
+ *
463
+ * @param data - The value to validate
464
+ * @returns Promise resolving to the validated value
465
+ * @throws {ValError} If validation fails
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * const schema = v.string().refine(async (s) => {
470
+ * return await checkIfExists(s);
471
+ * }, 'Not found');
472
+ *
473
+ * const value = await schema.parseAsync('test');
474
+ * ```
475
+ */
476
+ async parseAsync(data) {
477
+ const result = await this.safeParseAsync(data);
478
+ if (result.success) {
479
+ return result.data;
480
+ }
481
+ throw result.error;
482
+ }
483
+ /**
484
+ * Asynchronously parses the input and returns a result object.
485
+ *
486
+ * Never throws. Required when using async transforms or refinements.
487
+ *
488
+ * @param data - The value to validate
489
+ * @returns Promise resolving to result object
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * const schema = v.string().transform(async (s) => {
494
+ * return await lookupValue(s);
495
+ * });
496
+ *
497
+ * const result = await schema.safeParseAsync('key');
498
+ * if (result.success) {
499
+ * console.log(result.data);
500
+ * }
501
+ * ```
502
+ */
503
+ async safeParseAsync(data) {
504
+ const result = this['~standard'].validate(data);
505
+ // Handle both sync and async validation results
506
+ const resolvedResult = result instanceof Promise ? await result : result;
507
+ if (isValidationSuccess(resolvedResult)) {
508
+ return {
509
+ success: true,
510
+ data: resolvedResult.value,
511
+ };
512
+ }
513
+ return {
514
+ success: false,
515
+ error: new ValError(resolvedResult.issues),
516
+ };
517
+ }
518
+ }
519
+ /**
520
+ * Schema for array values with validation methods.
521
+ *
522
+ * Supports chainable methods like `.min()`, `.max()`, `.length()`, `.nonempty()`.
523
+ * Each method returns a new schema instance (immutable).
524
+ *
525
+ * @template S - The element schema type
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * const schema = v.array(v.string());
530
+ * schema.parse(['a', 'b', 'c']); // ['a', 'b', 'c']
531
+ *
532
+ * const nonEmpty = v.array(v.number()).nonempty();
533
+ * nonEmpty.parse([1, 2]); // [1, 2]
534
+ * nonEmpty.parse([]); // throws
535
+ * ```
536
+ */
537
+ export class ValArray extends ValSchema {
538
+ element;
539
+ validators;
540
+ constructor(elementSchema, validators = []) {
541
+ const capturedValidators = validators;
542
+ const validateFn = (value) => {
543
+ if (!Array.isArray(value)) {
544
+ return { issues: [{ message: 'Expected array' }] };
545
+ }
546
+ const result = [];
547
+ const issues = [];
548
+ for (let i = 0; i < value.length; i++) {
549
+ const elementResult = elementSchema['~standard'].validate(value[i]);
550
+ if (elementResult.issues !== undefined) {
551
+ for (const issue of elementResult.issues) {
552
+ issues.push({
553
+ message: issue.message,
554
+ path: [i, ...(issue.path ?? [])],
555
+ });
556
+ }
557
+ }
558
+ else {
559
+ result.push(elementResult.value);
560
+ }
561
+ }
562
+ if (issues.length > 0) {
563
+ return { issues };
564
+ }
565
+ // Run array-level validators
566
+ for (let i = 0; i < capturedValidators.length; i++) {
567
+ const validator = capturedValidators[i];
568
+ if (validator !== undefined) {
569
+ const issue = validator(result);
570
+ if (issue !== null) {
571
+ return issue;
572
+ }
573
+ }
574
+ }
575
+ return { value: result };
576
+ };
577
+ const inputJsonSchemaFn = (target) => ({
578
+ type: 'array',
579
+ items: elementSchema['~standard'].jsonSchema.input({ target }),
580
+ });
581
+ const outputJsonSchemaFn = (target) => ({
582
+ type: 'array',
583
+ items: elementSchema['~standard'].jsonSchema.output({ target }),
584
+ });
585
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
586
+ this.element = elementSchema;
587
+ this.validators = validators;
588
+ this._hasTransforms = elementSchema.hasTransforms() || validators.length > 0;
589
+ }
590
+ /**
591
+ * Creates a copy of this schema with additional validators.
592
+ */
593
+ clone(additionalValidators) {
594
+ return new ValArray(this.element, [...this.validators, ...additionalValidators]);
595
+ }
596
+ /**
597
+ * Requires array to have at least `length` elements.
598
+ */
599
+ min(length, message) {
600
+ return this.clone([
601
+ (arr) => arr.length >= length
602
+ ? null
603
+ : { issues: [{ message: message ?? `Array must have at least ${length} element(s)` }] },
604
+ ]);
605
+ }
606
+ /**
607
+ * Requires array to have at most `length` elements.
608
+ */
609
+ max(length, message) {
610
+ return this.clone([
611
+ (arr) => arr.length <= length
612
+ ? null
613
+ : { issues: [{ message: message ?? `Array must have at most ${length} element(s)` }] },
614
+ ]);
615
+ }
616
+ /**
617
+ * Requires array to have exactly `len` elements.
618
+ */
619
+ length(len, message) {
620
+ return this.clone([
621
+ (arr) => arr.length === len
622
+ ? null
623
+ : { issues: [{ message: message ?? `Array must have exactly ${len} element(s)` }] },
624
+ ]);
625
+ }
626
+ /**
627
+ * Requires array to be non-empty (at least 1 element).
628
+ */
629
+ nonempty(message) {
630
+ return this.min(1, message ?? 'Array must not be empty');
631
+ }
632
+ }
633
+ /**
634
+ * Schema for union types (one of multiple schemas).
635
+ *
636
+ * @template T - Array of member schemas
637
+ *
638
+ * @example
639
+ * ```typescript
640
+ * const schema = v.union([v.string(), v.number()]);
641
+ * schema.parse('hello'); // 'hello'
642
+ * schema.parse(42); // 42
643
+ *
644
+ * type U = v.infer<typeof schema>; // string | number
645
+ * ```
646
+ */
647
+ export class ValUnion extends ValSchema {
648
+ options;
649
+ constructor(options) {
650
+ const validateFn = (value) => {
651
+ for (const option of options) {
652
+ const result = option['~standard'].validate(value);
653
+ if (result.issues === undefined) {
654
+ return { value: result.value };
655
+ }
656
+ }
657
+ return {
658
+ issues: [{ message: `Invalid union: none of ${options.length} variants matched` }],
659
+ };
660
+ };
661
+ const inputJsonSchemaFn = (target) => ({
662
+ oneOf: options.map((o) => o['~standard'].jsonSchema.input({ target })),
663
+ });
664
+ const outputJsonSchemaFn = (target) => ({
665
+ oneOf: options.map((o) => o['~standard'].jsonSchema.output({ target })),
666
+ });
667
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
668
+ this.options = options;
669
+ this._hasTransforms = options.some(s => s.hasTransforms());
670
+ }
671
+ }
672
+ // ============================================================================
673
+ // ValIntersection Class
674
+ // ============================================================================
675
+ /**
676
+ * Schema for intersection types (all of multiple schemas).
677
+ *
678
+ * @template L - Left schema
679
+ * @template R - Right schema
680
+ *
681
+ * @example
682
+ * ```typescript
683
+ * const A = v.object({ a: v.string() });
684
+ * const B = v.object({ b: v.number() });
685
+ * const AB = v.intersection(A, B);
686
+ *
687
+ * type AB = v.infer<typeof AB>; // { a: string } & { b: number }
688
+ * ```
689
+ */
690
+ export class ValIntersection extends ValSchema {
691
+ left;
692
+ right;
693
+ constructor(left, right) {
694
+ const validateFn = (value) => {
695
+ const leftResult = left['~standard'].validate(value);
696
+ if (leftResult.issues !== undefined) {
697
+ return leftResult;
698
+ }
699
+ const rightResult = right['~standard'].validate(value);
700
+ if (rightResult.issues !== undefined) {
701
+ return rightResult;
702
+ }
703
+ // Merge results for objects
704
+ if (typeof leftResult.value === 'object' &&
705
+ leftResult.value !== null &&
706
+ typeof rightResult.value === 'object' &&
707
+ rightResult.value !== null) {
708
+ return { value: { ...leftResult.value, ...rightResult.value } };
709
+ }
710
+ // For non-objects, just return the left result (both passed)
711
+ return { value: leftResult.value };
712
+ };
713
+ const inputJsonSchemaFn = (target) => ({
714
+ allOf: [
715
+ left['~standard'].jsonSchema.input({ target }),
716
+ right['~standard'].jsonSchema.input({ target }),
717
+ ],
718
+ });
719
+ const outputJsonSchemaFn = (target) => ({
720
+ allOf: [
721
+ left['~standard'].jsonSchema.output({ target }),
722
+ right['~standard'].jsonSchema.output({ target }),
723
+ ],
724
+ });
725
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
726
+ this.left = left;
727
+ this.right = right;
728
+ this._hasTransforms = left.hasTransforms() || right.hasTransforms();
729
+ }
730
+ }
731
+ /**
732
+ * Schema wrapper that makes the inner schema optional (accepts undefined).
733
+ */
734
+ export class ValOptional extends ValSchema {
735
+ innerSchema;
736
+ constructor(schema) {
737
+ const validateFn = (value) => {
738
+ if (value === undefined) {
739
+ return { value: undefined };
740
+ }
741
+ return schema['~standard'].validate(value);
742
+ };
743
+ const inputJsonSchemaFn = (target) => {
744
+ const innerSchema = schema['~standard'].jsonSchema.input({ target });
745
+ return { oneOf: [innerSchema, { type: 'null' }] };
746
+ };
747
+ const outputJsonSchemaFn = (target) => {
748
+ const innerSchema = schema['~standard'].jsonSchema.output({ target });
749
+ return { oneOf: [innerSchema, { type: 'null' }] };
750
+ };
751
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
752
+ this.innerSchema = schema;
753
+ }
754
+ isOptional() {
755
+ return true;
756
+ }
757
+ /**
758
+ * Returns the inner schema (unwrapped).
759
+ */
760
+ unwrap() {
761
+ return this.innerSchema;
762
+ }
763
+ }
764
+ /**
765
+ * Schema wrapper that makes the inner schema nullable (accepts null).
766
+ */
767
+ export class ValNullable extends ValSchema {
768
+ innerSchema;
769
+ constructor(schema) {
770
+ const validateFn = (value) => {
771
+ if (value === null) {
772
+ return { value: null };
773
+ }
774
+ return schema['~standard'].validate(value);
775
+ };
776
+ const inputJsonSchemaFn = (target) => {
777
+ const innerSchema = schema['~standard'].jsonSchema.input({ target });
778
+ return { oneOf: [innerSchema, { type: 'null' }] };
779
+ };
780
+ const outputJsonSchemaFn = (target) => {
781
+ const innerSchema = schema['~standard'].jsonSchema.output({ target });
782
+ return { oneOf: [innerSchema, { type: 'null' }] };
783
+ };
784
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
785
+ this.innerSchema = schema;
786
+ }
787
+ isNullable() {
788
+ return true;
789
+ }
790
+ /**
791
+ * Returns the inner schema (unwrapped).
792
+ */
793
+ unwrap() {
794
+ return this.innerSchema;
795
+ }
796
+ }
797
+ /**
798
+ * Schema wrapper that makes the inner schema nullish (accepts null or undefined).
799
+ */
800
+ export class ValNullish extends ValSchema {
801
+ innerSchema;
802
+ constructor(schema) {
803
+ const validateFn = (value) => {
804
+ if (value === null) {
805
+ return { value: null };
806
+ }
807
+ if (value === undefined) {
808
+ return { value: undefined };
809
+ }
810
+ return schema['~standard'].validate(value);
811
+ };
812
+ const inputJsonSchemaFn = (target) => {
813
+ const innerSchema = schema['~standard'].jsonSchema.input({ target });
814
+ return { oneOf: [innerSchema, { type: 'null' }] };
815
+ };
816
+ const outputJsonSchemaFn = (target) => {
817
+ const innerSchema = schema['~standard'].jsonSchema.output({ target });
818
+ return { oneOf: [innerSchema, { type: 'null' }] };
819
+ };
820
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
821
+ this.innerSchema = schema;
822
+ }
823
+ isOptional() {
824
+ return true;
825
+ }
826
+ isNullable() {
827
+ return true;
828
+ }
829
+ /**
830
+ * Returns the inner schema (unwrapped).
831
+ */
832
+ unwrap() {
833
+ return this.innerSchema;
834
+ }
835
+ }
836
+ /**
837
+ * Schema wrapper that provides a default value when input is undefined.
838
+ */
839
+ export class ValDefault extends ValSchema {
840
+ innerSchema;
841
+ constructor(schema, defaultValue) {
842
+ const capturedDefault = defaultValue;
843
+ const validateFn = (value) => {
844
+ if (value === undefined) {
845
+ const resolved = typeof capturedDefault === 'function'
846
+ ? capturedDefault()
847
+ : capturedDefault;
848
+ return { value: resolved };
849
+ }
850
+ return schema['~standard'].validate(value);
851
+ };
852
+ const inputJsonSchemaFn = (target) => {
853
+ return schema['~standard'].jsonSchema.input({ target });
854
+ };
855
+ const outputJsonSchemaFn = (target) => {
856
+ return schema['~standard'].jsonSchema.output({ target });
857
+ };
858
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
859
+ this.innerSchema = schema;
860
+ this._hasTransforms = true;
861
+ }
862
+ /**
863
+ * Removes the default wrapper.
864
+ */
865
+ removeDefault() {
866
+ return this.innerSchema;
867
+ }
868
+ }
869
+ /**
870
+ * Schema wrapper that provides a catch value when parsing fails.
871
+ */
872
+ export class ValCatch extends ValSchema {
873
+ innerSchema;
874
+ constructor(schema, catchValue) {
875
+ const capturedCatch = catchValue;
876
+ const validateFn = (value) => {
877
+ const result = schema['~standard'].validate(value);
878
+ if (result.issues !== undefined) {
879
+ const resolved = typeof capturedCatch === 'function'
880
+ ? capturedCatch()
881
+ : capturedCatch;
882
+ return { value: resolved };
883
+ }
884
+ return result;
885
+ };
886
+ const inputJsonSchemaFn = (target) => {
887
+ return schema['~standard'].jsonSchema.input({ target });
888
+ };
889
+ const outputJsonSchemaFn = (target) => {
890
+ return schema['~standard'].jsonSchema.output({ target });
891
+ };
892
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
893
+ this.innerSchema = schema;
894
+ this._hasTransforms = true;
895
+ }
896
+ /**
897
+ * Removes the catch wrapper.
898
+ */
899
+ removeCatch() {
900
+ return this.innerSchema;
901
+ }
902
+ }
903
+ // ============================================================================
904
+ // Transform and Refinement Schema Classes
905
+ // ============================================================================
906
+ /**
907
+ * Schema that applies a transform function after validation.
908
+ *
909
+ * Changes the output type of the schema.
910
+ *
911
+ * @template Input - The original input type
912
+ * @template Middle - The intermediate validated type
913
+ * @template Output - The final transformed output type
914
+ */
915
+ export class ValTransformed extends ValSchema {
916
+ constructor(schema, fn) {
917
+ const capturedFn = fn;
918
+ const capturedSchema = schema;
919
+ const validateFn = (value) => {
920
+ const result = capturedSchema['~standard'].validate(value);
921
+ // Handle async inner validation
922
+ if (result instanceof Promise) {
923
+ return result.then((resolvedResult) => {
924
+ if (resolvedResult.issues !== undefined) {
925
+ return resolvedResult;
926
+ }
927
+ const transformed = capturedFn(resolvedResult.value);
928
+ if (transformed instanceof Promise) {
929
+ return transformed.then((v) => success(v));
930
+ }
931
+ return success(transformed);
932
+ });
933
+ }
934
+ if (result.issues !== undefined) {
935
+ return result;
936
+ }
937
+ // Apply transform
938
+ const transformed = capturedFn(result.value);
939
+ if (transformed instanceof Promise) {
940
+ return transformed.then((v) => success(v));
941
+ }
942
+ return success(transformed);
943
+ };
944
+ const inputJsonSchemaFn = (target) => {
945
+ return capturedSchema['~standard'].jsonSchema.input({ target });
946
+ };
947
+ // Output JSON schema is not available for arbitrary transforms
948
+ const outputJsonSchemaFn = (_target) => {
949
+ return {};
950
+ };
951
+ // Cast to sync validate function - async is handled by parseAsync/safeParseAsync
952
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
953
+ this._hasTransforms = true;
954
+ }
955
+ }
956
+ /**
957
+ * Schema that adds a refinement predicate.
958
+ *
959
+ * Does not change the output type.
960
+ *
961
+ * @template Input - The input type
962
+ * @template Output - The output type
963
+ */
964
+ export class ValRefined extends ValSchema {
965
+ constructor(schema, predicate, options) {
966
+ const capturedPredicate = predicate;
967
+ const capturedOptions = options;
968
+ const capturedSchema = schema;
969
+ const validateFn = (value) => {
970
+ const result = capturedSchema['~standard'].validate(value);
971
+ // Handle async inner validation
972
+ if (result instanceof Promise) {
973
+ return result.then((resolvedResult) => {
974
+ if (resolvedResult.issues !== undefined) {
975
+ return resolvedResult;
976
+ }
977
+ return applyRefinement(resolvedResult.value);
978
+ });
979
+ }
980
+ if (result.issues !== undefined) {
981
+ return result;
982
+ }
983
+ return applyRefinement(result.value);
984
+ function applyRefinement(validatedValue) {
985
+ const predicateResult = capturedPredicate(validatedValue);
986
+ if (predicateResult instanceof Promise) {
987
+ return predicateResult.then((isValid) => {
988
+ if (!isValid) {
989
+ const issue = {
990
+ message: capturedOptions.message ?? 'Invalid value',
991
+ };
992
+ if (capturedOptions.path !== undefined) {
993
+ issue.path = capturedOptions.path;
994
+ }
995
+ return { issues: [issue] };
996
+ }
997
+ return success(validatedValue);
998
+ });
999
+ }
1000
+ if (!predicateResult) {
1001
+ const issue = {
1002
+ message: capturedOptions.message ?? 'Invalid value',
1003
+ };
1004
+ if (capturedOptions.path !== undefined) {
1005
+ issue.path = capturedOptions.path;
1006
+ }
1007
+ return { issues: [issue] };
1008
+ }
1009
+ return success(validatedValue);
1010
+ }
1011
+ };
1012
+ const inputJsonSchemaFn = (target) => {
1013
+ return capturedSchema['~standard'].jsonSchema.input({ target });
1014
+ };
1015
+ const outputJsonSchemaFn = (target) => {
1016
+ return capturedSchema['~standard'].jsonSchema.output({ target });
1017
+ };
1018
+ // Cast to sync validate function - async is handled by parseAsync/safeParseAsync
1019
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
1020
+ this._hasTransforms = true;
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Schema that applies a superRefine function for advanced validation.
1025
+ *
1026
+ * Allows adding multiple issues via the context.
1027
+ *
1028
+ * @template Input - The input type
1029
+ * @template Output - The output type
1030
+ */
1031
+ export class ValSuperRefined extends ValSchema {
1032
+ constructor(schema, fn) {
1033
+ const capturedFn = fn;
1034
+ const capturedSchema = schema;
1035
+ const validateFn = (value) => {
1036
+ const result = capturedSchema['~standard'].validate(value);
1037
+ // Handle async inner validation
1038
+ if (result instanceof Promise) {
1039
+ return result.then((resolvedResult) => {
1040
+ if (resolvedResult.issues !== undefined) {
1041
+ return resolvedResult;
1042
+ }
1043
+ return applySuperRefine(resolvedResult.value);
1044
+ });
1045
+ }
1046
+ if (result.issues !== undefined) {
1047
+ return result;
1048
+ }
1049
+ return applySuperRefine(result.value);
1050
+ function applySuperRefine(validatedValue) {
1051
+ const issues = [];
1052
+ const ctx = {
1053
+ addIssue(issue) {
1054
+ const newIssue = {
1055
+ message: issue.message,
1056
+ };
1057
+ if (issue.path !== undefined) {
1058
+ newIssue.path = issue.path;
1059
+ }
1060
+ issues.push(newIssue);
1061
+ },
1062
+ };
1063
+ const fnResult = capturedFn(validatedValue, ctx);
1064
+ if (fnResult instanceof Promise) {
1065
+ return fnResult.then(() => {
1066
+ if (issues.length > 0) {
1067
+ return { issues };
1068
+ }
1069
+ return success(validatedValue);
1070
+ });
1071
+ }
1072
+ if (issues.length > 0) {
1073
+ return { issues };
1074
+ }
1075
+ return success(validatedValue);
1076
+ }
1077
+ };
1078
+ const inputJsonSchemaFn = (target) => {
1079
+ return capturedSchema['~standard'].jsonSchema.input({ target });
1080
+ };
1081
+ const outputJsonSchemaFn = (target) => {
1082
+ return capturedSchema['~standard'].jsonSchema.output({ target });
1083
+ };
1084
+ // Cast to sync validate function - async is handled by parseAsync/safeParseAsync
1085
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
1086
+ this._hasTransforms = true;
1087
+ }
1088
+ }
1089
+ /**
1090
+ * Schema that pipes the output of one schema into another.
1091
+ *
1092
+ * Useful for chaining transforms with additional validation.
1093
+ *
1094
+ * @template Input - The original input type
1095
+ * @template Middle - The intermediate validated type
1096
+ * @template Output - The final output type from the second schema
1097
+ */
1098
+ export class ValPiped extends ValSchema {
1099
+ constructor(first, second) {
1100
+ const capturedFirst = first;
1101
+ const capturedSecond = second;
1102
+ const validateFn = (value) => {
1103
+ const firstResult = capturedFirst['~standard'].validate(value);
1104
+ // Handle async first validation
1105
+ if (firstResult instanceof Promise) {
1106
+ return firstResult.then((resolvedFirst) => {
1107
+ if (resolvedFirst.issues !== undefined) {
1108
+ return resolvedFirst;
1109
+ }
1110
+ return capturedSecond['~standard'].validate(resolvedFirst.value);
1111
+ });
1112
+ }
1113
+ if (firstResult.issues !== undefined) {
1114
+ return firstResult;
1115
+ }
1116
+ return capturedSecond['~standard'].validate(firstResult.value);
1117
+ };
1118
+ const inputJsonSchemaFn = (target) => {
1119
+ return capturedFirst['~standard'].jsonSchema.input({ target });
1120
+ };
1121
+ const outputJsonSchemaFn = (target) => {
1122
+ return capturedSecond['~standard'].jsonSchema.output({ target });
1123
+ };
1124
+ // Cast to sync validate function - async is handled by parseAsync/safeParseAsync
1125
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
1126
+ this._hasTransforms = first.hasTransforms() || second.hasTransforms();
1127
+ }
1128
+ }
1129
+ /**
1130
+ * Schema that preprocesses input before validation.
1131
+ *
1132
+ * Useful for coercing input types before validation.
1133
+ *
1134
+ * @template Input - The preprocessed input type
1135
+ * @template Output - The validated output type
1136
+ */
1137
+ export class ValPreprocessed extends ValSchema {
1138
+ constructor(preprocessFn, schema) {
1139
+ const capturedPreprocess = preprocessFn;
1140
+ const capturedSchema = schema;
1141
+ const validateFn = (value) => {
1142
+ const preprocessed = capturedPreprocess(value);
1143
+ return capturedSchema['~standard'].validate(preprocessed);
1144
+ };
1145
+ // Input schema is unknown since preprocessing accepts any input
1146
+ const inputJsonSchemaFn = (_target) => {
1147
+ return {};
1148
+ };
1149
+ const outputJsonSchemaFn = (target) => {
1150
+ return capturedSchema['~standard'].jsonSchema.output({ target });
1151
+ };
1152
+ // Cast to sync validate function - async is handled by parseAsync/safeParseAsync
1153
+ super(validateFn, inputJsonSchemaFn, outputJsonSchemaFn);
1154
+ this._hasTransforms = true;
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Creates a ValSchema from an existing Standard Schema object.
1159
+ *
1160
+ * Useful for wrapping schemas created with the factory functions.
1161
+ *
1162
+ * @param standardSchema - A Standard Schema compliant object
1163
+ * @returns A ValSchema instance
1164
+ */
1165
+ export function fromStandardSchema(standardSchema) {
1166
+ const std = standardSchema['~standard'];
1167
+ return new ValSchema(std.validate, (target) => std.jsonSchema.input({ target }), (target) => std.jsonSchema.output({ target }));
1168
+ }
1169
+ /**
1170
+ * Creates a ValSchema from a simple Standard Schema (without JSON Schema support).
1171
+ *
1172
+ * @param standardSchema - A Standard Schema compliant object
1173
+ * @param jsonSchemaFn - Function to generate JSON Schema
1174
+ * @returns A ValSchema instance
1175
+ */
1176
+ export function fromSimpleStandardSchema(standardSchema, jsonSchemaFn) {
1177
+ return new ValSchema(standardSchema['~standard'].validate, jsonSchemaFn);
1178
+ }
1179
+ //# sourceMappingURL=schema.js.map