remix 3.0.0-beta.4 → 3.0.0-beta.6
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 +4 -2
- package/dist/assets/types/hmr.d.ts +2 -0
- package/dist/cli-entry.js +1 -1
- package/dist/data-table/cli.d.ts +2 -0
- package/dist/data-table/cli.d.ts.map +1 -0
- package/dist/{ui/scroll-lock.js → data-table/cli.js} +1 -1
- package/dist/node-hmr/runtime.d.ts +2 -0
- package/dist/node-hmr/runtime.d.ts.map +1 -0
- package/dist/node-hmr/runtime.js +2 -0
- package/dist/node-hmr/types.d.ts +2 -0
- package/dist/node-hmr.d.ts +2 -0
- package/dist/node-hmr.d.ts.map +1 -0
- package/dist/{ui/glyph.js → node-hmr.js} +1 -1
- package/dist/ui/accordion/primitives.d.ts +2 -0
- package/dist/ui/accordion/primitives.d.ts.map +1 -0
- package/dist/ui/accordion/primitives.js +2 -0
- package/dist/ui/button.d.ts +1 -0
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +1 -0
- package/dist/ui/checkbox.d.ts +3 -0
- package/dist/ui/checkbox.d.ts.map +1 -0
- package/dist/ui/checkbox.js +3 -0
- package/dist/ui/combobox/primitives.d.ts +2 -0
- package/dist/ui/combobox/primitives.d.ts.map +1 -0
- package/dist/ui/combobox/primitives.js +2 -0
- package/dist/ui/dev/refresh.d.ts +2 -0
- package/dist/ui/dev/refresh.d.ts.map +1 -0
- package/dist/ui/dev/refresh.js +2 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.d.ts.map +1 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/menu/primitives.d.ts +2 -0
- package/dist/ui/menu/primitives.d.ts.map +1 -0
- package/dist/ui/menu/primitives.js +2 -0
- package/dist/ui/radio.d.ts +3 -0
- package/dist/ui/radio.d.ts.map +1 -0
- package/dist/ui/radio.js +3 -0
- package/dist/ui/select/primitives.d.ts +2 -0
- package/dist/ui/select/primitives.d.ts.map +1 -0
- package/dist/ui/select/primitives.js +2 -0
- package/dist/ui/tabs/primitives.d.ts +2 -0
- package/dist/ui/tabs/primitives.d.ts.map +1 -0
- package/dist/ui/tabs/primitives.js +2 -0
- package/dist/ui/tabs.d.ts +2 -0
- package/dist/ui/tabs.d.ts.map +1 -0
- package/{src/ui/theme.ts → dist/ui/tabs.js} +1 -1
- package/dist/ui/toggle/primitives.d.ts +2 -0
- package/dist/ui/toggle/primitives.d.ts.map +1 -0
- package/dist/ui/toggle/primitives.js +2 -0
- package/dist/ui/toggle.d.ts +3 -0
- package/dist/ui/toggle.d.ts.map +1 -0
- package/dist/ui/toggle.js +3 -0
- package/dist/ui-hmr/assets.d.ts +2 -0
- package/dist/ui-hmr/assets.d.ts.map +1 -0
- package/dist/ui-hmr/assets.js +2 -0
- package/dist/ui-hmr/node.d.ts +3 -0
- package/dist/ui-hmr/node.d.ts.map +1 -0
- package/dist/ui-hmr/node.js +3 -0
- package/dist/ui-hmr/runtime/browser.d.ts +2 -0
- package/dist/ui-hmr/runtime/browser.d.ts.map +1 -0
- package/dist/ui-hmr/runtime/browser.js +2 -0
- package/dist/ui-hmr/runtime/server.d.ts +2 -0
- package/dist/ui-hmr/runtime/server.d.ts.map +1 -0
- package/dist/ui-hmr/runtime/server.js +2 -0
- package/dist/ui-hmr.d.ts +2 -0
- package/dist/ui-hmr.d.ts.map +1 -0
- package/{src/ui/glyph.ts → dist/ui-hmr.js} +1 -1
- package/package.json +122 -142
- package/src/assets/README.md +322 -56
- package/src/assets/types/hmr.d.ts +2 -0
- package/src/cli/README.md +105 -1
- package/src/cookie/README.md +4 -4
- package/src/data-table/README.md +202 -68
- package/src/data-table/cli.ts +2 -0
- package/src/data-table-mysql/README.md +46 -17
- package/src/data-table-postgres/README.md +39 -13
- package/src/data-table-sqlite/README.md +38 -20
- package/src/fetch-proxy/README.md +25 -0
- package/src/form-data-parser/README.md +4 -4
- package/src/mime/README.md +8 -1
- package/src/node-fetch-server/README.md +39 -13
- package/src/node-hmr/README.md +307 -0
- package/src/node-hmr/runtime.ts +2 -0
- package/src/node-hmr/types.d.ts +2 -0
- package/{dist/ui/theme.js → src/node-hmr.ts} +1 -1
- package/src/route-pattern/README.md +141 -13
- package/src/session/README.md +1 -1
- package/src/session-middleware/README.md +9 -7
- package/src/test/README.md +161 -115
- package/src/ui/README.md +116 -157
- package/src/ui/accordion/README.md +50 -14
- package/src/ui/accordion/primitives/README.md +202 -0
- package/src/ui/accordion/primitives.ts +2 -0
- package/src/ui/anchor/README.md +37 -2
- package/src/ui/breadcrumbs/README.md +4 -4
- package/src/ui/button/README.md +26 -26
- package/src/ui/button.ts +1 -0
- package/src/ui/checkbox/README.md +59 -0
- package/src/ui/checkbox.ts +3 -0
- package/src/ui/combobox/README.md +58 -9
- package/src/ui/combobox/primitives/README.md +194 -0
- package/src/ui/combobox/primitives.ts +2 -0
- package/src/ui/dev/refresh.ts +2 -0
- package/src/ui/input/README.md +52 -0
- package/src/ui/input.ts +3 -0
- package/src/ui/listbox/README.md +9 -41
- package/src/ui/menu/README.md +55 -14
- package/src/ui/menu/primitives/README.md +161 -0
- package/src/ui/menu/primitives.ts +2 -0
- package/src/ui/popover/README.md +20 -39
- package/src/ui/radio/README.md +53 -0
- package/src/ui/radio.ts +3 -0
- package/src/ui/select/README.md +29 -19
- package/src/ui/select/primitives/README.md +117 -0
- package/src/ui/select/primitives.ts +2 -0
- package/src/ui/tabs/README.md +141 -0
- package/src/ui/tabs/primitives/README.md +141 -0
- package/src/ui/tabs/primitives.ts +2 -0
- package/src/ui/tabs.ts +2 -0
- package/src/ui/test/README.md +151 -60
- package/src/ui/toggle/README.md +56 -0
- package/src/ui/toggle/primitives/README.md +56 -0
- package/src/ui/toggle/primitives.ts +2 -0
- package/src/ui/toggle.ts +3 -0
- package/src/ui-hmr/README.md +119 -0
- package/{dist/ui/separator.js → src/ui-hmr/assets.ts} +1 -1
- package/src/ui-hmr/node.ts +3 -0
- package/src/ui-hmr/runtime/browser.ts +2 -0
- package/src/ui-hmr/runtime/server.ts +2 -0
- package/src/ui-hmr.ts +2 -0
- package/dist/ui/glyph.d.ts +0 -2
- package/dist/ui/glyph.d.ts.map +0 -1
- package/dist/ui/scroll-lock.d.ts +0 -2
- package/dist/ui/scroll-lock.d.ts.map +0 -1
- package/dist/ui/separator.d.ts +0 -2
- package/dist/ui/separator.d.ts.map +0 -1
- package/dist/ui/theme.d.ts +0 -2
- package/dist/ui/theme.d.ts.map +0 -1
- package/src/ui/glyph/README.md +0 -72
- package/src/ui/scroll-lock/README.md +0 -33
- package/src/ui/scroll-lock.ts +0 -2
- package/src/ui/separator.ts +0 -2
- package/src/ui/theme/README.md +0 -103
package/src/cli/README.md
CHANGED
|
@@ -7,9 +7,11 @@ Command-line interface for creating and managing Remix projects.
|
|
|
7
7
|
- Create new Remix projects with `npx remix@next new` or installed `remix new`
|
|
8
8
|
- Print shell completion scripts with `remix completion`
|
|
9
9
|
- Check project environment and Remix app conventions with `remix doctor`
|
|
10
|
-
-
|
|
10
|
+
- Apply available low-risk project fixes with `remix doctor --fix`
|
|
11
|
+
- Manage the current app database with `remix db`
|
|
11
12
|
- Inspect the current app route tree with `remix routes`
|
|
12
13
|
- Run project tests with `remix test`
|
|
14
|
+
- Configure commands with a static, commented `remix.json` file
|
|
13
15
|
- Print the current Remix version with `remix version`
|
|
14
16
|
- Use the same CLI through the `remix` package or the `remix/cli` API
|
|
15
17
|
- Scaffold a starter app that matches the Remix project layout conventions
|
|
@@ -47,6 +49,9 @@ remix new my-remix-app
|
|
|
47
49
|
remix completion bash >> ~/.bashrc
|
|
48
50
|
remix doctor
|
|
49
51
|
remix doctor --fix
|
|
52
|
+
remix db migrate
|
|
53
|
+
remix db status
|
|
54
|
+
remix db reset --force
|
|
50
55
|
remix routes
|
|
51
56
|
remix routes --table
|
|
52
57
|
remix routes --table --no-headers
|
|
@@ -64,6 +69,9 @@ await runRemix(['new', 'my-remix-app'])
|
|
|
64
69
|
await runRemix(['completion', 'bash'])
|
|
65
70
|
await runRemix(['doctor'])
|
|
66
71
|
await runRemix(['doctor', '--fix'])
|
|
72
|
+
await runRemix(['db', 'migrate'])
|
|
73
|
+
await runRemix(['db', 'status'])
|
|
74
|
+
await runRemix(['db', 'reset', '--force'])
|
|
67
75
|
await runRemix(['routes'])
|
|
68
76
|
await runRemix(['routes', '--table'])
|
|
69
77
|
await runRemix(['routes', '--table', '--no-headers'])
|
|
@@ -71,8 +79,104 @@ await runRemix(['test'])
|
|
|
71
79
|
await runRemix(['version'])
|
|
72
80
|
```
|
|
73
81
|
|
|
82
|
+
Destructive database commands (`remix db wipe` and `remix db reset`) refuse to run without `--force`.
|
|
83
|
+
|
|
74
84
|
`runRemix()` returns the CLI exit code as a promise.
|
|
75
85
|
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
The CLI loads an optional `remix.json`. The file is parsed as JSONC, so it may contain comments and
|
|
89
|
+
trailing commas. Every top-level field is optional:
|
|
90
|
+
|
|
91
|
+
```jsonc
|
|
92
|
+
{
|
|
93
|
+
"$schema": "https://remix.run/schemas/remix.json",
|
|
94
|
+
|
|
95
|
+
"db": {
|
|
96
|
+
"adapter": {
|
|
97
|
+
"type": "sqlite",
|
|
98
|
+
"filename": { "env": "DATABASE_URL", "default": "./db/app.sqlite" },
|
|
99
|
+
"foreignKeys": true,
|
|
100
|
+
"busyTimeout": 5000,
|
|
101
|
+
},
|
|
102
|
+
"migrations": {
|
|
103
|
+
"directory": "./db/migrations",
|
|
104
|
+
"journalTable": "data_table_migrations",
|
|
105
|
+
},
|
|
106
|
+
"seed": "./db/seed.sql",
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
"doctor": {
|
|
110
|
+
"strict": true,
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
"test": {
|
|
114
|
+
// Test discovery
|
|
115
|
+
"files": ["**/*.test{,.browser,.e2e}.{ts,tsx}"],
|
|
116
|
+
"browserFiles": ["**/*.test.browser.{ts,tsx}"],
|
|
117
|
+
"e2eFiles": ["**/*.test.e2e.{ts,tsx}"],
|
|
118
|
+
"exclude": ["node_modules/**", "dist/**"],
|
|
119
|
+
"type": ["server", "browser", "e2e"],
|
|
120
|
+
"only": ["/checkout/i"],
|
|
121
|
+
|
|
122
|
+
// Test execution
|
|
123
|
+
"concurrency": 4,
|
|
124
|
+
"pool": "forks",
|
|
125
|
+
"setup": "./test/setup.ts",
|
|
126
|
+
"watch": false,
|
|
127
|
+
|
|
128
|
+
// Playwright
|
|
129
|
+
"playwright": {
|
|
130
|
+
"echo": false,
|
|
131
|
+
"open": false,
|
|
132
|
+
"configFile": "./playwright.config.ts",
|
|
133
|
+
"projects": ["chromium", "firefox"],
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
// Output
|
|
137
|
+
"reporter": "spec",
|
|
138
|
+
"quiet": false,
|
|
139
|
+
|
|
140
|
+
// Coverage
|
|
141
|
+
"coverage": {
|
|
142
|
+
"enabled": true,
|
|
143
|
+
"dir": ".coverage",
|
|
144
|
+
"include": ["app/**"],
|
|
145
|
+
"exclude": ["**/*.test.*"],
|
|
146
|
+
"branches": 80,
|
|
147
|
+
"functions": 80,
|
|
148
|
+
"lines": 80,
|
|
149
|
+
"statements": 80,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Explicit command flags and positional arguments override configured values. Repeated flags replace
|
|
156
|
+
configured arrays, while nested Playwright and coverage settings merge by field. Relative paths and
|
|
157
|
+
globs are resolved from the directory containing the config file. Use `remix doctor --no-strict` to
|
|
158
|
+
disable configured strict mode for one run.
|
|
159
|
+
|
|
160
|
+
`remix db` requires `db.adapter`. Adapters use `type: "sqlite"`, `type: "postgres"`, or
|
|
161
|
+
`type: "mysql"`; PostgreSQL uses `connectionString` and MySQL uses `uri`. A connection value may be
|
|
162
|
+
a string or an object naming an environment variable with an optional default. `db.seed` names a
|
|
163
|
+
SQL file that `remix db seed` and `remix db reset` run against the database. Database flags such as
|
|
164
|
+
`--migrations`, `--seed`, `--journal-table`, and `--connection-env` override the corresponding
|
|
165
|
+
config for one invocation. When no global `--config` is provided, database commands find the
|
|
166
|
+
nearest `remix.json` by walking up from the working directory.
|
|
167
|
+
|
|
168
|
+
Use the global `--config` option to select another JSONC file. The option itself is resolved from the
|
|
169
|
+
CLI working directory and may appear before or after the command:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
remix --config ./config/remix.ci.json test
|
|
173
|
+
remix test --config ./config/remix.ci.json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
A missing default `remix.json` is ignored. A missing explicitly selected file, malformed JSONC,
|
|
177
|
+
unknown property, or invalid value is reported as a CLI error. The optional `$schema` field enables
|
|
178
|
+
editor completion and validation; it has no runtime effect.
|
|
179
|
+
|
|
76
180
|
## License
|
|
77
181
|
|
|
78
182
|
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
package/src/cookie/README.md
CHANGED
|
@@ -84,16 +84,16 @@ let response = new Response('Hello, world!', {
|
|
|
84
84
|
|
|
85
85
|
### Custom Encoding
|
|
86
86
|
|
|
87
|
-
By default,
|
|
87
|
+
By default, cookie values are percent-encoded and wrapped in base64 so arbitrary string values can be safely stored in cookies. This is suitable for most use cases, but you can provide your own functions to customize the encoding and decoding of the cookie value.
|
|
88
88
|
|
|
89
89
|
```tsx
|
|
90
90
|
let sessionCookie = createCookie('session', {
|
|
91
|
-
encode: (value) => value,
|
|
92
|
-
decode: (value) => value,
|
|
91
|
+
encode: (value) => value.replaceAll(' ', '-'),
|
|
92
|
+
decode: (value) => value.replaceAll('-', ' '),
|
|
93
93
|
})
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
This can be useful for viewing the value of cookies in a human-readable format in the browser's developer tools. But you should be sure that the cookie value contains only characters that are [valid in a cookie value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#attributes).
|
|
96
|
+
Custom `encode` functions are used as the full cookie value codec. Their return values are signed when `secrets` are configured and then serialized as-is, without the default base64 wrapper. This can be useful for viewing the value of cookies in a human-readable format in the browser's developer tools. But you should be sure that the encoded cookie value contains only characters that are [valid in a cookie value](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#attributes) and safe for HTTP headers. Raw Unicode characters should be escaped or encoded by your custom function before they are returned.
|
|
97
97
|
|
|
98
98
|
## Related Packages
|
|
99
99
|
|
package/src/data-table/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
|
|
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
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
|
|
37
|
+
Define tables once, then create a database for your SQL dialect.
|
|
37
38
|
|
|
38
39
|
```ts
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
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
|
|
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
|
|
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() }],
|
|
@@ -343,7 +342,7 @@ await db.transaction(async (tx) => {
|
|
|
343
342
|
|
|
344
343
|
## Migrations
|
|
345
344
|
|
|
346
|
-
`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 wraps each migration in a transaction when the
|
|
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.
|
|
347
346
|
|
|
348
347
|
### Example Setup
|
|
349
348
|
|
|
@@ -357,7 +356,7 @@ app/
|
|
|
357
356
|
20260301113000_add_user_status/
|
|
358
357
|
up.sql
|
|
359
358
|
down.sql
|
|
360
|
-
|
|
359
|
+
db.ts
|
|
361
360
|
```
|
|
362
361
|
|
|
363
362
|
- Keep migration directories in one parent directory (for example `app/db/migrations`).
|
|
@@ -388,90 +387,120 @@ drop table if exists users;
|
|
|
388
387
|
|
|
389
388
|
### Multi-Statement Driver Configuration
|
|
390
389
|
|
|
391
|
-
The runner sends each migration to the
|
|
390
|
+
The runner sends each migration to the database as a single multi-statement script. Make sure the underlying driver accepts multiple statements:
|
|
392
391
|
|
|
393
392
|
- `better-sqlite3`: works out of the box (`db.exec`).
|
|
394
393
|
- `pg`: works out of the box when no parameter array is passed.
|
|
395
394
|
- `mysql2`: requires `multipleStatements: true` on the connection/pool.
|
|
396
395
|
|
|
397
396
|
```ts
|
|
398
|
-
import {
|
|
397
|
+
import { createMysqlDatabase } from 'remix/data-table/mysql'
|
|
399
398
|
|
|
400
|
-
let
|
|
399
|
+
let db = createMysqlDatabase({
|
|
401
400
|
uri: process.env.DATABASE_URL,
|
|
402
401
|
multipleStatements: true,
|
|
403
402
|
})
|
|
404
403
|
```
|
|
405
404
|
|
|
406
|
-
###
|
|
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
|
+
},
|
|
424
|
+
}
|
|
425
|
+
```
|
|
407
426
|
|
|
408
|
-
|
|
427
|
+
Application runtime setup remains application-owned. It does not need to expose any special
|
|
428
|
+
exports for the CLI.
|
|
409
429
|
|
|
410
|
-
|
|
411
|
-
import path from 'node:path'
|
|
412
|
-
import { Pool } from 'pg'
|
|
413
|
-
import { createPostgresDatabaseAdapter } from 'remix/data-table/postgres'
|
|
414
|
-
import { createMigrationRunner } from 'remix/data-table/migrations'
|
|
415
|
-
import { loadMigrations } from 'remix/data-table/migrations/node'
|
|
430
|
+
Run lifecycle commands through the Remix CLI:
|
|
416
431
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
432
|
+
```sh
|
|
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
|
|
439
|
+
```
|
|
420
440
|
|
|
421
|
-
|
|
422
|
-
let adapter = createPostgresDatabaseAdapter(pool)
|
|
423
|
-
let migrations = await loadMigrations(path.resolve('app/db/migrations'))
|
|
424
|
-
let runner = createMigrationRunner(adapter, migrations)
|
|
441
|
+
`--to` accepts a bare migration id (`20260301113000`) or the full directory name (`20260301113000_add_user_status`).
|
|
425
442
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
})
|
|
432
|
-
} finally {
|
|
433
|
-
await pool.end()
|
|
434
|
-
}
|
|
435
|
-
```
|
|
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
|
|
436
448
|
|
|
437
|
-
|
|
449
|
+
Load migrations and pass the resolved collection directly to the database:
|
|
438
450
|
|
|
439
451
|
```ts
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
452
|
+
import { loadMigrations } from 'remix/data-table/migrations/node'
|
|
453
|
+
|
|
454
|
+
let migrations = await loadMigrations('./db/migrations')
|
|
455
|
+
await db.migrate(migrations)
|
|
443
456
|
```
|
|
444
457
|
|
|
445
|
-
|
|
458
|
+
`Database.migrate()` supports forward and backward directions, a target or step bound, dry runs,
|
|
459
|
+
and a custom journal table:
|
|
446
460
|
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
461
|
+
```ts
|
|
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)
|
|
452
472
|
```
|
|
453
473
|
|
|
454
|
-
|
|
474
|
+
`to` and `step` are mutually exclusive. Omit `journalTable` to use `data_table_migrations`.
|
|
475
|
+
|
|
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:
|
|
455
482
|
|
|
456
483
|
```ts
|
|
457
|
-
|
|
458
|
-
await runner.down({ step: 1 })
|
|
459
|
-
```
|
|
484
|
+
import { loadSeed } from 'remix/data-table/migrations/node'
|
|
460
485
|
|
|
461
|
-
|
|
486
|
+
let seed = await loadSeed('./db/seed.sql')
|
|
462
487
|
|
|
463
|
-
|
|
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' })
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
When a lifecycle command is the last thing a process does, close database-owned connections so
|
|
495
|
+
the process can exit:
|
|
464
496
|
|
|
465
497
|
```ts
|
|
466
|
-
|
|
467
|
-
for (let script of plan.sql) {
|
|
468
|
-
console.log(script)
|
|
469
|
-
}
|
|
498
|
+
await db.close()
|
|
470
499
|
```
|
|
471
500
|
|
|
472
501
|
### Transaction Modes
|
|
473
502
|
|
|
474
|
-
By default each migration is wrapped in a transaction when the
|
|
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`:
|
|
475
504
|
|
|
476
505
|
```sql
|
|
477
506
|
-- data-table/transaction: none
|
|
@@ -480,8 +509,8 @@ create index concurrently users_email_active_idx on users (email) where status =
|
|
|
480
509
|
|
|
481
510
|
Supported modes:
|
|
482
511
|
|
|
483
|
-
- `auto` (default): wrap when the
|
|
484
|
-
- `required`: wrap; the runner throws if the
|
|
512
|
+
- `auto` (default): wrap when the database supports transactional DDL.
|
|
513
|
+
- `required`: wrap; the runner throws if the database cannot support it.
|
|
485
514
|
- `none`: never wrap. Use this for statements like postgres `CREATE INDEX CONCURRENTLY` that cannot run inside a transaction.
|
|
486
515
|
|
|
487
516
|
You can also set `transaction` directly on a `MigrationDescriptor` when registering migrations programmatically.
|
|
@@ -491,7 +520,7 @@ You can also set `transaction` directly on a `MigrationDescriptor` when register
|
|
|
491
520
|
For non-filesystem runtimes, register migrations directly:
|
|
492
521
|
|
|
493
522
|
```ts
|
|
494
|
-
import { createMigrationRegistry
|
|
523
|
+
import { createMigrationRegistry } from 'remix/data-table/migrations'
|
|
495
524
|
|
|
496
525
|
let registry = createMigrationRegistry()
|
|
497
526
|
registry.register({
|
|
@@ -501,8 +530,7 @@ registry.register({
|
|
|
501
530
|
down: 'drop table users;',
|
|
502
531
|
})
|
|
503
532
|
|
|
504
|
-
|
|
505
|
-
await runner.up()
|
|
533
|
+
await db.migrate(registry)
|
|
506
534
|
```
|
|
507
535
|
|
|
508
536
|
## Raw SQL Escape Hatch
|
|
@@ -530,14 +558,120 @@ let result = await db.exec(sql`
|
|
|
530
558
|
`)
|
|
531
559
|
```
|
|
532
560
|
|
|
533
|
-
`sql` keeps values parameterized
|
|
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.
|
|
534
668
|
|
|
535
669
|
## Related Packages
|
|
536
670
|
|
|
537
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
|
|
538
|
-
- [`data-table-postgres`](https://github.com/remix-run/remix/tree/main/packages/data-table-postgres) - PostgreSQL
|
|
539
|
-
- [`data-table-mysql`](https://github.com/remix-run/remix/tree/main/packages/data-table-mysql) - MySQL
|
|
540
|
-
- [`data-table-sqlite`](https://github.com/remix-run/remix/tree/main/packages/data-table-sqlite) - SQLite
|
|
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
|
|
541
675
|
|
|
542
676
|
## License
|
|
543
677
|
|