zod-sqlite 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,45 +58,45 @@ npm install zod-sqlite
58
58
  Here's a simple example creating a users table:
59
59
 
60
60
  ```typescript
61
- import { z } from 'zod'
62
- import { createTable } from 'zod-sqlite'
63
-
64
- const users = createTable({
65
- columns: [
66
- { name: 'id', schema: z.int() },
67
- { name: 'email', schema: z.email() },
68
- { name: 'username', schema: z.string().min(3)
69
- .max(20) },
70
- { name: 'created_at', schema: z.date().default(new Date()) },
71
- ],
72
- indexes: [{ columns: ['email'], name: 'idx_users_email', unique: true }],
73
- name: 'users',
74
- primaryKeys: ['id'],
75
- })
76
-
77
- // Use the generated SQL
78
- console.log(users.table)
79
- // CREATE TABLE users (
80
- // id INTEGER NOT NULL,
81
- // email TEXT NOT NULL,
82
- // username TEXT NOT NULL CHECK(length(username) >= 3 AND length(username) <= 20),
83
- // created_at DATE NOT NULL DEFAULT '2026-01-06T...',
84
- // PRIMARY KEY (id)
85
- // );
86
-
87
- console.log(users.indexes[0])
88
- // CREATE UNIQUE INDEX idx_users_email ON users (email);
89
-
90
- // Validate data at runtime
91
- const result = users.schema.safeParse({
92
- created_at: new Date(),
93
- email: 'user@example.com',
94
- id: 1,
95
- username: 'john',
96
- })
97
-
98
- // TypeScript type inference
99
- type User = z.infer<typeof users.schema>
61
+ import { z } from 'zod'
62
+ import { createTable } from 'zod-sqlite'
63
+
64
+ const users = createTable({
65
+ columns: [
66
+ { name: 'id', schema: z.int() },
67
+ { name: 'email', schema: z.email() },
68
+ { name: 'username', schema: z.string().min(3)
69
+ .max(20) },
70
+ { name: 'created_at', schema: z.date().default(new Date()) },
71
+ ],
72
+ indexes: [{ columns: ['email'], name: 'idx_users_email', unique: true }],
73
+ name: 'users',
74
+ primaryKeys: ['id'],
75
+ })
76
+
77
+ // Use the generated SQL
78
+ console.log(users.table)
79
+ // CREATE TABLE users (
80
+ // id INTEGER NOT NULL,
81
+ // email TEXT NOT NULL,
82
+ // username TEXT NOT NULL CHECK(length(username) >= 3 AND length(username) <= 20),
83
+ // created_at DATE NOT NULL DEFAULT '2026-01-06T...',
84
+ // PRIMARY KEY (id)
85
+ // );
86
+
87
+ console.log(users.indexes[0])
88
+ // CREATE UNIQUE INDEX idx_users_email ON users (email);
89
+
90
+ // Validate data at runtime
91
+ const result = users.schema.safeParse({
92
+ created_at: new Date(),
93
+ email: 'user@example.com',
94
+ id: 1,
95
+ username: 'john',
96
+ })
97
+
98
+ // TypeScript type inference
99
+ type User = z.infer<typeof users.schema>
100
100
  // { id: number; email: string; username: string; created_at: Date }
101
101
  ```
102
102
 
@@ -114,7 +114,7 @@ Zod schemas serve as the source of truth for your database structure. Each colum
114
114
  `createTable` is the primary entry point. It accepts a configuration object and returns three things:
115
115
 
116
116
  ```typescript
117
- const result = createTable(config)
117
+ const result = createTable(config)
118
118
  // Returns: { table: string, indexes: string[], schema: ZodObject }
119
119
  ```
120
120
 
@@ -142,7 +142,7 @@ Creates a table definition with SQL statements and validation schema.
142
142
 
143
143
  **Returns:**
144
144
 
