@b9g/zen 0.1.0 → 0.1.1

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