@b9g/zen 0.1.0 → 0.1.2

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="./zen.d.ts" />
2
- import "../chunk-2IEEEMRN.js";
2
+ import "../chunk-NBXBBEMA.js";
3
3
  import {
4
4
  AlreadyExistsError,
5
5
  CURRENT_DATE,
@@ -20,19 +20,26 @@ import {
20
20
  TableDefinitionError,
21
21
  TransactionError,
22
22
  ValidationError,
23
+ createTemplate,
23
24
  decodeData,
24
25
  extendZod,
26
+ getTableMeta,
27
+ getViewMeta,
25
28
  hasErrorCode,
26
29
  ident,
30
+ inferFieldType,
27
31
  isDatabaseError,
28
32
  isSQLBuiltin,
29
33
  isSQLIdentifier,
30
34
  isSQLTemplate,
35
+ isTable,
36
+ isView,
31
37
  makeTemplate,
32
38
  resolveSQLBuiltin,
33
39
  table,
34
- validateWithStandardSchema
35
- } from "../chunk-56M5Z3A6.js";
40
+ validateWithStandardSchema,
41
+ view
42
+ } from "../chunk-BEX6VPES.js";
36
43
 
37
44
  // src/zen.ts
38
45
  import { z as zod } from "zod";
@@ -67,7 +74,7 @@ function getPrimaryKeyValue(entity, table2) {
67
74
  function entityKey(tableName, primaryKey) {
68
75
  return `${tableName}:${primaryKey}`;
69
76
  }
70
- function buildEntityMap(rows, tables) {
77
+ function buildEntityMap(rows, tables, driver) {
71
78
  const entities = /* @__PURE__ */ new Map();
72
79
  for (const row of rows) {
73
80
  for (const table2 of tables) {
@@ -79,7 +86,7 @@ function buildEntityMap(rows, tables) {
79
86
  continue;
80
87
  const key = entityKey(table2.name, pk);
81
88
  if (!entities.has(key)) {
82
- const decoded = decodeData(table2, data);
89
+ const decoded = decodeData(table2, data, driver);
83
90
  const parsed = validateWithStandardSchema(
84
91
  table2.schema,
85
92
  decoded
@@ -235,7 +242,7 @@ function validateRegisteredTables(rows, tables) {
235
242
  );
236
243
  }
237
244
  }
238
- function normalize(rows, tables) {
245
+ function normalize(rows, tables, driver) {
239
246
  if (tables.length === 0) {
240
247
  throw new Error("At least one table is required");
241
248
  }
@@ -243,16 +250,16 @@ function normalize(rows, tables) {
243
250
  return [];
244
251
  }
245
252
  validateRegisteredTables(rows, tables);
246
- const entities = buildEntityMap(rows, tables);
253
+ const entities = buildEntityMap(rows, tables, driver);
247
254
  resolveReferences(entities, tables);
248
255
  applyDerivedProperties(entities, tables);
249
256
  const mainTable = tables[0];
250
257
  return extractMainEntities(rows, mainTable, entities);
251
258
  }
252
- function normalizeOne(row, tables) {
259
+ function normalizeOne(row, tables, driver) {
253
260
  if (!row)
254
261
  return null;
255
- const results = normalize([row], tables);
262
+ const results = normalize([row], tables, driver);
256
263
  return results[0] ?? null;
257
264
  }
258
265
 
@@ -318,7 +325,7 @@ function injectSchemaExpressions(table2, data, operation) {
318
325
  }
319
326
  return result;
320
327
  }
321
- function encodeData(table2, data) {
328
+ function encodeData(table2, data, driver) {
322
329
  const encoded = {};
323
330
  const shape = table2.schema.shape;
324
331
  for (const [key, value] of Object.entries(data)) {
@@ -326,16 +333,21 @@ function encodeData(table2, data) {
326
333
  const fieldSchema = shape?.[key];
327
334
  if (fieldMeta?.encode && typeof fieldMeta.encode === "function") {
328
335
  encoded[key] = fieldMeta.encode(value);
336
+ } else if (driver?.encodeValue && fieldSchema) {
337
+ const fieldType = inferFieldType(fieldSchema);
338
+ encoded[key] = driver.encodeValue(value, fieldType);
329
339
  } else if (fieldSchema) {
330
340
  let core = fieldSchema;
331
341
  while (typeof core.unwrap === "function") {
332
- if (core instanceof z.ZodArray || core instanceof z.ZodObject) {
342
+ if (core instanceof z.ZodArray || core instanceof z.ZodObject || core instanceof z.ZodDate) {
333
343
  break;
334
344
  }
335
345
  core = core.unwrap();
336
346
  }
337
347
  if ((core instanceof z.ZodObject || core instanceof z.ZodArray) && value !== null && value !== void 0) {
338
348
  encoded[key] = JSON.stringify(value);
349
+ } else if (core instanceof z.ZodDate && value instanceof Date && !isNaN(value.getTime())) {
350
+ encoded[key] = value.toISOString().replace("T", " ").replace("Z", "");
339
351
  } else {
340
352
  encoded[key] = value;
341
353
  }
@@ -345,6 +357,14 @@ function encodeData(table2, data) {
345
357
  }
346
358
  return encoded;
347
359
  }
360
+ function assertNotView(table2, operation) {
361
+ const meta = getTableMeta(table2);
362
+ if (meta.isView) {
363
+ throw new Error(
364
+ `Cannot ${operation} on view "${table2.name}". Views are read-only. Use the base table "${meta.viewOf}" for mutations.`
365
+ );
366
+ }
367
+ }
348
368
  function mergeExpression(baseStrings, baseValues, expr) {
349
369
  baseStrings[baseStrings.length - 1] += expr.strings[0];
350
370
  for (let i = 1; i < expr.strings.length; i++) {
@@ -600,6 +620,7 @@ var Transaction = class {
600
620
  }
601
621
  all(tables) {
602
622
  const tableArray = Array.isArray(tables) ? tables : [tables];
623
+ const primaryTable = tableArray[0];
603
624
  return async (strings, ...values) => {
604
625
  const { strings: colStrings, values: colValues } = buildSelectCols(tableArray);
605
626
  const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
@@ -611,7 +632,7 @@ var Transaction = class {
611
632
  }
612
633
  queryValues.push(...colValues);
613
634
  queryStrings[queryStrings.length - 1] += " FROM ";
614
- queryValues.push(ident(tableArray[0].name));
635
+ queryValues.push(ident(primaryTable.name));
615
636
  queryStrings.push(" ");
616
637
  queryStrings[queryStrings.length - 1] += expandedStrings[0];
617
638
  for (let i = 1; i < expandedStrings.length; i++) {
@@ -622,7 +643,7 @@ var Transaction = class {
622
643
  makeTemplate(queryStrings),
623
644
  queryValues
624
645
  );
625
- return normalize(rows, tableArray);
646
+ return normalize(rows, tableArray, this.#driver);
626
647
  };
627
648
  }
628
649
  get(tables, id) {
@@ -638,10 +659,11 @@ var Transaction = class {
638
659
  return this.#driver.get(strings, values).then((row) => {
639
660
  if (!row)
640
661
  return null;
641
- return decodeData(table2, row);
662
+ return decodeData(table2, row, this.#driver);
642
663
  });
643
664
  }
644
665
  const tableArray = Array.isArray(tables) ? tables : [tables];
666
+ const primaryTable = tableArray[0];
645
667
  return async (strings, ...values) => {
646
668
  const { strings: colStrings, values: colValues } = buildSelectCols(tableArray);
647
669
  const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
@@ -653,7 +675,7 @@ var Transaction = class {
653
675
  }
654
676
  queryValues.push(...colValues);
655
677
  queryStrings[queryStrings.length - 1] += " FROM ";
656
- queryValues.push(ident(tableArray[0].name));
678
+ queryValues.push(ident(primaryTable.name));
657
679
  queryStrings.push(" ");
658
680
  queryStrings[queryStrings.length - 1] += expandedStrings[0];
659
681
  for (let i = 1; i < expandedStrings.length; i++) {
@@ -664,10 +686,15 @@ var Transaction = class {
664
686
  makeTemplate(queryStrings),
665
687
  queryValues
666
688
  );
667
- return normalizeOne(row, tableArray);
689
+ return normalizeOne(
690
+ row,
691
+ tableArray,
692
+ this.#driver
693
+ );
668
694
  };
669
695
  }
670
696
  async insert(table2, data) {
697
+ assertNotView(table2, "insert");
671
698
  if (Array.isArray(data)) {
672
699
  if (data.length === 0) {
673
700
  return [];
@@ -716,7 +743,7 @@ var Transaction = class {
716
743
  schema,
717
744
  regularData
718
745
  );
719
- const encoded = encodeData(table2, validated);
746
+ const encoded = encodeData(table2, validated, this.#driver);
720
747
  const insertParts = buildInsertParts(
721
748
  table2.name,
722
749
  encoded,
@@ -729,7 +756,7 @@ var Transaction = class {
729
756
  strings,
730
757
  values
731
758
  );
732
- return decodeData(table2, row);
759
+ return decodeData(table2, row, this.#driver);
733
760
  }
734
761
  await this.#driver.run(insertParts.strings, insertParts.values);
735
762
  const pk = table2.meta.primary;
@@ -741,12 +768,13 @@ var Transaction = class {
741
768
  values
742
769
  );
743
770
  if (row) {
744
- return decodeData(table2, row);
771
+ return decodeData(table2, row, this.#driver);
745
772
  }
746
773
  }
747
774
  return validated;
748
775
  }
749
776
  update(table2, data, idOrIds) {
777
+ assertNotView(table2, "update");
750
778
  if (idOrIds === void 0) {
751
779
  return async (strings, ...values) => {
752
780
  return this.#updateWithWhere(table2, data, strings, values);
@@ -789,7 +817,7 @@ var Transaction = class {
789
817
  if (allColumns.length === 0) {
790
818
  throw new Error("No fields to update");
791
819
  }
792
- const encoded = encodeData(table2, validated);
820
+ const encoded = encodeData(table2, validated, this.#driver);
793
821
  const updateParts = buildUpdateByIdParts(
794
822
  table2.name,
795
823
  pk,
@@ -806,7 +834,7 @@ var Transaction = class {
806
834
  );
807
835
  if (!row2)
808
836
  return null;
809
- return decodeData(table2, row2);
837
+ return decodeData(table2, row2, this.#driver);
810
838
  }
811
839
  await this.#driver.run(updateParts.strings, updateParts.values);
812
840
  const { strings: selectStrings, values: selectValues } = buildSelectByPkParts(
@@ -820,7 +848,7 @@ var Transaction = class {
820
848
  );
821
849
  if (!row)
822
850
  return null;
823
- return decodeData(table2, row);
851
+ return decodeData(table2, row, this.#driver);
824
852
  }
825
853
  async #updateByIds(table2, data, ids) {
826
854
  if (ids.length === 0) {
@@ -857,7 +885,7 @@ var Transaction = class {
857
885
  if (allColumns.length === 0) {
858
886
  throw new Error("No fields to update");
859
887
  }
860
- const encoded = encodeData(table2, validated);
888
+ const encoded = encodeData(table2, validated, this.#driver);
861
889
  const updateParts = buildUpdateByIdsParts(
862
890
  table2.name,
863
891
  pk,
@@ -874,7 +902,7 @@ var Transaction = class {
874
902
  );
875
903
  const resultMap2 = /* @__PURE__ */ new Map();
876
904
  for (const row of rows2) {
877
- const entity = decodeData(table2, row);
905
+ const entity = decodeData(table2, row, this.#driver);
878
906
  resultMap2.set(row[pk], entity);
879
907
  }
880
908
  return ids.map((id) => resultMap2.get(id) ?? null);
@@ -887,7 +915,7 @@ var Transaction = class {
887
915
  );
888
916
  const resultMap = /* @__PURE__ */ new Map();
889
917
  for (const row of rows) {
890
- const entity = decodeData(table2, row);
918
+ const entity = decodeData(table2, row, this.#driver);
891
919
  resultMap.set(row[pk], entity);
892
920
  }
893
921
  return ids.map((id) => resultMap.get(id) ?? null);
@@ -920,7 +948,7 @@ var Transaction = class {
920
948
  if (allColumns.length === 0) {
921
949
  throw new Error("No fields to update");
922
950
  }
923
- const encoded = encodeData(table2, validated);
951
+ const encoded = encodeData(table2, validated, this.#driver);
924
952
  const { strings: whereStrings, values: whereValues } = expandFragments(
925
953
  strings,
926
954
  templateValues
@@ -957,7 +985,7 @@ var Transaction = class {
957
985
  makeTemplate(queryStrings),
958
986
  queryValues
959
987
  );
960
- return rows2.map((row) => decodeData(table2, row));
988
+ return rows2.map((row) => decodeData(table2, row, this.#driver));
961
989
  }
962
990
  const selectIdStrings = ["SELECT "];
963
991
  const selectIdValues = [];
@@ -987,9 +1015,10 @@ var Transaction = class {
987
1015
  selectStrings,
988
1016
  selectVals
989
1017
  );
990
- return rows.map((row) => decodeData(table2, row));
1018
+ return rows.map((row) => decodeData(table2, row, this.#driver));
991
1019
  }
992
1020
  delete(table2, idOrIds) {
1021
+ assertNotView(table2, "delete");
993
1022
  if (idOrIds === void 0) {
994
1023
  return async (strings, ...values) => {
995
1024
  const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
@@ -1028,6 +1057,7 @@ var Transaction = class {
1028
1057
  return this.#driver.run(strings, values);
1029
1058
  }
1030
1059
  softDelete(table2, idOrIds) {
1060
+ assertNotView(table2, "softDelete");
1031
1061
  const softDeleteField = table2.meta.softDeleteField;
1032
1062
  if (!softDeleteField) {
1033
1063
  throw new Error(
@@ -1205,9 +1235,11 @@ var Database = class extends EventTarget {
1205
1235
  #driver;
1206
1236
  #version = 0;
1207
1237
  #opened = false;
1208
- constructor(driver) {
1238
+ #tables = [];
1239
+ constructor(driver, options) {
1209
1240
  super();
1210
1241
  this.#driver = driver;
1242
+ this.#tables = options?.tables ?? [];
1211
1243
  }
1212
1244
  /**
1213
1245
  * Current database schema version.
@@ -1284,6 +1316,7 @@ var Database = class extends EventTarget {
1284
1316
  }
1285
1317
  all(tables) {
1286
1318
  const tableArray = Array.isArray(tables) ? tables : [tables];
1319
+ const primaryTable = tableArray[0];
1287
1320
  return async (strings, ...values) => {
1288
1321
  const { strings: colStrings, values: colValues } = buildSelectCols(tableArray);
1289
1322
  const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
@@ -1295,7 +1328,7 @@ var Database = class extends EventTarget {
1295
1328
  }
1296
1329
  queryValues.push(...colValues);
1297
1330
  queryStrings[queryStrings.length - 1] += " FROM ";
1298
- queryValues.push(ident(tableArray[0].name));
1331
+ queryValues.push(ident(primaryTable.name));
1299
1332
  queryStrings.push(" ");
1300
1333
  queryStrings[queryStrings.length - 1] += expandedStrings[0];
1301
1334
  for (let i = 1; i < expandedStrings.length; i++) {
@@ -1306,7 +1339,7 @@ var Database = class extends EventTarget {
1306
1339
  makeTemplate(queryStrings),
1307
1340
  queryValues
1308
1341
  );
1309
- return normalize(rows, tableArray);
1342
+ return normalize(rows, tableArray, this.#driver);
1310
1343
  };
1311
1344
  }
1312
1345
  get(tables, id) {
@@ -1322,10 +1355,11 @@ var Database = class extends EventTarget {
1322
1355
  return this.#driver.get(strings, values).then((row) => {
1323
1356
  if (!row)
1324
1357
  return null;
1325
- return decodeData(table2, row);
1358
+ return decodeData(table2, row, this.#driver);
1326
1359
  });
1327
1360
  }
1328
1361
  const tableArray = Array.isArray(tables) ? tables : [tables];
1362
+ const primaryTable = tableArray[0];
1329
1363
  return async (strings, ...values) => {
1330
1364
  const { strings: colStrings, values: colValues } = buildSelectCols(tableArray);
1331
1365
  const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
@@ -1337,7 +1371,7 @@ var Database = class extends EventTarget {
1337
1371
  }
1338
1372
  queryValues.push(...colValues);
1339
1373
  queryStrings[queryStrings.length - 1] += " FROM ";
1340
- queryValues.push(ident(tableArray[0].name));
1374
+ queryValues.push(ident(primaryTable.name));
1341
1375
  queryStrings.push(" ");
1342
1376
  queryStrings[queryStrings.length - 1] += expandedStrings[0];
1343
1377
  for (let i = 1; i < expandedStrings.length; i++) {
@@ -1348,10 +1382,15 @@ var Database = class extends EventTarget {
1348
1382
  makeTemplate(queryStrings),
1349
1383
  queryValues
1350
1384
  );
1351
- return normalizeOne(row, tableArray);
1385
+ return normalizeOne(
1386
+ row,
1387
+ tableArray,
1388
+ this.#driver
1389
+ );
1352
1390
  };
1353
1391
  }
1354
1392
  async insert(table2, data) {
1393
+ assertNotView(table2, "insert");
1355
1394
  if (Array.isArray(data)) {
1356
1395
  if (data.length === 0) {
1357
1396
  return [];
@@ -1400,7 +1439,7 @@ var Database = class extends EventTarget {
1400
1439
  schema,
1401
1440
  regularData
1402
1441
  );
1403
- const encoded = encodeData(table2, validated);
1442
+ const encoded = encodeData(table2, validated, this.#driver);
1404
1443
  const insertParts = buildInsertParts(
1405
1444
  table2.name,
1406
1445
  encoded,
@@ -1413,7 +1452,7 @@ var Database = class extends EventTarget {
1413
1452
  strings,
1414
1453
  values
1415
1454
  );
1416
- return decodeData(table2, row);
1455
+ return decodeData(table2, row, this.#driver);
1417
1456
  }
1418
1457
  await this.#driver.run(insertParts.strings, insertParts.values);
1419
1458
  const pk = table2.meta.primary;
@@ -1425,12 +1464,13 @@ var Database = class extends EventTarget {
1425
1464
  values
1426
1465
  );
1427
1466
  if (row) {
1428
- return decodeData(table2, row);
1467
+ return decodeData(table2, row, this.#driver);
1429
1468
  }
1430
1469
  }
1431
1470
  return validated;
1432
1471
  }
1433
1472
  update(table2, data, idOrIds) {
1473
+ assertNotView(table2, "update");
1434
1474
  if (idOrIds === void 0) {
1435
1475
  return async (strings, ...values) => {
1436
1476
  return this.#updateWithWhere(table2, data, strings, values);
@@ -1473,7 +1513,7 @@ var Database = class extends EventTarget {
1473
1513
  if (allColumns.length === 0) {
1474
1514
  throw new Error("No fields to update");
1475
1515
  }
1476
- const encoded = encodeData(table2, validated);
1516
+ const encoded = encodeData(table2, validated, this.#driver);
1477
1517
  const updateParts = buildUpdateByIdParts(
1478
1518
  table2.name,
1479
1519
  pk,
@@ -1490,7 +1530,7 @@ var Database = class extends EventTarget {
1490
1530
  );
1491
1531
  if (!row2)
1492
1532
  return null;
1493
- return decodeData(table2, row2);
1533
+ return decodeData(table2, row2, this.#driver);
1494
1534
  }
1495
1535
  await this.#driver.run(updateParts.strings, updateParts.values);
1496
1536
  const { strings: selectStrings, values: selectValues } = buildSelectByPkParts(
@@ -1504,7 +1544,7 @@ var Database = class extends EventTarget {
1504
1544
  );
1505
1545
  if (!row)
1506
1546
  return null;
1507
- return decodeData(table2, row);
1547
+ return decodeData(table2, row, this.#driver);
1508
1548
  }
1509
1549
  async #updateByIds(table2, data, ids) {
1510
1550
  if (ids.length === 0) {
@@ -1541,7 +1581,7 @@ var Database = class extends EventTarget {
1541
1581
  if (allColumns.length === 0) {
1542
1582
  throw new Error("No fields to update");
1543
1583
  }
1544
- const encoded = encodeData(table2, validated);
1584
+ const encoded = encodeData(table2, validated, this.#driver);
1545
1585
  const updateParts = buildUpdateByIdsParts(
1546
1586
  table2.name,
1547
1587
  pk,
@@ -1558,7 +1598,7 @@ var Database = class extends EventTarget {
1558
1598
  );
1559
1599
  const resultMap2 = /* @__PURE__ */ new Map();
1560
1600
  for (const row of rows2) {
1561
- const entity = decodeData(table2, row);
1601
+ const entity = decodeData(table2, row, this.#driver);
1562
1602
  resultMap2.set(row[pk], entity);
1563
1603
  }
1564
1604
  return ids.map((id) => resultMap2.get(id) ?? null);
@@ -1571,7 +1611,7 @@ var Database = class extends EventTarget {
1571
1611
  );
1572
1612
  const resultMap = /* @__PURE__ */ new Map();
1573
1613
  for (const row of rows) {
1574
- const entity = decodeData(table2, row);
1614
+ const entity = decodeData(table2, row, this.#driver);
1575
1615
  resultMap.set(row[pk], entity);
1576
1616
  }
1577
1617
  return ids.map((id) => resultMap.get(id) ?? null);
@@ -1604,7 +1644,7 @@ var Database = class extends EventTarget {
1604
1644
  if (allColumns.length === 0) {
1605
1645
  throw new Error("No fields to update");
1606
1646
  }
1607
- const encoded = encodeData(table2, validated);
1647
+ const encoded = encodeData(table2, validated, this.#driver);
1608
1648
  const { strings: whereStrings, values: whereValues } = expandFragments(
1609
1649
  strings,
1610
1650
  templateValues
@@ -1641,7 +1681,7 @@ var Database = class extends EventTarget {
1641
1681
  makeTemplate(queryStrings),
1642
1682
  queryValues
1643
1683
  );
1644
- return rows2.map((row) => decodeData(table2, row));
1684
+ return rows2.map((row) => decodeData(table2, row, this.#driver));
1645
1685
  }
1646
1686
  const selectIdStrings = ["SELECT "];
1647
1687
  const selectIdValues = [];
@@ -1671,9 +1711,10 @@ var Database = class extends EventTarget {
1671
1711
  selectStrings,
1672
1712
  selectVals
1673
1713
  );
1674
- return rows.map((row) => decodeData(table2, row));
1714
+ return rows.map((row) => decodeData(table2, row, this.#driver));
1675
1715
  }
1676
1716
  delete(table2, idOrIds) {
1717
+ assertNotView(table2, "delete");
1677
1718
  if (idOrIds === void 0) {
1678
1719
  return async (strings, ...values) => {
1679
1720
  const { strings: expandedStrings, values: expandedValues } = expandFragments(strings, values);
@@ -1712,6 +1753,7 @@ var Database = class extends EventTarget {
1712
1753
  return this.#driver.run(strings, values);
1713
1754
  }
1714
1755
  softDelete(table2, idOrIds) {
1756
+ assertNotView(table2, "softDelete");
1715
1757
  const softDeleteField = table2.meta.softDeleteField;
1716
1758
  if (!softDeleteField) {
1717
1759
  throw new Error(
@@ -1762,7 +1804,49 @@ var Database = class extends EventTarget {
1762
1804
  queryStrings.push(" = ");
1763
1805
  queryValues.push(id);
1764
1806
  queryStrings.push("");
1765
- return this.#driver.run(makeTemplate(queryStrings), queryValues);
1807
+ const count = await this.#driver.run(
1808
+ makeTemplate(queryStrings),
1809
+ queryValues
1810
+ );
1811
+ if (count > 0) {
1812
+ await this.#cascadeSoftDelete(table2, [id]);
1813
+ }
1814
+ return count;
1815
+ }
1816
+ /**
1817
+ * Cascade soft delete to tables that reference the given table with onDelete: "cascade".
1818
+ * Only cascades to tables that have a soft delete field.
1819
+ */
1820
+ async #cascadeSoftDelete(table2, ids) {
1821
+ if (ids.length === 0 || this.#tables.length === 0)
1822
+ return;
1823
+ for (const refTable of this.#tables) {
1824
+ const refs = refTable.references();
1825
+ for (const ref of refs) {
1826
+ if (ref.table.name === table2.name && ref.onDelete === "cascade") {
1827
+ if (!refTable.meta.softDeleteField)
1828
+ continue;
1829
+ const fkField = ref.fieldName;
1830
+ const refPk = refTable.meta.primary;
1831
+ if (!refPk)
1832
+ continue;
1833
+ const whereIn = refTable.in(fkField, ids);
1834
+ const selectTemplate = createTemplate(
1835
+ makeTemplate(["SELECT ", " FROM ", " WHERE ", ""]),
1836
+ [ident(refPk), ident(refTable.name), whereIn]
1837
+ );
1838
+ const { strings: selectStrings, values: selectValues } = expandFragments(selectTemplate[0], selectTemplate.slice(1));
1839
+ const rows = await this.#driver.all(
1840
+ selectStrings,
1841
+ selectValues
1842
+ );
1843
+ if (rows.length > 0) {
1844
+ const cascadeIds = rows.map((row) => row[refPk]);
1845
+ await this.#softDeleteByIds(refTable, cascadeIds);
1846
+ }
1847
+ }
1848
+ }
1849
+ }
1766
1850
  }
1767
1851
  async #softDeleteByIds(table2, ids) {
1768
1852
  if (ids.length === 0) {
@@ -1803,16 +1887,39 @@ var Database = class extends EventTarget {
1803
1887
  queryValues.push(ids[i]);
1804
1888
  queryStrings.push(i < ids.length - 1 ? ", " : ")");
1805
1889
  }
1806
- return this.#driver.run(makeTemplate(queryStrings), queryValues);
1890
+ const count = await this.#driver.run(
1891
+ makeTemplate(queryStrings),
1892
+ queryValues
1893
+ );
1894
+ if (count > 0) {
1895
+ await this.#cascadeSoftDelete(table2, ids);
1896
+ }
1897
+ return count;
1807
1898
  }
1808
1899
  async #softDeleteWithWhere(table2, strings, templateValues) {
1809
1900
  const softDeleteField = table2.meta.softDeleteField;
1901
+ const pk = table2.meta.primary;
1810
1902
  const schemaExprs = injectSchemaExpressions(table2, {}, "update");
1811
1903
  const { expressions, symbols } = extractDBExpressions(schemaExprs);
1812
1904
  const { strings: expandedStrings, values: expandedValues } = expandFragments(
1813
1905
  strings,
1814
1906
  templateValues
1815
1907
  );
1908
+ let affectedIds = [];
1909
+ if (this.#tables.length > 0 && pk) {
1910
+ const selectStrings = ["SELECT ", " FROM ", " "];
1911
+ const selectValues = [ident(pk), ident(table2.name)];
1912
+ selectStrings[selectStrings.length - 1] += expandedStrings[0];
1913
+ for (let i = 1; i < expandedStrings.length; i++) {
1914
+ selectStrings.push(expandedStrings[i]);
1915
+ }
1916
+ selectValues.push(...expandedValues);
1917
+ const rows = await this.#driver.all(
1918
+ makeTemplate(selectStrings),
1919
+ selectValues
1920
+ );
1921
+ affectedIds = rows.map((row) => row[pk]);
1922
+ }
1816
1923
  const queryStrings = ["UPDATE "];
1817
1924
  const queryValues = [];
1818
1925
  queryValues.push(ident(table2.name));
@@ -1839,7 +1946,14 @@ var Database = class extends EventTarget {
1839
1946
  queryStrings.push(expandedStrings[i]);
1840
1947
  }
1841
1948
  queryValues.push(...expandedValues);
1842
- return this.#driver.run(makeTemplate(queryStrings), queryValues);
1949
+ const count = await this.#driver.run(
1950
+ makeTemplate(queryStrings),
1951
+ queryValues
1952
+ );
1953
+ if (count > 0 && affectedIds.length > 0) {
1954
+ await this.#cascadeSoftDelete(table2, affectedIds);
1955
+ }
1956
+ return count;
1843
1957
  }
1844
1958
  // ==========================================================================
1845
1959
  // Raw - No Normalization
@@ -1930,6 +2044,7 @@ var Database = class extends EventTarget {
1930
2044
  * await db.ensureTable(Posts); // FK to Users - ensure Users first
1931
2045
  */
1932
2046
  async ensureTable(table2) {
2047
+ assertNotView(table2, "ensureTable");
1933
2048
  if (!this.#driver.ensureTable) {
1934
2049
  throw new Error(
1935
2050
  "Driver does not implement ensureTable(). Schema ensure methods require a driver with schema management support."
@@ -1941,6 +2056,29 @@ var Database = class extends EventTarget {
1941
2056
  }
1942
2057
  return await doEnsure();
1943
2058
  }
2059
+ /**
2060
+ * Ensure a view exists in the database.
2061
+ *
2062
+ * Creates the view if it doesn't exist, or replaces it if it does.
2063
+ * The base table must already exist.
2064
+ *
2065
+ * @example
2066
+ * const ActiveUsers = view("active_users", Users)`WHERE ${Users.cols.deletedAt} IS NULL`;
2067
+ * await db.ensureTable(Users);
2068
+ * await db.ensureView(ActiveUsers);
2069
+ */
2070
+ async ensureView(viewObj) {
2071
+ if (!this.#driver.ensureView) {
2072
+ throw new Error(
2073
+ "Driver does not implement ensureView(). Schema ensure methods require a driver with schema management support."
2074
+ );
2075
+ }
2076
+ const doEnsure = () => this.#driver.ensureView(viewObj);
2077
+ if (this.#driver.withMigrationLock) {
2078
+ return await this.#driver.withMigrationLock(doEnsure);
2079
+ }
2080
+ return await doEnsure();
2081
+ }
1944
2082
  /**
1945
2083
  * Ensure constraints (unique, foreign key) are applied to an existing table.
1946
2084
  *
@@ -1963,6 +2101,7 @@ var Database = class extends EventTarget {
1963
2101
  * await db.ensureConstraints(Users);
1964
2102
  */
1965
2103
  async ensureConstraints(table2) {
2104
+ assertNotView(table2, "ensureConstraints");
1966
2105
  if (!this.#driver.ensureConstraints) {
1967
2106
  throw new Error(
1968
2107
  "Driver does not implement ensureConstraints(). Schema ensure methods require a driver with schema management support."
@@ -2132,12 +2271,17 @@ export {
2132
2271
  Transaction,
2133
2272
  TransactionError,
2134
2273
  ValidationError,
2274
+ extendZod,
2275
+ getViewMeta,
2135
2276
  hasErrorCode,
2136
2277
  ident,
2137
2278
  isDatabaseError,
2138
2279
  isSQLBuiltin,
2139
2280
  isSQLIdentifier,
2140
2281
  isSQLTemplate,
2282
+ isTable,
2283
+ isView,
2141
2284
  table,
2285
+ view,
2142
2286
  zod as z
2143
2287
  };