@alxia/react-router 0.4.0 → 0.6.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 CHANGED
@@ -4,14 +4,31 @@
4
4
  [React Router](https://reactrouter.com) framework app, server rendered,
5
5
  under Bun. One Vite plugin and no server file: the pages are served by
6
6
  alxia in `react-router dev` and from the build. When you want more,
7
- `app/server.ts` adds the app's hooks — logger, compression, sessions,
7
+ `app/server.ts` adds the app's middlewares — logger, compression, sessions,
8
8
  guards — and an `/api` beside the pages, and each loader reads what those
9
- hooks built, typed.
9
+ middlewares built, typed.
10
10
 
11
11
  ## Quick start
12
12
 
13
- Start from the official template, `bunx create-react-router@latest`, and
14
- make four changes.
13
+ ### A new app
14
+
15
+ ```sh
16
+ bun create @alxia my-app --template react-router
17
+ cd my-app
18
+ bun dev
19
+ ```
20
+
21
+ [`@alxia/create`](https://www.npmjs.com/package/@alxia/create) writes React
22
+ Router's official template with everything below already done: this
23
+ package and `@alxia/core` installed, `alxia()` in `vite.config.ts`, the
24
+ `bunfig.toml`, `start` on Bun, Biome, and a `Dockerfile` that builds on
25
+ `oven/bun` and runs `build/` alone on Alpine. Its dependencies are
26
+ moved to the newest releases alxia accepts.
27
+
28
+ ### An existing React Router app
29
+
30
+ Start from the official template, `bunx create-react-router@latest`, or your
31
+ own app, and make four changes.
15
32
 
16
33
  **1. Install** alxia and this package:
17
34
 
@@ -54,21 +71,25 @@ server stops at startup with
54
71
 
55
72
  ```diff
56
73
  - "start": "react-router-serve ./build/server/index.js",
57
- + "start": "bun build/server/index.js",
74
+ + "start": "NODE_ENV=production bun build/server/index.js",
58
75
  ```
59
76
 
60
77
  With the `bunfig.toml` in place, the template's `dev`, `build` and
61
- `typecheck` scripts are unchanged:
78
+ `typecheck` scripts are unchanged. alxia's dev helps follow React Router's
79
+ mode, on under `react-router dev`; `bun create @alxia` writes
80
+ `NODE_ENV=development react-router dev` as `dev`, so that the app's own
81
+ `Bun.env` reads agree:
62
82
 
63
83
  - **`bun run dev`**: every request Vite does not answer itself (pages,
64
84
  data, `/api`, an upgrade to a `ws` route) reaches alxia, with HMR.
65
85
  - **`bun run build`** writes `build/server/index.js`, a server you can run.
66
86
  - **`bun run start`** serves the pages and the client build. It listens on
67
- `PORT` (3000) and `HOST` (`0.0.0.0`), and stops on `SIGTERM`.
87
+ `PORT` (3000) and `HOST` (`0.0.0.0`), and shuts down gracefully on
88
+ `SIGTERM`: the requests in flight finish, then the `onStop` hooks run.
68
89
 
69
90
  After a build, `bunx --bun vite preview` serves that built server through
70
91
  Vite's preview server, as `bun run start` would: the pages, `/api`, the
71
- client's files and the app's hooks. So does React Router's prerendering.
92
+ client's files and the app's middlewares. So does React Router's prerendering.
72
93
 
73
94
  `@alxia/core` and `react-router` 8 are peers, and `vite` 7 or 8 is an
74
95
  optional peer, for `/vite`. The package declares no dependency.
@@ -91,8 +112,8 @@ commented, and the `Register` declaration. It refuses to overwrite a file
91
112
  already there; `--force` overwrites it. Run it from the app's root, once
92
113
  `@alxia/react-router` is installed.
93
114
 
94
- The examples use `@alxia/logger` (`bun add @alxia/logger`); any plugin
95
- works the same way. The plugin picks the file up in dev and in the build:
115
+ The examples use `@alxia/logger` (`bun add @alxia/logger`); any middleware
116
+ works the same way (`use` the observers first). The Vite plugin picks the file up in dev and in the build:
96
117
 
97
118
  ```ts
98
119
  // app/server.ts
@@ -145,9 +166,14 @@ export function loader({ context }: Route.LoaderArgs) {
145
166
  How `alxiaOf(context)` is typed:
146
167
 
147
168
  - with the `Register` declaration above, by that server: reading something
148
- no hook derives is a compile error;
169
+ no middleware derives is a compile error;
149
170
  - without it, `alxiaOf<typeof server>(context)` names the server;
150
- - with neither, `alxiaOf(context)` is `BaseContext`.
171
+ - with neither, by the base `@alxia/core`'s own `Register` names
172
+ (`context: typeof base`), when the app has one;
173
+ - with none of them, `alxiaOf(context)` is `BaseContext`.
174
+
175
+ This package's `Register` wins over core's: the server's app is the base
176
+ and all `configure` adds after it.
151
177
 
152
178
  [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#typing-the-loaders)
153
179
 
@@ -193,7 +219,7 @@ bunx react-router reveal entry.server
193
219
  [readyOption]() {
194
220
  ```
195
221
 
196
- `nonceOf` returns `undefined` when no hook set a nonce, so the entry works
222
+ `nonceOf` returns `undefined` when no middleware set a nonce, so the entry works
197
223
  with or without secure-headers: neither package depends on the other. Any
198
224
  `derive` that returns a string `nonce` works the same.
199
225
  [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#a-csp-nonce)
@@ -220,7 +246,7 @@ export default createServer({
220
246
 
221
247
  In dev, the plugin hands each upgrade Vite does not claim (its HMR, its
222
248
  `server.proxy`) to the app, run by `Bun.serve` as `listen` runs it: the
223
- hooks before the route, a refusal's status, `socket.data`, `publish`. An
249
+ middlewares before the route, a refusal's status, `socket.data`, `publish`. An
224
250
  edit to the server is used from the next connection.
225
251
  [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#websockets)
226
252
 
@@ -248,7 +274,7 @@ client })` itself. [More](https://github.com/softistx/alxia/blob/develop/package
248
274
 
249
275
  ## The client's files
250
276
 
251
- In a build, `build/client` is served before `configure`'s hooks:
277
+ In a build, `build/client` is served before `configure`'s middlewares:
252
278
 
253
279
  | path | `Cache-Control` |
254
280
  | --- | --- |
@@ -258,24 +284,32 @@ In a build, `build/client` is served before `configure`'s hooks:
258
284
  A missing asset gets alxia's JSON 404, not a page. In dev, Vite serves
259
285
  these files.
260
286
 
261
- ## Leaving the pages out of OpenAPI
287
+ ## Leaving the pages out of `matchesSpec`
262
288
 
263
- The examples use `@alxia/openapi` (`bun add @alxia/openapi`):
289
+ An API under `/api`, spec first, is checked against its OpenAPI document
290
+ by [`@alxia/openapi`](https://www.npmjs.com/package/@alxia/openapi)'s
291
+ `matchesSpec` (`bun add -d @alxia/openapi`), which reads `app.routes`. The
292
+ catch-all and the client's files are routes too, and no operation of the
293
+ document:
264
294
 
265
295
  ```ts
266
- // app/server.ts
267
- import { docs } from '@alxia/openapi';
268
- import { createServer, isReactRouterRoute } from '@alxia/react-router';
269
-
270
- export default createServer({
271
- configure: (app) =>
272
- app.use(docs(app, { info: { title: 'Shop', version: '1.0.0' }, exclude: isReactRouterRoute })),
296
+ // app/server.test.ts
297
+ import { test } from 'bun:test';
298
+ import { matchesSpec } from '@alxia/openapi';
299
+ import { isReactRouterRoute } from '@alxia/react-router';
300
+ import type { ServerBuild } from 'react-router';
301
+ import { operations } from './generated/alxia';
302
+ import server from './server';
303
+
304
+ test('the API routes every operation of openapi.yaml, and nothing else', async () => {
305
+ const build: ServerBuild = await import(new URL('../build/server/index.js', import.meta.url).href);
306
+ const app = server.create({ build });
307
+ matchesSpec(app, operations, { exclude: isReactRouterRoute });
273
308
  });
274
309
  ```
275
310
 
276
- The catch-all adds nothing to the app's route table, so the typed client
277
- never shows it. `isReactRouterRoute` names the catch-all and the client
278
- files, so `@alxia/openapi` can leave them out.
311
+ `isReactRouterRoute` is true for each route this package declared, so
312
+ `matchesSpec` checks your own routes alone.
279
313
 
280
314
  ## Built for Bun
281
315
 
@@ -328,7 +362,8 @@ has each option and why.
328
362
 
329
363
  ## Docker
330
364
 
331
- A multi-stage `Dockerfile` on Bun's official image, as `@alxia/create`'s
365
+ A multi-stage `Dockerfile` on Bun's official images, built on Debian's
366
+ and run on Alpine's, as `@alxia/create`'s
332
367
  `react-router` template ships it. For an app from `create-react-router`,
333
368
  it replaces the template's, which runs on Node. The build is
334
369
  self-contained, so the image holds `build/` alone, no `node_modules`:
@@ -341,7 +376,7 @@ RUN bun install --frozen-lockfile
341
376
  COPY . .
342
377
  RUN bun run build
343
378
 
344
- FROM oven/bun:1
379
+ FROM oven/bun:1-alpine
345
380
  WORKDIR /app
346
381
  ENV NODE_ENV=production
347
382
  COPY --from=build /app/build ./build
@@ -389,17 +424,17 @@ has the commented file, and what to copy for a package left external.
389
424
  | `ReactRouterServer<App>` | what it returns: `create(wiring)` makes the app, `start(app)` listens |
390
425
  | `ServerWiring` | what `create` takes: `build`, `mode`, `client` |
391
426
  | `FreshApp` | the app `beforeAll`, or `configure` without it, receives |
392
- | `alxiaOf<App>(context)` | what alxia's hooks built, in a loader, an action or a middleware. Typed by the registered server, by the type argument (a server or an app), or as `BaseContext` |
427
+ | `alxiaOf<App>(context)` | what alxia's middlewares built, in a loader, an action or a middleware. Typed by the registered server, by the type argument (a server or an app), or as `BaseContext` |
393
428
  | `Register` | the interface to augment with `server: typeof server` |
394
- | `RegisteredApp` | the app `alxiaOf` reads with no type argument |
395
- | `RegisteredOf<R>` | the app a `Register`-shaped interface names: its server's, a fresh app, or `InvalidRegister` |
429
+ | `RegisteredApp` | the app `alxiaOf` reads with no type argument: the registered server's, else the one `@alxia/core`'s `Register` names, else a fresh app |
430
+ | `RegisteredOf<R, Core?>` | the app a `Register`-shaped interface names: its server's, `InvalidRegister`, or with no server `Core`, by default core's `RegisteredBase` |
396
431
  | `InvalidRegister` | what a `Register` naming neither a server nor an app reads as: every key of the app's own a compile error |
397
432
  | `AppOf<Server>` | the app a server makes |
398
433
  | `alxiaContext` | the React Router context key `alxiaOf` reads, set on every request |
399
- | `nonceOf(context)` | the request's CSP nonce, for `entry.server.tsx`: the context's `nonce` when a hook set one, such as `secureHeaders({ nonce: true })`, else `undefined` |
434
+ | `nonceOf(context)` | the request's CSP nonce, for `entry.server.tsx`: the context's `nonce` when a middleware set one, such as `secureHeaders({ nonce: true })`, else `undefined` |
400
435
  | `reactRouter(app, options)` | the catch-all and the client's files, for a server of your own. `build`, `mode`, `getLoadContext`, `client` |
401
436
  | `ReactRouterOptions<Ctx>` | its options |
402
- | `isReactRouterRoute(route)` | whether this package declared a route, for OpenAPI's `exclude` |
437
+ | `isReactRouterRoute(route)` | whether this package declared a route, for `matchesSpec`'s `exclude` |
403
438
 
404
439
  From `@alxia/react-router/vite`:
405
440
 
@@ -420,3 +455,4 @@ The `alxia-react-router` bin, run with `bunx`:
420
455
  - [Troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md): each message, and the traps that print none.
421
456
  - [Roadmap](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/roadmap.md): what is coming, and what is not planned.
422
457
  - [Example](https://github.com/softistx/alxia/tree/develop/examples/react-router): the official template, these four changes, then an `app/server.ts` with a session, an `/api`, secure headers with a nonce and a streamed page.
458
+ - [Recipes](https://github.com/softistx/alxia/blob/develop/docs/recipes/README.md): [Deploy with Docker](https://github.com/softistx/alxia/blob/develop/docs/recipes/deploying.md), [Health checks and graceful shutdown](https://github.com/softistx/alxia/blob/develop/docs/recipes/health-and-shutdown.md).
package/dist/cli/index.js CHANGED
@@ -29,7 +29,7 @@ const server = createServer({
29
29
  // limit, a logger that should see every request. Returns the app.
30
30
  // beforeAll: (app) => app,
31
31
 
32
- // The app the pages run behind: its plugins, its hooks, its /api. What it
32
+ // The app the pages run behind: its middlewares, its plugins, its /api. What it
33
33
  // builds is what alxiaOf(context) reads in the loaders. Returns the app.
34
34
  // configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),
35
35
 
@@ -142,7 +142,7 @@ ${USAGE}`);
142
142
  return 1;
143
143
  }
144
144
  output.out(`${NAME}: wrote ${result.written}, the server alxia() runs by default.
145
- Next: uncomment configure in ${result.written} to add the app's hooks and /api; bun run dev picks it up.`);
145
+ Next: uncomment configure in ${result.written} to add the app's middlewares and /api; bun run dev picks it up.`);
146
146
  return 0;
147
147
  } catch (error) {
148
148
  output.err(error instanceof Error ? error.message : String(error));
@@ -156,5 +156,5 @@ process.exitCode = await main(process.argv.slice(2), process.cwd(), {
156
156
  err: (line) => console.error(line)
157
157
  });
158
158
 
159
- //# debugId=7E4161A01E7D668A64756E2164756E21
159
+ //# debugId=8582AE498438642864756E2164756E21
160
160
  //# sourceMappingURL=index.js.map
@@ -3,11 +3,11 @@
3
3
  "sources": ["../src/cli/reveal.ts", "../src/cli/template.ts", "../src/cli/main.ts", "../src/cli/index.ts"],
4
4
  "sourcesContent": [
5
5
  "/**\n * `alxia-react-router reveal`: where the server file goes, read cheaply from\n * the app's own config files, and writing it there.\n */\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport {\n\tbasename,\n\tdirname,\n\tisAbsolute,\n\tjoin,\n\trelative,\n\tresolve,\n} from 'node:path';\nimport { NAME } from '../vite/entry';\nimport { SERVER_FILE } from './template';\n\nconst EXTENSIONS = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'];\n\n/** `vite.config.ts`, or the first of its other extensions found in `root`. */\nfunction configFile(root: string, base: string): string | undefined {\n\treturn EXTENSIONS.map((extension) => join(root, `${base}${extension}`)).find(\n\t\t(file) => existsSync(file),\n\t);\n}\n\n/** A config file's source, without its comments: Bun's transpiler drops them. */\nasync function sourceOf(file: string): Promise<string> {\n\tconst loader = /\\.[cm]?ts$/.test(file) ? 'ts' : 'js';\n\treturn new Bun.Transpiler({ loader }).transformSync(\n\t\tawait readFile(file, 'utf8'),\n\t);\n}\n\n/**\n * The string literal given to `key` in `source`; `undefined` when `key` is\n * not there. A `key` given anything else is refused with `computed`.\n */\nfunction literal(\n\tsource: string,\n\t{ key, value, computed }: { key: RegExp; value: RegExp; computed: string },\n): string | undefined {\n\tconst found = source.match(value)?.[2];\n\tif (found !== undefined) return found;\n\tif (key.test(source)) throw new Error(computed);\n\treturn undefined;\n}\n\n/**\n * The file the plugin would load as the server: `alxia({ entry })`'s, or\n * `server.ts` in React Router's app directory, `app/` unless its config\n * names another. Each is read as a string literal; one computed is refused,\n * since reveal would write a file the plugin does not load.\n */\nexport async function revealTarget(root: string): Promise<string> {\n\tconst vite = configFile(root, 'vite.config');\n\tif (vite === undefined) {\n\t\tthrow new Error(\n\t\t\t`${NAME}: no vite.config.ts in ${root}. Run reveal from the app's root, beside vite.config.ts.`,\n\t\t);\n\t}\n\tconst entry = literal(await sourceOf(vite), {\n\t\tkey: /\\balxia\\(\\s*\\{[^}]*?\\bentry\\s*:/s,\n\t\tvalue: /\\balxia\\(\\s*\\{[^}]*?\\bentry\\s*:\\s*(['\"`])([^'\"`$]+)\\1/s,\n\t\tcomputed: `${NAME}: ${basename(vite)} gives alxia() an entry reveal cannot read. Write it as a string literal, alxia({ entry: 'app/server.ts' }), and run reveal again.`,\n\t});\n\tif (entry !== undefined) {\n\t\treturn isAbsolute(entry) ? entry : resolve(root, entry);\n\t}\n\tconst rr = configFile(root, 'react-router.config');\n\tconst app =\n\t\t(rr === undefined\n\t\t\t? undefined\n\t\t\t: literal(await sourceOf(rr), {\n\t\t\t\t\tkey: /\\bappDirectory\\s*:/,\n\t\t\t\t\tvalue: /\\bappDirectory\\s*:\\s*(['\"`])([^'\"`$]+)\\1/,\n\t\t\t\t\tcomputed: `${NAME}: ${basename(rr)} computes appDirectory, which reveal cannot read. Write it as a string literal, appDirectory: 'app', and run reveal again.`,\n\t\t\t\t})) ?? 'app';\n\treturn resolve(root, app, 'server.ts');\n}\n\nexport type Revealed =\n\t| { readonly written: string }\n\t| { readonly exists: string };\n\n/**\n * Writes the server file into the app at `root`, unless one is already\n * there and `force` is not set. The path comes back relative to `root`.\n */\nexport async function reveal(root: string, force: boolean): Promise<Revealed> {\n\tconst file = await revealTarget(root);\n\tconst label = relative(root, file);\n\tif (existsSync(file) && !force) return { exists: label };\n\tawait mkdir(dirname(file), { recursive: true });\n\tawait writeFile(file, SERVER_FILE);\n\treturn { written: label };\n}\n",
6
- "/**\n * The server file `alxia-react-router reveal` writes: `createServer()` as\n * the default server runs it, its options commented, and the `Register`\n * declaration that types the loaders.\n */\n\n/** The file, as written. Every commented option compiles once uncommented. */\nexport const SERVER_FILE = `/**\n * The alxia server of this React Router app: what alxia() from\n * @alxia/react-router/vite runs when there is no server file, written out by\n * \\`alxia-react-router reveal\\`. Uncomment an option to customise it.\n */\nimport { createServer } from '@alxia/react-router';\n// import { userAgentContext } from './context'; // for getLoadContext, below\n\nconst server = createServer({\n\t// Runs first, on a new app, before the client's files too: a guard, a rate\n\t// limit, a logger that should see every request. Returns the app.\n\t// beforeAll: (app) => app,\n\n\t// The app the pages run behind: its plugins, its hooks, its /api. What it\n\t// builds is what alxiaOf(context) reads in the loaders. Returns the app.\n\t// configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n\n\t// Sets the app's own React Router context keys, ctx typed by configure's\n\t// app. A key made with createContext() from react-router, in app/context.ts.\n\t// getLoadContext: (ctx, context) => {\n\t// \tcontext.set(userAgentContext, ctx.request.headers.get('user-agent'));\n\t// },\n});\n\nexport default server;\n\n// What alxiaOf(context) reads in the loaders, with no type argument.\ndeclare module '@alxia/react-router' {\n\tinterface Register {\n\t\tserver: typeof server;\n\t}\n}\n`;\n",
7
- "/**\n * The `alxia-react-router` command: its arguments, what it prints, and its\n * exit code. `index.ts` runs it on the process's own.\n */\nimport { NAME } from '../vite/entry';\nimport { reveal } from './reveal';\n\nexport const USAGE = `Usage: ${NAME} <command> [options]\n\nCommands:\n reveal [--force] Write the server alxia() runs by default to app/server.ts,\n or to alxia({ entry })'s file, to customise it. --force\n overwrites a file already there.\n\nOptions:\n --help Show this help.`;\n\n/** Where the command prints: the process's streams, or a spec's. */\nexport interface Output {\n\treadonly out: (line: string) => void;\n\treadonly err: (line: string) => void;\n}\n\n/** Runs the command in `root`; resolves to the process's exit code. */\nexport async function main(\n\targs: readonly string[],\n\troot: string,\n\toutput: Output,\n): Promise<number> {\n\tconst [command, ...options] = args;\n\tif (command === undefined) {\n\t\toutput.err(USAGE);\n\t\treturn 1;\n\t}\n\tif (command === '--help' || command === '-h') {\n\t\toutput.out(USAGE);\n\t\treturn 0;\n\t}\n\tif (command !== 'reveal') {\n\t\toutput.err(`${NAME}: unknown command ${command}.\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\tif (options.includes('--help') || options.includes('-h')) {\n\t\toutput.out(USAGE);\n\t\treturn 0;\n\t}\n\tconst unknown = options.find((option) => option !== '--force');\n\tif (unknown !== undefined) {\n\t\toutput.err(`${NAME}: unknown option ${unknown} for reveal.\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\ttry {\n\t\tconst result = await reveal(root, options.includes('--force'));\n\t\tif ('exists' in result) {\n\t\t\toutput.err(\n\t\t\t\t`${NAME}: ${result.exists} already exists, and reveal leaves it as it is. Run ${NAME} reveal --force to overwrite it.`,\n\t\t\t);\n\t\t\treturn 1;\n\t\t}\n\t\toutput.out(\n\t\t\t`${NAME}: wrote ${result.written}, the server alxia() runs by default.\\nNext: uncomment configure in ${result.written} to add the app's hooks and /api; bun run dev picks it up.`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\toutput.err(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t}\n}\n",
6
+ "/**\n * The server file `alxia-react-router reveal` writes: `createServer()` as\n * the default server runs it, its options commented, and the `Register`\n * declaration that types the loaders.\n */\n\n/** The file, as written. Every commented option compiles once uncommented. */\nexport const SERVER_FILE = `/**\n * The alxia server of this React Router app: what alxia() from\n * @alxia/react-router/vite runs when there is no server file, written out by\n * \\`alxia-react-router reveal\\`. Uncomment an option to customise it.\n */\nimport { createServer } from '@alxia/react-router';\n// import { userAgentContext } from './context'; // for getLoadContext, below\n\nconst server = createServer({\n\t// Runs first, on a new app, before the client's files too: a guard, a rate\n\t// limit, a logger that should see every request. Returns the app.\n\t// beforeAll: (app) => app,\n\n\t// The app the pages run behind: its middlewares, its plugins, its /api. What it\n\t// builds is what alxiaOf(context) reads in the loaders. Returns the app.\n\t// configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n\n\t// Sets the app's own React Router context keys, ctx typed by configure's\n\t// app. A key made with createContext() from react-router, in app/context.ts.\n\t// getLoadContext: (ctx, context) => {\n\t// \tcontext.set(userAgentContext, ctx.request.headers.get('user-agent'));\n\t// },\n});\n\nexport default server;\n\n// What alxiaOf(context) reads in the loaders, with no type argument.\ndeclare module '@alxia/react-router' {\n\tinterface Register {\n\t\tserver: typeof server;\n\t}\n}\n`;\n",
7
+ "/**\n * The `alxia-react-router` command: its arguments, what it prints, and its\n * exit code. `index.ts` runs it on the process's own.\n */\nimport { NAME } from '../vite/entry';\nimport { reveal } from './reveal';\n\nexport const USAGE = `Usage: ${NAME} <command> [options]\n\nCommands:\n reveal [--force] Write the server alxia() runs by default to app/server.ts,\n or to alxia({ entry })'s file, to customise it. --force\n overwrites a file already there.\n\nOptions:\n --help Show this help.`;\n\n/** Where the command prints: the process's streams, or a spec's. */\nexport interface Output {\n\treadonly out: (line: string) => void;\n\treadonly err: (line: string) => void;\n}\n\n/** Runs the command in `root`; resolves to the process's exit code. */\nexport async function main(\n\targs: readonly string[],\n\troot: string,\n\toutput: Output,\n): Promise<number> {\n\tconst [command, ...options] = args;\n\tif (command === undefined) {\n\t\toutput.err(USAGE);\n\t\treturn 1;\n\t}\n\tif (command === '--help' || command === '-h') {\n\t\toutput.out(USAGE);\n\t\treturn 0;\n\t}\n\tif (command !== 'reveal') {\n\t\toutput.err(`${NAME}: unknown command ${command}.\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\tif (options.includes('--help') || options.includes('-h')) {\n\t\toutput.out(USAGE);\n\t\treturn 0;\n\t}\n\tconst unknown = options.find((option) => option !== '--force');\n\tif (unknown !== undefined) {\n\t\toutput.err(`${NAME}: unknown option ${unknown} for reveal.\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\ttry {\n\t\tconst result = await reveal(root, options.includes('--force'));\n\t\tif ('exists' in result) {\n\t\t\toutput.err(\n\t\t\t\t`${NAME}: ${result.exists} already exists, and reveal leaves it as it is. Run ${NAME} reveal --force to overwrite it.`,\n\t\t\t);\n\t\t\treturn 1;\n\t\t}\n\t\toutput.out(\n\t\t\t`${NAME}: wrote ${result.written}, the server alxia() runs by default.\\nNext: uncomment configure in ${result.written} to add the app's middlewares and /api; bun run dev picks it up.`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\toutput.err(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t}\n}\n",
8
8
  "#!/usr/bin/env bun\n/**\n * `alxia-react-router`, the package's bin: `bunx alxia-react-router reveal`\n * writes the default server into the app, to customise it.\n */\nimport { main } from './main';\n\nprocess.exitCode = await main(process.argv.slice(2), process.cwd(), {\n\tout: (line) => console.log(line),\n\terr: (line) => console.error(line),\n});\n"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "7E4161A01E7D668A64756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "8582AE498438642864756E2164756E21",
12
12
  "names": []
13
13
  }
@@ -4,5 +4,5 @@
4
4
  * declaration that types the loaders.
5
5
  */
6
6
  /** The file, as written. Every commented option compiles once uncommented. */
7
- export declare const SERVER_FILE = "/**\n * The alxia server of this React Router app: what alxia() from\n * @alxia/react-router/vite runs when there is no server file, written out by\n * `alxia-react-router reveal`. Uncomment an option to customise it.\n */\nimport { createServer } from '@alxia/react-router';\n// import { userAgentContext } from './context'; // for getLoadContext, below\n\nconst server = createServer({\n\t// Runs first, on a new app, before the client's files too: a guard, a rate\n\t// limit, a logger that should see every request. Returns the app.\n\t// beforeAll: (app) => app,\n\n\t// The app the pages run behind: its plugins, its hooks, its /api. What it\n\t// builds is what alxiaOf(context) reads in the loaders. Returns the app.\n\t// configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n\n\t// Sets the app's own React Router context keys, ctx typed by configure's\n\t// app. A key made with createContext() from react-router, in app/context.ts.\n\t// getLoadContext: (ctx, context) => {\n\t// \tcontext.set(userAgentContext, ctx.request.headers.get('user-agent'));\n\t// },\n});\n\nexport default server;\n\n// What alxiaOf(context) reads in the loaders, with no type argument.\ndeclare module '@alxia/react-router' {\n\tinterface Register {\n\t\tserver: typeof server;\n\t}\n}\n";
7
+ export declare const SERVER_FILE = "/**\n * The alxia server of this React Router app: what alxia() from\n * @alxia/react-router/vite runs when there is no server file, written out by\n * `alxia-react-router reveal`. Uncomment an option to customise it.\n */\nimport { createServer } from '@alxia/react-router';\n// import { userAgentContext } from './context'; // for getLoadContext, below\n\nconst server = createServer({\n\t// Runs first, on a new app, before the client's files too: a guard, a rate\n\t// limit, a logger that should see every request. Returns the app.\n\t// beforeAll: (app) => app,\n\n\t// The app the pages run behind: its middlewares, its plugins, its /api. What it\n\t// builds is what alxiaOf(context) reads in the loaders. Returns the app.\n\t// configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n\n\t// Sets the app's own React Router context keys, ctx typed by configure's\n\t// app. A key made with createContext() from react-router, in app/context.ts.\n\t// getLoadContext: (ctx, context) => {\n\t// \tcontext.set(userAgentContext, ctx.request.headers.get('user-agent'));\n\t// },\n});\n\nexport default server;\n\n// What alxiaOf(context) reads in the loaders, with no type argument.\ndeclare module '@alxia/react-router' {\n\tinterface Register {\n\t\tserver: typeof server;\n\t}\n}\n";
8
8
  //# sourceMappingURL=template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/cli/template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,WAAW,iyCAgCvB,CAAC"}
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/cli/template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,WAAW,uyCAgCvB,CAAC"}
package/dist/context.d.ts CHANGED
@@ -2,12 +2,12 @@
2
2
  * The key under which `reactRouter()` hands every loader, action and
3
3
  * middleware the request's alxia context, and the typed way to read it.
4
4
  */
5
- import type { Alxia, AnyAlxia, ContextOf, Empty } from '@alxia/core';
5
+ import type { Alxia, AnyAlxia, ContextOf, RegisteredBase } from '@alxia/core';
6
6
  import { type RouterContextProvider } from 'react-router';
7
- import type { FreshApp, ReactRouterServer } from './server';
7
+ import type { ReactRouterServer } from './server';
8
8
  /**
9
9
  * The key `reactRouter()` sets on React Router's context provider, on every
10
- * request, to what alxia's hooks built for it. It lives in this package, so
10
+ * request, to what alxia's middlewares built for it. It lives in this package, so
11
11
  * it is one object whichever way the server was built or loaded: a key made
12
12
  * in the app's own `app/` folder is copied into React Router's build, and
13
13
  * the server that imports it separately sets a different one.
@@ -30,7 +30,10 @@ export declare const alxiaContext: import("react-router").RouterContext<unknown>
30
30
  * }
31
31
  * ```
32
32
  *
33
- * Unregistered, `alxiaOf(context)` reads `BaseContext`.
33
+ * Unregistered here, `alxiaOf(context)` reads the app `@alxia/core`'s own
34
+ * `Register` names as its `context`, and `BaseContext` when neither is
35
+ * declared. When both are, this one wins: the server's app is the whole
36
+ * app the pages run behind, the base's context and all `configure` adds.
34
37
  */
35
38
  export interface Register {
36
39
  }
@@ -43,18 +46,23 @@ export type AppOf<Server> = Server extends ReactRouterServer<infer App> ? App :
43
46
  */
44
47
  export type InvalidRegister = Alxia<{
45
48
  readonly 'Register.server must be typeof server, the default export of createServer()': never;
46
- }, Empty, '', never>;
47
- /** The app `alxiaOf` reads for a `Register` interface: its server's, a fresh one, or `InvalidRegister`. */
48
- export type RegisteredOf<R> = R extends {
49
+ }, ''>;
50
+ /**
51
+ * The app `alxiaOf` reads for a `Register` interface: its server's, or
52
+ * `InvalidRegister`; with no server, `Core`, the app `@alxia/core`'s
53
+ * `Register` names (a fresh one when it names none).
54
+ */
55
+ export type RegisteredOf<R, Core extends AnyAlxia = RegisteredBase> = R extends {
49
56
  readonly server: infer Server;
50
- } ? Server extends AnyAlxia | ReactRouterServer<AnyAlxia> ? AppOf<Server> : InvalidRegister : FreshApp;
51
- /** What `alxiaOf` reads with no type argument: the registered server's app, or a fresh one. */
57
+ } ? Server extends AnyAlxia | ReactRouterServer<AnyAlxia> ? AppOf<Server> : InvalidRegister : Core;
58
+ /** What `alxiaOf` reads with no type argument: the registered server's app, else core's registered app, else a fresh one. */
52
59
  export type RegisteredApp = RegisteredOf<Register>;
53
60
  /**
54
- * What alxia's hooks built for this request, read in a loader, an action or
61
+ * What alxia's middlewares built for this request, read in a loader, an action or
55
62
  * a middleware, typed by the app: the server `Register` names, or the one
56
63
  * given as the type argument — `typeof server`, or an app *before* the
57
- * catch-all. With neither, `BaseContext`.
64
+ * catch-all. With neither, the app `@alxia/core`'s `Register` names, and
65
+ * `BaseContext` when nothing is registered.
58
66
  *
59
67
  * ```ts
60
68
  * export async function loader({ context }: Route.LoaderArgs) {
@@ -66,5 +74,8 @@ export type RegisteredApp = RegisteredOf<Register>;
66
74
  * `react-router dev` without the plugin, or a test that calls a loader
67
75
  * directly.
68
76
  */
69
- export declare function alxiaOf<App extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp>(context: Readonly<RouterContextProvider>): ContextOf<AppOf<App>>;
77
+ export declare function alxiaOf<App extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp>(context: Readonly<RouterContextProvider>): ContextOf<AppOf<App>> & {
78
+ /** The catch-all's route, which every request reaching React Router matched. */
79
+ readonly route: string;
80
+ };
70
81
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAiB,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAK5D;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,+CAAkC,CAAC;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,QAAQ;CAAG;AAE5B,iDAAiD;AACjD,MAAM,MAAM,KAAK,CAAC,MAAM,IACvB,MAAM,SAAS,iBAAiB,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,GAAG,MAAM,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,CAClC;IACC,QAAQ,CAAC,6EAA6E,EAAE,KAAK,CAAC;CAC9F,EACD,KAAK,EACL,EAAE,EACF,KAAK,CACL,CAAC;AAEF,2GAA2G;AAC3G,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAA;CAAE,GACtE,MAAM,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GACpD,KAAK,CAAC,MAAM,CAAC,GACb,eAAe,GAChB,QAAQ,CAAC;AAEZ,+FAA+F;AAC/F,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;AAEnD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CACtB,GAAG,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GAAG,aAAa,EACjE,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAQjE"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAiB,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKlD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,+CAAkC,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,QAAQ;CAAG;AAE5B,iDAAiD;AACjD,MAAM,MAAM,KAAK,CAAC,MAAM,IACvB,MAAM,SAAS,iBAAiB,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,GAAG,MAAM,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,CAClC;IACC,QAAQ,CAAC,6EAA6E,EAAE,KAAK,CAAC;CAC9F,EACD,EAAE,CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,YAAY,CACvB,CAAC,EACD,IAAI,SAAS,QAAQ,GAAG,cAAc,IACnC,CAAC,SAAS;IACb,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC9B,GACE,MAAM,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GACpD,KAAK,CAAC,MAAM,CAAC,GACb,eAAe,GAChB,IAAI,CAAC;AAER,6HAA6H;AAC7H,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CACtB,GAAG,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GAAG,aAAa,EAElE,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GACtC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB,CAQA"}
package/dist/index.js CHANGED
@@ -124,7 +124,7 @@ function createServer(options = {}) {
124
124
  create(wiring) {
125
125
  const mode = options.mode ?? wiring.mode ?? "production";
126
126
  const client = options.client === false ? undefined : options.client ?? wiring.client;
127
- const fresh = alxia();
127
+ const fresh = alxia({ dev: mode === "development" });
128
128
  const before = options.beforeAll?.(fresh) ?? fresh;
129
129
  if (client !== undefined && mode === "production") {
130
130
  declareClient(before, client);
@@ -141,26 +141,25 @@ function createServer(options = {}) {
141
141
  return app;
142
142
  },
143
143
  start(app) {
144
- const server = app.listen({
144
+ const own = options.listen?.onListen;
145
+ return app.listen({
145
146
  port: Number(process.env["PORT"] || 3000),
146
147
  hostname: process.env["HOST"] || "0.0.0.0",
147
- ...options.listen
148
+ ...options.listen,
149
+ onListen: (info) => {
150
+ if (own !== undefined)
151
+ own(info);
152
+ else if (options.onListen !== undefined) {
153
+ options.onListen(info.server);
154
+ } else
155
+ announce(info);
156
+ }
148
157
  });
149
- for (const signal of ["SIGINT", "SIGTERM"]) {
150
- process.once(signal, () => {
151
- app.stop().then(() => process.exit(0), (error) => {
152
- console.error(error);
153
- process.exit(1);
154
- });
155
- });
156
- }
157
- (options.onListen ?? announce)(server);
158
- return server;
159
158
  }
160
159
  };
161
160
  }
162
- function announce(server) {
163
- console.log(`alxia listening on ${server.url}`);
161
+ function announce({ dev, table, url }) {
162
+ console.log(dev ? table : `alxia listening on ${url}`);
164
163
  }
165
164
  export {
166
165
  alxiaContext,
@@ -171,5 +170,5 @@ export {
171
170
  reactRouter
172
171
  };
173
172
 
174
- //# debugId=63BB3B2311BA25F964756E2164756E21
173
+ //# debugId=BA278F18656F972964756E2164756E21
175
174
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -2,13 +2,13 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/context.ts", "../src/nonce.ts", "../src/react-router.ts", "../src/assets.ts", "../src/server.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * The key under which `reactRouter()` hands every loader, action and\n * middleware the request's alxia context, and the typed way to read it.\n */\nimport type { Alxia, AnyAlxia, ContextOf, Empty } from '@alxia/core';\nimport { createContext, type RouterContextProvider } from 'react-router';\nimport type { FreshApp, ReactRouterServer } from './server';\n\n/** The default of the key: no catch-all set it. */\nconst MISSING: unique symbol = Symbol('alxia context missing');\n\n/**\n * The key `reactRouter()` sets on React Router's context provider, on every\n * request, to what alxia's hooks built for it. It lives in this package, so\n * it is one object whichever way the server was built or loaded: a key made\n * in the app's own `app/` folder is copied into React Router's build, and\n * the server that imports it separately sets a different one.\n */\nexport const alxiaContext = createContext<unknown>(MISSING);\n\n/**\n * Names the server whose context `alxiaOf(context)` reads when given no\n * type argument. One React Router build has one server, so it may be\n * declared once, beside it:\n *\n * ```ts\n * // app/server.ts\n * const server = createServer({ configure: (app) => app.use(session) });\n * export default server;\n *\n * declare module '@alxia/react-router' {\n * interface Register {\n * server: typeof server;\n * }\n * }\n * ```\n *\n * Unregistered, `alxiaOf(context)` reads `BaseContext`.\n */\n// biome-ignore lint/suspicious/noEmptyInterface: an app augments it\nexport interface Register {}\n\n/** The app a server makes, or the app itself. */\nexport type AppOf<Server> =\n\tServer extends ReactRouterServer<infer App> ? App : Server;\n\n/**\n * What `Register` names when it is neither a server nor an app — the\n * module, say, rather than its default export: an app whose context has\n * nothing but this key, so that reading anything of it is a compile error.\n */\nexport type InvalidRegister = Alxia<\n\t{\n\t\treadonly 'Register.server must be typeof server, the default export of createServer()': never;\n\t},\n\tEmpty,\n\t'',\n\tnever\n>;\n\n/** The app `alxiaOf` reads for a `Register` interface: its server's, a fresh one, or `InvalidRegister`. */\nexport type RegisteredOf<R> = R extends { readonly server: infer Server }\n\t? Server extends AnyAlxia | ReactRouterServer<AnyAlxia>\n\t\t? AppOf<Server>\n\t\t: InvalidRegister\n\t: FreshApp;\n\n/** What `alxiaOf` reads with no type argument: the registered server's app, or a fresh one. */\nexport type RegisteredApp = RegisteredOf<Register>;\n\n/**\n * What alxia's hooks built for this request, read in a loader, an action or\n * a middleware, typed by the app: the server `Register` names, or the one\n * given as the type argument — `typeof server`, or an app *before* the\n * catch-all. With neither, `BaseContext`.\n *\n * ```ts\n * export async function loader({ context }: Route.LoaderArgs) {\n * const { user, log } = alxiaOf(context);\n * }\n * ```\n *\n * Throws when the request did not come through `reactRouter()`: under\n * `react-router dev` without the plugin, or a test that calls a loader\n * directly.\n */\nexport function alxiaOf<\n\tApp extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp,\n>(context: Readonly<RouterContextProvider>): ContextOf<AppOf<App>> {\n\tconst value = context.get(alxiaContext);\n\tif (value === MISSING) {\n\t\tthrow new Error(\n\t\t\t\"alxiaOf(): this request has no alxia context. Serve the React Router app through alxia: add alxia() from @alxia/react-router/vite to vite.config.ts's plugins, or, with a server of your own, serve the build through reactRouter() from @alxia/react-router.\",\n\t\t);\n\t}\n\treturn value as ContextOf<AppOf<App>>;\n}\n",
6
- "/** `nonceOf`: the request's CSP nonce, read from alxia's context if a hook set one. */\nimport type { RouterContextProvider } from 'react-router';\nimport { alxiaContext } from './context';\n\n/**\n * This request's CSP nonce, for `entry.server.tsx`: the `nonce` alxia's\n * hooks put on the context — `secureHeaders({ nonce: true })` from\n * `@alxia/secure-headers`, or a `derive` of your own — or `undefined` when\n * none did, or when the request did not come through alxia. It reads the\n * key if present: neither package depends on the other.\n *\n * ```tsx\n * const nonce = nonceOf(loadContext);\n * <ServerRouter context={routerContext} url={request.url} nonce={nonce} />\n * ```\n */\nexport function nonceOf(\n\tcontext: Readonly<RouterContextProvider>,\n): string | undefined {\n\tconst value = context.get(alxiaContext);\n\tif (typeof value !== 'object' || value === null || !('nonce' in value)) {\n\t\treturn undefined;\n\t}\n\treturn typeof value.nonce === 'string' ? value.nonce : undefined;\n}\n",
7
- "import { fileURLToPath } from 'node:url';\nimport type {\n\tAlxia,\n\tAnyAlxia,\n\tAnyReply,\n\tBaseContext,\n\tMaybePromise,\n\tRouteDefinition,\n\tStatusCode,\n} from '@alxia/core';\nimport {\n\tcreateRequestHandler,\n\ttype RequestHandler,\n\tRouterContextProvider,\n\ttype ServerBuild,\n} from 'react-router';\nimport { serveClient } from './assets';\nimport { alxiaContext } from './context';\n\nexport interface ReactRouterOptions<Ctx> {\n\t/**\n\t * React Router's server build: the module itself, or a function that\n\t * returns it — `() => import('virtual:react-router/server-build')` in a\n\t * server entry Vite builds, `() => import('./build/server/index.js')`\n\t * beside a build. A function is called on every request in\n\t * `development`, so an edit is picked up, and once in `production`.\n\t */\n\treadonly build: ServerBuild | (() => MaybePromise<ServerBuild>);\n\t/** React Router's server mode: `production` by default. */\n\treadonly mode?: 'development' | 'production';\n\t/**\n\t * Sets the app's own keys on React Router's context provider, from the\n\t * context alxia's hooks built. `ctx` is typed by the app at the point of\n\t * `use`: reading what no hook before it derives is a compile error.\n\t * `alxiaContext` is always set, whether or not this is given.\n\t */\n\treadonly getLoadContext?: (\n\t\tctx: BaseContext & Ctx,\n\t\tcontext: RouterContextProvider,\n\t) => MaybePromise<void>;\n\t/**\n\t * The client build's folder, `build/client`: a path or a `file:` URL.\n\t * Its `assets/` is served immutable, and every other top-level file or\n\t * folder — the copies of `public/` — with an hour's cache, before the\n\t * catch-all. Ignored in `development`, where Vite serves them.\n\t */\n\treadonly client?: string | URL;\n}\n\n/** The methods the catch-all takes. A `HEAD` reaches its `GET`. */\nconst METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;\n\n/** The handlers `reactRouter()` declared: the catch-all's and the client build's. */\nconst declared = new WeakSet<RouteDefinition['handler']>();\n\n/**\n * Whether `reactRouter()` declared this route: the catch-all, or one of the\n * client build's files. For `@alxia/openapi`'s `exclude`, so the document\n * lists the app's API and not its pages.\n *\n * ```ts\n * app.use(docs(app, { info, exclude: isReactRouterRoute }));\n * ```\n */\nexport function isReactRouterRoute(route: RouteDefinition): boolean {\n\treturn declared.has(route.handler);\n}\n\n/**\n * A React Router framework app, server rendered on `app`: `GET`, `POST`,\n * `PUT`, `PATCH` and `DELETE` at `/*`, behind every hook declared on `app`\n * before it. Each loader, action and middleware reads what those hooks\n * built through `alxiaOf<App>(context)`. The app's own routes — an `/api`\n * — answer their paths, declared before it or after; the client build's\n * files are served when `client` is given. The catch-all adds nothing to\n * the app's route table: pages are not something the typed client calls.\n *\n * ```ts\n * const app = base.use((app) =>\n * reactRouter(app, { build: () => import('./build/server/index.js'), client: 'build/client' }),\n * );\n * ```\n */\nexport function reactRouter<\n\tCtx extends object,\n\tRoutes extends object,\n\tPrefix extends string,\n\tShortcuts extends AnyReply,\n>(\n\tapp: Alxia<Ctx, Routes, Prefix, Shortcuts>,\n\toptions: ReactRouterOptions<Ctx>,\n): Alxia<Ctx, Routes, Prefix, Shortcuts> {\n\tconst mode = options.mode ?? 'production';\n\tconst handle = requestHandler(options.build, mode);\n\tconst getLoadContext = options.getLoadContext;\n\tconst handler = async (ctx: BaseContext & Ctx) => {\n\t\tconst context = new RouterContextProvider();\n\t\tcontext.set(alxiaContext, ctx);\n\t\tawait getLoadContext?.(ctx, context);\n\t\t// React Router answers a HEAD with no headers at all: hand it the GET,\n\t\t// and the core drops the body.\n\t\tconst request =\n\t\t\tctx.request.method === 'HEAD'\n\t\t\t\t? new Request(ctx.request, { method: 'GET' })\n\t\t\t\t: ctx.request;\n\t\tconst response = await handle(request, context);\n\t\treturn ctx.reply(\n\t\t\tresponse.status as StatusCode,\n\t\t\tresponse.body ?? undefined,\n\t\t\t{ headers: response.headers },\n\t\t);\n\t};\n\n\tconst routes = asRoutes(app);\n\tdeclaring(app, () => {\n\t\tif (options.client !== undefined && mode === 'production') {\n\t\t\tserveClient(routes, pathOf(options.client));\n\t\t}\n\t\tfor (const method of METHODS) routes[method]('/*', handler);\n\t});\n\treturn app;\n}\n\n/**\n * Declares the client build's files on `app`, marked for\n * `isReactRouterRoute`: what `reactRouter()` does with `client`, apart, so\n * that `createServer()` serves them before the hooks of `configure`.\n */\nexport function declareClient(app: AnyAlxia, client: string | URL): void {\n\tdeclaring(app, () => serveClient(asRoutes(app), pathOf(client)));\n}\n\n/** Runs `declare`, and marks every route it added to `app` as ours. */\nfunction declaring(app: AnyAlxia, declare: () => void): void {\n\tconst before = new Set(app.routes);\n\tdeclare();\n\tfor (const route of app.routes) {\n\t\tif (!before.has(route)) declared.add(route.handler);\n\t}\n}\n\n/** The app's route methods, untyped: the catch-all's handler is not one a route type admits. */\nfunction asRoutes(app: AnyAlxia) {\n\treturn app as unknown as {\n\t\tstatic(path: string, source: string, options: object): unknown;\n\t\tfile(path: string, file: string, options: object): unknown;\n\t} & Record<\n\t\t(typeof METHODS)[number],\n\t\t(path: string, handler: unknown) => unknown\n\t>;\n}\n\n/** A handler that resolves a function build once in production, on every request in development. */\nfunction requestHandler(\n\tbuild: ReactRouterOptions<never>['build'],\n\tmode: 'development' | 'production',\n): RequestHandler {\n\tif (typeof build !== 'function' || mode === 'development') {\n\t\treturn createRequestHandler(build, mode);\n\t}\n\tlet resolved: Promise<RequestHandler> | undefined;\n\treturn async (request, context) => {\n\t\tresolved ??= Promise.resolve(build()).then(\n\t\t\t(server) => createRequestHandler(server, mode),\n\t\t\t(error: unknown) => {\n\t\t\t\tresolved = undefined;\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\treturn (await resolved)(request, context);\n\t};\n}\n\nfunction pathOf(client: string | URL): string {\n\treturn client instanceof URL ? fileURLToPath(client) : client;\n}\n",
5
+ "/**\n * The key under which `reactRouter()` hands every loader, action and\n * middleware the request's alxia context, and the typed way to read it.\n */\nimport type { Alxia, AnyAlxia, ContextOf, RegisteredBase } from '@alxia/core';\nimport { createContext, type RouterContextProvider } from 'react-router';\nimport type { ReactRouterServer } from './server';\n\n/** The default of the key: no catch-all set it. */\nconst MISSING: unique symbol = Symbol('alxia context missing');\n\n/**\n * The key `reactRouter()` sets on React Router's context provider, on every\n * request, to what alxia's middlewares built for it. It lives in this package, so\n * it is one object whichever way the server was built or loaded: a key made\n * in the app's own `app/` folder is copied into React Router's build, and\n * the server that imports it separately sets a different one.\n */\nexport const alxiaContext = createContext<unknown>(MISSING);\n\n/**\n * Names the server whose context `alxiaOf(context)` reads when given no\n * type argument. One React Router build has one server, so it may be\n * declared once, beside it:\n *\n * ```ts\n * // app/server.ts\n * const server = createServer({ configure: (app) => app.use(session) });\n * export default server;\n *\n * declare module '@alxia/react-router' {\n * interface Register {\n * server: typeof server;\n * }\n * }\n * ```\n *\n * Unregistered here, `alxiaOf(context)` reads the app `@alxia/core`'s own\n * `Register` names as its `context`, and `BaseContext` when neither is\n * declared. When both are, this one wins: the server's app is the whole\n * app the pages run behind, the base's context and all `configure` adds.\n */\n// biome-ignore lint/suspicious/noEmptyInterface: an app augments it\nexport interface Register {}\n\n/** The app a server makes, or the app itself. */\nexport type AppOf<Server> =\n\tServer extends ReactRouterServer<infer App> ? App : Server;\n\n/**\n * What `Register` names when it is neither a server nor an app — the\n * module, say, rather than its default export: an app whose context has\n * nothing but this key, so that reading anything of it is a compile error.\n */\nexport type InvalidRegister = Alxia<\n\t{\n\t\treadonly 'Register.server must be typeof server, the default export of createServer()': never;\n\t},\n\t''\n>;\n\n/**\n * The app `alxiaOf` reads for a `Register` interface: its server's, or\n * `InvalidRegister`; with no server, `Core`, the app `@alxia/core`'s\n * `Register` names (a fresh one when it names none).\n */\nexport type RegisteredOf<\n\tR,\n\tCore extends AnyAlxia = RegisteredBase,\n> = R extends {\n\treadonly server: infer Server;\n}\n\t? Server extends AnyAlxia | ReactRouterServer<AnyAlxia>\n\t\t? AppOf<Server>\n\t\t: InvalidRegister\n\t: Core;\n\n/** What `alxiaOf` reads with no type argument: the registered server's app, else core's registered app, else a fresh one. */\nexport type RegisteredApp = RegisteredOf<Register>;\n\n/**\n * What alxia's middlewares built for this request, read in a loader, an action or\n * a middleware, typed by the app: the server `Register` names, or the one\n * given as the type argument — `typeof server`, or an app *before* the\n * catch-all. With neither, the app `@alxia/core`'s `Register` names, and\n * `BaseContext` when nothing is registered.\n *\n * ```ts\n * export async function loader({ context }: Route.LoaderArgs) {\n * const { user, log } = alxiaOf(context);\n * }\n * ```\n *\n * Throws when the request did not come through `reactRouter()`: under\n * `react-router dev` without the plugin, or a test that calls a loader\n * directly.\n */\nexport function alxiaOf<\n\tApp extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp,\n>(\n\tcontext: Readonly<RouterContextProvider>,\n): ContextOf<AppOf<App>> & {\n\t/** The catch-all's route, which every request reaching React Router matched. */\n\treadonly route: string;\n} {\n\tconst value = context.get(alxiaContext);\n\tif (value === MISSING) {\n\t\tthrow new Error(\n\t\t\t\"alxiaOf(): this request has no alxia context. Serve the React Router app through alxia: add alxia() from @alxia/react-router/vite to vite.config.ts's plugins, or, with a server of your own, serve the build through reactRouter() from @alxia/react-router.\",\n\t\t);\n\t}\n\treturn value as ContextOf<AppOf<App>> & { readonly route: string };\n}\n",
6
+ "/** `nonceOf`: the request's CSP nonce, read from alxia's context if a middleware set one. */\nimport type { RouterContextProvider } from 'react-router';\nimport { alxiaContext } from './context';\n\n/**\n * This request's CSP nonce, for `entry.server.tsx`: the `nonce` alxia's\n * middlewares put on the context — `secureHeaders({ nonce: true })` from\n * `@alxia/secure-headers`, or a `derive` of your own — or `undefined` when\n * none did, or when the request did not come through alxia. It reads the\n * key if present: neither package depends on the other.\n *\n * ```tsx\n * const nonce = nonceOf(loadContext);\n * <ServerRouter context={routerContext} url={request.url} nonce={nonce} />\n * ```\n */\nexport function nonceOf(\n\tcontext: Readonly<RouterContextProvider>,\n): string | undefined {\n\tconst value = context.get(alxiaContext);\n\tif (typeof value !== 'object' || value === null || !('nonce' in value)) {\n\t\treturn undefined;\n\t}\n\treturn typeof value.nonce === 'string' ? value.nonce : undefined;\n}\n",
7
+ "import { fileURLToPath } from 'node:url';\nimport type {\n\tAlxia,\n\tAnyAlxia,\n\tBaseContext,\n\tMaybePromise,\n\tRouteDefinition,\n\tStatusCode,\n} from '@alxia/core';\nimport {\n\tcreateRequestHandler,\n\ttype RequestHandler,\n\tRouterContextProvider,\n\ttype ServerBuild,\n} from 'react-router';\nimport { serveClient } from './assets';\nimport { alxiaContext } from './context';\n\nexport interface ReactRouterOptions<Ctx> {\n\t/**\n\t * React Router's server build: the module itself, or a function that\n\t * returns it — `() => import('virtual:react-router/server-build')` in a\n\t * server entry Vite builds, `() => import('./build/server/index.js')`\n\t * beside a build. A function is called on every request in\n\t * `development`, so an edit is picked up, and once in `production`.\n\t */\n\treadonly build: ServerBuild | (() => MaybePromise<ServerBuild>);\n\t/** React Router's server mode: `production` by default. */\n\treadonly mode?: 'development' | 'production';\n\t/**\n\t * Sets the app's own keys on React Router's context provider, from the\n\t * context alxia's middlewares built. `ctx` is typed by the app at the point of\n\t * `app.plugin`: reading what nothing before it adds is a compile error.\n\t * `alxiaContext` is always set, whether or not this is given.\n\t */\n\treadonly getLoadContext?: (\n\t\tctx: BaseContext & Ctx,\n\t\tcontext: RouterContextProvider,\n\t) => MaybePromise<void>;\n\t/**\n\t * The client build's folder, `build/client`: a path or a `file:` URL.\n\t * Its `assets/` is served immutable, and every other top-level file or\n\t * folder — the copies of `public/` — with an hour's cache, before the\n\t * catch-all. Ignored in `development`, where Vite serves them.\n\t */\n\treadonly client?: string | URL;\n}\n\n/** The methods the catch-all takes. A `HEAD` reaches its `GET`. */\nconst METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;\n\n/** The handlers `reactRouter()` declared: the catch-all's and the client build's. */\nconst declared = new WeakSet<RouteDefinition['handler']>();\n\n/**\n * Whether `reactRouter()` declared this route: the catch-all, or one of the\n * client build's files. For `@alxia/openapi`'s `matchesSpec`, whose\n * `exclude` leaves them out, so the app's routes are checked against the\n * operations of its OpenAPI document and its pages are not.\n *\n * ```ts\n * matchesSpec(app, operations, { exclude: isReactRouterRoute });\n * ```\n */\nexport function isReactRouterRoute(route: RouteDefinition): boolean {\n\treturn declared.has(route.handler);\n}\n\n/**\n * A React Router framework app, server rendered on `app`: `GET`, `POST`,\n * `PUT`, `PATCH` and `DELETE` at `/*`, behind every middleware declared on `app`\n * before it. Each loader, action and middleware reads what those added\n * through `alxiaOf<App>(context)`. The app's own routes — an `/api`\n * — answer their paths, declared before it or after; the client build's\n * files are served when `client` is given.\n *\n * ```ts\n * const app = base.plugin((app) =>\n * reactRouter(app, { build: () => import('./build/server/index.js'), client: 'build/client' }),\n * );\n * ```\n */\nexport function reactRouter<Ctx extends object, Prefix extends string>(\n\tapp: Alxia<Ctx, Prefix>,\n\toptions: ReactRouterOptions<Ctx>,\n): Alxia<Ctx, Prefix> {\n\tconst mode = options.mode ?? 'production';\n\tconst handle = requestHandler(options.build, mode);\n\tconst getLoadContext = options.getLoadContext;\n\tconst handler = async (ctx: BaseContext & Ctx) => {\n\t\tconst context = new RouterContextProvider();\n\t\tcontext.set(alxiaContext, ctx);\n\t\tawait getLoadContext?.(ctx, context);\n\t\t// React Router answers a HEAD with no headers at all: hand it the GET,\n\t\t// and the core drops the body.\n\t\tconst request =\n\t\t\tctx.request.method === 'HEAD'\n\t\t\t\t? new Request(ctx.request, { method: 'GET' })\n\t\t\t\t: ctx.request;\n\t\tconst response = await handle(request, context);\n\t\treturn ctx.reply(\n\t\t\tresponse.status as StatusCode,\n\t\t\tresponse.body ?? undefined,\n\t\t\t{ headers: response.headers },\n\t\t);\n\t};\n\n\tconst routes = asRoutes(app);\n\tdeclaring(app, () => {\n\t\tif (options.client !== undefined && mode === 'production') {\n\t\t\tserveClient(routes, pathOf(options.client));\n\t\t}\n\t\tfor (const method of METHODS) routes[method]('/*', handler);\n\t});\n\treturn app;\n}\n\n/**\n * Declares the client build's files on `app`, marked for\n * `isReactRouterRoute`: what `reactRouter()` does with `client`, apart, so\n * that `createServer()` serves them before the middlewares of `configure`.\n */\nexport function declareClient(app: AnyAlxia, client: string | URL): void {\n\tdeclaring(app, () => serveClient(asRoutes(app), pathOf(client)));\n}\n\n/** Runs `declare`, and marks every route it added to `app` as ours. */\nfunction declaring(app: AnyAlxia, declare: () => void): void {\n\tconst before = new Set(app.routes);\n\tdeclare();\n\tfor (const route of app.routes) {\n\t\tif (!before.has(route)) declared.add(route.handler);\n\t}\n}\n\n/** The app's route methods, untyped: the catch-all's handler is not one a route type admits. */\nfunction asRoutes(app: AnyAlxia) {\n\treturn app as unknown as {\n\t\tstatic(path: string, source: string, options: object): unknown;\n\t\tfile(path: string, file: string, options: object): unknown;\n\t} & Record<\n\t\t(typeof METHODS)[number],\n\t\t(path: string, handler: unknown) => unknown\n\t>;\n}\n\n/** A handler that resolves a function build once in production, on every request in development. */\nfunction requestHandler(\n\tbuild: ReactRouterOptions<never>['build'],\n\tmode: 'development' | 'production',\n): RequestHandler {\n\tif (typeof build !== 'function' || mode === 'development') {\n\t\treturn createRequestHandler(build, mode);\n\t}\n\tlet resolved: Promise<RequestHandler> | undefined;\n\treturn async (request, context) => {\n\t\tresolved ??= Promise.resolve(build()).then(\n\t\t\t(server) => createRequestHandler(server, mode),\n\t\t\t(error: unknown) => {\n\t\t\t\tresolved = undefined;\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\treturn (await resolved)(request, context);\n\t};\n}\n\nfunction pathOf(client: string | URL): string {\n\treturn client instanceof URL ? fileURLToPath(client) : client;\n}\n",
8
8
  "/**\n * The client build's files, served before the catch-all: the hashed\n * `assets/` immutable, every other top-level file or folder — what React\n * Router copied from `public/` — with an hour's cache.\n */\nimport { readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** A hashed asset never changes under its name. */\nexport const IMMUTABLE = 'public, max-age=31536000, immutable';\n/** A public file keeps its name across builds: revalidated after an hour. */\nexport const AN_HOUR = 'public, max-age=3600';\n\ninterface Serving {\n\tstatic(path: string, source: string, options: object): unknown;\n\tfile(path: string, file: string, options: object): unknown;\n}\n\n/** Declares the client build's routes on `app`. */\nexport function serveClient(app: Serving, client: string): void {\n\tif (!isDirectory(client)) {\n\t\tthrow new TypeError(\n\t\t\t`reactRouter(): client is ${client}, which is not a directory. Pass the client build, build/client by default.`,\n\t\t);\n\t}\n\tfor (const name of readdirSync(client).sort()) {\n\t\tif (name.startsWith('.')) continue;\n\t\tconst path = join(client, name);\n\t\tconst cacheControl = name === 'assets' ? IMMUTABLE : AN_HOUR;\n\t\t// Declared as a request's URL carries the name: `my file.pdf` is\n\t\t// `/my%20file.pdf`, `café.png` is `/caf%C3%A9.png`.\n\t\tconst route = new URL(`/${name}`, 'http://localhost').pathname;\n\t\ttry {\n\t\t\tif (isDirectory(path)) app.static(route, path, { cacheControl });\n\t\t\telse app.file(route, path, { cacheControl });\n\t\t} catch (error) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`reactRouter(): ${path} cannot be served at a path of its own name; rename it. ${(error as Error).message}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction isDirectory(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isDirectory();\n\t} catch {\n\t\treturn false;\n\t}\n}\n",
9
- "/**\n * `createServer()`: the alxia server of a React Router app, as\n * `@alxia/react-router/vite` builds it. The plugin hands it React Router's\n * build, the mode and the client folder; the app's own `app/server.ts`\n * customises the rest, or is not written at all.\n */\nimport {\n\ttype Alxia,\n\ttype AnyAlxia,\n\talxia,\n\ttype ContextOf,\n\ttype Empty,\n\ttype ListenOptions,\n\ttype MaybePromise,\n} from '@alxia/core';\nimport type { RouterContextProvider, ServerBuild } from 'react-router';\nimport { declareClient, reactRouter } from './react-router';\n\n/** An app as `alxia()` makes it: what `beforeAll`, or `configure`, receives. */\nexport type FreshApp = Alxia<Empty, Empty, '', never>;\n\n/**\n * What the Vite plugin hands the server, and a test passes to `create`:\n * React Router's server build, its mode, and the client build's folder.\n */\nexport interface ServerWiring {\n\t/**\n\t * React Router's server build, or a function that returns it: called on\n\t * every request in `development`, once in `production`.\n\t */\n\treadonly build: ServerBuild | (() => MaybePromise<ServerBuild>);\n\t/** `development` under `react-router dev`, `production` in a build, and by default. */\n\treadonly mode?: 'development' | 'production';\n\t/** The client build's folder, a path or a `file:` URL: served in `production`. */\n\treadonly client?: string | URL | undefined;\n}\n\nexport interface ServerOptions<Before extends AnyAlxia, App extends AnyAlxia> {\n\t/**\n\t * Runs first, on a new app: what is declared here runs before the\n\t * client's files too — a guard, a rate limit, a logger that should see\n\t * every request. Returns the app, so its types flow to `configure`.\n\t */\n\treadonly beforeAll?: (app: FreshApp) => Before;\n\t/**\n\t * The app the pages run behind: its plugins, its hooks, its `/api`.\n\t * Declared after the client's files and before the catch-all. Returns\n\t * the app: what it builds is what the loaders read through `alxiaOf`.\n\t */\n\treadonly configure?: (app: Before) => App;\n\t/**\n\t * Sets the app's own keys on React Router's context provider, `ctx`\n\t * typed by `configure`'s app. `alxiaContext` is always set.\n\t */\n\treadonly getLoadContext?: (\n\t\tctx: ContextOf<App>,\n\t\tcontext: RouterContextProvider,\n\t) => MaybePromise<void>;\n\t/** Overrides the plugin's server build: rarely wanted. */\n\treadonly build?: ServerWiring['build'];\n\t/** Overrides the plugin's mode, `development` in dev and `production` in a build. */\n\treadonly mode?: 'development' | 'production';\n\t/**\n\t * Overrides the plugin's client folder, `build/client` beside the built\n\t * server. `false` serves none of it: declare the files yourself, in\n\t * `beforeAll` or `configure`.\n\t */\n\treadonly client?: string | URL | false;\n\t/**\n\t * `listen`'s options for `bun build/server/index.js`; a `port` or `hostname` here wins over `PORT` (3000)\n\t * and `HOST` (`0.0.0.0`) from the environment.\n\t */\n\treadonly listen?: ListenOptions;\n\t/**\n\t * Called once the built server listens and its `SIGINT` and `SIGTERM` handlers are in place.\n\t * Prints `alxia listening on <url>` by default.\n\t */\n\treadonly onListen?: (server: Bun.Server<unknown>) => void;\n}\n\n/**\n * The server `createServer()` describes, made into an app by the Vite\n * plugin, by a test, or by a server file of your own.\n */\nexport interface ReactRouterServer<App extends AnyAlxia> {\n\t/**\n\t * The app, serving React Router's build: `build/server/index.js`'s\n\t * default export. A test drives it with `app.request`.\n\t *\n\t * ```ts\n\t * const app = server.create({ build: await import('./build/server/index.js') });\n\t * ```\n\t */\n\tcreate(wiring: ServerWiring): App;\n\t/**\n\t * Listens with `app`, on `listen`, `PORT` and `HOST`, and stops it on\n\t * `SIGINT` or `SIGTERM`: its `onStop` hooks run, and the process exits.\n\t * What `bun build/server/index.js` runs.\n\t */\n\tstart(app: App): Bun.Server<unknown>;\n}\n\n/**\n * The alxia server of a React Router app, for `app/server.ts`. Every option\n * is optional: the Vite plugin wires React Router's build, the mode and the\n * client folder, and without the file uses `createServer()` as it is.\n *\n * ```ts\n * const server = createServer({\n * configure: (app) => app.use(logger()).get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n * });\n * export default server;\n * ```\n *\n * The request runs through `beforeAll`, the client's files, `configure`,\n * then the pages.\n */\nexport function createServer<\n\tBefore extends AnyAlxia = FreshApp,\n\tApp extends AnyAlxia = Before,\n>(options: ServerOptions<Before, App> = {}): ReactRouterServer<App> {\n\treturn {\n\t\tcreate(wiring) {\n\t\t\tconst mode = options.mode ?? wiring.mode ?? 'production';\n\t\t\tconst client =\n\t\t\t\toptions.client === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: (options.client ?? wiring.client);\n\t\t\tconst fresh = alxia();\n\t\t\t// Without beforeAll or configure, Before and App are their defaults,\n\t\t\t// the app passed through: a type argument given by hand is believed.\n\t\t\tconst before = (options.beforeAll?.(fresh) ?? fresh) as Before;\n\t\t\tif (client !== undefined && mode === 'production') {\n\t\t\t\tdeclareClient(before, client);\n\t\t\t}\n\t\t\tconst app = (options.configure?.(before) ?? before) as App;\n\t\t\tconst getLoadContext = options.getLoadContext;\n\t\t\treactRouter(app, {\n\t\t\t\tbuild: options.build ?? wiring.build,\n\t\t\t\tmode,\n\t\t\t\t...(getLoadContext === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tgetLoadContext: (ctx, context) =>\n\t\t\t\t\t\t\t\tgetLoadContext(ctx as ContextOf<App>, context),\n\t\t\t\t\t\t}),\n\t\t\t});\n\t\t\treturn app;\n\t\t},\n\t\tstart(app) {\n\t\t\tconst server = app.listen({\n\t\t\t\tport: Number(process.env['PORT'] || 3000),\n\t\t\t\thostname: process.env['HOST'] || '0.0.0.0',\n\t\t\t\t...options.listen,\n\t\t\t});\n\t\t\t// Stop as the platform asks: the app's onStop hooks run, and the process ends.\n\t\t\t// Installed before onListen: a supervisor may signal as soon as it reads\n\t\t\t// that the server listens, and a signal with no handler yet kills the process.\n\t\t\tfor (const signal of ['SIGINT', 'SIGTERM'] as const) {\n\t\t\t\tprocess.once(signal, () => {\n\t\t\t\t\tvoid app.stop().then(\n\t\t\t\t\t\t() => process.exit(0),\n\t\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\t\tconsole.error(error);\n\t\t\t\t\t\t\tprocess.exit(1);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t\t(options.onListen ?? announce)(server);\n\t\t\treturn server;\n\t\t},\n\t};\n}\n\nfunction announce(server: Bun.Server<unknown>): void {\n\tconsole.log(`alxia listening on ${server.url}`);\n}\n"
9
+ "/**\n * `createServer()`: the alxia server of a React Router app, as\n * `@alxia/react-router/vite` builds it. The plugin hands it React Router's\n * build, the mode and the client folder; the app's own `app/server.ts`\n * customises the rest, or is not written at all.\n */\nimport {\n\ttype Alxia,\n\ttype AnyAlxia,\n\talxia,\n\ttype ContextOf,\n\ttype Empty,\n\ttype ListenInfo,\n\ttype ListenOptions,\n\ttype MaybePromise,\n} from '@alxia/core';\nimport type { RouterContextProvider, ServerBuild } from 'react-router';\nimport { declareClient, reactRouter } from './react-router';\n\n/** An app as `alxia()` makes it: what `beforeAll`, or `configure`, receives. */\nexport type FreshApp = Alxia<Empty, ''>;\n\n/**\n * What the Vite plugin hands the server, and a test passes to `create`:\n * React Router's server build, its mode, and the client build's folder.\n */\nexport interface ServerWiring {\n\t/**\n\t * React Router's server build, or a function that returns it: called on\n\t * every request in `development`, once in `production`.\n\t */\n\treadonly build: ServerBuild | (() => MaybePromise<ServerBuild>);\n\t/** `development` under `react-router dev`, `production` in a build, and by default. */\n\treadonly mode?: 'development' | 'production';\n\t/** The client build's folder, a path or a `file:` URL: served in `production`. */\n\treadonly client?: string | URL | undefined;\n}\n\nexport interface ServerOptions<Before extends AnyAlxia, App extends AnyAlxia> {\n\t/**\n\t * Runs first, on a new app: what is declared here runs before the\n\t * client's files too — a guard, a rate limit, a logger that should see\n\t * every request. Returns the app, so its types flow to `configure`.\n\t */\n\treadonly beforeAll?: (app: FreshApp) => Before;\n\t/**\n\t * The app the pages run behind: its middlewares, its plugins, its `/api`.\n\t * Declared after the client's files and before the catch-all. Returns\n\t * the app: what it builds is what the loaders read through `alxiaOf`.\n\t */\n\treadonly configure?: (app: Before) => App;\n\t/**\n\t * Sets the app's own keys on React Router's context provider, `ctx`\n\t * typed by `configure`'s app. `alxiaContext` is always set.\n\t */\n\treadonly getLoadContext?: (\n\t\tctx: ContextOf<App>,\n\t\tcontext: RouterContextProvider,\n\t) => MaybePromise<void>;\n\t/** Overrides the plugin's server build: rarely wanted. */\n\treadonly build?: ServerWiring['build'];\n\t/** Overrides the plugin's mode, `development` in dev and `production` in a build. */\n\treadonly mode?: 'development' | 'production';\n\t/**\n\t * Overrides the plugin's client folder, `build/client` beside the built\n\t * server. `false` serves none of it: declare the files yourself, in\n\t * `beforeAll` or `configure`.\n\t */\n\treadonly client?: string | URL | false;\n\t/**\n\t * `listen`'s options for `bun build/server/index.js` — `shutdownTimeout`,\n\t * `signals` among them; a `port` or `hostname` here wins over `PORT` (3000)\n\t * and `HOST` (`0.0.0.0`) from the environment.\n\t */\n\treadonly listen?: ListenOptions;\n\t/**\n\t * Called once the built server listens and its `SIGINT` and `SIGTERM` handlers are in place.\n\t * Prints `alxia listening on <url>` by default, and in dev the route\n\t * table (`alxia({ dev })`). An `onListen` in `listen` wins over it.\n\t */\n\treadonly onListen?: (server: Bun.Server<unknown>) => void;\n}\n\n/**\n * The server `createServer()` describes, made into an app by the Vite\n * plugin, by a test, or by a server file of your own.\n */\nexport interface ReactRouterServer<App extends AnyAlxia> {\n\t/**\n\t * The app, serving React Router's build: `build/server/index.js`'s\n\t * default export. A test drives it with `app.request`.\n\t *\n\t * ```ts\n\t * const app = server.create({ build: await import('./build/server/index.js') });\n\t * ```\n\t */\n\tcreate(wiring: ServerWiring): App;\n\t/**\n\t * Listens with `app`, on `listen`, `PORT` and `HOST`, and shuts it down\n\t * gracefully on `SIGINT` or `SIGTERM`, as `@alxia/core`'s `listen` does:\n\t * the requests in flight finish within `shutdownTimeout`, its `onStop`\n\t * hooks run, and the process exits. What `bun build/server/index.js` runs.\n\t */\n\tstart(app: App): Bun.Server<unknown>;\n}\n\n/**\n * The alxia server of a React Router app, for `app/server.ts`. Every option\n * is optional: the Vite plugin wires React Router's build, the mode and the\n * client folder, and without the file uses `createServer()` as it is.\n *\n * ```ts\n * const server = createServer({\n * configure: (app) => app.use(logger()).get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n * });\n * export default server;\n * ```\n *\n * The request runs through `beforeAll`, the client's files, `configure`,\n * then the pages.\n */\nexport function createServer<\n\tBefore extends AnyAlxia = FreshApp,\n\tApp extends AnyAlxia = Before,\n>(options: ServerOptions<Before, App> = {}): ReactRouterServer<App> {\n\treturn {\n\t\tcreate(wiring) {\n\t\t\tconst mode = options.mode ?? wiring.mode ?? 'production';\n\t\t\tconst client =\n\t\t\t\toptions.client === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: (options.client ?? wiring.client);\n\t\t\t// The app helps the developer in React Router's dev alone: a production\n\t\t\t// build is never in dev, whatever NODE_ENV says.\n\t\t\tconst fresh = alxia({ dev: mode === 'development' });\n\t\t\t// Without beforeAll or configure, Before and App are their defaults,\n\t\t\t// the app passed through: a type argument given by hand is believed.\n\t\t\tconst before = (options.beforeAll?.(fresh) ?? fresh) as Before;\n\t\t\tif (client !== undefined && mode === 'production') {\n\t\t\t\tdeclareClient(before, client);\n\t\t\t}\n\t\t\tconst app = (options.configure?.(before) ?? before) as App;\n\t\t\tconst getLoadContext = options.getLoadContext;\n\t\t\treactRouter(app, {\n\t\t\t\tbuild: options.build ?? wiring.build,\n\t\t\t\tmode,\n\t\t\t\t...(getLoadContext === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tgetLoadContext: (ctx, context) =>\n\t\t\t\t\t\t\t\tgetLoadContext(ctx as ContextOf<App>, context),\n\t\t\t\t\t\t}),\n\t\t\t});\n\t\t\treturn app;\n\t\t},\n\t\tstart(app) {\n\t\t\tconst own = options.listen?.onListen;\n\t\t\t// `listen` shuts the app down on SIGINT and SIGTERM — readiness 503,\n\t\t\t// the requests in flight drained, the onStop hooks, then the exit —\n\t\t\t// with its handlers in place before it tells onListen: a supervisor\n\t\t\t// may signal as soon as it reads that the server listens.\n\t\t\treturn app.listen({\n\t\t\t\tport: Number(process.env['PORT'] || 3000),\n\t\t\t\thostname: process.env['HOST'] || '0.0.0.0',\n\t\t\t\t...options.listen,\n\t\t\t\tonListen: (info) => {\n\t\t\t\t\tif (own !== undefined) own(info);\n\t\t\t\t\telse if (options.onListen !== undefined) {\n\t\t\t\t\t\toptions.onListen(info.server);\n\t\t\t\t\t} else announce(info);\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t};\n}\n\n/** In dev, the route table `listen` prints; else one line. */\nfunction announce({ dev, table, url }: ListenInfo): void {\n\tconsole.log(dev ? table : `alxia listening on ${url}`);\n}\n"
10
10
  ],
11
- "mappings": "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",
12
- "debugId": "63BB3B2311BA25F964756E2164756E21",
11
+ "mappings": "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",
12
+ "debugId": "BA278F18656F972964756E2164756E21",
13
13
  "names": []
14
14
  }
package/dist/nonce.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- /** `nonceOf`: the request's CSP nonce, read from alxia's context if a hook set one. */
1
+ /** `nonceOf`: the request's CSP nonce, read from alxia's context if a middleware set one. */
2
2
  import type { RouterContextProvider } from 'react-router';
3
3
  /**
4
4
  * This request's CSP nonce, for `entry.server.tsx`: the `nonce` alxia's
5
- * hooks put on the context — `secureHeaders({ nonce: true })` from
5
+ * middlewares put on the context — `secureHeaders({ nonce: true })` from
6
6
  * `@alxia/secure-headers`, or a `derive` of your own — or `undefined` when
7
7
  * none did, or when the request did not come through alxia. It reads the
8
8
  * key if present: neither package depends on the other.
@@ -1 +1 @@
1
- {"version":3,"file":"nonce.d.ts","sourceRoot":"","sources":["../src/nonce.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAG1D;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CACtB,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GACtC,MAAM,GAAG,SAAS,CAMpB"}
1
+ {"version":3,"file":"nonce.d.ts","sourceRoot":"","sources":["../src/nonce.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAG1D;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CACtB,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GACtC,MAAM,GAAG,SAAS,CAMpB"}