@remix-run/data-table 0.3.0 → 0.4.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.
Files changed (82) hide show
  1. package/README.md +205 -79
  2. package/dist/cli.d.ts +54 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +53 -0
  5. package/dist/index.d.ts +5 -4
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +7 -7
  8. package/dist/lib/column.d.ts +1 -1
  9. package/dist/lib/column.d.ts.map +1 -1
  10. package/dist/lib/database/execution-context.d.ts +5 -5
  11. package/dist/lib/database/execution-context.d.ts.map +1 -1
  12. package/dist/lib/database/execution-context.js +1 -0
  13. package/dist/lib/database/helpers.js +6 -6
  14. package/dist/lib/database/query-execution.d.ts.map +1 -1
  15. package/dist/lib/database/query-execution.js +17 -17
  16. package/dist/lib/database/relations.js +5 -5
  17. package/dist/lib/database/write-lifecycle.d.ts +2 -2
  18. package/dist/lib/database/write-lifecycle.d.ts.map +1 -1
  19. package/dist/lib/database/write-lifecycle.js +5 -5
  20. package/dist/lib/database.d.ts +74 -59
  21. package/dist/lib/database.d.ts.map +1 -1
  22. package/dist/lib/database.js +176 -83
  23. package/dist/lib/{adapter.d.ts → driver.d.ts} +46 -48
  24. package/dist/lib/driver.d.ts.map +1 -0
  25. package/dist/lib/errors.d.ts +2 -2
  26. package/dist/lib/errors.d.ts.map +1 -1
  27. package/dist/lib/errors.js +4 -4
  28. package/dist/lib/migrations/journal-store.d.ts +6 -6
  29. package/dist/lib/migrations/journal-store.d.ts.map +1 -1
  30. package/dist/lib/migrations/journal-store.js +20 -11
  31. package/dist/lib/migrations/runner.d.ts +12 -19
  32. package/dist/lib/migrations/runner.d.ts.map +1 -1
  33. package/dist/lib/migrations/runner.js +151 -129
  34. package/dist/lib/migrations-node.d.ts +19 -1
  35. package/dist/lib/migrations-node.d.ts.map +1 -1
  36. package/dist/lib/migrations-node.js +22 -1
  37. package/dist/lib/migrations.d.ts +55 -20
  38. package/dist/lib/migrations.d.ts.map +1 -1
  39. package/dist/lib/operators.d.ts +1 -1
  40. package/dist/lib/operators.js +1 -1
  41. package/dist/lib/query.d.ts +1 -1
  42. package/dist/lib/query.d.ts.map +1 -1
  43. package/dist/lib/query.js +4 -4
  44. package/dist/lib/sql-helpers.d.ts +1 -1
  45. package/dist/lib/sql-helpers.d.ts.map +1 -1
  46. package/dist/lib/sql.d.ts +1 -1
  47. package/dist/lib/sql.js +1 -1
  48. package/dist/lib/table.d.ts +1 -1
  49. package/dist/lib/table.d.ts.map +1 -1
  50. package/dist/lib/table.js +5 -5
  51. package/dist/migrations/node.d.ts +1 -1
  52. package/dist/migrations/node.d.ts.map +1 -1
  53. package/dist/migrations/node.js +1 -1
  54. package/dist/migrations.d.ts +1 -2
  55. package/dist/migrations.d.ts.map +1 -1
  56. package/dist/migrations.js +2 -3
  57. package/dist/operators.js +1 -1
  58. package/dist/sql-helpers.js +1 -1
  59. package/package.json +13 -9
  60. package/src/cli.ts +120 -0
  61. package/src/index.ts +19 -20
  62. package/src/lib/column.ts +1 -1
  63. package/src/lib/database/execution-context.ts +10 -6
  64. package/src/lib/database/helpers.ts +1 -1
  65. package/src/lib/database/query-execution.ts +15 -11
  66. package/src/lib/database/write-lifecycle.ts +4 -4
  67. package/src/lib/database.ts +223 -96
  68. package/src/lib/{adapter.ts → driver.ts} +48 -48
  69. package/src/lib/errors.ts +4 -4
  70. package/src/lib/migrations/journal-store.ts +21 -11
  71. package/src/lib/migrations/runner.ts +200 -147
  72. package/src/lib/migrations-node.ts +23 -1
  73. package/src/lib/migrations.ts +58 -19
  74. package/src/lib/operators.ts +1 -1
  75. package/src/lib/query.ts +1 -1
  76. package/src/lib/sql-helpers.ts +1 -1
  77. package/src/lib/sql.ts +1 -1
  78. package/src/lib/table.ts +1 -1
  79. package/src/migrations/node.ts +1 -1
  80. package/src/migrations.ts +3 -4
  81. package/dist/lib/adapter.d.ts.map +0 -1
  82. /package/dist/lib/{adapter.js → driver.js} +0 -0
