remix 3.0.0-beta.5 → 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.
Files changed (59) hide show
  1. package/README.md +4 -2
  2. package/dist/assets/types/hmr.d.ts +2 -0
  3. package/dist/cli-entry.js +1 -1
  4. package/dist/data-table/cli.d.ts +2 -0
  5. package/dist/data-table/cli.d.ts.map +1 -0
  6. package/dist/data-table/cli.js +2 -0
  7. package/dist/node-hmr/runtime.d.ts +2 -0
  8. package/dist/node-hmr/runtime.d.ts.map +1 -0
  9. package/dist/node-hmr/runtime.js +2 -0
  10. package/dist/node-hmr/types.d.ts +2 -0
  11. package/dist/node-hmr.d.ts +2 -0
  12. package/dist/node-hmr.d.ts.map +1 -0
  13. package/dist/node-hmr.js +2 -0
  14. package/dist/ui/dev/refresh.d.ts +2 -0
  15. package/dist/ui/dev/refresh.d.ts.map +1 -0
  16. package/dist/ui/dev/refresh.js +2 -0
  17. package/dist/ui-hmr/assets.d.ts +2 -0
  18. package/dist/ui-hmr/assets.d.ts.map +1 -0
  19. package/dist/ui-hmr/assets.js +2 -0
  20. package/dist/ui-hmr/node.d.ts +3 -0
  21. package/dist/ui-hmr/node.d.ts.map +1 -0
  22. package/dist/ui-hmr/node.js +3 -0
  23. package/dist/ui-hmr/runtime/browser.d.ts +2 -0
  24. package/dist/ui-hmr/runtime/browser.d.ts.map +1 -0
  25. package/dist/ui-hmr/runtime/browser.js +2 -0
  26. package/dist/ui-hmr/runtime/server.d.ts +2 -0
  27. package/dist/ui-hmr/runtime/server.d.ts.map +1 -0
  28. package/dist/ui-hmr/runtime/server.js +2 -0
  29. package/dist/ui-hmr.d.ts +2 -0
  30. package/dist/ui-hmr.d.ts.map +1 -0
  31. package/dist/ui-hmr.js +2 -0
  32. package/package.json +78 -126
  33. package/src/assets/README.md +322 -56
  34. package/src/assets/types/hmr.d.ts +2 -0
  35. package/src/cli/README.md +105 -1
  36. package/src/cookie/README.md +4 -4
  37. package/src/data-table/README.md +202 -68
  38. package/src/data-table/cli.ts +2 -0
  39. package/src/data-table-mysql/README.md +22 -18
  40. package/src/data-table-postgres/README.md +14 -14
  41. package/src/data-table-sqlite/README.md +38 -20
  42. package/src/fetch-proxy/README.md +25 -0
  43. package/src/node-fetch-server/README.md +11 -11
  44. package/src/node-hmr/README.md +307 -0
  45. package/src/node-hmr/runtime.ts +2 -0
  46. package/src/node-hmr/types.d.ts +2 -0
  47. package/src/node-hmr.ts +2 -0
  48. package/src/route-pattern/README.md +91 -8
  49. package/src/session-middleware/README.md +9 -7
  50. package/src/test/README.md +161 -115
  51. package/src/ui/README.md +74 -1
  52. package/src/ui/dev/refresh.ts +2 -0
  53. package/src/ui/test/README.md +151 -60
  54. package/src/ui-hmr/README.md +119 -0
  55. package/src/ui-hmr/assets.ts +2 -0
  56. package/src/ui-hmr/node.ts +3 -0
  57. package/src/ui-hmr/runtime/browser.ts +2 -0
  58. package/src/ui-hmr/runtime/server.ts +2 -0
  59. package/src/ui-hmr.ts +2 -0
@@ -1,12 +1,12 @@
1
1
  # data-table-postgres
2
2
 
3
- PostgreSQL adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table). Use this package when you want `data-table` APIs backed by `pg`.
3
+ PostgreSQL database driver for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table), backed by `pg`.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Native `pg` Integration**: Works with `pg` `Pool` and `PoolClient` instances
7
+ - **Native `pg` Integration**: Creates a pool from `pg` configuration or uses an existing pool or client
8
8
  - **Full `data-table` API Support**: Queries, relations, writes, and transactions
