@remix-run/data-table 0.2.1 → 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 (113) hide show
  1. package/README.md +251 -123
  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/column.js +10 -1
  11. package/dist/lib/database/execution-context.d.ts +5 -5
  12. package/dist/lib/database/execution-context.d.ts.map +1 -1
  13. package/dist/lib/database/execution-context.js +1 -0
  14. package/dist/lib/database/helpers.js +6 -6
  15. package/dist/lib/database/query-execution.d.ts.map +1 -1
  16. package/dist/lib/database/query-execution.js +17 -17
  17. package/dist/lib/database/relations.js +5 -5
  18. package/dist/lib/database/write-lifecycle.d.ts +2 -2
  19. package/dist/lib/database/write-lifecycle.d.ts.map +1 -1
  20. package/dist/lib/database/write-lifecycle.js +5 -5
  21. package/dist/lib/database.d.ts +74 -59
  22. package/dist/lib/database.d.ts.map +1 -1
  23. package/dist/lib/database.js +176 -83
  24. package/dist/lib/driver.d.ts +287 -0
  25. package/dist/lib/driver.d.ts.map +1 -0
  26. package/dist/lib/errors.d.ts +2 -2
  27. package/dist/lib/errors.d.ts.map +1 -1
  28. package/dist/lib/errors.js +4 -4
  29. package/dist/lib/migrations/directive.d.ts +12 -0
  30. package/dist/lib/migrations/directive.d.ts.map +1 -0
  31. package/dist/lib/migrations/directive.js +28 -0
  32. package/dist/lib/migrations/directory-name.d.ts +12 -0
  33. package/dist/lib/migrations/directory-name.d.ts.map +1 -0
  34. package/dist/lib/migrations/directory-name.js +18 -0
  35. package/dist/lib/migrations/journal-store.d.ts +7 -7
  36. package/dist/lib/migrations/journal-store.d.ts.map +1 -1
  37. package/dist/lib/migrations/journal-store.js +37 -28
  38. package/dist/lib/migrations/registry.d.ts +1 -1
  39. package/dist/lib/migrations/registry.js +1 -1
  40. package/dist/lib/migrations/runner.d.ts +12 -19
  41. package/dist/lib/migrations/runner.d.ts.map +1 -1
  42. package/dist/lib/migrations/runner.js +170 -175
  43. package/dist/lib/migrations-node.d.ts +26 -5
  44. package/dist/lib/migrations-node.d.ts.map +1 -1
  45. package/dist/lib/migrations-node.js +73 -41
  46. package/dist/lib/migrations.d.ts +63 -209
  47. package/dist/lib/migrations.d.ts.map +1 -1
  48. package/dist/lib/migrations.js +1 -38
  49. package/dist/lib/operators.d.ts +1 -1
  50. package/dist/lib/operators.js +1 -1
  51. package/dist/lib/query.d.ts +34 -1
  52. package/dist/lib/query.d.ts.map +1 -1
  53. package/dist/lib/query.js +32 -4
  54. package/dist/lib/sql-helpers.d.ts +1 -7
  55. package/dist/lib/sql-helpers.d.ts.map +1 -1
  56. package/dist/lib/sql-helpers.js +0 -16
  57. package/dist/lib/sql.d.ts +1 -1
  58. package/dist/lib/sql.js +1 -1
  59. package/dist/lib/table.d.ts +1 -1
  60. package/dist/lib/table.d.ts.map +1 -1
  61. package/dist/lib/table.js +5 -5
  62. package/dist/migrations/node.d.ts +1 -1
  63. package/dist/migrations/node.d.ts.map +1 -1
  64. package/dist/migrations/node.js +1 -1
  65. package/dist/migrations.d.ts +2 -6
  66. package/dist/migrations.d.ts.map +1 -1
  67. package/dist/migrations.js +2 -5
  68. package/dist/operators.js +1 -1
  69. package/dist/sql-helpers.d.ts +1 -1
  70. package/dist/sql-helpers.d.ts.map +1 -1
  71. package/dist/sql-helpers.js +1 -1
  72. package/package.json +13 -9
  73. package/src/cli.ts +120 -0
  74. package/src/index.ts +20 -54
  75. package/src/lib/column.ts +14 -2
  76. package/src/lib/database/execution-context.ts +10 -6
  77. package/src/lib/database/helpers.ts +1 -1
  78. package/src/lib/database/query-execution.ts +15 -11
  79. package/src/lib/database/write-lifecycle.ts +4 -4
  80. package/src/lib/database.ts +223 -96
  81. package/src/lib/driver.ts +333 -0
  82. package/src/lib/errors.ts +4 -4
  83. package/src/lib/migrations/directive.ts +38 -0
  84. package/src/lib/migrations/directory-name.ts +23 -0
  85. package/src/lib/migrations/journal-store.ts +42 -28
  86. package/src/lib/migrations/registry.ts +1 -1
  87. package/src/lib/migrations/runner.ts +224 -205
  88. package/src/lib/migrations-node.ts +80 -38
  89. package/src/lib/migrations.ts +62 -228
  90. package/src/lib/operators.ts +1 -1
  91. package/src/lib/query.ts +34 -1
  92. package/src/lib/sql-helpers.ts +1 -22
  93. package/src/lib/sql.ts +1 -1
  94. package/src/lib/table.ts +1 -1
  95. package/src/migrations/node.ts +1 -1
  96. package/src/migrations.ts +5 -16
  97. package/src/sql-helpers.ts +0 -1
  98. package/dist/lib/adapter.d.ts +0 -550
  99. package/dist/lib/adapter.d.ts.map +0 -1
  100. package/dist/lib/migrations/filename.d.ts +0 -12
  101. package/dist/lib/migrations/filename.d.ts.map +0 -1
  102. package/dist/lib/migrations/filename.js +0 -20
  103. package/dist/lib/migrations/helpers.d.ts +0 -11
  104. package/dist/lib/migrations/helpers.d.ts.map +0 -1
  105. package/dist/lib/migrations/helpers.js +0 -77
  106. package/dist/lib/migrations/schema-api.d.ts +0 -7
  107. package/dist/lib/migrations/schema-api.d.ts.map +0 -1
  108. package/dist/lib/migrations/schema-api.js +0 -326
  109. package/src/lib/adapter.ts +0 -653
  110. package/src/lib/migrations/filename.ts +0 -25
  111. package/src/lib/migrations/helpers.ts +0 -108
  112. package/src/lib/migrations/schema-api.ts +0 -417
  113. /package/dist/lib/{adapter.js → driver.js} +0 -0
