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,9 +1,42 @@
1
1
  'use strict';
2
2
 
3
+ var module$1 = require('module');
4
+
5
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
+ // src/expressions.ts
7
+ function renderPortableToken(token, dialect) {
8
+ switch (token) {
9
+ case "now":
10
+ return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
11
+ case "current_date":
12
+ return "CURRENT_DATE";
13
+ case "current_time":
14
+ return "CURRENT_TIME";
15
+ case "uuidv4":
16
+ if (dialect === "postgresql") return "gen_random_uuid()";
17
+ if (dialect === "mysql") return "(UUID())";
18
+ return "(lower(hex(randomblob(16))))";
19
+ }
20
+ }
21
+ module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
22
+ function toAsyncDriver(driver) {
23
+ return {
24
+ async execute(sql, params) {
25
+ return await driver.execute(sql, params);
26
+ },
27
+ async close() {
28
+ await driver.close();
29
+ }
30
+ };
31
+ }
32
+
3
33
  // src/index.ts
4
34
  function isDefaultValue(value) {
5
35
  return typeof value === "object" && value !== null && "kind" in value && (value.kind === "literal" || value.kind === "expression");
6
36
  }
37
+ function bindsParameters(value) {
38
+ return value.kind === "expression" && typeof value.expression === "object" && "parts" in value.expression;
39
+ }
7
40
  function parseReference(ref, options) {
8
41
  const dot = ref.lastIndexOf(".");
9
42
  if (dot <= 0 || dot === ref.length - 1) {
@@ -17,48 +50,71 @@ function parseReference(ref, options) {
17
50
  };
18
51
  }
19
52
  var Column = class _Column {
20
- constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null) {
53
+ constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null, dbName = null) {
21
54
  this.type = type;
22
55
  this.flags = flags;
23
56
  this.defaultValue = defaultValue;
24
57
  this.onUpdateValue = onUpdateValue;
25
58
  this.reference = reference;
59
+ this.dbName = dbName;
26
60
  }
27
61
  type;
28
62
  flags;
29
63
  defaultValue;
30
64
  onUpdateValue;
31
65
  reference;
32
- primaryKey() {
66
+ dbName;
67
+ /** Clone this column with one facet replaced, carrying every other over. */
68
+ derive(patch) {
33
69
  return new _Column(
34
70
  this.type,
35
- { ...this.flags, primaryKey: true, hasDefault: true },
36
- this.defaultValue,
37
- this.onUpdateValue,
38
- this.reference
71
+ patch.flags ?? this.flags,
72
+ patch.defaultValue !== void 0 ? patch.defaultValue : this.defaultValue,
73
+ patch.onUpdateValue !== void 0 ? patch.onUpdateValue : this.onUpdateValue,
74
+ patch.reference !== void 0 ? patch.reference : this.reference,
75
+ patch.dbName !== void 0 ? patch.dbName : this.dbName
39
76
  );
40
77
  }
78
+ primaryKey() {
79
+ return this.derive({ flags: { ...this.flags, primaryKey: true, hasDefault: true } });
80
+ }
41
81
  notNull() {
42
- return new _Column(
43
- this.type,
44
- { ...this.flags, notNull: true },
45
- this.defaultValue,
46
- this.onUpdateValue,
47
- this.reference
48
- );
82
+ return this.derive({ flags: { ...this.flags, notNull: true } });
49
83
  }
50
84
  /**
51
85
  * Add a `UNIQUE` constraint to the column (mirrors SQLAlchemy's
52
86
  * `mapped_column(unique=True)`). DDL-only — does not change the inferred type.
53
87
  */
54
88
  unique() {
55
- return new _Column(
56
- this.type,
57
- { ...this.flags, unique: true },
58
- this.defaultValue,
59
- this.onUpdateValue,
60
- this.reference
61
- );
89
+ return this.derive({ flags: { ...this.flags, unique: true } });
90
+ }
91
+ /**
92
+ * Map this property to a differently-named database column, à la SQLAlchemy's
93
+ * `mapped_column("consumer_name")` (Django's `db_column`, Prisma's `@map`).
94
+ *
95
+ * The override applies everywhere the name reaches SQL — select, insert,
96
+ * update, delete, where, order by, group by, returning, conflict targets, the
97
+ * migration IR and the drift check — while the TypeScript row keeps the
98
+ * property name. Use it to keep a `snake_case` schema behind a `camelCase`
99
+ * model; {@link Model.naming} does the same for a whole table at once.
100
+ *
101
+ * @param dbName The real column name in the database.
102
+ * @returns A new column bound to that name.
103
+ * @throws Error When `dbName` is empty.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * class ApiKey extends Model {
108
+ * static tablename = "api_keys";
109
+ * consumerName = column.text().name("consumer_name").notNull();
110
+ * }
111
+ * ```
112
+ */
113
+ name(dbName) {
114
+ if (dbName.length === 0) {
115
+ throw new Error("column.name() requires a non-empty database column name.");
116
+ }
117
+ return this.derive({ dbName });
62
118
  }
63
119
  /**
64
120
  * Declare a foreign-key reference to another table's column, à la SQLAlchemy's
@@ -71,37 +127,50 @@ var Column = class _Column {
71
127
  * @throws Error When `ref` is not a valid `"table.column"` string.
72
128
  */
73
129
  references(ref, options) {
74
- return new _Column(
75
- this.type,
76
- this.flags,
77
- this.defaultValue,
78
- this.onUpdateValue,
79
- parseReference(ref, options)
80
- );
130
+ return this.derive({ reference: parseReference(ref, options) });
81
131
  }
82
132
  /**
83
133
  * Set the insert-time default: a constant value of type `T`, or a portable
84
134
  * server-side expression from {@link sql} (e.g. `sql.now()`, `sql.uuidv4()`).
135
+ *
136
+ * @param value The literal default, or a {@link sql} expression.
137
+ * @returns A new column carrying the default.
138
+ * @throws Error When given a `sql.expr` fragment — a `DEFAULT` clause has
139
+ * nowhere to bind parameters; use `sql.raw()` for a verbatim expression.
85
140
  */
86
141
  default(value) {
87
142
  const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
88
- return new _Column(
89
- this.type,
90
- { ...this.flags, hasDefault: true },
91
- resolved,
92
- this.onUpdateValue,
93
- this.reference
94
- );
143
+ if (bindsParameters(resolved)) {
144
+ throw new Error(
145
+ "sql.expr`...` binds parameters and cannot be a column default \u2014 use sql.raw() for a verbatim DEFAULT expression."
146
+ );
147
+ }
148
+ return this.derive({
149
+ flags: { ...this.flags, hasDefault: true },
150
+ defaultValue: resolved
151
+ });
95
152
  }
96
153
  /**
97
154
  * Re-apply a value whenever the row is updated (e.g. an `updated_at` column
98
155
  * with `sql.now()`). Mirrors SQLAlchemy's `onupdate`.
156
+ *
157
+ * @param value The literal value, or a {@link sql} expression.
158
+ * @returns A new column carrying the on-update value.
159
+ * @throws Error When given a `sql.expr` fragment (see {@link Column.default}).
99
160
  */
100
161
  onUpdate(value) {
101
162
  const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
102
- return new _Column(this.type, this.flags, this.defaultValue, resolved, this.reference);
163
+ if (bindsParameters(resolved)) {
164
+ throw new Error(
165
+ "sql.expr`...` binds parameters and cannot be an onUpdate default \u2014 use sql.raw() for a verbatim expression."
166
+ );
167
+ }
168
+ return this.derive({ onUpdateValue: resolved });
103
169
  }
104
170
  };
171
+ function toSnakeCase(name) {
172
+ return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
173
+ }
105
174
  var columnsCache = /* @__PURE__ */ new WeakMap();
106
175
  function columnsOf(model) {
107
176
  const cached = columnsCache.get(model);
@@ -116,16 +185,43 @@ function columnsOf(model) {
116
185
  columnsCache.set(model, out);
117
186
  return out;
118
187
  }
188
+ var nameMapCache = /* @__PURE__ */ new WeakMap();
189
+ function columnNamesOf(model) {
190
+ const cached = nameMapCache.get(model);
191
+ if (cached !== void 0) return cached;
192
+ const strategy = model.naming ?? "preserve";
193
+ const map = {};
194
+ const seen = /* @__PURE__ */ new Map();
195
+ let renamed = false;
196
+ for (const [prop, col] of Object.entries(columnsOf(model))) {
197
+ const dbName = col.dbName ?? (strategy === "snake_case" ? toSnakeCase(prop) : prop);
198
+ const collision = seen.get(dbName);
199
+ if (collision !== void 0) {
200
+ throw new Error(
201
+ `${model.tablename}: properties "${collision}" and "${prop}" both map to column "${dbName}".`
202
+ );
203
+ }
204
+ seen.set(dbName, prop);
205
+ map[prop] = dbName;
206
+ if (dbName !== prop) renamed = true;
207
+ }
208
+ const result = renamed ? map : null;
209
+ nameMapCache.set(model, result);
210
+ return result;
211
+ }
119
212
 
120
213
  // src/migrations/ir.ts
121
214
  function constraintName(prefix, table, columns) {
122
215
  return `${prefix}_${table}_${columns.join("_")}`;
123
216
  }
124
217
  function reflectTable(model) {
218
+ const names = columnNamesOf(model);
219
+ const toColumn = (prop) => names?.[prop] ?? prop;
125
220
  const columns = {};
126
221
  const primaryKey = [];
127
- for (const [name, col] of Object.entries(columnsOf(model))) {
222
+ for (const [prop, col] of Object.entries(columnsOf(model))) {
128
223
  const isPk = col.flags.primaryKey;
224
+ const name = toColumn(prop);
129
225
  columns[name] = {
130
226
  name,
131
227
  type: col.type,
@@ -140,15 +236,16 @@ function reflectTable(model) {
140
236
  const uniqueConstraints = [];
141
237
  const foreignKeys = [];
142
238
  for (const c of model.tableArgs?.() ?? []) {
239
+ const cols = c.columns.map(toColumn);
143
240
  if (c.kind === "unique") {
144
241
  uniqueConstraints.push({
145
- name: c.name ?? constraintName("uq", model.tablename, c.columns),
146
- columns: c.columns
242
+ name: c.name ?? constraintName("uq", model.tablename, cols),
243
+ columns: cols
147
244
  });
148
245
  } else {
149
246
  foreignKeys.push({
150
- name: c.name ?? constraintName("fk", model.tablename, c.columns),
151
- columns: c.columns,
247
+ name: c.name ?? constraintName("fk", model.tablename, cols),
248
+ columns: cols,
152
249
  refTable: c.refTable,
153
250
  refColumns: c.refColumns,
154
251
  onDelete: c.onDelete,
@@ -239,6 +336,8 @@ function renderColumnType(type, dialect) {
239
336
  return "NUMERIC";
240
337
  case "blob":
241
338
  return "BLOB";
339
+ case "array":
340
+ throw new Error(unsupportedArray("sqlite"));
242
341
  default:
243
342
  return "TEXT";
244
343
  }
@@ -280,6 +379,8 @@ function renderColumnType(type, dialect) {
280
379
  return "CHAR(36)";
281
380
  case "enum":
282
381
  return `ENUM(${(meta.values ?? []).map(quoteLiteral).join(", ")})`;
382
+ case "array":
383
+ throw new Error(unsupportedArray("mysql"));
283
384
  }
284
385
  }
285
386
  switch (kind) {
@@ -318,27 +419,39 @@ function renderColumnType(type, dialect) {
318
419
  return "UUID";
319
420
  case "enum":
320
421
  return "TEXT";
422
+ case "array":
423
+ return `${renderColumnType(arrayElement(meta.element), dialect)}[]`;
321
424
  }
322
425
  }
323
- function renderDefault(def, dialect) {
426
+ function unsupportedArray(dialect) {
427
+ 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.`;
428
+ }
429
+ function arrayElement(element) {
430
+ if (!element) {
431
+ throw new Error(
432
+ "An array column has no element type \u2014 build it with column.array()."
433
+ );
434
+ }
435
+ return element;
436
+ }
437
+ function renderDefault(def, dialect, type) {
324
438
  if (def.kind === "expression") {
325
439
  const expr = def.expression;
326
- if (typeof expr === "object") return expr.raw;
327
- switch (expr) {
328
- case "now":
329
- return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
330
- case "current_date":
331
- return "CURRENT_DATE";
332
- case "current_time":
333
- return "CURRENT_TIME";
334
- case "uuidv4":
335
- if (dialect === "postgresql") return "gen_random_uuid()";
336
- if (dialect === "mysql") return "(UUID())";
337
- return "(lower(hex(randomblob(16))))";
440
+ if (typeof expr === "object") {
441
+ if ("raw" in expr) return expr.raw;
442
+ throw new Error(
443
+ "sql.expr`...` binds parameters and cannot be rendered as a DEFAULT \u2014 use sql.raw()."
444
+ );
338
445
  }
446
+ return renderPortableToken(expr, dialect);
339
447
  }
340
448
  const value = def.value;
341
449
  if (value === null) return "NULL";
450
+ if (Array.isArray(value) && type?.kind === "array") {
451
+ const elementType = renderColumnType(arrayElement(type.meta.element), dialect);
452
+ const items = value.map((v) => renderDefault({ kind: "literal", value: v }, dialect));
453
+ return `ARRAY[${items.join(", ")}]::${elementType}[]`;
454
+ }
342
455
  if (typeof value === "boolean") {
343
456
  return dialect === "postgresql" ? value ? "TRUE" : "FALSE" : value ? "1" : "0";
344
457
  }
@@ -384,7 +497,9 @@ function tableConstraintClauses(table, dialect) {
384
497
  function renderColumnDef(col, dialect) {
385
498
  let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
386
499
  if (col.notNull) sql += " NOT NULL";
387
- if (col.default !== null) sql += ` DEFAULT ${renderDefault(col.default, dialect)}`;
500
+ if (col.default !== null) {
501
+ sql += ` DEFAULT ${renderDefault(col.default, dialect, col.type)}`;
502
+ }
388
503
  sql += columnConstraintSuffix(col, dialect);
389
504
  return sql;
390
505
  }
@@ -408,7 +523,9 @@ function renderCreateTable(table, dialect) {
408
523
  typeStmts.push(`CREATE TYPE ${quoteId(typeName, dialect)} AS ENUM (${values})`);
409
524
  let def = `${quoteId(c.name, dialect)} ${quoteId(typeName, dialect)}`;
410
525
  if (c.notNull) def += " NOT NULL";
411
- if (c.default !== null) def += ` DEFAULT ${renderDefault(c.default, dialect)}`;
526
+ if (c.default !== null) {
527
+ def += ` DEFAULT ${renderDefault(c.default, dialect, c.type)}`;
528
+ }
412
529
  return def + columnConstraintSuffix(c, dialect);
413
530
  }
414
531
  if (dialect === "postgresql" && isAutoIncrementPk(table, c)) {
@@ -552,7 +669,7 @@ function renderAlterColumn(table, to, dialect) {
552
669
  to.notNull ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET NOT NULL` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP NOT NULL`
553
670
  );
554
671
  stmts.push(
555
- to.default !== null ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET DEFAULT ${renderDefault(to.default, dialect)}` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP DEFAULT`
672
+ 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`
556
673
  );
557
674
  return stmts;
558
675
  }
@@ -1041,62 +1158,26 @@ function affinityToKind(affinity) {
1041
1158
  }
1042
1159
  function sqliteForeignKeys(driver, table) {
1043
1160
  const rows = driver.execute(`PRAGMA foreign_key_list(${JSON.stringify(table)})`, []).rows;
1044
- const byId = /* @__PURE__ */ new Map();
1045
- for (const r of rows) {
1046
- const list = byId.get(r.id) ?? [];
1047
- list.push(r);
1048
- byId.set(r.id, list);
1049
- }
1050
- const fks = [];
1051
- for (const group of byId.values()) {
1052
- const ordered = [...group].sort((a, b) => a.seq - b.seq);
1053
- const columns = ordered.map((r) => r.from);
1054
- fks.push({
1055
- name: `fk_${table}_${columns.join("_")}`,
1056
- columns,
1057
- refTable: ordered[0]?.table ?? "",
1058
- refColumns: ordered.map((r) => r.to)
1059
- });
1060
- }
1061
- return fks;
1161
+ return sqliteForeignKeysFromPragma(table, rows);
1062
1162
  }
1063
1163
  function sqliteUniques(driver, table) {
1064
1164
  const indexes = driver.execute(`PRAGMA index_list(${JSON.stringify(table)})`, []).rows;
1065
1165
  const uniques = [];
1066
1166
  for (const idx of indexes) {
1067
- if (Number(idx.unique) !== 1 || idx.origin === "pk") continue;
1068
- const cols = driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, []).rows.sort((a, b) => a.seqno - b.seqno).map((c) => c.name);
1069
- if (cols.length > 0) {
1070
- uniques.push({ name: `uq_${table}_${cols.join("_")}`, columns: cols });
1071
- }
1167
+ if (!isUniqueIndex(idx)) continue;
1168
+ const cols = driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, []).rows;
1169
+ const unique = sqliteUniqueFromPragma(table, cols);
1170
+ if (unique) uniques.push(unique);
1072
1171
  }
1073
1172
  return uniques;
1074
1173
  }
1075
1174
  function introspectSqlite(driver) {
1076
- const tablesRows = driver.execute(
1077
- "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name != 'tempest_db_js_migrations'",
1078
- []
1079
- ).rows;
1175
+ const tablesRows = driver.execute(SQLITE_TABLES_SQL, []).rows;
1080
1176
  const tables = {};
1081
1177
  for (const row of tablesRows) {
1082
1178
  const tableName = String(row.name);
1083
1179
  const info = driver.execute(`PRAGMA table_info(${JSON.stringify(tableName)})`, []).rows;
1084
- const columns = {};
1085
- const primaryKey = [];
1086
- for (const col of info) {
1087
- const isPk = Number(col.pk) > 0;
1088
- const affinity = sqliteAffinity(col.type);
1089
- columns[col.name] = {
1090
- name: col.name,
1091
- type: { kind: affinityToKind(affinity), meta: {} },
1092
- notNull: Number(col.notnull) === 1 || isPk,
1093
- primaryKey: isPk,
1094
- default: null,
1095
- unique: false,
1096
- references: null
1097
- };
1098
- if (isPk) primaryKey.push(col.name);
1099
- }
1180
+ const { columns, primaryKey } = sqliteColumnsFromPragma(info);
1100
1181
  tables[tableName] = {
1101
1182
  name: tableName,
1102
1183
  columns,
@@ -1125,8 +1206,9 @@ function constraintKeys(table) {
1125
1206
  return { fks, uniques };
1126
1207
  }
1127
1208
  function checkDrift(driver, models) {
1128
- const actual = introspectSqlite(driver);
1129
- const expected = reflectSchema(models);
1209
+ return compareSqliteSchemas(introspectSqlite(driver), reflectSchema(models));
1210
+ }
1211
+ function compareSqliteSchemas(actual, expected) {
1130
1212
  const issues = [];
1131
1213
  for (const [tableName, expectedTable] of Object.entries(expected.tables)) {
1132
1214
  const actualTable = actual.tables[tableName];
@@ -1197,6 +1279,138 @@ function checkDrift(driver, models) {
1197
1279
  }
1198
1280
  return issues;
1199
1281
  }
1282
+ function sqliteColumnsFromPragma(info) {
1283
+ const columns = {};
1284
+ const primaryKey = [];
1285
+ for (const col of info) {
1286
+ const isPk = Number(col.pk) > 0;
1287
+ columns[col.name] = {
1288
+ name: col.name,
1289
+ type: { kind: affinityToKind(sqliteAffinity(col.type)), meta: {} },
1290
+ notNull: Number(col.notnull) === 1 || isPk,
1291
+ primaryKey: isPk,
1292
+ default: null,
1293
+ unique: false,
1294
+ references: null
1295
+ };
1296
+ if (isPk) primaryKey.push(col.name);
1297
+ }
1298
+ return { columns, primaryKey };
1299
+ }
1300
+ function sqliteForeignKeysFromPragma(table, rows) {
1301
+ const byId = /* @__PURE__ */ new Map();
1302
+ for (const row of rows) {
1303
+ const group = byId.get(row.id) ?? [];
1304
+ group.push(row);
1305
+ byId.set(row.id, group);
1306
+ }
1307
+ const fks = [];
1308
+ for (const group of byId.values()) {
1309
+ const ordered = [...group].sort((a, b) => a.seq - b.seq);
1310
+ fks.push({
1311
+ name: `fk_${table}_${ordered.map((r) => r.from).join("_")}`,
1312
+ columns: ordered.map((r) => r.from),
1313
+ refTable: ordered[0]?.table ?? "",
1314
+ refColumns: ordered.map((r) => r.to)
1315
+ });
1316
+ }
1317
+ return fks;
1318
+ }
1319
+ function sqliteUniqueFromPragma(table, rows) {
1320
+ const cols = [...rows].sort((a, b) => a.seqno - b.seqno).map((c) => c.name);
1321
+ if (cols.length === 0) return null;
1322
+ return { name: `uq_${table}_${cols.join("_")}`, columns: cols };
1323
+ }
1324
+ function isUniqueIndex(idx) {
1325
+ return Number(idx.unique) === 1 && idx.origin !== "pk";
1326
+ }
1327
+ var SQLITE_TABLES_SQL = "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name != 'tempest_db_js_migrations'";
1328
+ async function introspectSqliteAsync(driver) {
1329
+ const tablesRows = (await driver.execute(SQLITE_TABLES_SQL, [])).rows;
1330
+ const tables = {};
1331
+ for (const row of tablesRows) {
1332
+ const tableName = String(row.name);
1333
+ const info = (await driver.execute(`PRAGMA table_info(${JSON.stringify(tableName)})`, [])).rows;
1334
+ const { columns, primaryKey } = sqliteColumnsFromPragma(info);
1335
+ const fkRows = (await driver.execute(`PRAGMA foreign_key_list(${JSON.stringify(tableName)})`, [])).rows;
1336
+ const indexes = (await driver.execute(`PRAGMA index_list(${JSON.stringify(tableName)})`, [])).rows;
1337
+ const uniqueConstraints = [];
1338
+ for (const idx of indexes) {
1339
+ if (!isUniqueIndex(idx)) continue;
1340
+ const cols = (await driver.execute(`PRAGMA index_info(${JSON.stringify(idx.name)})`, [])).rows;
1341
+ const unique = sqliteUniqueFromPragma(tableName, cols);
1342
+ if (unique) uniqueConstraints.push(unique);
1343
+ }
1344
+ tables[tableName] = {
1345
+ name: tableName,
1346
+ columns,
1347
+ primaryKey,
1348
+ uniqueConstraints,
1349
+ foreignKeys: sqliteForeignKeysFromPragma(tableName, fkRows)
1350
+ };
1351
+ }
1352
+ return { tables };
1353
+ }
1354
+ async function checkDriftAsync(driver, dialect, models) {
1355
+ if (dialect === "postgresql") return checkDriftPostgres(driver, models);
1356
+ if (dialect === "sqlite") {
1357
+ return compareSqliteSchemas(
1358
+ await introspectSqliteAsync(driver),
1359
+ reflectSchema(models)
1360
+ );
1361
+ }
1362
+ return [
1363
+ "drift checking is not implemented for MySQL \u2014 its information_schema introspection is still missing"
1364
+ ];
1365
+ }
1366
+ function pgTypeToColumnType(dataType, udtName) {
1367
+ if (dataType.toLowerCase() === "array") {
1368
+ return {
1369
+ kind: "array",
1370
+ meta: { element: { kind: pgUdtToKind(udtName.replace(/^_/, "")), meta: {} } }
1371
+ };
1372
+ }
1373
+ return { kind: pgTypeToKind(dataType, udtName), meta: {} };
1374
+ }
1375
+ function pgUdtToKind(udtName) {
1376
+ switch (udtName) {
1377
+ case "int2":
1378
+ return "smallint";
1379
+ case "int4":
1380
+ return "integer";
1381
+ case "int8":
1382
+ return "bigint";
1383
+ case "float4":
1384
+ return "real";
1385
+ case "float8":
1386
+ return "double";
1387
+ case "numeric":
1388
+ return "numeric";
1389
+ case "varchar":
1390
+ return "varchar";
1391
+ case "bpchar":
1392
+ return "char";
1393
+ case "bool":
1394
+ return "boolean";
1395
+ case "date":
1396
+ return "date";
1397
+ case "time":
1398
+ case "timetz":
1399
+ return "time";
1400
+ case "timestamp":
1401
+ case "timestamptz":
1402
+ return "timestamp";
1403
+ case "bytea":
1404
+ return "blob";
1405
+ case "json":
1406
+ case "jsonb":
1407
+ return "json";
1408
+ case "uuid":
1409
+ return "uuid";
1410
+ default:
1411
+ return "text";
1412
+ }
1413
+ }
1200
1414
  function pgTypeToKind(dataType, udtName) {
1201
1415
  const t = dataType.toLowerCase();
1202
1416
  if (t === "user-defined") return "enum";
@@ -1244,10 +1458,7 @@ async function introspectPostgres(driver) {
1244
1458
  const isPk = pkSet.has(name);
1245
1459
  columns[name] = {
1246
1460
  name,
1247
- type: {
1248
- kind: pgTypeToKind(String(col.data_type), String(col.udt_name)),
1249
- meta: {}
1250
- },
1461
+ type: pgTypeToColumnType(String(col.data_type), String(col.udt_name)),
1251
1462
  notNull: col.is_nullable === "NO" || isPk,
1252
1463
  primaryKey: isPk,
1253
1464
  default: null,
@@ -1303,6 +1514,10 @@ async function postgresUniques(driver, table) {
1303
1514
  columns: r.cols ?? []
1304
1515
  }));
1305
1516
  }
1517
+ function describeKind(type) {
1518
+ if (type.kind !== "array") return type.kind;
1519
+ return `${type.meta.element ? describeKind(type.meta.element) : "unknown"}[]`;
1520
+ }
1306
1521
  async function checkDriftPostgres(driver, models) {
1307
1522
  const actual = await introspectPostgres(driver);
1308
1523
  const expected = reflectSchema(models);
@@ -1319,9 +1534,9 @@ async function checkDriftPostgres(driver, models) {
1319
1534
  issues.push(`column "${tableName}.${colName}" is missing from the database`);
1320
1535
  continue;
1321
1536
  }
1322
- if (expectedCol.type.kind !== actualCol.type.kind) {
1537
+ if (describeKind(expectedCol.type) !== describeKind(actualCol.type)) {
1323
1538
  issues.push(
1324
- `column "${tableName}.${colName}" type differs: model ${expectedCol.type.kind}, db ${actualCol.type.kind}`
1539
+ `column "${tableName}.${colName}" type differs: model ${describeKind(expectedCol.type)}, db ${describeKind(actualCol.type)}`
1325
1540
  );
1326
1541
  }
1327
1542
  if (expectedCol.notNull !== actualCol.notNull) {
@@ -1588,23 +1803,24 @@ function parseRenameFlags(rest) {
1588
1803
  }
1589
1804
  return out;
1590
1805
  }
1591
- function pending(config, runner) {
1592
- const done = runner.applied();
1806
+ async function pending(config, runner) {
1807
+ const done = await runner.applied();
1593
1808
  return topoOrder(config.migrations).filter((m) => !done.has(m.revision));
1594
1809
  }
1595
- function runMigrationCli(argv, config) {
1810
+ async function runMigrationCli(argv, config) {
1596
1811
  const [command, ...rest] = argv;
1597
- const runner = new MigrationRunner(config.driver, config.dialect);
1812
+ const driver = toAsyncDriver(config.driver);
1813
+ const runner = new AsyncMigrationRunner(driver, config.dialect);
1598
1814
  const appliedAt = config.appliedAt ?? "1970-01-01T00:00:00.000Z";
1599
1815
  switch (command) {
1600
1816
  case "current": {
1601
- const applied = [...runner.applied()].sort();
1817
+ const applied = [...await runner.applied()].sort();
1602
1818
  return ok(applied.length > 0 ? applied : ["(no migrations applied)"]);
1603
1819
  }
1604
1820
  case "heads":
1605
1821
  return ok(heads(config.migrations));
1606
1822
  case "history": {
1607
- const done = runner.applied();
1823
+ const done = await runner.applied();
1608
1824
  return ok(
1609
1825
  topoOrder(config.migrations).map(
1610
1826
  (m) => `${done.has(m.revision) ? "\u2713" : "\xB7"} ${m.revision}${m.label ? ` \u2014 ${m.label}` : ""}`
@@ -1614,7 +1830,7 @@ function runMigrationCli(argv, config) {
1614
1830
  case "upgrade": {
1615
1831
  if (rest.includes("--sql")) {
1616
1832
  const lines = [];
1617
- for (const migration of pending(config, runner)) {
1833
+ for (const migration of await pending(config, runner)) {
1618
1834
  const op = new Op();
1619
1835
  migration.up(op);
1620
1836
  lines.push(`-- ${migration.revision}`);
@@ -1625,19 +1841,19 @@ function runMigrationCli(argv, config) {
1625
1841
  }
1626
1842
  return ok(lines.length > 0 ? lines : ["-- nothing to upgrade"]);
1627
1843
  }
1628
- const ran = runner.upgrade(config.migrations, appliedAt);
1844
+ const ran = await runner.upgrade(config.migrations, appliedAt);
1629
1845
  return ok(ran.length > 0 ? ran.map((r) => `applied ${r}`) : ["nothing to upgrade"]);
1630
1846
  }
1631
1847
  case "downgrade": {
1632
1848
  const steps = rest[0] ? Number(rest[0]) : 1;
1633
- const reverted = runner.downgrade(config.migrations, steps);
1849
+ const reverted = await runner.downgrade(config.migrations, steps);
1634
1850
  return ok(
1635
1851
  reverted.length > 0 ? reverted.map((r) => `reverted ${r}`) : ["nothing to downgrade"]
1636
1852
  );
1637
1853
  }
1638
1854
  case "check": {
1639
1855
  if (!config.models) return fail(["check requires models in the config"]);
1640
- const drift = config.dialect === "sqlite" ? checkDrift(config.driver, config.models) : [];
1856
+ const drift = await checkDriftAsync(driver, config.dialect, config.models);
1641
1857
  const undiffed = diffSchema(
1642
1858
  replaySchema(config.migrations),
1643
1859
  reflectSchema(config.models)
@@ -1684,7 +1900,9 @@ exports.UnknownRevision = UnknownRevision;
1684
1900
  exports.applyOperation = applyOperation;
1685
1901
  exports.applyRenames = applyRenames;
1686
1902
  exports.checkDrift = checkDrift;
1903
+ exports.checkDriftAsync = checkDriftAsync;
1687
1904
  exports.checkDriftPostgres = checkDriftPostgres;
1905
+ exports.compareSqliteSchemas = compareSqliteSchemas;
1688
1906
  exports.defineMigrationConfig = defineMigrationConfig;
1689
1907
  exports.detectRenames = detectRenames;
1690
1908
  exports.diffSchema = diffSchema;
@@ -1693,6 +1911,7 @@ exports.generateMigration = generateMigration;
1693
1911
  exports.heads = heads;
1694
1912
  exports.introspectPostgres = introspectPostgres;
1695
1913
  exports.introspectSqlite = introspectSqlite;
1914
+ exports.introspectSqliteAsync = introspectSqliteAsync;
1696
1915
  exports.invert = invert;
1697
1916
  exports.invertAll = invertAll;
1698
1917
  exports.makeRevisionId = makeRevisionId;