package/src/index.ts CHANGED
@@ -1,37 +1,23 @@
1
1
  export type {
2
- AdapterCapabilityOverrides,
3
- AdapterCapabilities,
2
+ DatabaseCapabilities,
3
+ DatabaseDriver,
4
+ DataManipulationOperation,
4
5
  DataManipulationRequest,
5
6
  ColumnCheck,
6
7
  ColumnComputed,
7
8
  ColumnDefault,
8
9
  ColumnDefinition,
9
10
  ColumnTypeName,
10
- CountOperation,
11
11
  DataManipulationResult,
12
- DataManipulationOperation,
13
- DeleteOperation,
14
- DatabaseAdapter,
15
- ExistsOperation,
16
12
  ForeignKeyAction,
17
13
  IdentityOptions,
18
- InsertManyOperation,
19
- InsertOperation,
20
- JoinClause,
21
- JoinType,
22
- RawOperation,
23
- ReturningSelection,
24
- SelectColumn,
25
- SelectOperation,
26
14
  TableRef,
27
15
  TransactionOptions,
28
16
  TransactionToken,
29
- UpdateOperation,
30
- UpsertOperation,
31
- } from './lib/adapter.ts'
17
+ } from './lib/driver.ts'
32
18
 
33
19
  export {
34
- DataTableAdapterError,
20
+ DataTableDatabaseError,
35
21
  DataTableConstraintError,
36
22
  DataTableError,
37
23
  DataTableQueryError,
@@ -145,6 +131,7 @@ export type {
145
131
  CreateResultOptions,
146
132
  CreateRowOptions,
147
133
  DeleteManyOptions,
134
+ DatabaseOptions,
148
135
  FindManyOptions,
149
136
  FindOneOptions,
150
137
  OrderByInput,
@@ -160,6 +147,18 @@ export type {
160
147
  WriteRowResult,
161
148
  WriteRowsResult,
162
149
  } from './lib/database.ts'
163
- export { createDatabase, Database } from './lib/database.ts'
150
+ export { Database } from './lib/database.ts'
151
+ export type {
152
+ DatabaseMigrateOptions,
153
+ DatabaseMigrationStatusOptions,
154
+ DatabaseResetOptions,
155
+ MigrateResult,
156
+ MigrationDescriptor,
157
+ MigrationRegistry,
158
+ Migrations,
159
+ MigrationStatus,
160
+ MigrationStatusEntry,
161
+ Seed,
162
+ } from './lib/migrations.ts'
164
163
  export type { AnyQuery } from './lib/query.ts'
165
164
  export { Query, query } from './lib/query.ts'
package/src/lib/column.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ColumnDefinition, ForeignKeyAction, IdentityOptions, TableRef } from './adapter.ts'
1
+ import type { ColumnDefinition, ForeignKeyAction, IdentityOptions, TableRef } from './driver.ts'
2
2
 
3
3
  function toTableRef(name: string): TableRef {
4
4
  let segments = name.split('.')
@@ -1,15 +1,19 @@
1
1
  import type {
2
+ DatabaseCapabilities,
2
3
  DataManipulationOperation,
3
4
  DataManipulationResult,
4
- DatabaseAdapter,
5
- } from '../adapter.ts'
6
- import type { Database } from '../database.ts'
5
+ TransactionOptions,
6
+ } from '../driver.ts'
7
7
 
8
8
  export const executeOperation = Symbol('executeOperation')
9
+ export const runInTransaction = Symbol('runInTransaction')
9
10
 
10
- export type QueryExecutionContext = {
11
- adapter: DatabaseAdapter
11
+ export type QueryExecutionContext<dialect extends string = string> = {
12
+ capabilities: DatabaseCapabilities
12
13
  now(): unknown
13
- transaction<result>(callback: (database: Database) => Promise<result>): Promise<result>
14
14
  [executeOperation](operation: DataManipulationOperation): Promise<DataManipulationResult>
15
+ [runInTransaction]<result>(
16
+ callback: (database: QueryExecutionContext<dialect>) => Promise<result>,
17
+ options?: TransactionOptions,
18
+ ): Promise<result>
15
19
  }
@@ -80,7 +80,7 @@ export function resolveCreateRowWhere<table extends AnyTable>(
80
80
  throw new DataTableQueryError(
81
81
  'create({ returnRow: true }) requires primary key values for table "' +
82
82
  getTableName(table) +
83
- '" when adapter does not support RETURNING',
83
+ '" when the database does not support RETURNING',
84
84
  )
85
85
  }
86
86
 
@@ -9,7 +9,7 @@ import type {
9
9
  SelectOperation,
10
10
  UpdateOperation,
11
11
  UpsertOperation,
12
- } from '../adapter.ts'
12
+ } from '../driver.ts'
13
13
  import { DataTableQueryError } from '../errors.ts'
14
14
  import type { ReturningInput, WriteResult, WriteRowResult, WriteRowsResult } from '../database.ts'
15
15
  import { normalizeWhereInput } from '../operators.ts'
@@ -18,7 +18,11 @@ import { cloneQueryState, querySnapshot } from '../query.ts'
18
18
  import type { AnyTable } from '../table.ts'
19
19
  import { getPrimaryKeyObject, getTableName } from '../table.ts'
20
20
 
21
- import { executeOperation, type QueryExecutionContext } from './execution-context.ts'
21
+ import {
22
+ executeOperation,
23
+ runInTransaction,
24
+ type QueryExecutionContext,
25
+ } from './execution-context.ts'
22
26
  import {
23
27
  buildPrimaryKeyPredicate,
24
28
  hasScopedWriteModifiers,
@@ -244,7 +248,7 @@ async function executeInsert(
244
248
  )
245
249
  let returning = options?.returning
246
250
 
247
- assertReturningCapability(database.adapter, 'insert', returning)
251
+ assertReturningCapability(database.capabilities, 'insert', returning)
248
252
 
249
253
  if (returning) {
250
254
  let operation: InsertOperation<AnyTable> = {
@@ -314,7 +318,7 @@ async function executeInsertMany(
314
318
  }
315
319
 
316
320
  let returning = options?.returning
317
- assertReturningCapability(database.adapter, 'insertMany', returning)
321
+ assertReturningCapability(database.capabilities, 'insertMany', returning)
318
322
 
319
323
  if (returning) {
320
324
  let operation: InsertManyOperation<AnyTable> = {
@@ -371,7 +375,7 @@ async function executeUpdate(
371
375
  options?: { returning?: ReturningInput<Record<string, unknown>>; touch?: boolean },
372
376
  ): Promise<WriteResult | WriteRowsResult<Record<string, unknown>>> {
373
377
  let returning = options?.returning
374
- assertReturningCapability(database.adapter, 'update', returning)
378
+ assertReturningCapability(database.capabilities, 'update', returning)
375
379
  let preparedChanges = prepareUpdateValues(
376
380
  table,
377
381
  changes as never,
@@ -386,7 +390,7 @@ async function executeUpdate(
386
390
  let result: DataManipulationResult
387
391
 
388
392
  if (hasScopedWriteModifiers(state)) {
389
- result = await database.transaction(async (tx) => {
393
+ result = await database[runInTransaction](async (tx) => {
390
394
  let primaryKeys = await loadPrimaryKeyRowsForScope(tx, table, state)
391
395
  let primaryKeyPredicate = buildPrimaryKeyPredicate(table, primaryKeys)
392
396
 
@@ -448,7 +452,7 @@ async function executeDelete(
448
452
  options?: { returning?: ReturningInput<Record<string, unknown>> },
449
453
  ): Promise<WriteResult | WriteRowsResult<Record<string, unknown>>> {
450
454
  let returning = options?.returning
451
- assertReturningCapability(database.adapter, 'delete', returning)
455
+ assertReturningCapability(database.capabilities, 'delete', returning)
452
456
  let tableName = getTableName(table)
453
457
  let deleteContext = {
454
458
  tableName,
@@ -462,7 +466,7 @@ async function executeDelete(
462
466
  let result: DataManipulationResult
463
467
 
464
468
  if (hasScopedWriteModifiers(state)) {
465
- result = await database.transaction(async (tx) => {
469
+ result = await database[runInTransaction](async (tx) => {
466
470
  let primaryKeys = await loadPrimaryKeyRowsForScope(tx, table, state)
467
471
  let primaryKeyPredicate = buildPrimaryKeyPredicate(table, primaryKeys)
468
472
 
@@ -527,8 +531,8 @@ async function executeUpsert(
527
531
  update?: Record<string, unknown>
528
532
  },
529
533
  ): Promise<WriteResult | WriteRowResult<Record<string, unknown>>> {
530
- if (!database.adapter.capabilities.upsert) {
531
- throw new DataTableQueryError('Adapter does not support upsert')
534
+ if (!database.capabilities.upsert) {
535
+ throw new DataTableQueryError('Database does not support upsert')
532
536
  }
533
537
 
534
538
  let preparedValues = prepareInsertValues(
@@ -547,7 +551,7 @@ async function executeUpsert(
547
551
  )
548
552
  : undefined
549
553
  let returning = options?.returning
550
- assertReturningCapability(database.adapter, 'upsert', returning)
554
+ assertReturningCapability(database.capabilities, 'upsert', returning)
551
555
 
552
556
  if (returning) {
553
557
  let operation: UpsertOperation<AnyTable> = {
@@ -1,4 +1,4 @@
1
- import type { DatabaseAdapter, ReturningSelection } from '../adapter.ts'
1
+ import type { DatabaseCapabilities, ReturningSelection } from '../driver.ts'
2
2
  import { DataTableQueryError, DataTableValidationError } from '../errors.ts'
3
3
  import type { ReturningInput } from '../database.ts'
4
4
  import type {
@@ -249,12 +249,12 @@ export function runAfterDeleteHook<table extends AnyTable>(
249
249
  }
250
250
 
251
251
  export function assertReturningCapability<row extends Record<string, unknown>>(
252
- adapter: DatabaseAdapter,
252
+ capabilities: DatabaseCapabilities,
253
253
  operation: 'insert' | 'insertMany' | 'update' | 'delete' | 'upsert',
254
254
  returning: ReturningInput<row> | undefined,
255
255
  ): void {
256
- if (returning && !adapter.capabilities.returning) {
257
- throw new DataTableQueryError(operation + '() returning is not supported by this adapter')
256
+ if (returning && !capabilities.returning) {
257
+ throw new DataTableQueryError(operation + '() returning is not supported by this database')
258
258
  }
259
259
  }
260
260
 
@@ -2,13 +2,19 @@ import type {
2
2
  ColumnDefinition,
3
3
  DataManipulationOperation,
4
4
  DataManipulationResult,
5
- DatabaseAdapter,
5
+ DatabaseCapabilities,
6
+ DatabaseDriver,
7
+ TableRef,
6
8
  TransactionOptions,
7
9
  TransactionToken,
8
- } from './adapter.ts'
10
+ } from './driver.ts'
9
11
  import type { ColumnBuilder } from './column.ts'
10
- import { DataTableAdapterError, DataTableQueryError } from './errors.ts'
11
- import { executeOperation, type QueryExecutionContext } from './database/execution-context.ts'
12
+ import { DataTableDatabaseError, DataTableQueryError } from './errors.ts'
13
+ import {
14
+ executeOperation,
15
+ runInTransaction,
16
+ type QueryExecutionContext,
17
+ } from './database/execution-context.ts'
12
18
  import {
13
19
  asQueryTableInput,
14
20
  getPrimaryKeyWhere,
@@ -26,6 +32,15 @@ import type {
26
32
  } from './query.ts'
27
33
  import { bindQueryRuntime, query as createQuery } from './query.ts'
28
34
  import type { ColumnInput, NormalizeColumnInput, TableMetadataLike } from './references.ts'
35
+ import type {
36
+ DatabaseMigrateOptions,
37
+ DatabaseMigrationStatusOptions,
38
+ DatabaseResetOptions,
39
+ MigrateResult,
40
+ Migrations,
41
+ MigrationStatusEntry,
42
+ } from './migrations.ts'
43
+ import { createMigrationRunner } from './migrations/runner.ts'
29
44
  import type { SqlStatement } from './sql.ts'
30
45
  import { isSqlStatement, rawSql } from './sql.ts'
31
46
  import type {
@@ -327,7 +342,9 @@ type SavepointCounter = {
327
342
  value: number
328
343
  }
329
344
 
330
- type DatabaseOptions = {
345
+ /** Options shared by database instances. */
346
+ export interface DatabaseOptions {
347
+ /** Clock function used for auto-managed timestamps. */
331
348
  now?: () => unknown
332
349
  }
333
350
 
@@ -336,39 +353,168 @@ type DatabaseInternalState = {
336
353
  savepointCounter: SavepointCounter
337
354
  }
338
355
 
339
- const createInternalDatabase = Symbol('createInternalDatabase')
340
-
341
356
  /**
342
- * High-level database runtime used to build and execute data manipulation operations.
357
+ * High-level database runtime used to query and manage a database.
343
358
  *
344
- * Create instances directly with `new Database(adapter, options)` or use
345
- * `createDatabase(adapter, options)` as a thin wrapper.
359
+ * Database dialects extend this class and provide a {@link DatabaseDriver} to the constructor.
360
+ * The driver owns SQL execution, transactions, and connection lifecycle while this class provides
361
+ * the shared query, persistence, and migration APIs.
346
362
  */
347
- export class Database implements QueryExecutionContext {
348
- #adapter: DatabaseAdapter
363
+ export class Database<dialect extends string = string> {
364
+ #driver: DatabaseDriver<dialect>
365
+ #executionContext: QueryExecutionContext<dialect>
349
366
  #token?: TransactionToken
350
367
  #now: () => unknown
351
368
  #savepointCounter: SavepointCounter
352
369
 
353
- constructor(adapter: DatabaseAdapter, options?: DatabaseOptions) {
354
- this.#adapter = adapter
370
+ /**
371
+ * Creates a database backed by a driver.
372
+ * @param driver Low-level database engine integration.
373
+ * @param options Database runtime options.
374
+ */
375
+ constructor(driver: DatabaseDriver<dialect>, options?: DatabaseOptions) {
376
+ this.#driver = driver
355
377
  this.#now = options?.now ?? defaultNow
356
378
  this.#savepointCounter = { value: 0 }
379
+
380
+ let database = this
381
+ this.#executionContext = {
382
+ get capabilities() {
383
+ return database.capabilities
384
+ },
385
+ now() {
386
+ return database.now()
387
+ },
388
+ [executeOperation](operation) {
389
+ return database.#executeOperation(operation)
390
+ },
391
+ [runInTransaction](callback, transactionOptions) {
392
+ return database.#runInTransaction(
393
+ (transactionDatabase) => callback(transactionDatabase.#executionContext),
394
+ transactionOptions,
395
+ )
396
+ },
397
+ }
357
398
  }
358
399
 
359
- static [createInternalDatabase](
360
- adapter: DatabaseAdapter,
400
+ static #createInternalDatabase<dialect extends string>(
401
+ driver: DatabaseDriver<dialect>,
361
402
  options: DatabaseOptions | undefined,
362
403
  internal: DatabaseInternalState,
363
- ): Database {
364
- let database = new Database(adapter, options)
404
+ ): Database<dialect> {
405
+ let database = new Database(driver, options)
365
406
  database.#token = internal.token
366
407
  database.#savepointCounter = internal.savepointCounter
367
408
  return database
368
409
  }
369
410
 
370
- get adapter(): DatabaseAdapter {
371
- return this.#adapter
411
+ /** Stable identifier for the SQL dialect. */
412
+ get dialect(): dialect {
413
+ return this.#driver.dialect
414
+ }
415
+
416
+ /** Immutable feature flags used by shared query and migration behavior. */
417
+ get capabilities(): DatabaseCapabilities {
418
+ return this.#driver.capabilities
419
+ }
420
+
421
+ /**
422
+ * Executes a migration or raw multi-statement SQL script.
423
+ * @param sql SQL script to execute.
424
+ * @returns A promise that resolves when execution completes.
425
+ */
426
+ executeScript(sql: string): Promise<void> {
427
+ return this.#driver.executeScript(sql, this.#token)
428
+ }
429
+
430
+ /**
431
+ * Reports whether a table exists.
432
+ * @param table Table to inspect.
433
+ * @returns A promise that resolves to `true` when the table exists.
434
+ */
435
+ hasTable(table: TableRef): Promise<boolean> {
436
+ return this.#driver.hasTable(table, this.#token)
437
+ }
438
+
439
+ /**
440
+ * Reports whether a column exists on a table.
441
+ * @param table Table to inspect.
442
+ * @param column Column name to inspect.
443
+ * @returns A promise that resolves to `true` when the column exists.
444
+ */
445
+ hasColumn(table: TableRef, column: string): Promise<boolean> {
446
+ return this.#driver.hasColumn(table, column, this.#token)
447
+ }
448
+
449
+ /**
450
+ * Closes resources owned by this database.
451
+ * @returns A promise that resolves when owned resources have been released.
452
+ */
453
+ async close(): Promise<void> {
454
+ this.#assertLifecycleOperationAllowed('close')
455
+ await this.#driver.close()
456
+ }
457
+
458
+ /**
459
+ * Destructively recreates the configured database.
460
+ * @returns A promise that resolves when the database is ready for use.
461
+ */
462
+ async wipe(): Promise<void> {
463
+ this.#assertLifecycleOperationAllowed('wipe')
464
+ await this.#driver.wipe()
465
+ }
466
+
467
+ /**
468
+ * Applies or reverts migrations in order.
469
+ *
470
+ * @param migrations Migration descriptors or registry to apply.
471
+ * @param options Migration direction, bound, dry-run, and journal configuration.
472
+ * @returns The migrations applied or reverted by this run and their SQL scripts.
473
+ */
474
+ async migrate(migrations: Migrations, options?: DatabaseMigrateOptions): Promise<MigrateResult> {
475
+ this.#assertLifecycleOperationAllowed('migrate')
476
+ let { direction = 'up', journalTable, ...migrateOptions } = options ?? {}
477
+ let runner = createMigrationRunner(this.#driver, migrations, { journalTable })
478
+ return direction === 'up' ? runner.up(migrateOptions) : runner.down(migrateOptions)
479
+ }
480
+
481
+ /**
482
+ * Reports the current state of the provided migrations.
483
+ *
484
+ * @param migrations Migration descriptors or registry to inspect.
485
+ * @param options Migration journal configuration.
486
+ * @returns Status entries for the provided migrations.
487
+ */
488
+ async migrationStatus(
489
+ migrations: Migrations,
490
+ options: DatabaseMigrationStatusOptions = {},
491
+ ): Promise<MigrationStatusEntry[]> {
492
+ this.#assertLifecycleOperationAllowed('migrationStatus')
493
+ let runner = createMigrationRunner(this.#driver, migrations, options)
494
+ return runner.status()
495
+ }
496
+
497
+ /**
498
+ * Wipes the database, applies migrations, and optionally seeds data.
499
+ *
500
+ * @param options Migrations and optional seed function used to rebuild the database.
501
+ * @returns A promise that resolves when the database has been rebuilt.
502
+ */
503
+ async reset(options: DatabaseResetOptions): Promise<void> {
504
+ this.#assertLifecycleOperationAllowed('reset')
505
+ await this.wipe()
506
+ await this.migrate(options.migrations, { journalTable: options.journalTable })
507
+ await options.seed?.(this)
508
+ }
509
+
510
+ #assertLifecycleOperationAllowed(
511
+ method: 'close' | 'migrate' | 'migrationStatus' | 'reset' | 'wipe',
512
+ ): void {
513
+ if (this.#token) {
514
+ throw new DataTableQueryError(
515
+ 'Cannot call ' + method + '() from a transaction-scoped database',
516
+ )
517
+ }
372
518
  }
373
519
 
374
520
  now(): unknown {
@@ -423,7 +569,7 @@ export class Database implements QueryExecutionContext {
423
569
  return toWriteResult(result)
424
570
  }
425
571
 
426
- if (this.#adapter.capabilities.returning) {
572
+ if (this.capabilities.returning) {
427
573
  let result = (await query.insert(values, {
428
574
  returning: '*',
429
575
  touch,
@@ -485,9 +631,9 @@ export class Database implements QueryExecutionContext {
485
631
  let query: QueryForTable<table> = this.query(asQueryTableInput(table))
486
632
 
487
633
  if (options?.returnRows === true) {
488
- if (!this.#adapter.capabilities.returning) {
634
+ if (!this.capabilities.returning) {
489
635
  throw new DataTableQueryError(
490
- 'createMany({ returnRows: true }) is not supported by this adapter',
636
+ 'createMany({ returnRows: true }) is not supported by this database',
491
637
  )
492
638
  }
493
639
 
@@ -616,7 +762,7 @@ export class Database implements QueryExecutionContext {
616
762
  ): Promise<TableRowWith<table, LoadedRelationMap<relations>>> {
617
763
  let where = getPrimaryKeyWhere(table, value)
618
764
 
619
- if (this.#adapter.capabilities.returning) {
765
+ if (this.capabilities.returning) {
620
766
  let updateResult = (await this.query(asQueryTableInput(table)).where(where).update(changes, {
621
767
  touch: options?.touch,
622
768
  returning: '*',
@@ -729,23 +875,30 @@ export class Database implements QueryExecutionContext {
729
875
  ? statementOrInput
730
876
  : rawSql(statementOrInput, values)
731
877
 
732
- return this[executeOperation]({
878
+ return this.#executeOperation({
733
879
  kind: 'raw',
734
880
  sql: sqlStatement,
735
881
  })
736
882
  }
737
883
 
738
- return executeQuery(this, statementOrInput)
884
+ return executeQuery(this.#executionContext, statementOrInput)
739
885
  }
740
886
 
741
887
  async transaction<result>(
742
- callback: (database: Database) => Promise<result>,
888
+ callback: (database: Database<dialect>) => Promise<result>,
889
+ options?: TransactionOptions,
890
+ ): Promise<result> {
891
+ return this.#runInTransaction(callback, options)
892
+ }
893
+
894
+ async #runInTransaction<result>(
895
+ callback: (database: Database<dialect>) => Promise<result>,
743
896
  options?: TransactionOptions,
744
897
  ): Promise<result> {
745
898
  if (!this.#token) {
746
- let token = await this.#adapter.beginTransaction(options)
747
- let tx = Database[createInternalDatabase](
748
- this.#adapter,
899
+ let token = await this.#driver.beginTransaction(options)
900
+ let tx = Database.#createInternalDatabase(
901
+ this.#driver,
749
902
  { now: this.#now },
750
903
  {
751
904
  token,
@@ -753,47 +906,71 @@ export class Database implements QueryExecutionContext {
753
906
  },
754
907
  )
755
908
 
909
+ let result: result
756
910
  try {
757
- let result = await callback(tx)
758
- await this.#adapter.commitTransaction(token)
759
- return result
911
+ result = await callback(tx)
760
912
  } catch (error) {
761
- await this.#adapter.rollbackTransaction(token)
913
+ try {
914
+ await this.#driver.rollbackTransaction(token)
915
+ } catch (rollbackError) {
916
+ throw new AggregateError(
917
+ [error, rollbackError],
918
+ 'Database transaction and rollback both failed',
919
+ { cause: error },
920
+ )
921
+ }
762
922
  throw error
763
923
  }
924
+
925
+ await this.#driver.commitTransaction(token)
926
+ return result
764
927
  }
765
928
 
766
- if (!this.#adapter.capabilities.savepoints) {
767
- throw new DataTableQueryError('Nested transactions require adapter savepoint support')
929
+ if (!this.capabilities.savepoints) {
930
+ throw new DataTableQueryError('Nested transactions require database savepoint support')
768
931
  }
769
932
 
770
933
  let savepointName = 'sp_' + String(this.#savepointCounter.value)
771
934
  this.#savepointCounter.value += 1
772
935
 
773
- await this.#adapter.createSavepoint(this.#token, savepointName)
936
+ await this.#driver.createSavepoint(this.#token, savepointName)
774
937
 
938
+ let result: result
775
939
  try {
776
- let result = await callback(this)
777
- await this.#adapter.releaseSavepoint(this.#token, savepointName)
778
- return result
940
+ result = await callback(this)
779
941
  } catch (error) {
780
- await this.#adapter.rollbackToSavepoint(this.#token, savepointName)
781
- await this.#adapter.releaseSavepoint(this.#token, savepointName)
942
+ let failures: unknown[] = [error]
943
+ try {
944
+ await this.#driver.rollbackToSavepoint(this.#token, savepointName)
945
+ } catch (rollbackError) {
946
+ failures.push(rollbackError)
947
+ }
948
+ try {
949
+ await this.#driver.releaseSavepoint(this.#token, savepointName)
950
+ } catch (releaseError) {
951
+ failures.push(releaseError)
952
+ }
953
+ if (failures.length > 1) {
954
+ throw new AggregateError(failures, 'Nested transaction cleanup failed', { cause: error })
955
+ }
782
956
  throw error
783
957
  }
958
+
959
+ await this.#driver.releaseSavepoint(this.#token, savepointName)
960
+ return result
784
961
  }
785
962
 
786
- async [executeOperation](operation: DataManipulationOperation): Promise<DataManipulationResult> {
963
+ async #executeOperation(operation: DataManipulationOperation): Promise<DataManipulationResult> {
787
964
  try {
788
- return await this.#adapter.execute({
965
+ return await this.#driver.execute({
789
966
  operation,
790
967
  transaction: this.#token,
791
968
  })
792
969
  } catch (error) {
793
- throw new DataTableAdapterError('Adapter execution failed', {
970
+ throw new DataTableDatabaseError('Database execution failed', {
794
971
  cause: error,
795
972
  metadata: {
796
- dialect: this.#adapter.dialect,
973
+ dialect: this.dialect,
797
974
  operationKind: operation.kind,
798
975
  },
799
976
  })
@@ -801,56 +978,6 @@ export class Database implements QueryExecutionContext {
801
978
  }
802
979
  }
803
980
 
804
- /**
805
- * Creates a database runtime from an adapter.
806
- * Thin wrapper around `new Database(adapter, options)`.
807
- * @param adapter Adapter implementation responsible for SQL execution.
808
- * @param options Optional runtime options.
809
- * @param options.now Clock function used for auto-managed timestamps.
810
- * @returns A {@link Database} API instance.
811
- * @example
812
- * ```ts
813
- * import { column as c, createDatabase, table } from 'remix/data-table'
814
- *
815
- * let users = table({
816
- * name: 'users',
817
- * columns: {
818
- * id: c.integer(),
819
- * email: c.varchar(255),
820
- * },
821
- * })
822
- *
823
- * let db = createDatabase(adapter)
824
- * let rows = await db.query(users).where({ id: 1 }).all()
825
- * ```
826
- */
827
- export function createDatabase(
828
- adapter: DatabaseAdapter,
829
- options?: { now?: () => unknown },
830
- ): Database {
831
- return new Database(adapter, options)
832
- }
833
-
834
- /**
835
- * Creates a database runtime bound to an existing adapter transaction token.
836
- * This is an internal helper used by the migration runner.
837
- * @param adapter Adapter implementation responsible for SQL execution.
838
- * @param token Active adapter transaction token.
839
- * @param options Optional runtime options.
840
- * @param options.now Clock function used for auto-managed timestamps.
841
- * @returns A {@link Database} API instance bound to the provided transaction.
842
- */
843
- export function createDatabaseWithTransaction(
844
- adapter: DatabaseAdapter,
845
- token: TransactionToken,
846
- options?: { now?: () => unknown },
847
- ): Database {
848
- return Database[createInternalDatabase](adapter, options, {
849
- token,
850
- savepointCounter: { value: 0 },
851
- })
852
- }
853
-
854
981
  function defaultNow(): Date {
855
982
  return new Date()
856
983
  }