tempest-db-js 0.7.0 → 0.9.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.
@@ -1,4 +1,4 @@
1
- import { renderPortableToken, columnNamesOf, columnsOf, toAsyncDriver } from './chunk-4AWUP7BM.js';
1
+ import { renderPortableToken, columnNamesOf, columnsOf, toAsyncDriver, LiteralParams, getDialect } from './chunk-HXK6WIBP.js';
2
2
 
3
3
  // src/migrations/operations.ts
4
4
  var IrreversibleMigration = class extends Error {
@@ -33,6 +33,10 @@ function invert(op) {
33
33
  return { kind: "recreate_table", from: op.to, to: op.from };
34
34
  case "add_constraint":
35
35
  return { kind: "drop_constraint", table: op.table, constraint: op.constraint };
36
+ case "create_index":
37
+ return { kind: "drop_index", table: op.table, index: op.index };
38
+ case "drop_index":
39
+ return { kind: "create_index", table: op.table, index: op.index };
36
40
  case "drop_constraint":
37
41
  return { kind: "add_constraint", table: op.table, constraint: op.constraint };
38
42
  case "execute":
@@ -223,7 +227,8 @@ function renderDefault(def, dialect, type) {
223
227
  "sql.expr`...` binds parameters and cannot be rendered as a DEFAULT \u2014 use sql.raw()."
224
228
  );
225
229
  }
226
- return renderPortableToken(expr, dialect);
230
+ const rendered = renderPortableToken(expr, dialect);
231
+ return dialect === "sqlite" && rendered.includes("(") ? `(${rendered})` : rendered;
227
232
  }
228
233
  const value = def.value;
229
234
  if (value === null) return "NULL";