package/README.md CHANGED
@@ -4,13 +4,14 @@ Typed relational query toolkit for JavaScript runtimes.
4
4
 
5
5
  ## Features
6
6
 
7
- - **One API Across Databases**: Same query and relation APIs across PostgreSQL, MySQL, and SQLite adapters
7
+ - **One API Across Databases**: Same query and relation APIs across PostgreSQL, MySQL, and SQLite
8
8
  - **One Query API**: Build reusable `Query` objects with `query(table)` and execute them with `db.exec(...)`, or use `db.query(table)` as shorthand
9
9
  - **Type-Safe Reads**: Typed `select`, relation loading, and predicate keys
10
10
  - **Optional Runtime Validation**: Add `validate(context)` at the table level for create/update validation and coercion
11
11
  - **Relation-First Queries**: `hasMany`, `hasOne`, `belongsTo`, `hasManyThrough`, and nested eager loading
12
12
  - **Safe Scoped Writes**: `update`/`delete` with `orderBy`/`limit` run safely in a transaction
13
- - **First-Class Migrations**: Up/down migrations with schema builders, runner controls, and dry-run planning
13
+ - **First-Class Migrations**: Plain SQL `up.sql`/`down.sql` files with a journaling runner and dry-run planning
14
+ - **Database Lifecycle Commands**: Wipe, migrate, inspect, seed, and reset through `remix db`
14
15
  - **Raw SQL Escape Hatch**: Execute SQL directly with `db.exec(sql\`...\`)`
15
16
 
16
17
  `data-table` gives you two complementary APIs:
@@ -33,12 +34,11 @@ npm i mysql2
33
34
 
34
35
  ## Setup
35
36
 
36
- Define tables once, then create a database with an adapter.
37
+ Define tables once, then create a database for your SQL dialect.
37
38
 
38
39
  ```ts
39
- import { Pool } from 'pg'
40
- import { column as c, createDatabase, hasMany, query, table } from 'remix/data-table'
41
- import { createPostgresDatabaseAdapter } from 'remix/data-table-postgres'
40
+ import { column as c, hasMany, query, table } from 'remix/data-table'
41
+ import { createPostgresDatabase } from 'remix/data-table/postgres'
42
42
 
43
43
  let users = table({
44
44
  name: 'users',
@@ -63,8 +63,7 @@ let orders = table({
63
63
 
64
64
  let userOrders = hasMany(users, orders)
65
65
 
66
- let pool = new Pool({ connectionString: process.env.DATABASE_URL })
67
- let db = createDatabase(createPostgresDatabaseAdapter(pool))
66
+ let db = createPostgresDatabase({ connectionString: process.env.DATABASE_URL })
68
67
  ```
69
68
 
70
69
  ## Query Objects
@@ -201,7 +200,7 @@ let createManyResult = await db.createMany(orders, [
201
200
  { id: 'o_102', user_id: 'u_003', status: 'pending', total: 48.5, created_at: Date.now() },
202
201
  ])
203
202
 
