tempest-db-js 0.4.0 → 0.6.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 { columnsOf } from './chunk-JR4MLFQN.js';
1
+ import { renderPortableToken, columnNamesOf, columnsOf, toAsyncDriver } from './chunk-G7O5DCCC.js';
2
2
 
3
3
  // src/migrations/operations.ts
4
4
  var IrreversibleMigration = class extends Error {
@@ -116,6 +116,8 @@ function renderColumnType(type, dialect) {
116
116
  return "NUMERIC";
117
117
  case "blob":
118
118
  return "BLOB";
119
+ case "array":
120
+ throw new Error(unsupportedArray("sqlite"));
119
121
  default:
120
122
  return "TEXT";
121
123
  }
@@ -157,6 +159,8 @@ function renderColumnType(type, dialect) {
157
159
  return "CHAR(36)";
158
160
  case "enum":
159
161
  return `ENUM(${(meta.values ?? []).map(quoteLiteral).join(", ")})`;
162
+ case "array":
163
+ throw new Error(unsupportedArray("mysql"));
160
164
  }
161
165
  }
162
166
  switch (kind) {
@@ -195,27 +199,39 @@ function renderColumnType(type, dialect) {
195
199
  return "UUID";
196
200
  case "enum":
197
201
  return "TEXT";
202
+ case "array":
203
+ return `${renderColumnType(arrayElement(meta.element), dialect)}[]`;
198
204
  }
199
205
  }
