@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 +92 -14
- package/dist/context.d.ts +32 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -5
- package/dist/index.js.map +7 -6
- package/dist/nonce.d.ts +2 -2
- package/dist/nonce.d.ts.map +1 -1
- package/dist/vite/dev.d.ts +6 -1
- package/dist/vite/dev.d.ts.map +1 -1
- package/dist/vite/index.js +17 -9
- package/dist/vite/index.js.map +5 -5
- package/dist/vite/preview.d.ts.map +1 -1
- package/dist/vite/socket.d.ts +8 -1
- package/dist/vite/socket.d.ts.map +1 -1
- package/dist/with-alxia.d.ts +36 -0
- package/dist/with-alxia.d.ts.map +1 -0
- package/docs/guide.md +136 -6
- package/docs/roadmap.md +40 -17
- package/docs/troubleshooting.md +89 -16
- package/package.json +9 -9
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 {
|
|
166
|
+
import { type AlxiaArgs, withAlxia } from '@alxia/react-router';
|
|
157
167
|
import type { Route } from './+types/home';
|
|
158
168
|
|
|
159
|
-
export
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
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)`
|
|
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, `
|
|
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
|
|
421
|
-
`ctx.server`
|
|
422
|
-
|
|
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`
|
|
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 {
|
|
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:
|
|
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
|
package/dist/context.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
|
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=
|
|
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,
|
|
6
|
-
"/** `nonceOf`: the request's CSP nonce, read from alxia's context if a middleware set one. */\nimport
|
|
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": ";AAKA;AAIA,IAAM,UAAyB,OAAO,uBAAuB;AAStD,IAAM,eAAe,cAAuB,OAAO;AA+EnD,SAAS,OAEf,
|
|
12
|
-
"debugId": "
|
|
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
|
|
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:
|
|
15
|
+
export declare function nonceOf(context: ProviderLike): string | undefined;
|
|
16
16
|
//# sourceMappingURL=nonce.d.ts.map
|
package/dist/nonce.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nonce.d.ts","sourceRoot":"","sources":["../src/nonce.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,OAAO,KAAK,
|
|
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"}
|
package/dist/vite/dev.d.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/dist/vite/dev.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/vite/index.js
CHANGED
|
@@ -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", () =>
|
|
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
|
-
|
|
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=
|
|
452
|
+
//# debugId=090F44FD671492F864756E2164756E21
|
|
445
453
|
//# sourceMappingURL=index.js.map
|