@@ -271,9 +276,31 @@ function renderForeignKeyConstraint(fk, dialect) {
271
276
  function tableConstraintClauses(table, dialect) {
272
277
  return [
273
278
  ...table.uniqueConstraints.map((uc) => renderUniqueConstraint(uc, dialect)),
274
- ...table.foreignKeys.map((fk) => renderForeignKeyConstraint(fk, dialect))
279
+ ...table.foreignKeys.map((fk) => renderForeignKeyConstraint(fk, dialect)),
280
+ ...table.checks.map((ck) => renderCheckConstraint(ck, dialect))
275
281
  ];
276
282
  }
283
+ function renderPredicate(node, dialect) {
284
+ const literals = new LiteralParams();
285
+ return getDialect(dialect).renderConditionLiteral(node, literals);
286
+ }
287
+ function renderCheckConstraint(ck, dialect) {
288
+ return `CONSTRAINT ${quoteId(ck.name, dialect)} CHECK (${renderPredicate(ck.expression, dialect)})`;
289
+ }
290
+ function renderCreateIndex(table, ix, dialect) {
291
+ if (ix.where && dialect === "mysql") {
292
+ throw new Error(
293
+ `MySQL has no partial index; ${ix.name} on ${table} declares a WHERE predicate.`
294
+ );
295
+ }
296
+ const unique = ix.unique ? "UNIQUE " : "";
297
+ const cols = ix.columns.map((c) => quoteId(c, dialect)).join(", ");
298
+ const where = ix.where ? ` WHERE ${renderPredicate(ix.where, dialect)}` : "";
299
+ return `CREATE ${unique}INDEX ${quoteId(ix.name, dialect)} ON ${quoteId(table, dialect)} (${cols})${where}`;
300
+ }
301
+ function renderDropIndex(table, ix, dialect) {
302
+ return dialect === "mysql" ? `DROP INDEX ${quoteId(ix.name, dialect)} ON ${quoteId(table, dialect)}` : `DROP INDEX ${quoteId(ix.name, dialect)}`;
303
+ }
277
304
  function renderColumnDef(col, dialect) {
278
305
  let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
279
306
  if (col.notNull) sql += " NOT NULL";
@@ -326,7 +353,10 @@ function renderCreateTable(table, dialect) {
326
353
  ...typeStmts,
327
354
  `CREATE TABLE ${quoteId(table.name, dialect)} (
328
355
  ${cols.join(",\n ")}
329
- )`
356
+ )`,
357
+ // Indexes are separate statements, not table clauses — which is also why a
358
+ // SQLite table rebuild has to recreate them.
359
+ ...table.indexes.map((ix) => renderCreateIndex(table.name, ix, dialect))
330
360
  ];
331
361
  }
332
362
  function renderOperation(op, dialect) {
@@ -359,6 +389,10 @@ function renderOperation(op, dialect) {
359
389
  return renderAddConstraint(op.table, op.constraint, dialect);
360
390
  case "drop_constraint":
361
391
  return renderDropConstraint(op.table, op.constraint, dialect);
392
+ case "create_index":
393
+ return [renderCreateIndex(op.table, op.index, dialect)];
394
+ case "drop_index":
395
+ return [renderDropIndex(op.table, op.index, dialect)];
362
396
  case "execute":
363
397
  return [op.up];
364
398
  }
@@ -369,7 +403,7 @@ function renderAddConstraint(table, constraint, dialect) {
369
403
  `add_constraint on SQLite needs a table-rebuild (recreate_table); constraint ${constraint.constraint.name} on ${table}`
370
404
  );
371
405
  }
372
- const clause = constraint.type === "unique" ? renderUniqueConstraint(constraint.constraint, dialect) : renderForeignKeyConstraint(constraint.constraint, dialect);
406
+ const clause = constraint.type === "unique" ? renderUniqueConstraint(constraint.constraint, dialect) : constraint.type === "check" ? renderCheckConstraint(constraint.constraint, dialect) : renderForeignKeyConstraint(constraint.constraint, dialect);
373
407
  return [`ALTER TABLE ${quoteId(table, dialect)} ADD ${clause}`];
374
408
  }
375
409
  function renderDropConstraint(table, constraint, dialect) {
@@ -406,6 +440,10 @@ function renderSqliteRebuild(from, to) {
406
440
  common.length > 0 ? `INSERT INTO ${quoteId(tmp, "sqlite")} (${commonSql}) SELECT ${commonSql} FROM ${quoteId(from.name, "sqlite")}` : `-- no common columns to copy from ${from.name}`,
407
441
  `DROP TABLE ${quoteId(from.name, "sqlite")}`,
408
442
  `ALTER TABLE ${quoteId(tmp, "sqlite")} RENAME TO ${quoteId(to.name, "sqlite")}`,
443
+ // The rebuild dropped the old table, and its indexes with it: SQLite ties an
444
+ // index to the table it was created on. Recreating them is part of the
445
+ // rebuild, not a follow-up somebody has to remember.
446
+ ...to.indexes.map((ix) => renderCreateIndex(to.name, ix, "sqlite")),
409
447
  "PRAGMA foreign_keys=on"
410
448
  ];
411
449
  }
@@ -508,8 +546,49 @@ function diffConstraints(current, target) {
508
546
  ops.push(addForeignKey(table, tgt));
509
547
  }
510
548
  }
549
+ const currentCk = new Map(current.checks.map((c) => [c.name, c]));
550
+ const targetCk = new Map(target.checks.map((c) => [c.name, c]));
551
+ for (const [name, cur] of currentCk) {
552
+ const tgt = targetCk.get(name);
553
+ if (!tgt || checkSignature(cur) !== checkSignature(tgt)) {
554
+ ops.push({ kind: "drop_constraint", table, constraint: checkNamed(cur) });
555
+ }
556
+ }
557
+ for (const [name, tgt] of targetCk) {
558
+ const cur = currentCk.get(name);
559
+ if (!cur || checkSignature(cur) !== checkSignature(tgt)) {
560
+ ops.push({ kind: "add_constraint", table, constraint: checkNamed(tgt) });
561
+ }
562
+ }
563
+ const currentIx = new Map(current.indexes.map((i) => [i.name, i]));
564
+ const targetIx = new Map(target.indexes.map((i) => [i.name, i]));
565
+ for (const [name, cur] of currentIx) {
566
+ const tgt = targetIx.get(name);
567
+ if (!tgt || indexSignature(cur) !== indexSignature(tgt)) {
568
+ ops.push({ kind: "drop_index", table, index: cur });
569
+ }
570
+ }
571
+ for (const [name, tgt] of targetIx) {
572
+ const cur = currentIx.get(name);
573
+ if (!cur || indexSignature(cur) !== indexSignature(tgt)) {
574
+ ops.push({ kind: "create_index", table, index: tgt });
575
+ }
576
+ }
511
577
  return ops;
512
578
  }
579
+ function checkSignature(ck) {
580
+ return JSON.stringify(ck.expression);
581
+ }
582
+ function indexSignature(ix) {
583
+ return JSON.stringify({
584
+ columns: ix.columns,
585
+ unique: ix.unique,
586
+ where: ix.where
587
+ });
588
+ }
589
+ function checkNamed(ck) {
590
+ return { type: "check", constraint: ck };
591
+ }
513
592
  function uniqueNamed(uc) {
514
593
  return { type: "unique", constraint: uc };
515
594
  }
@@ -624,6 +703,41 @@ function heads(migrations) {
624
703
  function constraintName(prefix, table, columns) {
625
704
  return `${prefix}_${table}_${columns.join("_")}`;
626
705
  }
706
+ function renameConditionColumns(node, toColumn) {
707
+ const expr = (current) => {
708
+ switch (current.kind) {
709
+ case "column":
710
+ return { kind: "column", name: toColumn(current.name) };
711
+ case "fn":
712
+ return { ...current, args: current.args.map(expr) };
713
+ case "cast":
714
+ return { ...current, operand: expr(current.operand) };
715
+ default:
716
+ return current;
717
+ }
718
+ };
719
+ switch (node.kind) {
720
+ case "fields": {
721
+ const fields = {};
722
+ for (const [key, value] of Object.entries(node.fields)) {
723
+ fields[toColumn(key)] = value;
724
+ }
725
+ return { kind: "fields", fields };
726
+ }
727
+ case "and":
728
+ case "or":
729
+ return {
730
+ ...node,
731
+ parts: node.parts.map((part) => renameConditionColumns(part, toColumn))
732
+ };
733
+ case "not":
734
+ return { kind: "not", part: renameConditionColumns(node.part, toColumn) };
735
+ case "compare":
736
+ return { ...node, left: expr(node.left), right: expr(node.right) };
737
+ default:
738
+ return node;
739
+ }
740
+ }
627
741
  function reflectTable(model) {
628
742
  const names = columnNamesOf(model);
629
743
  const toColumn = (prop) => names?.[prop] ?? prop;
@@ -645,6 +759,8 @@ function reflectTable(model) {
645
759
  }
646
760
  const uniqueConstraints = [];
647
761
  const foreignKeys = [];
762
+ const checks = [];
763
+ const indexes = [];
648
764
  for (const c of model.tableArgs?.() ?? []) {
649
765
  const cols = c.columns.map(toColumn);
650
766
  if (c.kind === "unique") {
@@ -652,6 +768,18 @@ function reflectTable(model) {
652
768
  name: c.name ?? constraintName("uq", model.tablename, cols),
653
769
  columns: cols
654
770
  });
771
+ } else if (c.kind === "check") {
772
+ checks.push({
773
+ name: c.name ?? constraintName("ck", model.tablename, cols),
774
+ expression: renameConditionColumns(c.expression, toColumn)
775
+ });
776
+ } else if (c.kind === "index") {
777
+ indexes.push({
778
+ name: c.name ?? constraintName("ix", model.tablename, cols),
779
+ columns: cols,
780
+ unique: c.unique === true,
781
+ where: c.where ? renameConditionColumns(c.where, toColumn) : null
782
+ });
655
783
  } else {
656
784
  foreignKeys.push({
657
785
  name: c.name ?? constraintName("fk", model.tablename, cols),
@@ -663,7 +791,15 @@ function reflectTable(model) {
663
791
  });
664
792
  }
665
793
  }
666
- return { name: model.tablename, columns, primaryKey, uniqueConstraints, foreignKeys };
794
+ return {
795
+ name: model.tablename,
796
+ columns,
797
+ primaryKey,
798
+ uniqueConstraints,
799
+ foreignKeys,
800
+ checks,
801
+ indexes
802
+ };
667
803
  }
668
804
  function reflectSchema(models) {
669
805
  const tables = {};
@@ -727,6 +863,8 @@ function introspectSqlite(driver) {
727
863
  columns,
728
864
  primaryKey,
729
865
  uniqueConstraints: sqliteUniques(driver, tableName),
866
+ checks: [],
867
+ indexes: sqliteIndexes(driver, tableName),
730
868
  foreignKeys: sqliteForeignKeys(driver, tableName)
731
869
  };
732
870
  }
@@ -815,6 +953,7 @@ function compareSqliteSchemas(actual, expected) {
815
953
  );
816
954
  }
817
955
  }
956
+ issues.push(...indexDrift(tableName, actualTable, expectedTable));
818
957
  }
819
958
  for (const tableName of Object.keys(actual.tables)) {
820
959
  if (!expected.tables[tableName]) {
@@ -865,9 +1004,58 @@ function sqliteUniqueFromPragma(table, rows) {
865
1004
  if (cols.length === 0) return null;
866
1005
  return { name: `uq_${table}_${cols.join("_")}`, columns: cols };
867
1006
  }
1007
+ function indexesFromPragma(rows, columnsOfIndex) {
1008
+ const indexes = [];
1009
+ for (const idx of rows) {
1010
+ if (idx.origin !== "c") continue;
1011
+ if (idx.partial === 1) continue;
1012
+ const cols = [...columnsOfIndex(idx.name)].sort((a, b) => a.seqno - b.seqno).map((c) => c.name);
1013
+ if (cols.length === 0) continue;
1014
+ indexes.push({
1015
+ name: idx.name,
1016
+ columns: cols,
1017
+ unique: Number(idx.unique) === 1,
1018
+ where: null
1019
+ });
1020
+ }
1021
+ return indexes;
1022
+ }
1023
+ function sqliteIndexes(driver, table) {
1024
+ const rows = driver.execute(`PRAGMA index_list(${JSON.stringify(table)})`, []).rows;
1025
+ return indexesFromPragma(
1026
+ rows,
1027
+ (name) => driver.execute(`PRAGMA index_info(${JSON.stringify(name)})`, []).rows
1028
+ );
1029
+ }
868
1030
  function isUniqueIndex(idx) {
869
1031
  return Number(idx.unique) === 1 && idx.origin !== "pk";
870
1032
  }
1033
+ function indexDrift(tableName, actual, expected) {
1034
+ const issues = [];
1035
+ const signature = (ix) => `${ix.columns.join(",")}${ix.unique ? " unique" : ""}`;
1036
+ const actualByName = new Map(actual.indexes.map((ix) => [ix.name, ix]));
1037
+ const expectedByName = new Map(expected.indexes.map((ix) => [ix.name, ix]));
1038
+ for (const [name, exp] of expectedByName) {
1039
+ const act = actualByName.get(name);
1040
+ if (!act) {
1041
+ issues.push(`index "${tableName}.${name}" is missing from the database`);
1042
+ continue;
1043
+ }
1044
+ if (signature(act) !== signature(exp)) {
1045
+ issues.push(
1046
+ `index "${tableName}.${name}" differs: model (${signature(exp)}), db (${signature(act)})`
1047
+ );
1048
+ }
1049
+ }
1050
+ for (const name of actualByName.keys()) {
1051
+ if (!expectedByName.has(name)) {
1052
+ issues.push(
1053
+ `index "${tableName}.${name}" exists in the database but not in the model`
1054
+ );
1055
+ }
1056
+ }
1057
+ return issues;
1058
+ }
871
1059
  var SQLITE_TABLES_SQL = "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name != 'tempest_db_js_migrations'";
872
1060
  async function introspectSqliteAsync(driver) {
873
1061
  const tablesRows = (await driver.execute(SQLITE_TABLES_SQL, [])).rows;
@@ -885,12 +1073,27 @@ async function introspectSqliteAsync(driver) {
885
1073
  const unique = sqliteUniqueFromPragma(tableName, cols);
886
1074
  if (unique) uniqueConstraints.push(unique);
887
1075
  }
1076
+ const explicitIndexes = [];
1077
+ for (const idx of indexes) {
1078
+ if (idx.origin !== "c" || idx.partial === 1) continue;
1079
+ const cols = (await driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, [])).rows;
1080
+ const sorted = [...cols].sort((a, b) => a.seqno - b.seqno).map((c) => c.name);
1081
+ if (sorted.length === 0) continue;
1082
+ explicitIndexes.push({
1083
+ name: idx.name,
1084
+ columns: sorted,
1085
+ unique: Number(idx.unique) === 1,
1086
+ where: null
1087
+ });
1088
+ }
888
1089
  tables[tableName] = {
889
1090
  name: tableName,
890
1091
  columns,
891
1092
  primaryKey,
892
1093
  uniqueConstraints,
893
- foreignKeys: sqliteForeignKeysFromPragma(tableName, fkRows)
1094
+ foreignKeys: sqliteForeignKeysFromPragma(tableName, fkRows),
1095
+ checks: [],
1096
+ indexes: explicitIndexes
894
1097
  };
895
1098
  }
896
1099
  return { tables };
@@ -1016,6 +1219,8 @@ async function introspectPostgres(driver) {
1016
1219
  columns,
1017
1220
  primaryKey,
1018
1221
  uniqueConstraints: await postgresUniques(driver, tableName),
1222
+ checks: [],
1223
+ indexes: await postgresIndexes(driver, tableName),
1019
1224
  foreignKeys: await postgresForeignKeys(driver, tableName)
1020
1225
  };
1021
1226
  }
@@ -1058,6 +1263,29 @@ async function postgresUniques(driver, table) {
1058
1263
  columns: r.cols ?? []
1059
1264
  }));
1060
1265
  }
1266
+ async function postgresIndexes(driver, table) {
1267
+ const { rows } = await driver.execute(
1268
+ `SELECT i.relname AS name,
1269
+ ix.indisunique AS is_unique,
1270
+ ix.indpred IS NOT NULL AS is_partial,
1271
+ array_to_string(array_agg(a.attname ORDER BY k.ord), ',') AS columns
1272
+ FROM pg_index ix
1273
+ JOIN pg_class i ON i.oid = ix.indexrelid
1274
+ JOIN pg_class t ON t.oid = ix.indrelid
1275
+ JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON true
1276
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
1277
+ WHERE t.relname = $1
1278
+ AND NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conindid = ix.indexrelid)
1279
+ GROUP BY i.relname, ix.indisunique, ix.indpred`,
1280
+ [table]
1281
+ );
1282
+ return rows.filter((row) => !row.is_partial).map((row) => ({
1283
+ name: row.name,
1284
+ columns: row.columns.split(","),
1285
+ unique: row.is_unique === true,
1286
+ where: null
1287
+ }));
1288
+ }
1061
1289
  function describeKind(type) {
1062
1290
  if (type.kind !== "array") return type.kind;
1063
1291
  return `${type.meta.element ? describeKind(type.meta.element) : "unknown"}[]`;
@@ -1108,6 +1336,7 @@ async function checkDriftPostgres(driver, models) {
1108
1336
  );
1109
1337
  }
1110
1338
  }
1339
+ issues.push(...indexDrift(tableName, actualTable, expectedTable));
1111
1340
  }
1112
1341
  for (const tableName of Object.keys(actual.tables)) {
1113
1342
  if (!expected.tables[tableName]) {
@@ -1526,7 +1755,22 @@ function applyOperation(schema, op) {
1526
1755
  tables[op.table] = op.constraint.type === "unique" ? {
1527
1756
  ...t,
1528
1757
  uniqueConstraints: [...t.uniqueConstraints, op.constraint.constraint]
1529
- } : { ...t, foreignKeys: [...t.foreignKeys, op.constraint.constraint] };
1758
+ } : op.constraint.type === "check" ? { ...t, checks: [...t.checks, op.constraint.constraint] } : { ...t, foreignKeys: [...t.foreignKeys, op.constraint.constraint] };
1759
+ }
1760
+ break;
1761
+ }
1762
+ case "create_index": {
1763
+ const t = tables[op.table];
1764
+ if (t) tables[op.table] = { ...t, indexes: [...t.indexes, op.index] };
1765
+ break;
1766
+ }
1767
+ case "drop_index": {
1768
+ const t = tables[op.table];
1769
+ if (t) {
1770
+ tables[op.table] = {
1771
+ ...t,
1772
+ indexes: t.indexes.filter((i) => i.name !== op.index.name)
1773
+ };
1530
1774
  }
1531
1775
  break;
1532
1776
  }
@@ -1537,7 +1781,7 @@ function applyOperation(schema, op) {
1537
1781
  tables[op.table] = op.constraint.type === "unique" ? {
1538
1782
  ...t,
1539
1783
  uniqueConstraints: t.uniqueConstraints.filter((u) => u.name !== dropName)
1540
- } : {
1784
+ } : op.constraint.type === "check" ? { ...t, checks: t.checks.filter((c) => c.name !== dropName) } : {
1541
1785
  ...t,
1542
1786
  foreignKeys: t.foreignKeys.filter((f) => f.name !== dropName)
1543
1787
  };
@@ -1682,5 +1926,5 @@ async function runMigrationCli(argv, config) {
1682
1926
  }
1683
1927
 
1684
1928
  export { AsyncMigrationRunner, CyclicMigrationGraph, IrreversibleMigration, MigrationRunner, Op, UnknownRevision, applyOperation, applyRenames, checkDrift, checkDriftAsync, checkDriftPostgres, compareSqliteSchemas, defineMigrationConfig, detectRenames, diffSchema, emptySchema, generateMigration, heads, introspectPostgres, introspectSqlite, introspectSqliteAsync, invert, invertAll, makeRevisionId, reflectSchema, reflectTable, renderColumnDef, renderColumnType, renderDefault, renderOperation, replaySchema, runMigrationCli, sqliteAffinity, topoOrder };
1685
- //# sourceMappingURL=chunk-SI4CLSF7.js.map
1686
- //# sourceMappingURL=chunk-SI4CLSF7.js.map
1929
+ //# sourceMappingURL=chunk-ZBIHRVUS.js.map
1930
+ //# sourceMappingURL=chunk-ZBIHRVUS.js.map