145
- ```
145
+ ```plaintext
146
146
  {
147
147
  table: string // CREATE TABLE SQL statement
148
148
  indexes: string[] // Array of CREATE INDEX statements
@@ -153,14 +153,14 @@ Creates a table definition with SQL statements and validation schema.
153
153
  **Example:**
154
154
 
155
155
  ```typescript
156
- const { indexes, schema, table } = createTable({
157
- columns: [
158
- { name: 'id', schema: z.int() },
159
- { name: 'name', schema: z.string() },
160
- { name: 'price', schema: z.number().min(0) },
161
- ],
162
- name: 'products',
163
- primaryKeys: ['id'],
156
+ const { indexes, schema, table } = createTable({
157
+ columns: [
158
+ { name: 'id', schema: z.int() },
159
+ { name: 'name', schema: z.string() },
160
+ { name: 'price', schema: z.number().min(0) },
161
+ ],
162
+ name: 'products',
163
+ primaryKeys: ['id'],
164
164
  })
165
165
  ```
166
166
 
@@ -169,11 +169,11 @@ const { indexes, schema, table } = createTable({
169
169
  Configuration object for table creation.
170
170
 
171
171
  ```typescript
172
- interface TableConfig {
173
- columns: Array<ColumnConfig> // Array of column definitions
174
- indexes?: Array<IndexConfig> // Optional index configurations
175
- name: string // Table name
176
- primaryKeys: Array<string> // Column names forming primary key
172
+ interface TableConfig {
173
+ columns: Array<ColumnConfig> // Array of column definitions
174
+ indexes?: Array<IndexConfig> // Optional index configurations
175
+ name: string // Table name
176
+ primaryKeys: Array<string> // Column names forming primary key
177
177
  }
178
178
  ```
179
179
 
@@ -182,11 +182,11 @@ interface TableConfig {
182
182
  Configuration for a single column.
183
183
 
184
184
  ```typescript
185
- interface ColumnConfig {
186
- name: string // Column name
187
- references?: ForeignKeyReference // Foreign key configuration
188
- schema: ZodType // Zod schema defining type and validation
189
- unique?: boolean // Whether values must be unique
185
+ interface ColumnConfig {
186
+ name: string // Column name
187
+ references?: ForeignKeyReference // Foreign key configuration
188
+ schema: ZodType // Zod schema defining type and validation
189
+ unique?: boolean // Whether values must be unique
190
190
  }
191
191
  ```
192
192
 
@@ -195,18 +195,18 @@ interface ColumnConfig {
195
195
  Foreign key constraint configuration.
196
196
 
197
197
  ```typescript
198
- interface ForeignKeyReference {
199
- column: string // Referenced column name
200
- onDelete?: ForeignKeyAction // Action on parent deletion
201
- onUpdate?: ForeignKeyAction // Action on parent update
202
- table: string // Referenced table name
203
- }
204
-
205
- type ForeignKeyAction
206
- = | 'CASCADE'
207
- | 'NO ACTION'
208
- | 'RESTRICT'
209
- | 'SET DEFAULT'
198
+ interface ForeignKeyReference {
199
+ column: string // Referenced column name
200
+ onDelete?: ForeignKeyAction // Action on parent deletion
201
+ onUpdate?: ForeignKeyAction // Action on parent update
202
+ table: string // Referenced table name
203
+ }
204
+
205
+ type ForeignKeyAction
206
+ = | 'CASCADE'
207
+ | 'NO ACTION'
208
+ | 'RESTRICT'
209
+ | 'SET DEFAULT'
210
210
  | 'SET NULL'
211
211
  ```
212
212
 
@@ -215,11 +215,11 @@ type ForeignKeyAction
215
215
  Index configuration for query optimization.
216
216
 
217
217
  ```typescript
218
- interface IndexConfig {
219
- columns: Array<string> // Indexed column names
220
- name: string // Index name
221
- unique?: boolean // Whether this is a unique index
222
- where?: string // Optional WHERE clause for partial index
218
+ interface IndexConfig {
219
+ columns: Array<string> // Indexed column names
220
+ name: string // Index name
221
+ unique?: boolean // Whether this is a unique index
222
+ where?: string // Optional WHERE clause for partial index
223
223
  }
224
224
  ```
225
225
 
@@ -271,9 +271,9 @@ These Zod wrappers are automatically unwrapped:
271
271
  - `.default(value)` - Adds DEFAULT clause
272
272
 
273
273
  ```typescript
274
- z.string().optional() // TEXT (nullable)
275
- z.number().default(0) // REAL NOT NULL DEFAULT 0
276
- z.string().nullable()
274
+ z.string().optional() // TEXT (nullable)
275
+ z.number().default(0) // REAL NOT NULL DEFAULT 0
276
+ z.string().nullable()
277
277
  .default('n/a') // TEXT DEFAULT 'n/a'
278
278
  ```
279
279
 
@@ -281,14 +281,14 @@ z.string().nullable()
281
281
 
282
282
  ### Basic Columns
283
283
 
284
- ```
284
+ ```plaintext
285
285
  { name: 'email', schema: z.email() }
286
286
  // SQL: email TEXT NOT NULL
287
287
  ```
288
288
 
289
289
  ### Optional and Nullable Columns
290
290
 
291
- ```
291
+ ```plaintext
292
292
  { name: 'bio', schema: z.string().optional() }
293
293
  // SQL: bio TEXT
294
294
 
@@ -298,7 +298,7 @@ z.string().nullable()
298
298
 
299
299
  ### Columns with Default Values
300
300
 
301
- ```
301
+ ```plaintext
302
302
  { name: 'status', schema: z.enum(['active', 'inactive']).default('active') }
303
303
  // SQL: status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'inactive'))
304
304
 
@@ -308,7 +308,7 @@ z.string().nullable()
308
308
 
309
309
  ### Unique Columns
310
310
 
311
- ```
311
+ ```plaintext
312
312
  { name: 'username', schema: z.string(), unique: true }
313
313
  // SQL: username TEXT NOT NULL UNIQUE
314
314
  ```
@@ -319,7 +319,7 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
319
319
 
320
320
  ### Enum Constraints
321
321
 
322
- ```
322
+ ```plaintext
323
323
  {
324
324
  name: 'role',
325
325
  schema: z.enum(['admin', 'user', 'guest'])
@@ -329,7 +329,7 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
329
329
 
330
330
  ### Literal Constraints
331
331
 
332
- ```
332
+ ```plaintext
333
333
  {
334
334
  name: 'type',
335
335
  schema: z.literal('premium')
@@ -349,7 +349,7 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
349
349
 
350
350
  ### Numeric Range Constraints
351
351
 
352
- ```
352
+ ```plaintext
353
353
  {
354
354
  name: 'age',
355
355
  schema: z.int().min(18).max(120)
@@ -365,7 +365,7 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
365
365
 
366
366
  ### String Length Constraints
367
367
 
368
- ```
368
+ ```plaintext
369
369
  {
370
370
  name: 'username',
371
371
  schema: z.string().min(3).max(20)
@@ -386,14 +386,14 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
386
386
  The most common pattern for entity tables:
387
387
 
388
388
  ```typescript
389
- createTable({
390
- columns: [
391
- { name: 'id', schema: z.int() },
392
- { name: 'email', schema: z.string() },
393
- ],
394
- name: 'users',
395
- primaryKeys: ['id'],
396
- })
389
+ createTable({
390
+ columns: [
391
+ { name: 'id', schema: z.int() },
392
+ { name: 'email', schema: z.string() },
393
+ ],
394
+ name: 'users',
395
+ primaryKeys: ['id'],
396
+ })
397
397
  // SQL: PRIMARY KEY (id)
398
398
  ```
399
399
 
@@ -402,35 +402,35 @@ createTable({
402
402
  Used for junction tables and multi-tenant data:
403
403
 
404
404
  ```typescript
405
- createTable({
406
- columns: [
407
- { name: 'user_id', schema: z.int() },
408
- { name: 'role_id', schema: z.int() },
409
- ],
410
- name: 'user_roles',
411
- primaryKeys: [
412
- 'user_id',
413
- 'role_id',
414
- ],
415
- })
405
+ createTable({
406
+ columns: [
407
+ { name: 'user_id', schema: z.int() },
408
+ { name: 'role_id', schema: z.int() },
409
+ ],
410
+ name: 'user_roles',
411
+ primaryKeys: [
412
+ 'user_id',
413
+ 'role_id',
414
+ ],
415
+ })
416
416
  // SQL: PRIMARY KEY (user_id, role_id)
417
417
  ```
418
418
 
419
419
  ### Multi-Tenant Example
420
420
 
421
421
  ```typescript
422
- createTable({
423
- columns: [
424
- { name: 'tenant_id', schema: z.string() },
425
- { name: 'doc_id', schema: z.int() },
426
- { name: 'title', schema: z.string() },
427
- ],
428
- name: 'documents',
429
- primaryKeys: [
430
- 'tenant_id',
431
- 'doc_id',
432
- ],
433
- })
422
+ createTable({
423
+ columns: [
424
+ { name: 'tenant_id', schema: z.string() },
425
+ { name: 'doc_id', schema: z.int() },
426
+ { name: 'title', schema: z.string() },
427
+ ],
428
+ name: 'documents',
429
+ primaryKeys: [
430
+ 'tenant_id',
431
+ 'doc_id',
432
+ ],
433
+ })
434
434
  // SQL: PRIMARY KEY (tenant_id, doc_id)
435
435
  ```
436
436
 
@@ -439,21 +439,21 @@ createTable({
439
439
  ### Basic Foreign Key
440
440
 
441
441
  ```typescript
442
- createTable({
443
- columns: [
444
- { name: 'id', schema: z.int() },
445
- {
446
- name: 'author_id',
447
- references: {
448
- column: 'id',
449
- table: 'users',
450
- },
451
- schema: z.int(),
452
- },
453
- ],
454
- name: 'posts',
455
- primaryKeys: ['id'],
456
- })
442
+ createTable({
443
+ columns: [
444
+ { name: 'id', schema: z.int() },
445
+ {
446
+ name: 'author_id',
447
+ references: {
448
+ column: 'id',
449
+ table: 'users',
450
+ },
451
+ schema: z.int(),
452
+ },
453
+ ],
454
+ name: 'posts',
455
+ primaryKeys: ['id'],
456
+ })
457
457
  // SQL: author_id INTEGER NOT NULL REFERENCES users(id)
458
458
  ```
459
459
 
@@ -461,7 +461,7 @@ createTable({
461
461
 
462
462
  Automatically delete child records when parent is deleted:
463
463
 
464
- ```
464
+ ```plaintext
465
465
  {
466
466
  name: 'user_id',
467
467
  schema: z.int(),
@@ -478,7 +478,7 @@ Automatically delete child records when parent is deleted:
478
478
 
479
479
  Prevent deletion of parent if children exist:
480
480
 
481
- ```
481
+ ```plaintext
482
482
  {
483
483
  name: 'category_id',
484
484
  schema: z.int(),
@@ -495,7 +495,7 @@ Prevent deletion of parent if children exist:
495
495
 
496
496
  Set foreign key to NULL when parent is deleted:
497
497
 
498
- ```
498
+ ```plaintext
499
499
  {
500
500
  name: 'manager_id',
501
501
  schema: z.int().nullable(),
@@ -512,7 +512,7 @@ Set foreign key to NULL when parent is deleted:
512
512
 
513
513
  Propagate updates to child records:
514
514
 
515
- ```
515
+ ```plaintext
516
516
  {
517
517
  name: 'parent_id',
518
518
  schema: z.int(),
@@ -530,7 +530,7 @@ Propagate updates to child records:
530
530
 
531
531
  ### Simple Index
532
532
 
533
- ```
533
+ ```plaintext
534
534
  indexes: [
535
535
  {
536
536
  columns: ['email'],
@@ -545,13 +545,13 @@ indexes: [
545
545
  Enforce uniqueness at the database level:
546
546
 
547
547
  ```typescript
548
- indexes: [
549
- {
550
- columns: ['username'],
551
- name: 'idx_users_username',
552
- unique: true,
553
- },
554
- ]
548
+ indexes: [
549
+ {
550
+ columns: ['username'],
551
+ name: 'idx_users_username',
552
+ unique: true,
553
+ },
554
+ ]
555
555
  // SQL: CREATE UNIQUE INDEX idx_users_username ON users (username);
556
556
  ```
557
557
 
@@ -560,15 +560,15 @@ indexes: [
560
560
  Index multiple columns together for multi-column queries:
561
561
 
562
562
  ```typescript
563
- indexes: [
564
- {
565
- columns: [
566
- 'author_id',
567
- 'created_at',
568
- ],
569
- name: 'idx_posts_author_date',
570
- },
571
- ]
563
+ indexes: [
564
+ {
565
+ columns: [
566
+ 'author_id',
567
+ 'created_at',
568
+ ],
569
+ name: 'idx_posts_author_date',
570
+ },
571
+ ]
572
572
  // SQL: CREATE INDEX idx_posts_author_date ON posts (author_id, created_at);
573
573
  ```
574
574
 
@@ -583,13 +583,13 @@ SELECT * FROM posts WHERE author_id = 123 AND created_at > '2024-01-01';
583
583
  Index only rows matching a condition:
584
584
 
585
585
  ```typescript
586
- indexes: [
587
- {
588
- columns: ['last_login'],
589
- name: 'idx_active_users',
590
- where: 'deleted_at IS NULL',
591
- },
592
- ]
586
+ indexes: [
587
+ {
588
+ columns: ['last_login'],
589
+ name: 'idx_active_users',
590
+ where: 'deleted_at IS NULL',
591
+ },
592
+ ]
593
593
  // SQL: CREATE INDEX idx_active_users ON posts (last_login) WHERE deleted_at IS NULL;
594
594
  ```
595
595
 
@@ -603,160 +603,160 @@ Benefits:
603
603
  ### Complete Blog Example
604
604
 
605
605
  ```typescript
606
- // Users table
607
- const users = createTable({
608
- columns: [
609
- { name: 'id', schema: z.int() },
610
- { name: 'email', schema: z.email(), unique: true },
611
- { name: 'username', schema: z.string().min(3)
612
- .max(20), unique: true },
613
- { name: 'role', schema: z.enum([
614
- 'admin',
615
- 'author',
616
- 'reader',
617
- ]).default('reader') },
618
- { name: 'created_at', schema: z.date().default(new Date()) },
619
- ],
620
- indexes: [
621
- { columns: ['email'], name: 'idx_users_email', unique: true },
622
- { columns: ['role'], name: 'idx_users_role' },
623
- ],
624
- name: 'users',
625
- primaryKeys: ['id'],
626
- })
627
-
628
- // Posts table with foreign key
629
- const posts = createTable({
630
- columns: [
631
- { name: 'id', schema: z.int() },
632
- { name: 'title', schema: z.string().min(1)
633
- .max(200) },
634
- { name: 'content', schema: z.string() },
635
- { name: 'status', schema: z.enum([
636
- 'draft',
637
- 'published',
638
- 'archived',
639
- ]).default('draft') },
640
- {
641
- name: 'author_id',
642
- references: {
643
- column: 'id',
644
- onDelete: 'CASCADE',
645
- table: 'users',
646
- },
647
- schema: z.int(),
648
- },
649
- { name: 'published_at', schema: z.date().nullable() },
650
- { name: 'created_at', schema: z.date().default(new Date()) },
651
- ],
652
- indexes: [
653
- { columns: ['author_id'], name: 'idx_posts_author' },
654
- {
655
- columns: ['published_at'],
656
- name: 'idx_posts_published',
657
- where: 'status = \'published\'',
658
- },
659
- { columns: [
660
- 'status',
661
- 'created_at',
662
- ], name: 'idx_posts_status_date' },
663
- ],
664
- name: 'posts',
665
- primaryKeys: ['id'],
666
- })
667
-
668
- // Junction table for tags (many-to-many)
669
- const postTags = createTable({
670
- columns: [
671
- {
672
- name: 'post_id',
673
- references: {
674
- column: 'id',
675
- onDelete: 'CASCADE',
676
- table: 'posts',
677
- },
678
- schema: z.int(),
679
- },
680
- {
681
- name: 'tag_id',
682
- references: {
683
- column: 'id',
684
- onDelete: 'CASCADE',
685
- table: 'tags',
686
- },
687
- schema: z.int(),
688
- },
689
- { name: 'created_at', schema: z.date().default(new Date()) },
690
- ],
691
- name: 'post_tags',
692
- primaryKeys: [
693
- 'post_id',
694
- 'tag_id',
695
- ],
606
+ // Users table
607
+ const users = createTable({
608
+ columns: [
609
+ { name: 'id', schema: z.int() },
610
+ { name: 'email', schema: z.email(), unique: true },
611
+ { name: 'username', schema: z.string().min(3)
612
+ .max(20), unique: true },
613
+ { name: 'role', schema: z.enum([
614
+ 'admin',
615
+ 'author',
616
+ 'reader',
617
+ ]).default('reader') },
618
+ { name: 'created_at', schema: z.date().default(new Date()) },
619
+ ],
620
+ indexes: [
621
+ { columns: ['email'], name: 'idx_users_email', unique: true },
622
+ { columns: ['role'], name: 'idx_users_role' },
623
+ ],
624
+ name: 'users',
625
+ primaryKeys: ['id'],
626
+ })
627
+
628
+ // Posts table with foreign key
629
+ const posts = createTable({
630
+ columns: [
631
+ { name: 'id', schema: z.int() },
632
+ { name: 'title', schema: z.string().min(1)
633
+ .max(200) },
634
+ { name: 'content', schema: z.string() },
635
+ { name: 'status', schema: z.enum([
636
+ 'draft',
637
+ 'published',
638
+ 'archived',
639
+ ]).default('draft') },
640
+ {
641
+ name: 'author_id',
642
+ references: {
643
+ column: 'id',
644
+ onDelete: 'CASCADE',
645
+ table: 'users',
646
+ },
647
+ schema: z.int(),
648
+ },
649
+ { name: 'published_at', schema: z.date().nullable() },
650
+ { name: 'created_at', schema: z.date().default(new Date()) },
651
+ ],
652
+ indexes: [
653
+ { columns: ['author_id'], name: 'idx_posts_author' },
654
+ {
655
+ columns: ['published_at'],
656
+ name: 'idx_posts_published',
657
+ where: 'status = \'published\'',
658
+ },
659
+ { columns: [
660
+ 'status',
661
+ 'created_at',
662
+ ], name: 'idx_posts_status_date' },
663
+ ],
664
+ name: 'posts',
665
+ primaryKeys: ['id'],
666
+ })
667
+
668
+ // Junction table for tags (many-to-many)
669
+ const postTags = createTable({
670
+ columns: [
671
+ {
672
+ name: 'post_id',
673
+ references: {
674
+ column: 'id',
675
+ onDelete: 'CASCADE',
676
+ table: 'posts',
677
+ },
678
+ schema: z.int(),
679
+ },
680
+ {
681
+ name: 'tag_id',
682
+ references: {
683
+ column: 'id',
684
+ onDelete: 'CASCADE',
685
+ table: 'tags',
686
+ },
687
+ schema: z.int(),
688
+ },
689
+ { name: 'created_at', schema: z.date().default(new Date()) },
690
+ ],
691
+ name: 'post_tags',
692
+ primaryKeys: [
693
+ 'post_id',
694
+ 'tag_id',
695
+ ],
696
696
  })
697
697
  ```
698
698
 
699
699
  ### E-commerce Example
700
700
 
701
701
  ```typescript
702
- const products = createTable({
703
- columns: [
704
- { name: 'id', schema: z.int() },
705
- { name: 'sku', schema: z.string().length(10), unique: true },
706
- { name: 'name', schema: z.string().min(1) },
707
- { name: 'description', schema: z.string().optional() },
708
- { name: 'price', schema: z.number().min(0) },
709
- { name: 'stock', schema: z.int().min(0)
710
- .default(0) },
711
- { name: 'active', schema: z.boolean().default(true) },
712
- {
713
- name: 'category_id',
714
- references: {
715
- column: 'id',
716
- onDelete: 'RESTRICT',
717
- table: 'categories',
718
- },
719
- schema: z.int(),
720
- },
721
- ],
722
- indexes: [
723
- { columns: ['sku'], name: 'idx_products_sku', unique: true },
724
- { columns: ['category_id'], name: 'idx_products_category' },
725
- {
726
- columns: [
727
- 'price',
728
- 'stock',
729
- ],
730
- name: 'idx_products_active',
731
- where: 'active = 1',
732
- },
733
- ],
734
- name: 'products',
735
- primaryKeys: ['id'],
702
+ const products = createTable({
703
+ columns: [
704
+ { name: 'id', schema: z.int() },
705
+ { name: 'sku', schema: z.string().length(10), unique: true },
706
+ { name: 'name', schema: z.string().min(1) },
707
+ { name: 'description', schema: z.string().optional() },
708
+ { name: 'price', schema: z.number().min(0) },
709
+ { name: 'stock', schema: z.int().min(0)
710
+ .default(0) },
711
+ { name: 'active', schema: z.boolean().default(true) },
712
+ {
713
+ name: 'category_id',
714
+ references: {
715
+ column: 'id',
716
+ onDelete: 'RESTRICT',
717
+ table: 'categories',
718
+ },
719
+ schema: z.int(),
720
+ },
721
+ ],
722
+ indexes: [
723
+ { columns: ['sku'], name: 'idx_products_sku', unique: true },
724
+ { columns: ['category_id'], name: 'idx_products_category' },
725
+ {
726
+ columns: [
727
+ 'price',
728
+ 'stock',
729
+ ],
730
+ name: 'idx_products_active',
731
+ where: 'active = 1',
732
+ },
733
+ ],
734
+ name: 'products',
735
+ primaryKeys: ['id'],
736
736
  })
737
737
  ```
738
738
 
739
739
  ### Self-Referencing Table
740
740
 
741
741
  ```typescript
742
- const employees = createTable({
743
- columns: [
744
- { name: 'id', schema: z.int() },
745
- { name: 'name', schema: z.string() },
746
- { name: 'email', schema: z.email(), unique: true },
747
- {
748
- name: 'manager_id',
749
- references: {
750
- column: 'id',
751
- onDelete: 'SET NULL',
752
- table: 'employees',
753
- },
754
- schema: z.int().nullable(),
755
- },
756
- ],
757
- indexes: [{ columns: ['manager_id'], name: 'idx_employees_manager' }],
758
- name: 'employees',
759
- primaryKeys: ['id'],
742
+ const employees = createTable({
743
+ columns: [
744
+ { name: 'id', schema: z.int() },
745
+ { name: 'name', schema: z.string() },
746
+ { name: 'email', schema: z.email(), unique: true },
747
+ {
748
+ name: 'manager_id',
749
+ references: {
750
+ column: 'id',
751
+ onDelete: 'SET NULL',
752
+ table: 'employees',
753
+ },
754
+ schema: z.int().nullable(),
755
+ },
756
+ ],
757
+ indexes: [{ columns: ['manager_id'], name: 'idx_employees_manager' }],
758
+ name: 'employees',
759
+ primaryKeys: ['id'],
760
760
  })
761
761
  ```
762
762
 
@@ -768,20 +768,20 @@ const employees = createTable({
768
768
 
769
769
  2. **Use appropriate types**: Choose the most specific Zod type that matches your data:
770
770
  ```typescript
771
- z.int() // For IDs and counts
772
- z.number().min(0) // For prices and quantities
773
- z.enum([
774
- 'a',
775
- 'b',
776
- ]) // For status fields
771
+ z.int() // For IDs and counts
772
+ z.number().min(0) // For prices and quantities
773
+ z.enum([
774
+ 'a',
775
+ 'b',
776
+ ]) // For status fields
777
777
  z.email() // For email addresses
778
778
  ```
779
779
 
780
780
  3. **Add validation at the schema level**: Leverage Zod's validation to prevent invalid data:
781
781
  ```typescript
782
- z.string().min(3)
783
- .max(50) // Username length
784
- z.number().min(0)
782
+ z.string().min(3)
783
+ .max(50) // Username length
784
+ z.number().min(0)
785
785
  .max(5) // Rating scale
786
786
  ```
787
787
 
@@ -807,23 +807,23 @@ const employees = createTable({
807
807
 
808
808
  3. **Use composite indexes wisely**: Order matters - most selective column first:
809
809
  ```typescript
810
- indexes: [
811
- // Good: Filters by tenant first (high selectivity)
812
- { columns: [
813
- 'tenant_id',
814
- 'user_id',
815
- ], name: 'idx_tenant_user' },
810
+ indexes: [
811
+ // Good: Filters by tenant first (high selectivity)
812
+ { columns: [
813
+ 'tenant_id',
814
+ 'user_id',
815
+ ], name: 'idx_tenant_user' },
816
816
  ]
817
817
  ```
818
818
 
819
819
  4. **Consider partial indexes**: Reduce index size for filtered queries:
820
820
  ```typescript
821
- indexes: [
822
- {
823
- columns: ['created_at'],
824
- name: 'idx_active_records',
825
- where: 'deleted_at IS NULL', // Only index active records
826
- },
821
+ indexes: [
822
+ {
823
+ columns: ['created_at'],
824
+ name: 'idx_active_records',
825
+ where: 'deleted_at IS NULL', // Only index active records
826
+ },
827
827
  ]
828
828
  ```
829
829
 
@@ -847,15 +847,15 @@ const employees = createTable({
847
847
 
848
848
  1. **Use type inference**: Let TypeScript infer types from your schema:
849
849
  ```typescript
850
- const { schema } = createTable(config)
850
+ const { schema } = createTable(config)
851
851
  type User = z.infer<typeof schema>
852
852
  ```
853
853
 
854
854
  2. **Validate at boundaries**: Use the generated schema to validate external data:
855
855
  ```typescript
856
- const result = users.schema.safeParse(inputData)
857
- if (!result.success) {
858
- console.error(result.error)
856
+ const result = users.schema.safeParse(inputData)
857
+ if (!result.success) {
858
+ console.error(result.error)
859
859
  }
860
860
  ```
861
861
 
@@ -870,43 +870,43 @@ Single-column foreign keys are supported at the column level. Composite foreign
870
870
  **Pattern for composite foreign keys:**
871
871
 
872
872
  ```typescript
873
- // 1. Create parent table with composite primary key
874
- const { table: ordersTable } = createTable({
875
- columns: [
876
- { name: 'tenant_id', schema: z.string() },
877
- { name: 'order_id', schema: z.int() },
878
- { name: 'total', schema: z.number() },
879
- ],
880
- name: 'orders',
881
- primaryKeys: [
882
- 'tenant_id',
883
- 'order_id',
884
- ],
885
- })
886
-
887
- // 2. Create child table with matching columns
888
- const { table: itemsTable } = createTable({
889
- columns: [
890
- { name: 'id', schema: z.int() },
891
- { name: 'tenant_id', schema: z.string() },
892
- { name: 'order_id', schema: z.int() },
893
- { name: 'product', schema: z.string() },
894
- ],
895
- name: 'order_items',
896
- primaryKeys: ['id'],
897
- })
898
-
899
- // 3. Execute table creation
900
- db.exec(ordersTable)
901
- db.exec(itemsTable)
902
-
903
- // 4. Add composite foreign key constraint
904
- db.exec(`
905
- ALTER TABLE order_items
906
- ADD CONSTRAINT fk_order
907
- FOREIGN KEY (tenant_id, order_id)
908
- REFERENCES orders(tenant_id, order_id)
909
- ON DELETE CASCADE
873
+ // 1. Create parent table with composite primary key
874
+ const { table: ordersTable } = createTable({
875
+ columns: [
876
+ { name: 'tenant_id', schema: z.string() },
877
+ { name: 'order_id', schema: z.int() },
878
+ { name: 'total', schema: z.number() },
879
+ ],
880
+ name: 'orders',
881
+ primaryKeys: [
882
+ 'tenant_id',
883
+ 'order_id',
884
+ ],
885
+ })
886
+
887
+ // 2. Create child table with matching columns
888
+ const { table: itemsTable } = createTable({
889
+ columns: [
890
+ { name: 'id', schema: z.int() },
891
+ { name: 'tenant_id', schema: z.string() },
892
+ { name: 'order_id', schema: z.int() },
893
+ { name: 'product', schema: z.string() },
894
+ ],
895
+ name: 'order_items',
896
+ primaryKeys: ['id'],
897
+ })
898
+
899
+ // 3. Execute table creation
900
+ db.exec(ordersTable)
901
+ db.exec(itemsTable)
902
+
903
+ // 4. Add composite foreign key constraint
904
+ db.exec(`
905
+ ALTER TABLE order_items
906
+ ADD CONSTRAINT fk_order
907
+ FOREIGN KEY (tenant_id, order_id)
908
+ REFERENCES orders(tenant_id, order_id)
909
+ ON DELETE CASCADE
910
910
  `)
911
911
  ```
912
912
 
@@ -919,7 +919,7 @@ Only specific Zod validations generate CHECK constraints:
919
919
 
920
920
  Custom refinements and complex validations work at the application level but don't generate SQL constraints:
921
921
 
922
- ```
922
+ ```plaintext
923
923
  {
924
924
  name: 'email',
925
925
  schema: z.string().refine(val => val.includes('@'), 'Must contain @')
@@ -935,7 +935,7 @@ Custom refinements and complex validations work at the application level but don
935
935
 
936
936
  Arrays and objects are stored as TEXT with JSON serialization. You must handle serialization manually:
937
937
 
938
- ```
938
+ ```plaintext
939
939
  { name: 'tags', schema: z.array(z.string()) }
940
940
  // SQL: tags TEXT NOT NULL
941
941
 
@@ -949,7 +949,7 @@ const tags = JSON.parse(result.tags) // ['tech', 'news']
949
949
 
950
950
  **Recommendation:** For better queryability, consider separate tables for array data:
951
951
  ```typescript
952
- // Instead of storing tags as JSON array
952
+ // Instead of storing tags as JSON array
953
953
  // Use a junction table: post_tags (post_id, tag_id)
954
954
  ```
955
955
 
@@ -957,7 +957,7 @@ const tags = JSON.parse(result.tags) // ['tech', 'news']
957
957
 
958
958
  Dates are stored as TEXT in ISO 8601 format. SQLite doesn't have a native DATE type:
959
959
 
960
- ```
960
+ ```plaintext
961
961
  { name: 'created_at', schema: z.date() }
962
962
  // SQL: created_at DATE NOT NULL
963
963
  // Stored as TEXT: '2026-01-07T12:30:00.000Z'
@@ -1002,12 +1002,12 @@ This tool is designed specifically for SQLite. Some features may not translate t
1002
1002
  **Critical:** SQLite does not enforce foreign key constraints by default. You must enable them:
1003
1003
 
1004
1004
  ```typescript
1005
- // Example using db0
1006
- const db = createDatabase(sqlite({
1007
- name: './db.sqlite',
1008
- }))
1009
-
1010
- // Enable for each database connection
1005
+ // Example using db0
1006
+ const db = createDatabase(sqlite({
1007
+ name: './db.sqlite',
1008
+ }))
1009
+
1010
+ // Enable for each database connection
1011
1011
  db.exec('PRAGMA foreign_keys = ON')
1012
1012
  ```
1013
1013
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as zod from "zod/v4/core";
2
-
3
2
  //#region src/types.d.ts
4
3
  /**
5
4
  * SQLite native column types.
@@ -454,7 +453,7 @@ declare function createTable<const TColumns extends ReadonlyArray<ColumnConfig<a
454
453
  indexes: string[];
455
454
  schema: import("zod").ZodObject<{ [K in TColumns[number]["name"]]: Extract<TColumns[number], {
456
455
  name: K;
457
- }>["schema"] }, import("zod/v4/core").$strip>;
456
+ }>["schema"]; }, import("zod/v4/core").$strip>;
458
457
  table: string;
459
458
  };
460
459
  //#endregion
package/dist/index.mjs CHANGED
@@ -48,9 +48,7 @@ function formatCheckConstraint(name, schema) {
48
48
  case "greater_than":
49
49
  constraints.push(`${name} >= ${checkValue}`);
50
50
  break;
51
- case "less_than":
52
- constraints.push(`${name} <= ${checkValue}`);
53
- break;
51
+ case "less_than": constraints.push(`${name} <= ${checkValue}`);
54
52
  }
55
53
  }
56
54
  return constraints.length > 0 ? `CHECK(${constraints.join(" AND ")})` : void 0;
@@ -69,9 +67,7 @@ function formatCheckConstraint(name, schema) {
69
67
  case "max_length":
70
68
  constraints.push(`length(${name}) <= ${checkDefinition.maximum}`);
71
69
  break;
72
- case "min_length":
73
- constraints.push(`length(${name}) >= ${checkDefinition.minimum}`);
74
- break;
70
+ case "min_length": constraints.push(`length(${name}) >= ${checkDefinition.minimum}`);
75
71
  }
76
72
  }
77
73
  return constraints.length > 0 ? `CHECK(${constraints.join(" AND ")})` : void 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zod-sqlite",
3
3
  "type": "module",
4
- "version": "1.0.3",
4
+ "version": "1.0.4",
5
5
  "private": false,
6
6
  "description": "Generate type-safe SQLite table schemas from Zod validation schemas.",
7
7
  "author": "Favour Emeka <favorodera@gmail.com>",
@@ -42,16 +42,16 @@
42
42
  "zod": "^4.4.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@favorodera/eslint-config": "^1.0.0",
46
- "@types/node": "^25.6.2",
47
- "@typescript/native-preview": "7.0.0-dev.20260509.2",
48
- "@vitest/ui": "4.1.8",
45
+ "@favorodera/eslint-config": "^1.0.4",
46
+ "@types/node": "^26.2.0",
47
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
48
+ "@vitest/ui": "4.1.10",
49
49
  "db0": "^0.3.4",
50
- "eslint": "^10.4.1",
50
+ "eslint": "^10.8.1",
51
51
  "relizy": "^1.4.9",
52
- "tsdown": "^0.22.0",
52
+ "tsdown": "^0.22.14",
53
53
  "typescript": "^6.0.3",
54
- "vitest": "^4.1.5"
54
+ "vitest": "^4.1.10"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "tsdown",
@@ -60,6 +60,6 @@
60
60
  "test:watch": "vitest --watch --ui",
61
61
  "typecheck": "tsc --noEmit",
62
62
  "lint": "eslint . --fix",
63
- "ready": "pnpm install && pnpm build && pnpm lint && pnpm typecheck && pnpm test"
63
+ "ready": "pnpm install && pnpm lint && pnpm typecheck && pnpm test && pnpm build"
64
64
  }
65
65
  }