zod-sqlite 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,478 @@
1
+ import * as zod from 'zod/v4/core';
2
+ import * as zod$1 from 'zod';
3
+
4
+ /**
5
+ * SQLite native column types.
6
+ *
7
+ * SQLite uses a dynamic type system with these five storage classes.
8
+ * Column values are stored in one of these formats, and SQLite performs
9
+ * type conversions as needed during operations.
10
+ *
11
+ * @see https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
12
+ */
13
+ type SQLiteType = 'TEXT' | 'INTEGER' | 'REAL' | 'BLOB' | 'NULL';
14
+ /**
15
+ * SQLite supported column types.
16
+ *
17
+ * These types are supported by SQLite but not as native storage classes.
18
+ * They are often used as aliases for other types or for specific purposes.
19
+ *
20
+ * @see https://www.sqlite.org/datatype3.html#type_affinity
21
+ */
22
+ type SQLiteSupportType = 'BOOLEAN' | 'BIGINT' | 'DATE' | 'DATETIME' | 'NUMERIC' | 'FLOAT';
23
+ /**
24
+ * Foreign key constraint actions for ON DELETE and ON UPDATE clauses.
25
+ *
26
+ * These actions determine how referential integrity is maintained when
27
+ * parent rows are modified or deleted. They control the cascading behavior
28
+ * of foreign key relationships.
29
+ *
30
+ * **Actions:**
31
+ * - `NO ACTION`: Deferred constraint check (performed after statement completes)
32
+ * - `RESTRICT`: Prevent the operation immediately (fails before any changes)
33
+ * - `SET NULL`: Automatically set foreign key columns to NULL
34
+ * - `SET DEFAULT`: Automatically set foreign key columns to their default values
35
+ * - `CASCADE`: Propagate the DELETE or UPDATE to all child rows
36
+ *
37
+ * **Default behavior**: If not specified, SQLite uses `NO ACTION`
38
+ *
39
+ * @example
40
+ * // Delete a user and all their posts automatically
41
+ * references: {
42
+ * table: 'users',
43
+ * column: 'id',
44
+ * onDelete: 'CASCADE'
45
+ * }
46
+ *
47
+ * @example
48
+ * // Prevent deleting a category that has products
49
+ * references: {
50
+ * table: 'categories',
51
+ * column: 'id',
52
+ * onDelete: 'RESTRICT'
53
+ * }
54
+ *
55
+ * @see https://www.sqlite.org/foreignkeys.html#fk_actions
56
+ */
57
+ type ForeignKeyAction = 'NO ACTION' | 'RESTRICT' | 'SET NULL' | 'SET DEFAULT' | 'CASCADE';
58
+ /**
59
+ * Foreign key reference configuration.
60
+ *
61
+ * Defines a relationship to another table by referencing one of its columns.
62
+ * This creates a constraint ensuring that values in this column must exist
63
+ * in the referenced table's column (referential integrity).
64
+ *
65
+ * **Important notes:**
66
+ * - The referenced column should typically be a PRIMARY KEY or have a UNIQUE constraint
67
+ * - Foreign key constraints are not enforced by default in SQLite (must enable with PRAGMA)
68
+ * - Composite foreign keys (multiple columns) are defined at the table level, not here
69
+ *
70
+ * @example
71
+ * // Simple foreign key to users table
72
+ * {
73
+ * table: 'users',
74
+ * column: 'id',
75
+ * onDelete: 'CASCADE'
76
+ * }
77
+ *
78
+ * @example
79
+ * // Foreign key with both actions specified
80
+ * {
81
+ * table: 'categories',
82
+ * column: 'id',
83
+ * onDelete: 'SET NULL',
84
+ * onUpdate: 'CASCADE'
85
+ * }
86
+ */
87
+ type ForeignKeyReference = {
88
+ /**
89
+ * Name of the referenced table.
90
+ *
91
+ * This is the parent table that contains the column being referenced.
92
+ */
93
+ table: string;
94
+ /**
95
+ * Name of the referenced column in the parent table.
96
+ *
97
+ * This column should typically be a PRIMARY KEY or have a UNIQUE constraint
98
+ * to ensure the relationship is well-defined.
99
+ */
100
+ column: string;
101
+ /**
102
+ * Action to take when the referenced row is deleted.
103
+ *
104
+ * Determines what happens to this row when the parent row is deleted.
105
+ *
106
+ * @default 'NO ACTION'
107
+ */
108
+ onDelete?: ForeignKeyAction;
109
+ /**
110
+ * Action to take when the referenced column value is updated.
111
+ *
112
+ * Determines what happens to this foreign key value when the parent
113
+ * column value changes.
114
+ *
115
+ * @default 'NO ACTION'
116
+ */
117
+ onUpdate?: ForeignKeyAction;
118
+ };
119
+ /**
120
+ * Database index configuration.
121
+ *
122
+ * Indexes improve query performance by creating a separate data structure
123
+ * that allows faster lookups on specific columns. They're essential for:
124
+ * - Columns frequently used in WHERE clauses
125
+ * - Columns used in JOIN conditions
126
+ * - Columns used in ORDER BY clauses
127
+ *
128
+ * **Trade-offs:**
129
+ * - **Pros**: Faster SELECT queries, faster JOIN operations
130
+ * - **Cons**: Slower INSERT/UPDATE/DELETE, increased storage size
131
+ *
132
+ * **Best practices:**
133
+ * - Index foreign key columns
134
+ * - Index columns frequently used in WHERE clauses
135
+ * - Consider composite indexes for multi-column queries
136
+ * - Use partial indexes (with WHERE clause) for filtered queries
137
+ *
138
+ * @example
139
+ * // Simple index on email column
140
+ * {
141
+ * name: 'idx_users_email',
142
+ * columns: ['email'],
143
+ * unique: true
144
+ * }
145
+ *
146
+ * @example
147
+ * // Composite index for queries filtering by status and date
148
+ * {
149
+ * name: 'idx_orders_status_date',
150
+ * columns: ['status', 'created_at']
151
+ * }
152
+ *
153
+ * @example
154
+ * // Partial index for active users only
155
+ * {
156
+ * name: 'idx_active_users',
157
+ * columns: ['username'],
158
+ * where: 'status = "active"'
159
+ * }
160
+ */
161
+ type IndexConfig = {
162
+ /**
163
+ * Unique name for the index.
164
+ *
165
+ * Used in the CREATE INDEX statement. Convention is to prefix with 'idx_'
166
+ * followed by table name and column names.
167
+ *
168
+ * @example 'idx_users_email'
169
+ * @example 'idx_posts_author_created'
170
+ */
171
+ name: string;
172
+ /**
173
+ * Column names to include in the index.
174
+ *
175
+ * For composite indexes, the order matters: queries that filter on the
176
+ * first columns benefit most from the index.
177
+ *
178
+ * @example ['email'] - single column
179
+ * @example ['last_name', 'first_name'] - composite index
180
+ */
181
+ columns: string[];
182
+ /**
183
+ * Whether this is a unique index.
184
+ *
185
+ * When true, enforces uniqueness constraint on the indexed columns.
186
+ * Duplicate values will cause INSERT/UPDATE to fail.
187
+ *
188
+ * **Note**: PRIMARY KEY columns automatically have a unique index.
189
+ *
190
+ * @default false
191
+ */
192
+ unique?: boolean;
193
+ /**
194
+ * Optional WHERE clause for a partial index.
195
+ *
196
+ * Only rows matching this condition are included in the index, reducing
197
+ * index size and improving performance for filtered queries.
198
+ *
199
+ * Useful when you frequently query a subset of rows.
200
+ *
201
+ * @example 'deleted_at IS NULL' - index only active records
202
+ * @example 'status = "published"' - index only published content
203
+ * @example 'price > 100' - index only expensive items
204
+ */
205
+ where?: string;
206
+ };
207
+ /**
208
+ * Database column configuration.
209
+ *
210
+ * Defines a single column in a table, including its name, type (via Zod schema),
211
+ * validation rules, and optional constraints like uniqueness and foreign keys.
212
+ *
213
+ * **Column definition workflow:**
214
+ * 1. Zod schema is analyzed to determine SQLite type (TEXT, INTEGER, etc.)
215
+ * 2. Zod schema provides TypeScript type safety and runtime validation
216
+ * 3. Constraints (unique, foreign key) are applied at the database level
217
+ *
218
+ * @example
219
+ * // Simple text column
220
+ * {
221
+ * name: 'email',
222
+ * schema: z.email()
223
+ * }
224
+ *
225
+ * @example
226
+ * // Unique column with validation
227
+ * {
228
+ * name: 'username',
229
+ * schema: z.string().min(3).max(20),
230
+ * unique: true
231
+ * }
232
+ *
233
+ * @example
234
+ * // Foreign key column
235
+ * {
236
+ * name: 'author_id',
237
+ * schema: z.int(),
238
+ * references: {
239
+ * table: 'users',
240
+ * column: 'id',
241
+ * onDelete: 'CASCADE'
242
+ * }
243
+ * }
244
+ *
245
+ * @example
246
+ * // Optional column with default
247
+ * {
248
+ * name: 'status',
249
+ * schema: z.enum(['draft', 'published']).default('draft')
250
+ * }
251
+ */
252
+ /**
253
+ * Database column configuration.
254
+ *
255
+ * Defines a single column in a table, including its name, type (via Zod schema),
256
+ * validation rules, and optional constraints like uniqueness and foreign keys.
257
+ *
258
+ * @template TName - Literal string type for the column name
259
+ * @template TSchema - Zod type for the column schema
260
+ */
261
+ type ColumnConfig<TName extends string = string, TSchema extends zod.$ZodType = zod.$ZodType> = {
262
+ /**
263
+ * Name of the column.
264
+ *
265
+ * Used in CREATE TABLE statement and all SQL queries.
266
+ * Should use snake_case by convention.
267
+ *
268
+ * @example 'user_id'
269
+ * @example 'created_at'
270
+ */
271
+ name: TName;
272
+ /**
273
+ * Zod schema defining the column's type, validation, and constraints.
274
+ *
275
+ * The Zod schema serves dual purposes:
276
+ * 1. **Database level**: Mapped to SQLite type (TEXT, INTEGER, REAL, BLOB, NULL)
277
+ * 2. **Application level**: Provides TypeScript types and runtime validation
278
+ *
279
+ * **Zod to SQLite type mapping:**
280
+ * - `z.string()`, `z.enum()`, `z.literal()`, `z.date()` → TEXT
281
+ * - `z.number()` → REAL (or INTEGER if `.int()` is used)
282
+ * - `z.bigint()`, `z.boolean()` → INTEGER
283
+ * - `z.custom()` for file/blob → BLOB
284
+ * - `z.null()`, `z.undefined()` → NULL
285
+ *
286
+ * **Zod features mapped to SQL:**
287
+ * - `.optional()`, `.nullable()` → column allows NULL
288
+ * - `.default(value)` → DEFAULT clause in SQL
289
+ * - `.min()`, `.max()`, `.email()`, etc. → runtime validation only
290
+ *
291
+ * @example z.string() - TEXT column
292
+ * @example z.int() - INTEGER column
293
+ * @example z.string().nullable() - TEXT column that allows NULL
294
+ * @example z.boolean().default(false) - INTEGER with DEFAULT 0
295
+ */
296
+ schema: TSchema;
297
+ /**
298
+ * Whether column values must be unique across all rows.
299
+ *
300
+ * When true, adds a UNIQUE constraint to the column. Any attempt to
301
+ * INSERT or UPDATE a duplicate value will fail.
302
+ *
303
+ * **Note**: This is different from PRIMARY KEY:
304
+ * - UNIQUE columns can be NULL (unless also NOT NULL)
305
+ * - A table can have multiple UNIQUE columns
306
+ * - PRIMARY KEY implies UNIQUE + NOT NULL
307
+ *
308
+ * @default false
309
+ *
310
+ * @example
311
+ * // Unique email address
312
+ * {
313
+ * name: 'email',
314
+ * schema: z.email(),
315
+ * unique: true
316
+ * }
317
+ */
318
+ unique?: boolean;
319
+ /**
320
+ * Foreign key reference configuration.
321
+ *
322
+ * Creates a constraint linking this column to a column in another table.
323
+ * Ensures referential integrity by preventing orphaned records.
324
+ *
325
+ * **Requirements:**
326
+ * - Referenced column should typically be a PRIMARY KEY or UNIQUE
327
+ * - Types must be compatible between the two columns
328
+ * - Foreign keys must be enabled: `PRAGMA foreign_keys = ON`
329
+ *
330
+ * **Composite foreign keys** (multiple columns referencing multiple columns)
331
+ * cannot be defined here - they must be defined at the table level as a
332
+ * separate constraint.
333
+ *
334
+ * @example
335
+ * {
336
+ * name: 'user_id',
337
+ * schema: z.int(),
338
+ * references: {
339
+ * table: 'users',
340
+ * column: 'id',
341
+ * onDelete: 'CASCADE'
342
+ * }
343
+ * }
344
+ */
345
+ references?: ForeignKeyReference;
346
+ };
347
+ /**
348
+ * Complete database table configuration.
349
+ */
350
+ type TableConfig<TColumns extends readonly ColumnConfig<string, zod.$ZodType>[] = readonly ColumnConfig<string, zod.$ZodType>[]> = {
351
+ /**
352
+ * Name of the table.
353
+ *
354
+ * Used in CREATE TABLE statement and all SQL queries.
355
+ * Should use snake_case by convention (e.g., 'user_profiles', 'order_items').
356
+ *
357
+ * **Naming conventions:**
358
+ * - Use plural nouns for tables representing collections (e.g., 'users', 'posts')
359
+ * - Use singular nouns for junction tables (e.g., 'user_role', 'post_tag')
360
+ * - Avoid SQL reserved keywords (e.g., 'order' → 'orders' or 'user_order')
361
+ *
362
+ * @example 'users'
363
+ * @example 'blog_posts'
364
+ * @example 'user_sessions'
365
+ */
366
+ name: string;
367
+ /**
368
+ * Array of column configurations defining the table structure.
369
+ *
370
+ * Each column specifies its name, type (via Zod schema), and optional
371
+ * constraints. The order of columns here determines their order in the
372
+ * CREATE TABLE statement.
373
+ *
374
+ * **Best practices:**
375
+ * - List primary key column(s) first
376
+ * - Group related columns together
377
+ * - Place frequently queried columns earlier
378
+ * - Put large TEXT/BLOB columns last
379
+ *
380
+ * @example
381
+ * [
382
+ * { name: 'id', schema: z.int() },
383
+ * { name: 'email', schema: z.email() },
384
+ * { name: 'created_at', schema: z.date().default(new Date()) }
385
+ * ]
386
+ */
387
+ columns: TColumns;
388
+ /**
389
+ * Column name(s) that form the primary key.
390
+ *
391
+ * The primary key uniquely identifies each row in the table and cannot
392
+ * contain NULL values. Always provided as an array, even for single-column
393
+ * primary keys.
394
+ *
395
+ * **Single column primary key:**
396
+ * - Use an array with one element (e.g., `['id']`)
397
+ * - Typically an auto-incrementing integer
398
+ * - Most common pattern for standard entity tables
399
+ *
400
+ * **Composite primary key:**
401
+ * - Use an array with multiple elements (e.g., `['user_id', 'post_id']`)
402
+ * - Common in junction tables for many-to-many relationships
403
+ * - Order matters: most specific/selective column should come first
404
+ * - The combination of all columns must be unique
405
+ *
406
+ * **Notes:**
407
+ * - Every table should have a primary key
408
+ * - Primary key columns are automatically indexed by SQLite
409
+ * - Primary key implies UNIQUE + NOT NULL constraints on all columns
410
+ * - Cannot be changed after table creation (requires table rebuild)
411
+ *
412
+ * @example ['id'] - single column primary key
413
+ * @example ['user_id', 'role_id'] - composite primary key for junction table
414
+ * @example ['tenant_id', 'record_id'] - composite key for multi-tenant data
415
+ * @example ['country_code', 'postal_code'] - composite key for location data
416
+ */
417
+ primaryKeys: ReadonlyArray<TColumns[number]['name']>;
418
+ /**
419
+ * Optional array of index configurations for query optimization.
420
+ *
421
+ * Indexes speed up SELECT queries but slow down INSERT/UPDATE/DELETE
422
+ * operations. Create indexes strategically based on your query patterns.
423
+ *
424
+ * **When to add indexes:**
425
+ * - Foreign key columns (for JOIN performance)
426
+ * - Columns frequently used in WHERE clauses
427
+ * - Columns used in ORDER BY clauses
428
+ * - Columns with high selectivity (many unique values)
429
+ *
430
+ * **When NOT to add indexes:**
431
+ * - Small tables (< 1000 rows) - full table scan is fast enough
432
+ * - Columns with low selectivity (few unique values, like boolean)
433
+ * - Tables with frequent writes and rare reads
434
+ * - Columns rarely used in queries
435
+ *
436
+ * @example
437
+ * [
438
+ * // Index foreign key for JOIN performance
439
+ * { name: 'idx_posts_author', columns: ['author_id'] },
440
+ *
441
+ * // Unique index for lookups
442
+ * { name: 'idx_users_email', columns: ['email'], unique: true },
443
+ *
444
+ * // Composite index for multi-column queries
445
+ * { name: 'idx_posts_status_date', columns: ['status', 'created_at'] },
446
+ *
447
+ * // Partial index for filtered queries
448
+ * {
449
+ * name: 'idx_active_users',
450
+ * columns: ['last_login'],
451
+ * where: 'deleted_at IS NULL'
452
+ * }
453
+ * ]
454
+ */
455
+ indexes?: IndexConfig[];
456
+ };
457
+
458
+ /**
459
+ * Creates a table definition and Zod schema.
460
+ *
461
+ * This function is the main entry point for defining a table. It takes a configuration object
462
+ * and returns:
463
+ * 1. `table`: SQL CREATE TABLE statement
464
+ * 2. `indexes`: Array of SQL CREATE INDEX statements
465
+ * 3. `schema`: Zod object schema matching the table structure
466
+ *
467
+ * @param config - Table configuration object
468
+ * @returns Object containing SQL statements and Zod schema
469
+ */
470
+ declare function createTable<const TColumns extends readonly ColumnConfig<any, any>[]>(config: TableConfig<TColumns>): {
471
+ table: string;
472
+ indexes: string[];
473
+ schema: zod$1.ZodObject<{ [K in TColumns[number]["name"]]: Extract<TColumns[number], {
474
+ name: K;
475
+ }>["schema"]; }, zod.$strip>;
476
+ };
477
+
478
+ export { type ColumnConfig, type ForeignKeyAction, type ForeignKeyReference, type IndexConfig, type SQLiteSupportType, type SQLiteType, type TableConfig, createTable };