@b9g/zen 0.1.2 → 0.1.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.
@@ -0,0 +1,495 @@
1
+ /**
2
+ * Database wrapper - the main API for schema-driven SQL.
3
+ *
4
+ * Provides typed queries with entity normalization and reference resolution.
5
+ * Extends EventTarget for IndexedDB-style migration events.
6
+ */
7
+ import type { Table, View, Queryable, Row, Insert, FullTableOnly, JoinedRow } from "./table.js";
8
+ import { CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME, NOW, TODAY, isSQLBuiltin, resolveSQLBuiltin, type SQLBuiltin } from "./builtins.js";
9
+ export { CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME, NOW, TODAY, isSQLBuiltin, resolveSQLBuiltin, type SQLBuiltin, };
10
+ /**
11
+ * Encode data for database insert/update operations.
12
+ * Converts app values → DB values using .db.encode() functions.
13
+ *
14
+ * Priority order:
15
+ * 1. Custom field-level .db.encode() (always wins)
16
+ * 2. Driver.encodeValue() (dialect-specific)
17
+ * 3. Auto-encode fallback (JSON.stringify, Date→string)
18
+ *
19
+ * @param table - The table definition
20
+ * @param data - The data to encode
21
+ * @param driver - Optional driver for dialect-specific encoding
22
+ */
23
+ export declare function encodeData<T extends Table<any>>(table: T, data: Record<string, unknown>, driver?: Driver): Record<string, unknown>;
24
+ import { decodeData } from "./table.js";
25
+ export { decodeData };
26
+ /**
27
+ * Database driver interface.
28
+ *
29
+ * Drivers own all SQL generation and dialect-specific behavior. They receive
30
+ * template parts (strings + values) and build SQL with native placeholders.
31
+ *
32
+ * This keeps the Database class dialect-agnostic - it only interacts with
33
+ * drivers through this interface and the `supportsReturning` capability flag.
34
+ */
35
+ export interface Driver {
36
+ /**
37
+ * Execute a query and return all rows.
38
+ * Driver joins strings with native placeholders (? or $1, $2, ...).
39
+ */
40
+ all<T = Record<string, unknown>>(strings: TemplateStringsArray, values: unknown[]): Promise<T[]>;
41
+ /**
42
+ * Execute a query and return the first row.
43
+ */
44
+ get<T = Record<string, unknown>>(strings: TemplateStringsArray, values: unknown[]): Promise<T | null>;
45
+ /**
46
+ * Execute a statement and return the number of affected rows.
47
+ */
48
+ run(strings: TemplateStringsArray, values: unknown[]): Promise<number>;
49
+ /**
50
+ * Execute a query and return a single value, or null if no rows.
51
+ */
52
+ val<T = unknown>(strings: TemplateStringsArray, values: unknown[]): Promise<T | null>;
53
+ /**
54
+ * Close the database connection.
55
+ */
56
+ close(): Promise<void>;
57
+ /**
58
+ * Execute a function within a database transaction.
59
+ */
60
+ transaction<T>(fn: (txDriver: Driver) => Promise<T>): Promise<T>;
61
+ /**
62
+ * Whether this driver supports RETURNING clause for INSERT/UPDATE.
63
+ * - SQLite: true
64
+ * - PostgreSQL: true
65
+ * - MySQL: false
66
+ * - MariaDB 10.5+: true
67
+ */
68
+ readonly supportsReturning: boolean;
69
+ /**
70
+ * Execute a function while holding an exclusive migration lock.
71
+ */
72
+ withMigrationLock?<T>(fn: () => Promise<T>): Promise<T>;
73
+ /**
74
+ * Encode a JS value for database insertion.
75
+ * Called for each value before building the SQL query.
76
+ *
77
+ * @param value - The JS value to encode
78
+ * @param fieldType - The field type: "text", "integer", "real", "boolean", "datetime", "json"
79
+ * @returns The encoded value suitable for this database dialect
80
+ */
81
+ encodeValue?(value: unknown, fieldType: string): unknown;
82
+ /**
83
+ * Decode a database value to JS.
84
+ * Called for each column value after query execution.
85
+ *
86
+ * @param value - The raw database value
87
+ * @param fieldType - The field type: "text", "integer", "real", "boolean", "datetime", "json"
88
+ * @returns The decoded JS value
89
+ */
90
+ decodeValue?(value: unknown, fieldType: string): unknown;
91
+ /**
92
+ * Ensure a table exists with its columns and indexes.
93
+ *
94
+ * **For new tables**: Creates the table with full structure including
95
+ * primary key, unique constraints, foreign keys, and indexes.
96
+ *
97
+ * **For existing tables**: Only performs safe, additive operations:
98
+ * - Adds missing columns
99
+ * - Adds missing non-unique indexes
100
+ *
101
+ * Throws SchemaDriftError if existing table has missing constraints
102
+ * (directs user to run ensureConstraints).
103
+ */
104
+ ensureTable?<T extends Table<any>>(table: T): Promise<EnsureResult>;
105
+ /**
106
+ * Ensure constraints (unique, foreign key) are applied to an existing table.
107
+ *
108
+ * Performs preflight checks to detect data violations before applying
109
+ * constraints. Throws ConstraintPreflightError if violations found.
110
+ */
111
+ ensureConstraints?<T extends Table<any>>(table: T): Promise<EnsureResult>;
112
+ /**
113
+ * Ensure a view exists in the database.
114
+ *
115
+ * Creates the view if it doesn't exist, or replaces it if it does.
116
+ * The base table must already exist.
117
+ */
118
+ ensureView?<T extends View<any>>(view: T): Promise<EnsureResult>;
119
+ /**
120
+ * Copy column data for safe rename migrations.
121
+ *
122
+ * Executes: UPDATE <table> SET <toField> = <fromField> WHERE <toField> IS NULL
123
+ *
124
+ * @param table The table to update
125
+ * @param fromField Source column (may be legacy/not in schema)
126
+ * @param toField Destination column (must exist in schema)
127
+ * @returns Number of rows updated
128
+ */
129
+ copyColumn?<T extends Table<any>>(table: T, fromField: string, toField: string): Promise<number>;
130
+ /** Optional introspection: list columns for a table (name, type, nullability). */
131
+ getColumns?(tableName: string): Promise<{
132
+ name: string;
133
+ type?: string;
134
+ notnull?: boolean;
135
+ }[]>;
136
+ }
137
+ /**
138
+ * Result from ensure operations.
139
+ */
140
+ export interface EnsureResult {
141
+ /** Whether any DDL was executed (false = no-op) */
142
+ applied: boolean;
143
+ }
144
+ /**
145
+ * Event fired when database version increases during open().
146
+ *
147
+ * Similar to IndexedDB's IDBVersionChangeEvent combined with
148
+ * ServiceWorker's ExtendableEvent (for waitUntil support).
149
+ *
150
+ * **Migration model**: Zealot uses monotonic, forward-only versioning:
151
+ * - Versions are integers that only increase: 1 → 2 → 3 → ...
152
+ * - Downgrading (e.g., 3 → 2) is NOT supported
153
+ * - Branching version histories are NOT supported
154
+ * - Each version should be deployed once and never modified
155
+ *
156
+ * **Best practices**:
157
+ * - Use conditional checks: `if (e.oldVersion < 2) { ... }`
158
+ * - Prefer additive changes (new columns, indexes) over destructive ones
159
+ * - Never modify past migrations - add new versions instead
160
+ * - Keep migrations idempotent when possible (use db.ensureTable())
161
+ */
162
+ export declare class DatabaseUpgradeEvent extends Event {
163
+ #private;
164
+ readonly oldVersion: number;
165
+ readonly newVersion: number;
166
+ constructor(type: string, init: {
167
+ oldVersion: number;
168
+ newVersion: number;
169
+ });
170
+ /**
171
+ * Extend the event lifetime until the promise settles.
172
+ * Like ExtendableEvent.waitUntil() from ServiceWorker.
173
+ */
174
+ waitUntil(promise: Promise<void>): void;
175
+ /**
176
+ * @internal Wait for all waitUntil promises to settle.
177
+ */
178
+ _settle(): Promise<void>;
179
+ }
180
+ /**
181
+ * Tagged template query function that returns normalized entities.
182
+ */
183
+ export type TaggedQuery<T> = (strings: TemplateStringsArray, ...values: unknown[]) => Promise<T>;
184
+ /**
185
+ * Transaction context with query methods.
186
+ *
187
+ * Provides the same query interface as Database, but bound to a single
188
+ * connection for the duration of the transaction.
189
+ */
190
+ export declare class Transaction {
191
+ #private;
192
+ constructor(driver: Driver);
193
+ all<T extends Queryable<any, any>>(table: T): TaggedQuery<Row<T>[]>;
194
+ all<T extends Queryable<any, any>, Rest extends Queryable<any, any>[]>(tables: [T, ...Rest]): TaggedQuery<JoinedRow<T, [T, ...Rest]>[]>;
195
+ get<T extends Queryable<any, any>>(table: T, id: string | number): Promise<Row<T> | null>;
196
+ get<T extends Queryable<any, any>>(table: T): TaggedQuery<Row<T> | null>;
197
+ get<T extends Queryable<any, any>, Rest extends Queryable<any, any>[]>(tables: [T, ...Rest]): TaggedQuery<JoinedRow<T, [T, ...Rest]> | null>;
198
+ insert<T extends Table<any>>(table: T & FullTableOnly<T>, data: Insert<T>): Promise<Row<T>>;
199
+ insert<T extends Table<any>>(table: T & FullTableOnly<T>, data: Insert<T>[]): Promise<Row<T>[]>;
200
+ update<T extends Table<any>>(table: T & FullTableOnly<T>, data: Partial<Insert<T>>, id: string | number): Promise<Row<T> | null>;
201
+ update<T extends Table<any>>(table: T & FullTableOnly<T>, data: Partial<Insert<T>>, ids: (string | number)[]): Promise<(Row<T> | null)[]>;
202
+ update<T extends Table<any>>(table: T & FullTableOnly<T>, data: Partial<Insert<T>>): TaggedQuery<Row<T>[]>;
203
+ delete<T extends Table<any>>(table: T, id: string | number): Promise<number>;
204
+ delete<T extends Table<any>>(table: T, ids: (string | number)[]): Promise<number>;
205
+ delete<T extends Table<any>>(table: T): TaggedQuery<number>;
206
+ softDelete<T extends Table<any>>(table: T, id: string | number): Promise<number>;
207
+ softDelete<T extends Table<any>>(table: T, ids: (string | number)[]): Promise<number>;
208
+ softDelete<T extends Table<any>>(table: T): TaggedQuery<number>;
209
+ query<T = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
210
+ exec(strings: TemplateStringsArray, ...values: unknown[]): Promise<number>;
211
+ val<T>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T | null>;
212
+ /**
213
+ * Print the generated SQL and parameters without executing.
214
+ * Useful for debugging query composition and fragment expansion.
215
+ * Note: SQL shown uses ? placeholders; actual query may use $1, $2 etc.
216
+ */
217
+ print(strings: TemplateStringsArray, ...values: unknown[]): {
218
+ sql: string;
219
+ params: unknown[];
220
+ };
221
+ }
222
+ /**
223
+ * Database wrapper with typed queries and entity normalization.
224
+ * Extends EventTarget for IndexedDB-style "upgradeneeded" events.
225
+ *
226
+ * @example
227
+ * const db = new Database(driver);
228
+ *
229
+ * db.addEventListener("upgradeneeded", (e) => {
230
+ * e.waitUntil(runMigrations(e));
231
+ * });
232
+ *
233
+ * await db.open(2);
234
+ */
235
+ export declare class Database extends EventTarget {
236
+ #private;
237
+ constructor(driver: Driver, options?: {
238
+ tables?: Table<any>[];
239
+ });
240
+ /**
241
+ * Current database schema version.
242
+ * Returns 0 if database has never been opened.
243
+ */
244
+ get version(): number;
245
+ /**
246
+ * Open the database at a specific version.
247
+ *
248
+ * If the requested version is higher than the current version,
249
+ * fires an "upgradeneeded" event and waits for all waitUntil()
250
+ * promises before completing.
251
+ *
252
+ * Migration safety: Uses exclusive locking to prevent race conditions
253
+ * when multiple processes attempt migrations simultaneously.
254
+ *
255
+ * @example
256
+ * db.addEventListener("upgradeneeded", (e) => {
257
+ * e.waitUntil(runMigrations(e));
258
+ * });
259
+ * await db.open(2);
260
+ */
261
+ open(version: number): Promise<void>;
262
+ /**
263
+ * Query multiple entities with joins and reference resolution.
264
+ *
265
+ * @example
266
+ * // Single table
267
+ * const posts = await db.all(Posts)`WHERE published = ${true}`;
268
+ *
269
+ * // Multi-table with joins (typed!)
270
+ * const posts = await db.all([Posts, Users])`
271
+ * JOIN users ON users.id = posts.author_id
272
+ * WHERE published = ${true}
273
+ * `;
274
+ * posts[0].author.name // typed as string!
275
+ */
276
+ all<T extends Queryable<any, any>>(table: T): TaggedQuery<Row<T>[]>;
277
+ all<T extends Queryable<any, any>, Rest extends Queryable<any, any>[]>(tables: [T, ...Rest]): TaggedQuery<JoinedRow<T, [T, ...Rest]>[]>;
278
+ /**
279
+ * Query a single entity.
280
+ *
281
+ * @example
282
+ * // By primary key
283
+ * const post = await db.get(Posts, postId);
284
+ *
285
+ * // With query
286
+ * const post = await db.get(Posts)`WHERE slug = ${slug}`;
287
+ *
288
+ * // Multi-table (typed!)
289
+ * const post = await db.get([Posts, Users])`
290
+ * JOIN users ON users.id = posts.author_id
291
+ * WHERE posts.id = ${postId}
292
+ * `;
293
+ * post?.author.name // typed as string!
294
+ */
295
+ get<T extends Queryable<any, any>>(table: T, id: string | number): Promise<Row<T> | null>;
296
+ get<T extends Queryable<any, any>>(table: T): TaggedQuery<Row<T> | null>;
297
+ get<T extends Queryable<any, any>, Rest extends Queryable<any, any>[]>(tables: [T, ...Rest]): TaggedQuery<JoinedRow<T, [T, ...Rest]> | null>;
298
+ /**
299
+ * Insert one or more entities.
300
+ *
301
+ * Uses RETURNING to get the actual inserted row(s) (with DB defaults).
302
+ *
303
+ * @example
304
+ * // Single insert
305
+ * const user = await db.insert(Users, {
306
+ * id: crypto.randomUUID(),
307
+ * email: "alice@example.com",
308
+ * name: "Alice",
309
+ * });
310
+ *
311
+ * // Bulk insert
312
+ * const users = await db.insert(Users, [
313
+ * { id: "1", email: "alice@example.com", name: "Alice" },
314
+ * { id: "2", email: "bob@example.com", name: "Bob" },
315
+ * ]);
316
+ */
317
+ insert<T extends Table<any>>(table: T & FullTableOnly<T>, data: Insert<T>): Promise<Row<T>>;
318
+ insert<T extends Table<any>>(table: T & FullTableOnly<T>, data: Insert<T>[]): Promise<Row<T>[]>;
319
+ /**
320
+ * Update entities.
321
+ *
322
+ * @example
323
+ * // Update by primary key
324
+ * const user = await db.update(Users, { name: "Bob" }, userId);
325
+ *
326
+ * // Update multiple by primary keys
327
+ * const users = await db.update(Users, { active: true }, [id1, id2, id3]);
328
+ *
329
+ * // Update with custom WHERE clause
330
+ * const count = await db.update(Users, { active: false })`WHERE lastLogin < ${cutoff}`;
331
+ */
332
+ update<T extends Table<any>>(table: T & FullTableOnly<T>, data: Partial<Insert<T>>, id: string | number): Promise<Row<T> | null>;
333
+ update<T extends Table<any>>(table: T & FullTableOnly<T>, data: Partial<Insert<T>>, ids: (string | number)[]): Promise<(Row<T> | null)[]>;
334
+ update<T extends Table<any>>(table: T & FullTableOnly<T>, data: Partial<Insert<T>>): TaggedQuery<Row<T>[]>;
335
+ /**
336
+ * Delete entities.
337
+ *
338
+ * @example
339
+ * // Delete by primary key (returns 0 or 1)
340
+ * const count = await db.delete(Users, userId);
341
+ *
342
+ * // Delete multiple by primary keys
343
+ * const count = await db.delete(Users, [id1, id2, id3]);
344
+ *
345
+ * // Delete with custom WHERE clause
346
+ * const count = await db.delete(Users)`WHERE inactive = ${true}`;
347
+ */
348
+ delete<T extends Table<any>>(table: T, id: string | number): Promise<number>;
349
+ delete<T extends Table<any>>(table: T, ids: (string | number)[]): Promise<number>;
350
+ delete<T extends Table<any>>(table: T): TaggedQuery<number>;
351
+ /**
352
+ * Soft delete entities by marking the soft delete field with the current timestamp.
353
+ *
354
+ * @example
355
+ * // Soft delete by primary key (returns 0 or 1)
356
+ * const count = await db.softDelete(Users, userId);
357
+ *
358
+ * // Soft delete multiple by primary keys
359
+ * const count = await db.softDelete(Users, [id1, id2, id3]);
360
+ *
361
+ * // Soft delete with custom WHERE clause
362
+ * const count = await db.softDelete(Users)`WHERE inactive = ${true}`;
363
+ */
364
+ softDelete<T extends Table<any>>(table: T, id: string | number): Promise<number>;
365
+ softDelete<T extends Table<any>>(table: T, ids: (string | number)[]): Promise<number>;
366
+ softDelete<T extends Table<any>>(table: T): TaggedQuery<number>;
367
+ /**
368
+ * Execute a raw query and return rows.
369
+ *
370
+ * @example
371
+ * const counts = await db.query<{ count: number }>`
372
+ * SELECT COUNT(*) as count FROM posts WHERE author_id = ${userId}
373
+ * `;
374
+ */
375
+ query<T = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
376
+ /**
377
+ * Execute a statement (INSERT, UPDATE, DELETE, DDL).
378
+ *
379
+ * @example
380
+ * await db.exec`CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY)`;
381
+ */
382
+ exec(strings: TemplateStringsArray, ...values: unknown[]): Promise<number>;
383
+ /**
384
+ * Execute a query and return a single value.
385
+ *
386
+ * @example
387
+ * const count = await db.val<number>`SELECT COUNT(*) FROM posts`;
388
+ */
389
+ val<T>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T | null>;
390
+ /**
391
+ * Print the generated SQL and parameters without executing.
392
+ * Useful for debugging query composition and fragment expansion.
393
+ * Note: SQL shown uses ? placeholders; actual query may use $1, $2 etc.
394
+ */
395
+ print(strings: TemplateStringsArray, ...values: unknown[]): {
396
+ sql: string;
397
+ params: unknown[];
398
+ };
399
+ /**
400
+ * Ensure a table exists with its columns and indexes.
401
+ *
402
+ * **For new tables**: Creates the table with full structure including
403
+ * primary key, unique constraints, foreign keys, and indexes.
404
+ *
405
+ * **For existing tables**: Only performs safe, additive operations:
406
+ * - Adds missing columns
407
+ * - Adds missing non-unique indexes
408
+ *
409
+ * Unique constraints and foreign keys on existing tables require
410
+ * explicit `ensureConstraints()` call (they can fail or lock).
411
+ *
412
+ * @throws {EnsureError} If DDL execution fails
413
+ * @throws {SchemaDriftError} If existing table has missing constraints
414
+ * (directs user to run ensureConstraints)
415
+ *
416
+ * @example
417
+ * // In migration handler
418
+ * await db.ensureTable(Users);
419
+ * await db.ensureTable(Posts); // FK to Users - ensure Users first
420
+ */
421
+ ensureTable<T extends Table<any>>(table: T): Promise<EnsureResult>;
422
+ /**
423
+ * Ensure a view exists in the database.
424
+ *
425
+ * Creates the view if it doesn't exist, or replaces it if it does.
426
+ * The base table must already exist.
427
+ *
428
+ * @example
429
+ * const ActiveUsers = view("active_users", Users)`WHERE ${Users.cols.deletedAt} IS NULL`;
430
+ * await db.ensureTable(Users);
431
+ * await db.ensureView(ActiveUsers);
432
+ */
433
+ ensureView<T extends View<any>>(viewObj: T): Promise<EnsureResult>;
434
+ /**
435
+ * Ensure constraints (unique, foreign key) are applied to an existing table.
436
+ *
437
+ * **WARNING**: This operation can be expensive and cause locks on large tables.
438
+ * It performs preflight checks to detect data violations before applying constraints.
439
+ *
440
+ * For each declared constraint:
441
+ * 1. Preflight: Check for violations (duplicates for UNIQUE, orphans for FK)
442
+ * 2. If violations found: Throw ConstraintPreflightError with diagnostic query
443
+ * 3. If clean: Apply the constraint
444
+ *
445
+ * @throws {Error} If table doesn't exist
446
+ * @throws {ConstraintPreflightError} If data violates a constraint
447
+ * @throws {EnsureError} If DDL execution fails
448
+ *
449
+ * @example
450
+ * // After ensuring table structure
451
+ * await db.ensureTable(Users);
452
+ * // Explicitly apply constraints (may lock, may fail)
453
+ * await db.ensureConstraints(Users);
454
+ */
455
+ ensureConstraints<T extends Table<any>>(table: T): Promise<EnsureResult>;
456
+ /**
457
+ * Copy column data for safe rename migrations.
458
+ *
459
+ * Executes: UPDATE <table> SET <toField> = <fromField> WHERE <toField> IS NULL
460
+ *
461
+ * This is idempotent - rows where toField already has a value are skipped.
462
+ * The fromField may be a legacy column not in the current schema.
463
+ *
464
+ * @param table The table to update
465
+ * @param fromField Source column (may be legacy/not in schema)
466
+ * @param toField Destination column (must exist in schema)
467
+ * @returns Number of rows updated
468
+ *
469
+ * @example
470
+ * // Rename "email" to "emailAddress":
471
+ * // 1. Add new column
472
+ * await db.ensureTable(UsersWithEmailAddress);
473
+ * // 2. Copy data
474
+ * const updated = await db.copyColumn(Users, "email", "emailAddress");
475
+ * // 3. Later: remove old column (manual migration)
476
+ */
477
+ copyColumn<T extends Table<any>>(table: T, fromField: string, toField: string): Promise<number>;
478
+ /**
479
+ * Execute a function within a database transaction.
480
+ *
481
+ * If the function completes successfully, the transaction is committed.
482
+ * If the function throws an error, the transaction is rolled back.
483
+ *
484
+ * All operations within the transaction callback use the same database
485
+ * connection, ensuring transactional consistency.
486
+ *
487
+ * @example
488
+ * await db.transaction(async (tx) => {
489
+ * const user = await tx.insert(users, { id: "1", name: "Alice" });
490
+ * await tx.insert(posts, { id: "1", authorId: user.id, title: "Hello" });
491
+ * // If any insert fails, both are rolled back
492
+ * });
493
+ */
494
+ transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T>;
495
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * DDL generation from table definitions.
3
+ *
4
+ * Generates CREATE TABLE statements for SQLite, PostgreSQL, and MySQL.
5
+ * Uses only Zod's public APIs - no _def access.
6
+ */
7
+ import { z } from "zod";
8
+ import type { Table, View } from "./table.js";
9
+ import { type SQLTemplate } from "./template.js";
10
+ export type SQLDialect = "sqlite" | "postgresql" | "mysql";
11
+ export interface DDLOptions {
12
+ dialect?: SQLDialect;
13
+ ifNotExists?: boolean;
14
+ }
15
+ /**
16
+ * Generate a single column definition as a template with ident markers.
17
+ * @internal Used by driver ensureTable implementations.
18
+ */
19
+ export declare function generateColumnDDL(fieldName: string, zodType: z.ZodType, fieldMeta: Record<string, any>, dialect?: SQLDialect): SQLTemplate;
20
+ /**
21
+ * Generate CREATE TABLE DDL as a template with ident markers.
22
+ * Pass to driver.run() to execute, or use renderDDL() in tests.
23
+ */
24
+ export declare function generateDDL<T extends Table<any>>(table: T, options?: DDLOptions): SQLTemplate;
25
+ /**
26
+ * Generate CREATE VIEW DDL as a template.
27
+ *
28
+ * For views, the DDL is:
29
+ * DROP VIEW IF EXISTS "view_name";
30
+ * CREATE VIEW "view_name" AS SELECT * FROM "base_table" WHERE ...;
31
+ *
32
+ * Note: We use DROP + CREATE because SQLite doesn't support CREATE OR REPLACE VIEW.
33
+ */
34
+ export declare function generateViewDDL<T extends View<any>>(viewObj: T, _options?: DDLOptions): SQLTemplate;