zod-sqlite 1.0.2 → 1.0.3
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/LICENSE +21 -0
- package/README.md +226 -185
- package/dist/index.d.mts +461 -0
- package/dist/index.mjs +287 -0
- package/package.json +38 -55
- package/dist/index.cjs +0 -255
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -478
- package/dist/index.d.ts +0 -478
- package/dist/index.js +0 -253
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
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
|
|
1
|
+
<div align="center">
|
|
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>
|
|
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
|
+
<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>
|
|
8
|
+
<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
|
+
</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<br>
|
|
4
13
|
|
|
5
14
|
## Table of Contents
|
|
6
15
|
|
|
@@ -44,32 +53,25 @@ This tool bridges the gap between Zod schemas and SQLite database definitions. I
|
|
|
44
53
|
npm install zod-sqlite
|
|
45
54
|
```
|
|
46
55
|
|
|
47
|
-
Requires Zod v4 as a peer dependency:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm install zod
|
|
51
|
-
```
|
|
52
|
-
|
|
53
56
|
## Quick Start
|
|
54
57
|
|
|
55
58
|
Here's a simple example creating a users table:
|
|
56
59
|
|
|
57
60
|
```typescript
|
|
58
|
-
import { createTable } from 'zod-sqlite'
|
|
59
61
|
import { z } from 'zod'
|
|
62
|
+
import { createTable } from 'zod-sqlite'
|
|
60
63
|
|
|
61
64
|
const users = createTable({
|
|
62
|
-
name: 'users',
|
|
63
65
|
columns: [
|
|
64
66
|
{ name: 'id', schema: z.int() },
|
|
65
67
|
{ name: 'email', schema: z.email() },
|
|
66
|
-
{ name: 'username', schema: z.string().min(3)
|
|
67
|
-
|
|
68
|
+
{ name: 'username', schema: z.string().min(3)
|
|
69
|
+
.max(20) },
|
|
70
|
+
{ name: 'created_at', schema: z.date().default(new Date()) },
|
|
68
71
|
],
|
|
72
|
+
indexes: [{ columns: ['email'], name: 'idx_users_email', unique: true }],
|
|
73
|
+
name: 'users',
|
|
69
74
|
primaryKeys: ['id'],
|
|
70
|
-
indexes: [
|
|
71
|
-
{ name: 'idx_users_email', columns: ['email'], unique: true }
|
|
72
|
-
]
|
|
73
75
|
})
|
|
74
76
|
|
|
75
77
|
// Use the generated SQL
|
|
@@ -87,10 +89,10 @@ console.log(users.indexes[0])
|
|
|
87
89
|
|
|
88
90
|
// Validate data at runtime
|
|
89
91
|
const result = users.schema.safeParse({
|
|
90
|
-
|
|
92
|
+
created_at: new Date(),
|
|
91
93
|
email: 'user@example.com',
|
|
94
|
+
id: 1,
|
|
92
95
|
username: 'john',
|
|
93
|
-
created_at: new Date()
|
|
94
96
|
})
|
|
95
97
|
|
|
96
98
|
// TypeScript type inference
|
|
@@ -140,7 +142,7 @@ Creates a table definition with SQL statements and validation schema.
|
|
|
140
142
|
|
|
141
143
|
**Returns:**
|
|
142
144
|
|
|
143
|
-
```
|
|
145
|
+
```
|
|
144
146
|
{
|
|
145
147
|
table: string // CREATE TABLE SQL statement
|
|
146
148
|
indexes: string[] // Array of CREATE INDEX statements
|
|
@@ -151,14 +153,14 @@ Creates a table definition with SQL statements and validation schema.
|
|
|
151
153
|
**Example:**
|
|
152
154
|
|
|
153
155
|
```typescript
|
|
154
|
-
const {
|
|
155
|
-
name: 'products',
|
|
156
|
+
const { indexes, schema, table } = createTable({
|
|
156
157
|
columns: [
|
|
157
158
|
{ name: 'id', schema: z.int() },
|
|
158
159
|
{ name: 'name', schema: z.string() },
|
|
159
|
-
{ name: 'price', schema: z.number().min(0) }
|
|
160
|
+
{ name: 'price', schema: z.number().min(0) },
|
|
160
161
|
],
|
|
161
|
-
|
|
162
|
+
name: 'products',
|
|
163
|
+
primaryKeys: ['id'],
|
|
162
164
|
})
|
|
163
165
|
```
|
|
164
166
|
|
|
@@ -167,11 +169,11 @@ const { table, indexes, schema } = createTable({
|
|
|
167
169
|
Configuration object for table creation.
|
|
168
170
|
|
|
169
171
|
```typescript
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
175
177
|
}
|
|
176
178
|
```
|
|
177
179
|
|
|
@@ -180,11 +182,11 @@ type TableConfig = {
|
|
|
180
182
|
Configuration for a single column.
|
|
181
183
|
|
|
182
184
|
```typescript
|
|
183
|
-
|
|
184
|
-
name: string
|
|
185
|
-
schema: ZodType // Zod schema defining type and validation
|
|
186
|
-
unique?: boolean // Whether values must be unique
|
|
185
|
+
interface ColumnConfig {
|
|
186
|
+
name: string // Column name
|
|
187
187
|
references?: ForeignKeyReference // Foreign key configuration
|
|
188
|
+
schema: ZodType // Zod schema defining type and validation
|
|
189
|
+
unique?: boolean // Whether values must be unique
|
|
188
190
|
}
|
|
189
191
|
```
|
|
190
192
|
|
|
@@ -193,19 +195,19 @@ type ColumnConfig = {
|
|
|
193
195
|
Foreign key constraint configuration.
|
|
194
196
|
|
|
195
197
|
```typescript
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
201
203
|
}
|
|
202
204
|
|
|
203
|
-
type ForeignKeyAction
|
|
204
|
-
| '
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
type ForeignKeyAction
|
|
206
|
+
= | 'CASCADE'
|
|
207
|
+
| 'NO ACTION'
|
|
208
|
+
| 'RESTRICT'
|
|
209
|
+
| 'SET DEFAULT'
|
|
210
|
+
| 'SET NULL'
|
|
209
211
|
```
|
|
210
212
|
|
|
211
213
|
### IndexConfig
|
|
@@ -213,11 +215,11 @@ type ForeignKeyAction =
|
|
|
213
215
|
Index configuration for query optimization.
|
|
214
216
|
|
|
215
217
|
```typescript
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
unique?: boolean
|
|
220
|
-
where?: string
|
|
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
|
|
221
223
|
}
|
|
222
224
|
```
|
|
223
225
|
|
|
@@ -269,23 +271,24 @@ These Zod wrappers are automatically unwrapped:
|
|
|
269
271
|
- `.default(value)` - Adds DEFAULT clause
|
|
270
272
|
|
|
271
273
|
```typescript
|
|
272
|
-
z.string().optional()
|
|
273
|
-
z.number().default(0)
|
|
274
|
-
z.string().nullable()
|
|
274
|
+
z.string().optional() // TEXT (nullable)
|
|
275
|
+
z.number().default(0) // REAL NOT NULL DEFAULT 0
|
|
276
|
+
z.string().nullable()
|
|
277
|
+
.default('n/a') // TEXT DEFAULT 'n/a'
|
|
275
278
|
```
|
|
276
279
|
|
|
277
280
|
## Column Configuration
|
|
278
281
|
|
|
279
282
|
### Basic Columns
|
|
280
283
|
|
|
281
|
-
```
|
|
284
|
+
```
|
|
282
285
|
{ name: 'email', schema: z.email() }
|
|
283
286
|
// SQL: email TEXT NOT NULL
|
|
284
287
|
```
|
|
285
288
|
|
|
286
289
|
### Optional and Nullable Columns
|
|
287
290
|
|
|
288
|
-
```
|
|
291
|
+
```
|
|
289
292
|
{ name: 'bio', schema: z.string().optional() }
|
|
290
293
|
// SQL: bio TEXT
|
|
291
294
|
|
|
@@ -295,7 +298,7 @@ z.string().nullable().default('n/a') // TEXT DEFAULT 'n/a'
|
|
|
295
298
|
|
|
296
299
|
### Columns with Default Values
|
|
297
300
|
|
|
298
|
-
```
|
|
301
|
+
```
|
|
299
302
|
{ name: 'status', schema: z.enum(['active', 'inactive']).default('active') }
|
|
300
303
|
// SQL: status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'inactive'))
|
|
301
304
|
|
|
@@ -305,7 +308,7 @@ z.string().nullable().default('n/a') // TEXT DEFAULT 'n/a'
|
|
|
305
308
|
|
|
306
309
|
### Unique Columns
|
|
307
310
|
|
|
308
|
-
```
|
|
311
|
+
```
|
|
309
312
|
{ name: 'username', schema: z.string(), unique: true }
|
|
310
313
|
// SQL: username TEXT NOT NULL UNIQUE
|
|
311
314
|
```
|
|
@@ -316,7 +319,7 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
|
|
|
316
319
|
|
|
317
320
|
### Enum Constraints
|
|
318
321
|
|
|
319
|
-
```
|
|
322
|
+
```
|
|
320
323
|
{
|
|
321
324
|
name: 'role',
|
|
322
325
|
schema: z.enum(['admin', 'user', 'guest'])
|
|
@@ -326,7 +329,7 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
|
|
|
326
329
|
|
|
327
330
|
### Literal Constraints
|
|
328
331
|
|
|
329
|
-
```
|
|
332
|
+
```
|
|
330
333
|
{
|
|
331
334
|
name: 'type',
|
|
332
335
|
schema: z.literal('premium')
|
|
@@ -346,7 +349,7 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
|
|
|
346
349
|
|
|
347
350
|
### Numeric Range Constraints
|
|
348
351
|
|
|
349
|
-
```
|
|
352
|
+
```
|
|
350
353
|
{
|
|
351
354
|
name: 'age',
|
|
352
355
|
schema: z.int().min(18).max(120)
|
|
@@ -362,7 +365,7 @@ SQL CHECK constraints are automatically generated from Zod validation rules.
|
|
|
362
365
|
|
|
363
366
|
### String Length Constraints
|
|
364
367
|
|
|
365
|
-
```
|
|
368
|
+
```
|
|
366
369
|
{
|
|
367
370
|
name: 'username',
|
|
368
371
|
schema: z.string().min(3).max(20)
|
|
@@ -384,12 +387,12 @@ The most common pattern for entity tables:
|
|
|
384
387
|
|
|
385
388
|
```typescript
|
|
386
389
|
createTable({
|
|
387
|
-
name: 'users',
|
|
388
390
|
columns: [
|
|
389
391
|
{ name: 'id', schema: z.int() },
|
|
390
|
-
{ name: 'email', schema: z.string() }
|
|
392
|
+
{ name: 'email', schema: z.string() },
|
|
391
393
|
],
|
|
392
|
-
|
|
394
|
+
name: 'users',
|
|
395
|
+
primaryKeys: ['id'],
|
|
393
396
|
})
|
|
394
397
|
// SQL: PRIMARY KEY (id)
|
|
395
398
|
```
|
|
@@ -400,12 +403,15 @@ Used for junction tables and multi-tenant data:
|
|
|
400
403
|
|
|
401
404
|
```typescript
|
|
402
405
|
createTable({
|
|
403
|
-
name: 'user_roles',
|
|
404
406
|
columns: [
|
|
405
407
|
{ name: 'user_id', schema: z.int() },
|
|
406
|
-
{ name: 'role_id', schema: z.int() }
|
|
408
|
+
{ name: 'role_id', schema: z.int() },
|
|
409
|
+
],
|
|
410
|
+
name: 'user_roles',
|
|
411
|
+
primaryKeys: [
|
|
412
|
+
'user_id',
|
|
413
|
+
'role_id',
|
|
407
414
|
],
|
|
408
|
-
primaryKeys: ['user_id', 'role_id']
|
|
409
415
|
})
|
|
410
416
|
// SQL: PRIMARY KEY (user_id, role_id)
|
|
411
417
|
```
|
|
@@ -414,13 +420,16 @@ createTable({
|
|
|
414
420
|
|
|
415
421
|
```typescript
|
|
416
422
|
createTable({
|
|
417
|
-
name: 'documents',
|
|
418
423
|
columns: [
|
|
419
424
|
{ name: 'tenant_id', schema: z.string() },
|
|
420
425
|
{ name: 'doc_id', schema: z.int() },
|
|
421
|
-
{ name: 'title', schema: z.string() }
|
|
426
|
+
{ name: 'title', schema: z.string() },
|
|
427
|
+
],
|
|
428
|
+
name: 'documents',
|
|
429
|
+
primaryKeys: [
|
|
430
|
+
'tenant_id',
|
|
431
|
+
'doc_id',
|
|
422
432
|
],
|
|
423
|
-
primaryKeys: ['tenant_id', 'doc_id']
|
|
424
433
|
})
|
|
425
434
|
// SQL: PRIMARY KEY (tenant_id, doc_id)
|
|
426
435
|
```
|
|
@@ -431,19 +440,19 @@ createTable({
|
|
|
431
440
|
|
|
432
441
|
```typescript
|
|
433
442
|
createTable({
|
|
434
|
-
name: 'posts',
|
|
435
443
|
columns: [
|
|
436
444
|
{ name: 'id', schema: z.int() },
|
|
437
|
-
{
|
|
438
|
-
name: 'author_id',
|
|
439
|
-
schema: z.int(),
|
|
445
|
+
{
|
|
446
|
+
name: 'author_id',
|
|
440
447
|
references: {
|
|
448
|
+
column: 'id',
|
|
441
449
|
table: 'users',
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
450
|
+
},
|
|
451
|
+
schema: z.int(),
|
|
452
|
+
},
|
|
445
453
|
],
|
|
446
|
-
|
|
454
|
+
name: 'posts',
|
|
455
|
+
primaryKeys: ['id'],
|
|
447
456
|
})
|
|
448
457
|
// SQL: author_id INTEGER NOT NULL REFERENCES users(id)
|
|
449
458
|
```
|
|
@@ -452,7 +461,7 @@ createTable({
|
|
|
452
461
|
|
|
453
462
|
Automatically delete child records when parent is deleted:
|
|
454
463
|
|
|
455
|
-
```
|
|
464
|
+
```
|
|
456
465
|
{
|
|
457
466
|
name: 'user_id',
|
|
458
467
|
schema: z.int(),
|
|
@@ -469,7 +478,7 @@ Automatically delete child records when parent is deleted:
|
|
|
469
478
|
|
|
470
479
|
Prevent deletion of parent if children exist:
|
|
471
480
|
|
|
472
|
-
```
|
|
481
|
+
```
|
|
473
482
|
{
|
|
474
483
|
name: 'category_id',
|
|
475
484
|
schema: z.int(),
|
|
@@ -486,7 +495,7 @@ Prevent deletion of parent if children exist:
|
|
|
486
495
|
|
|
487
496
|
Set foreign key to NULL when parent is deleted:
|
|
488
497
|
|
|
489
|
-
```
|
|
498
|
+
```
|
|
490
499
|
{
|
|
491
500
|
name: 'manager_id',
|
|
492
501
|
schema: z.int().nullable(),
|
|
@@ -503,7 +512,7 @@ Set foreign key to NULL when parent is deleted:
|
|
|
503
512
|
|
|
504
513
|
Propagate updates to child records:
|
|
505
514
|
|
|
506
|
-
```
|
|
515
|
+
```
|
|
507
516
|
{
|
|
508
517
|
name: 'parent_id',
|
|
509
518
|
schema: z.int(),
|
|
@@ -521,12 +530,12 @@ Propagate updates to child records:
|
|
|
521
530
|
|
|
522
531
|
### Simple Index
|
|
523
532
|
|
|
524
|
-
```
|
|
533
|
+
```
|
|
525
534
|
indexes: [
|
|
526
|
-
{
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
535
|
+
{
|
|
536
|
+
columns: ['email'],
|
|
537
|
+
name: 'idx_users_email',
|
|
538
|
+
},
|
|
530
539
|
]
|
|
531
540
|
// SQL: CREATE INDEX idx_users_email ON users (email);
|
|
532
541
|
```
|
|
@@ -537,11 +546,11 @@ Enforce uniqueness at the database level:
|
|
|
537
546
|
|
|
538
547
|
```typescript
|
|
539
548
|
indexes: [
|
|
540
|
-
{
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
unique: true
|
|
544
|
-
}
|
|
549
|
+
{
|
|
550
|
+
columns: ['username'],
|
|
551
|
+
name: 'idx_users_username',
|
|
552
|
+
unique: true,
|
|
553
|
+
},
|
|
545
554
|
]
|
|
546
555
|
// SQL: CREATE UNIQUE INDEX idx_users_username ON users (username);
|
|
547
556
|
```
|
|
@@ -552,10 +561,13 @@ Index multiple columns together for multi-column queries:
|
|
|
552
561
|
|
|
553
562
|
```typescript
|
|
554
563
|
indexes: [
|
|
555
|
-
{
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
564
|
+
{
|
|
565
|
+
columns: [
|
|
566
|
+
'author_id',
|
|
567
|
+
'created_at',
|
|
568
|
+
],
|
|
569
|
+
name: 'idx_posts_author_date',
|
|
570
|
+
},
|
|
559
571
|
]
|
|
560
572
|
// SQL: CREATE INDEX idx_posts_author_date ON posts (author_id, created_at);
|
|
561
573
|
```
|
|
@@ -572,11 +584,11 @@ Index only rows matching a condition:
|
|
|
572
584
|
|
|
573
585
|
```typescript
|
|
574
586
|
indexes: [
|
|
575
|
-
{
|
|
576
|
-
name: 'idx_active_users',
|
|
587
|
+
{
|
|
577
588
|
columns: ['last_login'],
|
|
578
|
-
|
|
579
|
-
|
|
589
|
+
name: 'idx_active_users',
|
|
590
|
+
where: 'deleted_at IS NULL',
|
|
591
|
+
},
|
|
580
592
|
]
|
|
581
593
|
// SQL: CREATE INDEX idx_active_users ON posts (last_login) WHERE deleted_at IS NULL;
|
|
582
594
|
```
|
|
@@ -593,78 +605,94 @@ Benefits:
|
|
|
593
605
|
```typescript
|
|
594
606
|
// Users table
|
|
595
607
|
const users = createTable({
|
|
596
|
-
name: 'users',
|
|
597
608
|
columns: [
|
|
598
609
|
{ name: 'id', schema: z.int() },
|
|
599
610
|
{ name: 'email', schema: z.email(), unique: true },
|
|
600
|
-
{ name: 'username', schema: z.string().min(3)
|
|
601
|
-
|
|
602
|
-
{ name: '
|
|
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()) },
|
|
603
619
|
],
|
|
604
|
-
primaryKeys: ['id'],
|
|
605
620
|
indexes: [
|
|
606
|
-
{
|
|
607
|
-
{
|
|
608
|
-
]
|
|
621
|
+
{ columns: ['email'], name: 'idx_users_email', unique: true },
|
|
622
|
+
{ columns: ['role'], name: 'idx_users_role' },
|
|
623
|
+
],
|
|
624
|
+
name: 'users',
|
|
625
|
+
primaryKeys: ['id'],
|
|
609
626
|
})
|
|
610
627
|
|
|
611
628
|
// Posts table with foreign key
|
|
612
629
|
const posts = createTable({
|
|
613
|
-
name: 'posts',
|
|
614
630
|
columns: [
|
|
615
631
|
{ name: 'id', schema: z.int() },
|
|
616
|
-
{ name: 'title', schema: z.string().min(1)
|
|
632
|
+
{ name: 'title', schema: z.string().min(1)
|
|
633
|
+
.max(200) },
|
|
617
634
|
{ name: 'content', schema: z.string() },
|
|
618
|
-
{ name: 'status', schema: z.enum([
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
635
|
+
{ name: 'status', schema: z.enum([
|
|
636
|
+
'draft',
|
|
637
|
+
'published',
|
|
638
|
+
'archived',
|
|
639
|
+
]).default('draft') },
|
|
640
|
+
{
|
|
641
|
+
name: 'author_id',
|
|
622
642
|
references: {
|
|
623
|
-
table: 'users',
|
|
624
643
|
column: 'id',
|
|
625
|
-
onDelete: 'CASCADE'
|
|
626
|
-
|
|
644
|
+
onDelete: 'CASCADE',
|
|
645
|
+
table: 'users',
|
|
646
|
+
},
|
|
647
|
+
schema: z.int(),
|
|
627
648
|
},
|
|
628
649
|
{ name: 'published_at', schema: z.date().nullable() },
|
|
629
|
-
{ name: 'created_at', schema: z.date().default(new Date()) }
|
|
650
|
+
{ name: 'created_at', schema: z.date().default(new Date()) },
|
|
630
651
|
],
|
|
631
|
-
primaryKeys: ['id'],
|
|
632
652
|
indexes: [
|
|
633
|
-
{
|
|
634
|
-
{
|
|
635
|
-
name: 'idx_posts_published',
|
|
653
|
+
{ columns: ['author_id'], name: 'idx_posts_author' },
|
|
654
|
+
{
|
|
636
655
|
columns: ['published_at'],
|
|
637
|
-
|
|
656
|
+
name: 'idx_posts_published',
|
|
657
|
+
where: 'status = \'published\'',
|
|
638
658
|
},
|
|
639
|
-
{
|
|
640
|
-
|
|
659
|
+
{ columns: [
|
|
660
|
+
'status',
|
|
661
|
+
'created_at',
|
|
662
|
+
], name: 'idx_posts_status_date' },
|
|
663
|
+
],
|
|
664
|
+
name: 'posts',
|
|
665
|
+
primaryKeys: ['id'],
|
|
641
666
|
})
|
|
642
667
|
|
|
643
668
|
// Junction table for tags (many-to-many)
|
|
644
669
|
const postTags = createTable({
|
|
645
|
-
name: 'post_tags',
|
|
646
670
|
columns: [
|
|
647
|
-
{
|
|
648
|
-
name: 'post_id',
|
|
649
|
-
schema: z.int(),
|
|
671
|
+
{
|
|
672
|
+
name: 'post_id',
|
|
650
673
|
references: {
|
|
651
|
-
table: 'posts',
|
|
652
674
|
column: 'id',
|
|
653
|
-
onDelete: 'CASCADE'
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
{
|
|
657
|
-
name: 'tag_id',
|
|
675
|
+
onDelete: 'CASCADE',
|
|
676
|
+
table: 'posts',
|
|
677
|
+
},
|
|
658
678
|
schema: z.int(),
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
name: 'tag_id',
|
|
659
682
|
references: {
|
|
660
|
-
table: 'tags',
|
|
661
683
|
column: 'id',
|
|
662
|
-
onDelete: 'CASCADE'
|
|
663
|
-
|
|
684
|
+
onDelete: 'CASCADE',
|
|
685
|
+
table: 'tags',
|
|
686
|
+
},
|
|
687
|
+
schema: z.int(),
|
|
664
688
|
},
|
|
665
|
-
{ name: 'created_at', schema: z.date().default(new Date()) }
|
|
689
|
+
{ name: 'created_at', schema: z.date().default(new Date()) },
|
|
690
|
+
],
|
|
691
|
+
name: 'post_tags',
|
|
692
|
+
primaryKeys: [
|
|
693
|
+
'post_id',
|
|
694
|
+
'tag_id',
|
|
666
695
|
],
|
|
667
|
-
primaryKeys: ['post_id', 'tag_id']
|
|
668
696
|
})
|
|
669
697
|
```
|
|
670
698
|
|
|
@@ -672,35 +700,39 @@ const postTags = createTable({
|
|
|
672
700
|
|
|
673
701
|
```typescript
|
|
674
702
|
const products = createTable({
|
|
675
|
-
name: 'products',
|
|
676
703
|
columns: [
|
|
677
704
|
{ name: 'id', schema: z.int() },
|
|
678
705
|
{ name: 'sku', schema: z.string().length(10), unique: true },
|
|
679
706
|
{ name: 'name', schema: z.string().min(1) },
|
|
680
707
|
{ name: 'description', schema: z.string().optional() },
|
|
681
708
|
{ name: 'price', schema: z.number().min(0) },
|
|
682
|
-
{ name: 'stock', schema: z.int().min(0)
|
|
709
|
+
{ name: 'stock', schema: z.int().min(0)
|
|
710
|
+
.default(0) },
|
|
683
711
|
{ name: 'active', schema: z.boolean().default(true) },
|
|
684
|
-
{
|
|
685
|
-
name: 'category_id',
|
|
686
|
-
schema: z.int(),
|
|
712
|
+
{
|
|
713
|
+
name: 'category_id',
|
|
687
714
|
references: {
|
|
688
|
-
table: 'categories',
|
|
689
715
|
column: 'id',
|
|
690
|
-
onDelete: 'RESTRICT'
|
|
691
|
-
|
|
692
|
-
|
|
716
|
+
onDelete: 'RESTRICT',
|
|
717
|
+
table: 'categories',
|
|
718
|
+
},
|
|
719
|
+
schema: z.int(),
|
|
720
|
+
},
|
|
693
721
|
],
|
|
694
|
-
primaryKeys: ['id'],
|
|
695
722
|
indexes: [
|
|
696
|
-
{
|
|
697
|
-
{
|
|
698
|
-
{
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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'],
|
|
704
736
|
})
|
|
705
737
|
```
|
|
706
738
|
|
|
@@ -708,25 +740,23 @@ const products = createTable({
|
|
|
708
740
|
|
|
709
741
|
```typescript
|
|
710
742
|
const employees = createTable({
|
|
711
|
-
name: 'employees',
|
|
712
743
|
columns: [
|
|
713
744
|
{ name: 'id', schema: z.int() },
|
|
714
745
|
{ name: 'name', schema: z.string() },
|
|
715
746
|
{ name: 'email', schema: z.email(), unique: true },
|
|
716
|
-
{
|
|
717
|
-
name: 'manager_id',
|
|
718
|
-
schema: z.int().nullable(),
|
|
747
|
+
{
|
|
748
|
+
name: 'manager_id',
|
|
719
749
|
references: {
|
|
720
|
-
table: 'employees',
|
|
721
750
|
column: 'id',
|
|
722
|
-
onDelete: 'SET NULL'
|
|
723
|
-
|
|
724
|
-
|
|
751
|
+
onDelete: 'SET NULL',
|
|
752
|
+
table: 'employees',
|
|
753
|
+
},
|
|
754
|
+
schema: z.int().nullable(),
|
|
755
|
+
},
|
|
725
756
|
],
|
|
757
|
+
indexes: [{ columns: ['manager_id'], name: 'idx_employees_manager' }],
|
|
758
|
+
name: 'employees',
|
|
726
759
|
primaryKeys: ['id'],
|
|
727
|
-
indexes: [
|
|
728
|
-
{ name: 'idx_employees_manager', columns: ['manager_id'] }
|
|
729
|
-
]
|
|
730
760
|
})
|
|
731
761
|
```
|
|
732
762
|
|
|
@@ -738,16 +768,21 @@ const employees = createTable({
|
|
|
738
768
|
|
|
739
769
|
2. **Use appropriate types**: Choose the most specific Zod type that matches your data:
|
|
740
770
|
```typescript
|
|
741
|
-
z.int()
|
|
742
|
-
z.number().min(0)
|
|
743
|
-
z.enum([
|
|
744
|
-
|
|
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
|
+
z.email() // For email addresses
|
|
745
778
|
```
|
|
746
779
|
|
|
747
780
|
3. **Add validation at the schema level**: Leverage Zod's validation to prevent invalid data:
|
|
748
781
|
```typescript
|
|
749
|
-
z.string().min(3)
|
|
750
|
-
|
|
782
|
+
z.string().min(3)
|
|
783
|
+
.max(50) // Username length
|
|
784
|
+
z.number().min(0)
|
|
785
|
+
.max(5) // Rating scale
|
|
751
786
|
```
|
|
752
787
|
|
|
753
788
|
### Foreign Keys
|
|
@@ -774,18 +809,21 @@ const employees = createTable({
|
|
|
774
809
|
```typescript
|
|
775
810
|
indexes: [
|
|
776
811
|
// Good: Filters by tenant first (high selectivity)
|
|
777
|
-
{
|
|
812
|
+
{ columns: [
|
|
813
|
+
'tenant_id',
|
|
814
|
+
'user_id',
|
|
815
|
+
], name: 'idx_tenant_user' },
|
|
778
816
|
]
|
|
779
817
|
```
|
|
780
818
|
|
|
781
819
|
4. **Consider partial indexes**: Reduce index size for filtered queries:
|
|
782
820
|
```typescript
|
|
783
821
|
indexes: [
|
|
784
|
-
{
|
|
785
|
-
name: 'idx_active_records',
|
|
822
|
+
{
|
|
786
823
|
columns: ['created_at'],
|
|
787
|
-
|
|
788
|
-
|
|
824
|
+
name: 'idx_active_records',
|
|
825
|
+
where: 'deleted_at IS NULL', // Only index active records
|
|
826
|
+
},
|
|
789
827
|
]
|
|
790
828
|
```
|
|
791
829
|
|
|
@@ -834,24 +872,27 @@ Single-column foreign keys are supported at the column level. Composite foreign
|
|
|
834
872
|
```typescript
|
|
835
873
|
// 1. Create parent table with composite primary key
|
|
836
874
|
const { table: ordersTable } = createTable({
|
|
837
|
-
name: 'orders',
|
|
838
875
|
columns: [
|
|
839
876
|
{ name: 'tenant_id', schema: z.string() },
|
|
840
877
|
{ name: 'order_id', schema: z.int() },
|
|
841
878
|
{ name: 'total', schema: z.number() },
|
|
842
879
|
],
|
|
843
|
-
|
|
880
|
+
name: 'orders',
|
|
881
|
+
primaryKeys: [
|
|
882
|
+
'tenant_id',
|
|
883
|
+
'order_id',
|
|
884
|
+
],
|
|
844
885
|
})
|
|
845
886
|
|
|
846
887
|
// 2. Create child table with matching columns
|
|
847
888
|
const { table: itemsTable } = createTable({
|
|
848
|
-
name: 'order_items',
|
|
849
889
|
columns: [
|
|
850
890
|
{ name: 'id', schema: z.int() },
|
|
851
891
|
{ name: 'tenant_id', schema: z.string() },
|
|
852
892
|
{ name: 'order_id', schema: z.int() },
|
|
853
893
|
{ name: 'product', schema: z.string() },
|
|
854
894
|
],
|
|
895
|
+
name: 'order_items',
|
|
855
896
|
primaryKeys: ['id'],
|
|
856
897
|
})
|
|
857
898
|
|
|
@@ -878,7 +919,7 @@ Only specific Zod validations generate CHECK constraints:
|
|
|
878
919
|
|
|
879
920
|
Custom refinements and complex validations work at the application level but don't generate SQL constraints:
|
|
880
921
|
|
|
881
|
-
```
|
|
922
|
+
```
|
|
882
923
|
{
|
|
883
924
|
name: 'email',
|
|
884
925
|
schema: z.string().refine(val => val.includes('@'), 'Must contain @')
|
|
@@ -894,7 +935,7 @@ Custom refinements and complex validations work at the application level but don
|
|
|
894
935
|
|
|
895
936
|
Arrays and objects are stored as TEXT with JSON serialization. You must handle serialization manually:
|
|
896
937
|
|
|
897
|
-
```
|
|
938
|
+
```
|
|
898
939
|
{ name: 'tags', schema: z.array(z.string()) }
|
|
899
940
|
// SQL: tags TEXT NOT NULL
|
|
900
941
|
|
|
@@ -916,7 +957,7 @@ const tags = JSON.parse(result.tags) // ['tech', 'news']
|
|
|
916
957
|
|
|
917
958
|
Dates are stored as TEXT in ISO 8601 format. SQLite doesn't have a native DATE type:
|
|
918
959
|
|
|
919
|
-
```
|
|
960
|
+
```
|
|
920
961
|
{ name: 'created_at', schema: z.date() }
|
|
921
962
|
// SQL: created_at DATE NOT NULL
|
|
922
963
|
// Stored as TEXT: '2026-01-07T12:30:00.000Z'
|
|
@@ -962,7 +1003,7 @@ This tool is designed specifically for SQLite. Some features may not translate t
|
|
|
962
1003
|
|
|
963
1004
|
```typescript
|
|
964
1005
|
// Example using db0
|
|
965
|
-
const db = createDatabase(sqlite({
|
|
1006
|
+
const db = createDatabase(sqlite({
|
|
966
1007
|
name: './db.sqlite',
|
|
967
1008
|
}))
|
|
968
1009
|
|