204
- // Return inserted rows (requires adapter RETURNING support)
203
+ // Return inserted rows (requires database RETURNING support)
205
204
  let insertedRows = await db.createMany(
206
205
  orders,
207
206
  [{ id: 'o_103', user_id: 'u_003', status: 'pending', total: 12, created_at: Date.now() }],
@@ -249,8 +248,7 @@ Return behavior:
249
248
 
250
249
  ### Validation and Lifecycle
251
250
 
252
- Validation is optional and table-scoped. Define `validate(context)` to validate/coerce write
253
- payloads, and add lifecycle callbacks when you need custom read/write/delete behavior.
251
+ Validation is optional and table-scoped. Define `validate(context)` to validate/coerce write payloads, and add lifecycle callbacks when you need custom read/write/delete behavior.
254
252
 
255
253
  ```ts
256
254
  import { column as c, fail, table } from 'remix/data-table'
@@ -344,11 +342,7 @@ await db.transaction(async (tx) => {
344
342
 
345
343
  ## Migrations
346
344
 
347
- `data-table` includes a first-class migration system under `remix/data-table/migrations`.
348
- Migrations are adapter-driven: adapters execute SQL for their dialect/runtime, and SQL compilation
349
- is handled by adapter-owned compilers (with optional shared pure helpers from `data-table`).
350
- For adapter authors (including third-party adapters), shared SQL helper utilities are available at
351
- `remix/data-table/sql-helpers`.
345
+ `data-table` ships a SQL-first migration system under `remix/data-table/migrations`. Each migration is a directory containing hand-written `up.sql` and (optionally) `down.sql`. The runner journals applied migrations, detects checksum drift and missing applied migrations, and wraps each migration in a transaction when the database supports transactional DDL.
352
346
 
353
347
  ### Example Setup
354
348
 
@@ -356,159 +350,187 @@ For adapter authors (including third-party adapters), shared SQL helper utilitie
356
350
  app/
357
351
  db/
358
352
  migrations/
359
- 20260228090000_create_users.ts
360
- 20260301113000_add_user_status.ts
361
- migrate.ts
353
+ 20260228090000_create_users/
354
+ up.sql
355
+ down.sql
356
+ 20260301113000_add_user_status/
357
+ up.sql
358
+ down.sql
359
+ db.ts
362
360
  ```
363
361
 
364
- - Keep migration files in one directory (for example `app/db/migrations`).
365
- - Name each file as `YYYYMMDDHHmmss_name.ts` (or `.js`, `.mjs`, `.cjs`, `.cts`).
366
- - Each file must `default` export `createMigration(...)`; `id` and `name` are inferred from filename.
362
+ - Keep migration directories in one parent directory (for example `app/db/migrations`).
363
+ - Each directory is named `YYYYMMDDHHmmss_<slug>`.
364
+ - `up.sql` is required. `down.sql` is optional (omit for irreversible migrations).
365
+ - Scripts may contain multiple statements. `id` and `name` are inferred from the directory name.
367
366
 
368
367
  ### Migration File Example
369
368
 
370
- ```ts
371
- import { column as c, table } from 'remix/data-table'
372
- import { createMigration } from 'remix/data-table/migrations'
369
+ `20260228090000_create_users/up.sql`:
373
370
 
374
- let users = table({
375
- name: 'users',
376
- columns: {
377
- id: c.integer().primaryKey(),
378
- email: c.varchar(255).notNull().unique(),
379
- created_at: c.timestamp({ withTimezone: true }).defaultNow(),
380
- },
381
- })
371
+ ```sql
372
+ create table users (
373
+ id serial primary key,
374
+ email varchar(255) not null unique,
375
+ created_at timestamptz not null default now()
376
+ );
382
377
 
383
- export default createMigration({
384
- async up({ db, schema }) {
385
- await schema.createTable(users)
386
- await schema.createIndex(users, 'email', { unique: true })
378
+ create unique index users_email_idx on users (email);
379
+ ```
387
380
 
388
- if (db.adapter.dialect === 'sqlite') {
389
- await db.exec('pragma foreign_keys = on')
390
- }
391
- },
392
- async down({ schema }) {
393
- await schema.dropTable(users, { ifExists: true })
394
- },
395
- })
381
+ `20260228090000_create_users/down.sql`:
382
+
383
+ ```sql
384
+ drop index if exists users_email_idx;
385
+ drop table if exists users;
396
386
  ```
397
387
 
398
- ### Runner Script Example
388
+ ### Multi-Statement Driver Configuration
399
389
 
400
- In `app/db/migrate.ts`:
390
+ The runner sends each migration to the database as a single multi-statement script. Make sure the underlying driver accepts multiple statements:
401
391
 
402
- ```ts
403
- import path from 'node:path'
404
- import { Pool } from 'pg'
405
- import { createPostgresDatabaseAdapter } from 'remix/data-table-postgres'
406
- import { createMigrationRunner } from 'remix/data-table/migrations'
407
- import { loadMigrations } from 'remix/data-table/migrations/node'
392
+ - `better-sqlite3`: works out of the box (`db.exec`).
393
+ - `pg`: works out of the box when no parameter array is passed.
394
+ - `mysql2`: requires `multipleStatements: true` on the connection/pool.
408
395
 
409
- let directionArg = process.argv[2] ?? 'up'
410
- let direction = directionArg === 'down' ? 'down' : 'up'
411
- let to = process.argv[3]
396
+ ```ts
397
+ import { createMysqlDatabase } from 'remix/data-table/mysql'
412
398
 
413
- let pool = new Pool({ connectionString: process.env.DATABASE_URL })
414
- let adapter = createPostgresDatabaseAdapter(pool)
415
- let migrations = await loadMigrations(path.resolve('app/db/migrations'))
416
- let runner = createMigrationRunner(adapter, migrations)
399
+ let db = createMysqlDatabase({
400
+ uri: process.env.DATABASE_URL,
401
+ multipleStatements: true,
402
+ })
403
+ ```
417
404
 
418
- try {
419
- let result = direction === 'up' ? await runner.up({ to }) : await runner.down({ to })
420
- console.log(direction + ' complete', {
421
- applied: result.applied.map((entry) => entry.id),
422
- reverted: result.reverted.map((entry) => entry.id),
423
- })
424
- } finally {
425
- await pool.end()
405
+ ### Database Command Configuration
406
+
407
+ Configure `remix db` statically in `remix.json`. Connection secrets are read from the named
408
+ environment variable at command runtime, and paths are resolved relative to `remix.json`:
409
+
410
+ ```jsonc
411
+ {
412
+ "$schema": "https://remix.run/schemas/remix.json",
413
+ "db": {
414
+ "adapter": {
415
+ "type": "postgres",
416
+ "connectionString": { "env": "DATABASE_URL" },
417
+ },
418
+ "migrations": {
419
+ "directory": "./db/migrations",
420
+ "journalTable": "app_migrations",
421
+ },
422
+ "seed": "./db/seed.sql",
423
+ },
426
424
  }
427
425
  ```
428
426
 
429
- Use `journalTable` if you want a custom migrations journal table name:
427
+ Application runtime setup remains application-owned. It does not need to expose any special
428
+ exports for the CLI.
430
429
 
431
- ```ts
432
- let runner = createMigrationRunner(adapter, migrations, {
433
- journalTable: 'app_migrations',
434
- })
435
- ```
436
-
437
- Run it with your runtime, for example:
430
+ Run lifecycle commands through the Remix CLI:
438
431
 
439
432
  ```sh
440
- node ./app/db/migrate.ts up
441
- node ./app/db/migrate.ts up 20260301113000
442
- node ./app/db/migrate.ts down
443
- node ./app/db/migrate.ts down 20260228090000
433
+ remix db status
434
+ remix db migrate
435
+ remix db migrate --to 20260301113000_add_user_status
436
+ remix db seed
437
+ remix db reset
438
+ remix db wipe
444
439
  ```
445
440
 
446
- Use `step` when you want bounded rollforward/rollback behavior instead of a target id:
441
+ `--to` accepts a bare migration id (`20260301113000`) or the full directory name (`20260301113000_add_user_status`).
442
+
443
+ `remix db status` reports applied migrations whose files are no longer present as `missing`. If the journal table does not exist, it reports every migration as pending without creating the table. Forward migration runs stop before executing SQL when an applied journal entry is missing from the current migration set. Rollbacks skip those orphaned journal entries so migrations that are still present can be reverted.
444
+
445
+ `wipe` and `reset` are destructive. They require a config-backed database so it can close, recreate, and reconnect to the configured database.
446
+
447
+ ### Programmatic Migrations
448
+
449
+ Load migrations and pass the resolved collection directly to the database:
447
450
 
448
451
  ```ts
449
- await runner.up({ step: 1 })
450
- await runner.down({ step: 1 })
451
- ```
452
+ import { loadMigrations } from 'remix/data-table/migrations/node'
452
453
 
453
- `to` and `step` are mutually exclusive. Use one or the other for a given run.
454
+ let migrations = await loadMigrations('./db/migrations')
455
+ await db.migrate(migrations)
456
+ ```
454
457
 
455
- Use `dryRun` to compile and inspect the SQL plan without applying migrations:
458
+ `Database.migrate()` supports forward and backward directions, a target or step bound, dry runs,
459
+ and a custom journal table:
456
460
 
457
461
  ```ts
458
- let dryRunResult = await runner.up({ dryRun: true })
459
- console.log(dryRunResult.sql)
462
+ await db.migrate(migrations)
463
+ await db.migrate(migrations, { to: '20260301113000_add_user_status' })
464
+ await db.migrate(migrations, { step: 1 })
465
+ await db.migrate(migrations, { direction: 'down' })
466
+ await db.migrate(migrations, { direction: 'down', to: '20260301113000' })
467
+ await db.migrate(migrations, { direction: 'down', step: 1 })
468
+ await db.migrate(migrations, { journalTable: 'app_migrations' })
469
+
470
+ let plan = await db.migrate(migrations, { dryRun: true })
471
+ for (let script of plan.sql) console.log(script)
460
472
  ```
461
473
 
462
- When migration transactions are enabled, migration-time `schema.createTable(...)`, `db.exec(...)`,
463
- query-builder data operations, and `schema.hasTable(...)` / `schema.hasColumn(...)` all run in the same
464
- adapter transaction context.
474
+ `to` and `step` are mutually exclusive. Omit `journalTable` to use `data_table_migrations`.
465
475
 
466
- You can also pass a pre-built SQL statement into `schema.plan(...)` when authoring migrations:
476
+ Database drivers with migration locking run the complete migration and journal lifecycle through the
477
+ connection that owns the lock. This keeps advisory locks correctly paired when the driver uses a
478
+ connection pool, including pools configured with a single connection.
479
+
480
+ Read status separately, or rebuild a database with migrations and an optional seed. A seed is a
481
+ function that receives the database; `loadSeed()` builds one from a SQL file:
467
482
 
468
483
  ```ts
469
- import { sql } from 'remix/data-table'
484
+ import { loadSeed } from 'remix/data-table/migrations/node'
485
+
486
+ let seed = await loadSeed('./db/seed.sql')
470
487
 
471
- await schema.plan(sql`update users set status = ${'active'} where status is null`)
488
+ let status = await db.migrationStatus(migrations, { journalTable: 'app_migrations' })
489
+ await db.reset({ migrations })
490
+ await db.reset({ migrations, seed })
491
+ await db.reset({ migrations, seed, journalTable: 'app_migrations' })
472
492
  ```
473
493
 
474
- You can run lightweight schema checks inside a migration with `schema.hasTable(...)` and
475
- `schema.hasColumn(...)` when you need defensive conditional behavior. Methods that take a table name
476
- accept either a string (`'app.users'`) or a `table(...)` object.
494
+ When a lifecycle command is the last thing a process does, close database-owned connections so
495
+ the process can exit:
477
496
 
478
- In `dryRun` mode, introspection methods still check the live database state. They do not simulate
479
- tables/columns from pending operations in the current dry-run plan.
497
+ ```ts
498
+ await db.close()
499
+ ```
480
500
 
481
- For key-oriented migration APIs, single-column and compound forms are both supported:
501
+ ### Transaction Modes
482
502
 
483
- ```ts
484
- await schema.alterTable(users, (table) => {
485
- table.addPrimaryKey('id')
486
- table.addForeignKey('account_id', 'accounts', 'id')
487
- table.addForeignKey(['tenant_id', 'account_id'], 'accounts', ['tenant_id', 'id'])
488
- })
503
+ By default each migration is wrapped in a transaction when the database supports transactional DDL. Override per migration with a directive on the first non-blank line of `up.sql`:
504
+
505
+ ```sql
506
+ -- data-table/transaction: none
507
+ create index concurrently users_email_active_idx on users (email) where status = 'active';
489
508
  ```
490
509
 
491
- Constraint and index names are optional in migration APIs. When omitted, `data-table` generates
492
- deterministic names for primary keys, uniques, foreign keys, checks, and indexes.
510
+ Supported modes:
511
+
512
+ - `auto` (default): wrap when the database supports transactional DDL.
513
+ - `required`: wrap; the runner throws if the database cannot support it.
514
+ - `none`: never wrap. Use this for statements like postgres `CREATE INDEX CONCURRENTLY` that cannot run inside a transaction.
493
515
 
494
- This is useful when you want to:
516
+ You can also set `transaction` directly on a `MigrationDescriptor` when registering migrations programmatically.
495
517
 
496
- - Review generated SQL in CI before deploying
497
- - Verify migration ordering and target/step selection
498
- - Audit dialect-specific SQL differences across adapters
518
+ ### Programmatic Registration
499
519
 
500
- For non-filesystem runtimes, register migrations manually:
520
+ For non-filesystem runtimes, register migrations directly:
501
521
 
502
522
  ```ts
503
- import { createMigrationRegistry, createMigrationRunner } from 'remix/data-table/migrations'
504
- import createUsers from './db/migrations/20260228090000_create_users.ts'
523
+ import { createMigrationRegistry } from 'remix/data-table/migrations'
505
524
 
506
525
  let registry = createMigrationRegistry()
507
- registry.register({ id: '20260228090000', name: 'create_users', migration: createUsers })
526
+ registry.register({
527
+ id: '20260228090000',
528
+ name: 'create_users',
529
+ up: 'create table users (id serial primary key, email text not null);',
530
+ down: 'drop table users;',
531
+ })
508
532
 
509
- // adapter from createPostgresDatabaseAdapter/createMysqlDatabaseAdapter/createSqliteDatabaseAdapter
510
- let runner = createMigrationRunner(adapter, registry)
511
- await runner.up()
533
+ await db.migrate(registry)
512
534
  ```
513
535
 
514
536
  ## Raw SQL Escape Hatch
@@ -536,14 +558,120 @@ let result = await db.exec(sql`
536
558
  `)
537
559
  ```
538
560
 
539
- `sql` keeps values parameterized per adapter dialect, so you can avoid manual string concatenation.
561
+ `sql` keeps values parameterized for the database dialect, so you can avoid manual string concatenation.
562
+
563
+ ## Custom Database Drivers
564
+
565
+ Applications normally use one of the concrete SQLite, PostgreSQL, or MySQL factories. Integration
566
+ packages can add another dialect by implementing `DatabaseDriver` and extending `Database`. This
567
+ complete skeleton assumes the underlying client exposes the operations needed by the driver:
568
+
569
+ ```ts
570
+ import {
571
+ Database,
572
+ type DatabaseDriver,
573
+ type DataManipulationRequest,
574
+ type DataManipulationResult,
575
+ type DatabaseOptions,
576
+ type TableRef,
577
+ type TransactionOptions,
578
+ type TransactionToken,
579
+ } from 'remix/data-table'
580
+ import type { AcmeClient } from 'acme-database'
581
+
582
+ class AcmeDriver implements DatabaseDriver<'acme'> {
583
+ readonly dialect = 'acme'
584
+ readonly capabilities = {
585
+ returning: true,
586
+ savepoints: true,
587
+ upsert: true,
588
+ transactionalDdl: true,
589
+ migrationLock: false,
590
+ } as const
591
+
592
+ #client: AcmeClient
593
+
594
+ constructor(client: AcmeClient) {
595
+ this.#client = client
596
+ }
597
+
598
+ execute(request: DataManipulationRequest): Promise<DataManipulationResult> {
599
+ return this.#client.execute(request)
600
+ }
601
+
602
+ executeScript(sql: string, transaction?: TransactionToken): Promise<void> {
603
+ return this.#client.executeScript(sql, transaction)
604
+ }
605
+
606
+ beginTransaction(options?: TransactionOptions): Promise<TransactionToken> {
607
+ return this.#client.beginTransaction(options)
608
+ }
609
+
610
+ commitTransaction(transaction: TransactionToken): Promise<void> {
611
+ return this.#client.commitTransaction(transaction)
612
+ }
613
+
614
+ rollbackTransaction(transaction: TransactionToken): Promise<void> {
615
+ return this.#client.rollbackTransaction(transaction)
616
+ }
617
+
618
+ hasTable(table: TableRef, transaction?: TransactionToken): Promise<boolean> {
619
+ return this.#client.hasTable(table, transaction)
620
+ }
621
+
622
+ hasColumn(table: TableRef, column: string, transaction?: TransactionToken): Promise<boolean> {
623
+ return this.#client.hasColumn(table, column, transaction)
624
+ }
625
+
626
+ createSavepoint(transaction: TransactionToken, name: string): Promise<void> {
627
+ return this.#client.createSavepoint(transaction, name)
628
+ }
629
+
630
+ rollbackToSavepoint(transaction: TransactionToken, name: string): Promise<void> {
631
+ return this.#client.rollbackToSavepoint(transaction, name)
632
+ }
633
+
634
+ releaseSavepoint(transaction: TransactionToken, name: string): Promise<void> {
635
+ return this.#client.releaseSavepoint(transaction, name)
636
+ }
637
+
638
+ wipe(): Promise<void> {
639
+ return this.#client.wipe()
640
+ }
641
+
642
+ close(): void | Promise<void> {
643
+ return this.#client.close()
644
+ }
645
+ }
646
+
647
+ export class AcmeDatabase extends Database<'acme'> {
648
+ constructor(client: AcmeClient, options?: DatabaseOptions) {
649
+ super(new AcmeDriver(client), options)
650
+ }
651
+ }
652
+ ```
653
+
654
+ `Database` supplies queries, CRUD helpers, relations, transactions, and migrations. The private
655
+ driver owns SQL execution and connection lifecycle. A driver provides:
656
+
657
+ - `dialect` and an immutable `capabilities` object
658
+ - `execute()` and `executeScript()`
659
+ - `hasTable()`, `hasColumn()`, `wipe()`, and idempotent `close()`
660
+ - transaction and savepoint lifecycle methods using opaque `TransactionToken` values
661
+
662
+ Drivers whose capabilities report `migrationLock: true` also implement `withMigrationLock()` and
663
+ run its callback with a `DatabaseDriver` bound to the connection that owns the lock.
664
+
665
+ `Database`, `DatabaseDriver`, and the supporting driver protocol types are all exported directly
666
+ from `remix/data-table`. Transaction callbacks receive a transaction-scoped `Database`, so custom
667
+ subclass methods are intentionally unavailable inside the callback.
540
668
 
541
669
  ## Related Packages
542
670
 
543
671
  - [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Optional schema parsing you can use inside table-level `validate(...)` hooks
544
- - [`data-table-postgres`](https://github.com/remix-run/remix/tree/main/packages/data-table-postgres) - PostgreSQL adapter
545
- - [`data-table-mysql`](https://github.com/remix-run/remix/tree/main/packages/data-table-mysql) - MySQL adapter
546
- - [`data-table-sqlite`](https://github.com/remix-run/remix/tree/main/packages/data-table-sqlite) - SQLite adapter
672
+ - [`data-table-postgres`](https://github.com/remix-run/remix/tree/main/packages/data-table-postgres) - PostgreSQL database integration
673
+ - [`data-table-mysql`](https://github.com/remix-run/remix/tree/main/packages/data-table-mysql) - MySQL database integration
674
+ - [`data-table-sqlite`](https://github.com/remix-run/remix/tree/main/packages/data-table-sqlite) - SQLite database integration
547
675
 
548
676
  ## License
549
677
 
package/dist/cli.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { Database } from './lib/database.ts';
2
+ import type { Migrations, Seed } from './lib/migrations.ts';
3
+ interface DatabaseCommandOptions {
4
+ /** Database instance used by the command. */
5
+ db: Database;
6
+ }
7
+ /** Structured invocation options accepted by {@link runRemixDb}. */
8
+ export type RunRemixDbOptions = (DatabaseCommandOptions & {
9
+ /** Applies pending migrations. */
10
+ command: 'migrate';
11
+ /** Migrations to apply. */
12
+ migrations: Migrations;
13
+ /**
14
+ * Stops after applying this migration. Accepts a bare migration id or
15
+ * the full `id_name` directory form.
16
+ */
17
+ to?: string;
18
+ /** Migration journal table. */
19
+ journalTable?: string;
20
+ }) | (DatabaseCommandOptions & {
21
+ /** Wipes, migrates, and optionally seeds the database. */
22
+ command: 'reset';
23
+ /** Migrations to apply after wiping the database. */
24
+ migrations: Migrations;
25
+ /** Initializes database data after migrations finish. */
26
+ seed?: Seed;
27
+ /** Migration journal table. */
28
+ journalTable?: string;
29
+ }) | (DatabaseCommandOptions & {
30
+ /** Runs the application's seed function. */
31
+ command: 'seed';
32
+ /** Initializes application database data. */
33
+ seed: Seed;
34
+ }) | (DatabaseCommandOptions & {
35
+ /** Reports the status of known migrations. */
36
+ command: 'status';
37
+ /** Migrations to inspect. */
38
+ migrations: Migrations;
39
+ /** Migration journal table. */
40
+ journalTable?: string;
41
+ }) | (DatabaseCommandOptions & {
42
+ /** Destructively recreates the configured database. */
43
+ command: 'wipe';
44
+ });
45
+ /**
46
+ * Runs a data-table database command from structured invocation options.
47
+ *
48
+ * @param options Database command and application database values.
49
+ * @returns The exit code the host CLI should use. Always resolves `0`;
50
+ * command failures throw.
51
+ */
52
+ export declare function runRemixDb(options: RunRemixDbOptions): Promise<number>;
53
+ export {};
54
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAE3D,UAAU,sBAAsB;IAC9B,6CAA6C;IAC7C,EAAE,EAAE,QAAQ,CAAA;CACb;AAED,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GACzB,CAAC,sBAAsB,GAAG;IACxB,kCAAkC;IAClC,OAAO,EAAE,SAAS,CAAA;IAClB,2BAA2B;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAC,GACF,CAAC,sBAAsB,GAAG;IACxB,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAA;IAChB,qDAAqD;IACrD,UAAU,EAAE,UAAU,CAAA;IACtB,yDAAyD;IACzD,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAC,GACF,CAAC,sBAAsB,GAAG;IACxB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf,6CAA6C;IAC7C,IAAI,EAAE,IAAI,CAAA;CACX,CAAC,GACF,CAAC,sBAAsB,GAAG;IACxB,8CAA8C;IAC9C,OAAO,EAAE,QAAQ,CAAA;IACjB,6BAA6B;IAC7B,UAAU,EAAE,UAAU,CAAA;IACtB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAC,GACF,CAAC,sBAAsB,GAAG;IACxB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAA;CAChB,CAAC,CAAA;AAEN;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4D5E"}
package/dist/cli.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Runs a data-table database command from structured invocation options.
3
+ *
4
+ * @param options Database command and application database values.
5
+ * @returns The exit code the host CLI should use. Always resolves `0`;
6
+ * command failures throw.
7
+ */
8
+ export async function runRemixDb(options) {
9
+ if (options.command === 'migrate') {
10
+ let migrateOptions = options.to === undefined
11
+ ? { journalTable: options.journalTable }
12
+ : { to: options.to, journalTable: options.journalTable };
13
+ let result = await options.db.migrate(options.migrations, migrateOptions);
14
+ if (result.applied.length === 0) {
15
+ console.log('no pending migrations');
16
+ }
17
+ for (let entry of result.applied) {
18
+ console.log('applied ' + entry.id + '_' + entry.name);
19
+ }
20
+ return 0;
21
+ }
22
+ if (options.command === 'reset') {
23
+ await options.db.reset({
24
+ migrations: options.migrations,
25
+ seed: options.seed,
26
+ journalTable: options.journalTable,
27
+ });
28
+ console.log('database reset');
29
+ return 0;
30
+ }
31
+ if (options.command === 'seed') {
32
+ await options.seed(options.db);
33
+ console.log('database seeded');
34
+ return 0;
35
+ }
36
+ if (options.command === 'status') {
37
+ let entries = await options.db.migrationStatus(options.migrations, {
38
+ journalTable: options.journalTable,
39
+ });
40
+ for (let entry of entries) {
41
+ console.log(entry.id + ' ' + entry.name + ' ' + entry.status);
42
+ }
43
+ return 0;
44
+ }
45
+ if (options.command === 'wipe') {
46
+ await options.db.wipe();
47
+ return 0;
48
+ }
49
+ // Guard against unchecked command strings from plain-JS callers so an
50
+ // unknown command can never fall through to a destructive operation.
51
+ let command = options.command;
52
+ throw new Error('Unknown database command: ' + command);
53
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { AdapterCapabilityOverrides, AdapterCapabilities, DataManipulationRequest, DataMigrationRequest, AddCheckChange, AddCheckOperation, AddColumnChange, AddForeignKeyChange, AddForeignKeyOperation, AddPrimaryKeyChange, AddUniqueChange, AlterTableChange, AlterTableOperation, ChangeColumnChange, CheckConstraint, ColumnCheck, ColumnComputed, ColumnDefault, ColumnDefinition, ColumnTypeName, CountOperation, CreateIndexOperation, CreateTableOperation, DataMigrationResult, DataMigrationOperation, DataManipulationResult, DataManipulationOperation, DeleteOperation, DatabaseAdapter, DropCheckChange, DropCheckOperation, DropColumnChange, DropForeignKeyChange, DropForeignKeyOperation, DropIndexOperation, DropPrimaryKeyChange, DropTableOperation, DropUniqueChange, ExistsOperation, ForeignKeyAction, ForeignKeyConstraint, IndexDefinition, IndexMethod, InsertManyOperation, InsertOperation, JoinClause, JoinType, PrimaryKeyConstraint, RawOperation, RenameColumnChange, RenameIndexOperation, RenameTableOperation, ReturningSelection, SelectColumn, SelectOperation, SetTableCommentChange, TableRef, TransactionOptions, TransactionToken, UniqueConstraint, UpdateOperation, UpsertOperation, } from './lib/adapter.ts';
2
- export { DataTableAdapterError, DataTableConstraintError, DataTableError, DataTableQueryError, DataTableValidationError, } from './lib/errors.ts';
1
+ export type { DatabaseCapabilities, DatabaseDriver, DataManipulationOperation, DataManipulationRequest, ColumnCheck, ColumnComputed, ColumnDefault, ColumnDefinition, ColumnTypeName, DataManipulationResult, ForeignKeyAction, IdentityOptions, TableRef, TransactionOptions, TransactionToken, } from './lib/driver.ts';
2
+ export { DataTableDatabaseError, DataTableConstraintError, DataTableError, DataTableQueryError, DataTableValidationError, } from './lib/errors.ts';
3
3
  export type { AnyRelation, AnyColumn, AnyTable, BelongsToOptions, ColumnReference, ColumnReferenceForQualifiedName, HasManyOptions, HasManyThroughOptions, HasOneOptions, KeySelector, OrderByClause, OrderDirection, PrimaryKeyInput, Relation, RelationCardinality, RelationKind, RelationMapForTable, Table, TableAfterDelete, TableAfterDeleteContext, TableAfterRead, TableAfterReadContext, TableAfterReadResult, TableAfterWrite, TableAfterWriteContext, TableBeforeDelete, TableBeforeDeleteContext, TableBeforeDeleteResult, TableBeforeWrite, TableBeforeWriteContext, TableBeforeWriteResult, TableColumnInput, TableColumnName, TableColumns, TableLifecycleOperation, TableName, TablePrimaryKey, TableReference, TableRow, TableRowWith, TableColumnsDefinition, TableValidate, TableValidationContext, TableWriteOperation, TableValidationOperation, TableValidationResult, TimestampConfig, TimestampOptions, ValidationFailure, ValidationIssue, } from './lib/table.ts';
4
4
  export { belongsTo, columnMetadataKey, fail, getTableColumns, getTableColumnDefinitions, getTableBeforeDelete, getTableBeforeWrite, getTableAfterDelete, getTableAfterRead, getTableAfterWrite, getTableName, getTablePrimaryKey, getTableReference, getTableTimestamps, getTableValidator, hasMany, hasManyThrough, hasOne, table, tableMetadataKey, timestamps, } from './lib/table.ts';
5
5
  export type { ColumnNamespace } from './lib/column.ts';
@@ -8,8 +8,9 @@ export type { Predicate, WhereInput, WhereObject } from './lib/operators.ts';
8
8
  export { and, between, eq, gt, gte, ilike, inList, isNull, like, lt, lte, ne, notInList, notNull, or, } from './lib/operators.ts';
9
9
  export type { SqlStatement } from './lib/sql.ts';
10
10
  export { rawSql, sql } from './lib/sql.ts';
11
- export type { CountOptions, CreateManyResultOptions, CreateManyRowsOptions, CreateResultOptions, CreateRowOptions, DeleteManyOptions, FindManyOptions, FindOneOptions, OrderByInput, OrderByTuple, QueryColumnTypesForTable, QueryForTable, QueryTableInput, SingleTableColumn, SingleTableWhere, UpdateManyOptions, UpdateOptions, WriteResult, WriteRowResult, WriteRowsResult, } from './lib/database.ts';
12
- export { createDatabase, Database } from './lib/database.ts';
11
+ export type { CountOptions, CreateManyResultOptions, CreateManyRowsOptions, CreateResultOptions, CreateRowOptions, DeleteManyOptions, DatabaseOptions, FindManyOptions, FindOneOptions, OrderByInput, OrderByTuple, QueryColumnTypesForTable, QueryForTable, QueryTableInput, SingleTableColumn, SingleTableWhere, UpdateManyOptions, UpdateOptions, WriteResult, WriteRowResult, WriteRowsResult, } from './lib/database.ts';
12
+ export { Database } from './lib/database.ts';
13
+ export type { DatabaseMigrateOptions, DatabaseMigrationStatusOptions, DatabaseResetOptions, MigrateResult, MigrationDescriptor, MigrationRegistry, Migrations, MigrationStatus, MigrationStatusEntry, Seed, } from './lib/migrations.ts';
13
14
  export type { AnyQuery } from './lib/query.ts';
14
15
  export { Query, query } from './lib/query.ts';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}