9
- - **Adapter-Owned Compiler**: SQL compilation lives in this adapter, with optional shared pure helpers from `data-table`
9
+ - **PostgreSQL Compiler**: SQL compilation is handled automatically for PostgreSQL
10
10
  - **Multi-Statement Migrations**: `executeScript()` runs `up.sql` / `down.sql` files natively via `pg`
11
11
  - **Postgres Capabilities Enabled By Default**:
12
12
  - `returning: true`
@@ -24,20 +24,20 @@ npm i remix pg
24
24
  ## Usage
25
25
 
26
26
  ```ts
27
- import { Pool } from 'pg'
28
- import { createDatabase } from 'remix/data-table'
29
- import { createPostgresDatabaseAdapter } from 'remix/data-table/postgres'
27
+ import { createPostgresDatabase } from 'remix/data-table/postgres'
30
28
 
31
- let pool = new Pool({
29
+ let db = createPostgresDatabase({
32
30
  connectionString: process.env.DATABASE_URL,
33
31
  })
34
-
35
- let db = createDatabase(createPostgresDatabaseAdapter(pool))
36
32
  ```
37
33
 
38
34
  Use `db.query(...)`, relation loading, and transactions from `remix/data-table`. Import any driver-specific types you need directly from `pg`.
39
35
 
40
- ## Adapter Capabilities
36
+ Config-backed databases support `db.wipe()` and `db.reset()`. Call `await db.close()` during application shutdown to close the internally created pool. You may pass an existing `pg` pool or client when your application owns the driver lifecycle; `db.close()` leaves supplied clients alone, and destructive lifecycle methods are unavailable in that mode. `db.wipe()` requires a database name resolvable from the connection config (`database`, the path of `connectionString`, or the `PGDATABASE` environment variable) and throws when none is present.
37
+
38
+ Migration runs reserve one connection for the PostgreSQL advisory lock, migration SQL, and journal updates. Lock acquisition waits up to 60 seconds (via `lock_timeout`) and fails with an error instead of blocking forever. After a successful run the connection is unlocked and returned to the pool; if the migration or unlock fails, the reserved connection is destroyed instead of being reused, so a dirty session can never leak back into the pool. Nested migration lock acquisition throws instead of deadlocking.
39
+
40
+ ## Database Capabilities
41
41
 
42
42
  `data-table-postgres` reports this capability set by default:
43
43
 
@@ -51,7 +51,7 @@ Use `db.query(...)`, relation loading, and transactions from `remix/data-table`.
51
51
 
52
52
  ### Transaction Options
53
53
 
54
- Transaction options are passed through to the adapter as hints.
54
+ Transaction options are passed through to PostgreSQL as hints.
55
55
 
56
56
  ```ts
57
57
  await db.transaction(async (txDb) => txDb.exec('select 1'), {
@@ -77,7 +77,7 @@ Then run:
77
77
 
78
78
  ```sh
79
79
  REMIX_DATA_TABLE_POSTGRES_TEST_URL=postgres://postgres:postgres@127.0.0.1:5432/remix \
80
- pnpm test src/lib/adapter.integration.test.ts
80
+ pnpm test src/lib/driver.integration.test.ts
81
81
  ```
82
82
 
83
83
  Remove the container when you are done:
@@ -90,8 +90,8 @@ podman rm -f postgres
90
90
 
91
91
  - [`data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table) - Core query/relations API
