tina4-nodejs 3.13.14 → 3.13.16

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.
@@ -3,7 +3,12 @@ import { join, resolve } from "node:path";
3
3
  import type { FieldDefinition, DatabaseAdapter } from "./types.js";
4
4
  import type { SQLiteAdapter } from "./adapters/sqlite.js";
5
5
  import type { DiscoveredModel } from "./model.js";
6
- import { getAdapter } from "./database.js";
6
+ import {
7
+ getAdapter,
8
+ adapterQuery, adapterExecute, adapterTableExists,
9
+ adapterCreateTable, adapterColumns,
10
+ adapterStartTransaction, adapterCommit, adapterRollback,
11
+ } from "./database.js";
7
12
 
8
13
  // ---------------------------------------------------------------------------
9
14
  // Firebird ALTER TABLE ADD idempotency check
@@ -74,8 +79,8 @@ async function shouldSkipForFirebird(
74
79
  /**
75
80
  * Sync model definitions to the database (create tables, add columns).
76
81
  */
77
- export function syncModels(models: DiscoveredModel[]): void {
78
- const adapter = getAdapter() as SQLiteAdapter;
82
+ export async function syncModels(models: DiscoveredModel[]): Promise<void> {
83
+ const adapter = getAdapter();
79
84
 
80
85
  for (const { definition } of models) {
81
86
  const { tableName, fields, softDelete, fieldMapping } = definition;
@@ -100,17 +105,26 @@ export function syncModels(models: DiscoveredModel[]): void {
100
105
  dbFields[dbCol] = def;
101
106
  }
102
107
 
103
- if (!adapter.tableExists(tableName)) {
104
- adapter.createTable(tableName, dbFields);
108
+ if (!(await adapterTableExists(adapter, tableName))) {
109
+ // adapterCreateTable prefers createTableAsync — engine-aware DDL on
110
+ // PostgreSQL/MySQL/MSSQL/Firebird (TIMESTAMP/BOOLEAN/SERIAL etc.).
111
+ await adapterCreateTable(adapter, tableName, dbFields);
105
112
  console.log(` Created table: ${tableName}`);
106
113
  } else {
107
- // Check for new columns
108
- const existing = adapter.getTableColumns(tableName);
109
- const existingNames = new Set(existing.map((c) => c.name));
114
+ // Check for new columns. SQLite exposes the legacy getTableColumns/
115
+ // addColumn helpers; other engines use columns()/ALTER TABLE.
116
+ const existingCols = (adapter as any).getTableColumns
117
+ ? (adapter as SQLiteAdapter).getTableColumns(tableName)
118
+ : await adapterColumns(adapter, tableName);
119
+ const existingNames = new Set(existingCols.map((c) => c.name.toLowerCase()));
110
120
 
111
121
  for (const [colName, def] of Object.entries(dbFields)) {
112
- if (!existingNames.has(colName)) {
113
- adapter.addColumn(tableName, colName, def);
122
+ if (!existingNames.has(colName.toLowerCase())) {
123
+ if ((adapter as any).addColumn) {
124
+ (adapter as any).addColumn(tableName, colName, def);
125
+ } else {
126
+ await adapterExecute(adapter, buildAddColumnSql(adapter, tableName, colName, def));
127
+ }
114
128
  console.log(` Added column: ${tableName}.${colName}`);
115
129
  }
116
130
  }
@@ -118,6 +132,35 @@ export function syncModels(models: DiscoveredModel[]): void {
118
132
  }
119
133
  }
120
134
 
135
+ /**
136
+ * Build an engine-aware `ALTER TABLE ... ADD COLUMN` statement for adapters
137
+ * that don't expose the SQLite-style addColumn() helper.
138
+ */
139
+ function buildAddColumnSql(
140
+ adapter: DatabaseAdapter,
141
+ table: string,
142
+ colName: string,
143
+ def: FieldDefinition,
144
+ ): string {
145
+ const engine = adapter.constructor.name;
146
+ const isPg = engine === "PostgresAdapter";
147
+ const typeMap: Record<string, string> = isPg
148
+ ? { integer: "INTEGER", string: def.maxLength ? `VARCHAR(${def.maxLength})` : "VARCHAR(255)", text: "TEXT", number: "DOUBLE PRECISION", numeric: "DOUBLE PRECISION", boolean: "BOOLEAN", datetime: "TIMESTAMP" }
149
+ : { integer: "INTEGER", string: def.maxLength ? `VARCHAR(${def.maxLength})` : "VARCHAR(255)", text: "TEXT", number: "DOUBLE PRECISION", numeric: "DOUBLE PRECISION", boolean: "INTEGER", datetime: "TIMESTAMP" };
150
+ const sqlType = typeMap[def.type] ?? "TEXT";
151
+ let sql = `ALTER TABLE "${table}" ADD COLUMN "${colName}" ${sqlType}`;
152
+ if (def.default !== undefined && def.default !== "now") {
153
+ const dv =
154
+ typeof def.default === "string" ? `'${def.default}'`
155
+ : typeof def.default === "boolean" ? (isPg ? (def.default ? "TRUE" : "FALSE") : (def.default ? "1" : "0"))
156
+ : String(def.default);
157
+ sql += ` DEFAULT ${dv}`;
158
+ } else if (def.default === "now") {
159
+ sql += " DEFAULT CURRENT_TIMESTAMP";
160
+ }
161
+ return sql;
162
+ }
163
+
121
164
  /**
122
165
  * Migration tracking table name.
123
166
  */
@@ -126,25 +169,25 @@ const MIGRATION_TABLE = "tina4_migration";
126
169
  /**
127
170
  * Ensure the migration tracking table exists with batch support.
128
171
  */
129
- export function ensureMigrationTable(): void {
172
+ export async function ensureMigrationTable(): Promise<void> {
130
173
  const adapter = getAdapter();
131
- if (!adapter.tableExists(MIGRATION_TABLE)) {
174
+ if (!(await adapterTableExists(adapter, MIGRATION_TABLE))) {
132
175
  if (isFirebirdAdapter(adapter)) {
133
176
  // Firebird: no AUTOINCREMENT, no TEXT type, use generator for IDs
134
177
  try {
135
- adapter.execute("CREATE GENERATOR GEN_TINA4_MIGRATION_ID");
136
- try { adapter.execute("COMMIT"); } catch { /* ignore */ }
178
+ await adapterExecute(adapter, "CREATE GENERATOR GEN_TINA4_MIGRATION_ID");
179
+ try { await adapterExecute(adapter, "COMMIT"); } catch { /* ignore */ }
137
180
  } catch {
138
181
  // Generator may already exist
139
182
  }
140
- adapter.execute(`CREATE TABLE "${MIGRATION_TABLE}" (
183
+ await adapterExecute(adapter, `CREATE TABLE "${MIGRATION_TABLE}" (
141
184
  id INTEGER NOT NULL PRIMARY KEY,
142
185
  name VARCHAR(500) NOT NULL,
143
186
  batch INTEGER NOT NULL DEFAULT 1,
144
187
  applied_at VARCHAR(50) NOT NULL
145
188
  )`);
146
189
  } else {
147
- (adapter as SQLiteAdapter).createTable(MIGRATION_TABLE, {
190
+ await adapterCreateTable(adapter, MIGRATION_TABLE, {
148
191
  id: { type: "integer", primaryKey: true, autoIncrement: true },
149
192
  name: { type: "string", required: true },
150
193
  batch: { type: "integer", required: true },
@@ -154,10 +197,12 @@ export function ensureMigrationTable(): void {
154
197
  } else {
155
198
  // Ensure batch column exists on older tables that only had passed/description
156
199
  try {
157
- const cols = (adapter as SQLiteAdapter).getTableColumns(MIGRATION_TABLE);
158
- const colNames = new Set(cols.map((c) => c.name));
200
+ const cols = (adapter as any).getTableColumns
201
+ ? (adapter as SQLiteAdapter).getTableColumns(MIGRATION_TABLE)
202
+ : await adapterColumns(adapter, MIGRATION_TABLE);
203
+ const colNames = new Set(cols.map((c) => c.name.toLowerCase()));
159
204
  if (!colNames.has("batch")) {
160
- adapter.execute(`ALTER TABLE "${MIGRATION_TABLE}" ADD COLUMN batch INTEGER NOT NULL DEFAULT 1`);
205
+ await adapterExecute(adapter, `ALTER TABLE "${MIGRATION_TABLE}" ADD COLUMN batch INTEGER NOT NULL DEFAULT 1`);
161
206
  }
162
207
  } catch {
163
208
  // ignore — column may already exist
@@ -168,9 +213,9 @@ export function ensureMigrationTable(): void {
168
213
  /**
169
214
  * Get the current batch number (max batch + 1).
170
215
  */
171
- export function getNextBatch(): number {
216
+ export async function getNextBatch(): Promise<number> {
172
217
  const adapter = getAdapter();
173
- const rows = adapter.query<{ max_batch: number | null }>(
218
+ const rows = await adapterQuery<{ max_batch: number | null }>(adapter,
174
219
  `SELECT MAX(batch) as max_batch FROM "${MIGRATION_TABLE}"`,
175
220
  );
176
221
  return (rows[0]?.max_batch ?? 0) + 1;
@@ -179,9 +224,9 @@ export function getNextBatch(): number {
179
224
  /**
180
225
  * Check if a migration has already been applied.
181
226
  */
182
- export function isMigrationApplied(name: string): boolean {
227
+ export async function isMigrationApplied(name: string): Promise<boolean> {
183
228
  const adapter = getAdapter();
184
- const rows = adapter.query(
229
+ const rows = await adapterQuery(adapter,
185
230
  `SELECT id FROM "${MIGRATION_TABLE}" WHERE name = ?`,
186
231
  [name],
187
232
  );
@@ -191,20 +236,20 @@ export function isMigrationApplied(name: string): boolean {
191
236
  /**
192
237
  * Record a migration as applied.
193
238
  */
194
- export function recordMigration(name: string, batch: number, passed: number = 1): void {
239
+ export async function recordMigration(name: string, batch: number, passed: number = 1): Promise<void> {
195
240
  const adapter = getAdapter();
196
241
  if (isFirebirdAdapter(adapter)) {
197
242
  // Firebird: generate ID from sequence
198
- const rows = adapter.query<{ NEXT_ID: number }>(
243
+ const rows = await adapterQuery<{ NEXT_ID: number }>(adapter,
199
244
  "SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS NEXT_ID FROM RDB$DATABASE",
200
245
  );
201
246
  const nextId = rows[0]?.NEXT_ID ?? 1;
202
- adapter.execute(
247
+ await adapterExecute(adapter,
203
248
  `INSERT INTO "${MIGRATION_TABLE}" (id, name, batch) VALUES (?, ?, ?)`,
204
249
  [nextId, name, batch],
205
250
  );
206
251
  } else {
207
- adapter.execute(
252
+ await adapterExecute(adapter,
208
253
  `INSERT INTO "${MIGRATION_TABLE}" (name, batch) VALUES (?, ?)`,
209
254
  [name, batch],
210
255
  );
@@ -214,30 +259,30 @@ export function recordMigration(name: string, batch: number, passed: number = 1)
214
259
  /**
215
260
  * Apply a migration (run its up function and record it).
216
261
  */
217
- export function applyMigration(
262
+ export async function applyMigration(
218
263
  name: string,
219
- up: () => void,
264
+ up: () => void | Promise<void>,
220
265
  batch: number,
221
- ): void {
222
- if (isMigrationApplied(name)) {
266
+ ): Promise<void> {
267
+ if (await isMigrationApplied(name)) {
223
268
  return;
224
269
  }
225
- up();
226
- recordMigration(name, batch);
270
+ await up();
271
+ await recordMigration(name, batch);
227
272
  }
228
273
 
229
274
  /**
230
275
  * Get all migrations from the last batch.
231
276
  */
232
- export function getLastBatchMigrations(): Array<{ id: number; name: string; batch: number }> {
277
+ export async function getLastBatchMigrations(): Promise<Array<{ id: number; name: string; batch: number }>> {
233
278
  const adapter = getAdapter();
234
- const rows = adapter.query<{ max_batch: number | null }>(
279
+ const rows = await adapterQuery<{ max_batch: number | null }>(adapter,
235
280
  `SELECT MAX(batch) as max_batch FROM "${MIGRATION_TABLE}"`,
236
281
  );
237
282
  const lastBatch = rows[0]?.max_batch;
238
283
  if (lastBatch === null || lastBatch === undefined) return [];
239
284
 
240
- return adapter.query<{ id: number; name: string; batch: number }>(
285
+ return adapterQuery<{ id: number; name: string; batch: number }>(adapter,
241
286
  `SELECT id, name, batch FROM "${MIGRATION_TABLE}" WHERE batch = ? ORDER BY id DESC`,
242
287
  [lastBatch],
243
288
  );
@@ -246,9 +291,9 @@ export function getLastBatchMigrations(): Array<{ id: number; name: string; batc
246
291
  /**
247
292
  * Remove a migration record (used during rollback).
248
293
  */
249
- export function removeMigrationRecord(name: string): void {
294
+ export async function removeMigrationRecord(name: string): Promise<void> {
250
295
  const adapter = getAdapter();
251
- adapter.execute(
296
+ await adapterExecute(adapter,
252
297
  `DELETE FROM "${MIGRATION_TABLE}" WHERE name = ?`,
253
298
  [name],
254
299
  );
@@ -267,21 +312,21 @@ export function removeMigrationRecord(name: string): void {
267
312
  * @param delimiter - SQL statement delimiter (default: ";")
268
313
  * @returns Array of rolled-back migration names
269
314
  */
270
- export function rollback(
271
- migrationsDir?: string | Map<string, () => void>,
315
+ export async function rollback(
316
+ migrationsDir?: string | Map<string, () => void | Promise<void>>,
272
317
  delimiter?: string,
273
- ): string[] {
318
+ ): Promise<string[]> {
274
319
  // Handle legacy API: if first arg is a Map, use old behaviour
275
320
  if (migrationsDir instanceof Map) {
276
321
  const downFunctions = migrationsDir;
277
- const migrations = getLastBatchMigrations();
322
+ const migrations = await getLastBatchMigrations();
278
323
  const rolledBack: string[] = [];
279
324
  for (const migration of migrations) {
280
325
  const down = downFunctions.get(migration.name);
281
326
  if (down) {
282
- down();
327
+ await down();
283
328
  }
284
- removeMigrationRecord(migration.name);
329
+ await removeMigrationRecord(migration.name);
285
330
  rolledBack.push(migration.name);
286
331
  }
287
332
  return rolledBack;
@@ -290,7 +335,7 @@ export function rollback(
290
335
  const dir = resolve(migrationsDir ?? "migrations");
291
336
  const delim = delimiter ?? ";";
292
337
  const db = getAdapter();
293
- const migrations = getLastBatchMigrations();
338
+ const migrations = await getLastBatchMigrations();
294
339
  const rolledBack: string[] = [];
295
340
 
296
341
  for (const migration of migrations) {
@@ -303,14 +348,14 @@ export function rollback(
303
348
  if (sqlContent) {
304
349
  const statements = splitStatements(sqlContent, delim);
305
350
  try {
306
- db.startTransaction();
351
+ await adapterStartTransaction(db);
307
352
  for (const stmt of statements) {
308
- db.execute(stmt);
353
+ await adapterExecute(db, stmt);
309
354
  }
310
- db.commit();
355
+ await adapterCommit(db);
311
356
  } catch (err) {
312
357
  try {
313
- db.rollback();
358
+ await adapterRollback(db);
314
359
  } catch {
315
360
  // rollback may fail if auto-rolled-back
316
361
  }
@@ -323,7 +368,7 @@ export function rollback(
323
368
  console.warn(` Warning: No .down.sql file found for ${migration.name} — skipping SQL execution`);
324
369
  }
325
370
 
326
- removeMigrationRecord(migration.name);
371
+ await removeMigrationRecord(migration.name);
327
372
  rolledBack.push(migration.name);
328
373
  }
329
374
 
@@ -333,9 +378,9 @@ export function rollback(
333
378
  /**
334
379
  * Get all applied migrations.
335
380
  */
336
- export function getAppliedMigrations(): Array<{ id: number; name: string; batch: number; applied_at: string }> {
381
+ export async function getAppliedMigrations(): Promise<Array<{ id: number; name: string; batch: number; applied_at: string }>> {
337
382
  const adapter = getAdapter();
338
- return adapter.query<{ id: number; name: string; batch: number; applied_at: string }>(
383
+ return adapterQuery<{ id: number; name: string; batch: number; applied_at: string }>(adapter,
339
384
  `SELECT * FROM "${MIGRATION_TABLE}" ORDER BY id ASC`,
340
385
  );
341
386
  }
@@ -465,23 +510,31 @@ export async function migrate(
465
510
  }
466
511
 
467
512
  // Ensure tracking table with batch support
468
- if (!db.tableExists(MIGRATION_TABLE)) {
513
+ if (!(await adapterTableExists(db, MIGRATION_TABLE))) {
469
514
  if (isFirebirdAdapter(db)) {
470
515
  // Firebird: no AUTOINCREMENT, no TEXT type, use generator for IDs
471
516
  try {
472
- db.execute("CREATE GENERATOR GEN_TINA4_MIGRATION_ID");
473
- try { db.execute("COMMIT"); } catch { /* ignore */ }
517
+ await adapterExecute(db, "CREATE GENERATOR GEN_TINA4_MIGRATION_ID");
518
+ try { await adapterExecute(db, "COMMIT"); } catch { /* ignore */ }
474
519
  } catch {
475
520
  // Generator may already exist
476
521
  }
477
- db.execute(`CREATE TABLE "${MIGRATION_TABLE}" (
522
+ await adapterExecute(db, `CREATE TABLE "${MIGRATION_TABLE}" (
478
523
  id INTEGER NOT NULL PRIMARY KEY,
479
524
  name VARCHAR(500) NOT NULL,
480
525
  batch INTEGER NOT NULL DEFAULT 1,
481
526
  applied_at VARCHAR(50) NOT NULL
482
527
  )`);
528
+ } else if (db.constructor.name === "PostgresAdapter") {
529
+ // PostgreSQL: SERIAL + TIMESTAMP (not AUTOINCREMENT/TEXT).
530
+ await adapterExecute(db, `CREATE TABLE IF NOT EXISTS "${MIGRATION_TABLE}" (
531
+ id SERIAL PRIMARY KEY,
532
+ name TEXT NOT NULL,
533
+ batch INTEGER NOT NULL DEFAULT 1,
534
+ applied_at TEXT NOT NULL
535
+ )`);
483
536
  } else {
484
- db.execute(`CREATE TABLE IF NOT EXISTS "${MIGRATION_TABLE}" (
537
+ await adapterExecute(db, `CREATE TABLE IF NOT EXISTS "${MIGRATION_TABLE}" (
485
538
  id INTEGER PRIMARY KEY AUTOINCREMENT,
486
539
  name TEXT NOT NULL,
487
540
  batch INTEGER NOT NULL DEFAULT 1,
@@ -491,7 +544,7 @@ export async function migrate(
491
544
  } else {
492
545
  // Migrate old schema: if table has 'description' + 'passed' columns, migrate data
493
546
  try {
494
- const testRows = db.query<Record<string, unknown>>(
547
+ await adapterQuery<Record<string, unknown>>(db,
495
548
  `SELECT * FROM "${MIGRATION_TABLE}" LIMIT 0`,
496
549
  );
497
550
  // Check column names by querying pragma or just try adding batch
@@ -510,7 +563,7 @@ export async function migrate(
510
563
  // Determine the batch number for this run
511
564
  let currentBatch = 1;
512
565
  try {
513
- const batchRows = db.query<{ max_batch: number | null }>(
566
+ const batchRows = await adapterQuery<{ max_batch: number | null }>(db,
514
567
  `SELECT MAX(batch) as max_batch FROM "${MIGRATION_TABLE}"`,
515
568
  );
516
569
  currentBatch = (batchRows[0]?.max_batch ?? 0) + 1;
@@ -525,7 +578,7 @@ export async function migrate(
525
578
  // Check if already applied — support both 'name' and legacy 'description' column
526
579
  let alreadyApplied = false;
527
580
  try {
528
- const existing = db.query<{ id: number }>(
581
+ const existing = await adapterQuery<{ id: number }>(db,
529
582
  `SELECT id FROM "${MIGRATION_TABLE}" WHERE name = ?`,
530
583
  [migrationId],
531
584
  );
@@ -533,7 +586,7 @@ export async function migrate(
533
586
  } catch {
534
587
  // Might be old schema with 'description' column instead of 'name'
535
588
  try {
536
- const existing = db.query<{ id: number; passed: number }>(
589
+ const existing = await adapterQuery<{ id: number; passed: number }>(db,
537
590
  `SELECT id, passed FROM "${MIGRATION_TABLE}" WHERE description = ?`,
538
591
  [migrationId],
539
592
  );
@@ -541,7 +594,7 @@ export async function migrate(
541
594
  alreadyApplied = true;
542
595
  } else if (existing.length > 0 && existing[0].passed === 0) {
543
596
  // Failed record from old schema — remove to retry
544
- db.execute(
597
+ await adapterExecute(db,
545
598
  `DELETE FROM "${MIGRATION_TABLE}" WHERE description = ?`,
546
599
  [migrationId],
547
600
  );
@@ -565,7 +618,7 @@ export async function migrate(
565
618
  const statements = splitStatements(sqlContent, delimiter);
566
619
 
567
620
  try {
568
- db.startTransaction();
621
+ await adapterStartTransaction(db);
569
622
 
570
623
  for (const stmt of statements) {
571
624
  // Firebird lacks IF NOT EXISTS for ALTER TABLE ADD.
@@ -576,7 +629,7 @@ export async function migrate(
576
629
  console.log(` Migration ${file}: ${skipReason}`);
577
630
  continue;
578
631
  }
579
- db.execute(stmt);
632
+ await adapterExecute(db, stmt);
580
633
  }
581
634
 
582
635
  // Record as applied with batch number
@@ -584,33 +637,33 @@ export async function migrate(
584
637
  try {
585
638
  if (isFirebirdAdapter(db)) {
586
639
  // Firebird: generate ID from sequence
587
- const idRows = db.query<{ NEXT_ID: number }>(
640
+ const idRows = await adapterQuery<{ NEXT_ID: number }>(db,
588
641
  "SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS NEXT_ID FROM RDB$DATABASE",
589
642
  );
590
643
  const nextId = idRows[0]?.NEXT_ID ?? 1;
591
- db.execute(
644
+ await adapterExecute(db,
592
645
  `INSERT INTO "${MIGRATION_TABLE}" (id, name, batch, applied_at) VALUES (?, ?, ?, ?)`,
593
646
  [nextId, migrationId, currentBatch, now],
594
647
  );
595
648
  } else {
596
- db.execute(
649
+ await adapterExecute(db,
597
650
  `INSERT INTO "${MIGRATION_TABLE}" (name, batch, applied_at) VALUES (?, ?, ?)`,
598
651
  [migrationId, currentBatch, now],
599
652
  );
600
653
  }
601
654
  } catch {
602
655
  // Old schema fallback — try description/content/passed columns
603
- db.execute(
656
+ await adapterExecute(db,
604
657
  `INSERT INTO "${MIGRATION_TABLE}" (description, content, passed, run_at) VALUES (?, ?, 1, ?)`,
605
658
  [migrationId, sqlContent, now],
606
659
  );
607
660
  }
608
661
 
609
- db.commit();
662
+ await adapterCommit(db);
610
663
  result.applied.push(file);
611
664
  } catch (err) {
612
665
  try {
613
- db.rollback();
666
+ await adapterRollback(db);
614
667
  } catch {
615
668
  // rollback may fail if transaction was auto-rolled-back
616
669
  }
@@ -646,7 +699,7 @@ export async function status(
646
699
  }
647
700
 
648
701
  // Ensure tracking table exists
649
- if (!db.tableExists(MIGRATION_TABLE)) {
702
+ if (!(await adapterTableExists(db, MIGRATION_TABLE))) {
650
703
  // No table means nothing has been run — all files are pending
651
704
  const files = sortMigrationFiles(
652
705
  readdirSync(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql")),
@@ -663,7 +716,7 @@ export async function status(
663
716
  // Get all applied migration names from the DB
664
717
  const appliedNames = new Set<string>();
665
718
  try {
666
- const rows = db.query<{ name: string }>(
719
+ const rows = await adapterQuery<{ name: string }>(db,
667
720
  `SELECT name FROM "${MIGRATION_TABLE}"`,
668
721
  );
669
722
  for (const row of rows) {
@@ -672,7 +725,7 @@ export async function status(
672
725
  } catch {
673
726
  // Old schema with 'description' column
674
727
  try {
675
- const rows = db.query<{ description: string; passed: number }>(
728
+ const rows = await adapterQuery<{ description: string; passed: number }>(db,
676
729
  `SELECT description, passed FROM "${MIGRATION_TABLE}" WHERE passed = 1`,
677
730
  );
678
731
  for (const row of rows) {
@@ -848,10 +901,10 @@ export class Migration {
848
901
  async rollback(steps = 1): Promise<string[]> {
849
902
  const db = this.db ?? (await import("./database.js")).getAdapter();
850
903
  // If tracking table doesn't exist yet there's nothing to roll back
851
- if (!db.tableExists(MIGRATION_TABLE)) return [];
904
+ if (!(await adapterTableExists(db, MIGRATION_TABLE))) return [];
852
905
  const rolled: string[] = [];
853
906
  for (let i = 0; i < steps; i++) {
854
- const batch = rollback(this.dir, this.delimiter);
907
+ const batch = await rollback(this.dir, this.delimiter);
855
908
  if (batch.length === 0) break;
856
909
  rolled.push(...batch);
857
910
  }
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import type { DatabaseAdapter } from "./types.js";
21
- import { getAdapter } from "./database.js";
21
+ import { getAdapter, adapterFetch, adapterFetchOne } from "./database.js";
22
22
 
23
23
  export class QueryBuilder {
24
24
  private table: string;
@@ -202,12 +202,13 @@ export class QueryBuilder {
202
202
  *
203
203
  * @returns Array of row objects.
204
204
  */
205
- get<T = Record<string, unknown>>(): T[] {
205
+ async get<T = Record<string, unknown>>(): Promise<T[]> {
206
206
  this.ensureDb();
207
207
  const sql = this.toSql();
208
208
  const allParams = [...this.params, ...this.havingParams];
209
209
 
210
- return this.db!.fetch<T>(
210
+ return adapterFetch<T>(
211
+ this.db!,
211
212
  sql,
212
213
  allParams.length > 0 ? allParams : undefined,
213
214
  this.limitVal,
@@ -220,12 +221,13 @@ export class QueryBuilder {
220
221
  *
221
222
  * @returns A single row object, or null.
222
223
  */
223
- first<T = Record<string, unknown>>(): T | null {
224
+ async first<T = Record<string, unknown>>(): Promise<T | null> {
224
225
  this.ensureDb();
225
226
  const sql = this.toSql();
226
227
  const allParams = [...this.params, ...this.havingParams];
227
228
 
228
- return this.db!.fetchOne<T>(
229
+ return adapterFetchOne<T>(
230
+ this.db!,
229
231
  sql,
230
232
  allParams.length > 0 ? allParams : undefined,
231
233
  );
@@ -236,7 +238,7 @@ export class QueryBuilder {
236
238
  *
237
239
  * @returns Number of matching rows.
238
240
  */
239
- count(): number {
241
+ async count(): Promise<number> {
240
242
  this.ensureDb();
241
243
 
242
244
  // Build a count query by replacing columns
@@ -247,7 +249,8 @@ export class QueryBuilder {
247
249
 
248
250
  const allParams = [...this.params, ...this.havingParams];
249
251
 
250
- const row = this.db!.fetchOne<Record<string, unknown>>(
252
+ const row = await adapterFetchOne<Record<string, unknown>>(
253
+ this.db!,
251
254
  sql,
252
255
  allParams.length > 0 ? allParams : undefined,
253
256
  );
@@ -264,8 +267,8 @@ export class QueryBuilder {
264
267
  *
265
268
  * @returns True if at least one row matches.
266
269
  */
267
- exists(): boolean {
268
- return this.count() > 0;
270
+ async exists(): Promise<boolean> {
271
+ return (await this.count()) > 0;
269
272
  }
270
273
 
271
274
  /**
@@ -2,6 +2,7 @@
2
2
  // Zero external dependencies.
3
3
 
4
4
  import { FakeData } from "./fakeData.js";
5
+ import { adapterExecute } from "./database.js";
5
6
  import type { DatabaseAdapter, FieldDefinition } from "./types.js";
6
7
 
7
8
  /**
@@ -54,7 +55,7 @@ export async function seedTable(
54
55
  const placeholders = columns.map(() => "?").join(", ");
55
56
  const values = columns.map((c) => row[c]);
56
57
 
57
- db.execute(
58
+ await adapterExecute(db,
58
59
  `INSERT INTO "${tableName}" (${colList}) VALUES (${placeholders})`,
59
60
  values,
60
61
  );