@b9g/zen 0.1.0

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/src/zen.js ADDED
@@ -0,0 +1,2143 @@
1
+ /// <reference types="./zen.d.ts" />
2
+ import "../chunk-2IEEEMRN.js";
3
+ import {
4
+ AlreadyExistsError,
5
+ CURRENT_DATE,
6
+ CURRENT_TIME,
7
+ CURRENT_TIMESTAMP,
8
+ ConnectionError,
9
+ ConstraintPreflightError,
10
+ ConstraintViolationError,
11
+ DatabaseError,
12
+ EnsureError,
13
+ MigrationError,
14
+ MigrationLockError,
15
+ NOW,
16
+ NotFoundError,
17
+ QueryError,
18
+ SchemaDriftError,
19
+ TODAY,
20
+ TableDefinitionError,
21
+ TransactionError,
22
+ ValidationError,
23
+ decodeData,
24
+ extendZod,
25
+ hasErrorCode,
26
+ ident,
27
+ isDatabaseError,
28
+ isSQLBuiltin,
29
+ isSQLIdentifier,
30
+ isSQLTemplate,
31
+ makeTemplate,
32
+ resolveSQLBuiltin,
33
+ table,
34
+ validateWithStandardSchema
35
+ } from "../chunk-56M5Z3A6.js";
36
+
37
+ // src/zen.ts
38
+ import { z as zod } from "zod";
39
+
40
+ // src/impl/database.ts
41
+ import { z } from "zod";
42
+
43
+ // src/impl/query.ts
44
+ function extractEntityData(row, tableName) {
45
+ const prefix = `${tableName}.`;
46
+ const entity = {};
47
+ let hasData = false;
48
+ for (const [key, value] of Object.entries(row)) {
49
+ if (key.startsWith(prefix)) {
50
+ const fieldName = key.slice(prefix.length);
51
+ entity[fieldName] = value;
52
+ if (value !== null && value !== void 0) {
53
+ hasData = true;
54
+ }
55
+ }
56
+ }
57
+ return hasData ? entity : null;
58
+ }
59
+ function getPrimaryKeyValue(entity, table2) {
60
+ const pk = table2.meta.primary;
61
+ if (pk === null) {
62
+ return null;
63
+ }
64
+ const value = entity[pk];
65
+ return value !== null && value !== void 0 ? String(value) : null;
66
+ }
67
+ function entityKey(tableName, primaryKey) {
68
+ return `${tableName}:${primaryKey}`;
69
+ }
70
+ function buildEntityMap(rows, tables) {
71
+ const entities = /* @__PURE__ */ new Map();
72
+ for (const row of rows) {
73
+ for (const table2 of tables) {
74
+ const data = extractEntityData(row, table2.name);
75
+ if (!data)
76
+ continue;
77
+ const pk = getPrimaryKeyValue(data, table2);
78
+ if (!pk)
79
+ continue;
80
+ const key = entityKey(table2.name, pk);
81
+ if (!entities.has(key)) {
82
+ const decoded = decodeData(table2, data);
83
+ const parsed = validateWithStandardSchema(
84
+ table2.schema,
85
+ decoded
86
+ );
87
+ entities.set(key, parsed);
88
+ }
89
+ }
90
+ }
91
+ return entities;
92
+ }
93
+ function resolveReferences(entities, tables) {
94
+ for (const table2 of tables) {
95
+ const refs = table2.references();
96
+ if (refs.length === 0)
97
+ continue;
98
+ const prefix = `${table2.name}:`;
99
+ for (const [key, entity] of entities) {
100
+ if (!key.startsWith(prefix))
101
+ continue;
102
+ for (const ref of refs) {
103
+ const foreignKeyValue = entity[ref.fieldName];
104
+ const refEntity = foreignKeyValue === null || foreignKeyValue === void 0 ? null : entities.get(
105
+ entityKey(ref.table.name, String(foreignKeyValue))
106
+ ) ?? null;
107
+ Object.defineProperty(entity, ref.as, {
108
+ value: refEntity,
109
+ enumerable: true,
110
+ writable: false,
111
+ configurable: true
112
+ });
113
+ }
114
+ }
115
+ }
116
+ const reverseIndex = /* @__PURE__ */ new Map();
117
+ for (const table2 of tables) {
118
+ const refs = table2.references();
119
+ if (refs.length === 0)
120
+ continue;
121
+ for (const ref of refs) {
122
+ if (!ref.reverseAs)
123
+ continue;
124
+ const indexKey = `${table2.name}:${ref.fieldName}`;
125
+ const fkIndex = /* @__PURE__ */ new Map();
126
+ const prefix = `${table2.name}:`;
127
+ for (const [key, entity] of entities) {
128
+ if (!key.startsWith(prefix))
129
+ continue;
130
+ const fkValue = entity[ref.fieldName];
131
+ if (fkValue === null || fkValue === void 0)
132
+ continue;
133
+ const fkStr = String(fkValue);
134
+ if (!fkIndex.has(fkStr)) {
135
+ fkIndex.set(fkStr, []);
136
+ }
137
+ fkIndex.get(fkStr).push(entity);
138
+ }
139
+ reverseIndex.set(indexKey, fkIndex);
140
+ }
141
+ }
142
+ for (const table2 of tables) {
143
+ const refs = table2.references();
144
+ if (refs.length === 0)
145
+ continue;
146
+ for (const ref of refs) {
147
+ if (!ref.reverseAs)
148
+ continue;
149
+ const indexKey = `${table2.name}:${ref.fieldName}`;
150
+ const fkIndex = reverseIndex.get(indexKey);
151
+ if (!fkIndex)
152
+ continue;
153
+ const targetPrefix = `${ref.table.name}:`;
154
+ for (const [key, entity] of entities) {
155
+ if (!key.startsWith(targetPrefix))
156
+ continue;
157
+ const pk = entity[ref.table.meta.primary];
158
+ if (pk === null || pk === void 0)
159
+ continue;
160
+ const pkStr = String(pk);
161
+ Object.defineProperty(entity, ref.reverseAs, {
162
+ value: fkIndex.get(pkStr) ?? [],
163
+ enumerable: false,
164
+ writable: false,
165
+ configurable: true
166
+ });
167
+ }
168
+ }
169
+ }
170
+ }
171
+ function applyDerivedProperties(entities, tables) {
172
+ for (const table2 of tables) {
173
+ const derive = table2.meta.derive;
174
+ if (!derive)
175
+ continue;
176
+ const prefix = `${table2.name}:`;
177
+ for (const [key, entity] of entities) {
178
+ if (!key.startsWith(prefix))
179
+ continue;
180
+ for (const [propName, deriveFn] of Object.entries(derive)) {
181
+ Object.defineProperty(entity, propName, {
182
+ get() {
183
+ return deriveFn(this);
184
+ },
185
+ enumerable: false,
186
+ configurable: true
187
+ });
188
+ }
189
+ }
190
+ }
191
+ }
192
+ function extractMainEntities(rows, mainTable, entities) {
193
+ const results = [];
194
+ const seen = /* @__PURE__ */ new Set();
195
+ for (const row of rows) {
196
+ const data = extractEntityData(row, mainTable.name);
197
+ if (!data)
198
+ continue;
199
+ const pk = getPrimaryKeyValue(data, mainTable);
200
+ if (!pk)
201
+ continue;
202
+ const key = entityKey(mainTable.name, pk);
203
+ if (seen.has(key))
204
+ continue;
205
+ seen.add(key);
206
+ const entity = entities.get(key);
207
+ if (entity) {
208
+ results.push(entity);
209
+ }
210
+ }
211
+ return results;
212
+ }
213
+ function validateRegisteredTables(rows, tables) {
214
+ if (rows.length === 0)
215
+ return;
216
+ const registeredPrefixes = new Set(tables.map((t) => `${t.name}.`));
217
+ const unregisteredTables = [];
218
+ const firstRow = rows[0];
219
+ for (const key of Object.keys(firstRow)) {
220
+ const dotIndex = key.indexOf(".");
221
+ if (dotIndex === -1)
222
+ continue;
223
+ const prefix = key.slice(0, dotIndex + 1);
224
+ if (!registeredPrefixes.has(prefix)) {
225
+ const tableName = key.slice(0, dotIndex);
226
+ if (!unregisteredTables.includes(tableName)) {
227
+ unregisteredTables.push(tableName);
228
+ }
229
+ }
230
+ }
231
+ if (unregisteredTables.length > 0) {
232
+ const tableList = unregisteredTables.map((t) => `"${t}"`).join(", ");
233
+ throw new Error(
234
+ `Query results contain columns for table(s) ${tableList} not passed to all()/one(). Add them to the tables array, or use query() for raw results.`
235
+ );
236
+ }
237
+ }
238
+ function normalize(rows, tables) {
239
+ if (tables.length === 0) {
240
+ throw new Error("At least one table is required");
241
+ }
242
+ if (rows.length === 0) {
243
+ return [];
244
+ }
245
+ validateRegisteredTables(rows, tables);
246
+ const entities = buildEntityMap(rows, tables);
247
+ resolveReferences(entities, tables);
248
+ applyDerivedProperties(entities, tables);
249
+ const mainTable = tables[0];
250
+ return extractMainEntities(rows, mainTable, entities);
251
+ }
252
+ function normalizeOne(row, tables) {
253
+ if (!row)
254
+ return null;
255
+ const results = normalize([row], tables);
256
+ return results[0] ?? null;
257
+ }
258
+
259
+ // src/impl/database.ts
260
+ var DB_EXPR = Symbol.for("@b9g/zen:db-expr");
261
+ function isDBExpression(value) {
262
+ return value !== null && typeof value === "object" && DB_EXPR in value && value[DB_EXPR] === true;
263
+ }
264
+ function createDBExpr(strings, values = []) {
265
+ return { [DB_EXPR]: true, strings, values };
266
+ }
267
+ function extractDBExpressions(data, table2) {
268
+ const regularData = {};
269
+ const expressions = {};
270
+ const symbols = {};
271
+ for (const [key, value] of Object.entries(data)) {
272
+ if (isDBExpression(value)) {
273
+ if (table2) {
274
+ const fieldMeta = table2.meta.fields[key];
275
+ if (fieldMeta?.encode || fieldMeta?.decode) {
276
+ throw new Error(
277
+ `Cannot use DB expression for field "${key}" which has encode/decode. DB expressions bypass encoding and are sent directly to the database.`
278
+ );
279
+ }
280
+ }
281
+ expressions[key] = value;
282
+ } else if (isSQLBuiltin(value)) {
283
+ if (table2) {
284
+ const fieldMeta = table2.meta.fields[key];
285
+ if (fieldMeta?.encode || fieldMeta?.decode) {
286
+ throw new Error(
287
+ `Cannot use SQL symbol for field "${key}" which has encode/decode. SQL symbols bypass encoding and are sent directly to the database.`
288
+ );
289
+ }
290
+ }
291
+ symbols[key] = value;
292
+ } else {
293
+ regularData[key] = value;
294
+ }
295
+ }
296
+ return { regularData, expressions, symbols };
297
+ }
298
+ function injectSchemaExpressions(table2, data, operation) {
299
+ const result = { ...data };
300
+ for (const [fieldName, fieldMeta] of Object.entries(table2.meta.fields)) {
301
+ if (fieldName in data)
302
+ continue;
303
+ if (fieldMeta.autoIncrement)
304
+ continue;
305
+ const meta = operation === "insert" ? fieldMeta.inserted ?? fieldMeta.upserted : fieldMeta.updated ?? fieldMeta.upserted;
306
+ if (!meta)
307
+ continue;
308
+ if (meta.type === "sql") {
309
+ result[fieldName] = createDBExpr(
310
+ meta.template[0],
311
+ meta.template.slice(1)
312
+ );
313
+ } else if (meta.type === "symbol") {
314
+ result[fieldName] = meta.symbol;
315
+ } else if (meta.type === "function") {
316
+ result[fieldName] = meta.fn();
317
+ }
318
+ }
319
+ return result;
320
+ }
321
+ function encodeData(table2, data) {
322
+ const encoded = {};
323
+ const shape = table2.schema.shape;
324
+ for (const [key, value] of Object.entries(data)) {
325
+ const fieldMeta = table2.meta.fields[key];
326
+ const fieldSchema = shape?.[key];
327
+ if (fieldMeta?.encode && typeof fieldMeta.encode === "function") {
328
+ encoded[key] = fieldMeta.encode(value);
329
+ } else if (fieldSchema) {
330
+ let core = fieldSchema;
331
+ while (typeof core.unwrap === "function") {
332
+ if (core instanceof z.ZodArray || core instanceof z.ZodObject) {
333
+ break;
334
+ }
335
+ core = core.unwrap();
336
+ }
337
+ if ((core instanceof z.ZodObject || core instanceof z.ZodArray) && value !== null && value !== void 0) {
338
+ encoded[key] = JSON.stringify(value);
339
+ } else {
340
+ encoded[key] = value;
341
+ }
342
+ } else {
343
+ encoded[key] = value;
344
+ }
345
+ }
346
+ return encoded;
347
+ }
348
+ function mergeExpression(baseStrings, baseValues, expr) {
349
+ baseStrings[baseStrings.length - 1] += expr.strings[0];
350
+ for (let i = 1; i < expr.strings.length; i++) {
351
+ baseStrings.push(expr.strings[i]);
352
+ }
353
+ baseValues.push(...expr.values);
354
+ }
355
+ function buildInsertParts(tableName, data, expressions, symbols = {}) {
356
+ const regularCols = Object.keys(data);
357
+ const exprCols = Object.keys(expressions);
358
+ const symbolCols = Object.keys(symbols);
359
+ const allCols = [...regularCols, ...symbolCols, ...exprCols];
360
+ if (allCols.length === 0) {
361
+ throw new Error("Insert requires at least one column");
362
+ }
363
+ const strings = ["INSERT INTO "];
364
+ const values = [];
365
+ values.push(ident(tableName));
366
+ strings.push(" (");
367
+ for (let i = 0; i < allCols.length; i++) {
368
+ values.push(ident(allCols[i]));
369
+ strings.push(i < allCols.length - 1 ? ", " : ") VALUES (");
370
+ }
371
+ const valueCols = [...regularCols, ...symbolCols];
372
+ for (let i = 0; i < valueCols.length; i++) {
373
+ const col = valueCols[i];
374
+ values.push(col in data ? data[col] : symbols[col]);
375
+ strings.push(i < valueCols.length - 1 ? ", " : "");
376
+ }
377
+ for (let i = 0; i < exprCols.length; i++) {
378
+ if (valueCols.length > 0 || i > 0) {
379
+ strings[strings.length - 1] += ", ";
380
+ }
381
+ mergeExpression(strings, values, expressions[exprCols[i]]);
382
+ }
383
+ strings[strings.length - 1] += ")";
384
+ return {
385
+ strings: makeTemplate(strings),
386
+ values
387
+ };
388
+ }
389
+ function buildUpdateByIdParts(tableName, pk, data, expressions, id, symbols = {}) {
390
+ const regularCols = Object.keys(data);
391
+ const exprCols = Object.keys(expressions);
392
+ const symbolCols = Object.keys(symbols);
393
+ const valueCols = [...regularCols, ...symbolCols];
394
+ const strings = ["UPDATE "];
395
+ const values = [];
396
+ values.push(ident(tableName));
397
+ strings.push(" SET ");
398
+ for (let i = 0; i < valueCols.length; i++) {
399
+ const col = valueCols[i];
400
+ values.push(ident(col));
401
+ strings.push(" = ");
402
+ values.push(col in data ? data[col] : symbols[col]);
403
+ strings.push(i < valueCols.length - 1 ? ", " : "");
404
+ }
405
+ for (let i = 0; i < exprCols.length; i++) {
406
+ if (valueCols.length > 0 || i > 0) {
407
+ strings[strings.length - 1] += ", ";
408
+ }
409
+ values.push(ident(exprCols[i]));
410
+ strings.push(" = ");
411
+ mergeExpression(strings, values, expressions[exprCols[i]]);
412
+ }
413
+ strings[strings.length - 1] += " WHERE ";
414
+ values.push(ident(pk));
415
+ strings.push(" = ");
416
+ values.push(id);
417
+ strings.push("");
418
+ return {
419
+ strings: makeTemplate(strings),
420
+ values
421
+ };
422
+ }
423
+ function buildUpdateByIdsParts(tableName, pk, data, expressions, ids, symbols = {}) {
424
+ const regularCols = Object.keys(data);
425
+ const exprCols = Object.keys(expressions);
426
+ const symbolCols = Object.keys(symbols);
427
+ const valueCols = [...regularCols, ...symbolCols];
428
+ const strings = ["UPDATE "];
429
+ const values = [];
430
+ values.push(ident(tableName));
431
+ strings.push(" SET ");
432
+ for (let i = 0; i < valueCols.length; i++) {
433
+ const col = valueCols[i];
434
+ values.push(ident(col));
435
+ strings.push(" = ");
436
+ values.push(col in data ? data[col] : symbols[col]);
437
+ strings.push(i < valueCols.length - 1 ? ", " : "");
438
+ }
439
+ for (let i = 0; i < exprCols.length; i++) {
440
+ if (valueCols.length > 0 || i > 0) {
441
+ strings[strings.length - 1] += ", ";
442
+ }
443
+ values.push(ident(exprCols[i]));
444
+ strings.push(" = ");
445
+ mergeExpression(strings, values, expressions[exprCols[i]]);
446
+ }
447
+ strings[strings.length - 1] += " WHERE ";
448
+ values.push(ident(pk));
449
+ strings.push(" IN (");
450
+ for (let i = 0; i < ids.length; i++) {
451
+ values.push(ids[i]);
452
+ strings.push(i < ids.length - 1 ? ", " : ")");
453
+ }
454
+ return {
455
+ strings: makeTemplate(strings),
456
+ values
457
+ };
458
+ }
459
+ function buildSelectCols(tables) {
460
+ const strings = [""];
461
+ const values = [];
462
+ let needsComma = false;
463
+ for (const table2 of tables) {
464
+ const tableName = table2.name;
465
+ const shape = table2.schema.shape;
466
+ const derivedFields = new Set(
467
+ table2.meta.derivedFields ?? []
468
+ );
469
+ for (const fieldName of Object.keys(shape)) {
470
+ if (derivedFields.has(fieldName))
471
+ continue;
472
+ if (needsComma) {
473
+ strings[strings.length - 1] += ", ";
474
+ }
475
+ values.push(ident(tableName));
476
+ strings.push(".");
477
+ values.push(ident(fieldName));
478
+ strings.push(" AS ");
479
+ values.push(ident(`${tableName}.${fieldName}`));
480
+ strings.push("");
481
+ needsComma = true;
482
+ }
483
+ const derivedExprs = table2.meta.derivedExprs ?? [];
484
+ for (const expr of derivedExprs) {
485
+ if (needsComma) {
486
+ strings[strings.length - 1] += ", ";
487
+ }
488
+ const alias = `${tableName}.${expr.fieldName}`;
489
+ strings[strings.length - 1] += "(";
490
+ const exprStrings = expr.template[0];
491
+ const exprValues = expr.template.slice(1);
492
+ strings[strings.length - 1] += exprStrings[0];
493
+ for (let i = 1; i < exprStrings.length; i++) {
494
+ strings.push(exprStrings[i]);
495
+ }
496
+ values.push(...exprValues);
497
+ strings[strings.length - 1] += ") AS ";
498
+ values.push(ident(alias));
499
+ strings.push("");
500
+ needsComma = true;
501
+ }
502
+ }
503
+ return { strings, values };
504
+ }
505
+ function buildSelectByPkParts(tableName, pk, id) {
506
+ return {
507
+ strings: makeTemplate(["SELECT * FROM ", " WHERE ", " = ", ""]),
508
+ values: [ident(tableName), ident(pk), id]
509
+ };
510
+ }
511
+ function buildSelectByPksParts(tableName, pk, ids) {
512
+ const strings = ["SELECT * FROM ", " WHERE ", " IN ("];
513
+ const values = [ident(tableName), ident(pk)];
514
+ for (let i = 0; i < ids.length; i++) {
515
+ values.push(ids[i]);
516
+ strings.push(i < ids.length - 1 ? ", " : ")");
517
+ }
518
+ return {
519
+ strings: makeTemplate(strings),
520
+ values
521
+ };
522
+ }
523
+ function buildDeleteByPkParts(tableName, pk, id) {
524
+ return {
525
+ strings: makeTemplate(["DELETE FROM ", " WHERE ", " = ", ""]),
526
+ values: [ident(tableName), ident(pk), id]
527
+ };
528
+ }
529
+ function buildDeleteByPksParts(tableName, pk, ids) {
530
+ const strings = ["DELETE FROM ", " WHERE ", " IN ("];
531
+ const values = [ident(tableName), ident(pk)];
532
+ for (let i = 0; i < ids.length; i++) {
533
+ values.push(ids[i]);
534
+ strings.push(i < ids.length - 1 ? ", " : ")");
535
+ }
536
+ return {
537
+ strings: makeTemplate(strings),
538
+ values
539
+ };
540
+ }
541
+ function appendReturning(parts) {
542
+ const strings = [...parts.strings];
543
+ strings[strings.length - 1] += " RETURNING *";
544
+ return {
545
+ strings: makeTemplate(strings),
546
+ values: parts.values
547
+ };
548
+ }
549
+ function expandFragments(strings, values) {
550
+ const newStrings = [strings[0]];
551
+ const newValues = [];
552
+ for (let i = 0; i < values.length; i++) {
553
+ const value = values[i];
554
+ if (isSQLTemplate(value)) {
555
+ const valueStrings = value[0];
556
+ const valueValues = value.slice(1);
557
+ newStrings[newStrings.length - 1] += valueStrings[0];
558
+ for (let j = 1; j < valueStrings.length; j++) {
559
+ newStrings.push(valueStrings[j]);
560
+ }
561
+ newValues.push(...valueValues);
562
+ newStrings[newStrings.length - 1] += strings[i + 1];
563
+ } else {
564
+ newStrings.push(strings[i + 1]);
565
+ newValues.push(value);
566
+ }
567
+ }
568
+ return {
569
+ strings: makeTemplate(newStrings),
570
+ values: newValues
571
+ };
572
+ }
573
+ var DatabaseUpgradeEvent = class extends Event {
574
+ oldVersion;
575
+ newVersion;
576
+ #promises = [];
577
+ constructor(type, init) {
578
+ super(type);
579
+ this.oldVersion = init.oldVersion;
580
+ this.newVersion = init.newVersion;
581
+ }
582
+ /**
583
+ * Extend the event lifetime until the promise settles.
584
+ * Like ExtendableEvent.waitUntil() from ServiceWorker.
585
+ */
586
+ waitUntil(promise) {
587
+ this.#promises.push(promise);
588
+ }
589
+ /**
590
+ * @internal Wait for all waitUntil promises to settle.
591
+ */
592
+ async _settle() {
593
+ await Promise.all(this.#promises);
594
+ }
595
+ };
596
+ var Transaction = class {
597
+ #driver;
598
+ constructor(driver) {
599
+ this.#driver = driver;
600
+ }
601
+ all(tables) {
602
+ const tableArray = Array.isArray(tables) ? tables : [tables];
603
+ return async (strings, ...values) => {
604
+ const { strings: colStrings, values: colValues } = buildSelectCols(tableArray);
605
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
606
+ const queryStrings = ["SELECT "];
607
+ const queryValues = [];
608
+ queryStrings[0] += colStrings[0];
609
+ for (let i = 1; i < colStrings.length; i++) {
610
+ queryStrings.push(colStrings[i]);
611
+ }
612
+ queryValues.push(...colValues);
613
+ queryStrings[queryStrings.length - 1] += " FROM ";
614
+ queryValues.push(ident(tableArray[0].name));
615
+ queryStrings.push(" ");
616
+ queryStrings[queryStrings.length - 1] += expandedStrings[0];
617
+ for (let i = 1; i < expandedStrings.length; i++) {
618
+ queryStrings.push(expandedStrings[i]);
619
+ }
620
+ queryValues.push(...expandedValues);
621
+ const rows = await this.#driver.all(
622
+ makeTemplate(queryStrings),
623
+ queryValues
624
+ );
625
+ return normalize(rows, tableArray);
626
+ };
627
+ }
628
+ get(tables, id) {
629
+ if (id !== void 0) {
630
+ const table2 = tables;
631
+ const pk = table2.meta.primary;
632
+ if (!pk) {
633
+ return Promise.reject(
634
+ new Error(`Table ${table2.name} has no primary key defined`)
635
+ );
636
+ }
637
+ const { strings, values } = buildSelectByPkParts(table2.name, pk, id);
638
+ return this.#driver.get(strings, values).then((row) => {
639
+ if (!row)
640
+ return null;
641
+ return decodeData(table2, row);
642
+ });
643
+ }
644
+ const tableArray = Array.isArray(tables) ? tables : [tables];
645
+ return async (strings, ...values) => {
646
+ const { strings: colStrings, values: colValues } = buildSelectCols(tableArray);
647
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
648
+ const queryStrings = ["SELECT "];
649
+ const queryValues = [];
650
+ queryStrings[0] += colStrings[0];
651
+ for (let i = 1; i < colStrings.length; i++) {
652
+ queryStrings.push(colStrings[i]);
653
+ }
654
+ queryValues.push(...colValues);
655
+ queryStrings[queryStrings.length - 1] += " FROM ";
656
+ queryValues.push(ident(tableArray[0].name));
657
+ queryStrings.push(" ");
658
+ queryStrings[queryStrings.length - 1] += expandedStrings[0];
659
+ for (let i = 1; i < expandedStrings.length; i++) {
660
+ queryStrings.push(expandedStrings[i]);
661
+ }
662
+ queryValues.push(...expandedValues);
663
+ const row = await this.#driver.get(
664
+ makeTemplate(queryStrings),
665
+ queryValues
666
+ );
667
+ return normalizeOne(row, tableArray);
668
+ };
669
+ }
670
+ async insert(table2, data) {
671
+ if (Array.isArray(data)) {
672
+ if (data.length === 0) {
673
+ return [];
674
+ }
675
+ const results = [];
676
+ for (const row of data) {
677
+ results.push(await this.#insertOne(table2, row));
678
+ }
679
+ return results;
680
+ }
681
+ return this.#insertOne(table2, data);
682
+ }
683
+ async #insertOne(table2, data) {
684
+ if (table2.meta.isPartial) {
685
+ throw new Error(
686
+ `Cannot insert into partial table "${table2.name}". Use the full table definition instead.`
687
+ );
688
+ }
689
+ if (table2.meta.isDerived) {
690
+ throw new Error(
691
+ `Cannot insert into derived table "${table2.name}". Derived tables are SELECT-only.`
692
+ );
693
+ }
694
+ const dataWithSchemaExprs = injectSchemaExpressions(
695
+ table2,
696
+ data,
697
+ "insert"
698
+ );
699
+ const { regularData, expressions, symbols } = extractDBExpressions(
700
+ dataWithSchemaExprs,
701
+ table2
702
+ );
703
+ let schema = table2.schema;
704
+ const skipFields = { ...expressions, ...symbols };
705
+ if (Object.keys(skipFields).length > 0) {
706
+ const skipFieldSchemas = Object.keys(skipFields).reduce(
707
+ (acc, key) => {
708
+ acc[key] = table2.schema.shape[key].optional();
709
+ return acc;
710
+ },
711
+ {}
712
+ );
713
+ schema = table2.schema.extend(skipFieldSchemas);
714
+ }
715
+ const validated = validateWithStandardSchema(
716
+ schema,
717
+ regularData
718
+ );
719
+ const encoded = encodeData(table2, validated);
720
+ const insertParts = buildInsertParts(
721
+ table2.name,
722
+ encoded,
723
+ expressions,
724
+ symbols
725
+ );
726
+ if (this.#driver.supportsReturning) {
727
+ const { strings, values } = appendReturning(insertParts);
728
+ const row = await this.#driver.get(
729
+ strings,
730
+ values
731
+ );
732
+ return decodeData(table2, row);
733
+ }
734
+ await this.#driver.run(insertParts.strings, insertParts.values);
735
+ const pk = table2.meta.primary;
736
+ const pkValue = pk ? encoded[pk] ?? (expressions[pk] ? void 0 : null) : null;
737
+ if (pk && pkValue !== void 0 && pkValue !== null) {
738
+ const { strings, values } = buildSelectByPkParts(table2.name, pk, pkValue);
739
+ const row = await this.#driver.get(
740
+ strings,
741
+ values
742
+ );
743
+ if (row) {
744
+ return decodeData(table2, row);
745
+ }
746
+ }
747
+ return validated;
748
+ }
749
+ update(table2, data, idOrIds) {
750
+ if (idOrIds === void 0) {
751
+ return async (strings, ...values) => {
752
+ return this.#updateWithWhere(table2, data, strings, values);
753
+ };
754
+ }
755
+ if (Array.isArray(idOrIds)) {
756
+ return this.#updateByIds(table2, data, idOrIds);
757
+ }
758
+ return this.#updateById(table2, data, idOrIds);
759
+ }
760
+ async #updateById(table2, data, id) {
761
+ const pk = table2.meta.primary;
762
+ if (!pk) {
763
+ throw new Error(`Table ${table2.name} has no primary key defined`);
764
+ }
765
+ if (table2.meta.isDerived) {
766
+ throw new Error(
767
+ `Cannot update derived table "${table2.name}". Derived tables are SELECT-only.`
768
+ );
769
+ }
770
+ const dataWithSchemaExprs = injectSchemaExpressions(
771
+ table2,
772
+ data,
773
+ "update"
774
+ );
775
+ const { regularData, expressions, symbols } = extractDBExpressions(
776
+ dataWithSchemaExprs,
777
+ table2
778
+ );
779
+ const partialSchema = table2.schema.partial();
780
+ const validated = validateWithStandardSchema(
781
+ partialSchema,
782
+ regularData
783
+ );
784
+ const allColumns = [
785
+ ...Object.keys(validated),
786
+ ...Object.keys(expressions),
787
+ ...Object.keys(symbols)
788
+ ];
789
+ if (allColumns.length === 0) {
790
+ throw new Error("No fields to update");
791
+ }
792
+ const encoded = encodeData(table2, validated);
793
+ const updateParts = buildUpdateByIdParts(
794
+ table2.name,
795
+ pk,
796
+ encoded,
797
+ expressions,
798
+ id,
799
+ symbols
800
+ );
801
+ if (this.#driver.supportsReturning) {
802
+ const { strings, values } = appendReturning(updateParts);
803
+ const row2 = await this.#driver.get(
804
+ strings,
805
+ values
806
+ );
807
+ if (!row2)
808
+ return null;
809
+ return decodeData(table2, row2);
810
+ }
811
+ await this.#driver.run(updateParts.strings, updateParts.values);
812
+ const { strings: selectStrings, values: selectValues } = buildSelectByPkParts(
813
+ table2.name,
814
+ pk,
815
+ id
816
+ );
817
+ const row = await this.#driver.get(
818
+ selectStrings,
819
+ selectValues
820
+ );
821
+ if (!row)
822
+ return null;
823
+ return decodeData(table2, row);
824
+ }
825
+ async #updateByIds(table2, data, ids) {
826
+ if (ids.length === 0) {
827
+ return [];
828
+ }
829
+ const pk = table2.meta.primary;
830
+ if (!pk) {
831
+ throw new Error(`Table ${table2.name} has no primary key defined`);
832
+ }
833
+ if (table2.meta.isDerived) {
834
+ throw new Error(
835
+ `Cannot update derived table "${table2.name}". Derived tables are SELECT-only.`
836
+ );
837
+ }
838
+ const dataWithSchemaExprs = injectSchemaExpressions(
839
+ table2,
840
+ data,
841
+ "update"
842
+ );
843
+ const { regularData, expressions, symbols } = extractDBExpressions(
844
+ dataWithSchemaExprs,
845
+ table2
846
+ );
847
+ const partialSchema = table2.schema.partial();
848
+ const validated = validateWithStandardSchema(
849
+ partialSchema,
850
+ regularData
851
+ );
852
+ const allColumns = [
853
+ ...Object.keys(validated),
854
+ ...Object.keys(expressions),
855
+ ...Object.keys(symbols)
856
+ ];
857
+ if (allColumns.length === 0) {
858
+ throw new Error("No fields to update");
859
+ }
860
+ const encoded = encodeData(table2, validated);
861
+ const updateParts = buildUpdateByIdsParts(
862
+ table2.name,
863
+ pk,
864
+ encoded,
865
+ expressions,
866
+ ids,
867
+ symbols
868
+ );
869
+ if (this.#driver.supportsReturning) {
870
+ const { strings, values } = appendReturning(updateParts);
871
+ const rows2 = await this.#driver.all(
872
+ strings,
873
+ values
874
+ );
875
+ const resultMap2 = /* @__PURE__ */ new Map();
876
+ for (const row of rows2) {
877
+ const entity = decodeData(table2, row);
878
+ resultMap2.set(row[pk], entity);
879
+ }
880
+ return ids.map((id) => resultMap2.get(id) ?? null);
881
+ }
882
+ await this.#driver.run(updateParts.strings, updateParts.values);
883
+ const { strings: selectStrings, values: selectValues } = buildSelectByPksParts(table2.name, pk, ids);
884
+ const rows = await this.#driver.all(
885
+ selectStrings,
886
+ selectValues
887
+ );
888
+ const resultMap = /* @__PURE__ */ new Map();
889
+ for (const row of rows) {
890
+ const entity = decodeData(table2, row);
891
+ resultMap.set(row[pk], entity);
892
+ }
893
+ return ids.map((id) => resultMap.get(id) ?? null);
894
+ }
895
+ async #updateWithWhere(table2, data, strings, templateValues) {
896
+ if (table2.meta.isDerived) {
897
+ throw new Error(
898
+ `Cannot update derived table "${table2.name}". Derived tables are SELECT-only.`
899
+ );
900
+ }
901
+ const pk = table2.meta.primary;
902
+ if (!pk) {
903
+ throw new Error(`Table ${table2.name} has no primary key defined`);
904
+ }
905
+ const dataWithSchemaExprs = injectSchemaExpressions(
906
+ table2,
907
+ data,
908
+ "update"
909
+ );
910
+ const { regularData, expressions } = extractDBExpressions(
911
+ dataWithSchemaExprs,
912
+ table2
913
+ );
914
+ const partialSchema = table2.schema.partial();
915
+ const validated = validateWithStandardSchema(
916
+ partialSchema,
917
+ regularData
918
+ );
919
+ const allColumns = [...Object.keys(validated), ...Object.keys(expressions)];
920
+ if (allColumns.length === 0) {
921
+ throw new Error("No fields to update");
922
+ }
923
+ const encoded = encodeData(table2, validated);
924
+ const { strings: whereStrings, values: whereValues } = expandFragments(
925
+ strings,
926
+ templateValues
927
+ );
928
+ const setCols = Object.keys(encoded);
929
+ const exprCols = Object.keys(expressions);
930
+ const queryStrings = ["UPDATE "];
931
+ const queryValues = [];
932
+ queryValues.push(ident(table2.name));
933
+ queryStrings.push(" SET ");
934
+ for (let i = 0; i < setCols.length; i++) {
935
+ const col = setCols[i];
936
+ queryValues.push(ident(col));
937
+ queryStrings.push(" = ");
938
+ queryValues.push(encoded[col]);
939
+ queryStrings.push(i < setCols.length - 1 ? ", " : "");
940
+ }
941
+ for (let i = 0; i < exprCols.length; i++) {
942
+ if (setCols.length > 0 || i > 0) {
943
+ queryStrings[queryStrings.length - 1] += ", ";
944
+ }
945
+ queryValues.push(ident(exprCols[i]));
946
+ queryStrings.push(" = ");
947
+ mergeExpression(queryStrings, queryValues, expressions[exprCols[i]]);
948
+ }
949
+ queryStrings[queryStrings.length - 1] += " " + whereStrings[0];
950
+ for (let i = 1; i < whereStrings.length; i++) {
951
+ queryStrings.push(whereStrings[i]);
952
+ }
953
+ queryValues.push(...whereValues);
954
+ if (this.#driver.supportsReturning) {
955
+ queryStrings[queryStrings.length - 1] += " RETURNING *";
956
+ const rows2 = await this.#driver.all(
957
+ makeTemplate(queryStrings),
958
+ queryValues
959
+ );
960
+ return rows2.map((row) => decodeData(table2, row));
961
+ }
962
+ const selectIdStrings = ["SELECT "];
963
+ const selectIdValues = [];
964
+ selectIdValues.push(ident(pk));
965
+ selectIdStrings.push(" FROM ");
966
+ selectIdValues.push(ident(table2.name));
967
+ selectIdStrings.push(" " + whereStrings[0]);
968
+ for (let i = 1; i < whereStrings.length; i++) {
969
+ selectIdStrings.push(whereStrings[i]);
970
+ }
971
+ selectIdValues.push(...whereValues);
972
+ const idRows = await this.#driver.all(
973
+ makeTemplate(selectIdStrings),
974
+ selectIdValues
975
+ );
976
+ const ids = idRows.map((r) => r[pk]);
977
+ if (ids.length === 0) {
978
+ return [];
979
+ }
980
+ await this.#driver.run(makeTemplate(queryStrings), queryValues);
981
+ const { strings: selectStrings, values: selectVals } = buildSelectByPksParts(
982
+ table2.name,
983
+ pk,
984
+ ids
985
+ );
986
+ const rows = await this.#driver.all(
987
+ selectStrings,
988
+ selectVals
989
+ );
990
+ return rows.map((row) => decodeData(table2, row));
991
+ }
992
+ delete(table2, idOrIds) {
993
+ if (idOrIds === void 0) {
994
+ return async (strings, ...values) => {
995
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
996
+ const deleteStrings = ["DELETE FROM "];
997
+ const deleteValues = [ident(table2.name)];
998
+ deleteStrings.push(" " + expandedStrings[0]);
999
+ for (let i = 1; i < expandedStrings.length; i++) {
1000
+ deleteStrings.push(expandedStrings[i]);
1001
+ }
1002
+ deleteValues.push(...expandedValues);
1003
+ return this.#driver.run(makeTemplate(deleteStrings), deleteValues);
1004
+ };
1005
+ }
1006
+ if (Array.isArray(idOrIds)) {
1007
+ return this.#deleteByIds(table2, idOrIds);
1008
+ }
1009
+ return this.#deleteById(table2, idOrIds);
1010
+ }
1011
+ async #deleteById(table2, id) {
1012
+ const pk = table2.meta.primary;
1013
+ if (!pk) {
1014
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1015
+ }
1016
+ const { strings, values } = buildDeleteByPkParts(table2.name, pk, id);
1017
+ return this.#driver.run(strings, values);
1018
+ }
1019
+ async #deleteByIds(table2, ids) {
1020
+ if (ids.length === 0) {
1021
+ return 0;
1022
+ }
1023
+ const pk = table2.meta.primary;
1024
+ if (!pk) {
1025
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1026
+ }
1027
+ const { strings, values } = buildDeleteByPksParts(table2.name, pk, ids);
1028
+ return this.#driver.run(strings, values);
1029
+ }
1030
+ softDelete(table2, idOrIds) {
1031
+ const softDeleteField = table2.meta.softDeleteField;
1032
+ if (!softDeleteField) {
1033
+ throw new Error(
1034
+ `Table ${table2.name} does not have a soft delete field. Use softDelete() wrapper to mark a field.`
1035
+ );
1036
+ }
1037
+ if (idOrIds === void 0) {
1038
+ return async (strings, ...values) => {
1039
+ return this.#softDeleteWithWhere(table2, strings, values);
1040
+ };
1041
+ }
1042
+ if (Array.isArray(idOrIds)) {
1043
+ return this.#softDeleteByIds(table2, idOrIds);
1044
+ }
1045
+ return this.#softDeleteById(table2, idOrIds);
1046
+ }
1047
+ async #softDeleteById(table2, id) {
1048
+ const pk = table2.meta.primary;
1049
+ if (!pk) {
1050
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1051
+ }
1052
+ const softDeleteField = table2.meta.softDeleteField;
1053
+ const schemaExprs = injectSchemaExpressions(table2, {}, "update");
1054
+ const { expressions, symbols } = extractDBExpressions(schemaExprs);
1055
+ const queryStrings = ["UPDATE "];
1056
+ const queryValues = [];
1057
+ queryValues.push(ident(table2.name));
1058
+ queryStrings.push(" SET ");
1059
+ queryValues.push(ident(softDeleteField));
1060
+ queryStrings.push(" = CURRENT_TIMESTAMP");
1061
+ for (const [field, expr] of Object.entries(expressions)) {
1062
+ if (field !== softDeleteField) {
1063
+ queryStrings[queryStrings.length - 1] += ", ";
1064
+ queryValues.push(ident(field));
1065
+ queryStrings.push(" = ");
1066
+ mergeExpression(queryStrings, queryValues, expr);
1067
+ }
1068
+ }
1069
+ for (const [field, sym] of Object.entries(symbols)) {
1070
+ if (field !== softDeleteField) {
1071
+ queryStrings[queryStrings.length - 1] += ", ";
1072
+ queryValues.push(ident(field));
1073
+ queryStrings.push(` = ${resolveSQLBuiltin(sym)}`);
1074
+ }
1075
+ }
1076
+ queryStrings[queryStrings.length - 1] += " WHERE ";
1077
+ queryValues.push(ident(pk));
1078
+ queryStrings.push(" = ");
1079
+ queryValues.push(id);
1080
+ queryStrings.push("");
1081
+ return this.#driver.run(makeTemplate(queryStrings), queryValues);
1082
+ }
1083
+ async #softDeleteByIds(table2, ids) {
1084
+ if (ids.length === 0) {
1085
+ return 0;
1086
+ }
1087
+ const pk = table2.meta.primary;
1088
+ if (!pk) {
1089
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1090
+ }
1091
+ const softDeleteField = table2.meta.softDeleteField;
1092
+ const schemaExprs = injectSchemaExpressions(table2, {}, "update");
1093
+ const { expressions, symbols } = extractDBExpressions(schemaExprs);
1094
+ const queryStrings = ["UPDATE "];
1095
+ const queryValues = [];
1096
+ queryValues.push(ident(table2.name));
1097
+ queryStrings.push(" SET ");
1098
+ queryValues.push(ident(softDeleteField));
1099
+ queryStrings.push(" = CURRENT_TIMESTAMP");
1100
+ for (const [field, expr] of Object.entries(expressions)) {
1101
+ if (field !== softDeleteField) {
1102
+ queryStrings[queryStrings.length - 1] += ", ";
1103
+ queryValues.push(ident(field));
1104
+ queryStrings.push(" = ");
1105
+ mergeExpression(queryStrings, queryValues, expr);
1106
+ }
1107
+ }
1108
+ for (const [field, sym] of Object.entries(symbols)) {
1109
+ if (field !== softDeleteField) {
1110
+ queryStrings[queryStrings.length - 1] += ", ";
1111
+ queryValues.push(ident(field));
1112
+ queryStrings.push(` = ${resolveSQLBuiltin(sym)}`);
1113
+ }
1114
+ }
1115
+ queryStrings[queryStrings.length - 1] += " WHERE ";
1116
+ queryValues.push(ident(pk));
1117
+ queryStrings.push(" IN (");
1118
+ for (let i = 0; i < ids.length; i++) {
1119
+ queryValues.push(ids[i]);
1120
+ queryStrings.push(i < ids.length - 1 ? ", " : ")");
1121
+ }
1122
+ return this.#driver.run(makeTemplate(queryStrings), queryValues);
1123
+ }
1124
+ async #softDeleteWithWhere(table2, strings, templateValues) {
1125
+ const softDeleteField = table2.meta.softDeleteField;
1126
+ const schemaExprs = injectSchemaExpressions(table2, {}, "update");
1127
+ const { expressions, symbols } = extractDBExpressions(schemaExprs);
1128
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1129
+ strings,
1130
+ templateValues
1131
+ );
1132
+ const queryStrings = ["UPDATE "];
1133
+ const queryValues = [];
1134
+ queryValues.push(ident(table2.name));
1135
+ queryStrings.push(" SET ");
1136
+ queryValues.push(ident(softDeleteField));
1137
+ queryStrings.push(" = CURRENT_TIMESTAMP");
1138
+ for (const [field, expr] of Object.entries(expressions)) {
1139
+ if (field !== softDeleteField) {
1140
+ queryStrings[queryStrings.length - 1] += ", ";
1141
+ queryValues.push(ident(field));
1142
+ queryStrings.push(" = ");
1143
+ mergeExpression(queryStrings, queryValues, expr);
1144
+ }
1145
+ }
1146
+ for (const [field, sym] of Object.entries(symbols)) {
1147
+ if (field !== softDeleteField) {
1148
+ queryStrings[queryStrings.length - 1] += ", ";
1149
+ queryValues.push(ident(field));
1150
+ queryStrings.push(` = ${resolveSQLBuiltin(sym)}`);
1151
+ }
1152
+ }
1153
+ queryStrings[queryStrings.length - 1] += " " + expandedStrings[0];
1154
+ for (let i = 1; i < expandedStrings.length; i++) {
1155
+ queryStrings.push(expandedStrings[i]);
1156
+ }
1157
+ queryValues.push(...expandedValues);
1158
+ return this.#driver.run(makeTemplate(queryStrings), queryValues);
1159
+ }
1160
+ // ==========================================================================
1161
+ // Raw - No Normalization
1162
+ // ==========================================================================
1163
+ async query(strings, ...values) {
1164
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1165
+ strings,
1166
+ values
1167
+ );
1168
+ return this.#driver.all(expandedStrings, expandedValues);
1169
+ }
1170
+ async exec(strings, ...values) {
1171
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1172
+ strings,
1173
+ values
1174
+ );
1175
+ return this.#driver.run(expandedStrings, expandedValues);
1176
+ }
1177
+ async val(strings, ...values) {
1178
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1179
+ strings,
1180
+ values
1181
+ );
1182
+ return this.#driver.val(expandedStrings, expandedValues);
1183
+ }
1184
+ // ==========================================================================
1185
+ // Debugging
1186
+ // ==========================================================================
1187
+ /**
1188
+ * Print the generated SQL and parameters without executing.
1189
+ * Useful for debugging query composition and fragment expansion.
1190
+ * Note: SQL shown uses ? placeholders; actual query may use $1, $2 etc.
1191
+ */
1192
+ print(strings, ...values) {
1193
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1194
+ strings,
1195
+ values
1196
+ );
1197
+ let sql = expandedStrings[0];
1198
+ for (let i = 1; i < expandedStrings.length; i++) {
1199
+ sql += "?" + expandedStrings[i];
1200
+ }
1201
+ return { sql, params: expandedValues };
1202
+ }
1203
+ };
1204
+ var Database = class extends EventTarget {
1205
+ #driver;
1206
+ #version = 0;
1207
+ #opened = false;
1208
+ constructor(driver) {
1209
+ super();
1210
+ this.#driver = driver;
1211
+ }
1212
+ /**
1213
+ * Current database schema version.
1214
+ * Returns 0 if database has never been opened.
1215
+ */
1216
+ get version() {
1217
+ return this.#version;
1218
+ }
1219
+ /**
1220
+ * Open the database at a specific version.
1221
+ *
1222
+ * If the requested version is higher than the current version,
1223
+ * fires an "upgradeneeded" event and waits for all waitUntil()
1224
+ * promises before completing.
1225
+ *
1226
+ * Migration safety: Uses exclusive locking to prevent race conditions
1227
+ * when multiple processes attempt migrations simultaneously.
1228
+ *
1229
+ * @example
1230
+ * db.addEventListener("upgradeneeded", (e) => {
1231
+ * e.waitUntil(runMigrations(e));
1232
+ * });
1233
+ * await db.open(2);
1234
+ */
1235
+ async open(version) {
1236
+ if (this.#opened) {
1237
+ throw new Error("Database already opened");
1238
+ }
1239
+ const runMigration = async () => {
1240
+ await this.#ensureMigrationsTable();
1241
+ const currentVersion = await this.#getCurrentVersionLocked();
1242
+ if (version > currentVersion) {
1243
+ const event = new DatabaseUpgradeEvent("upgradeneeded", {
1244
+ oldVersion: currentVersion,
1245
+ newVersion: version
1246
+ });
1247
+ this.dispatchEvent(event);
1248
+ await event._settle();
1249
+ await this.#setVersion(version);
1250
+ }
1251
+ };
1252
+ if (this.#driver.withMigrationLock) {
1253
+ await this.#driver.withMigrationLock(runMigration);
1254
+ } else {
1255
+ await this.#driver.transaction(runMigration);
1256
+ }
1257
+ this.#version = version;
1258
+ this.#opened = true;
1259
+ }
1260
+ // ==========================================================================
1261
+ // Migration Table Helpers
1262
+ // ==========================================================================
1263
+ async #ensureMigrationsTable() {
1264
+ const createTable = makeTemplate([
1265
+ `CREATE TABLE IF NOT EXISTS _migrations (
1266
+ version INTEGER PRIMARY KEY
1267
+ )`
1268
+ ]);
1269
+ await this.#driver.run(createTable, []);
1270
+ }
1271
+ async #getCurrentVersionLocked() {
1272
+ const selectVersion = makeTemplate([
1273
+ `SELECT MAX(version) as version FROM _migrations`
1274
+ ]);
1275
+ const row = await this.#driver.get(selectVersion, []);
1276
+ return row?.version ?? 0;
1277
+ }
1278
+ async #setVersion(version) {
1279
+ const insertVersion = makeTemplate([
1280
+ `INSERT INTO _migrations (version) VALUES (`,
1281
+ `)`
1282
+ ]);
1283
+ await this.#driver.run(insertVersion, [version]);
1284
+ }
1285
+ all(tables) {
1286
+ const tableArray = Array.isArray(tables) ? tables : [tables];
1287
+ return async (strings, ...values) => {
1288
+ const { strings: colStrings, values: colValues } = buildSelectCols(tableArray);
1289
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
1290
+ const queryStrings = ["SELECT "];
1291
+ const queryValues = [];
1292
+ queryStrings[0] += colStrings[0];
1293
+ for (let i = 1; i < colStrings.length; i++) {
1294
+ queryStrings.push(colStrings[i]);
1295
+ }
1296
+ queryValues.push(...colValues);
1297
+ queryStrings[queryStrings.length - 1] += " FROM ";
1298
+ queryValues.push(ident(tableArray[0].name));
1299
+ queryStrings.push(" ");
1300
+ queryStrings[queryStrings.length - 1] += expandedStrings[0];
1301
+ for (let i = 1; i < expandedStrings.length; i++) {
1302
+ queryStrings.push(expandedStrings[i]);
1303
+ }
1304
+ queryValues.push(...expandedValues);
1305
+ const rows = await this.#driver.all(
1306
+ makeTemplate(queryStrings),
1307
+ queryValues
1308
+ );
1309
+ return normalize(rows, tableArray);
1310
+ };
1311
+ }
1312
+ get(tables, id) {
1313
+ if (id !== void 0) {
1314
+ const table2 = tables;
1315
+ const pk = table2.meta.primary;
1316
+ if (!pk) {
1317
+ return Promise.reject(
1318
+ new Error(`Table ${table2.name} has no primary key defined`)
1319
+ );
1320
+ }
1321
+ const { strings, values } = buildSelectByPkParts(table2.name, pk, id);
1322
+ return this.#driver.get(strings, values).then((row) => {
1323
+ if (!row)
1324
+ return null;
1325
+ return decodeData(table2, row);
1326
+ });
1327
+ }
1328
+ const tableArray = Array.isArray(tables) ? tables : [tables];
1329
+ return async (strings, ...values) => {
1330
+ const { strings: colStrings, values: colValues } = buildSelectCols(tableArray);
1331
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
1332
+ const queryStrings = ["SELECT "];
1333
+ const queryValues = [];
1334
+ queryStrings[0] += colStrings[0];
1335
+ for (let i = 1; i < colStrings.length; i++) {
1336
+ queryStrings.push(colStrings[i]);
1337
+ }
1338
+ queryValues.push(...colValues);
1339
+ queryStrings[queryStrings.length - 1] += " FROM ";
1340
+ queryValues.push(ident(tableArray[0].name));
1341
+ queryStrings.push(" ");
1342
+ queryStrings[queryStrings.length - 1] += expandedStrings[0];
1343
+ for (let i = 1; i < expandedStrings.length; i++) {
1344
+ queryStrings.push(expandedStrings[i]);
1345
+ }
1346
+ queryValues.push(...expandedValues);
1347
+ const row = await this.#driver.get(
1348
+ makeTemplate(queryStrings),
1349
+ queryValues
1350
+ );
1351
+ return normalizeOne(row, tableArray);
1352
+ };
1353
+ }
1354
+ async insert(table2, data) {
1355
+ if (Array.isArray(data)) {
1356
+ if (data.length === 0) {
1357
+ return [];
1358
+ }
1359
+ const results = [];
1360
+ for (const row of data) {
1361
+ results.push(await this.#insertOne(table2, row));
1362
+ }
1363
+ return results;
1364
+ }
1365
+ return this.#insertOne(table2, data);
1366
+ }
1367
+ async #insertOne(table2, data) {
1368
+ if (table2.meta.isPartial) {
1369
+ throw new Error(
1370
+ `Cannot insert into partial table "${table2.name}". Use the full table definition instead.`
1371
+ );
1372
+ }
1373
+ if (table2.meta.isDerived) {
1374
+ throw new Error(
1375
+ `Cannot insert into derived table "${table2.name}". Derived tables are SELECT-only.`
1376
+ );
1377
+ }
1378
+ const dataWithSchemaExprs = injectSchemaExpressions(
1379
+ table2,
1380
+ data,
1381
+ "insert"
1382
+ );
1383
+ const { regularData, expressions, symbols } = extractDBExpressions(
1384
+ dataWithSchemaExprs,
1385
+ table2
1386
+ );
1387
+ let schema = table2.schema;
1388
+ const skipFields = { ...expressions, ...symbols };
1389
+ if (Object.keys(skipFields).length > 0) {
1390
+ const skipFieldSchemas = Object.keys(skipFields).reduce(
1391
+ (acc, key) => {
1392
+ acc[key] = table2.schema.shape[key].optional();
1393
+ return acc;
1394
+ },
1395
+ {}
1396
+ );
1397
+ schema = table2.schema.extend(skipFieldSchemas);
1398
+ }
1399
+ const validated = validateWithStandardSchema(
1400
+ schema,
1401
+ regularData
1402
+ );
1403
+ const encoded = encodeData(table2, validated);
1404
+ const insertParts = buildInsertParts(
1405
+ table2.name,
1406
+ encoded,
1407
+ expressions,
1408
+ symbols
1409
+ );
1410
+ if (this.#driver.supportsReturning) {
1411
+ const { strings, values } = appendReturning(insertParts);
1412
+ const row = await this.#driver.get(
1413
+ strings,
1414
+ values
1415
+ );
1416
+ return decodeData(table2, row);
1417
+ }
1418
+ await this.#driver.run(insertParts.strings, insertParts.values);
1419
+ const pk = table2.meta.primary;
1420
+ const pkValue = pk ? encoded[pk] ?? (expressions[pk] ? void 0 : null) : null;
1421
+ if (pk && pkValue !== void 0 && pkValue !== null) {
1422
+ const { strings, values } = buildSelectByPkParts(table2.name, pk, pkValue);
1423
+ const row = await this.#driver.get(
1424
+ strings,
1425
+ values
1426
+ );
1427
+ if (row) {
1428
+ return decodeData(table2, row);
1429
+ }
1430
+ }
1431
+ return validated;
1432
+ }
1433
+ update(table2, data, idOrIds) {
1434
+ if (idOrIds === void 0) {
1435
+ return async (strings, ...values) => {
1436
+ return this.#updateWithWhere(table2, data, strings, values);
1437
+ };
1438
+ }
1439
+ if (Array.isArray(idOrIds)) {
1440
+ return this.#updateByIds(table2, data, idOrIds);
1441
+ }
1442
+ return this.#updateById(table2, data, idOrIds);
1443
+ }
1444
+ async #updateById(table2, data, id) {
1445
+ const pk = table2.meta.primary;
1446
+ if (!pk) {
1447
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1448
+ }
1449
+ if (table2.meta.isDerived) {
1450
+ throw new Error(
1451
+ `Cannot update derived table "${table2.name}". Derived tables are SELECT-only.`
1452
+ );
1453
+ }
1454
+ const dataWithSchemaExprs = injectSchemaExpressions(
1455
+ table2,
1456
+ data,
1457
+ "update"
1458
+ );
1459
+ const { regularData, expressions, symbols } = extractDBExpressions(
1460
+ dataWithSchemaExprs,
1461
+ table2
1462
+ );
1463
+ const partialSchema = table2.schema.partial();
1464
+ const validated = validateWithStandardSchema(
1465
+ partialSchema,
1466
+ regularData
1467
+ );
1468
+ const allColumns = [
1469
+ ...Object.keys(validated),
1470
+ ...Object.keys(expressions),
1471
+ ...Object.keys(symbols)
1472
+ ];
1473
+ if (allColumns.length === 0) {
1474
+ throw new Error("No fields to update");
1475
+ }
1476
+ const encoded = encodeData(table2, validated);
1477
+ const updateParts = buildUpdateByIdParts(
1478
+ table2.name,
1479
+ pk,
1480
+ encoded,
1481
+ expressions,
1482
+ id,
1483
+ symbols
1484
+ );
1485
+ if (this.#driver.supportsReturning) {
1486
+ const { strings, values } = appendReturning(updateParts);
1487
+ const row2 = await this.#driver.get(
1488
+ strings,
1489
+ values
1490
+ );
1491
+ if (!row2)
1492
+ return null;
1493
+ return decodeData(table2, row2);
1494
+ }
1495
+ await this.#driver.run(updateParts.strings, updateParts.values);
1496
+ const { strings: selectStrings, values: selectValues } = buildSelectByPkParts(
1497
+ table2.name,
1498
+ pk,
1499
+ id
1500
+ );
1501
+ const row = await this.#driver.get(
1502
+ selectStrings,
1503
+ selectValues
1504
+ );
1505
+ if (!row)
1506
+ return null;
1507
+ return decodeData(table2, row);
1508
+ }
1509
+ async #updateByIds(table2, data, ids) {
1510
+ if (ids.length === 0) {
1511
+ return [];
1512
+ }
1513
+ const pk = table2.meta.primary;
1514
+ if (!pk) {
1515
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1516
+ }
1517
+ if (table2.meta.isDerived) {
1518
+ throw new Error(
1519
+ `Cannot update derived table "${table2.name}". Derived tables are SELECT-only.`
1520
+ );
1521
+ }
1522
+ const dataWithSchemaExprs = injectSchemaExpressions(
1523
+ table2,
1524
+ data,
1525
+ "update"
1526
+ );
1527
+ const { regularData, expressions, symbols } = extractDBExpressions(
1528
+ dataWithSchemaExprs,
1529
+ table2
1530
+ );
1531
+ const partialSchema = table2.schema.partial();
1532
+ const validated = validateWithStandardSchema(
1533
+ partialSchema,
1534
+ regularData
1535
+ );
1536
+ const allColumns = [
1537
+ ...Object.keys(validated),
1538
+ ...Object.keys(expressions),
1539
+ ...Object.keys(symbols)
1540
+ ];
1541
+ if (allColumns.length === 0) {
1542
+ throw new Error("No fields to update");
1543
+ }
1544
+ const encoded = encodeData(table2, validated);
1545
+ const updateParts = buildUpdateByIdsParts(
1546
+ table2.name,
1547
+ pk,
1548
+ encoded,
1549
+ expressions,
1550
+ ids,
1551
+ symbols
1552
+ );
1553
+ if (this.#driver.supportsReturning) {
1554
+ const { strings, values } = appendReturning(updateParts);
1555
+ const rows2 = await this.#driver.all(
1556
+ strings,
1557
+ values
1558
+ );
1559
+ const resultMap2 = /* @__PURE__ */ new Map();
1560
+ for (const row of rows2) {
1561
+ const entity = decodeData(table2, row);
1562
+ resultMap2.set(row[pk], entity);
1563
+ }
1564
+ return ids.map((id) => resultMap2.get(id) ?? null);
1565
+ }
1566
+ await this.#driver.run(updateParts.strings, updateParts.values);
1567
+ const { strings: selectStrings, values: selectValues } = buildSelectByPksParts(table2.name, pk, ids);
1568
+ const rows = await this.#driver.all(
1569
+ selectStrings,
1570
+ selectValues
1571
+ );
1572
+ const resultMap = /* @__PURE__ */ new Map();
1573
+ for (const row of rows) {
1574
+ const entity = decodeData(table2, row);
1575
+ resultMap.set(row[pk], entity);
1576
+ }
1577
+ return ids.map((id) => resultMap.get(id) ?? null);
1578
+ }
1579
+ async #updateWithWhere(table2, data, strings, templateValues) {
1580
+ if (table2.meta.isDerived) {
1581
+ throw new Error(
1582
+ `Cannot update derived table "${table2.name}". Derived tables are SELECT-only.`
1583
+ );
1584
+ }
1585
+ const pk = table2.meta.primary;
1586
+ if (!pk) {
1587
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1588
+ }
1589
+ const dataWithSchemaExprs = injectSchemaExpressions(
1590
+ table2,
1591
+ data,
1592
+ "update"
1593
+ );
1594
+ const { regularData, expressions } = extractDBExpressions(
1595
+ dataWithSchemaExprs,
1596
+ table2
1597
+ );
1598
+ const partialSchema = table2.schema.partial();
1599
+ const validated = validateWithStandardSchema(
1600
+ partialSchema,
1601
+ regularData
1602
+ );
1603
+ const allColumns = [...Object.keys(validated), ...Object.keys(expressions)];
1604
+ if (allColumns.length === 0) {
1605
+ throw new Error("No fields to update");
1606
+ }
1607
+ const encoded = encodeData(table2, validated);
1608
+ const { strings: whereStrings, values: whereValues } = expandFragments(
1609
+ strings,
1610
+ templateValues
1611
+ );
1612
+ const setCols = Object.keys(encoded);
1613
+ const exprCols = Object.keys(expressions);
1614
+ const queryStrings = ["UPDATE "];
1615
+ const queryValues = [];
1616
+ queryValues.push(ident(table2.name));
1617
+ queryStrings.push(" SET ");
1618
+ for (let i = 0; i < setCols.length; i++) {
1619
+ const col = setCols[i];
1620
+ queryValues.push(ident(col));
1621
+ queryStrings.push(" = ");
1622
+ queryValues.push(encoded[col]);
1623
+ queryStrings.push(i < setCols.length - 1 ? ", " : "");
1624
+ }
1625
+ for (let i = 0; i < exprCols.length; i++) {
1626
+ if (setCols.length > 0 || i > 0) {
1627
+ queryStrings[queryStrings.length - 1] += ", ";
1628
+ }
1629
+ queryValues.push(ident(exprCols[i]));
1630
+ queryStrings.push(" = ");
1631
+ mergeExpression(queryStrings, queryValues, expressions[exprCols[i]]);
1632
+ }
1633
+ queryStrings[queryStrings.length - 1] += " " + whereStrings[0];
1634
+ for (let i = 1; i < whereStrings.length; i++) {
1635
+ queryStrings.push(whereStrings[i]);
1636
+ }
1637
+ queryValues.push(...whereValues);
1638
+ if (this.#driver.supportsReturning) {
1639
+ queryStrings[queryStrings.length - 1] += " RETURNING *";
1640
+ const rows2 = await this.#driver.all(
1641
+ makeTemplate(queryStrings),
1642
+ queryValues
1643
+ );
1644
+ return rows2.map((row) => decodeData(table2, row));
1645
+ }
1646
+ const selectIdStrings = ["SELECT "];
1647
+ const selectIdValues = [];
1648
+ selectIdValues.push(ident(pk));
1649
+ selectIdStrings.push(" FROM ");
1650
+ selectIdValues.push(ident(table2.name));
1651
+ selectIdStrings.push(" " + whereStrings[0]);
1652
+ for (let i = 1; i < whereStrings.length; i++) {
1653
+ selectIdStrings.push(whereStrings[i]);
1654
+ }
1655
+ selectIdValues.push(...whereValues);
1656
+ const idRows = await this.#driver.all(
1657
+ makeTemplate(selectIdStrings),
1658
+ selectIdValues
1659
+ );
1660
+ const ids = idRows.map((r) => r[pk]);
1661
+ if (ids.length === 0) {
1662
+ return [];
1663
+ }
1664
+ await this.#driver.run(makeTemplate(queryStrings), queryValues);
1665
+ const { strings: selectStrings, values: selectVals } = buildSelectByPksParts(
1666
+ table2.name,
1667
+ pk,
1668
+ ids
1669
+ );
1670
+ const rows = await this.#driver.all(
1671
+ selectStrings,
1672
+ selectVals
1673
+ );
1674
+ return rows.map((row) => decodeData(table2, row));
1675
+ }
1676
+ delete(table2, idOrIds) {
1677
+ if (idOrIds === void 0) {
1678
+ return async (strings, ...values) => {
1679
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
1680
+ const deleteStrings = ["DELETE FROM "];
1681
+ const deleteValues = [ident(table2.name)];
1682
+ deleteStrings.push(" " + expandedStrings[0]);
1683
+ for (let i = 1; i < expandedStrings.length; i++) {
1684
+ deleteStrings.push(expandedStrings[i]);
1685
+ }
1686
+ deleteValues.push(...expandedValues);
1687
+ return this.#driver.run(makeTemplate(deleteStrings), deleteValues);
1688
+ };
1689
+ }
1690
+ if (Array.isArray(idOrIds)) {
1691
+ return this.#deleteByIds(table2, idOrIds);
1692
+ }
1693
+ return this.#deleteById(table2, idOrIds);
1694
+ }
1695
+ async #deleteById(table2, id) {
1696
+ const pk = table2.meta.primary;
1697
+ if (!pk) {
1698
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1699
+ }
1700
+ const { strings, values } = buildDeleteByPkParts(table2.name, pk, id);
1701
+ return this.#driver.run(strings, values);
1702
+ }
1703
+ async #deleteByIds(table2, ids) {
1704
+ if (ids.length === 0) {
1705
+ return 0;
1706
+ }
1707
+ const pk = table2.meta.primary;
1708
+ if (!pk) {
1709
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1710
+ }
1711
+ const { strings, values } = buildDeleteByPksParts(table2.name, pk, ids);
1712
+ return this.#driver.run(strings, values);
1713
+ }
1714
+ softDelete(table2, idOrIds) {
1715
+ const softDeleteField = table2.meta.softDeleteField;
1716
+ if (!softDeleteField) {
1717
+ throw new Error(
1718
+ `Table ${table2.name} does not have a soft delete field. Use softDelete() wrapper to mark a field.`
1719
+ );
1720
+ }
1721
+ if (idOrIds === void 0) {
1722
+ return async (strings, ...values) => {
1723
+ return this.#softDeleteWithWhere(table2, strings, values);
1724
+ };
1725
+ }
1726
+ if (Array.isArray(idOrIds)) {
1727
+ return this.#softDeleteByIds(table2, idOrIds);
1728
+ }
1729
+ return this.#softDeleteById(table2, idOrIds);
1730
+ }
1731
+ async #softDeleteById(table2, id) {
1732
+ const pk = table2.meta.primary;
1733
+ if (!pk) {
1734
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1735
+ }
1736
+ const softDeleteField = table2.meta.softDeleteField;
1737
+ const schemaExprs = injectSchemaExpressions(table2, {}, "update");
1738
+ const { expressions, symbols } = extractDBExpressions(schemaExprs);
1739
+ const queryStrings = ["UPDATE "];
1740
+ const queryValues = [];
1741
+ queryValues.push(ident(table2.name));
1742
+ queryStrings.push(" SET ");
1743
+ queryValues.push(ident(softDeleteField));
1744
+ queryStrings.push(" = CURRENT_TIMESTAMP");
1745
+ for (const [field, expr] of Object.entries(expressions)) {
1746
+ if (field !== softDeleteField) {
1747
+ queryStrings[queryStrings.length - 1] += ", ";
1748
+ queryValues.push(ident(field));
1749
+ queryStrings.push(" = ");
1750
+ mergeExpression(queryStrings, queryValues, expr);
1751
+ }
1752
+ }
1753
+ for (const [field, sym] of Object.entries(symbols)) {
1754
+ if (field !== softDeleteField) {
1755
+ queryStrings[queryStrings.length - 1] += ", ";
1756
+ queryValues.push(ident(field));
1757
+ queryStrings.push(` = ${resolveSQLBuiltin(sym)}`);
1758
+ }
1759
+ }
1760
+ queryStrings[queryStrings.length - 1] += " WHERE ";
1761
+ queryValues.push(ident(pk));
1762
+ queryStrings.push(" = ");
1763
+ queryValues.push(id);
1764
+ queryStrings.push("");
1765
+ return this.#driver.run(makeTemplate(queryStrings), queryValues);
1766
+ }
1767
+ async #softDeleteByIds(table2, ids) {
1768
+ if (ids.length === 0) {
1769
+ return 0;
1770
+ }
1771
+ const pk = table2.meta.primary;
1772
+ if (!pk) {
1773
+ throw new Error(`Table ${table2.name} has no primary key defined`);
1774
+ }
1775
+ const softDeleteField = table2.meta.softDeleteField;
1776
+ const schemaExprs = injectSchemaExpressions(table2, {}, "update");
1777
+ const { expressions, symbols } = extractDBExpressions(schemaExprs);
1778
+ const queryStrings = ["UPDATE "];
1779
+ const queryValues = [];
1780
+ queryValues.push(ident(table2.name));
1781
+ queryStrings.push(" SET ");
1782
+ queryValues.push(ident(softDeleteField));
1783
+ queryStrings.push(" = CURRENT_TIMESTAMP");
1784
+ for (const [field, expr] of Object.entries(expressions)) {
1785
+ if (field !== softDeleteField) {
1786
+ queryStrings[queryStrings.length - 1] += ", ";
1787
+ queryValues.push(ident(field));
1788
+ queryStrings.push(" = ");
1789
+ mergeExpression(queryStrings, queryValues, expr);
1790
+ }
1791
+ }
1792
+ for (const [field, sym] of Object.entries(symbols)) {
1793
+ if (field !== softDeleteField) {
1794
+ queryStrings[queryStrings.length - 1] += ", ";
1795
+ queryValues.push(ident(field));
1796
+ queryStrings.push(` = ${resolveSQLBuiltin(sym)}`);
1797
+ }
1798
+ }
1799
+ queryStrings[queryStrings.length - 1] += " WHERE ";
1800
+ queryValues.push(ident(pk));
1801
+ queryStrings.push(" IN (");
1802
+ for (let i = 0; i < ids.length; i++) {
1803
+ queryValues.push(ids[i]);
1804
+ queryStrings.push(i < ids.length - 1 ? ", " : ")");
1805
+ }
1806
+ return this.#driver.run(makeTemplate(queryStrings), queryValues);
1807
+ }
1808
+ async #softDeleteWithWhere(table2, strings, templateValues) {
1809
+ const softDeleteField = table2.meta.softDeleteField;
1810
+ const schemaExprs = injectSchemaExpressions(table2, {}, "update");
1811
+ const { expressions, symbols } = extractDBExpressions(schemaExprs);
1812
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1813
+ strings,
1814
+ templateValues
1815
+ );
1816
+ const queryStrings = ["UPDATE "];
1817
+ const queryValues = [];
1818
+ queryValues.push(ident(table2.name));
1819
+ queryStrings.push(" SET ");
1820
+ queryValues.push(ident(softDeleteField));
1821
+ queryStrings.push(" = CURRENT_TIMESTAMP");
1822
+ for (const [field, expr] of Object.entries(expressions)) {
1823
+ if (field !== softDeleteField) {
1824
+ queryStrings[queryStrings.length - 1] += ", ";
1825
+ queryValues.push(ident(field));
1826
+ queryStrings.push(" = ");
1827
+ mergeExpression(queryStrings, queryValues, expr);
1828
+ }
1829
+ }
1830
+ for (const [field, sym] of Object.entries(symbols)) {
1831
+ if (field !== softDeleteField) {
1832
+ queryStrings[queryStrings.length - 1] += ", ";
1833
+ queryValues.push(ident(field));
1834
+ queryStrings.push(` = ${resolveSQLBuiltin(sym)}`);
1835
+ }
1836
+ }
1837
+ queryStrings[queryStrings.length - 1] += " " + expandedStrings[0];
1838
+ for (let i = 1; i < expandedStrings.length; i++) {
1839
+ queryStrings.push(expandedStrings[i]);
1840
+ }
1841
+ queryValues.push(...expandedValues);
1842
+ return this.#driver.run(makeTemplate(queryStrings), queryValues);
1843
+ }
1844
+ // ==========================================================================
1845
+ // Raw - No Normalization
1846
+ // ==========================================================================
1847
+ /**
1848
+ * Execute a raw query and return rows.
1849
+ *
1850
+ * @example
1851
+ * const counts = await db.query<{ count: number }>`
1852
+ * SELECT COUNT(*) as count FROM posts WHERE author_id = ${userId}
1853
+ * `;
1854
+ */
1855
+ async query(strings, ...values) {
1856
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1857
+ strings,
1858
+ values
1859
+ );
1860
+ return this.#driver.all(expandedStrings, expandedValues);
1861
+ }
1862
+ /**
1863
+ * Execute a statement (INSERT, UPDATE, DELETE, DDL).
1864
+ *
1865
+ * @example
1866
+ * await db.exec`CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY)`;
1867
+ */
1868
+ async exec(strings, ...values) {
1869
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1870
+ strings,
1871
+ values
1872
+ );
1873
+ return this.#driver.run(expandedStrings, expandedValues);
1874
+ }
1875
+ /**
1876
+ * Execute a query and return a single value.
1877
+ *
1878
+ * @example
1879
+ * const count = await db.val<number>`SELECT COUNT(*) FROM posts`;
1880
+ */
1881
+ async val(strings, ...values) {
1882
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1883
+ strings,
1884
+ values
1885
+ );
1886
+ return this.#driver.val(expandedStrings, expandedValues);
1887
+ }
1888
+ // ==========================================================================
1889
+ // Debugging
1890
+ // ==========================================================================
1891
+ /**
1892
+ * Print the generated SQL and parameters without executing.
1893
+ * Useful for debugging query composition and fragment expansion.
1894
+ * Note: SQL shown uses ? placeholders; actual query may use $1, $2 etc.
1895
+ */
1896
+ print(strings, ...values) {
1897
+ const { strings: expandedStrings, values: expandedValues } = expandFragments(
1898
+ strings,
1899
+ values
1900
+ );
1901
+ let sql = expandedStrings[0];
1902
+ for (let i = 1; i < expandedStrings.length; i++) {
1903
+ sql += "?" + expandedStrings[i];
1904
+ }
1905
+ return { sql, params: expandedValues };
1906
+ }
1907
+ // ==========================================================================
1908
+ // Schema Ensure Methods
1909
+ // ==========================================================================
1910
+ /**
1911
+ * Ensure a table exists with its columns and indexes.
1912
+ *
1913
+ * **For new tables**: Creates the table with full structure including
1914
+ * primary key, unique constraints, foreign keys, and indexes.
1915
+ *
1916
+ * **For existing tables**: Only performs safe, additive operations:
1917
+ * - Adds missing columns
1918
+ * - Adds missing non-unique indexes
1919
+ *
1920
+ * Unique constraints and foreign keys on existing tables require
1921
+ * explicit `ensureConstraints()` call (they can fail or lock).
1922
+ *
1923
+ * @throws {EnsureError} If DDL execution fails
1924
+ * @throws {SchemaDriftError} If existing table has missing constraints
1925
+ * (directs user to run ensureConstraints)
1926
+ *
1927
+ * @example
1928
+ * // In migration handler
1929
+ * await db.ensureTable(Users);
1930
+ * await db.ensureTable(Posts); // FK to Users - ensure Users first
1931
+ */
1932
+ async ensureTable(table2) {
1933
+ if (!this.#driver.ensureTable) {
1934
+ throw new Error(
1935
+ "Driver does not implement ensureTable(). Schema ensure methods require a driver with schema management support."
1936
+ );
1937
+ }
1938
+ const doEnsure = () => this.#driver.ensureTable(table2);
1939
+ if (this.#driver.withMigrationLock) {
1940
+ return await this.#driver.withMigrationLock(doEnsure);
1941
+ }
1942
+ return await doEnsure();
1943
+ }
1944
+ /**
1945
+ * Ensure constraints (unique, foreign key) are applied to an existing table.
1946
+ *
1947
+ * **WARNING**: This operation can be expensive and cause locks on large tables.
1948
+ * It performs preflight checks to detect data violations before applying constraints.
1949
+ *
1950
+ * For each declared constraint:
1951
+ * 1. Preflight: Check for violations (duplicates for UNIQUE, orphans for FK)
1952
+ * 2. If violations found: Throw ConstraintPreflightError with diagnostic query
1953
+ * 3. If clean: Apply the constraint
1954
+ *
1955
+ * @throws {Error} If table doesn't exist
1956
+ * @throws {ConstraintPreflightError} If data violates a constraint
1957
+ * @throws {EnsureError} If DDL execution fails
1958
+ *
1959
+ * @example
1960
+ * // After ensuring table structure
1961
+ * await db.ensureTable(Users);
1962
+ * // Explicitly apply constraints (may lock, may fail)
1963
+ * await db.ensureConstraints(Users);
1964
+ */
1965
+ async ensureConstraints(table2) {
1966
+ if (!this.#driver.ensureConstraints) {
1967
+ throw new Error(
1968
+ "Driver does not implement ensureConstraints(). Schema ensure methods require a driver with schema management support."
1969
+ );
1970
+ }
1971
+ const doEnsure = () => this.#driver.ensureConstraints(table2);
1972
+ if (this.#driver.withMigrationLock) {
1973
+ return await this.#driver.withMigrationLock(doEnsure);
1974
+ }
1975
+ return await doEnsure();
1976
+ }
1977
+ /**
1978
+ * Copy column data for safe rename migrations.
1979
+ *
1980
+ * Executes: UPDATE <table> SET <toField> = <fromField> WHERE <toField> IS NULL
1981
+ *
1982
+ * This is idempotent - rows where toField already has a value are skipped.
1983
+ * The fromField may be a legacy column not in the current schema.
1984
+ *
1985
+ * @param table The table to update
1986
+ * @param fromField Source column (may be legacy/not in schema)
1987
+ * @param toField Destination column (must exist in schema)
1988
+ * @returns Number of rows updated
1989
+ *
1990
+ * @example
1991
+ * // Rename "email" to "emailAddress":
1992
+ * // 1. Add new column
1993
+ * await db.ensureTable(UsersWithEmailAddress);
1994
+ * // 2. Copy data
1995
+ * const updated = await db.copyColumn(Users, "email", "emailAddress");
1996
+ * // 3. Later: remove old column (manual migration)
1997
+ */
1998
+ async copyColumn(table2, fromField, toField) {
1999
+ const fields = Object.keys(table2.meta.fields);
2000
+ if (!fields.includes(toField)) {
2001
+ throw new Error(
2002
+ `Destination field "${toField}" does not exist in table "${table2.name}". Available fields: ${fields.join(", ")}`
2003
+ );
2004
+ }
2005
+ if (this.#driver.copyColumn) {
2006
+ const doCopy2 = () => this.#driver.copyColumn(table2, fromField, toField);
2007
+ if (this.#driver.withMigrationLock) {
2008
+ return await this.#driver.withMigrationLock(doCopy2);
2009
+ }
2010
+ return await doCopy2();
2011
+ }
2012
+ const doCopy = async () => {
2013
+ const tableName = table2.name;
2014
+ const columnExists = await this.#checkColumnExists(tableName, fromField);
2015
+ if (!columnExists) {
2016
+ throw new EnsureError(
2017
+ `Source field "${fromField}" does not exist in table "${tableName}"`,
2018
+ { operation: "copyColumn", table: tableName, step: 0 }
2019
+ );
2020
+ }
2021
+ try {
2022
+ const updateStrings = makeTemplate([
2023
+ "UPDATE ",
2024
+ " SET ",
2025
+ " = ",
2026
+ " WHERE ",
2027
+ " IS NULL"
2028
+ ]);
2029
+ const updateValues = [
2030
+ ident(tableName),
2031
+ ident(toField),
2032
+ ident(fromField),
2033
+ ident(toField)
2034
+ ];
2035
+ return await this.#driver.run(updateStrings, updateValues);
2036
+ } catch (error) {
2037
+ throw new EnsureError(
2038
+ `copyColumn failed: ${error instanceof Error ? error.message : String(error)}`,
2039
+ { operation: "copyColumn", table: tableName, step: 0 },
2040
+ { cause: error }
2041
+ );
2042
+ }
2043
+ };
2044
+ if (this.#driver.withMigrationLock) {
2045
+ return await this.#driver.withMigrationLock(doCopy);
2046
+ }
2047
+ return await doCopy();
2048
+ }
2049
+ /**
2050
+ * Check if a column exists in the actual database table.
2051
+ * Queries the actual table structure to verify column existence.
2052
+ */
2053
+ async #checkColumnExists(tableName, columnName) {
2054
+ if (this.#driver.getColumns) {
2055
+ const columns = await this.#driver.getColumns(tableName);
2056
+ return columns.some((col) => col.name === columnName);
2057
+ }
2058
+ try {
2059
+ const pragmaStrings = makeTemplate(["PRAGMA table_info(", ")"]);
2060
+ const pragmaValues = [ident(tableName)];
2061
+ const columns = await this.#driver.all(
2062
+ pragmaStrings,
2063
+ pragmaValues
2064
+ );
2065
+ if (columns.length > 0) {
2066
+ return columns.some((col) => col.name === columnName);
2067
+ }
2068
+ } catch {
2069
+ }
2070
+ try {
2071
+ const schemaStrings = makeTemplate([
2072
+ "SELECT column_name FROM information_schema.columns WHERE table_name = ",
2073
+ " AND column_name = ",
2074
+ " LIMIT 1"
2075
+ ]);
2076
+ const schemaValues = [tableName, columnName];
2077
+ const result = await this.#driver.all(schemaStrings, schemaValues);
2078
+ return result.length > 0;
2079
+ } catch {
2080
+ return true;
2081
+ }
2082
+ }
2083
+ // ==========================================================================
2084
+ // Transactions
2085
+ // ==========================================================================
2086
+ /**
2087
+ * Execute a function within a database transaction.
2088
+ *
2089
+ * If the function completes successfully, the transaction is committed.
2090
+ * If the function throws an error, the transaction is rolled back.
2091
+ *
2092
+ * All operations within the transaction callback use the same database
2093
+ * connection, ensuring transactional consistency.
2094
+ *
2095
+ * @example
2096
+ * await db.transaction(async (tx) => {
2097
+ * const user = await tx.insert(users, { id: "1", name: "Alice" });
2098
+ * await tx.insert(posts, { id: "1", authorId: user.id, title: "Hello" });
2099
+ * // If any insert fails, both are rolled back
2100
+ * });
2101
+ */
2102
+ async transaction(fn) {
2103
+ return await this.#driver.transaction(async (txDriver) => {
2104
+ const tx = new Transaction(txDriver);
2105
+ return await fn(tx);
2106
+ });
2107
+ }
2108
+ };
2109
+
2110
+ // src/zen.ts
2111
+ extendZod(zod);
2112
+ export {
2113
+ AlreadyExistsError,
2114
+ CURRENT_DATE,
2115
+ CURRENT_TIME,
2116
+ CURRENT_TIMESTAMP,
2117
+ ConnectionError,
2118
+ ConstraintPreflightError,
2119
+ ConstraintViolationError,
2120
+ Database,
2121
+ DatabaseError,
2122
+ DatabaseUpgradeEvent,
2123
+ EnsureError,
2124
+ MigrationError,
2125
+ MigrationLockError,
2126
+ NOW,
2127
+ NotFoundError,
2128
+ QueryError,
2129
+ SchemaDriftError,
2130
+ TODAY,
2131
+ TableDefinitionError,
2132
+ Transaction,
2133
+ TransactionError,
2134
+ ValidationError,
2135
+ hasErrorCode,
2136
+ ident,
2137
+ isDatabaseError,
2138
+ isSQLBuiltin,
2139
+ isSQLIdentifier,
2140
+ isSQLTemplate,
2141
+ table,
2142
+ zod as z
2143
+ };