@alxia/react-router 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -43
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +4 -4
- package/dist/cli/template.d.ts +1 -1
- package/dist/cli/template.d.ts.map +1 -1
- package/dist/context.d.ts +23 -12
- package/dist/context.d.ts.map +1 -1
- package/dist/index.js.map +4 -4
- package/dist/react-router.d.ts +13 -13
- package/dist/react-router.d.ts.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/vite/bundle.d.ts +33 -0
- package/dist/vite/bundle.d.ts.map +1 -0
- package/dist/vite/index.d.ts +4 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +16 -2
- package/dist/vite/index.js.map +5 -4
- package/docs/README.md +1 -1
- package/docs/guide.md +259 -74
- package/docs/roadmap.md +29 -9
- package/docs/troubleshooting.md +103 -5
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -4,14 +4,31 @@
|
|
|
4
4
|
[React Router](https://reactrouter.com) framework app, server rendered,
|
|
5
5
|
under Bun. One Vite plugin and no server file: the pages are served by
|
|
6
6
|
alxia in `react-router dev` and from the build. When you want more,
|
|
7
|
-
`app/server.ts` adds the app's
|
|
7
|
+
`app/server.ts` adds the app's middlewares — logger, compression, sessions,
|
|
8
8
|
guards — and an `/api` beside the pages, and each loader reads what those
|
|
9
|
-
|
|
9
|
+
middlewares built, typed.
|
|
10
10
|
|
|
11
11
|
## Quick start
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
### A new app
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bun create @alxia my-app --template react-router
|
|
17
|
+
cd my-app
|
|
18
|
+
bun dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
[`@alxia/create`](https://www.npmjs.com/package/@alxia/create) writes React
|
|
22
|
+
Router's official template with everything below already done: this
|
|
23
|
+
package and `@alxia/core` installed, `alxia()` in `vite.config.ts`, the
|
|
24
|
+
`bunfig.toml`, `start` on Bun, Biome, and a `Dockerfile` that builds on
|
|
25
|
+
`oven/bun` and runs `build/` alone on Alpine. Its dependencies are
|
|
26
|
+
moved to the newest releases alxia accepts.
|
|
27
|
+
|
|
28
|
+
### An existing React Router app
|
|
29
|
+
|
|
30
|
+
Start from the official template, `bunx create-react-router@latest`, or your
|
|
31
|
+
own app, and make four changes.
|
|
15
32
|
|
|
16
33
|
**1. Install** alxia and this package:
|
|
17
34
|
|
|
@@ -68,7 +85,7 @@ With the `bunfig.toml` in place, the template's `dev`, `build` and
|
|
|
68
85
|
|
|
69
86
|
After a build, `bunx --bun vite preview` serves that built server through
|
|
70
87
|
Vite's preview server, as `bun run start` would: the pages, `/api`, the
|
|
71
|
-
client's files and the app's
|
|
88
|
+
client's files and the app's middlewares. So does React Router's prerendering.
|
|
72
89
|
|
|
73
90
|
`@alxia/core` and `react-router` 8 are peers, and `vite` 7 or 8 is an
|
|
74
91
|
optional peer, for `/vite`. The package declares no dependency.
|
|
@@ -91,8 +108,8 @@ commented, and the `Register` declaration. It refuses to overwrite a file
|
|
|
91
108
|
already there; `--force` overwrites it. Run it from the app's root, once
|
|
92
109
|
`@alxia/react-router` is installed.
|
|
93
110
|
|
|
94
|
-
The examples use `@alxia/logger` (`bun add @alxia/logger`); any
|
|
95
|
-
works the same way. The plugin picks the file up in dev and in the build:
|
|
111
|
+
The examples use `@alxia/logger` (`bun add @alxia/logger`); any middleware
|
|
112
|
+
works the same way (`use` the observers first). The Vite plugin picks the file up in dev and in the build:
|
|
96
113
|
|
|
97
114
|
```ts
|
|
98
115
|
// app/server.ts
|
|
@@ -145,9 +162,14 @@ export function loader({ context }: Route.LoaderArgs) {
|
|
|
145
162
|
How `alxiaOf(context)` is typed:
|
|
146
163
|
|
|
147
164
|
- with the `Register` declaration above, by that server: reading something
|
|
148
|
-
no
|
|
165
|
+
no middleware derives is a compile error;
|
|
149
166
|
- without it, `alxiaOf<typeof server>(context)` names the server;
|
|
150
|
-
- with neither, `
|
|
167
|
+
- with neither, by the base `@alxia/core`'s own `Register` names
|
|
168
|
+
(`context: typeof base`), when the app has one;
|
|
169
|
+
- with none of them, `alxiaOf(context)` is `BaseContext`.
|
|
170
|
+
|
|
171
|
+
This package's `Register` wins over core's: the server's app is the base
|
|
172
|
+
and all `configure` adds after it.
|
|
151
173
|
|
|
152
174
|
[More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#typing-the-loaders)
|
|
153
175
|
|
|
@@ -193,7 +215,7 @@ bunx react-router reveal entry.server
|
|
|
193
215
|
[readyOption]() {
|
|
194
216
|
```
|
|
195
217
|
|
|
196
|
-
`nonceOf` returns `undefined` when no
|
|
218
|
+
`nonceOf` returns `undefined` when no middleware set a nonce, so the entry works
|
|
197
219
|
with or without secure-headers: neither package depends on the other. Any
|
|
198
220
|
`derive` that returns a string `nonce` works the same.
|
|
199
221
|
[More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#a-csp-nonce)
|
|
@@ -220,7 +242,7 @@ export default createServer({
|
|
|
220
242
|
|
|
221
243
|
In dev, the plugin hands each upgrade Vite does not claim (its HMR, its
|
|
222
244
|
`server.proxy`) to the app, run by `Bun.serve` as `listen` runs it: the
|
|
223
|
-
|
|
245
|
+
middlewares before the route, a refusal's status, `socket.data`, `publish`. An
|
|
224
246
|
edit to the server is used from the next connection.
|
|
225
247
|
[More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#websockets)
|
|
226
248
|
|
|
@@ -248,7 +270,7 @@ client })` itself. [More](https://github.com/softistx/alxia/blob/develop/package
|
|
|
248
270
|
|
|
249
271
|
## The client's files
|
|
250
272
|
|
|
251
|
-
In a build, `build/client` is served before `configure`'s
|
|
273
|
+
In a build, `build/client` is served before `configure`'s middlewares:
|
|
252
274
|
|
|
253
275
|
| path | `Cache-Control` |
|
|
254
276
|
| --- | --- |
|
|
@@ -258,24 +280,32 @@ In a build, `build/client` is served before `configure`'s hooks:
|
|
|
258
280
|
A missing asset gets alxia's JSON 404, not a page. In dev, Vite serves
|
|
259
281
|
these files.
|
|
260
282
|
|
|
261
|
-
## Leaving the pages out of
|
|
283
|
+
## Leaving the pages out of `matchesSpec`
|
|
262
284
|
|
|
263
|
-
|
|
285
|
+
An API under `/api`, spec first, is checked against its OpenAPI document
|
|
286
|
+
by [`@alxia/openapi`](https://www.npmjs.com/package/@alxia/openapi)'s
|
|
287
|
+
`matchesSpec` (`bun add -d @alxia/openapi`), which reads `app.routes`. The
|
|
288
|
+
catch-all and the client's files are routes too, and no operation of the
|
|
289
|
+
document:
|
|
264
290
|
|
|
265
291
|
```ts
|
|
266
|
-
// app/server.ts
|
|
267
|
-
import {
|
|
268
|
-
import {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
292
|
+
// app/server.test.ts
|
|
293
|
+
import { test } from 'bun:test';
|
|
294
|
+
import { matchesSpec } from '@alxia/openapi';
|
|
295
|
+
import { isReactRouterRoute } from '@alxia/react-router';
|
|
296
|
+
import type { ServerBuild } from 'react-router';
|
|
297
|
+
import { operations } from './generated/alxia';
|
|
298
|
+
import server from './server';
|
|
299
|
+
|
|
300
|
+
test('the API routes every operation of openapi.yaml, and nothing else', async () => {
|
|
301
|
+
const build: ServerBuild = await import(new URL('../build/server/index.js', import.meta.url).href);
|
|
302
|
+
const app = server.create({ build });
|
|
303
|
+
matchesSpec(app, operations, { exclude: isReactRouterRoute });
|
|
273
304
|
});
|
|
274
305
|
```
|
|
275
306
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
files, so `@alxia/openapi` can leave them out.
|
|
307
|
+
`isReactRouterRoute` is true for each route this package declared, so
|
|
308
|
+
`matchesSpec` checks your own routes alone.
|
|
279
309
|
|
|
280
310
|
## Built for Bun
|
|
281
311
|
|
|
@@ -288,7 +318,9 @@ build, it adds:
|
|
|
288
318
|
bundled, and loaded in dev, as that variant;
|
|
289
319
|
- `bun` and `bun:*` to `resolve.builtins`: Bun's own modules stay imports
|
|
290
320
|
of `build/server/index.js`;
|
|
291
|
-
- `build.target: 'esnext'`, where Vite's default targets browsers
|
|
321
|
+
- `build.target: 'esnext'`, where Vite's default targets browsers;
|
|
322
|
+
- in the build only, `resolve.noExternal: true`: every package is bundled
|
|
323
|
+
into `build/server/index.js`, so `build/` runs with no `node_modules`.
|
|
292
324
|
|
|
293
325
|
```ts
|
|
294
326
|
// app/server.ts: Bun's modules, bundled as they are
|
|
@@ -308,22 +340,31 @@ export default createServer({
|
|
|
308
340
|
What the app sets wins: its own conditions and builtins are kept, `bun` is
|
|
309
341
|
added beside them, and a `build.target` it set is left as it is.
|
|
310
342
|
`ssr.target` stays `node`, and no polyfill is added; an app that sets
|
|
311
|
-
`ssr.target: 'webworker'` itself gets none of this, only Vite's defaults.
|
|
343
|
+
`ssr.target: 'webworker'` itself gets none of this, only Vite's defaults.
|
|
344
|
+
A package that cannot be bundled, a native addon, stays external when the
|
|
345
|
+
app names it, and every package does with `ssr.external: true`:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
// vite.config.ts: sharp imported from node_modules at runtime
|
|
349
|
+
export default defineConfig({
|
|
350
|
+
ssr: { external: ['sharp'] },
|
|
351
|
+
plugins: [reactRouter(), alxia()],
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The
|
|
312
356
|
[guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#built-for-bun)
|
|
313
357
|
has each option and why.
|
|
314
358
|
|
|
315
359
|
## Docker
|
|
316
360
|
|
|
317
|
-
A multi-stage `Dockerfile` on Bun's official
|
|
361
|
+
A multi-stage `Dockerfile` on Bun's official images, built on Debian's
|
|
362
|
+
and run on Alpine's, as `@alxia/create`'s
|
|
318
363
|
`react-router` template ships it. For an app from `create-react-router`,
|
|
319
|
-
it replaces the template's, which runs on Node
|
|
364
|
+
it replaces the template's, which runs on Node. The build is
|
|
365
|
+
self-contained, so the image holds `build/` alone, no `node_modules`:
|
|
320
366
|
|
|
321
367
|
```dockerfile
|
|
322
|
-
FROM oven/bun:1 AS production-dependencies
|
|
323
|
-
WORKDIR /app
|
|
324
|
-
COPY package.json bun.lock* bunfig.toml* ./
|
|
325
|
-
RUN bun install --frozen-lockfile --production
|
|
326
|
-
|
|
327
368
|
FROM oven/bun:1 AS build
|
|
328
369
|
WORKDIR /app
|
|
329
370
|
COPY package.json bun.lock* bunfig.toml* ./
|
|
@@ -331,15 +372,13 @@ RUN bun install --frozen-lockfile
|
|
|
331
372
|
COPY . .
|
|
332
373
|
RUN bun run build
|
|
333
374
|
|
|
334
|
-
FROM oven/bun:1
|
|
375
|
+
FROM oven/bun:1-alpine
|
|
335
376
|
WORKDIR /app
|
|
336
377
|
ENV NODE_ENV=production
|
|
337
|
-
COPY package.json ./
|
|
338
|
-
COPY --from=production-dependencies /app/node_modules ./node_modules
|
|
339
378
|
COPY --from=build /app/build ./build
|
|
340
379
|
USER bun
|
|
341
380
|
EXPOSE 3000
|
|
342
|
-
CMD ["bun", "build/server/index.js"]
|
|
381
|
+
CMD ["bun", "--no-install", "build/server/index.js"]
|
|
343
382
|
```
|
|
344
383
|
|
|
345
384
|
```sh
|
|
@@ -350,7 +389,7 @@ docker run -p 3000:3000 my-app
|
|
|
350
389
|
Keep the template's `.dockerignore`, and commit `bun.lock`. The server
|
|
351
390
|
runs as the non-root `bun` user, on `PORT` (3000). The
|
|
352
391
|
[guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#docker)
|
|
353
|
-
has the commented file.
|
|
392
|
+
has the commented file, and what to copy for a package left external.
|
|
354
393
|
|
|
355
394
|
## Traps
|
|
356
395
|
|
|
@@ -381,17 +420,17 @@ has the commented file.
|
|
|
381
420
|
| `ReactRouterServer<App>` | what it returns: `create(wiring)` makes the app, `start(app)` listens |
|
|
382
421
|
| `ServerWiring` | what `create` takes: `build`, `mode`, `client` |
|
|
383
422
|
| `FreshApp` | the app `beforeAll`, or `configure` without it, receives |
|
|
384
|
-
| `alxiaOf<App>(context)` | what alxia's
|
|
423
|
+
| `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` |
|
|
385
424
|
| `Register` | the interface to augment with `server: typeof server` |
|
|
386
|
-
| `RegisteredApp` | the app `alxiaOf` reads with no type argument |
|
|
387
|
-
| `RegisteredOf<R
|
|
425
|
+
| `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 |
|
|
426
|
+
| `RegisteredOf<R, Core?>` | the app a `Register`-shaped interface names: its server's, `InvalidRegister`, or with no server `Core`, by default core's `RegisteredBase` |
|
|
388
427
|
| `InvalidRegister` | what a `Register` naming neither a server nor an app reads as: every key of the app's own a compile error |
|
|
389
428
|
| `AppOf<Server>` | the app a server makes |
|
|
390
429
|
| `alxiaContext` | the React Router context key `alxiaOf` reads, set on every request |
|
|
391
|
-
| `nonceOf(context)` | the request's CSP nonce, for `entry.server.tsx`: the context's `nonce` when a
|
|
430
|
+
| `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` |
|
|
392
431
|
| `reactRouter(app, options)` | the catch-all and the client's files, for a server of your own. `build`, `mode`, `getLoadContext`, `client` |
|
|
393
432
|
| `ReactRouterOptions<Ctx>` | its options |
|
|
394
|
-
| `isReactRouterRoute(route)` | whether this package declared a route, for
|
|
433
|
+
| `isReactRouterRoute(route)` | whether this package declared a route, for `matchesSpec`'s `exclude` |
|
|
395
434
|
|
|
396
435
|
From `@alxia/react-router/vite`:
|
|
397
436
|
|
package/dist/cli/index.js
CHANGED
|
@@ -29,7 +29,7 @@ const server = createServer({
|
|
|
29
29
|
// limit, a logger that should see every request. Returns the app.
|
|
30
30
|
// beforeAll: (app) => app,
|
|
31
31
|
|
|
32
|
-
// The app the pages run behind: its
|
|
32
|
+
// The app the pages run behind: its middlewares, its plugins, its /api. What it
|
|
33
33
|
// builds is what alxiaOf(context) reads in the loaders. Returns the app.
|
|
34
34
|
// configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),
|
|
35
35
|
|
|
@@ -142,7 +142,7 @@ ${USAGE}`);
|
|
|
142
142
|
return 1;
|
|
143
143
|
}
|
|
144
144
|
output.out(`${NAME}: wrote ${result.written}, the server alxia() runs by default.
|
|
145
|
-
Next: uncomment configure in ${result.written} to add the app's
|
|
145
|
+
Next: uncomment configure in ${result.written} to add the app's middlewares and /api; bun run dev picks it up.`);
|
|
146
146
|
return 0;
|
|
147
147
|
} catch (error) {
|
|
148
148
|
output.err(error instanceof Error ? error.message : String(error));
|
|
@@ -156,5 +156,5 @@ process.exitCode = await main(process.argv.slice(2), process.cwd(), {
|
|
|
156
156
|
err: (line) => console.error(line)
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
-
//# debugId=
|
|
159
|
+
//# debugId=8582AE498438642864756E2164756E21
|
|
160
160
|
//# sourceMappingURL=index.js.map
|
package/dist/cli/index.js.map
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"sources": ["../src/cli/reveal.ts", "../src/cli/template.ts", "../src/cli/main.ts", "../src/cli/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * `alxia-react-router reveal`: where the server file goes, read cheaply from\n * the app's own config files, and writing it there.\n */\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport {\n\tbasename,\n\tdirname,\n\tisAbsolute,\n\tjoin,\n\trelative,\n\tresolve,\n} from 'node:path';\nimport { NAME } from '../vite/entry';\nimport { SERVER_FILE } from './template';\n\nconst EXTENSIONS = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'];\n\n/** `vite.config.ts`, or the first of its other extensions found in `root`. */\nfunction configFile(root: string, base: string): string | undefined {\n\treturn EXTENSIONS.map((extension) => join(root, `${base}${extension}`)).find(\n\t\t(file) => existsSync(file),\n\t);\n}\n\n/** A config file's source, without its comments: Bun's transpiler drops them. */\nasync function sourceOf(file: string): Promise<string> {\n\tconst loader = /\\.[cm]?ts$/.test(file) ? 'ts' : 'js';\n\treturn new Bun.Transpiler({ loader }).transformSync(\n\t\tawait readFile(file, 'utf8'),\n\t);\n}\n\n/**\n * The string literal given to `key` in `source`; `undefined` when `key` is\n * not there. A `key` given anything else is refused with `computed`.\n */\nfunction literal(\n\tsource: string,\n\t{ key, value, computed }: { key: RegExp; value: RegExp; computed: string },\n): string | undefined {\n\tconst found = source.match(value)?.[2];\n\tif (found !== undefined) return found;\n\tif (key.test(source)) throw new Error(computed);\n\treturn undefined;\n}\n\n/**\n * The file the plugin would load as the server: `alxia({ entry })`'s, or\n * `server.ts` in React Router's app directory, `app/` unless its config\n * names another. Each is read as a string literal; one computed is refused,\n * since reveal would write a file the plugin does not load.\n */\nexport async function revealTarget(root: string): Promise<string> {\n\tconst vite = configFile(root, 'vite.config');\n\tif (vite === undefined) {\n\t\tthrow new Error(\n\t\t\t`${NAME}: no vite.config.ts in ${root}. Run reveal from the app's root, beside vite.config.ts.`,\n\t\t);\n\t}\n\tconst entry = literal(await sourceOf(vite), {\n\t\tkey: /\\balxia\\(\\s*\\{[^}]*?\\bentry\\s*:/s,\n\t\tvalue: /\\balxia\\(\\s*\\{[^}]*?\\bentry\\s*:\\s*(['\"`])([^'\"`$]+)\\1/s,\n\t\tcomputed: `${NAME}: ${basename(vite)} gives alxia() an entry reveal cannot read. Write it as a string literal, alxia({ entry: 'app/server.ts' }), and run reveal again.`,\n\t});\n\tif (entry !== undefined) {\n\t\treturn isAbsolute(entry) ? entry : resolve(root, entry);\n\t}\n\tconst rr = configFile(root, 'react-router.config');\n\tconst app =\n\t\t(rr === undefined\n\t\t\t? undefined\n\t\t\t: literal(await sourceOf(rr), {\n\t\t\t\t\tkey: /\\bappDirectory\\s*:/,\n\t\t\t\t\tvalue: /\\bappDirectory\\s*:\\s*(['\"`])([^'\"`$]+)\\1/,\n\t\t\t\t\tcomputed: `${NAME}: ${basename(rr)} computes appDirectory, which reveal cannot read. Write it as a string literal, appDirectory: 'app', and run reveal again.`,\n\t\t\t\t})) ?? 'app';\n\treturn resolve(root, app, 'server.ts');\n}\n\nexport type Revealed =\n\t| { readonly written: string }\n\t| { readonly exists: string };\n\n/**\n * Writes the server file into the app at `root`, unless one is already\n * there and `force` is not set. The path comes back relative to `root`.\n */\nexport async function reveal(root: string, force: boolean): Promise<Revealed> {\n\tconst file = await revealTarget(root);\n\tconst label = relative(root, file);\n\tif (existsSync(file) && !force) return { exists: label };\n\tawait mkdir(dirname(file), { recursive: true });\n\tawait writeFile(file, SERVER_FILE);\n\treturn { written: label };\n}\n",
|
|
6
|
-
"/**\n * The server file `alxia-react-router reveal` writes: `createServer()` as\n * the default server runs it, its options commented, and the `Register`\n * declaration that types the loaders.\n */\n\n/** The file, as written. Every commented option compiles once uncommented. */\nexport const SERVER_FILE = `/**\n * The alxia server of this React Router app: what alxia() from\n * @alxia/react-router/vite runs when there is no server file, written out by\n * \\`alxia-react-router reveal\\`. Uncomment an option to customise it.\n */\nimport { createServer } from '@alxia/react-router';\n// import { userAgentContext } from './context'; // for getLoadContext, below\n\nconst server = createServer({\n\t// Runs first, on a new app, before the client's files too: a guard, a rate\n\t// limit, a logger that should see every request. Returns the app.\n\t// beforeAll: (app) => app,\n\n\t// The app the pages run behind: its
|
|
7
|
-
"/**\n * The `alxia-react-router` command: its arguments, what it prints, and its\n * exit code. `index.ts` runs it on the process's own.\n */\nimport { NAME } from '../vite/entry';\nimport { reveal } from './reveal';\n\nexport const USAGE = `Usage: ${NAME} <command> [options]\n\nCommands:\n reveal [--force] Write the server alxia() runs by default to app/server.ts,\n or to alxia({ entry })'s file, to customise it. --force\n overwrites a file already there.\n\nOptions:\n --help Show this help.`;\n\n/** Where the command prints: the process's streams, or a spec's. */\nexport interface Output {\n\treadonly out: (line: string) => void;\n\treadonly err: (line: string) => void;\n}\n\n/** Runs the command in `root`; resolves to the process's exit code. */\nexport async function main(\n\targs: readonly string[],\n\troot: string,\n\toutput: Output,\n): Promise<number> {\n\tconst [command, ...options] = args;\n\tif (command === undefined) {\n\t\toutput.err(USAGE);\n\t\treturn 1;\n\t}\n\tif (command === '--help' || command === '-h') {\n\t\toutput.out(USAGE);\n\t\treturn 0;\n\t}\n\tif (command !== 'reveal') {\n\t\toutput.err(`${NAME}: unknown command ${command}.\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\tif (options.includes('--help') || options.includes('-h')) {\n\t\toutput.out(USAGE);\n\t\treturn 0;\n\t}\n\tconst unknown = options.find((option) => option !== '--force');\n\tif (unknown !== undefined) {\n\t\toutput.err(`${NAME}: unknown option ${unknown} for reveal.\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\ttry {\n\t\tconst result = await reveal(root, options.includes('--force'));\n\t\tif ('exists' in result) {\n\t\t\toutput.err(\n\t\t\t\t`${NAME}: ${result.exists} already exists, and reveal leaves it as it is. Run ${NAME} reveal --force to overwrite it.`,\n\t\t\t);\n\t\t\treturn 1;\n\t\t}\n\t\toutput.out(\n\t\t\t`${NAME}: wrote ${result.written}, the server alxia() runs by default.\\nNext: uncomment configure in ${result.written} to add the app's
|
|
6
|
+
"/**\n * The server file `alxia-react-router reveal` writes: `createServer()` as\n * the default server runs it, its options commented, and the `Register`\n * declaration that types the loaders.\n */\n\n/** The file, as written. Every commented option compiles once uncommented. */\nexport const SERVER_FILE = `/**\n * The alxia server of this React Router app: what alxia() from\n * @alxia/react-router/vite runs when there is no server file, written out by\n * \\`alxia-react-router reveal\\`. Uncomment an option to customise it.\n */\nimport { createServer } from '@alxia/react-router';\n// import { userAgentContext } from './context'; // for getLoadContext, below\n\nconst server = createServer({\n\t// Runs first, on a new app, before the client's files too: a guard, a rate\n\t// limit, a logger that should see every request. Returns the app.\n\t// beforeAll: (app) => app,\n\n\t// The app the pages run behind: its middlewares, its plugins, its /api. What it\n\t// builds is what alxiaOf(context) reads in the loaders. Returns the app.\n\t// configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n\n\t// Sets the app's own React Router context keys, ctx typed by configure's\n\t// app. A key made with createContext() from react-router, in app/context.ts.\n\t// getLoadContext: (ctx, context) => {\n\t// \tcontext.set(userAgentContext, ctx.request.headers.get('user-agent'));\n\t// },\n});\n\nexport default server;\n\n// What alxiaOf(context) reads in the loaders, with no type argument.\ndeclare module '@alxia/react-router' {\n\tinterface Register {\n\t\tserver: typeof server;\n\t}\n}\n`;\n",
|
|
7
|
+
"/**\n * The `alxia-react-router` command: its arguments, what it prints, and its\n * exit code. `index.ts` runs it on the process's own.\n */\nimport { NAME } from '../vite/entry';\nimport { reveal } from './reveal';\n\nexport const USAGE = `Usage: ${NAME} <command> [options]\n\nCommands:\n reveal [--force] Write the server alxia() runs by default to app/server.ts,\n or to alxia({ entry })'s file, to customise it. --force\n overwrites a file already there.\n\nOptions:\n --help Show this help.`;\n\n/** Where the command prints: the process's streams, or a spec's. */\nexport interface Output {\n\treadonly out: (line: string) => void;\n\treadonly err: (line: string) => void;\n}\n\n/** Runs the command in `root`; resolves to the process's exit code. */\nexport async function main(\n\targs: readonly string[],\n\troot: string,\n\toutput: Output,\n): Promise<number> {\n\tconst [command, ...options] = args;\n\tif (command === undefined) {\n\t\toutput.err(USAGE);\n\t\treturn 1;\n\t}\n\tif (command === '--help' || command === '-h') {\n\t\toutput.out(USAGE);\n\t\treturn 0;\n\t}\n\tif (command !== 'reveal') {\n\t\toutput.err(`${NAME}: unknown command ${command}.\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\tif (options.includes('--help') || options.includes('-h')) {\n\t\toutput.out(USAGE);\n\t\treturn 0;\n\t}\n\tconst unknown = options.find((option) => option !== '--force');\n\tif (unknown !== undefined) {\n\t\toutput.err(`${NAME}: unknown option ${unknown} for reveal.\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\ttry {\n\t\tconst result = await reveal(root, options.includes('--force'));\n\t\tif ('exists' in result) {\n\t\t\toutput.err(\n\t\t\t\t`${NAME}: ${result.exists} already exists, and reveal leaves it as it is. Run ${NAME} reveal --force to overwrite it.`,\n\t\t\t);\n\t\t\treturn 1;\n\t\t}\n\t\toutput.out(\n\t\t\t`${NAME}: wrote ${result.written}, the server alxia() runs by default.\\nNext: uncomment configure in ${result.written} to add the app's middlewares and /api; bun run dev picks it up.`,\n\t\t);\n\t\treturn 0;\n\t} catch (error) {\n\t\toutput.err(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t}\n}\n",
|
|
8
8
|
"#!/usr/bin/env bun\n/**\n * `alxia-react-router`, the package's bin: `bunx alxia-react-router reveal`\n * writes the default server into the app, to customise it.\n */\nimport { main } from './main';\n\nprocess.exitCode = await main(process.argv.slice(2), process.cwd(), {\n\tout: (line) => console.log(line),\n\terr: (line) => console.error(line),\n});\n"
|
|
9
9
|
],
|
|
10
|
-
"mappings": "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
|
|
11
|
-
"debugId": "
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "8582AE498438642864756E2164756E21",
|
|
12
12
|
"names": []
|
|
13
13
|
}
|
package/dist/cli/template.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* declaration that types the loaders.
|
|
5
5
|
*/
|
|
6
6
|
/** The file, as written. Every commented option compiles once uncommented. */
|
|
7
|
-
export declare const SERVER_FILE = "/**\n * The alxia server of this React Router app: what alxia() from\n * @alxia/react-router/vite runs when there is no server file, written out by\n * `alxia-react-router reveal`. Uncomment an option to customise it.\n */\nimport { createServer } from '@alxia/react-router';\n// import { userAgentContext } from './context'; // for getLoadContext, below\n\nconst server = createServer({\n\t// Runs first, on a new app, before the client's files too: a guard, a rate\n\t// limit, a logger that should see every request. Returns the app.\n\t// beforeAll: (app) => app,\n\n\t// The app the pages run behind: its
|
|
7
|
+
export declare const SERVER_FILE = "/**\n * The alxia server of this React Router app: what alxia() from\n * @alxia/react-router/vite runs when there is no server file, written out by\n * `alxia-react-router reveal`. Uncomment an option to customise it.\n */\nimport { createServer } from '@alxia/react-router';\n// import { userAgentContext } from './context'; // for getLoadContext, below\n\nconst server = createServer({\n\t// Runs first, on a new app, before the client's files too: a guard, a rate\n\t// limit, a logger that should see every request. Returns the app.\n\t// beforeAll: (app) => app,\n\n\t// The app the pages run behind: its middlewares, its plugins, its /api. What it\n\t// builds is what alxiaOf(context) reads in the loaders. Returns the app.\n\t// configure: (app) => app.get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n\n\t// Sets the app's own React Router context keys, ctx typed by configure's\n\t// app. A key made with createContext() from react-router, in app/context.ts.\n\t// getLoadContext: (ctx, context) => {\n\t// \tcontext.set(userAgentContext, ctx.request.headers.get('user-agent'));\n\t// },\n});\n\nexport default server;\n\n// What alxiaOf(context) reads in the loaders, with no type argument.\ndeclare module '@alxia/react-router' {\n\tinterface Register {\n\t\tserver: typeof server;\n\t}\n}\n";
|
|
8
8
|
//# sourceMappingURL=template.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/cli/template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/cli/template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,WAAW,uyCAgCvB,CAAC"}
|
package/dist/context.d.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* The key under which `reactRouter()` hands every loader, action and
|
|
3
3
|
* middleware the request's alxia context, and the typed way to read it.
|
|
4
4
|
*/
|
|
5
|
-
import type { Alxia, AnyAlxia, ContextOf,
|
|
5
|
+
import type { Alxia, AnyAlxia, ContextOf, RegisteredBase } from '@alxia/core';
|
|
6
6
|
import { type RouterContextProvider } from 'react-router';
|
|
7
|
-
import type {
|
|
7
|
+
import type { ReactRouterServer } from './server';
|
|
8
8
|
/**
|
|
9
9
|
* The key `reactRouter()` sets on React Router's context provider, on every
|
|
10
|
-
* request, to what alxia's
|
|
10
|
+
* request, to what alxia's middlewares built for it. It lives in this package, so
|
|
11
11
|
* it is one object whichever way the server was built or loaded: a key made
|
|
12
12
|
* in the app's own `app/` folder is copied into React Router's build, and
|
|
13
13
|
* the server that imports it separately sets a different one.
|
|
@@ -30,7 +30,10 @@ export declare const alxiaContext: import("react-router").RouterContext<unknown>
|
|
|
30
30
|
* }
|
|
31
31
|
* ```
|
|
32
32
|
*
|
|
33
|
-
* Unregistered, `alxiaOf(context)` reads `
|
|
33
|
+
* Unregistered here, `alxiaOf(context)` reads the app `@alxia/core`'s own
|
|
34
|
+
* `Register` names as its `context`, and `BaseContext` when neither is
|
|
35
|
+
* declared. When both are, this one wins: the server's app is the whole
|
|
36
|
+
* app the pages run behind, the base's context and all `configure` adds.
|
|
34
37
|
*/
|
|
35
38
|
export interface Register {
|
|
36
39
|
}
|
|
@@ -43,18 +46,23 @@ export type AppOf<Server> = Server extends ReactRouterServer<infer App> ? App :
|
|
|
43
46
|
*/
|
|
44
47
|
export type InvalidRegister = Alxia<{
|
|
45
48
|
readonly 'Register.server must be typeof server, the default export of createServer()': never;
|
|
46
|
-
},
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
+
}, '', never>;
|
|
50
|
+
/**
|
|
51
|
+
* The app `alxiaOf` reads for a `Register` interface: its server's, or
|
|
52
|
+
* `InvalidRegister`; with no server, `Core`, the app `@alxia/core`'s
|
|
53
|
+
* `Register` names (a fresh one when it names none).
|
|
54
|
+
*/
|
|
55
|
+
export type RegisteredOf<R, Core extends AnyAlxia = RegisteredBase> = R extends {
|
|
49
56
|
readonly server: infer Server;
|
|
50
|
-
} ? Server extends AnyAlxia | ReactRouterServer<AnyAlxia> ? AppOf<Server> : InvalidRegister :
|
|
51
|
-
/** What `alxiaOf` reads with no type argument: the registered server's app,
|
|
57
|
+
} ? Server extends AnyAlxia | ReactRouterServer<AnyAlxia> ? AppOf<Server> : InvalidRegister : Core;
|
|
58
|
+
/** What `alxiaOf` reads with no type argument: the registered server's app, else core's registered app, else a fresh one. */
|
|
52
59
|
export type RegisteredApp = RegisteredOf<Register>;
|
|
53
60
|
/**
|
|
54
|
-
* What alxia's
|
|
61
|
+
* What alxia's middlewares built for this request, read in a loader, an action or
|
|
55
62
|
* a middleware, typed by the app: the server `Register` names, or the one
|
|
56
63
|
* given as the type argument — `typeof server`, or an app *before* the
|
|
57
|
-
* catch-all. With neither, `
|
|
64
|
+
* catch-all. With neither, the app `@alxia/core`'s `Register` names, and
|
|
65
|
+
* `BaseContext` when nothing is registered.
|
|
58
66
|
*
|
|
59
67
|
* ```ts
|
|
60
68
|
* export async function loader({ context }: Route.LoaderArgs) {
|
|
@@ -66,5 +74,8 @@ export type RegisteredApp = RegisteredOf<Register>;
|
|
|
66
74
|
* `react-router dev` without the plugin, or a test that calls a loader
|
|
67
75
|
* directly.
|
|
68
76
|
*/
|
|
69
|
-
export declare function alxiaOf<App extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp>(context: Readonly<RouterContextProvider>): ContextOf<AppOf<App
|
|
77
|
+
export declare function alxiaOf<App extends AnyAlxia | ReactRouterServer<AnyAlxia> = RegisteredApp>(context: Readonly<RouterContextProvider>): ContextOf<AppOf<App>> & {
|
|
78
|
+
/** The catch-all's route, which every request reaching React Router matched. */
|
|
79
|
+
readonly route: string;
|
|
80
|
+
};
|
|
70
81
|
//# sourceMappingURL=context.d.ts.map
|
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,
|
|
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,EACF,KAAK,CACL,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,YAAY,CACvB,CAAC,EACD,IAAI,SAAS,QAAQ,GAAG,cAAc,IACnC,CAAC,SAAS;IACb,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC9B,GACE,MAAM,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GACpD,KAAK,CAAC,MAAM,CAAC,GACb,eAAe,GAChB,IAAI,CAAC;AAER,6HAA6H;AAC7H,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CACtB,GAAG,SAAS,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,GAAG,aAAa,EAElE,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GACtC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB,CAQA"}
|
package/dist/index.js.map
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/context.ts", "../src/nonce.ts", "../src/react-router.ts", "../src/assets.ts", "../src/server.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * The key under which `reactRouter()` hands every loader, action and\n * middleware the request's alxia context, and the typed way to read it.\n */\nimport type { Alxia, AnyAlxia, ContextOf,
|
|
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\tnever\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
6
|
"/** `nonceOf`: the request's CSP nonce, read from alxia's context if a hook set one. */\nimport type { RouterContextProvider } from 'react-router';\nimport { alxiaContext } from './context';\n\n/**\n * This request's CSP nonce, for `entry.server.tsx`: the `nonce` alxia's\n * hooks put on the context — `secureHeaders({ nonce: true })` from\n * `@alxia/secure-headers`, or a `derive` of your own — or `undefined` when\n * none did, or when the request did not come through alxia. It reads the\n * key if present: neither package depends on the other.\n *\n * ```tsx\n * const nonce = nonceOf(loadContext);\n * <ServerRouter context={routerContext} url={request.url} nonce={nonce} />\n * ```\n */\nexport function nonceOf(\n\tcontext: Readonly<RouterContextProvider>,\n): string | undefined {\n\tconst value = context.get(alxiaContext);\n\tif (typeof value !== 'object' || value === null || !('nonce' in value)) {\n\t\treturn undefined;\n\t}\n\treturn typeof value.nonce === 'string' ? value.nonce : undefined;\n}\n",
|
|
7
|
-
"import { fileURLToPath } from 'node:url';\nimport type {\n\tAlxia,\n\tAnyAlxia,\n\tAnyReply,\n\tBaseContext,\n\tMaybePromise,\n\tRouteDefinition,\n\tStatusCode,\n} from '@alxia/core';\nimport {\n\tcreateRequestHandler,\n\ttype RequestHandler,\n\tRouterContextProvider,\n\ttype ServerBuild,\n} from 'react-router';\nimport { serveClient } from './assets';\nimport { alxiaContext } from './context';\n\nexport interface ReactRouterOptions<Ctx> {\n\t/**\n\t * React Router's server build: the module itself, or a function that\n\t * returns it — `() => import('virtual:react-router/server-build')` in a\n\t * server entry Vite builds, `() => import('./build/server/index.js')`\n\t * beside a build. A function is called on every request in\n\t * `development`, so an edit is picked up, and once in `production`.\n\t */\n\treadonly build: ServerBuild | (() => MaybePromise<ServerBuild>);\n\t/** React Router's server mode: `production` by default. */\n\treadonly mode?: 'development' | 'production';\n\t/**\n\t * Sets the app's own keys on React Router's context provider, from the\n\t * context alxia's
|
|
7
|
+
"import { fileURLToPath } from 'node:url';\nimport type {\n\tAlxia,\n\tAnyAlxia,\n\tAnyReply,\n\tBaseContext,\n\tMaybePromise,\n\tRouteDefinition,\n\tStatusCode,\n} from '@alxia/core';\nimport {\n\tcreateRequestHandler,\n\ttype RequestHandler,\n\tRouterContextProvider,\n\ttype ServerBuild,\n} from 'react-router';\nimport { serveClient } from './assets';\nimport { alxiaContext } from './context';\n\nexport interface ReactRouterOptions<Ctx> {\n\t/**\n\t * React Router's server build: the module itself, or a function that\n\t * returns it — `() => import('virtual:react-router/server-build')` in a\n\t * server entry Vite builds, `() => import('./build/server/index.js')`\n\t * beside a build. A function is called on every request in\n\t * `development`, so an edit is picked up, and once in `production`.\n\t */\n\treadonly build: ServerBuild | (() => MaybePromise<ServerBuild>);\n\t/** React Router's server mode: `production` by default. */\n\treadonly mode?: 'development' | 'production';\n\t/**\n\t * Sets the app's own keys on React Router's context provider, from the\n\t * context alxia's 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<\n\tCtx extends object,\n\tPrefix extends string,\n\tShortcuts extends AnyReply,\n>(\n\tapp: Alxia<Ctx, Prefix, Shortcuts>,\n\toptions: ReactRouterOptions<Ctx>,\n): Alxia<Ctx, Prefix, Shortcuts> {\n\tconst mode = options.mode ?? 'production';\n\tconst handle = requestHandler(options.build, mode);\n\tconst getLoadContext = options.getLoadContext;\n\tconst handler = async (ctx: BaseContext & Ctx) => {\n\t\tconst context = new RouterContextProvider();\n\t\tcontext.set(alxiaContext, ctx);\n\t\tawait getLoadContext?.(ctx, context);\n\t\t// React Router answers a HEAD with no headers at all: hand it the GET,\n\t\t// and the core drops the body.\n\t\tconst request =\n\t\t\tctx.request.method === 'HEAD'\n\t\t\t\t? new Request(ctx.request, { method: 'GET' })\n\t\t\t\t: ctx.request;\n\t\tconst response = await handle(request, context);\n\t\treturn ctx.reply(\n\t\t\tresponse.status as StatusCode,\n\t\t\tresponse.body ?? undefined,\n\t\t\t{ headers: response.headers },\n\t\t);\n\t};\n\n\tconst routes = asRoutes(app);\n\tdeclaring(app, () => {\n\t\tif (options.client !== undefined && mode === 'production') {\n\t\t\tserveClient(routes, pathOf(options.client));\n\t\t}\n\t\tfor (const method of METHODS) routes[method]('/*', handler);\n\t});\n\treturn app;\n}\n\n/**\n * Declares the client build's files on `app`, marked for\n * `isReactRouterRoute`: what `reactRouter()` does with `client`, apart, so\n * that `createServer()` serves them before the middlewares of `configure`.\n */\nexport function declareClient(app: AnyAlxia, client: string | URL): void {\n\tdeclaring(app, () => serveClient(asRoutes(app), pathOf(client)));\n}\n\n/** Runs `declare`, and marks every route it added to `app` as ours. */\nfunction declaring(app: AnyAlxia, declare: () => void): void {\n\tconst before = new Set(app.routes);\n\tdeclare();\n\tfor (const route of app.routes) {\n\t\tif (!before.has(route)) declared.add(route.handler);\n\t}\n}\n\n/** The app's route methods, untyped: the catch-all's handler is not one a route type admits. */\nfunction asRoutes(app: AnyAlxia) {\n\treturn app as unknown as {\n\t\tstatic(path: string, source: string, options: object): unknown;\n\t\tfile(path: string, file: string, options: object): unknown;\n\t} & Record<\n\t\t(typeof METHODS)[number],\n\t\t(path: string, handler: unknown) => unknown\n\t>;\n}\n\n/** A handler that resolves a function build once in production, on every request in development. */\nfunction requestHandler(\n\tbuild: ReactRouterOptions<never>['build'],\n\tmode: 'development' | 'production',\n): RequestHandler {\n\tif (typeof build !== 'function' || mode === 'development') {\n\t\treturn createRequestHandler(build, mode);\n\t}\n\tlet resolved: Promise<RequestHandler> | undefined;\n\treturn async (request, context) => {\n\t\tresolved ??= Promise.resolve(build()).then(\n\t\t\t(server) => createRequestHandler(server, mode),\n\t\t\t(error: unknown) => {\n\t\t\t\tresolved = undefined;\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\treturn (await resolved)(request, context);\n\t};\n}\n\nfunction pathOf(client: string | URL): string {\n\treturn client instanceof URL ? fileURLToPath(client) : client;\n}\n",
|
|
8
8
|
"/**\n * The client build's files, served before the catch-all: the hashed\n * `assets/` immutable, every other top-level file or folder — what React\n * Router copied from `public/` — with an hour's cache.\n */\nimport { readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** A hashed asset never changes under its name. */\nexport const IMMUTABLE = 'public, max-age=31536000, immutable';\n/** A public file keeps its name across builds: revalidated after an hour. */\nexport const AN_HOUR = 'public, max-age=3600';\n\ninterface Serving {\n\tstatic(path: string, source: string, options: object): unknown;\n\tfile(path: string, file: string, options: object): unknown;\n}\n\n/** Declares the client build's routes on `app`. */\nexport function serveClient(app: Serving, client: string): void {\n\tif (!isDirectory(client)) {\n\t\tthrow new TypeError(\n\t\t\t`reactRouter(): client is ${client}, which is not a directory. Pass the client build, build/client by default.`,\n\t\t);\n\t}\n\tfor (const name of readdirSync(client).sort()) {\n\t\tif (name.startsWith('.')) continue;\n\t\tconst path = join(client, name);\n\t\tconst cacheControl = name === 'assets' ? IMMUTABLE : AN_HOUR;\n\t\t// Declared as a request's URL carries the name: `my file.pdf` is\n\t\t// `/my%20file.pdf`, `café.png` is `/caf%C3%A9.png`.\n\t\tconst route = new URL(`/${name}`, 'http://localhost').pathname;\n\t\ttry {\n\t\t\tif (isDirectory(path)) app.static(route, path, { cacheControl });\n\t\t\telse app.file(route, path, { cacheControl });\n\t\t} catch (error) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`reactRouter(): ${path} cannot be served at a path of its own name; rename it. ${(error as Error).message}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction isDirectory(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isDirectory();\n\t} catch {\n\t\treturn false;\n\t}\n}\n",
|
|
9
|
-
"/**\n * `createServer()`: the alxia server of a React Router app, as\n * `@alxia/react-router/vite` builds it. The plugin hands it React Router's\n * build, the mode and the client folder; the app's own `app/server.ts`\n * customises the rest, or is not written at all.\n */\nimport {\n\ttype Alxia,\n\ttype AnyAlxia,\n\talxia,\n\ttype ContextOf,\n\ttype Empty,\n\ttype ListenOptions,\n\ttype MaybePromise,\n} from '@alxia/core';\nimport type { RouterContextProvider, ServerBuild } from 'react-router';\nimport { declareClient, reactRouter } from './react-router';\n\n/** An app as `alxia()` makes it: what `beforeAll`, or `configure`, receives. */\nexport type FreshApp = Alxia<Empty,
|
|
9
|
+
"/**\n * `createServer()`: the alxia server of a React Router app, as\n * `@alxia/react-router/vite` builds it. The plugin hands it React Router's\n * build, the mode and the client folder; the app's own `app/server.ts`\n * customises the rest, or is not written at all.\n */\nimport {\n\ttype Alxia,\n\ttype AnyAlxia,\n\talxia,\n\ttype ContextOf,\n\ttype Empty,\n\ttype ListenOptions,\n\ttype MaybePromise,\n} from '@alxia/core';\nimport type { RouterContextProvider, ServerBuild } from 'react-router';\nimport { declareClient, reactRouter } from './react-router';\n\n/** An app as `alxia()` makes it: what `beforeAll`, or `configure`, receives. */\nexport type FreshApp = Alxia<Empty, '', never>;\n\n/**\n * What the Vite plugin hands the server, and a test passes to `create`:\n * React Router's server build, its mode, and the client build's folder.\n */\nexport interface ServerWiring {\n\t/**\n\t * React Router's server build, or a function that returns it: called on\n\t * every request in `development`, once in `production`.\n\t */\n\treadonly build: ServerBuild | (() => MaybePromise<ServerBuild>);\n\t/** `development` under `react-router dev`, `production` in a build, and by default. */\n\treadonly mode?: 'development' | 'production';\n\t/** The client build's folder, a path or a `file:` URL: served in `production`. */\n\treadonly client?: string | URL | undefined;\n}\n\nexport interface ServerOptions<Before extends AnyAlxia, App extends AnyAlxia> {\n\t/**\n\t * Runs first, on a new app: what is declared here runs before the\n\t * client's files too — a guard, a rate limit, a logger that should see\n\t * every request. Returns the app, so its types flow to `configure`.\n\t */\n\treadonly beforeAll?: (app: FreshApp) => Before;\n\t/**\n\t * The app the pages run behind: its middlewares, its plugins, its `/api`.\n\t * Declared after the client's files and before the catch-all. Returns\n\t * the app: what it builds is what the loaders read through `alxiaOf`.\n\t */\n\treadonly configure?: (app: Before) => App;\n\t/**\n\t * Sets the app's own keys on React Router's context provider, `ctx`\n\t * typed by `configure`'s app. `alxiaContext` is always set.\n\t */\n\treadonly getLoadContext?: (\n\t\tctx: ContextOf<App>,\n\t\tcontext: RouterContextProvider,\n\t) => MaybePromise<void>;\n\t/** Overrides the plugin's server build: rarely wanted. */\n\treadonly build?: ServerWiring['build'];\n\t/** Overrides the plugin's mode, `development` in dev and `production` in a build. */\n\treadonly mode?: 'development' | 'production';\n\t/**\n\t * Overrides the plugin's client folder, `build/client` beside the built\n\t * server. `false` serves none of it: declare the files yourself, in\n\t * `beforeAll` or `configure`.\n\t */\n\treadonly client?: string | URL | false;\n\t/**\n\t * `listen`'s options for `bun build/server/index.js`; a `port` or `hostname` here wins over `PORT` (3000)\n\t * and `HOST` (`0.0.0.0`) from the environment.\n\t */\n\treadonly listen?: ListenOptions;\n\t/**\n\t * Called once the built server listens and its `SIGINT` and `SIGTERM` handlers are in place.\n\t * Prints `alxia listening on <url>` by default.\n\t */\n\treadonly onListen?: (server: Bun.Server<unknown>) => void;\n}\n\n/**\n * The server `createServer()` describes, made into an app by the Vite\n * plugin, by a test, or by a server file of your own.\n */\nexport interface ReactRouterServer<App extends AnyAlxia> {\n\t/**\n\t * The app, serving React Router's build: `build/server/index.js`'s\n\t * default export. A test drives it with `app.request`.\n\t *\n\t * ```ts\n\t * const app = server.create({ build: await import('./build/server/index.js') });\n\t * ```\n\t */\n\tcreate(wiring: ServerWiring): App;\n\t/**\n\t * Listens with `app`, on `listen`, `PORT` and `HOST`, and stops it on\n\t * `SIGINT` or `SIGTERM`: its `onStop` hooks run, and the process exits.\n\t * What `bun build/server/index.js` runs.\n\t */\n\tstart(app: App): Bun.Server<unknown>;\n}\n\n/**\n * The alxia server of a React Router app, for `app/server.ts`. Every option\n * is optional: the Vite plugin wires React Router's build, the mode and the\n * client folder, and without the file uses `createServer()` as it is.\n *\n * ```ts\n * const server = createServer({\n * configure: (app) => app.use(logger()).get('/api/health', ({ reply }) => reply.ok({ ok: true })),\n * });\n * export default server;\n * ```\n *\n * The request runs through `beforeAll`, the client's files, `configure`,\n * then the pages.\n */\nexport function createServer<\n\tBefore extends AnyAlxia = FreshApp,\n\tApp extends AnyAlxia = Before,\n>(options: ServerOptions<Before, App> = {}): ReactRouterServer<App> {\n\treturn {\n\t\tcreate(wiring) {\n\t\t\tconst mode = options.mode ?? wiring.mode ?? 'production';\n\t\t\tconst client =\n\t\t\t\toptions.client === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: (options.client ?? wiring.client);\n\t\t\tconst fresh = alxia();\n\t\t\t// Without beforeAll or configure, Before and App are their defaults,\n\t\t\t// the app passed through: a type argument given by hand is believed.\n\t\t\tconst before = (options.beforeAll?.(fresh) ?? fresh) as Before;\n\t\t\tif (client !== undefined && mode === 'production') {\n\t\t\t\tdeclareClient(before, client);\n\t\t\t}\n\t\t\tconst app = (options.configure?.(before) ?? before) as App;\n\t\t\tconst getLoadContext = options.getLoadContext;\n\t\t\treactRouter(app, {\n\t\t\t\tbuild: options.build ?? wiring.build,\n\t\t\t\tmode,\n\t\t\t\t...(getLoadContext === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tgetLoadContext: (ctx, context) =>\n\t\t\t\t\t\t\t\tgetLoadContext(ctx as ContextOf<App>, context),\n\t\t\t\t\t\t}),\n\t\t\t});\n\t\t\treturn app;\n\t\t},\n\t\tstart(app) {\n\t\t\tconst server = app.listen({\n\t\t\t\tport: Number(process.env['PORT'] || 3000),\n\t\t\t\thostname: process.env['HOST'] || '0.0.0.0',\n\t\t\t\t...options.listen,\n\t\t\t});\n\t\t\t// Stop as the platform asks: the app's onStop hooks run, and the process ends.\n\t\t\t// Installed before onListen: a supervisor may signal as soon as it reads\n\t\t\t// that the server listens, and a signal with no handler yet kills the process.\n\t\t\tfor (const signal of ['SIGINT', 'SIGTERM'] as const) {\n\t\t\t\tprocess.once(signal, () => {\n\t\t\t\t\tvoid app.stop().then(\n\t\t\t\t\t\t() => process.exit(0),\n\t\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\t\tconsole.error(error);\n\t\t\t\t\t\t\tprocess.exit(1);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t\t(options.onListen ?? announce)(server);\n\t\t\treturn server;\n\t\t},\n\t};\n}\n\nfunction announce(server: Bun.Server<unknown>): void {\n\tconsole.log(`alxia listening on ${server.url}`);\n}\n"
|
|
10
10
|
],
|
|
11
|
-
"mappings": ";AAKA;AAIA,IAAM,UAAyB,OAAO,uBAAuB;AAStD,IAAM,eAAe,cAAuB,OAAO;
|
|
11
|
+
"mappings": "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",
|
|
12
12
|
"debugId": "63BB3B2311BA25F964756E2164756E21",
|
|
13
13
|
"names": []
|
|
14
14
|
}
|
package/dist/react-router.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export interface ReactRouterOptions<Ctx> {
|
|
|
13
13
|
readonly mode?: 'development' | 'production';
|
|
14
14
|
/**
|
|
15
15
|
* Sets the app's own keys on React Router's context provider, from the
|
|
16
|
-
* context alxia's
|
|
17
|
-
* `
|
|
16
|
+
* context alxia's middlewares built. `ctx` is typed by the app at the point of
|
|
17
|
+
* `app.plugin`: reading what nothing before it adds is a compile error.
|
|
18
18
|
* `alxiaContext` is always set, whether or not this is given.
|
|
19
19
|
*/
|
|
20
20
|
readonly getLoadContext?: (ctx: BaseContext & Ctx, context: RouterContextProvider) => MaybePromise<void>;
|
|
@@ -28,34 +28,34 @@ export interface ReactRouterOptions<Ctx> {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Whether `reactRouter()` declared this route: the catch-all, or one of the
|
|
31
|
-
* client build's files. For `@alxia/openapi`'s `
|
|
32
|
-
*
|
|
31
|
+
* client build's files. For `@alxia/openapi`'s `matchesSpec`, whose
|
|
32
|
+
* `exclude` leaves them out, so the app's routes are checked against the
|
|
33
|
+
* operations of its OpenAPI document and its pages are not.
|
|
33
34
|
*
|
|
34
35
|
* ```ts
|
|
35
|
-
*
|
|
36
|
+
* matchesSpec(app, operations, { exclude: isReactRouterRoute });
|
|
36
37
|
* ```
|
|
37
38
|
*/
|
|
38
39
|
export declare function isReactRouterRoute(route: RouteDefinition): boolean;
|
|
39
40
|
/**
|
|
40
41
|
* A React Router framework app, server rendered on `app`: `GET`, `POST`,
|
|
41
|
-
* `PUT`, `PATCH` and `DELETE` at `/*`, behind every
|
|
42
|
-
* before it. Each loader, action and middleware reads what those
|
|
43
|
-
*
|
|
42
|
+
* `PUT`, `PATCH` and `DELETE` at `/*`, behind every middleware declared on `app`
|
|
43
|
+
* before it. Each loader, action and middleware reads what those added
|
|
44
|
+
* through `alxiaOf<App>(context)`. The app's own routes — an `/api`
|
|
44
45
|
* — answer their paths, declared before it or after; the client build's
|
|
45
|
-
* files are served when `client` is given.
|
|
46
|
-
* the app's route table: pages are not something the typed client calls.
|
|
46
|
+
* files are served when `client` is given.
|
|
47
47
|
*
|
|
48
48
|
* ```ts
|
|
49
|
-
* const app = base.
|
|
49
|
+
* const app = base.plugin((app) =>
|
|
50
50
|
* reactRouter(app, { build: () => import('./build/server/index.js'), client: 'build/client' }),
|
|
51
51
|
* );
|
|
52
52
|
* ```
|
|
53
53
|
*/
|
|
54
|
-
export declare function reactRouter<Ctx extends object,
|
|
54
|
+
export declare function reactRouter<Ctx extends object, Prefix extends string, Shortcuts extends AnyReply>(app: Alxia<Ctx, Prefix, Shortcuts>, options: ReactRouterOptions<Ctx>): Alxia<Ctx, Prefix, Shortcuts>;
|
|
55
55
|
/**
|
|
56
56
|
* Declares the client build's files on `app`, marked for
|
|
57
57
|
* `isReactRouterRoute`: what `reactRouter()` does with `client`, apart, so
|
|
58
|
-
* that `createServer()` serves them before the
|
|
58
|
+
* that `createServer()` serves them before the middlewares of `configure`.
|
|
59
59
|
*/
|
|
60
60
|
export declare function declareClient(app: AnyAlxia, client: string | URL): void;
|
|
61
61
|
//# sourceMappingURL=react-router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,eAAe,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,EAGN,qBAAqB,EACrB,KAAK,WAAW,EAChB,MAAM,cAAc,CAAC;AAItB,MAAM,WAAW,kBAAkB,CAAC,GAAG;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,CAAC,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;IAChE,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CACzB,GAAG,EAAE,WAAW,GAAG,GAAG,EACtB,OAAO,EAAE,qBAAqB,KAC1B,YAAY,CAAC,IAAI,CAAC,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAC/B;AAQD
|
|
1
|
+
{"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,eAAe,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,EAGN,qBAAqB,EACrB,KAAK,WAAW,EAChB,MAAM,cAAc,CAAC;AAItB,MAAM,WAAW,kBAAkB,CAAC,GAAG;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,CAAC,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;IAChE,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CACzB,GAAG,EAAE,WAAW,GAAG,GAAG,EACtB,OAAO,EAAE,qBAAqB,KAC1B,YAAY,CAAC,IAAI,CAAC,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAC/B;AAQD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAElE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAC1B,GAAG,SAAS,MAAM,EAClB,MAAM,SAAS,MAAM,EACrB,SAAS,SAAS,QAAQ,EAE1B,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAClC,OAAO,EAAE,kBAAkB,CAAC,GAAG,CAAC,GAC9B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,CA8B/B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAEvE"}
|