200
- function renderDefault(def, dialect) {
206
+ function unsupportedArray(dialect) {
207
+ return `column.array() is PostgreSQL-only \u2014 ${dialect} has no native array type. Model the column as JSON there, accepting that array operators will not work.`;
208
+ }
209
+ function arrayElement(element) {
210
+ if (!element) {
211
+ throw new Error(
212
+ "An array column has no element type \u2014 build it with column.array()."
213
+ );
214
+ }
215
+ return element;
216
+ }
217
+ function renderDefault(def, dialect, type) {
201
218
  if (def.kind === "expression") {
202
219
  const expr = def.expression;
203
- if (typeof expr === "object") return expr.raw;
204
- switch (expr) {
205
- case "now":
206
- return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
207
- case "current_date":
208
- return "CURRENT_DATE";
209
- case "current_time":
210
- return "CURRENT_TIME";
211
- case "uuidv4":
212
- if (dialect === "postgresql") return "gen_random_uuid()";
213
- if (dialect === "mysql") return "(UUID())";
214
- return "(lower(hex(randomblob(16))))";
220
+ if (typeof expr === "object") {
221
+ if ("raw" in expr) return expr.raw;
222
+ throw new Error(
223
+ "sql.expr`...` binds parameters and cannot be rendered as a DEFAULT \u2014 use sql.raw()."
224
+ );
215
225
  }
226
+ return renderPortableToken(expr, dialect);
216
227
  }
217
228
  const value = def.value;
218
229
  if (value === null) return "NULL";
230
+ if (Array.isArray(value) && type?.kind === "array") {
231
+ const elementType = renderColumnType(arrayElement(type.meta.element), dialect);
232
+ const items = value.map((v) => renderDefault({ kind: "literal", value: v }, dialect));
233
+ return `ARRAY[${items.join(", ")}]::${elementType}[]`;
234
+ }
219
235
  if (typeof value === "boolean") {
220
236
  return dialect === "postgresql" ? value ? "TRUE" : "FALSE" : value ? "1" : "0";
221
237
  }
@@ -261,7 +277,9 @@ function tableConstraintClauses(table, dialect) {
261
277
  function renderColumnDef(col, dialect) {
262
278
  let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
263
279
  if (col.notNull) sql += " NOT NULL";
264
- if (col.default !== null) sql += ` DEFAULT ${renderDefault(col.default, dialect)}`;
280
+ if (col.default !== null) {
281
+ sql += ` DEFAULT ${renderDefault(col.default, dialect, col.type)}`;
282
+ }
265
283
  sql += columnConstraintSuffix(col, dialect);
266
284
  return sql;
267
285
  }
@@ -285,7 +303,9 @@ function renderCreateTable(table, dialect) {
285
303
  typeStmts.push(`CREATE TYPE ${quoteId(typeName, dialect)} AS ENUM (${values})`);
286
304
  let def = `${quoteId(c.name, dialect)} ${quoteId(typeName, dialect)}`;
287
305
  if (c.notNull) def += " NOT NULL";
288
- if (c.default !== null) def += ` DEFAULT ${renderDefault(c.default, dialect)}`;
306
+ if (c.default !== null) {
307
+ def += ` DEFAULT ${renderDefault(c.default, dialect, c.type)}`;
308
+ }
289
309
  return def + columnConstraintSuffix(c, dialect);
290
310
  }
291
311
  if (dialect === "postgresql" && isAutoIncrementPk(table, c)) {
@@ -429,7 +449,7 @@ function renderAlterColumn(table, to, dialect) {
429
449
  to.notNull ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET NOT NULL` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP NOT NULL`
430
450
  );
431
451
  stmts.push(
432
- to.default !== null ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET DEFAULT ${renderDefault(to.default, dialect)}` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP DEFAULT`
452
+ to.default !== null ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET DEFAULT ${renderDefault(to.default, dialect, to.type)}` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP DEFAULT`
433
453
  );
434
454
  return stmts;
435
455
  }
@@ -605,10 +625,13 @@ function constraintName(prefix, table, columns) {
605
625
  return `${prefix}_${table}_${columns.join("_")}`;
606
626
  }
607
627
  function reflectTable(model) {
628
+ const names = columnNamesOf(model);
629
+ const toColumn = (prop) => names?.[prop] ?? prop;
608
630
  const columns = {};
609
631
  const primaryKey = [];
610
- for (const [name, col] of Object.entries(columnsOf(model))) {
632
+ for (const [prop, col] of Object.entries(columnsOf(model))) {
611
633
  const isPk = col.flags.primaryKey;
634
+ const name = toColumn(prop);
612
635
  columns[name] = {
613
636
  name,
614
637
  type: col.type,
@@ -623,15 +646,16 @@ function reflectTable(model) {
623
646
  const uniqueConstraints = [];
624
647
  const foreignKeys = [];
625
648
  for (const c of model.tableArgs?.() ?? []) {
649
+ const cols = c.columns.map(toColumn);
626
650
  if (c.kind === "unique") {
627
651
  uniqueConstraints.push({
628
- name: c.name ?? constraintName("uq", model.tablename, c.columns),
629
- columns: c.columns
652
+ name: c.name ?? constraintName("uq", model.tablename, cols),
653
+ columns: cols
630
654
  });
631
655
  } else {
632
656
  foreignKeys.push({
633
- name: c.name ?? constraintName("fk", model.tablename, c.columns),
634
- columns: c.columns,
657
+ name: c.name ?? constraintName("fk", model.tablename, cols),
658
+ columns: cols,
635
659
  refTable: c.refTable,
636
660
  refColumns: c.refColumns,
637
661
  onDelete: c.onDelete,
@@ -678,62 +702,26 @@ function affinityToKind(affinity) {
678
702
  }
679
703
  function sqliteForeignKeys(driver, table) {
680
704
  const rows = driver.execute(`PRAGMA foreign_key_list(${JSON.stringify(table)})`, []).rows;
681
- const byId = /* @__PURE__ */ new Map();
682
- for (const r of rows) {
683
- const list = byId.get(r.id) ?? [];
684
- list.push(r);
685
- byId.set(r.id, list);
686
- }
687
- const fks = [];
688
- for (const group of byId.values()) {
689
- const ordered = [...group].sort((a, b) => a.seq - b.seq);
690
- const columns = ordered.map((r) => r.from);
691
- fks.push({
692
- name: `fk_${table}_${columns.join("_")}`,
693
- columns,
694
- refTable: ordered[0]?.table ?? "",
695
- refColumns: ordered.map((r) => r.to)
696
- });
697
- }
698
- return fks;
705
+ return sqliteForeignKeysFromPragma(table, rows);
699
706
  }
700
707
  function sqliteUniques(driver, table) {
701
708
  const indexes = driver.execute(`PRAGMA index_list(${JSON.stringify(table)})`, []).rows;
702
709
  const uniques = [];
703
710
  for (const idx of indexes) {
704
- if (Number(idx.unique) !== 1 || idx.origin === "pk") continue;
705
- const cols = driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, []).rows.sort((a, b) => a.seqno - b.seqno).map((c) => c.name);
706
- if (cols.length > 0) {
707
- uniques.push({ name: `uq_${table}_${cols.join("_")}`, columns: cols });
708
- }
711
+ if (!isUniqueIndex(idx)) continue;
712
+ const cols = driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, []).rows;
713
+ const unique = sqliteUniqueFromPragma(table, cols);
714
+ if (unique) uniques.push(unique);
709
715
  }
710
716
  return uniques;
711
717
  }
712
718
  function introspectSqlite(driver) {
713
- const tablesRows = driver.execute(
714
- "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name != 'tempest_db_js_migrations'",
715
- []
716
- ).rows;
719
+ const tablesRows = driver.execute(SQLITE_TABLES_SQL, []).rows;
717
720
  const tables = {};
718
721
  for (const row of tablesRows) {
719
722
  const tableName = String(row.name);
720
723
  const info = driver.execute(`PRAGMA table_info(${JSON.stringify(tableName)})`, []).rows;
721
- const columns = {};
722
- const primaryKey = [];
723
- for (const col of info) {
724
- const isPk = Number(col.pk) > 0;
725
- const affinity = sqliteAffinity(col.type);
726
- columns[col.name] = {
727
- name: col.name,
728
- type: { kind: affinityToKind(affinity), meta: {} },
729
- notNull: Number(col.notnull) === 1 || isPk,
730
- primaryKey: isPk,
731
- default: null,
732
- unique: false,
733
- references: null
734
- };
735
- if (isPk) primaryKey.push(col.name);
736
- }
724
+ const { columns, primaryKey } = sqliteColumnsFromPragma(info);
737
725
  tables[tableName] = {
738
726
  name: tableName,
739
727
  columns,
@@ -762,8 +750,9 @@ function constraintKeys(table) {
762
750
  return { fks, uniques };
763
751
  }
764
752
  function checkDrift(driver, models) {
765
- const actual = introspectSqlite(driver);
766
- const expected = reflectSchema(models);
753
+ return compareSqliteSchemas(introspectSqlite(driver), reflectSchema(models));
754
+ }
755
+ function compareSqliteSchemas(actual, expected) {
767
756
  const issues = [];
768
757
  for (const [tableName, expectedTable] of Object.entries(expected.tables)) {
769
758
  const actualTable = actual.tables[tableName];
@@ -834,6 +823,138 @@ function checkDrift(driver, models) {
834
823
  }
835
824
  return issues;
836
825
  }
826
+ function sqliteColumnsFromPragma(info) {
827
+ const columns = {};
828
+ const primaryKey = [];
829
+ for (const col of info) {
830
+ const isPk = Number(col.pk) > 0;
831
+ columns[col.name] = {
832
+ name: col.name,
833
+ type: { kind: affinityToKind(sqliteAffinity(col.type)), meta: {} },
834
+ notNull: Number(col.notnull) === 1 || isPk,
835
+ primaryKey: isPk,
836
+ default: null,
837
+ unique: false,
838
+ references: null
839
+ };
840
+ if (isPk) primaryKey.push(col.name);
841
+ }
842
+ return { columns, primaryKey };
843
+ }
844
+ function sqliteForeignKeysFromPragma(table, rows) {
845
+ const byId = /* @__PURE__ */ new Map();
846
+ for (const row of rows) {
847
+ const group = byId.get(row.id) ?? [];
848
+ group.push(row);
849
+ byId.set(row.id, group);
850
+ }
851
+ const fks = [];
852
+ for (const group of byId.values()) {
853
+ const ordered = [...group].sort((a, b) => a.seq - b.seq);
854
+ fks.push({
855
+ name: `fk_${table}_${ordered.map((r) => r.from).join("_")}`,
856
+ columns: ordered.map((r) => r.from),
857
+ refTable: ordered[0]?.table ?? "",
858
+ refColumns: ordered.map((r) => r.to)
859
+ });
860
+ }
861
+ return fks;
862
+ }
863
+ function sqliteUniqueFromPragma(table, rows) {
864
+ const cols = [...rows].sort((a, b) => a.seqno - b.seqno).map((c) => c.name);
865
+ if (cols.length === 0) return null;
866
+ return { name: `uq_${table}_${cols.join("_")}`, columns: cols };
867
+ }
868
+ function isUniqueIndex(idx) {
869
+ return Number(idx.unique) === 1 && idx.origin !== "pk";
870
+ }
871
+ var SQLITE_TABLES_SQL = "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name != 'tempest_db_js_migrations'";
872
+ async function introspectSqliteAsync(driver) {
873
+ const tablesRows = (await driver.execute(SQLITE_TABLES_SQL, [])).rows;
874
+ const tables = {};
875
+ for (const row of tablesRows) {
876
+ const tableName = String(row.name);
877
+ const info = (await driver.execute(`PRAGMA table_info(${JSON.stringify(tableName)})`, [])).rows;
878
+ const { columns, primaryKey } = sqliteColumnsFromPragma(info);
879
+ const fkRows = (await driver.execute(`PRAGMA foreign_key_list(${JSON.stringify(tableName)})`, [])).rows;
880
+ const indexes = (await driver.execute(`PRAGMA index_list(${JSON.stringify(tableName)})`, [])).rows;
881
+ const uniqueConstraints = [];
882
+ for (const idx of indexes) {
883
+ if (!isUniqueIndex(idx)) continue;
884
+ const cols = (await driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, [])).rows;
885
+ const unique = sqliteUniqueFromPragma(tableName, cols);
886
+ if (unique) uniqueConstraints.push(unique);
887
+ }
888
+ tables[tableName] = {
889
+ name: tableName,
890
+ columns,
891
+ primaryKey,
892
+ uniqueConstraints,
893
+ foreignKeys: sqliteForeignKeysFromPragma(tableName, fkRows)
894
+ };
895
+ }
896
+ return { tables };
897
+ }
898
+ async function checkDriftAsync(driver, dialect, models) {
899
+ if (dialect === "postgresql") return checkDriftPostgres(driver, models);
900
+ if (dialect === "sqlite") {
901
+ return compareSqliteSchemas(
902
+ await introspectSqliteAsync(driver),
903
+ reflectSchema(models)
904
+ );
905
+ }
906
+ return [
907
+ "drift checking is not implemented for MySQL \u2014 its information_schema introspection is still missing"
908
+ ];
909
+ }
910
+ function pgTypeToColumnType(dataType, udtName) {
911
+ if (dataType.toLowerCase() === "array") {
912
+ return {
913
+ kind: "array",
914
+ meta: { element: { kind: pgUdtToKind(udtName.replace(/^_/, "")), meta: {} } }
915
+ };
916
+ }
917
+ return { kind: pgTypeToKind(dataType, udtName), meta: {} };
918
+ }
919
+ function pgUdtToKind(udtName) {
920
+ switch (udtName) {
921
+ case "int2":
922
+ return "smallint";
923
+ case "int4":
924
+ return "integer";
925
+ case "int8":
926
+ return "bigint";
927
+ case "float4":
928
+ return "real";
929
+ case "float8":
930
+ return "double";
931
+ case "numeric":
932
+ return "numeric";
933
+ case "varchar":
934
+ return "varchar";
935
+ case "bpchar":
936
+ return "char";
937
+ case "bool":
938
+ return "boolean";
939
+ case "date":
940
+ return "date";
941
+ case "time":
942
+ case "timetz":
943
+ return "time";
944
+ case "timestamp":
945
+ case "timestamptz":
946
+ return "timestamp";
947
+ case "bytea":
948
+ return "blob";
949
+ case "json":
950
+ case "jsonb":
951
+ return "json";
952
+ case "uuid":
953
+ return "uuid";
954
+ default:
955
+ return "text";
956
+ }
957
+ }
837
958
  function pgTypeToKind(dataType, udtName) {
838
959
  const t = dataType.toLowerCase();
839
960
  if (t === "user-defined") return "enum";
@@ -881,10 +1002,7 @@ async function introspectPostgres(driver) {
881
1002
  const isPk = pkSet.has(name);
882
1003
  columns[name] = {
883
1004
  name,
884
- type: {
885
- kind: pgTypeToKind(String(col.data_type), String(col.udt_name)),
886
- meta: {}
887
- },
1005
+ type: pgTypeToColumnType(String(col.data_type), String(col.udt_name)),
888
1006
  notNull: col.is_nullable === "NO" || isPk,
889
1007
  primaryKey: isPk,
890
1008
  default: null,
@@ -940,6 +1058,10 @@ async function postgresUniques(driver, table) {
940
1058
  columns: r.cols ?? []
941
1059
  }));
942
1060
  }
1061
+ function describeKind(type) {
1062
+ if (type.kind !== "array") return type.kind;
1063
+ return `${type.meta.element ? describeKind(type.meta.element) : "unknown"}[]`;
1064
+ }
943
1065
  async function checkDriftPostgres(driver, models) {
944
1066
  const actual = await introspectPostgres(driver);
945
1067
  const expected = reflectSchema(models);
@@ -956,9 +1078,9 @@ async function checkDriftPostgres(driver, models) {
956
1078
  issues.push(`column "${tableName}.${colName}" is missing from the database`);
957
1079
  continue;
958
1080
  }
959
- if (expectedCol.type.kind !== actualCol.type.kind) {
1081
+ if (describeKind(expectedCol.type) !== describeKind(actualCol.type)) {
960
1082
  issues.push(
961
- `column "${tableName}.${colName}" type differs: model ${expectedCol.type.kind}, db ${actualCol.type.kind}`
1083
+ `column "${tableName}.${colName}" type differs: model ${describeKind(expectedCol.type)}, db ${describeKind(actualCol.type)}`
962
1084
  );
963
1085
  }
964
1086
  if (expectedCol.notNull !== actualCol.notNull) {
@@ -1471,23 +1593,24 @@ function parseRenameFlags(rest) {
1471
1593
  }
1472
1594
  return out;
1473
1595
  }
1474
- function pending(config, runner) {
1475
- const done = runner.applied();
1596
+ async function pending(config, runner) {
1597
+ const done = await runner.applied();
1476
1598
  return topoOrder(config.migrations).filter((m) => !done.has(m.revision));
1477
1599
  }
1478
- function runMigrationCli(argv, config) {
1600
+ async function runMigrationCli(argv, config) {
1479
1601
  const [command, ...rest] = argv;
1480
- const runner = new MigrationRunner(config.driver, config.dialect);
1602
+ const driver = toAsyncDriver(config.driver);
1603
+ const runner = new AsyncMigrationRunner(driver, config.dialect);
1481
1604
  const appliedAt = config.appliedAt ?? "1970-01-01T00:00:00.000Z";
1482
1605
  switch (command) {
1483
1606
  case "current": {
1484
- const applied = [...runner.applied()].sort();
1607
+ const applied = [...await runner.applied()].sort();
1485
1608
  return ok(applied.length > 0 ? applied : ["(no migrations applied)"]);
1486
1609
  }
1487
1610
  case "heads":
1488
1611
  return ok(heads(config.migrations));
1489
1612
  case "history": {
1490
- const done = runner.applied();
1613
+ const done = await runner.applied();
1491
1614
  return ok(
1492
1615
  topoOrder(config.migrations).map(
1493
1616
  (m) => `${done.has(m.revision) ? "\u2713" : "\xB7"} ${m.revision}${m.label ? ` \u2014 ${m.label}` : ""}`
@@ -1497,7 +1620,7 @@ function runMigrationCli(argv, config) {
1497
1620
  case "upgrade": {
1498
1621
  if (rest.includes("--sql")) {
1499
1622
  const lines = [];
1500
- for (const migration of pending(config, runner)) {
1623
+ for (const migration of await pending(config, runner)) {
1501
1624
  const op = new Op();
1502
1625
  migration.up(op);
1503
1626
  lines.push(`-- ${migration.revision}`);
@@ -1508,19 +1631,19 @@ function runMigrationCli(argv, config) {
1508
1631
  }
1509
1632
  return ok(lines.length > 0 ? lines : ["-- nothing to upgrade"]);
1510
1633
  }
1511
- const ran = runner.upgrade(config.migrations, appliedAt);
1634
+ const ran = await runner.upgrade(config.migrations, appliedAt);
1512
1635
  return ok(ran.length > 0 ? ran.map((r) => `applied ${r}`) : ["nothing to upgrade"]);
1513
1636
  }
1514
1637
  case "downgrade": {
1515
1638
  const steps = rest[0] ? Number(rest[0]) : 1;
1516
- const reverted = runner.downgrade(config.migrations, steps);
1639
+ const reverted = await runner.downgrade(config.migrations, steps);
1517
1640
  return ok(
1518
1641
  reverted.length > 0 ? reverted.map((r) => `reverted ${r}`) : ["nothing to downgrade"]
1519
1642
  );
1520
1643
  }
1521
1644
  case "check": {
1522
1645
  if (!config.models) return fail(["check requires models in the config"]);
1523
- const drift = config.dialect === "sqlite" ? checkDrift(config.driver, config.models) : [];
1646
+ const drift = await checkDriftAsync(driver, config.dialect, config.models);
1524
1647
  const undiffed = diffSchema(
1525
1648
  replaySchema(config.migrations),
1526
1649
  reflectSchema(config.models)
@@ -1558,6 +1681,6 @@ function runMigrationCli(argv, config) {
1558
1681
  }
1559
1682
  }
1560
1683
 
1561
- export { AsyncMigrationRunner, CyclicMigrationGraph, IrreversibleMigration, MigrationRunner, Op, UnknownRevision, applyOperation, applyRenames, checkDrift, checkDriftPostgres, defineMigrationConfig, detectRenames, diffSchema, emptySchema, generateMigration, heads, introspectPostgres, introspectSqlite, invert, invertAll, makeRevisionId, reflectSchema, reflectTable, renderColumnDef, renderColumnType, renderDefault, renderOperation, replaySchema, runMigrationCli, sqliteAffinity, topoOrder };
1562
- //# sourceMappingURL=chunk-43XL66JG.js.map
1563
- //# sourceMappingURL=chunk-43XL66JG.js.map
1684
+ 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-KOW3LSWP.js.map
1686
+ //# sourceMappingURL=chunk-KOW3LSWP.js.map