@alxia/react-router 0.7.5 → 0.8.1

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
@@ -151,30 +151,101 @@ login page's JavaScript.
151
151
 
152
152
  ## Reading the context in a loader
153
153
 
154
+ What alxia's middlewares built for a request reaches a loader, an action or
155
+ a middleware in three forms, each the same object, typed by the `Register`
156
+ declaration above:
157
+
158
+ | Form | Use it |
159
+ | --- | --- |
160
+ | `withAlxia(fn)` | with the generated `Route.LoaderArgs` and `Route.ActionArgs`: `alxia` beside the arguments |
161
+ | `alxiaOf(context)` | inside a plain function, or with a type argument where nothing is registered |
162
+ | `context.alxia` | with `LoaderFunctionArgs` and `ActionFunctionArgs` from `react-router` |
163
+
154
164
  ```ts
155
165
  // app/routes/home.tsx
156
- import { alxiaOf } from '@alxia/react-router';
166
+ import { type AlxiaArgs, withAlxia } from '@alxia/react-router';
157
167
  import type { Route } from './+types/home';
158
168
 
159
- export function loader({ context }: Route.LoaderArgs) {
160
- const { user, log } = alxiaOf(context); // typed: what configure built
161
- log.info('home');
162
- return { name: user?.name ?? 'anonymous' };
169
+ export const loader = withAlxia(({ alxia }: Route.LoaderArgs & AlxiaArgs) => {
170
+ alxia.log.info('home'); // typed: what configure built
171
+ return { name: alxia.user?.name ?? 'anonymous' };
172
+ });
173
+ ```
174
+
175
+ `withAlxia` returns a function of React Router's arguments alone, with
176
+ `fn`'s return type: `Route.ComponentProps['loaderData']` and
177
+ `useLoaderData<typeof loader>` read what `fn` returns. It works for an
178
+ action, and for a middleware, whose `next` it passes on.
179
+
180
+ `alxiaOf(context)`, in a plain function:
181
+
182
+ ```ts
183
+ import { alxiaOf } from '@alxia/react-router';
184
+
185
+ export async function action({ context }: Route.ActionArgs) {
186
+ const { user, log } = alxiaOf(context);
187
+ log.info('saved', { by: user?.name });
188
+ return { ok: true };
163
189
  }
164
190
  ```
165
191
 
166
- How `alxiaOf(context)` is typed:
192
+ How each is typed:
167
193
 
168
194
  - with the `Register` declaration above, by that server: reading something
169
195
  no middleware derives is a compile error;
170
- - without it, `alxiaOf<typeof server>(context)` names the server;
196
+ - without it, `AlxiaArgs<typeof server>` or `alxiaOf<typeof server>(context)`
197
+ names the server;
171
198
  - with neither, by the base `@alxia/core`'s own `Register` names
172
199
  (`context: typeof base`), when the app has one;
173
- - with none of them, `alxiaOf(context)` is `BaseContext`.
200
+ - with none of them, `BaseContext`.
174
201
 
175
202
  This package's `Register` wins over core's: the server's app is the base
176
203
  and all `configure` adds after it.
177
204
 