92
92
  - [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Schema parsing and validation
93
- - [`data-table-mysql`](https://github.com/remix-run/remix/tree/main/packages/data-table-mysql) - MySQL adapter
94
- - [`data-table-sqlite`](https://github.com/remix-run/remix/tree/main/packages/data-table-sqlite) - SQLite adapter
93
+ - [`data-table-mysql`](https://github.com/remix-run/remix/tree/main/packages/data-table-mysql) - MySQL database driver
94
+ - [`data-table-sqlite`](https://github.com/remix-run/remix/tree/main/packages/data-table-sqlite) - SQLite database driver
95
95
 
96
96
  ## License
97
97
 
@@ -1,12 +1,12 @@
1
1
  # data-table-sqlite
2
2
 
3
- SQLite adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table). Use this package when you want `data-table` APIs backed by a synchronous SQLite client.
3
+ SQLite database driver for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table), backed by a synchronous SQLite client.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Native Runtime SQLite Support**: Works with Node's `node:sqlite` `DatabaseSync`, Bun's `bun:sqlite` `Database`, and compatible synchronous SQLite clients
7
+ - **Native Runtime SQLite Support**: Opens a configured filename with Node's `node:sqlite` or Bun's `bun:sqlite`, or uses a compatible synchronous SQLite client
8
8
  - **Full `data-table` API Support**: Queries, relations, writes, and transactions
9
- - **Adapter-Owned Compiler**: SQL compilation lives in this adapter, with optional shared pure helpers from `data-table`
9
+ - **SQLite Compiler**: SQL compilation is handled automatically for SQLite
10
10
  - **Multi-Statement Migrations**: `executeScript()` runs `up.sql` / `down.sql` files via `Database.exec()`
11
11
  - **SQLite Capabilities Enabled By Default**:
12
12
  - `returning: true`
@@ -23,31 +23,42 @@ npm i remix
23
23
 
24
24
  ## Usage
25
25
 
26
- ### Node
27
-
28
26
  ```ts
29
- import { DatabaseSync } from 'node:sqlite'
30
- import { createDatabase } from 'remix/data-table'
31
- import { createSqliteDatabaseAdapter } from 'remix/data-table/sqlite'
27
+ import { createSqliteDatabase } from 'remix/data-table/sqlite'
32
28
 
33
- let sqlite = new DatabaseSync('app.db')
34
- let db = createDatabase(createSqliteDatabaseAdapter(sqlite))
29
+ let db = createSqliteDatabase({
30
+ filename: 'app.db',
31
+ foreignKeys: true,
32
+ })
35
33
  ```
36
34
 
37
- ### Bun
35
+ The config-backed database uses `node:sqlite` in Node.js and `bun:sqlite` in Bun. It supports `db.wipe()` and `db.reset()` because it can close and reopen the database file. Call `await db.close()` during application shutdown to release the connection and its file handle.
36
+
37
+ Foreign key enforcement defaults to off on every runtime. When `foreignKeys` is enabled, the database restores foreign key enforcement each time it opens the connection, including after destructive lifecycle operations.
38
+
39
+ The database also applies `pragma busy_timeout = 5000` whenever it opens the connection, so writes wait for a locked database instead of failing immediately with `SQLITE_BUSY`. Use `busyTimeout` to override the timeout in milliseconds (`0` disables the wait).
40
+
41
+ You may also pass an existing synchronous client when your application owns its lifecycle:
38
42
 
39
43
  ```ts
40
44
  import { Database } from 'bun:sqlite'
41
- import { createDatabase } from 'remix/data-table'
42
- import { createSqliteDatabaseAdapter } from 'remix/data-table/sqlite'
45
+ import { createSqliteDatabase } from 'remix/data-table/sqlite'
43
46
 
44
47
  let sqlite = new Database('app.db')
45
- let db = createDatabase(createSqliteDatabaseAdapter(sqlite))
48
+ let db = createSqliteDatabase(sqlite)
49
+
50
+ // Leaves the supplied client open.
51
+ await db.close()
52
+
53
+ // The application closes the client it owns.
54
+ sqlite.close()
46
55
  ```
47
56
 
57
+ Destructive lifecycle methods are unavailable when you pass an existing client.
58
+
48
59
  This is a good fit for local development, embedded deployments, and single-node services. Import any driver-specific types you need directly from your runtime's SQLite module.
49
60
 
50
- ## Adapter Capabilities
61
+ ## Database Capabilities
51
62
 
52
63
  `data-table-sqlite` reports this capability set by default:
53
64
 
@@ -59,23 +70,30 @@ This is a good fit for local development, embedded deployments, and single-node
59
70
 
60
71
  ## Advanced Usage
61
72
 
73
+ ### Destructive Lifecycle And Locking
74
+
75
+ `db.wipe()` and `db.reset()` assume a single process owns the database file. Stop other processes before wiping: on POSIX systems another process keeps writing to the deleted inode, and on Windows an open handle blocks deletion entirely. Wiping removes the `-wal`, `-shm`, and `-journal` sidecar files along with the main database file so a freshly created database never associates with stale sidecars.
76
+
77
+ SQLite migrations run without a cross-process migration lock (`migrationLock: false`), so run migrations from one process at a time.
78
+
79
+ `filename` resolves against the current working directory — for `remix db` commands, wherever you invoke the CLI. Prefer absolute paths or paths derived from `import.meta.dirname`.
80
+
62
81
  ### In-Memory Database For Tests
63
82
 
64
83
  ```ts
65
84
  import { DatabaseSync } from 'node:sqlite'
66
- import { createDatabase } from 'remix/data-table'
67
- import { createSqliteDatabaseAdapter } from 'remix/data-table/sqlite'
85
+ import { createSqliteDatabase } from 'remix/data-table/sqlite'
68
86
 
69
87
  let sqlite = new DatabaseSync(':memory:')
70
- let db = createDatabase(createSqliteDatabaseAdapter(sqlite))
88
+ let db = createSqliteDatabase(sqlite)
71
89
  ```
72
90
 
73
91
  ## Related Packages
74
92
 
75
93
  - [`data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table) - Core query/relations API
76
94
  - [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Schema parsing and validation
77
- - [`data-table-postgres`](https://github.com/remix-run/remix/tree/main/packages/data-table-postgres) - PostgreSQL adapter
78
- - [`data-table-mysql`](https://github.com/remix-run/remix/tree/main/packages/data-table-mysql) - MySQL adapter
95
+ - [`data-table-postgres`](https://github.com/remix-run/remix/tree/main/packages/data-table-postgres) - PostgreSQL database driver
96
+ - [`data-table-mysql`](https://github.com/remix-run/remix/tree/main/packages/data-table-mysql) - MySQL database driver
79
97
 
80
98
  ## License
81
99
 
@@ -7,6 +7,7 @@ HTTP proxy utilities built on the web [Fetch API](https://developer.mozilla.org/
7
7
  - **Web Standards** - Built on the standard [JavaScript Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
8
8
  - **Cookie Rewriting** - Supports rewriting `Set-Cookie` headers received from target server
9
9
  - **Forwarding Headers** - Supports `X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port` headers
10
+ - **Encoding Headers** - Strips stale encoding and framing headers from proxied responses
10
11
  - **Custom Fetch** - Supports custom `fetch` implementations
11
12
 
12
13
  ## Installation
@@ -36,9 +37,33 @@ let title = text.match(/<title>([^<]+)<\/title>/)[1]
36
37
  assert(title.includes('Remix'))
37
38
  ```
38
39
 
40
+ ## Encoding and Framing Headers
41
+
42
+ Since proxying is done via `fetch` rather than raw HTTP messages, some encoding and framing headers need to be removed.
43
+
44
+ The incoming `Accept-Encoding` request header describes the final client, so it is not forwarded to the target server.
45
+
46
+ Since `fetch` can decompress upstream responses and does not expose raw HTTP transfer framing, `fetch-proxy` strips response headers that may no longer describe the returned body: `Content-Encoding`, related `Content-Length`, and `Transfer-Encoding`.
47
+
48
+ To support serving compressed responses to the final client, you'll need to compress the response after the proxy returns it, e.g. with the [`compressResponse` helper from `remix/response`](https://github.com/remix-run/remix/tree/main/packages/response#compress-responses):
49
+
50
+ ```ts
51
+ import { createFetchProxy } from 'remix/fetch-proxy'
52
+ import { compressResponse } from 'remix/response/compress'
53
+
54
+ let proxy = createFetchProxy('https://remix.run')
55
+
56
+ async function handleFetch(request: Request): Promise<Response> {
57
+ let response = await proxy(request)
58
+
59
+ return compressResponse(response, request)
60
+ }
61
+ ```
62
+
39
63
  ## Related Packages
40
64
 
41
65
  - [`node-fetch-server`](https://github.com/remix-run/remix/tree/main/packages/node-fetch-server) - Build HTTP servers for Node.js using the web fetch API
66
+ - [`response`](https://github.com/remix-run/remix/tree/main/packages/response) - Create, transform, and compress Fetch API responses
42
67
 
43
68
  ## License
44
69
 
@@ -334,9 +334,9 @@ pnpm run bench:update-readme
334
334
 
335
335
  <!-- benchmarks:start -->
336
336
 
337
- Last updated: 2026-04-29T17:19:30.407Z
337
+ Last updated: 2026-07-23T20:29:32.942Z
338
338
 
339
- Environment: Darwin 25.3.0, Apple M1 Pro, Node.js v24.15.0
339
+ Environment: Darwin 25.4.0, Apple M5 Pro, Node.js v24.18.0
340
340
 
341
341
  Command: `wrk -t12 -c400 -d30s`
342
342
 
@@ -346,9 +346,9 @@ Simple HTML response benchmarks without inspecting the incoming request.
346
346
 
347
347
  | Server | Version | Requests/sec | Avg latency | Transfer/sec |
348
348
  | ------------------------- | --------: | -----------: | ----------: | -----------: |
349
- | `node:http` | `24.15.0` | `47,110` | `10.66ms` | `9.66MB` |
350
- | `remix/node-fetch-server` | `0.13.0` | `43,317` | `11.69ms` | `8.80MB` |
351
- | `express` | `5.2.1` | `39,752` | `13.69ms` | `9.59MB` |
349
+ | `node:http` | `24.18.0` | `66,594` | `5.96ms` | `13.65MB` |
350
+ | `remix/node-fetch-server` | `0.14.0` | `61,587` | `7.88ms` | `12.51MB` |
351
+ | `express` | `5.2.1` | `58,424` | `8.42ms` | `14.10MB` |
352
352
 
353
353
  ### Small Body
354
354
 
@@ -356,9 +356,9 @@ POST benchmarks that read and print the request method, headers, and a small bod
356
356
 
357
357
  | Server | Version | Requests/sec | Avg latency | Transfer/sec |
358
358
  | ------------------------- | --------: | -----------: | ----------: | -----------: |
359
- | `remix/node-fetch-server` | `0.13.0` | `25,430` | `24.25ms` | `5.17MB` |
360
- | `node:http` | `24.15.0` | `25,088` | `23.89ms` | `5.14MB` |
361
- | `express` | `5.2.1` | `22,845` | `27.16ms` | `5.51MB` |
359
+ | `node:http` | `24.18.0` | `35,303` | `15.60ms` | `7.24MB` |
360
+ | `express` | `5.2.1` | `32,614` | `16.93ms` | `7.87MB` |
361
+ | `remix/node-fetch-server` | `0.14.0` | `29,521` | `18.98ms` | `6.00MB` |
362
362
 
363
363
  ### Large Body
364
364
 
@@ -366,9 +366,9 @@ POST benchmarks that read and print the request method, headers, and a 1 MB body
366
366
 
367
367
  | Server | Version | Requests/sec | Avg latency | Transfer/sec |
368
368
  | ------------------------- | --------: | -----------: | ----------: | -----------: |
369
- | `remix/node-fetch-server` | `0.13.0` | `1,086` | `217.69ms` | `225.87KB` |
370
- | `node:http` | `24.15.0` | `1,079` | `198.67ms` | `226.54KB` |
371
- | `express` | `5.2.1` | `1,022` | `216.07ms` | `252.51KB` |
369
+ | `node:http` | `24.18.0` | `1,798` | `206.65ms` | `377.42KB` |
370
+ | `remix/node-fetch-server` | `0.14.0` | `1,752` | `167.69ms` | `364.40KB` |
371
+ | `express` | `5.2.1` | `1,731` | `223.19ms` | `427.67KB` |
372
372
 
373
373
  <!-- benchmarks:end -->
374
374
 
@@ -0,0 +1,307 @@
1
+ # node-hmr
2
+
3
+ Run Node.js applications with Hot Module Reloading.
4
+
5
+ ## Features
6
+
7
+ - **HMR Runtime**: Provides an `import.meta.hot` API for modules that can handle hot updates
8
+ - **Module Hook Friendly**: Use Node's module customization hooks API to automatically insert `import.meta.hot` usage
9
+ - **Restart Fallback**: Restarts the child Node process when updates aren't accepted
10
+ - **Fetch Proxy Support**: Wrap fetch handlers so requests are delayed/retried during server updates/restarts
11
+ - **Browser HMR Integration**: Optionally hosts browser HMR coordination that survives child restarts
12
+
13
+ ## Installation
14
+
15
+ ```sh
16
+ npm i remix
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Create a development script that starts your app server with HMR enabled, along with any additional Node args, such as the `--import` flag to provide [Node module customization hooks](https://nodejs.org/api/module.html#customization-hooks) for [JSX syntax support](https://github.com/remix-run/remix/tree/main/packages/node-tsx) and [Remix component HMR](https://github.com/remix-run/remix/tree/main/packages/ui-hmr):
22
+
23
+ ```ts
24
+ // hmr.ts
25
+ import { run } from 'remix/node-hmr'
26
+
27
+ run('./server.ts', {
28
+ nodeArgs: ['--import', 'remix/node-tsx', '--import', 'remix/ui-hmr/node'],
29
+ watch: {
30
+ ignore: ['**/node_modules/**'],
31
+ },
32
+ })
33
+ ```
34
+
35
+ Then run the script with Node:
36
+
37
+ ```json
38
+ {
39
+ "scripts": {
40
+ "hmr": "NODE_ENV=development node hmr.ts"
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Fetch Proxy Support
46
+
47
+ During development, server updates can briefly leave your app unable to handle requests. In a server-only context, requests may be rejected while the child server is restarting. In a browser context, the browser may refresh or revalidate at the same time as a server restart, which can result in failed requests or a broken page.
48
+
49
+ A stable proxy server can avoid this by continuing to listen on the public port while `node-hmr` updates the child server behind it. `createHmrReadyFetch()` works with any fetch handler, so you can compose it with `createFetchProxy()` from [`remix/fetch-proxy`](https://github.com/remix-run/remix/tree/main/packages/fetch-proxy) to forward requests to the child server while delaying or retrying requests during updates.
50
+
51
+ ```ts
52
+ // hmr.ts
53
+ import * as http from 'node:http'
54
+
55
+ import { createFetchProxy } from 'remix/fetch-proxy'
56
+ import { run, createHmrReadyFetch } from 'remix/node-hmr'
57
+ import { createRequestListener } from 'remix/node-fetch-server'
58
+
59
+ const hmrProxyPort = 44100
60
+ const appPort = 44101
61
+
62
+ const hmrRunner = run('./server.ts', {
63
+ env: {
64
+ ...process.env,
65
+ PORT: String(appPort),
66
+ },
67
+ nodeArgs: ['--import', 'remix/node-tsx'],
68
+ })
69
+
70
+ const proxyFetch = createFetchProxy(`http://127.0.0.1:${appPort}`, {
71
+ xForwardedHeaders: true,
72
+ })
73
+
74
+ const server = http.createServer(createRequestListener(createHmrReadyFetch(hmrRunner, proxyFetch)))
75
+
76
+ server.listen(hmrProxyPort)
77
+ ```
78
+
79
+ By default, `createHmrReadyFetch()` retries `GET` and `HEAD` requests when the wrapped fetch handler throws or returns a `502`, `503`, or `504` response, but only if the server updated or restarted while the request was in flight. You can customize this policy with `shouldRetry`:
80
+
81
+ ```ts
82
+ let fetchWhenReady = createHmrReadyFetch(hmrRunner, proxyFetch, {
83
+ shouldRetry({ request, response }) {
84
+ if (request.method !== 'GET' && request.method !== 'HEAD') return false
85
+
86
+ return response === undefined || [502, 503, 504].includes(response.status)
87
+ },
88
+ })
89
+ ```
90
+
91
+ ## Browser HMR Integration
92
+
93
+ `node-hmr` can coordinate browser-facing HMR alongside server HMR. The parent process hosts the browser event stream, tracks files reported by asset servers in the child process, sends matching file events back to the child runtime, and emits the resulting browser updates to connected clients.
94
+
95
+ This is co-ordinated through the use of a browser HMR channel which can be created within the app server when running in `node-hmr` via the `remix/node-hmr/runtime` import:
96
+
97
+ ```ts
98
+ import { createBrowserHmrChannel } from 'remix/node-hmr/runtime'
99
+
100
+ let browserHmrChannel = await createBrowserHmrChannel()
101
+ ```
102
+
103
+ The `remix/node-hmr/runtime` API is only available inside a child process supervised by `node-hmr`. Importing it outside `node-hmr` throws. Supervised child processes automatically receive the `REMIX_NODE_HMR` environment variable which you can check before dynamically importing the runtime API:
104
+
105
+ ```ts
106
+ if (process.env.REMIX_NODE_HMR) {
107
+ let { createBrowserHmrChannel } = await import('remix/node-hmr/runtime')
108
+ let browserHmrChannel = await createBrowserHmrChannel()
109
+ }
110
+ ```
111
+
112
+ A browser HMR channel is scoped to the current child process. It gives browser HMR tooling an EventSource URL, a way to report the files it wants watched, and a way to respond to file changes with browser HMR events.
113
+
114
+ Browser asset servers can use this API to co-ordinate browser HMR with the server, for example, [`remix/assets`](https://github.com/remix-run/remix/tree/main/packages/assets) via its `hmr` option to `createAssetServer`:
115
+
116
+ ```ts
117
+ import { createAssetServer } from 'remix/assets'
118
+
119
+ let isDevelopment = process.env.NODE_ENV === 'development'
120
+
121
+ let assetServer = createAssetServer({
122
+ basePath: '/assets',
123
+ fileMap: { '/app/*path': 'app/*path' },
124
+ allowFiles: ['app/routes.ts', 'app/**/public/**'],
125
+ denyFiles: ['app/**/*.test.*'],
126
+ hmr:
127
+ isDevelopment && process.env.REMIX_NODE_HMR
128
+ ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
129
+ : undefined,
130
+ watch: isDevelopment,
131
+ })
132
+ ```
133
+
134
+ When `node-hmr` hot updates or restarts server code in a way that should refresh server-rendered UI, it sends a `server:update` event to connected clients.
135
+
136
+ Call `emitServerReady()` when your app server is ready to receive requests. This lets the parent process delay browser `server:update` events until a restarted app server has finished listening:
137
+
138
+ ```ts
139
+ server.listen(port, () => {
140
+ if (process.env.REMIX_NODE_HMR) {
141
+ import('remix/node-hmr/runtime').then((nodeHmr) => nodeHmr.emitServerReady())
142
+ }
143
+ })
144
+ ```
145
+
146
+ ## File Watching
147
+
148
+ The file system is watched automatically so server source changes can hot update or restart the child process.
149
+
150
+ You can optionally provide an array of glob patterns to the `watch.ignore` option.
151
+
152
+ ```ts
153
+ import { run } from 'remix/node-hmr'
154
+
155
+ run('./server.ts', {
156
+ nodeArgs: ['--import', 'remix/node-tsx', '--import', 'remix/ui-hmr/node'],
157
+ watch: {
158
+ ignore: ['**/node_modules/**'],
159
+ },
160
+ })
161
+ ```
162
+
163
+ You can also configure polling behavior. Polling defaults to `true` on Windows and `false` elsewhere:
164
+
165
+ ```ts
166
+ import { run } from 'remix/node-hmr'
167
+
168
+ run('./server.ts', {
169
+ nodeArgs: ['--import', 'remix/node-tsx', '--import', 'remix/ui-hmr/node'],
170
+ watch: {
171
+ poll: true,
172
+ pollInterval: 100,
173
+ },
174
+ })
175
+ ```
176
+
177
+ ## `import.meta.hot`
178
+
179
+ The `import.meta.hot` API provided by `node-hmr` is a small runtime contract for modules that can handle updates without restarting the process. It is primarily intended for transforms like [remix/ui-hmr](https://github.com/remix-run/remix/tree/main/packages/ui-hmr), but it can also be used directly.
180
+
181
+ To type `import.meta.hot`, add the HMR types to your TypeScript config:
182
+
183
+ ```json
184
+ {
185
+ "compilerOptions": {
186
+ "types": ["remix/node-hmr/types"]
187
+ }
188
+ }
189
+ ```
190
+
191
+ HMR accept calls are statically analyzed. Write them directly as `import.meta.hot.accept(...)`. Dependency accepts must use string literals or arrays of string literals; do not alias `import.meta.hot` or pass dynamically constructed dependency lists.
192
+
193
+ ```ts
194
+ if (import.meta.hot) {
195
+ import.meta.hot.accept()
196
+ }
197
+ ```
198
+
199
+ For consistency with browser HMR environments, `node-hmr` also implements `import.meta.hot.on(...)`, but no events are fired in server modules.
200
+
201
+ ### Accepting updates
202
+
203
+ Calling `accept()` makes the current module an HMR boundary. When the module changes, `node-hmr` evaluates the updated module and calls your callback with its exports.
204
+
205
+ ```ts
206
+ export let value = 1
207
+
208
+ if (import.meta.hot) {
209
+ import.meta.hot.accept((module) => {
210
+ if (typeof module.value !== 'number') {
211
+ import.meta.hot?.invalidate('Updated module no longer exports value')
212
+ return
213
+ }
214
+
215
+ value = module.value
216
+ })
217
+ }
218
+ ```
219
+
220
+ You can also accept updates from direct dependencies.
221
+
222
+ ```ts
223
+ import { value } from './value.ts'
224
+
225
+ let currentValue = value
226
+
227
+ export function readValue() {
228
+ return currentValue
229
+ }
230
+
231
+ if (import.meta.hot) {
232
+ import.meta.hot.accept('./value.ts', (module) => {
233
+ if (typeof module.value !== 'number') {
234
+ import.meta.hot?.invalidate('Updated dependency no longer exports value')
235
+ return
236
+ }
237
+
238
+ currentValue = module.value
239
+ })
240
+ }
241
+ ```
242
+
243
+ Multiple dependencies can be accepted at once. The callback receives an array where only the changed dependency is defined.
244
+
245
+ ```ts
246
+ if (import.meta.hot) {
247
+ import.meta.hot.accept(['./one.ts', './two.ts'], ([oneModule, twoModule]) => {
248
+ // oneModule is defined when ./one.ts changed.
249
+ // twoModule is defined when ./two.ts changed.
250
+ })
251
+ }
252
+ ```
253
+
254
+ ### Cleaning up
255
+
256
+ Register cleanup that should run before the module is replaced or disposed.
257
+
258
+ ```ts
259
+ let interval = setInterval(refreshCache, 30_000)
260
+
261
+ if (import.meta.hot) {
262
+ import.meta.hot.dispose(() => {
263
+ clearInterval(interval)
264
+ })
265
+ }
266
+ ```
267
+
268
+ The `data` object is preserved across updates for the same module. Use it for small pieces of state.
269
+
270
+ ```ts
271
+ let count = Number(import.meta.hot?.data.count ?? 0)
272
+
273
+ export function increment() {
274
+ count++
275
+ }
276
+
277
+ if (import.meta.hot) {
278
+ import.meta.hot.dispose((data) => {
279
+ data.count = count
280
+ })
281
+ }
282
+ ```
283
+
284
+ ### Invalidating updates
285
+
286
+ Call `invalidate()` inside an accept callback when the update cannot be applied safely. `node-hmr` falls back to a process restart.
287
+
288
+ ```ts
289
+ if (import.meta.hot) {
290
+ import.meta.hot.accept((module) => {
291
+ if (typeof module.value !== 'number') {
292
+ import.meta.hot?.invalidate('Updated module no longer exports value')
293
+ return
294
+ }
295
+ })
296
+ }
297
+ ```
298
+
299
+ ## Related Packages
300
+
301
+ - [`assets`](https://github.com/remix-run/remix/tree/main/packages/assets) - Consumes browser HMR channels for coordinating server and browser HMR updates
302
+ - [`fetch-proxy`](https://github.com/remix-run/remix/tree/main/packages/fetch-proxy) - Creates fetch handlers for forwarding requests to another server
303
+ - [`ui-hmr`](https://github.com/remix-run/remix/tree/main/packages/ui-hmr) - Provides code transforms and runtime for HMR for Remix UI components
304
+
305
+ ## License
306
+
307
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/node-hmr/runtime'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export type * from '@remix-run/node-hmr/types'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/node-hmr'