@remix-run/data-table 0.1.0 → 0.2.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.
- package/README.md +306 -55
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/lib/adapter.d.ts +386 -16
- package/dist/lib/adapter.d.ts.map +1 -1
- package/dist/lib/column.d.ts +193 -0
- package/dist/lib/column.d.ts.map +1 -0
- package/dist/lib/column.js +302 -0
- package/dist/lib/database/execution-context.d.ts +10 -0
- package/dist/lib/database/execution-context.d.ts.map +1 -0
- package/dist/lib/database/execution-context.js +1 -0
- package/dist/lib/database/helpers.d.ts +26 -0
- package/dist/lib/database/helpers.d.ts.map +1 -0
- package/dist/lib/database/helpers.js +116 -0
- package/dist/lib/database/query-execution.d.ts +7 -0
- package/dist/lib/database/query-execution.d.ts.map +1 -0
- package/dist/lib/database/query-execution.js +401 -0
- package/dist/lib/database/relations.d.ts +4 -0
- package/dist/lib/database/relations.d.ts.map +1 -0
- package/dist/lib/database/relations.js +207 -0
- package/dist/lib/database/write-lifecycle.d.ts +13 -0
- package/dist/lib/database/write-lifecycle.d.ts.map +1 -0
- package/dist/lib/database/write-lifecycle.js +279 -0
- package/dist/lib/database.d.ts +141 -238
- package/dist/lib/database.d.ts.map +1 -1
- package/dist/lib/database.js +73 -1122
- package/dist/lib/errors.d.ts +9 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +9 -0
- package/dist/lib/migrations/filename.d.ts +12 -0
- package/dist/lib/migrations/filename.d.ts.map +1 -0
- package/dist/lib/migrations/filename.js +20 -0
- package/dist/lib/migrations/helpers.d.ts +11 -0
- package/dist/lib/migrations/helpers.d.ts.map +1 -0
- package/dist/lib/migrations/helpers.js +77 -0
- package/dist/lib/migrations/journal-store.d.ts +15 -0
- package/dist/lib/migrations/journal-store.d.ts.map +1 -0
- package/dist/lib/migrations/journal-store.js +83 -0
- package/dist/lib/migrations/registry.d.ts +27 -0
- package/dist/lib/migrations/registry.d.ts.map +1 -0
- package/dist/lib/migrations/registry.js +51 -0
- package/dist/lib/migrations/runner.d.ts +20 -0
- package/dist/lib/migrations/runner.d.ts.map +1 -0
- package/dist/lib/migrations/runner.js +273 -0
- package/dist/lib/migrations/schema-api.d.ts +7 -0
- package/dist/lib/migrations/schema-api.d.ts.map +1 -0
- package/dist/lib/migrations/schema-api.js +326 -0
- package/dist/lib/migrations-node.d.ts +17 -0
- package/dist/lib/migrations-node.d.ts.map +1 -0
- package/dist/lib/migrations-node.js +65 -0
- package/dist/lib/migrations.d.ts +292 -0
- package/dist/lib/migrations.d.ts.map +1 -0
- package/dist/lib/migrations.js +38 -0
- package/dist/lib/operators.d.ts +3 -0
- package/dist/lib/operators.d.ts.map +1 -1
- package/dist/lib/query.d.ts +159 -0
- package/dist/lib/query.d.ts.map +1 -0
- package/dist/lib/query.js +401 -0
- package/dist/lib/references.d.ts +0 -1
- package/dist/lib/references.d.ts.map +1 -1
- package/dist/lib/sql-helpers.d.ts +50 -0
- package/dist/lib/sql-helpers.d.ts.map +1 -0
- package/dist/lib/sql-helpers.js +111 -0
- package/dist/lib/sql.d.ts +23 -6
- package/dist/lib/sql.d.ts.map +1 -1
- package/dist/lib/sql.js +19 -5
- package/dist/lib/table.d.ts +355 -40
- package/dist/lib/table.d.ts.map +1 -1
- package/dist/lib/table.js +113 -90
- package/dist/migrations/node.d.ts +2 -0
- package/dist/migrations/node.d.ts.map +1 -0
- package/dist/migrations/node.js +1 -0
- package/dist/migrations.d.ts +8 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +5 -0
- package/dist/operators.d.ts +3 -0
- package/dist/operators.d.ts.map +1 -0
- package/dist/operators.js +1 -0
- package/dist/sql-helpers.d.ts +3 -0
- package/dist/sql-helpers.d.ts.map +1 -0
- package/dist/sql-helpers.js +1 -0
- package/package.json +23 -8
- package/src/index.ts +93 -10
- package/src/lib/adapter.ts +469 -25
- package/src/lib/column.ts +384 -0
- package/src/lib/database/execution-context.ts +15 -0
- package/src/lib/database/helpers.ts +216 -0
- package/src/lib/database/query-execution.ts +638 -0
- package/src/lib/database/relations.ts +332 -0
- package/src/lib/database/write-lifecycle.ts +487 -0
- package/src/lib/database.ts +246 -1848
- package/src/lib/errors.ts +10 -0
- package/src/lib/migrations/filename.ts +25 -0
- package/src/lib/migrations/helpers.ts +108 -0
- package/src/lib/migrations/journal-store.ts +122 -0
- package/src/lib/migrations/registry.ts +62 -0
- package/src/lib/migrations/runner.ts +374 -0
- package/src/lib/migrations/schema-api.ts +417 -0
- package/src/lib/migrations-node.ts +71 -0
- package/src/lib/migrations.ts +328 -0
- package/src/lib/operators.ts +3 -0
- package/src/lib/query.ts +958 -0
- package/src/lib/references.ts +0 -1
- package/src/lib/sql-helpers.ts +146 -0
- package/src/lib/sql.ts +23 -6
- package/src/lib/table.ts +484 -156
- package/src/migrations/node.ts +1 -0
- package/src/migrations.ts +26 -0
- package/src/operators.ts +18 -0
- package/src/sql-helpers.ts +9 -0
package/README.md
CHANGED
|
@@ -5,19 +5,20 @@ Typed relational query toolkit for JavaScript runtimes.
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **One API Across Databases**: Same query and relation APIs across PostgreSQL, MySQL, and SQLite adapters
|
|
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
14
|
- **Raw SQL Escape Hatch**: Execute SQL directly with `db.exec(sql\`...\`)`
|
|
14
15
|
|
|
15
16
|
`data-table` gives you two complementary APIs:
|
|
16
17
|
|
|
17
|
-
- [**Query
|
|
18
|
+
- [**Query Objects**](#query-objects) for expressive joins, aggregates, eager loading, and scoped writes
|
|
18
19
|
- [**CRUD Helpers**](#crud-helpers) for common create/read/update/delete flows (`find`, `create`, `update`, `delete`)
|
|
19
20
|
|
|
20
|
-
Both APIs are type-safe
|
|
21
|
+
Both APIs are type-safe. Runtime validation is opt-in with table-level `validate(context)`.
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
@@ -36,28 +37,27 @@ Define tables once, then create a database with an adapter.
|
|
|
36
37
|
|
|
37
38
|
```ts
|
|
38
39
|
import { Pool } from 'pg'
|
|
39
|
-
import
|
|
40
|
-
import { createDatabase, createTable, hasMany } from 'remix/data-table'
|
|
40
|
+
import { column as c, createDatabase, hasMany, query, table } from 'remix/data-table'
|
|
41
41
|
import { createPostgresDatabaseAdapter } from 'remix/data-table-postgres'
|
|
42
42
|
|
|
43
|
-
let users =
|
|
43
|
+
let users = table({
|
|
44
44
|
name: 'users',
|
|
45
45
|
columns: {
|
|
46
|
-
id:
|
|
47
|
-
email:
|
|
48
|
-
role:
|
|
49
|
-
created_at:
|
|
46
|
+
id: c.uuid(),
|
|
47
|
+
email: c.varchar(255),
|
|
48
|
+
role: c.enum(['customer', 'admin']),
|
|
49
|
+
created_at: c.integer(),
|
|
50
50
|
},
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
let orders =
|
|
53
|
+
let orders = table({
|
|
54
54
|
name: 'orders',
|
|
55
55
|
columns: {
|
|
56
|
-
id:
|
|
57
|
-
user_id:
|
|
58
|
-
status:
|
|
59
|
-
total:
|
|
60
|
-
created_at:
|
|
56
|
+
id: c.uuid(),
|
|
57
|
+
user_id: c.uuid(),
|
|
58
|
+
status: c.enum(['pending', 'processing', 'shipped', 'delivered']),
|
|
59
|
+
total: c.decimal(10, 2),
|
|
60
|
+
created_at: c.integer(),
|
|
61
61
|
},
|
|
62
62
|
})
|
|
63
63
|
|
|
@@ -67,15 +67,18 @@ let pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
|
67
67
|
let db = createDatabase(createPostgresDatabaseAdapter(pool))
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
## Query
|
|
70
|
+
## Query Objects
|
|
71
71
|
|
|
72
|
-
Use `db.query(table)` when you
|
|
72
|
+
Use `query(table)` when you want to build a standalone reusable query object. Execute it later with `db.exec(query)`. Use `db.query(table)` when you want the same chainable `Query` already bound to a database instance.
|
|
73
|
+
|
|
74
|
+
### Standalone Query Builder
|
|
75
|
+
|
|
76
|
+
`query(table)` is the primary query-builder API. It gives you an unbound `Query` value that can be composed, stored, reused, and executed against any compatible database instance.
|
|
73
77
|
|
|
74
78
|
```ts
|
|
75
|
-
import { eq, ilike } from 'remix/data-table'
|
|
79
|
+
import { eq, ilike, query } from 'remix/data-table'
|
|
76
80
|
|
|
77
|
-
let
|
|
78
|
-
.query(orders)
|
|
81
|
+
let pendingOrdersForExampleUsers = query(orders)
|
|
79
82
|
.join(users, eq(orders.user_id, users.id))
|
|
80
83
|
.where({ status: 'pending' })
|
|
81
84
|
.where(ilike(users.email, '%@example.com'))
|
|
@@ -87,32 +90,51 @@ let recentPendingOrders = await db
|
|
|
87
90
|
})
|
|
88
91
|
.orderBy(orders.created_at, 'desc')
|
|
89
92
|
.limit(20)
|
|
90
|
-
|
|
93
|
+
|
|
94
|
+
let recentPendingOrders = await db.exec(pendingOrdersForExampleUsers)
|
|
91
95
|
```
|
|
92
96
|
|
|
93
|
-
|
|
97
|
+
Unbound queries stay lazy until you pass them to `db.exec(...)`:
|
|
94
98
|
|
|
95
99
|
```ts
|
|
96
|
-
let
|
|
97
|
-
.query(users)
|
|
100
|
+
let shippedCustomerQuery = query(users)
|
|
98
101
|
.where({ role: 'customer' })
|
|
99
102
|
.with({
|
|
100
103
|
recentOrders: userOrders.where({ status: 'shipped' }).orderBy('created_at', 'desc').limit(3),
|
|
101
104
|
})
|
|
102
|
-
|
|
105
|
+
|
|
106
|
+
let customers = await db.exec(shippedCustomerQuery)
|
|
103
107
|
|
|
104
108
|
// customers[0].recentOrders is fully typed
|
|
105
109
|
```
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
The same standalone query builder also handles terminal read and write operations:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
let nextPendingOrder = await db.exec(
|
|
115
|
+
query(orders).where({ status: 'pending' }).orderBy('created_at', 'asc').first(),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
await db.exec(
|
|
119
|
+
query(orders)
|
|
120
|
+
.where({ status: 'pending' })
|
|
121
|
+
.orderBy('created_at', 'asc')
|
|
122
|
+
.limit(100)
|
|
123
|
+
.update({ status: 'processing' }),
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Bound Query Shorthand
|
|
128
|
+
|
|
129
|
+
If you already have a `db` instance in hand and do not need a standalone query value, `db.query(table)` returns the same query builder already bound to that database:
|
|
108
130
|
|
|
109
131
|
```ts
|
|
110
|
-
await db
|
|
132
|
+
let recentPendingOrders = await db
|
|
111
133
|
.query(orders)
|
|
112
134
|
.where({ status: 'pending' })
|
|
113
|
-
.orderBy('created_at', '
|
|
114
|
-
.limit(
|
|
115
|
-
.
|
|
135
|
+
.orderBy('created_at', 'desc')
|
|
136
|
+
.limit(20)
|
|
137
|
+
.all()
|
|
116
138
|
```
|
|
117
139
|
|
|
118
140
|
## CRUD Helpers
|
|
@@ -225,38 +247,80 @@ Return behavior:
|
|
|
225
247
|
- `updateMany`/`deleteMany` -> `WriteResult`
|
|
226
248
|
- `delete` -> `boolean`
|
|
227
249
|
|
|
228
|
-
###
|
|
229
|
-
|
|
230
|
-
For write operations, data validation happens before SQL is executed so invalid data does not get written to the database.
|
|
250
|
+
### Validation and Lifecycle
|
|
231
251
|
|
|
232
|
-
|
|
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.
|
|
233
254
|
|
|
234
|
-
|
|
235
|
-
|
|
255
|
+
```ts
|
|
256
|
+
import { column as c, fail, table } from 'remix/data-table'
|
|
236
257
|
|
|
237
|
-
|
|
258
|
+
let payments = table({
|
|
259
|
+
name: 'payments',
|
|
260
|
+
columns: {
|
|
261
|
+
id: c.uuid(),
|
|
262
|
+
amount: c.decimal(10, 2),
|
|
263
|
+
},
|
|
264
|
+
beforeWrite({ value }) {
|
|
265
|
+
return {
|
|
266
|
+
value: {
|
|
267
|
+
...value,
|
|
268
|
+
amount: typeof value.amount === 'string' ? value.amount.trim() : value.amount,
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
validate({ operation, value }) {
|
|
273
|
+
if (operation === 'create' && typeof value.amount === 'string') {
|
|
274
|
+
let amount = Number(value.amount)
|
|
238
275
|
|
|
239
|
-
|
|
276
|
+
if (!Number.isFinite(amount)) {
|
|
277
|
+
return fail('Expected a numeric amount', ['amount'])
|
|
278
|
+
}
|
|
240
279
|
|
|
241
|
-
|
|
242
|
-
|
|
280
|
+
return { value: { ...value, amount } }
|
|
281
|
+
}
|
|
243
282
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
283
|
+
return { value }
|
|
284
|
+
},
|
|
285
|
+
beforeDelete({ where }) {
|
|
286
|
+
if (where.length === 0) {
|
|
287
|
+
return fail('Refusing unscoped delete')
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
afterRead({ value }) {
|
|
291
|
+
if (!('amount' in value)) {
|
|
292
|
+
return { value }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
value: {
|
|
297
|
+
...value,
|
|
298
|
+
// Example read-time shaping
|
|
299
|
+
amount:
|
|
300
|
+
typeof value.amount === 'number' ? Math.round(value.amount * 100) / 100 : value.amount,
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
},
|
|
248
304
|
})
|
|
249
|
-
|
|
250
|
-
if (!result.success) {
|
|
251
|
-
// Handle validation issues
|
|
252
|
-
}
|
|
253
305
|
```
|
|
254
306
|
|
|
255
|
-
|
|
307
|
+
Use `fail(...)` in hooks when you want to return issues without manually building `{ issues: [...] }`.
|
|
308
|
+
|
|
309
|
+
Validation and lifecycle semantics:
|
|
256
310
|
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
311
|
+
- Write order is `beforeWrite -> validate -> timestamp/default touch -> execute -> afterWrite`
|
|
312
|
+
- `validate` runs for writes (`create`, `createMany`, `insert`, `insertMany`, `update`, `updateMany`, `upsert`)
|
|
313
|
+
- Hook context includes `{ operation: 'create' | 'update', tableName, value }`
|
|
314
|
+
- Write payloads are partial objects
|
|
315
|
+
- Unknown columns fail validation before and after hook processing
|
|
316
|
+
- `beforeDelete` can veto deletes by returning `{ issues }`
|
|
317
|
+
- `afterDelete` runs after successful deletes with `affectedRows`
|
|
318
|
+
- `afterRead` runs for each loaded row (root rows, eager-loaded relation rows, and write-returning rows)
|
|
319
|
+
- `afterRead` receives the current read shape, which may be partial/projection rows; guard field access accordingly
|
|
320
|
+
- Predicate values (`where`, `having`, join predicates) are not runtime-validated
|
|
321
|
+
- Lifecycle callbacks are synchronous; returning a Promise throws a validation error
|
|
322
|
+
- Callback validation errors include `metadata.source` (`beforeWrite`, `validate`, `beforeDelete`, `afterRead`, etc.) for easier debugging
|
|
323
|
+
- Callbacks do not introduce implicit transactions (use `db.transaction(...)` when you need rollback guarantees)
|
|
260
324
|
|
|
261
325
|
## Transactions
|
|
262
326
|
|
|
@@ -278,6 +342,175 @@ await db.transaction(async (tx) => {
|
|
|
278
342
|
})
|
|
279
343
|
```
|
|
280
344
|
|
|
345
|
+
## Migrations
|
|
346
|
+
|
|
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`.
|
|
352
|
+
|
|
353
|
+
### Example Setup
|
|
354
|
+
|
|
355
|
+
```txt
|
|
356
|
+
app/
|
|
357
|
+
db/
|
|
358
|
+
migrations/
|
|
359
|
+
20260228090000_create_users.ts
|
|
360
|
+
20260301113000_add_user_status.ts
|
|
361
|
+
migrate.ts
|
|
362
|
+
```
|
|
363
|
+
|
|
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.
|
|
367
|
+
|
|
368
|
+
### Migration File Example
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import { column as c, table } from 'remix/data-table'
|
|
372
|
+
import { createMigration } from 'remix/data-table/migrations'
|
|
373
|
+
|
|
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
|
+
})
|
|
382
|
+
|
|
383
|
+
export default createMigration({
|
|
384
|
+
async up({ db, schema }) {
|
|
385
|
+
await schema.createTable(users)
|
|
386
|
+
await schema.createIndex(users, 'email', { unique: true })
|
|
387
|
+
|
|
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
|
+
})
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Runner Script Example
|
|
399
|
+
|
|
400
|
+
In `app/db/migrate.ts`:
|
|
401
|
+
|
|
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'
|
|
408
|
+
|
|
409
|
+
let directionArg = process.argv[2] ?? 'up'
|
|
410
|
+
let direction = directionArg === 'down' ? 'down' : 'up'
|
|
411
|
+
let to = process.argv[3]
|
|
412
|
+
|
|
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)
|
|
417
|
+
|
|
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()
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Use `journalTable` if you want a custom migrations journal table name:
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
let runner = createMigrationRunner(adapter, migrations, {
|
|
433
|
+
journalTable: 'app_migrations',
|
|
434
|
+
})
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Run it with your runtime, for example:
|
|
438
|
+
|
|
439
|
+
```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
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Use `step` when you want bounded rollforward/rollback behavior instead of a target id:
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
await runner.up({ step: 1 })
|
|
450
|
+
await runner.down({ step: 1 })
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
`to` and `step` are mutually exclusive. Use one or the other for a given run.
|
|
454
|
+
|
|
455
|
+
Use `dryRun` to compile and inspect the SQL plan without applying migrations:
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
let dryRunResult = await runner.up({ dryRun: true })
|
|
459
|
+
console.log(dryRunResult.sql)
|
|
460
|
+
```
|
|
461
|
+
|
|
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.
|
|
465
|
+
|
|
466
|
+
You can also pass a pre-built SQL statement into `schema.plan(...)` when authoring migrations:
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
import { sql } from 'remix/data-table'
|
|
470
|
+
|
|
471
|
+
await schema.plan(sql`update users set status = ${'active'} where status is null`)
|
|
472
|
+
```
|
|
473
|
+
|
|
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.
|
|
477
|
+
|
|
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.
|
|
480
|
+
|
|
481
|
+
For key-oriented migration APIs, single-column and compound forms are both supported:
|
|
482
|
+
|
|
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
|
+
})
|
|
489
|
+
```
|
|
490
|
+
|
|
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.
|
|
493
|
+
|
|
494
|
+
This is useful when you want to:
|
|
495
|
+
|
|
496
|
+
- Review generated SQL in CI before deploying
|
|
497
|
+
- Verify migration ordering and target/step selection
|
|
498
|
+
- Audit dialect-specific SQL differences across adapters
|
|
499
|
+
|
|
500
|
+
For non-filesystem runtimes, register migrations manually:
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
import { createMigrationRegistry, createMigrationRunner } from 'remix/data-table/migrations'
|
|
504
|
+
import createUsers from './db/migrations/20260228090000_create_users.ts'
|
|
505
|
+
|
|
506
|
+
let registry = createMigrationRegistry()
|
|
507
|
+
registry.register({ id: '20260228090000', name: 'create_users', migration: createUsers })
|
|
508
|
+
|
|
509
|
+
// adapter from createPostgresDatabaseAdapter/createMysqlDatabaseAdapter/createSqliteDatabaseAdapter
|
|
510
|
+
let runner = createMigrationRunner(adapter, registry)
|
|
511
|
+
await runner.up()
|
|
512
|
+
```
|
|
513
|
+
|
|
281
514
|
## Raw SQL Escape Hatch
|
|
282
515
|
|
|
283
516
|
```ts
|
|
@@ -287,9 +520,27 @@ await db.exec(sql`select * from users where id = ${'u_001'}`)
|
|
|
287
520
|
await db.exec(rawSql('update users set role = ? where id = ?', ['admin', 'u_001']))
|
|
288
521
|
```
|
|
289
522
|
|
|
523
|
+
Use `sql` when you need raw SQL plus safe value interpolation:
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
import { sql } from 'remix/data-table'
|
|
527
|
+
|
|
528
|
+
let email = input.email
|
|
529
|
+
let minCreatedAt = input.minCreatedAt
|
|
530
|
+
|
|
531
|
+
let result = await db.exec(sql`
|
|
532
|
+
select id, email
|
|
533
|
+
from users
|
|
534
|
+
where email = ${email}
|
|
535
|
+
and created_at >= ${minCreatedAt}
|
|
536
|
+
`)
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
`sql` keeps values parameterized per adapter dialect, so you can avoid manual string concatenation.
|
|
540
|
+
|
|
290
541
|
## Related Packages
|
|
291
542
|
|
|
292
|
-
- [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) -
|
|
543
|
+
- [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Optional schema parsing you can use inside table-level `validate(...)` hooks
|
|
293
544
|
- [`data-table-postgres`](https://github.com/remix-run/remix/tree/main/packages/data-table-postgres) - PostgreSQL adapter
|
|
294
545
|
- [`data-table-mysql`](https://github.com/remix-run/remix/tree/main/packages/data-table-mysql) - MySQL adapter
|
|
295
546
|
- [`data-table-sqlite`](https://github.com/remix-run/remix/tree/main/packages/data-table-sqlite) - SQLite adapter
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export type { AdapterCapabilityOverrides, AdapterCapabilities,
|
|
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
2
|
export { DataTableAdapterError, DataTableConstraintError, DataTableError, DataTableQueryError, DataTableValidationError, } from './lib/errors.ts';
|
|
3
|
-
export type { AnyRelation, AnyColumn, AnyTable, BelongsToOptions, ColumnReference, ColumnReferenceForQualifiedName,
|
|
4
|
-
export { belongsTo, columnMetadataKey,
|
|
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
|
+
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
|
+
export type { ColumnNamespace } from './lib/column.ts';
|
|
6
|
+
export { ColumnBuilder, column } from './lib/column.ts';
|
|
5
7
|
export type { Predicate, WhereInput, WhereObject } from './lib/operators.ts';
|
|
6
8
|
export { and, between, eq, gt, gte, ilike, inList, isNull, like, lt, lte, ne, notInList, notNull, or, } from './lib/operators.ts';
|
|
7
9
|
export type { SqlStatement } from './lib/sql.ts';
|
|
8
10
|
export { rawSql, sql } from './lib/sql.ts';
|
|
9
|
-
export type { CountOptions, CreateManyResultOptions, CreateManyRowsOptions, CreateResultOptions, CreateRowOptions,
|
|
10
|
-
export { createDatabase,
|
|
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';
|
|
13
|
+
export type { AnyQuery } from './lib/query.ts';
|
|
14
|
+
export { Query, query } from './lib/query.ts';
|
|
11
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { DataTableAdapterError, DataTableConstraintError, DataTableError, DataTableQueryError, DataTableValidationError, } from "./lib/errors.js";
|
|
2
|
-
export { belongsTo, columnMetadataKey,
|
|
2
|
+
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.js";
|
|
3
|
+
export { ColumnBuilder, column } from "./lib/column.js";
|
|
3
4
|
export { and, between, eq, gt, gte, ilike, inList, isNull, like, lt, lte, ne, notInList, notNull, or, } from "./lib/operators.js";
|
|
4
5
|
export { rawSql, sql } from "./lib/sql.js";
|
|
5
|
-
export { createDatabase,
|
|
6
|
+
export { createDatabase, Database } from "./lib/database.js";
|
|
7
|
+
export { Query, query } from "./lib/query.js";
|