zod-sqlite 1.0.3 → 1.0.5

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