orchid-orm-schema-to-zod 0.0.1

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.
@@ -0,0 +1,1249 @@
1
+ import {
2
+ ArrayColumn,
3
+ ColumnType,
4
+ columnTypes as t,
5
+ DateColumn,
6
+ IntegerColumn,
7
+ jsonTypes,
8
+ TextColumn,
9
+ JSONType,
10
+ JSONTypeAny,
11
+ JSONDate,
12
+ JSONNumber,
13
+ JSONString,
14
+ JSONArray,
15
+ } from 'pqb';
16
+ import { columnToZod, instanceToZod, modelToZod } from './index';
17
+ import { z } from 'zod';
18
+ import { Buffer } from 'node:buffer';
19
+
20
+ type AssertEqual<T, Expected> = [T] extends [Expected]
21
+ ? [Expected] extends [T]
22
+ ? true
23
+ : false
24
+ : false;
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
27
+ const assertType = <T, Expected>(_: AssertEqual<T, Expected>) => {
28
+ // noop
29
+ };
30
+
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ const columnOrJsonToZod = (type: any): z.ZodTypeAny => {
33
+ return type instanceof ColumnType
34
+ ? columnToZod(type)
35
+ : columnToZod(t.json(() => type));
36
+ };
37
+
38
+ describe('model to zod', () => {
39
+ it('should convert a model to a zod validation schema', () => {
40
+ const model = class Model {
41
+ columns = {
42
+ shape: {
43
+ id: t.serial().primaryKey(),
44
+ name: t.text().nullable(),
45
+ },
46
+ };
47
+ };
48
+
49
+ const result = modelToZod(model);
50
+ assertType<
51
+ typeof result,
52
+ z.ZodObject<{ id: z.ZodNumber; name: z.ZodNullable<z.ZodString> }>
53
+ >(true);
54
+
55
+ expect(result.parse({ id: 1, name: 'name' })).toEqual({
56
+ id: 1,
57
+ name: 'name',
58
+ });
59
+
60
+ expect(() => result.parse({ id: '1' })).toThrow(
61
+ 'Expected number, received string',
62
+ );
63
+ });
64
+ });
65
+
66
+ describe('instance to zod', () => {
67
+ it('should convert object with shape to a zod validation schema', () => {
68
+ const item = {
69
+ shape: {
70
+ id: t.serial().primaryKey(),
71
+ name: t.text().nullable(),
72
+ },
73
+ };
74
+
75
+ const result = instanceToZod(item);
76
+ assertType<
77
+ typeof result,
78
+ z.ZodObject<{ id: z.ZodNumber; name: z.ZodNullable<z.ZodString> }>
79
+ >(true);
80
+
81
+ expect(result.parse({ id: 1, name: 'name' })).toEqual({
82
+ id: 1,
83
+ name: 'name',
84
+ });
85
+
86
+ expect(() => result.parse({ id: '1' })).toThrow(
87
+ 'Expected number, received string',
88
+ );
89
+ });
90
+ });
91
+
92
+ describe('schema to zod', () => {
93
+ describe('transform', () => {
94
+ it('should transform column value', () => {
95
+ const type = columnToZod(
96
+ t.text().transform((text) => text.split('').reverse().join('')),
97
+ );
98
+
99
+ expect(type.parse('123')).toBe('321');
100
+ });
101
+
102
+ it('should transform json value', () => {
103
+ const type = columnToZod(
104
+ t.json((t) =>
105
+ t.string().transform((text) => text.split('').reverse().join('')),
106
+ ),
107
+ );
108
+
109
+ expect(type.parse('123')).toBe('321');
110
+ });
111
+ });
112
+
113
+ describe('to', () => {
114
+ it('should transform column value to a type', () => {
115
+ const type = columnToZod(t.text().to(parseInt, t.integer()));
116
+ expect(type.parse('123')).toBe(123);
117
+ });
118
+
119
+ it('should transform json value to a type', () => {
120
+ const type = columnToZod(
121
+ t.json((t) => t.string().to(parseInt, t.number())),
122
+ );
123
+ expect(type.parse('123')).toBe(123);
124
+ });
125
+ });
126
+
127
+ describe('refine', () => {
128
+ it('should add a refine check for column type', () => {
129
+ const type = columnToZod(t.text().refine((val) => val !== 'val'));
130
+ expect(() => type.parse('val')).toThrow('Invalid input');
131
+ });
132
+
133
+ it('should add a refine check for json type', () => {
134
+ const type = columnToZod(
135
+ t.json((t) => t.string().refine((val) => val !== 'val')),
136
+ );
137
+ expect(() => type.parse('val')).toThrow('Invalid input');
138
+ });
139
+ });
140
+
141
+ describe('superRefine', () => {
142
+ it('should add a superRefine check for column type', () => {
143
+ const type = columnToZod(
144
+ t.text().superRefine((val, ctx) => {
145
+ if (val.length > 3) {
146
+ ctx.addIssue({
147
+ code: z.ZodIssueCode.too_big,
148
+ maximum: 3,
149
+ type: 'string',
150
+ inclusive: true,
151
+ message: 'Too many items 😡',
152
+ });
153
+ }
154
+ }),
155
+ );
156
+
157
+ expect(() => type.parse('1234')).toThrow('Too many items');
158
+ });
159
+
160
+ it('should add a superRefine check for json type', () => {
161
+ const type = columnToZod(
162
+ t.json((t) =>
163
+ t.string().superRefine((val, ctx) => {
164
+ if (val.length > 3) {
165
+ ctx.addIssue({
166
+ code: z.ZodIssueCode.too_big,
167
+ maximum: 3,
168
+ type: 'string',
169
+ inclusive: true,
170
+ message: 'Too many items 😡',
171
+ });
172
+ }
173
+ }),
174
+ ),
175
+ );
176
+
177
+ expect(() => type.parse('1234')).toThrow('Too many items');
178
+ });
179
+ });
180
+
181
+ describe('validationDefault', () => {
182
+ it('should set default value for column', () => {
183
+ const type = columnToZod(t.text().validationDefault('value'));
184
+
185
+ expect(type.parse(undefined)).toBe('value');
186
+ });
187
+
188
+ it('should set default value for json type', () => {
189
+ const type = columnToZod(t.json((t) => t.string().default('value')));
190
+
191
+ expect(type.parse(undefined)).toBe('value');
192
+ });
193
+ });
194
+
195
+ describe('nullable', () => {
196
+ it('should parse nullable', () => {
197
+ const schema = columnToZod(t.text().nullable());
198
+
199
+ assertType<typeof schema, z.ZodNullable<z.ZodString>>(true);
200
+
201
+ expect(schema.parse(null)).toBe(null);
202
+ });
203
+ });
204
+
205
+ const smallint = columnToZod(t.smallint());
206
+ const integer = columnToZod(t.integer());
207
+ const real = columnToZod(t.real());
208
+ const smallSerial = columnToZod(t.smallSerial());
209
+ const serial = columnToZod(t.serial());
210
+ const money = columnToZod(t.serial());
211
+ assertType<
212
+ | typeof smallint
213
+ | typeof integer
214
+ | typeof real
215
+ | typeof smallSerial
216
+ | typeof serial
217
+ | typeof money,
218
+ z.ZodNumber
219
+ >(true);
220
+
221
+ const testNumberMethods = (
222
+ type: IntegerColumn | JSONNumber,
223
+ isInt: boolean,
224
+ ) => {
225
+ if (isInt) {
226
+ expect(() => columnOrJsonToZod(type).parse(1.5)).toThrow(
227
+ 'Expected integer, received float',
228
+ );
229
+ }
230
+
231
+ expect(() => columnOrJsonToZod(type.lt(5)).parse(10)).toThrow(
232
+ 'Number must be less than 5',
233
+ );
234
+
235
+ expect(() => columnOrJsonToZod(type.lte(5)).parse(10)).toThrow(
236
+ 'Number must be less than or equal to 5',
237
+ );
238
+
239
+ expect(() => columnOrJsonToZod(type.max(5)).parse(10)).toThrow(
240
+ 'Number must be less than or equal to 5',
241
+ );
242
+
243
+ expect(() => columnOrJsonToZod(type.gt(5)).parse(0)).toThrow(
244
+ 'Number must be greater than 5',
245
+ );
246
+
247
+ expect(() => columnOrJsonToZod(type.gte(5)).parse(0)).toThrow(
248
+ 'Number must be greater than or equal to 5',
249
+ );
250
+
251
+ expect(() => columnOrJsonToZod(type.min(5)).parse(0)).toThrow(
252
+ 'Number must be greater than or equal to 5',
253
+ );
254
+
255
+ expect(() => columnOrJsonToZod(type.positive()).parse(-1)).toThrow(
256
+ 'Number must be greater than 0',
257
+ );
258
+
259
+ expect(() => columnOrJsonToZod(type.nonNegative()).parse(-1)).toThrow(
260
+ 'Number must be greater than or equal to 0',
261
+ );
262
+
263
+ expect(() => columnOrJsonToZod(type.negative()).parse(0)).toThrow(
264
+ 'Number must be less than 0',
265
+ );
266
+
267
+ expect(() => columnOrJsonToZod(type.nonPositive()).parse(1)).toThrow(
268
+ 'Number must be less than or equal to 0',
269
+ );
270
+
271
+ expect(() => columnOrJsonToZod(type.multipleOf(5)).parse(3)).toThrow(
272
+ 'Number must be a multiple of 5',
273
+ );
274
+
275
+ expect(() => columnOrJsonToZod(type.step(5)).parse(3)).toThrow(
276
+ 'Number must be a multiple of 5',
277
+ );
278
+ };
279
+
280
+ describe.each([
281
+ 'smallint',
282
+ 'integer',
283
+ 'real',
284
+ 'smallSerial',
285
+ 'serial',
286
+ 'money',
287
+ ])('%s', (method) => {
288
+ it('should convert to number', () => {
289
+ const schema = columnToZod(t[method as 'integer']());
290
+
291
+ expect(schema.parse(123)).toBe(123);
292
+
293
+ expect(() => schema.parse('s')).toThrow('Expected number');
294
+
295
+ testNumberMethods(
296
+ t[method as 'integer'](),
297
+ method !== 'real' && method !== 'money',
298
+ );
299
+ });
300
+ });
301
+
302
+ const bigint = columnToZod(t.bigint());
303
+ const numeric = columnToZod(t.numeric());
304
+ const decimal = columnToZod(t.decimal());
305
+ const doublePrecision = columnToZod(t.doublePrecision());
306
+ const bigSerial = columnToZod(t.bigSerial());
307
+ assertType<
308
+ | typeof bigint
309
+ | typeof numeric
310
+ | typeof decimal
311
+ | typeof doublePrecision
312
+ | typeof bigSerial,
313
+ z.ZodString
314
+ >(true);
315
+
316
+ describe.each([
317
+ 'bigint',
318
+ 'numeric',
319
+ 'decimal',
320
+ 'doublePrecision',
321
+ 'bigSerial',
322
+ ])('%s', (method) => {
323
+ it('should validate bigint and parse to a string', () => {
324
+ const schema = columnToZod(t[method as 'bigint']());
325
+
326
+ expect(schema.parse('123')).toBe('123');
327
+
328
+ expect(() => schema.parse('s')).toThrow('Failed to parse bigint');
329
+ });
330
+ });
331
+
332
+ const varchar = columnToZod(t.varchar());
333
+ const char = columnToZod(t.char());
334
+ const text = columnToZod(t.text());
335
+ const string = columnToZod(t.string());
336
+ assertType<
337
+ typeof varchar | typeof char | typeof text | typeof string,
338
+ z.ZodString
339
+ >(true);
340
+
341
+ const testStringMethods = (type: TextColumn | JSONString) => {
342
+ expect(() => columnOrJsonToZod(type.min(1)).parse('')).toThrow(
343
+ 'String must contain at least 1 character(s)',
344
+ );
345
+
346
+ expect(() => columnOrJsonToZod(type.max(1)).parse('123')).toThrow(
347
+ 'String must contain at most 1 character(s)',
348
+ );
349
+
350
+ expect(() => columnOrJsonToZod(type.length(1)).parse('')).toThrow(
351
+ 'String must contain at least 1 character(s)',
352
+ );
353
+
354
+ expect(() => columnOrJsonToZod(type.length(1)).parse('123')).toThrow(
355
+ 'String must contain at most 1 character(s)',
356
+ );
357
+
358
+ expect(() => columnOrJsonToZod(type.email()).parse('invalid')).toThrow(
359
+ 'Invalid email',
360
+ );
361
+
362
+ expect(() => columnOrJsonToZod(type.url()).parse('invalid')).toThrow(
363
+ 'Invalid url',
364
+ );
365
+
366
+ expect(() => columnOrJsonToZod(type.uuid()).parse('invalid')).toThrow(
367
+ 'Invalid uuid',
368
+ );
369
+
370
+ expect(() => columnOrJsonToZod(type.cuid()).parse('invalid')).toThrow(
371
+ 'Invalid cuid',
372
+ );
373
+
374
+ expect(columnOrJsonToZod(type.trim()).parse(' trimmed ')).toBe('trimmed');
375
+
376
+ expect(() => columnOrJsonToZod(type.nonempty()).parse('')).toThrow(
377
+ 'String must contain at least 1 character(s)',
378
+ );
379
+ };
380
+
381
+ describe.each(['varchar', 'char', 'text', 'string'])('%s', (method) => {
382
+ it('should convert to string', () => {
383
+ const schema = columnToZod(t[method as 'text']());
384
+
385
+ expect(schema.parse('s')).toBe('s');
386
+
387
+ expect(() => schema.parse(1)).toThrow('Expected string');
388
+
389
+ testStringMethods(t[method as 'text']());
390
+ });
391
+ });
392
+
393
+ describe('bytea', () => {
394
+ it('should check Buffer', () => {
395
+ const schema = columnToZod(t.bytea());
396
+
397
+ assertType<typeof schema, z.ZodType<Buffer>>(true);
398
+
399
+ const buffer = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
400
+ expect(schema.parse(buffer)).toBe(buffer);
401
+
402
+ expect(() => schema.parse([1, 0, 1])).toThrow(
403
+ 'Input not instance of Buffer',
404
+ );
405
+ });
406
+ });
407
+
408
+ const date = columnToZod(t.date());
409
+ const timestamp = columnToZod(t.timestamp());
410
+ const timestampWithTimeZone = columnToZod(t.timestampWithTimeZone());
411
+ assertType<
412
+ typeof date | typeof timestamp | typeof timestampWithTimeZone,
413
+ z.ZodDate
414
+ >(true);
415
+
416
+ const testDateMethods = (type: DateColumn | JSONDate) => {
417
+ const now = new Date();
418
+
419
+ expect(() =>
420
+ columnOrJsonToZod(type.min(new Date(now.getTime() + 100))).parse(now),
421
+ ).toThrow('Date must be greater than or equal to');
422
+
423
+ expect(() =>
424
+ columnOrJsonToZod(type.max(new Date(now.getTime() - 100))).parse(now),
425
+ ).toThrow('Date must be smaller than or equal to');
426
+ };
427
+
428
+ describe.each(['date', 'timestamp', 'timestampWithTimeZone'])(
429
+ '%s',
430
+ (method) => {
431
+ it('should parse from string to a Date', () => {
432
+ const schema = columnToZod(t[method as 'date']());
433
+
434
+ const date = new Date(2000, 0, 1, 0, 0, 0, 0);
435
+ expect(schema.parse(date)).toEqual(date);
436
+
437
+ expect(() => schema.parse('malformed')).toThrow('Invalid date');
438
+ });
439
+
440
+ it('should parse from Date to a Date', () => {
441
+ const schema = columnToZod(t[method as 'date']());
442
+
443
+ assertType<typeof schema, z.ZodDate>(true);
444
+
445
+ const date = new Date(2000, 0, 1, 0, 0, 0, 0);
446
+ expect(schema.parse(date)).toEqual(date);
447
+
448
+ testDateMethods(t[method as 'date']());
449
+ });
450
+ },
451
+ );
452
+
453
+ const time = columnToZod(t.time());
454
+ const timeWithTimeZone = columnToZod(t.timeWithTimeZone());
455
+ assertType<typeof time | typeof timeWithTimeZone, z.ZodString>(true);
456
+
457
+ describe.each(['time', 'timeWithTimeZone'])('%s', (method) => {
458
+ it('should validate and parse to a string', () => {
459
+ const schema = columnToZod(t[method as 'time']());
460
+
461
+ const input = method === 'time' ? '12:12:12' : '12:12:12.1234 +00:00';
462
+ expect(schema.parse(input)).toBe(input);
463
+
464
+ expect(() => schema.parse('malformed')).toThrow('Invalid time');
465
+ });
466
+ });
467
+
468
+ describe('interval', () => {
469
+ it('should validate and parse time interval', () => {
470
+ const schema = columnToZod(t.interval());
471
+
472
+ const interval = {
473
+ years: 1,
474
+ months: 1,
475
+ days: 1,
476
+ hours: 1,
477
+ seconds: 1,
478
+ };
479
+
480
+ assertType<ReturnType<typeof schema['parse']>, Partial<typeof interval>>(
481
+ true,
482
+ );
483
+
484
+ expect(schema.parse(interval)).toEqual(interval);
485
+
486
+ expect(() => schema.parse({ years: 'string' })).toThrow(
487
+ 'Expected number, received string',
488
+ );
489
+ });
490
+ });
491
+
492
+ describe('boolean', () => {
493
+ it('should validate and parse a boolean', () => {
494
+ const schema = columnToZod(t.boolean());
495
+
496
+ assertType<typeof schema, z.ZodBoolean>(true);
497
+
498
+ expect(schema.parse(true)).toBe(true);
499
+
500
+ expect(() => schema.parse(123)).toThrow(
501
+ 'Expected boolean, received number',
502
+ );
503
+ });
504
+ });
505
+
506
+ describe('enum', () => {
507
+ it('should validate and parse enum', () => {
508
+ const schema = columnToZod(t.enum('name', ['a', 'b', 'c']));
509
+
510
+ assertType<typeof schema, z.ZodEnum<['a', 'b', 'c']>>(true);
511
+
512
+ expect(schema.parse('a')).toBe('a');
513
+
514
+ expect(() => schema.parse('d')).toThrow('Invalid enum value');
515
+ });
516
+ });
517
+
518
+ const point = columnToZod(t.point());
519
+ const line = columnToZod(t.line());
520
+ const lseg = columnToZod(t.lseg());
521
+ const box = columnToZod(t.box());
522
+ const path = columnToZod(t.path());
523
+ const polygon = columnToZod(t.polygon());
524
+ const circle = columnToZod(t.circle());
525
+ assertType<
526
+ | typeof point
527
+ | typeof line
528
+ | typeof lseg
529
+ | typeof box
530
+ | typeof path
531
+ | typeof polygon
532
+ | typeof circle,
533
+ z.ZodString
534
+ >(true);
535
+
536
+ describe.each(['point', 'line', 'lseg', 'box', 'path', 'polygon', 'circle'])(
537
+ '%s',
538
+ (method) => {
539
+ it('should parse to a string without validation', () => {
540
+ const schema = columnToZod(t[method as 'point']());
541
+
542
+ expect(schema.parse('string')).toBe('string');
543
+
544
+ expect(() => schema.parse(123)).toThrow(
545
+ 'Expected string, received number',
546
+ );
547
+ });
548
+ },
549
+ );
550
+
551
+ const cidr = columnToZod(t.cidr());
552
+ const inet = columnToZod(t.inet());
553
+ const macaddr = columnToZod(t.macaddr());
554
+ const macaddr8 = columnToZod(t.macaddr8());
555
+ assertType<
556
+ typeof cidr | typeof inet | typeof macaddr | typeof macaddr8,
557
+ z.ZodString
558
+ >(true);
559
+
560
+ describe.each(['cidr', 'inet', 'macaddr', 'macaddr8'])('%s', (method) => {
561
+ it('should parse to a string without validation', () => {
562
+ const schema = columnToZod(t[method as 'cidr']());
563
+
564
+ expect(schema.parse('string')).toBe('string');
565
+
566
+ expect(() => schema.parse(123)).toThrow(
567
+ 'Expected string, received number',
568
+ );
569
+ });
570
+ });
571
+
572
+ const bit = columnToZod(t.bit(5));
573
+ const bitVarying = columnToZod(t.bitVarying());
574
+ assertType<typeof bit | typeof bitVarying, z.ZodString>(true);
575
+
576
+ describe.each(['bit', 'bitVarying'])('%s', (method) => {
577
+ it('should validate a string to contain only 1 or 0 and parse to a string', () => {
578
+ const schema = columnToZod(t[method as 'bit'](5));
579
+
580
+ expect(schema.parse('10101')).toBe('10101');
581
+
582
+ expect(() => schema.parse('2')).toThrow('Invalid');
583
+ });
584
+ });
585
+
586
+ const tsvector = columnToZod(t.tsvector());
587
+ const tsquery = columnToZod(t.tsquery());
588
+ assertType<typeof tsvector | typeof tsquery, z.ZodString>(true);
589
+
590
+ describe.each(['tsvector', 'tsquery'])('%s', (method) => {
591
+ it('should parse to a string without validation', () => {
592
+ const schema = columnToZod(t[method as 'tsvector']());
593
+
594
+ expect(schema.parse('string')).toBe('string');
595
+
596
+ expect(() => schema.parse(123)).toThrow(
597
+ 'Expected string, received number',
598
+ );
599
+ });
600
+ });
601
+
602
+ const xml = columnToZod(t.xml());
603
+ const jsonText = columnToZod(t.jsonText());
604
+ assertType<typeof xml | typeof jsonText, z.ZodString>(true);
605
+
606
+ describe.each(['xml', 'jsonText'])('%s', (method) => {
607
+ it('should parse to a string without validation', () => {
608
+ const schema = columnToZod(t[method as 'xml']());
609
+
610
+ expect(schema.parse('string')).toBe('string');
611
+
612
+ expect(() => schema.parse(123)).toThrow(
613
+ 'Expected string, received number',
614
+ );
615
+ });
616
+ });
617
+
618
+ describe('uuid', () => {
619
+ it('should validate uuid and parse to a string', () => {
620
+ const schema = columnToZod(t.uuid());
621
+
622
+ assertType<typeof schema, z.ZodString>(true);
623
+
624
+ const uuid = '123e4567-e89b-12d3-a456-426614174000';
625
+ expect(schema.parse(uuid)).toBe(uuid);
626
+
627
+ expect(() => schema.parse('1234')).toThrow('Invalid uuid');
628
+ });
629
+ });
630
+
631
+ const testArrayMethods = (
632
+ type: ArrayColumn<ColumnType> | JSONArray<JSONTypeAny>,
633
+ ) => {
634
+ expect(() => columnOrJsonToZod(type.min(1)).parse([])).toThrow(
635
+ 'Array must contain at least 1 element(s)',
636
+ );
637
+
638
+ expect(() => columnOrJsonToZod(type.max(1)).parse([1, 2])).toThrow(
639
+ 'Array must contain at most 1 element(s)',
640
+ );
641
+
642
+ expect(() => columnOrJsonToZod(type.length(1)).parse([])).toThrow(
643
+ 'Array must contain at least 1 element(s)',
644
+ );
645
+
646
+ expect(() => columnOrJsonToZod(type.length(1)).parse([1, 2])).toThrow(
647
+ 'Array must contain at most 1 element(s)',
648
+ );
649
+
650
+ expect(() => columnOrJsonToZod(type.nonempty()).parse([])).toThrow(
651
+ 'Array must contain at least 1 element(s)',
652
+ );
653
+ };
654
+
655
+ describe('array', () => {
656
+ it('should validate and parse array', () => {
657
+ const schema = columnToZod(t.array(t.integer()));
658
+
659
+ assertType<typeof schema, z.ZodArray<z.ZodNumber>>(true);
660
+
661
+ expect(schema.parse([1, 2, 3])).toEqual([1, 2, 3]);
662
+
663
+ expect(() => schema.parse(123)).toThrow(
664
+ 'Expected array, received number',
665
+ );
666
+ expect(() => schema.parse(['a'])).toThrow(
667
+ 'Expected number, received string',
668
+ );
669
+
670
+ testArrayMethods(t.array(t.integer()));
671
+ });
672
+ });
673
+
674
+ describe('json', () => {
675
+ describe('any', () => {
676
+ it('should parse to any', () => {
677
+ const schema = columnToZod(t.json((t) => t.any()));
678
+
679
+ assertType<typeof schema, z.ZodTypeAny>(true);
680
+
681
+ expect(schema.parse(123)).toBe(123);
682
+ });
683
+ });
684
+
685
+ describe('bigint', () => {
686
+ it('should validate bigint and parse to string', () => {
687
+ const schema = columnToZod(t.json((t) => t.bigint()));
688
+
689
+ assertType<typeof schema, z.ZodString>(true);
690
+
691
+ expect(schema.parse('123')).toBe('123');
692
+
693
+ expect(() => schema.parse('kokoko')).toThrow('Failed to parse bigint');
694
+ });
695
+ });
696
+
697
+ describe('boolean', () => {
698
+ it('should parse boolean', () => {
699
+ const schema = columnToZod(t.json((t) => t.boolean()));
700
+
701
+ assertType<typeof schema, z.ZodBoolean>(true);
702
+
703
+ expect(schema.parse(true)).toBe(true);
704
+
705
+ expect(() => schema.parse(123)).toThrow(
706
+ 'Expected boolean, received number',
707
+ );
708
+ });
709
+ });
710
+
711
+ describe('date', () => {
712
+ it('should parse a Date', () => {
713
+ const schema = columnToZod(t.json((t) => t.date()));
714
+
715
+ assertType<typeof schema, z.ZodDate>(true);
716
+
717
+ const date = new Date(2000, 0, 1);
718
+ expect(schema.parse(date).getTime()).toBe(date.getTime());
719
+
720
+ expect(() => schema.parse(new Date('koko'))).toThrow('Invalid date');
721
+
722
+ testDateMethods(jsonTypes.date());
723
+ });
724
+ });
725
+
726
+ describe('nan', () => {
727
+ it('should parse a NaN', () => {
728
+ const schema = columnToZod(t.json((t) => t.nan()));
729
+
730
+ assertType<typeof schema, z.ZodNaN>(true);
731
+
732
+ expect(schema.parse(NaN)).toBe(NaN);
733
+
734
+ expect(() => schema.parse(123)).toThrow(
735
+ 'Expected nan, received number',
736
+ );
737
+ });
738
+ });
739
+
740
+ describe('never', () => {
741
+ it('should parse a never', () => {
742
+ const schema = columnToZod(t.json((t) => t.never()));
743
+
744
+ assertType<typeof schema, z.ZodNever>(true);
745
+
746
+ expect(() => schema.parse(123)).toThrow(
747
+ 'Expected never, received number',
748
+ );
749
+ });
750
+ });
751
+
752
+ describe('null', () => {
753
+ it('should parse a null', () => {
754
+ const schema = columnToZod(t.json((t) => t.null()));
755
+
756
+ assertType<typeof schema, z.ZodNull>(true);
757
+
758
+ expect(schema.parse(null)).toBe(null);
759
+
760
+ expect(() => schema.parse(123)).toThrow(
761
+ 'Expected null, received number',
762
+ );
763
+ });
764
+ });
765
+
766
+ describe('number', () => {
767
+ it('should parse a number', () => {
768
+ const schema = columnToZod(t.json((t) => t.number()));
769
+
770
+ assertType<typeof schema, z.ZodNumber>(true);
771
+
772
+ expect(schema.parse(123)).toBe(123);
773
+
774
+ expect(() => schema.parse('123')).toThrow(
775
+ 'Expected number, received string',
776
+ );
777
+
778
+ testNumberMethods(jsonTypes.number().int(), true);
779
+ });
780
+ });
781
+
782
+ describe('string', () => {
783
+ it('should parse a string', () => {
784
+ const schema = columnToZod(t.json((t) => t.string()));
785
+
786
+ assertType<typeof schema, z.ZodString>(true);
787
+
788
+ expect(schema.parse('string')).toBe('string');
789
+
790
+ expect(() => schema.parse(123)).toThrow(
791
+ 'Expected string, received number',
792
+ );
793
+
794
+ testStringMethods(jsonTypes.string());
795
+ });
796
+ });
797
+
798
+ describe('undefined', () => {
799
+ it('should parse a undefined', () => {
800
+ const schema = columnToZod(t.json((t) => t.undefined()));
801
+
802
+ assertType<typeof schema, z.ZodUndefined>(true);
803
+
804
+ expect(schema.parse(undefined)).toBe(undefined);
805
+
806
+ expect(() => schema.parse(123)).toThrow(
807
+ 'Expected undefined, received number',
808
+ );
809
+ });
810
+ });
811
+
812
+ describe('unknown', () => {
813
+ it('should parse unknown', () => {
814
+ const schema = columnToZod(t.json((t) => t.unknown()));
815
+
816
+ assertType<typeof schema, z.ZodUnknown>(true);
817
+
818
+ expect(schema.parse(123)).toBe(123);
819
+ });
820
+ });
821
+
822
+ describe('void', () => {
823
+ it('should parse void', () => {
824
+ const schema = columnToZod(t.json((t) => t.void()));
825
+
826
+ assertType<typeof schema, z.ZodVoid>(true);
827
+
828
+ expect(schema.parse(undefined)).toBe(undefined);
829
+
830
+ expect(() => schema.parse(123)).toThrow(
831
+ 'Expected void, received number',
832
+ );
833
+ });
834
+ });
835
+
836
+ describe('array', () => {
837
+ it('should validate and parse array', () => {
838
+ const schema = columnToZod(t.json((t) => t.array(t.number())));
839
+
840
+ assertType<typeof schema, z.ZodArray<z.ZodNumber>>(true);
841
+
842
+ expect(schema.parse([1, 2, 3])).toEqual([1, 2, 3]);
843
+
844
+ expect(() => schema.parse(123)).toThrow(
845
+ 'Expected array, received number',
846
+ );
847
+
848
+ expect(() => schema.parse(['a'])).toThrow(
849
+ 'Expected number, received string',
850
+ );
851
+
852
+ testArrayMethods(jsonTypes.array(jsonTypes.number()));
853
+ });
854
+ });
855
+
856
+ describe('enum', () => {
857
+ it('should parse enum', () => {
858
+ const schema = columnToZod(t.json((t) => t.enum(['a', 'b', 'c'])));
859
+
860
+ assertType<typeof schema, z.ZodEnum<['a', 'b', 'c']>>(true);
861
+
862
+ expect(schema.parse('a')).toBe('a');
863
+
864
+ expect(() => schema.parse('d')).toThrow('Invalid enum value');
865
+ });
866
+ });
867
+
868
+ describe('instanceOf', () => {
869
+ it('should parse instance of', () => {
870
+ const schema = columnToZod(t.json((t) => t.instanceOf(Date)));
871
+
872
+ assertType<typeof schema, z.ZodType<Date, z.ZodTypeDef, Date>>(true);
873
+
874
+ const date = new Date();
875
+ expect(schema.parse(date)).toBe(date);
876
+
877
+ expect(() => schema.parse({})).toThrow('Input not instance of Date');
878
+ });
879
+ });
880
+
881
+ describe('literal', () => {
882
+ it('should parse literal', () => {
883
+ const schema = columnToZod(t.json((t) => t.literal('string')));
884
+
885
+ assertType<typeof schema, z.ZodLiteral<'string'>>(true);
886
+
887
+ expect(schema.parse('string')).toBe('string');
888
+
889
+ expect(() => schema.parse('koko')).toThrow('Invalid literal value');
890
+ });
891
+ });
892
+
893
+ describe('map', () => {
894
+ it('should parse map', () => {
895
+ const schema = columnToZod(
896
+ t.json((t) => t.map(t.string(), t.number())),
897
+ );
898
+
899
+ assertType<typeof schema, z.ZodMap<z.ZodString, z.ZodNumber>>(true);
900
+
901
+ const map = new Map();
902
+ map.set('key', 123);
903
+ expect(schema.parse(map)).toEqual(map);
904
+
905
+ map.set(123, 'key');
906
+ expect(() => schema.parse(map)).toThrow(
907
+ 'Expected number, received string',
908
+ );
909
+ });
910
+ });
911
+
912
+ describe('set', () => {
913
+ it('should parse set', () => {
914
+ const schema = columnToZod(t.json((t) => t.set(t.number())));
915
+
916
+ assertType<typeof schema, z.ZodSet<z.ZodNumber>>(true);
917
+
918
+ const set = new Set();
919
+ set.add(1);
920
+ expect(schema.parse(set)).toEqual(set);
921
+
922
+ set.add('string');
923
+ expect(() => schema.parse(set)).toThrow(
924
+ 'Expected number, received string',
925
+ );
926
+
927
+ const type = jsonTypes.set(jsonTypes.number());
928
+ expect(() => columnOrJsonToZod(type.min(1)).parse(new Set())).toThrow(
929
+ 'Invalid input',
930
+ );
931
+
932
+ expect(() =>
933
+ columnOrJsonToZod(type.max(1)).parse(new Set([1, 2])),
934
+ ).toThrow('Invalid input');
935
+
936
+ expect(() => columnOrJsonToZod(type.size(1)).parse(new Set())).toThrow(
937
+ 'Invalid input',
938
+ );
939
+
940
+ expect(() =>
941
+ columnOrJsonToZod(type.size(1)).parse(new Set([1, 2])),
942
+ ).toThrow('Invalid input');
943
+
944
+ expect(() =>
945
+ columnOrJsonToZod(type.nonempty()).parse(new Set()),
946
+ ).toThrow('Invalid input');
947
+ });
948
+ });
949
+
950
+ describe('nativeEnum', () => {
951
+ it('should parse native enum', () => {
952
+ enum Test {
953
+ one = 'one',
954
+ two = 'two',
955
+ }
956
+
957
+ const schema = columnToZod(t.json((t) => t.nativeEnum(Test)));
958
+
959
+ assertType<typeof schema, z.ZodNativeEnum<typeof Test>>(true);
960
+
961
+ expect(schema.parse('one')).toBe('one');
962
+
963
+ expect(() => schema.parse('ko')).toThrow('Invalid enum value');
964
+ });
965
+ });
966
+
967
+ describe('tuple', () => {
968
+ it('should parse tuple', () => {
969
+ const schema = columnToZod(
970
+ t.json((t) => t.tuple([t.number(), t.string()])),
971
+ );
972
+
973
+ assertType<typeof schema, z.ZodTuple<[z.ZodNumber, z.ZodString]>>(true);
974
+
975
+ expect(schema.parse([1, 'string'])).toEqual([1, 'string']);
976
+
977
+ expect(() => schema.parse(['string', 1])).toThrow(
978
+ `Expected number, received string`,
979
+ );
980
+ });
981
+
982
+ it('should parse rest elements', () => {
983
+ const schema = columnToZod(
984
+ t.json((t) => t.tuple([t.number()]).rest(t.string())),
985
+ );
986
+
987
+ assertType<typeof schema, z.ZodTuple<[z.ZodNumber], z.ZodString>>(true);
988
+
989
+ expect(schema.parse([1, 'a', 'b'])).toEqual([1, 'a', 'b']);
990
+
991
+ expect(() => schema.parse([1, 'a', 2])).toThrow(
992
+ 'Expected string, received number',
993
+ );
994
+ });
995
+ });
996
+
997
+ describe('nullable', () => {
998
+ it('should parse nullable', () => {
999
+ const schema = columnToZod(t.json((t) => t.nullable(t.number())));
1000
+
1001
+ assertType<typeof schema, z.ZodNullable<z.ZodNumber>>(true);
1002
+
1003
+ expect(schema.parse(null)).toBe(null);
1004
+ });
1005
+ });
1006
+
1007
+ describe('nullish', () => {
1008
+ it('should parse nullish', () => {
1009
+ const schema = columnToZod(t.json((t) => t.nullish(t.number())));
1010
+
1011
+ assertType<typeof schema, z.ZodNullable<z.ZodOptional<z.ZodNumber>>>(
1012
+ true,
1013
+ );
1014
+
1015
+ expect(schema.parse(null)).toBe(null);
1016
+ expect(schema.parse(undefined)).toBe(undefined);
1017
+ });
1018
+ });
1019
+
1020
+ describe('optional', () => {
1021
+ it('should parse optional', () => {
1022
+ const schema = columnToZod(t.json((t) => t.optional(t.number())));
1023
+
1024
+ assertType<typeof schema, z.ZodOptional<z.ZodNumber>>(true);
1025
+
1026
+ expect(schema.parse(undefined)).toBe(undefined);
1027
+ });
1028
+ });
1029
+
1030
+ describe('object', () => {
1031
+ it('should parse object', () => {
1032
+ const schema = columnToZod(
1033
+ t.json((t) => t.object({ key: t.number() })),
1034
+ );
1035
+
1036
+ assertType<typeof schema, z.ZodObject<{ key: z.ZodNumber }>>(true);
1037
+
1038
+ expect(schema.parse({ key: 123 })).toEqual({ key: 123 });
1039
+
1040
+ expect(() => schema.parse({ key: 'string' })).toThrow(
1041
+ 'Expected number, received string',
1042
+ );
1043
+ });
1044
+
1045
+ it('should parse object with passing through unknown keys', () => {
1046
+ const schema = columnToZod(
1047
+ t.json((t) => t.object({ key: t.number() }).passthrough()),
1048
+ );
1049
+
1050
+ assertType<
1051
+ typeof schema,
1052
+ z.ZodObject<{ key: z.ZodNumber }, 'passthrough'>
1053
+ >(true);
1054
+
1055
+ expect(schema.parse({ key: 123, koko: 'koko' })).toEqual({
1056
+ key: 123,
1057
+ koko: 'koko',
1058
+ });
1059
+
1060
+ expect(() => schema.parse({ key: 'string' })).toThrow(
1061
+ 'Expected number, received string',
1062
+ );
1063
+ });
1064
+
1065
+ it('should parse object with strict unknown keys', () => {
1066
+ const schema = columnToZod(
1067
+ t.json((t) => t.object({ key: t.number() }).strict()),
1068
+ );
1069
+
1070
+ assertType<typeof schema, z.ZodObject<{ key: z.ZodNumber }, 'strict'>>(
1071
+ true,
1072
+ );
1073
+
1074
+ expect(schema.parse({ key: 123 })).toEqual({ key: 123 });
1075
+
1076
+ expect(() => schema.parse({ key: 123, koko: 'koko' })).toThrow(
1077
+ 'Unrecognized key(s)',
1078
+ );
1079
+ });
1080
+
1081
+ it('should parse object with catch all option', () => {
1082
+ const schema = columnToZod(
1083
+ t.json((t) => t.object({ key: t.number() }).catchAll(t.number())),
1084
+ );
1085
+
1086
+ assertType<
1087
+ typeof schema,
1088
+ z.ZodObject<{ key: z.ZodNumber }, 'strip', z.ZodNumber>
1089
+ >(true);
1090
+
1091
+ expect(schema.parse({ key: 123, koko: 123 })).toEqual({
1092
+ key: 123,
1093
+ koko: 123,
1094
+ });
1095
+
1096
+ expect(() => schema.parse({ key: 123, koko: 'koko' })).toThrow(
1097
+ 'Expected number, received string',
1098
+ );
1099
+ });
1100
+ });
1101
+
1102
+ describe('record', () => {
1103
+ it('should parse record', () => {
1104
+ const schema = columnToZod(
1105
+ t.json((t) => t.record(t.string(), t.number())),
1106
+ );
1107
+
1108
+ assertType<typeof schema, z.ZodRecord<z.ZodString, z.ZodNumber>>(true);
1109
+
1110
+ expect(schema.parse({ key: 123 })).toEqual({ key: 123 });
1111
+
1112
+ expect(() => schema.parse({ key: 'string' })).toThrow(
1113
+ 'Expected number, received string',
1114
+ );
1115
+ });
1116
+ });
1117
+
1118
+ describe('intersection', () => {
1119
+ it('should parse intersection', () => {
1120
+ const schema = columnToZod(
1121
+ t.json((t) =>
1122
+ t.intersection(
1123
+ t.object({ a: t.string(), b: t.number() }),
1124
+ t.object({ a: t.string(), c: t.number() }),
1125
+ ),
1126
+ ),
1127
+ );
1128
+
1129
+ assertType<
1130
+ typeof schema,
1131
+ z.ZodIntersection<
1132
+ z.ZodObject<{ a: z.ZodString; b: z.ZodNumber }>,
1133
+ z.ZodObject<{ a: z.ZodString; c: z.ZodNumber }>
1134
+ >
1135
+ >(true);
1136
+
1137
+ expect(schema.parse({ a: 'string', b: 123, c: 123 })).toEqual({
1138
+ a: 'string',
1139
+ b: 123,
1140
+ c: 123,
1141
+ });
1142
+
1143
+ expect(() => schema.parse({ a: 'string', b: 123 })).toThrow('Required');
1144
+ });
1145
+ });
1146
+
1147
+ describe('union', () => {
1148
+ it('should parse union', () => {
1149
+ const schema = columnToZod(
1150
+ t.json((t) => t.union([t.number(), t.string()])),
1151
+ );
1152
+
1153
+ assertType<typeof schema, z.ZodUnion<[z.ZodNumber, z.ZodString]>>(true);
1154
+
1155
+ expect(schema.parse(123)).toBe(123);
1156
+ expect(schema.parse('string')).toBe('string');
1157
+
1158
+ expect(() => schema.parse(true)).toThrow('Invalid input');
1159
+ });
1160
+ });
1161
+
1162
+ describe('discriminatedUnion', () => {
1163
+ it('should parse discriminated union', () => {
1164
+ const schema = columnToZod(
1165
+ t.json((t) =>
1166
+ t.discriminatedUnion('type', [
1167
+ t.object({ type: t.literal('a'), a: t.string() }),
1168
+ t.object({ type: t.literal('b'), b: t.number() }),
1169
+ ]),
1170
+ ),
1171
+ );
1172
+
1173
+ assertType<
1174
+ typeof schema,
1175
+ z.ZodDiscriminatedUnion<
1176
+ 'type',
1177
+ z.Primitive,
1178
+ | z.ZodObject<{ type: z.ZodLiteral<'a'>; a: z.ZodString }>
1179
+ | z.ZodObject<{ type: z.ZodLiteral<'b'>; b: z.ZodNumber }>
1180
+ >
1181
+ >(true);
1182
+
1183
+ expect(schema.parse({ type: 'a', a: 'string' })).toEqual({
1184
+ type: 'a',
1185
+ a: 'string',
1186
+ });
1187
+ expect(schema.parse({ type: 'b', b: 123 })).toEqual({
1188
+ type: 'b',
1189
+ b: 123,
1190
+ });
1191
+
1192
+ expect(() => schema.parse({ type: 'c' })).toThrow(
1193
+ 'Invalid discriminator value',
1194
+ );
1195
+ });
1196
+ });
1197
+
1198
+ describe('lazy', () => {
1199
+ it('should parse lazy type', () => {
1200
+ interface Category {
1201
+ name: string;
1202
+ subCategories: Category[];
1203
+ }
1204
+
1205
+ const JsonCategory: JSONType<Category> = jsonTypes.lazy(() =>
1206
+ jsonTypes.object({
1207
+ name: jsonTypes.string(),
1208
+ subCategories: jsonTypes.array(JsonCategory),
1209
+ }),
1210
+ );
1211
+
1212
+ const schema = columnToZod(t.json(() => JsonCategory));
1213
+
1214
+ const valid = {
1215
+ name: 'name',
1216
+ subCategories: [{ name: 'name', subCategories: [] }],
1217
+ };
1218
+ expect(schema.parse(valid)).toEqual(valid);
1219
+
1220
+ expect(() =>
1221
+ schema.parse({ name: 'name', subCategories: [{ name: 'name' }] }),
1222
+ ).toThrow('Required');
1223
+ });
1224
+ });
1225
+ });
1226
+
1227
+ describe('as', () => {
1228
+ it('should convert one type to the same schema as another type', () => {
1229
+ const timestampAsInteger = columnToZod(
1230
+ t
1231
+ .timestamp()
1232
+ .encode((input: number) => new Date(input))
1233
+ .parse(Date.parse)
1234
+ .as(t.integer()),
1235
+ );
1236
+
1237
+ assertType<typeof timestampAsInteger, z.ZodNumber>(true);
1238
+ expect(timestampAsInteger.parse(123)).toBe(123);
1239
+
1240
+ const timestampAsDate = columnToZod(
1241
+ t.timestamp().parse((string) => new Date(string)),
1242
+ );
1243
+
1244
+ assertType<typeof timestampAsDate, z.ZodDate>(true);
1245
+ const date = new Date();
1246
+ expect(timestampAsDate.parse(date)).toEqual(date);
1247
+ });
1248
+ });
1249
+ });