205
+ ### The shorthand: `context.alxia`
206
+
207
+ The same object, with no import and no call, where the arguments are
208
+ react-router's own types:
209
+
210
+ ```ts
211
+ import type { LoaderFunctionArgs } from 'react-router';
212
+
213
+ export function loader({ context }: LoaderFunctionArgs) {
214
+ const { user, server } = context.alxia;
215
+ return { name: user?.name ?? 'anonymous' };
216
+ }
217
+ ```
218
+
219
+ With the generated `Route.LoaderArgs` it is there at runtime, but `tsc`
220
+ reports `Property 'alxia' does not exist on type
221
+ 'Readonly<RouterContextProvider>'` (TS2339): react-router's `./internal`
222
+ types, which the generated route types read, point at its development
223
+ build, a second declaration of `RouterContextProvider` that no package can
224
+ augment. Use `withAlxia` there until react-router ships one declaration.
225
+ [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#property-alxia-does-not-exist-on-type-readonlyroutercontextprovider)
226
+
227
+ ### The server: `server`
228
+
229
+ `alxiaOf(context).server` is the `Bun.Server` serving the request, core's
230
+ `ctx.server`: its `publish` reaches the app's [WebSocket](#websockets)
231
+ subscribers from an action.
232
+
233
+ ```ts
234
+ export async function action({ context }: Route.ActionArgs) {
235
+ alxiaOf(context).server?.publish('todos', JSON.stringify({ changed: true }));
236
+ return { ok: true };
237
+ }
238
+ ```
239
+
240
+ It is the listening server behind `bun build/server/index.js`. Under
241
+ `react-router dev` and `vite preview`, Vite's server is `node:http`, so it is
242
+ the `Bun.Server` the app's sockets are relayed to, on a loopback port:
243
+ `publish` reaches them, but its `url` is that port's, not Vite's, and its
244
+ `requestIP` and `timeout` know nothing of a request Vite took. Through
245
+ `app.request(…)`, as in a test, it is `undefined`.
246
+ It is not `Register`'s `server`, which names what `createServer()` returned
247
+ for the types.
248
+
178
249
  [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#typing-the-loaders)
179
250
 
180
251
  ## A CSP nonce
@@ -247,7 +318,9 @@ export default createServer({
247
318
  In dev, the plugin hands each upgrade Vite does not claim (its HMR, its
248
319
  `server.proxy`) to the app, run by `Bun.serve` as `listen` runs it: the
249
320
  middlewares before the route, a refusal's status, `socket.data`, `publish`. An
250
- edit to the server is used from the next connection.
321
+ edit to the server is used from the next connection. An action's
322
+ `alxiaOf(context).server.publish` reaches those sockets too
323
+ ([The server](#the-server-server)).
251
324
  [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#websockets)
252
325
 
253
326
  ## Options
@@ -417,9 +490,9 @@ has the commented file, and what to copy for a package left external.
417
490
  - **`@alxia/secure-headers`' default policy blocks the page's scripts**
418
491
  and forms: give the pages a policy of their own, with `nonce: true` and
419
492
  `nonceOf` in `entry.server.tsx` rather than `'unsafe-inline'`. [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#refused-to-execute-inline-script-because-it-violates-the-following-content-security-policy-directive-default-src-none)
420
- - **Under `react-router dev`, `page()` and an HTTP request's
421
- `ctx.server` are absent**: requests arrive through `app.fetch`. A
422
- socket's upgrade has its server. [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#ctxserver-is-undefined-under-react-router-dev)
493
+ - **Under `react-router dev`, `page()` is not served**, and an HTTP
494
+ request's `ctx.server` is the loopback server the sockets are relayed to:
495
+ its `publish` reaches them, its `url` and `requestIP` are not Vite's. [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#a-page-is-not-served-under-react-router-dev)
423
496
  - **An alxia route whose path covers a page takes it**, wherever it is
424
497
  declared: `GET /:slug` answers `/about`. Keep alxia's routes under
425
498
  `/api`. [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#a-page-answers-alxias-json-404-or-405-instead-of-rendering)
@@ -433,13 +506,18 @@ has the commented file, and what to copy for a package left external.
433
506
  | `ReactRouterServer<App>` | what it returns: `create(wiring)` makes the app, `start(app)` listens |
434
507
  | `ServerWiring` | what `create` takes: `build`, `mode`, `client` |
435
508
  | `FreshApp` | the app `beforeAll`, or `configure` without it, receives |
436
- | `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` |
509
+ | `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`. Its `server` is the `Bun.Server` serving the request |
510
+ | `context.alxia` | the same object, on React Router's `RouterContextProvider` (this package augments its type, as `alxiaOf(context)` with no type argument): typed with `LoaderFunctionArgs` and `ActionFunctionArgs` from `react-router`, not yet with the generated `Route.LoaderArgs`: use `withAlxia` there |
511
+ | `withAlxia(fn)` | a loader, an action or a middleware given `alxia`, the alxia context, beside React Router's arguments: annotate `Route.LoaderArgs & AlxiaArgs`. Returns a function of the arguments alone, with `fn`'s return type |
512
+ | `AlxiaArgs<App>` | what `withAlxia` adds: `{ alxia }`, typed by `Register`, or by the server it names |
513
+ | `AlxiaContextOf<App>` | what `alxiaOf<App>(context)` returns: the app's context and the catch-all's `route` |
514
+ | `ProviderLike` | what `alxiaOf` and `nonceOf` take: any React Router context provider's `get`, the generated route types' included |
437
515
  | `Register` | the interface to augment with `server: typeof server` |
438
516
  | `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 |
439
517
  | `RegisteredOf<R, Core?>` | the app a `Register`-shaped interface names: its server's, `InvalidRegister`, or with no server `Core`, by default core's `RegisteredBase` |
440
518
  | `InvalidRegister` | what a `Register` naming neither a server nor an app reads as: every key of the app's own a compile error |
441
519
  | `AppOf<Server>` | the app a server makes |
442
- | `alxiaContext` | the React Router context key `alxiaOf` reads, set on every request |
520
+ | `alxiaContext` | the React Router context key `alxiaOf` and `context.alxia` read, set on every request |
443
521
  | `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` |
444
522
  | `reactRouter(app, options)` | the catch-all and the client's files, for a server of your own. `build`, `mode`, `getLoadContext`, `client` |
445
523
  | `ReactRouterOptions<Ctx>` | its options |
package/dist/context.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * middleware the request's alxia context, and the typed way to read it.
4
4
  */
5
5
  import type { Alxia, AnyAlxia, ContextOf, RegisteredBase } from '@alxia/core';
6
- import { type RouterContextProvider } from 'react-router';
6
+ import { RouterContextProvider } from 'react-router';
7
7
  import type { ReactRouterServer } from './server';
8
8
  /**
9
9
  * The key `reactRouter()` sets on React Router's context provider, on every
@@ -74,8 +74,38 @@ export type RegisteredApp = RegisteredOf<Register>;
74
74
  * `react-router dev` without the plugin, or a test that calls a loader
75
75
  * directly.
76
76
  */
77
- 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: ProviderLike): AlxiaContextOf<App>;
78
+ /**
79
+ * What `alxiaOf` and `nonceOf` read of a provider: its `get`. Not the whole
80
+ * class, which this package gives an `alxia`: the generated route types'
81
+ * `context` is another declaration of it, without one, and must still pass.
82
+ */
83
+ export type ProviderLike = Pick<Readonly<RouterContextProvider>, 'get'>;
84
+ /** What `alxiaOf<App>(context)` returns, and `context.alxia` is for the registered app. */
85
+ export type AlxiaContextOf<App extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp> = ContextOf<AppOf<App>> & {
78
86
  /** The catch-all's route, which every request reaching React Router matched. */
79
87
  readonly route: string;
80
88
  };
89
+ declare module 'react-router' {
90
+ interface RouterContextProvider {
91
+ /**
92
+ * What alxia's middlewares built for this request — `alxiaOf(context)`,
93
+ * typed by the app `Register` names (`BaseContext` with none):
94
+ *
95
+ * ```ts
96
+ * export async function loader({ context }: LoaderFunctionArgs) {
97
+ * const { user, server } = context.alxia;
98
+ * }
99
+ * ```
100
+ *
101
+ * Typed with `react-router`'s own argument types. The generated
102
+ * `Route.LoaderArgs` reads a second declaration of this class (its
103
+ * `./internal` types point at react-router's development build), which
104
+ * no augmentation reaches: read `alxiaOf(context)` there. Throws as
105
+ * `alxiaOf` does on a request that did not come through
106
+ * `reactRouter()`. Server-side only: a `clientLoader` has none.
107
+ */
108
+ readonly alxia: AlxiaContextOf;
109
+ }
110
+ }
81
111
  //# 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,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"}
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,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACpE,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,EACjE,OAAO,EAAE,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,CAQ5C;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,KAAK,CAAC,CAAC;AAExE,2FAA2F;AAC3F,MAAM,MAAM,cAAc,CACzB,GAAG,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GAAG,aAAa,IAC/D,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,OAAO,QAAQ,cAAc,CAAC;IAC7B,UAAU,qBAAqB;QAC9B;;;;;;;;;;;;;;;;WAgBG;QACH,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;KAC/B;CACD"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { type AppOf, alxiaContext, alxiaOf, type InvalidRegister, type Register, type RegisteredApp, type RegisteredOf, } from './context';
1
+ export { type AlxiaContextOf, type AppOf, alxiaContext, alxiaOf, type InvalidRegister, type ProviderLike, type Register, type RegisteredApp, type RegisteredOf, } from './context';
2
2
  export { nonceOf } from './nonce';
3
3
  export { isReactRouterRoute, type ReactRouterOptions, reactRouter, } from './react-router';
4
4
  export { createServer, type FreshApp, type ReactRouterServer, type ServerOptions, type ServerWiring, } from './server';
5
+ export { type AlxiaArgs, withAlxia } from './with-alxia';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,KAAK,EACV,YAAY,EACZ,OAAO,EACP,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,YAAY,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EACN,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,WAAW,GACX,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,YAAY,EACZ,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,GACjB,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,YAAY,EACZ,OAAO,EACP,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,YAAY,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EACN,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,WAAW,GACX,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,YAAY,EACZ,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/context.ts
2
- import { createContext } from "react-router";
2
+ import { createContext, RouterContextProvider } from "react-router";
3
3
  var MISSING = Symbol("alxia context missing");
4
4
  var alxiaContext = createContext(MISSING);
5
5
  function alxiaOf(context) {
@@ -9,6 +9,13 @@ function alxiaOf(context) {
9
9
  }
10
10
  return value;
11
11
  }
12
+ Object.defineProperty(RouterContextProvider.prototype, "alxia", {
13
+ configurable: true,
14
+ enumerable: false,
15
+ get() {
16
+ return alxiaOf(this);
17
+ }
18
+ });
12
19
  // src/nonce.ts
13
20
  function nonceOf(context) {
14
21
  const value = context.get(alxiaContext);
@@ -24,7 +31,7 @@ import {
24
31
  } from "@alxia/core";
25
32
  import {
26
33
  createRequestHandler,
27
- RouterContextProvider
34
+ RouterContextProvider as RouterContextProvider2
28
35
  } from "react-router";
29
36
 
30
37
  // src/assets.ts
@@ -71,7 +78,7 @@ function reactRouter(app, options) {
71
78
  const handle = requestHandler(options.build, mode);
72
79
  const getLoadContext = options.getLoadContext;
73
80
  const handler = async (ctx) => {
74
- const context = new RouterContextProvider;
81
+ const context = new RouterContextProvider2;
75
82
  context.set(alxiaContext, ctx);
76
83
  await getLoadContext?.(ctx, context);
77
84
  const response = await handle(routerRequest(ctx), context);
@@ -172,14 +179,19 @@ function createServer(options = {}) {
172
179
  function announce({ dev, table, url }) {
173
180
  console.log(dev ? table : `alxia listening on ${url}`);
174
181
  }
182
+ // src/with-alxia.ts
183
+ function withAlxia(fn) {
184
+ return (args, ...rest) => fn({ ...args, alxia: alxiaOf(args.context) }, ...rest);
185
+ }
175
186
  export {
176
187
  alxiaContext,
177
188
  alxiaOf,
178
189
  createServer,
179
190
  isReactRouterRoute,
180
191
  nonceOf,
181
- reactRouter
192
+ reactRouter,
193
+ withAlxia
182
194
  };
183
195
 
184
- //# debugId=654F43B13E39430B64756E2164756E21
196
+ //# debugId=348D809D943AF31864756E2164756E21
185
197
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/context.ts", "../src/nonce.ts", "../src/react-router.ts", "../src/assets.ts", "../src/server.ts"],
3
+ "sources": ["../src/context.ts", "../src/nonce.ts", "../src/react-router.ts", "../src/assets.ts", "../src/server.ts", "../src/with-alxia.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, 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",
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, 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>(context: ProviderLike): AlxiaContextOf<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 AlxiaContextOf<App>;\n}\n\n/**\n * What `alxiaOf` and `nonceOf` read of a provider: its `get`. Not the whole\n * class, which this package gives an `alxia`: the generated route types'\n * `context` is another declaration of it, without one, and must still pass.\n */\nexport type ProviderLike = Pick<Readonly<RouterContextProvider>, 'get'>;\n\n/** What `alxiaOf<App>(context)` returns, and `context.alxia` is for the registered app. */\nexport type AlxiaContextOf<\n\tApp extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp,\n> = ContextOf<AppOf<App>> & {\n\t/** The catch-all's route, which every request reaching React Router matched. */\n\treadonly route: string;\n};\n\ndeclare module 'react-router' {\n\tinterface RouterContextProvider {\n\t\t/**\n\t\t * What alxia's middlewares built for this request — `alxiaOf(context)`,\n\t\t * typed by the app `Register` names (`BaseContext` with none):\n\t\t *\n\t\t * ```ts\n\t\t * export async function loader({ context }: LoaderFunctionArgs) {\n\t\t * const { user, server } = context.alxia;\n\t\t * }\n\t\t * ```\n\t\t *\n\t\t * Typed with `react-router`'s own argument types. The generated\n\t\t * `Route.LoaderArgs` reads a second declaration of this class (its\n\t\t * `./internal` types point at react-router's development build), which\n\t\t * no augmentation reaches: read `alxiaOf(context)` there. Throws as\n\t\t * `alxiaOf` does on a request that did not come through\n\t\t * `reactRouter()`. Server-side only: a `clientLoader` has none.\n\t\t */\n\t\treadonly alxia: AlxiaContextOf;\n\t}\n}\n\n// A getter on the prototype rather than a value set on each provider: it\n// reads `alxiaContext`, so it holds exactly when that key is set — a\n// provider React Router copies keeps it — and on a provider no\n// `reactRouter()` filled, a test's, it throws `alxiaOf`'s error rather than\n// reading `undefined`.\nObject.defineProperty(RouterContextProvider.prototype, 'alxia', {\n\tconfigurable: true,\n\tenumerable: false,\n\tget(this: Readonly<RouterContextProvider>) {\n\t\treturn alxiaOf(this);\n\t},\n});\n",
6
+ "/** `nonceOf`: the request's CSP nonce, read from alxia's context if a middleware set one. */\nimport { alxiaContext, type ProviderLike } 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(context: ProviderLike): 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
7
  "import { fileURLToPath } from 'node:url';\nimport {\n\ttype Alxia,\n\ttype AnyAlxia,\n\ttype BaseContext,\n\ttype MaybePromise,\n\toriginalUrl,\n\ttype RouteDefinition,\n\ttype StatusCode,\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\tconst response = await handle(routerRequest(ctx), 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 * The request React Router reads: at the URL the client asked for, which\n * `originalUrl` reads behind the app's trusted proxies (`alxia({ proxy })`),\n * so `request.url` in a loader names the public scheme and host; and a\n * `HEAD` as its `GET`, since React Router answers a `HEAD` with no headers\n * at all, and the core drops the body.\n */\nfunction routerRequest(ctx: BaseContext): Request {\n\tconst href = originalUrl(ctx).href;\n\tconst head = ctx.request.method === 'HEAD';\n\tconst request =\n\t\thref === ctx.url.href ? ctx.request : new Request(href, ctx.request);\n\treturn head ? new Request(request, { method: 'GET' }) : request;\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 ListenInfo,\n\ttype ListenOptions,\n\ttype MaybePromise,\n\ttype ProxyTrust,\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/**\n\t * The proxies in front of the app, `trustProxy({ trusted })` from\n\t * `@alxia/core`: `alxia({ proxy })`'s option, so `ctx.ip` is the\n\t * client's and React Router's `request.url` the URL it asked for.\n\t */\n\treadonly proxy?: ProxyTrust;\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({\n\t\t\t\tdev: mode === 'development',\n\t\t\t\t...(options.proxy === undefined ? {} : { proxy: options.proxy }),\n\t\t\t});\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"
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\ttype ProxyTrust,\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/**\n\t * The proxies in front of the app, `trustProxy({ trusted })` from\n\t * `@alxia/core`: `alxia({ proxy })`'s option, so `ctx.ip` is the\n\t * client's and React Router's `request.url` the URL it asked for.\n\t */\n\treadonly proxy?: ProxyTrust;\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({\n\t\t\t\tdev: mode === 'development',\n\t\t\t\t...(options.proxy === undefined ? {} : { proxy: options.proxy }),\n\t\t\t});\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
+ "/**\n * `withAlxia(fn)`: a loader, an action or a middleware handed the alxia\n * context as `alxia`, typed even under the generated `Route.*Args`.\n */\nimport type { AnyAlxia } from '@alxia/core';\nimport {\n\ttype AlxiaContextOf,\n\talxiaOf,\n\ttype ProviderLike,\n\ttype RegisteredApp,\n} from './context';\nimport type { ReactRouterServer } from './server';\n\n/**\n * What `withAlxia` adds to React Router's arguments: `alxia`, what\n * `alxiaOf<App>(context)` returns. Typed by `Register` by default; name a\n * server, `AlxiaArgs<typeof server>`, as `alxiaOf<typeof server>` does.\n */\nexport interface AlxiaArgs<\n\tApp extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp,\n> {\n\treadonly alxia: AlxiaContextOf<App>;\n}\n\n/**\n * A loader, an action or a middleware given `alxia`, the alxia context, beside\n * React Router's arguments. Annotate the parameter as the generated arguments\n * and `AlxiaArgs`:\n *\n * ```ts\n * export const loader = withAlxia(({ alxia, params }: Route.LoaderArgs & AlxiaArgs) => {\n * return { name: alxia.user?.name ?? null, id: params.id };\n * });\n * ```\n *\n * It returns a function of React Router's arguments alone (`Args` without\n * `alxia`), with `fn`'s return type, so `useLoaderData<typeof loader>` and `Route.ComponentProps` read what\n * `fn` returns. A middleware's `next` is passed on. Throws `alxiaOf`'s error\n * on a request that did not come through `reactRouter()`.\n */\nexport function withAlxia<\n\t// `unknown`, not `AlxiaArgs<AnyAlxia>`, whose `alxia` is `any`: an `fn`\n\t// left unannotated reads nothing of it.\n\tArgs extends { readonly alxia: unknown; readonly context: ProviderLike },\n\tRest extends unknown[],\n\tResult,\n>(\n\tfn: (args: Args, ...rest: Rest) => Result,\n): (args: Omit<Args, 'alxia'>, ...rest: Rest) => Result {\n\treturn (args, ...rest) =>\n\t\tfn({ ...args, alxia: alxiaOf(args.context) } as unknown as Args, ...rest);\n}\n"
10
11
  ],
11
- "mappings": "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",
12
- "debugId": "654F43B13E39430B64756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "348D809D943AF31864756E2164756E21",
13
14
  "names": []
14
15
  }
package/dist/nonce.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** `nonceOf`: the request's CSP nonce, read from alxia's context if a middleware set one. */
2
- import type { RouterContextProvider } from 'react-router';
2
+ import { type ProviderLike } from './context';
3
3
  /**
4
4
  * This request's CSP nonce, for `entry.server.tsx`: the `nonce` alxia's
5
5
  * middlewares put on the context — `secureHeaders({ nonce: true })` from
@@ -12,5 +12,5 @@ import type { RouterContextProvider } from 'react-router';
12
12
  * <ServerRouter context={routerContext} url={request.url} nonce={nonce} />
13
13
  * ```
14
14
  */
15
- export declare function nonceOf(context: Readonly<RouterContextProvider>): string | undefined;
15
+ export declare function nonceOf(context: ProviderLike): string | undefined;
16
16
  //# sourceMappingURL=nonce.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"nonce.d.ts","sourceRoot":"","sources":["../src/nonce.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAMjE"}
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { ViteDevServer } from 'vite';
6
6
  import { toRequest } from './node';
7
+ import { type SideOf } from './socket';
7
8
  /** What the dev server module exports: the alxia app, as `server.create` made it. */
8
9
  export interface DevApp {
9
10
  fetch(request: Request, server?: Bun.Server<unknown>): Promise<Response>;
@@ -14,7 +15,11 @@ export interface DevApp {
14
15
  * edit invalidates it, so an edit is a new app.
15
16
  */
16
17
  export declare function loadApp(server: ViteDevServer, entry: string | undefined): Promise<DevApp>;
17
- export declare function serveFromEntry(server: ViteDevServer, entry: string | undefined, req: Parameters<typeof toRequest>[0], res: Parameters<typeof toRequest>[1]): Promise<void>;
18
+ /**
19
+ * One request Vite left, answered by the app as the server file is now,
20
+ * given the side server its sockets are open on as `ctx.server`.
21
+ */
22
+ export declare function serveFromEntry(server: ViteDevServer, entry: string | undefined, sideOf: SideOf, req: Parameters<typeof toRequest>[0], res: Parameters<typeof toRequest>[1]): Promise<void>;
18
23
  /**
19
24
  * `react-router dev`: the app's sockets on Vite's server now, and, returned
20
25
  * for after Vite's own middlewares, every request Vite leaves to the app.
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/vite/dev.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,EAAQ,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGzC,qFAAqF;AACrF,MAAM,WAAW,MAAM;IACtB,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzE,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;CAChD;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC5B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED,wBAAsB,cAAc,CACnC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,EACpC,GAAG,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACvB,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,IAAI,CAeZ"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/vite/dev.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,EAAQ,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAEtD,qFAAqF;AACrF,MAAM,WAAW,MAAM;IACtB,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzE,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;CAChD;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC5B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CACnC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,EACpC,GAAG,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACvB,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,IAAI,CAiBZ"}
@@ -261,8 +261,11 @@ Content-Length: 0\r
261
261
  }
262
262
  function bridgeSockets(http, bridge) {
263
263
  if (http === null)
264
- return;
264
+ return () => {
265
+ return;
266
+ };
265
267
  const sides = { current: undefined, retired: new Set };
268
+ let closed = false;
266
269
  http.on("upgrade", (req, socket, bytes) => {
267
270
  if (isVites(req) || isProxied(req.url ?? "/", bridge.proxy))
268
271
  return;
@@ -273,7 +276,11 @@ function bridgeSockets(http, bridge) {
273
276
  relay(req, socket, bytes, sideFor(sides, app).port);
274
277
  }).catch((error) => refuse(bridge, socket, error));
275
278
  });
276
- http.once("close", () => closeAll(sides));
279
+ http.once("close", () => {
280
+ closed = true;
281
+ closeAll(sides);
282
+ });
283
+ return (app) => closed ? undefined : sideFor(sides, app);
277
284
  }
278
285
 
279
286
  // src/vite/dev.ts
@@ -286,12 +293,12 @@ async function loadApp(server, entry) {
286
293
  const module = await runner.import(id);
287
294
  return module.default;
288
295
  }
289
- async function serveFromEntry(server, entry, req, res) {
296
+ async function serveFromEntry(server, entry, sideOf, req, res) {
290
297
  const app = await loadApp(server, entry);
291
- await send(res, await app.fetch(toRequest(req, res)));
298
+ await send(res, await app.fetch(toRequest(req, res), sideOf(app)));
292
299
  }
293
300
  function serveDev(server, entry) {
294
- bridgeSockets(server.httpServer, {
301
+ const sideOf = bridgeSockets(server.httpServer, {
295
302
  proxy: server.config.server.proxy,
296
303
  load: () => loadApp(server, entry),
297
304
  logger: server.config.logger,
@@ -299,7 +306,7 @@ function serveDev(server, entry) {
299
306
  });
300
307
  return () => {
301
308
  server.middlewares.use((req, res, next) => {
302
- serveFromEntry(server, entry, req, res).catch((error) => {
309
+ serveFromEntry(server, entry, sideOf, req, res).catch((error) => {
303
310
  if (error instanceof Error)
304
311
  server.ssrFixStacktrace(error);
305
312
  next(error);
@@ -330,7 +337,7 @@ function servePreview(server) {
330
337
  app ??= load(file, label);
331
338
  return app;
332
339
  };
333
- bridgeSockets(server.httpServer, {
340
+ const sideOf = bridgeSockets(server.httpServer, {
334
341
  proxy: server.config.preview.proxy,
335
342
  load: () => current().catch((error) => {
336
343
  app = undefined;
@@ -341,7 +348,8 @@ function servePreview(server) {
341
348
  server.middlewares.use((req, res, next) => {
342
349
  current().then(async (loaded) => {
343
350
  try {
344
- await send(res, await loaded.fetch(toRequest(req, res)));
351
+ const side = sideOf(loaded);
352
+ await send(res, await loaded.fetch(toRequest(req, res), side));
345
353
  } catch (error) {
346
354
  if (res.headersSent)
347
355
  res.destroy(error);
@@ -441,5 +449,5 @@ export {
441
449
  alxia
442
450
  };
443
451
 
444
- //# debugId=66D983950B07D2DC64756E2164756E21
452
+ //# debugId=090F44FD671492F864756E2164756E21
445
453
  //# sourceMappingURL=index.js.map