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.
- package/README.md +382 -379
- package/dist/index.d.mts +206 -212
- package/dist/index.mjs +4 -287
- 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
|
|
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/
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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:
|
|
174
|
-
indexes?:
|
|
175
|
-
name: string // Table name
|
|
176
|
-
primaryKeys:
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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:
|
|
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
|
-
|
|
953
|
-
|
|
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
|
|