@rshono/core 1.0.0-rc.2 → 1.0.0-rc.21
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/LICENSE +21 -0
- package/README.md +348 -158
- package/bin/rshono.mjs +3 -4
- package/dist/builder/env-shadow-loader.cjs +123 -8
- package/dist/builder/page-entry-loader.cjs +28 -1
- package/dist/builder/page-files.d.ts +0 -1
- package/dist/builder/page-files.js +7 -3
- package/dist/builder/page-files.js.map +1 -1
- package/dist/builder/public-env.d.ts +7 -1
- package/dist/builder/public-env.js +7 -0
- package/dist/builder/public-env.js.map +1 -1
- package/dist/builder/react-versions.d.ts +12 -0
- package/dist/builder/react-versions.js +74 -0
- package/dist/builder/react-versions.js.map +1 -0
- package/dist/builder/rspack-config.d.ts +13 -4
- package/dist/builder/rspack-config.js +136 -53
- package/dist/builder/rspack-config.js.map +1 -1
- package/dist/builder/server-only-imports.d.ts +32 -0
- package/dist/builder/server-only-imports.js +51 -0
- package/dist/builder/server-only-imports.js.map +1 -0
- package/dist/cli/build.d.ts +2 -3
- package/dist/cli/build.js +51 -13
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev.d.ts +2 -3
- package/dist/cli/dev.js +149 -45
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/exit.d.ts +12 -0
- package/dist/cli/exit.js +16 -0
- package/dist/cli/exit.js.map +1 -0
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +91 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/start.d.ts +0 -1
- package/dist/cli/start.js +15 -18
- package/dist/cli/start.js.map +1 -1
- package/dist/config.d.ts +72 -78
- package/dist/config.js +17 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy/aws-lambda/runtime.d.ts +4 -7
- package/dist/deploy/aws-lambda/runtime.js +5 -8
- package/dist/deploy/aws-lambda/runtime.js.map +1 -1
- package/dist/deploy/build-marker.d.ts +9 -6
- package/dist/deploy/build-marker.js +8 -4
- package/dist/deploy/build-marker.js.map +1 -1
- package/dist/deploy/cloudflare/build.d.ts +0 -1
- package/dist/deploy/cloudflare/build.js +17 -13
- package/dist/deploy/cloudflare/build.js.map +1 -1
- package/dist/deploy/cloudflare/runtime.d.ts +2 -6
- package/dist/deploy/cloudflare/runtime.js +73 -68
- package/dist/deploy/cloudflare/runtime.js.map +1 -1
- package/dist/deploy/contract.d.ts +57 -41
- package/dist/deploy/contract.js.map +1 -1
- package/dist/deploy/filesystem.d.ts +3 -6
- package/dist/deploy/filesystem.js +23 -20
- package/dist/deploy/filesystem.js.map +1 -1
- package/dist/deploy/node/runtime.d.ts +5 -3
- package/dist/deploy/node/runtime.js +23 -9
- package/dist/deploy/node/runtime.js.map +1 -1
- package/dist/deploy/presets.d.ts +34 -30
- package/dist/deploy/presets.js +56 -44
- package/dist/deploy/presets.js.map +1 -1
- package/dist/deploy/public-paths.d.ts +42 -0
- package/dist/deploy/public-paths.js +66 -0
- package/dist/deploy/public-paths.js.map +1 -0
- package/dist/deploy/vercel/build.d.ts +0 -1
- package/dist/deploy/vercel/build.js +25 -16
- package/dist/deploy/vercel/build.js.map +1 -1
- package/dist/deploy/vercel/runtime.d.ts +10 -7
- package/dist/deploy/vercel/runtime.js +48 -11
- package/dist/deploy/vercel/runtime.js.map +1 -1
- package/dist/index.d.ts +19 -17
- package/dist/index.js +20 -10
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +212 -120
- package/dist/router.js +3 -8
- package/dist/router.js.map +1 -1
- package/dist/runtime/boundaries.d.ts +62 -48
- package/dist/runtime/boundaries.js +32 -34
- package/dist/runtime/boundaries.js.map +1 -1
- package/dist/runtime/client.d.ts +20 -11
- package/dist/runtime/client.js +20 -10
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/context.d.ts +304 -123
- package/dist/runtime/context.js +377 -149
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.d.ts +15 -1
- package/dist/runtime/control.js +24 -0
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/dev-protocol.d.ts +4 -9
- package/dist/runtime/dev-protocol.js.map +1 -1
- package/dist/runtime/empty-server-app.d.ts +0 -1
- package/dist/runtime/entry.client.d.ts +4 -1
- package/dist/runtime/entry.client.js +455 -313
- package/dist/runtime/entry.client.js.map +1 -1
- package/dist/runtime/entry.rsc.d.ts +15 -7
- package/dist/runtime/entry.rsc.js +811 -252
- package/dist/runtime/entry.rsc.js.map +1 -1
- package/dist/runtime/entry.ssr.d.ts +27 -10
- package/dist/runtime/entry.ssr.js +30 -48
- package/dist/runtime/entry.ssr.js.map +1 -1
- package/dist/runtime/failure-document.d.ts +11 -0
- package/dist/runtime/failure-document.js +35 -0
- package/dist/runtime/failure-document.js.map +1 -0
- package/dist/runtime/flight-inject.d.ts +23 -0
- package/dist/runtime/flight-inject.js +403 -0
- package/dist/runtime/flight-inject.js.map +1 -0
- package/dist/runtime/hot-update.d.ts +44 -0
- package/dist/runtime/hot-update.js +44 -0
- package/dist/runtime/hot-update.js.map +1 -0
- package/dist/runtime/navigation.d.ts +46 -54
- package/dist/runtime/navigation.js +22 -63
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/request.d.ts +42 -8
- package/dist/runtime/request.js +84 -7
- package/dist/runtime/request.js.map +1 -1
- package/dist/runtime/server.d.ts +20 -18
- package/dist/runtime/server.js +22 -17
- package/dist/runtime/server.js.map +1 -1
- package/dist/runtime/validate-entries.d.ts +77 -0
- package/dist/runtime/validate-entries.js +405 -0
- package/dist/runtime/validate-entries.js.map +1 -0
- package/dist/server/headers.d.ts +41 -15
- package/dist/server/headers.js +53 -24
- package/dist/server/headers.js.map +1 -1
- package/dist/server/load-config.d.ts +4 -5
- package/dist/server/load-config.js +22 -14
- package/dist/server/load-config.js.map +1 -1
- package/dist/server/load-env.d.ts +0 -1
- package/dist/server/prerendered.d.ts +88 -34
- package/dist/server/prerendered.js +184 -24
- package/dist/server/prerendered.js.map +1 -1
- package/dist/server/server-config.d.ts +35 -50
- package/dist/server/server-config.js +27 -74
- package/dist/server/server-config.js.map +1 -1
- package/dist/server/shutdown.d.ts +5 -4
- package/dist/server/shutdown.js +5 -3
- package/dist/server/shutdown.js.map +1 -1
- package/dist/server/ssg.d.ts +9 -7
- package/dist/server/ssg.js +209 -65
- package/dist/server/ssg.js.map +1 -1
- package/dist/server/static.d.ts +0 -1
- package/dist/server/static.js +7 -1
- package/dist/server/static.js.map +1 -1
- package/package.json +21 -23
- package/dist/builder/page-files.d.ts.map +0 -1
- package/dist/builder/public-env.d.ts.map +0 -1
- package/dist/builder/rspack-config.d.ts.map +0 -1
- package/dist/cli/build.d.ts.map +0 -1
- package/dist/cli/dev.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/start.d.ts.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/deploy/aws-lambda/runtime.d.ts.map +0 -1
- package/dist/deploy/build-marker.d.ts.map +0 -1
- package/dist/deploy/bun/runtime.d.ts +0 -11
- package/dist/deploy/bun/runtime.d.ts.map +0 -1
- package/dist/deploy/bun/runtime.js +0 -22
- package/dist/deploy/bun/runtime.js.map +0 -1
- package/dist/deploy/cloudflare/build.d.ts.map +0 -1
- package/dist/deploy/cloudflare/runtime.d.ts.map +0 -1
- package/dist/deploy/contract.d.ts.map +0 -1
- package/dist/deploy/deno/runtime.d.ts +0 -11
- package/dist/deploy/deno/runtime.d.ts.map +0 -1
- package/dist/deploy/deno/runtime.js +0 -16
- package/dist/deploy/deno/runtime.js.map +0 -1
- package/dist/deploy/filesystem.d.ts.map +0 -1
- package/dist/deploy/listen.d.ts +0 -20
- package/dist/deploy/listen.d.ts.map +0 -1
- package/dist/deploy/listen.js +0 -24
- package/dist/deploy/listen.js.map +0 -1
- package/dist/deploy/netlify/build.d.ts +0 -8
- package/dist/deploy/netlify/build.d.ts.map +0 -1
- package/dist/deploy/netlify/build.js +0 -52
- package/dist/deploy/netlify/build.js.map +0 -1
- package/dist/deploy/netlify/runtime.d.ts +0 -13
- package/dist/deploy/netlify/runtime.d.ts.map +0 -1
- package/dist/deploy/netlify/runtime.js +0 -24
- package/dist/deploy/netlify/runtime.js.map +0 -1
- package/dist/deploy/node/runtime.d.ts.map +0 -1
- package/dist/deploy/presets.d.ts.map +0 -1
- package/dist/deploy/vercel/build.d.ts.map +0 -1
- package/dist/deploy/vercel/runtime.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/runtime/boundaries.d.ts.map +0 -1
- package/dist/runtime/client.d.ts.map +0 -1
- package/dist/runtime/context.d.ts.map +0 -1
- package/dist/runtime/control.d.ts.map +0 -1
- package/dist/runtime/dev-protocol.d.ts.map +0 -1
- package/dist/runtime/empty-server-app.d.ts.map +0 -1
- package/dist/runtime/entry.client.d.ts.map +0 -1
- package/dist/runtime/entry.rsc.d.ts.map +0 -1
- package/dist/runtime/entry.ssr.d.ts.map +0 -1
- package/dist/runtime/navigation.d.ts.map +0 -1
- package/dist/runtime/request.d.ts.map +0 -1
- package/dist/runtime/server.d.ts.map +0 -1
- package/dist/server/compress.d.ts +0 -15
- package/dist/server/compress.d.ts.map +0 -1
- package/dist/server/compress.js +0 -76
- package/dist/server/compress.js.map +0 -1
- package/dist/server/headers.d.ts.map +0 -1
- package/dist/server/load-config.d.ts.map +0 -1
- package/dist/server/load-env.d.ts.map +0 -1
- package/dist/server/prerendered.d.ts.map +0 -1
- package/dist/server/server-config.d.ts.map +0 -1
- package/dist/server/shutdown.d.ts.map +0 -1
- package/dist/server/ssg.d.ts.map +0 -1
- package/dist/server/static.d.ts.map +0 -1
|
@@ -1,39 +1,199 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Everything about a prerendered page that doesn't need a filesystem: where the build puts it, and
|
|
3
|
-
*
|
|
2
|
+
* Everything about a prerendered page that doesn't need a filesystem: where the build puts it, and what it
|
|
3
|
+
* looks like once read back.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Apart from `ssg.ts`, which does the reading and writing through `node:fs`, because a deploy target without a
|
|
6
|
+
* filesystem reads the same layout out of an asset store — and importing it from there would drag `node:fs`
|
|
7
|
+
* into a bundle that has neither.
|
|
8
8
|
*/
|
|
9
9
|
export const VARIANTS = {
|
|
10
|
-
|
|
11
|
-
flight
|
|
10
|
+
// `headers` is what the build asks each representation for, so it stays the same request the browser makes:
|
|
11
|
+
// a document is the default and a flight payload is the `RSC` header. See `runtime/request.ts`.
|
|
12
|
+
html: { file: 'index.html', headers: {}, contentType: 'text/html' },
|
|
13
|
+
flight: { file: 'index.rsc', headers: { RSC: '1' }, contentType: 'text/x-component' },
|
|
12
14
|
};
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
* cannot be prerendered at all (one with a param or a wildcard left in it).
|
|
16
|
+
* How a build-time document render tells the prerender pass that the app minted a CSP nonce for its path.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* The other half of the same contract as {@link VARIANTS}, and it exists because the pass cannot see the
|
|
19
|
+
* request it made: it renders through the app's own middleware inside the server bundle, in a module graph of
|
|
20
|
+
* its own, and keeps the body alone. What it needs to know is whether a *request* for this path will mint a
|
|
21
|
+
* nonce, because a document that will is one the framework re-renders per request — so the file the pass is
|
|
22
|
+
* about to write is one no deployment will read, and saying "prerendered" about it is a lie by omission.
|
|
23
|
+
*
|
|
24
|
+
* Set only while `RSHONO_PRERENDER` is in the environment, which is `rshono build`'s own process, so it never
|
|
25
|
+
* reaches a deployed response; and never stored, since only the body is.
|
|
26
|
+
*/
|
|
27
|
+
export const PRERENDER_NONCE_HEADER = 'x-rshono-prerender-nonce';
|
|
28
|
+
/**
|
|
29
|
+
* The index the build leaves beside the pages, naming every file it wrote — one `files` array of
|
|
30
|
+
* {@link ssgFilePath} names.
|
|
31
|
+
*
|
|
32
|
+
* It exists so a reader can tell "this store has no page for that path" without asking the store, which is
|
|
33
|
+
* the difference between a `Map` lookup and a failed `readFile` (or, without a filesystem, a failed store
|
|
34
|
+
* fetch) on **every** request to a static route the build could not prerender — misses are deliberately not
|
|
35
|
+
* cached, so that one never warms up. Absent is not an error: a build from an older rshono has none, and a
|
|
36
|
+
* reader that finds none looks the way it always did.
|
|
37
|
+
*/
|
|
38
|
+
export const SSG_MANIFEST_FILE = 'manifest.json';
|
|
39
|
+
/**
|
|
40
|
+
* What a decoded path segment may not hold if it is to be one portable file name. `\ / : * ? " < > |` are
|
|
41
|
+
* refused by Windows and `/` by every host, so a `staticPaths` value containing one fails the build rather
|
|
42
|
+
* than writing a page on one machine and not on another — and a route pattern, whose params and wildcards are
|
|
43
|
+
* spelled with `:` and `*`, falls out of the same rule.
|
|
44
|
+
*/
|
|
45
|
+
const UNPORTABLE_SEGMENT = /[\\/:*?"<>|]/;
|
|
46
|
+
/**
|
|
47
|
+
* The DOS device names Windows still reserves, with or without an extension and whatever the case — so
|
|
48
|
+
* `con`, `CON` and `Con.txt` are all the console. `mkdir` on one fails with `EINVAL`, naming a path the
|
|
49
|
+
* author never wrote, which is why they are refused here with the rest.
|
|
50
|
+
*
|
|
51
|
+
* The list is Microsoft's, superscripts included: `CON PRN AUX NUL`, `COM0`–`COM9`, `COM¹ COM² COM³`, and
|
|
52
|
+
* the same three suffixes for `LPT`. A documentation slug of `con` is a plausible thing to write and
|
|
53
|
+
* builds fine on Linux today; refusing it everywhere is the same trade `:` and `*` already make — portable
|
|
54
|
+
* or not at all, rather than a build that works on the author's machine and fails in CI on Windows.
|
|
55
|
+
*/
|
|
56
|
+
const RESERVED_DEVICE_NAME = /^(?:con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³])(?:\..*)?$/i;
|
|
57
|
+
/**
|
|
58
|
+
* A trailing `.` or space, which Win32 **strips** rather than refusing: `docs/x.` is stored as `docs/x`, so
|
|
59
|
+
* the page is written where no reader will look for it — "a page the build reports as prerendered and
|
|
60
|
+
* nothing ever serves", which is the failure {@link ssgFilePath} exists to prevent. The silent half of this
|
|
61
|
+
* rule, and the more important one: the reserved names above at least fail loudly.
|
|
62
|
+
*/
|
|
63
|
+
const TRAILING_STRIPPED = /[. ]$/;
|
|
64
|
+
/**
|
|
65
|
+
* Decodes one path segment the way a URL does, treating a malformed escape as the literal text it is:
|
|
66
|
+
* `decodeURIComponent('%')` throws, and a request path is whatever the client chose to send.
|
|
67
|
+
*/
|
|
68
|
+
function decodeSegment(segment) {
|
|
69
|
+
try {
|
|
70
|
+
return decodeURIComponent(segment);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return segment;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether one decoded segment can be a directory in the prerender tree, on every host rather than on this
|
|
78
|
+
* one: not empty, not a relative-path marker, made only of characters a file name can hold
|
|
79
|
+
* ({@link UNPORTABLE_SEGMENT}, plus the control characters, which a percent-escape is free to carry into a
|
|
80
|
+
* path and no filesystem wants in a name), not a {@link RESERVED_DEVICE_NAME}, and not ending in a
|
|
81
|
+
* character Win32 would {@link TRAILING_STRIPPED | strip}.
|
|
82
|
+
*
|
|
83
|
+
* The last two are Windows rules that no macOS or Linux build can discover for itself — which is exactly
|
|
84
|
+
* why they belong in a check rather than in a caveat.
|
|
20
85
|
*/
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
return
|
|
24
|
-
|
|
86
|
+
function isStorableSegment(segment) {
|
|
87
|
+
if (segment === '' || segment === '.' || segment === '..')
|
|
88
|
+
return false;
|
|
89
|
+
if (RESERVED_DEVICE_NAME.test(segment) || TRAILING_STRIPPED.test(segment))
|
|
90
|
+
return false;
|
|
91
|
+
return !UNPORTABLE_SEGMENT.test(segment) && ![...segment].some((char) => char < ' ');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Where a path's prerendered output lives, relative to the output root — or `null` for a path no single file
|
|
95
|
+
* can answer.
|
|
96
|
+
*
|
|
97
|
+
* **The one canonical form**, shared by the build that writes the file and by every deploy target that reads
|
|
98
|
+
* it back, because the two drifting apart is invisible: the build reports the page as prerendered and every
|
|
99
|
+
* request afterwards falls through to SSR, forever. Each segment is stored *decoded*, so `/docs/café` is
|
|
100
|
+
* `docs/café` whether the caller holds the path percent-encoded — the build, which interpolates `staticPaths`
|
|
101
|
+
* values into a URL — or already decoded, as Hono's `c.req.path` is: it runs `decodeURI` over any path
|
|
102
|
+
* containing a `%`. Decoded is also the form every other static file server addresses, so the tree stays
|
|
103
|
+
* servable by something that is not this framework.
|
|
104
|
+
*
|
|
105
|
+
* Always `/`-separated: the same string addresses a file on a filesystem, which accepts forward slashes on
|
|
106
|
+
* Windows too, and a key in an asset store, where a backslash would be the wrong character.
|
|
107
|
+
*
|
|
108
|
+
* `null` is everything that is not one file: a `.` or `..` segment — so traversal is a miss rather than a
|
|
109
|
+
* lookup, which a store addressed by key, with no `resolve()` to fall back on, depends on — an empty segment,
|
|
110
|
+
* and any segment {@link isStorableSegment} refuses as unportable.
|
|
111
|
+
*/
|
|
112
|
+
export function ssgFilePath(path, variant = 'html') {
|
|
25
113
|
const file = VARIANTS[variant].file;
|
|
26
|
-
|
|
114
|
+
const trimmed = path.replace(/^\/+|\/+$/g, '');
|
|
115
|
+
if (trimmed === '')
|
|
116
|
+
return file;
|
|
117
|
+
const segments = [];
|
|
118
|
+
for (const encoded of trimmed.split('/')) {
|
|
119
|
+
const segment = decodeSegment(encoded);
|
|
120
|
+
if (!isStorableSegment(segment))
|
|
121
|
+
return null;
|
|
122
|
+
segments.push(segment);
|
|
123
|
+
}
|
|
124
|
+
return `${segments.join('/')}/${file}`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* {@link ssgFilePath} as a URL path, for a store addressed by one rather than by key. Every segment is escaped
|
|
128
|
+
* on the way out and the store decodes it back, so a page reached through a URL lands on the same file a
|
|
129
|
+
* filesystem target opens by name.
|
|
130
|
+
*/
|
|
131
|
+
export function ssgAssetPath(relPath) {
|
|
132
|
+
return relPath.split('/').map(encodeURIComponent).join('/');
|
|
27
133
|
}
|
|
134
|
+
/** The default cache budget: enough for a large documentation site's working set, small enough to be ignorable. */
|
|
135
|
+
const DEFAULT_CACHE_BYTES = 32 * 1024 * 1024;
|
|
28
136
|
/**
|
|
29
|
-
*
|
|
137
|
+
* A bounded, insertion-ordered cache of prerendered pages, so a site with thousands of them keeps a working set
|
|
138
|
+
* rather than the whole build in memory. Only *hits* are stored — caching misses would let anyone mint entries
|
|
139
|
+
* by requesting paths that don't exist — and the files never change while the server is up.
|
|
140
|
+
*
|
|
141
|
+
* Bounded by **bytes**, not by entry count: a count says nothing about how much memory it stands for, and a
|
|
142
|
+
* page can be any size. At 128 entries — the previous limit — a documentation site with half-megabyte pages
|
|
143
|
+
* retained ~64 MB across the two variants, with nothing about the number to suggest it.
|
|
30
144
|
*
|
|
31
|
-
*
|
|
32
|
-
|
|
145
|
+
* A page larger than the whole budget is served and not stored, rather than evicting everything to hold one.
|
|
146
|
+
*/
|
|
147
|
+
export function createPageCache(maxBytes = DEFAULT_CACHE_BYTES) {
|
|
148
|
+
const pages = new Map();
|
|
149
|
+
let bytes = 0;
|
|
150
|
+
return {
|
|
151
|
+
get: (key) => pages.get(key),
|
|
152
|
+
set(key, page) {
|
|
153
|
+
const size = page.body.byteLength;
|
|
154
|
+
if (size > maxBytes)
|
|
155
|
+
return;
|
|
156
|
+
// Deleted before it is set, so re-setting an existing key moves it to the *back*: `Map.set` on a key it
|
|
157
|
+
// already holds keeps that key's original position, which would leave the entry just stored sitting at
|
|
158
|
+
// the front as the next thing evicted — a store whose eviction could drop the write that caused it.
|
|
159
|
+
const replaced = pages.get(key);
|
|
160
|
+
if (replaced) {
|
|
161
|
+
bytes -= replaced.body.byteLength;
|
|
162
|
+
pages.delete(key);
|
|
163
|
+
}
|
|
164
|
+
pages.set(key, page);
|
|
165
|
+
bytes += size;
|
|
166
|
+
// Insertion-ordered, so the first key is the oldest.
|
|
167
|
+
while (bytes > maxBytes) {
|
|
168
|
+
const oldest = pages.keys().next().value;
|
|
169
|
+
bytes -= pages.get(oldest).body.byteLength;
|
|
170
|
+
pages.delete(oldest);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* A weak `ETag` for a page body — see {@link PrerenderedPage.etag} for why weak. Web Crypto rather than
|
|
177
|
+
* `node:crypto`, so one implementation serves both a Node server and `workerd`.
|
|
178
|
+
*/
|
|
179
|
+
async function weakEtag(body) {
|
|
180
|
+
const digest = await crypto.subtle.digest('SHA-256', body);
|
|
181
|
+
const base64url = btoa(String.fromCharCode(...new Uint8Array(digest)))
|
|
182
|
+
.replace(/\+/g, '-')
|
|
183
|
+
.replace(/\//g, '_')
|
|
184
|
+
.replace(/=+$/, '');
|
|
185
|
+
return `W/"${base64url.slice(0, 22)}"`;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Assembles a {@link PrerenderedPage} from a body just read out of the build. `storeEtag` is the validator the
|
|
189
|
+
* store supplied, where it has one: it already describes these exact bytes, so it is weakened rather than
|
|
190
|
+
* replaced by a fresh hash.
|
|
33
191
|
*/
|
|
34
|
-
export function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
192
|
+
export async function toPrerenderedPage(body, storeEtag) {
|
|
193
|
+
return {
|
|
194
|
+
body,
|
|
195
|
+
contentLength: String(body.byteLength),
|
|
196
|
+
etag: storeEtag ? storeEtag.replace(/^(?!W\/)/, 'W/') : await weakEtag(body),
|
|
197
|
+
};
|
|
38
198
|
}
|
|
39
199
|
//# sourceMappingURL=prerendered.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prerendered.js","sourceRoot":"","sources":["../../src/server/prerendered.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE;IAC3E,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB,EAAE,WAAW,EAAE,kBAAkB,EAAE;CACQ,CAAC;AAErG;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,OAAO,GAAqB,MAAM;IAC/E,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IACpC,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,OAAyB;IAC/E,IAAI,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,OAAO,WAAW,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC","sourcesContent":["/**\n * Everything about a prerendered page that doesn't need a filesystem: where the build puts it, and\n * what it looks like once read back.\n *\n * Split out of `ssg.ts` — which owns the writing and the reading, both through `node:fs` — because a\n * deploy target without a filesystem reads the very same layout out of an asset store, and importing\n * it from the fs module would drag `node:fs` and `node:crypto` into a bundle that has neither.\n */\n\n/**\n * The two representations of a page, prerendered side by side.\n *\n * A hard load wants the HTML document; a soft navigation asks the same URL for a flight payload.\n * Writing only the HTML meant every in-app click re-rendered a page that was already built, so the\n * prerender only ever paid off for cold loads and crawlers.\n */\nexport type PrerenderVariant = 'html' | 'flight';\n\nexport const VARIANTS = {\n html: { file: 'index.html', accept: 'text/html', contentType: 'text/html' },\n flight: { file: 'index.rsc', accept: 'text/x-component', contentType: 'text/x-component' },\n} as const satisfies Record<PrerenderVariant, { file: string; accept: string; contentType: string }>;\n\n/**\n * Where a route's prerendered output lives, relative to the output root — or `null` for a path that\n * cannot be prerendered at all (one with a param or a wildcard left in it).\n *\n * Always `/`-separated, never the host's separator: the same string addresses a file on a filesystem\n * (`resolve()` and `join()` both accept forward slashes on Windows) and a key in an asset store,\n * where a backslash would simply be the wrong character.\n */\nexport function ssgFilePath(routePath: string, variant: PrerenderVariant = 'html'): string | null {\n if (/[:*]/.test(routePath)) return null;\n const trimmed = routePath.replace(/^\\/+|\\/+$/g, '');\n const file = VARIANTS[variant].file;\n return trimmed === '' ? file : `${trimmed}/${file}`;\n}\n\n/**\n * {@link ssgFilePath} for a path that came off a request, so traversal is a miss rather than a lookup.\n *\n * The first line of defence for every deploy target: a store addressed by key has no `resolve()` to\n * fall back on, so `..` has to be refused here or not at all.\n */\nexport function prerenderedRelPath(requestPath: string, variant: PrerenderVariant): string | null {\n if (/(^|\\/)\\.\\.?(\\/|$)/.test(requestPath)) return null;\n return ssgFilePath(requestPath, variant);\n}\n\n/** A prerendered page, ready to serve: its body and a validator derived from those exact bytes. */\nexport interface PrerenderedPage {\n /** The document or the flight payload, depending on which {@link PrerenderVariant} was read. */\n body: string;\n /**\n * `Content-Length` for {@link body}, in bytes rather than characters.\n *\n * Served with the response because Hono sets no length for an in-memory body, and without one the\n * compressor cannot tell a 300-byte page from a 300 KB one — so it gzips both, including the ones\n * where the framing costs more than it saves.\n */\n contentLength: string;\n /**\n * `ETag` for the page, so a revalidating client can be answered with a 304 instead of the body.\n *\n * Deliberately **weak**. The bytes on the wire depend on whether the client took gzip, and a\n * strong validator would have to differ between those two — so the 200 and the 304 that\n * revalidates it would disagree, and a cache would treat them as different pages. A weak tag\n * says \"the same representation\", which is exactly what is true across content codings.\n */\n etag: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"prerendered.js","sourceRoot":"","sources":["../../src/server/prerendered.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,4GAA4G;IAC5G,gGAAgG;IAChG,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE;IACnE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,WAAW,EAAE,kBAAkB,EAAE;CAC8B,CAAC;AAEtH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,0BAA0B,CAAC;AAEjE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,oBAAoB,GAAG,yDAAyD,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAElC;;;GAGG;AACH,SAAS,aAAa,CAAC,OAAe;IACpC,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,iBAAiB,CAAC,OAAe;IACxC,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACxE,IAAI,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IACxF,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;AACvF,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAO,GAAqB,MAAM;IAC1E,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC/C,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAEhC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED,mHAAmH;AACnH,MAAM,mBAAmB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,QAAQ,GAAG,mBAAmB;IAI5D,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IACjD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO;QACL,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;QAC5B,GAAG,CAAC,GAAG,EAAE,IAAI;YACX,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;YAClC,IAAI,IAAI,GAAG,QAAQ;gBAAE,OAAO;YAC5B,wGAAwG;YACxG,uGAAuG;YACvG,oGAAoG;YACpG,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,QAAQ,EAAE,CAAC;gBACb,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC;gBAClC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACrB,KAAK,IAAI,IAAI,CAAC;YACd,qDAAqD;YACrD,OAAO,KAAK,GAAG,QAAQ,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC;gBAC1C,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,IAAI,CAAC,UAAU,CAAC;gBAC5C,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,QAAQ,CAAC,IAA6B;IACnD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;SACnE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtB,OAAO,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAA6B,EAAE,SAAyB;IAC9F,OAAO;QACL,IAAI;QACJ,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;QACtC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC;KAC7E,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Everything about a prerendered page that doesn't need a filesystem: where the build puts it, and what it\n * looks like once read back.\n *\n * Apart from `ssg.ts`, which does the reading and writing through `node:fs`, because a deploy target without a\n * filesystem reads the same layout out of an asset store — and importing it from there would drag `node:fs`\n * into a bundle that has neither.\n */\n\n/**\n * The two representations of a page, prerendered side by side: a hard load wants the HTML document, a soft\n * navigation asks the same URL for a flight payload. Without both, every in-app click would re-render a page\n * that was already built.\n */\nexport type PrerenderVariant = 'html' | 'flight';\n\nexport const VARIANTS = {\n // `headers` is what the build asks each representation for, so it stays the same request the browser makes:\n // a document is the default and a flight payload is the `RSC` header. See `runtime/request.ts`.\n html: { file: 'index.html', headers: {}, contentType: 'text/html' },\n flight: { file: 'index.rsc', headers: { RSC: '1' }, contentType: 'text/x-component' },\n} as const satisfies Record<PrerenderVariant, { file: string; headers: Record<string, string>; contentType: string }>;\n\n/**\n * How a build-time document render tells the prerender pass that the app minted a CSP nonce for its path.\n *\n * The other half of the same contract as {@link VARIANTS}, and it exists because the pass cannot see the\n * request it made: it renders through the app's own middleware inside the server bundle, in a module graph of\n * its own, and keeps the body alone. What it needs to know is whether a *request* for this path will mint a\n * nonce, because a document that will is one the framework re-renders per request — so the file the pass is\n * about to write is one no deployment will read, and saying \"prerendered\" about it is a lie by omission.\n *\n * Set only while `RSHONO_PRERENDER` is in the environment, which is `rshono build`'s own process, so it never\n * reaches a deployed response; and never stored, since only the body is.\n */\nexport const PRERENDER_NONCE_HEADER = 'x-rshono-prerender-nonce';\n\n/**\n * The index the build leaves beside the pages, naming every file it wrote — one `files` array of\n * {@link ssgFilePath} names.\n *\n * It exists so a reader can tell \"this store has no page for that path\" without asking the store, which is\n * the difference between a `Map` lookup and a failed `readFile` (or, without a filesystem, a failed store\n * fetch) on **every** request to a static route the build could not prerender — misses are deliberately not\n * cached, so that one never warms up. Absent is not an error: a build from an older rshono has none, and a\n * reader that finds none looks the way it always did.\n */\nexport const SSG_MANIFEST_FILE = 'manifest.json';\n\n/**\n * What a decoded path segment may not hold if it is to be one portable file name. `\\ / : * ? \" < > |` are\n * refused by Windows and `/` by every host, so a `staticPaths` value containing one fails the build rather\n * than writing a page on one machine and not on another — and a route pattern, whose params and wildcards are\n * spelled with `:` and `*`, falls out of the same rule.\n */\nconst UNPORTABLE_SEGMENT = /[\\\\/:*?\"<>|]/;\n\n/**\n * The DOS device names Windows still reserves, with or without an extension and whatever the case — so\n * `con`, `CON` and `Con.txt` are all the console. `mkdir` on one fails with `EINVAL`, naming a path the\n * author never wrote, which is why they are refused here with the rest.\n *\n * The list is Microsoft's, superscripts included: `CON PRN AUX NUL`, `COM0`–`COM9`, `COM¹ COM² COM³`, and\n * the same three suffixes for `LPT`. A documentation slug of `con` is a plausible thing to write and\n * builds fine on Linux today; refusing it everywhere is the same trade `:` and `*` already make — portable\n * or not at all, rather than a build that works on the author's machine and fails in CI on Windows.\n */\nconst RESERVED_DEVICE_NAME = /^(?:con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³])(?:\\..*)?$/i;\n\n/**\n * A trailing `.` or space, which Win32 **strips** rather than refusing: `docs/x.` is stored as `docs/x`, so\n * the page is written where no reader will look for it — \"a page the build reports as prerendered and\n * nothing ever serves\", which is the failure {@link ssgFilePath} exists to prevent. The silent half of this\n * rule, and the more important one: the reserved names above at least fail loudly.\n */\nconst TRAILING_STRIPPED = /[. ]$/;\n\n/**\n * Decodes one path segment the way a URL does, treating a malformed escape as the literal text it is:\n * `decodeURIComponent('%')` throws, and a request path is whatever the client chose to send.\n */\nfunction decodeSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n/**\n * Whether one decoded segment can be a directory in the prerender tree, on every host rather than on this\n * one: not empty, not a relative-path marker, made only of characters a file name can hold\n * ({@link UNPORTABLE_SEGMENT}, plus the control characters, which a percent-escape is free to carry into a\n * path and no filesystem wants in a name), not a {@link RESERVED_DEVICE_NAME}, and not ending in a\n * character Win32 would {@link TRAILING_STRIPPED | strip}.\n *\n * The last two are Windows rules that no macOS or Linux build can discover for itself — which is exactly\n * why they belong in a check rather than in a caveat.\n */\nfunction isStorableSegment(segment: string): boolean {\n if (segment === '' || segment === '.' || segment === '..') return false;\n if (RESERVED_DEVICE_NAME.test(segment) || TRAILING_STRIPPED.test(segment)) return false;\n return !UNPORTABLE_SEGMENT.test(segment) && ![...segment].some((char) => char < ' ');\n}\n\n/**\n * Where a path's prerendered output lives, relative to the output root — or `null` for a path no single file\n * can answer.\n *\n * **The one canonical form**, shared by the build that writes the file and by every deploy target that reads\n * it back, because the two drifting apart is invisible: the build reports the page as prerendered and every\n * request afterwards falls through to SSR, forever. Each segment is stored *decoded*, so `/docs/café` is\n * `docs/café` whether the caller holds the path percent-encoded — the build, which interpolates `staticPaths`\n * values into a URL — or already decoded, as Hono's `c.req.path` is: it runs `decodeURI` over any path\n * containing a `%`. Decoded is also the form every other static file server addresses, so the tree stays\n * servable by something that is not this framework.\n *\n * Always `/`-separated: the same string addresses a file on a filesystem, which accepts forward slashes on\n * Windows too, and a key in an asset store, where a backslash would be the wrong character.\n *\n * `null` is everything that is not one file: a `.` or `..` segment — so traversal is a miss rather than a\n * lookup, which a store addressed by key, with no `resolve()` to fall back on, depends on — an empty segment,\n * and any segment {@link isStorableSegment} refuses as unportable.\n */\nexport function ssgFilePath(path: string, variant: PrerenderVariant = 'html'): string | null {\n const file = VARIANTS[variant].file;\n const trimmed = path.replace(/^\\/+|\\/+$/g, '');\n if (trimmed === '') return file;\n\n const segments: string[] = [];\n for (const encoded of trimmed.split('/')) {\n const segment = decodeSegment(encoded);\n if (!isStorableSegment(segment)) return null;\n segments.push(segment);\n }\n return `${segments.join('/')}/${file}`;\n}\n\n/**\n * {@link ssgFilePath} as a URL path, for a store addressed by one rather than by key. Every segment is escaped\n * on the way out and the store decodes it back, so a page reached through a URL lands on the same file a\n * filesystem target opens by name.\n */\nexport function ssgAssetPath(relPath: string): string {\n return relPath.split('/').map(encodeURIComponent).join('/');\n}\n\n/** The default cache budget: enough for a large documentation site's working set, small enough to be ignorable. */\nconst DEFAULT_CACHE_BYTES = 32 * 1024 * 1024;\n\n/**\n * A bounded, insertion-ordered cache of prerendered pages, so a site with thousands of them keeps a working set\n * rather than the whole build in memory. Only *hits* are stored — caching misses would let anyone mint entries\n * by requesting paths that don't exist — and the files never change while the server is up.\n *\n * Bounded by **bytes**, not by entry count: a count says nothing about how much memory it stands for, and a\n * page can be any size. At 128 entries — the previous limit — a documentation site with half-megabyte pages\n * retained ~64 MB across the two variants, with nothing about the number to suggest it.\n *\n * A page larger than the whole budget is served and not stored, rather than evicting everything to hold one.\n */\nexport function createPageCache(maxBytes = DEFAULT_CACHE_BYTES): {\n get(key: string): PrerenderedPage | undefined;\n set(key: string, page: PrerenderedPage): void;\n} {\n const pages = new Map<string, PrerenderedPage>();\n let bytes = 0;\n return {\n get: (key) => pages.get(key),\n set(key, page) {\n const size = page.body.byteLength;\n if (size > maxBytes) return;\n // Deleted before it is set, so re-setting an existing key moves it to the *back*: `Map.set` on a key it\n // already holds keeps that key's original position, which would leave the entry just stored sitting at\n // the front as the next thing evicted — a store whose eviction could drop the write that caused it.\n const replaced = pages.get(key);\n if (replaced) {\n bytes -= replaced.body.byteLength;\n pages.delete(key);\n }\n pages.set(key, page);\n bytes += size;\n // Insertion-ordered, so the first key is the oldest.\n while (bytes > maxBytes) {\n const oldest = pages.keys().next().value!;\n bytes -= pages.get(oldest)!.body.byteLength;\n pages.delete(oldest);\n }\n },\n };\n}\n\n/**\n * A weak `ETag` for a page body — see {@link PrerenderedPage.etag} for why weak. Web Crypto rather than\n * `node:crypto`, so one implementation serves both a Node server and `workerd`.\n */\nasync function weakEtag(body: Uint8Array<ArrayBuffer>): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', body);\n const base64url = btoa(String.fromCharCode(...new Uint8Array(digest)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n return `W/\"${base64url.slice(0, 22)}\"`;\n}\n\n/**\n * Assembles a {@link PrerenderedPage} from a body just read out of the build. `storeEtag` is the validator the\n * store supplied, where it has one: it already describes these exact bytes, so it is weakened rather than\n * replaced by a fresh hash.\n */\nexport async function toPrerenderedPage(body: Uint8Array<ArrayBuffer>, storeEtag?: string | null): Promise<PrerenderedPage> {\n return {\n body,\n contentLength: String(body.byteLength),\n etag: storeEtag ? storeEtag.replace(/^(?!W\\/)/, 'W/') : await weakEtag(body),\n };\n}\n\n/** A prerendered page, ready to serve: its body and a validator derived from those exact bytes. */\nexport interface PrerenderedPage {\n /**\n * The document or the flight payload, depending on which {@link PrerenderVariant} was read. Bytes rather\n * than a string, because this cache entry is served verbatim to every request that hits it — as a string,\n * each would pay a fresh UTF-8 encode of the whole page.\n */\n body: Uint8Array<ArrayBuffer>;\n /**\n * `Content-Length` for {@link body}, in bytes. Served explicitly because Hono sets no length for an\n * in-memory body, and a proxy in front is entitled to make decisions with it.\n */\n contentLength: string;\n /**\n * `ETag` for the page, so a revalidating client can be answered with a 304 instead of the body.\n *\n * Deliberately **weak**: the bytes on the wire depend on whether something in front re-encoded them, so a\n * strong validator would differ per coding and a cache would treat the 200 and the 304 that revalidates it\n * as different pages. A weak tag says \"the same representation\", which stays true across codings.\n */\n etag: string;\n}\n"]}
|
|
@@ -1,72 +1,57 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RshonoConfig } from '../config.js';
|
|
2
2
|
/**
|
|
3
|
-
* The framework settings the server bundle needs at request time, fully resolved
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The value is produced once by {@link resolveServerConfig} from `rshono.config.ts`
|
|
7
|
-
* and compiled into the server bundle as the `__RSHONO_CONFIG__` literal (see
|
|
8
|
-
* `builder/rspack-config.ts`) — there is no runtime env-var interface for these.
|
|
3
|
+
* The framework settings the server bundle needs at request time, fully resolved — produced once by
|
|
4
|
+
* {@link resolveServerConfig} and compiled in as the `__RSHONO_CONFIG__` literal. There is no runtime env-var
|
|
5
|
+
* interface for these, and nothing here is a per-request concern: those are Hono middleware in `src/server.ts`.
|
|
9
6
|
*/
|
|
10
7
|
export interface ServerConfig {
|
|
11
8
|
/**
|
|
12
|
-
* `true` when the bundle came from `rshono dev`.
|
|
13
|
-
*
|
|
14
|
-
* Baked in rather than read from `process.env.NODE_ENV` at runtime: it is decided by which command
|
|
15
|
-
* produced the bundle, and a deploy target need not have a `process` to read it from.
|
|
9
|
+
* `true` when the bundle came from `rshono dev`. Baked in rather than read from `process.env.NODE_ENV`,
|
|
10
|
+
* because a deploy target need not have a `process`.
|
|
16
11
|
*/
|
|
17
12
|
isDev: boolean;
|
|
18
|
-
/** Deadline in ms for a single request (server action + flight + SSR). */
|
|
19
|
-
renderTimeoutMs: number;
|
|
20
13
|
/** Honour `X-Forwarded-Host` / `-Proto` when resolving the browser-facing URL. Forced on in dev. */
|
|
21
14
|
trustProxy: boolean;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/** Default listen port for `start` (overridden by `PORT`). */
|
|
35
|
-
port?: number;
|
|
36
|
-
/** Default bind address for `start` (overridden by `HOST`). */
|
|
37
|
-
host?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The output directory this bundle was written to, relative to the project root. Carried into the bundle
|
|
17
|
+
* because `deploy/filesystem.ts` locates the assets and prerendered pages beside it at runtime, and only the
|
|
18
|
+
* compiler knows which directory it used.
|
|
19
|
+
*/
|
|
20
|
+
outDir: string;
|
|
21
|
+
/**
|
|
22
|
+
* Whether this target's platform supplies per-request bindings as the second argument to
|
|
23
|
+
* `app.fetch(request, env)`. Baked in because only the build knows which preset was selected, and
|
|
24
|
+
* `getRequestContext().env` has to know: see `DeployPreset.envBindings`, which decides it.
|
|
25
|
+
*/
|
|
26
|
+
envBindings: boolean;
|
|
38
27
|
}
|
|
39
28
|
/**
|
|
40
|
-
* The
|
|
41
|
-
*
|
|
42
|
-
* `renderTimeoutMs` and `maxBodyBytes` are resolved here, into the bundle. `port` and `host` are not:
|
|
43
|
-
* they stay env-overridable at runtime, so the default is applied where the address is resolved
|
|
44
|
-
* (`deploy/listen.ts` for a server bundle, `cli/dev.ts` for the dev server) and this is what both read.
|
|
29
|
+
* The framework's built-in defaults. `port` and `host` are not config fields — they come from `PORT` / `HOST`
|
|
30
|
+
* or `--port` wherever the address is resolved, and this is what those fall back to.
|
|
45
31
|
*/
|
|
46
32
|
export declare const SERVER_DEFAULTS: {
|
|
47
|
-
readonly renderTimeoutMs: 10000;
|
|
48
|
-
readonly maxBodyBytes: number;
|
|
49
33
|
readonly port: 3000;
|
|
50
34
|
readonly host: '0.0.0.0';
|
|
51
35
|
};
|
|
52
36
|
/**
|
|
53
|
-
*
|
|
54
|
-
* {@link RSHonoConfig.cspDirectives} can override entries individually.
|
|
37
|
+
* Reads a port out of `--port` or `PORT`, so both are read the same way wherever the address is resolved.
|
|
55
38
|
*
|
|
56
|
-
* `
|
|
57
|
-
*
|
|
39
|
+
* A blank value is `undefined` — "unset", falling through to {@link SERVER_DEFAULTS}. An empty `PORT` is
|
|
40
|
+
* common in CI images and container templates, and the other reading of it is a silent failure: `Number('')`
|
|
41
|
+
* is `0`, which binds a random free port and then reports success.
|
|
42
|
+
*
|
|
43
|
+
* @param source how to name the value in the error — the flag or the variable it came from.
|
|
44
|
+
* @throws RangeError for anything present that is not a port, rather than letting `NaN` reach Node as a raw
|
|
45
|
+
* `ERR_SOCKET_BAD_PORT` with a bundler frame in the stack.
|
|
58
46
|
*/
|
|
59
|
-
export declare
|
|
60
|
-
/** Parse a {@link RSHonoConfig.bodySizeLimit} value into a byte count (`false`/`0` → `0`, disabling the cap). */
|
|
61
|
-
export declare function parseByteSize(value: string | number | false | undefined): number | undefined;
|
|
47
|
+
export declare function parsePort(value: string | undefined, source: string): number | undefined;
|
|
62
48
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* `
|
|
66
|
-
* shouldn't have to: `trustProxy` is forced on under `rshono dev`, where the framework's own proxy
|
|
67
|
-
* is the only way in (it sets the forwarded headers itself and binds to localhost).
|
|
49
|
+
* Resolves the user's config into the {@link ServerConfig} baked into the bundle. `isDev` is a build-time input
|
|
50
|
+
* rather than a config field because it decides one thing the user should not have to: `trustProxy` is forced on
|
|
51
|
+
* under `rshono dev`, where the framework's own localhost proxy is the only way in.
|
|
68
52
|
*/
|
|
69
|
-
export declare function resolveServerConfig(config:
|
|
53
|
+
export declare function resolveServerConfig(config: RshonoConfig, { isDev, outDir, envBindings }: {
|
|
70
54
|
isDev: boolean;
|
|
55
|
+
outDir: string;
|
|
56
|
+
envBindings?: boolean;
|
|
71
57
|
}): ServerConfig;
|
|
72
|
-
//# sourceMappingURL=server-config.d.ts.map
|
|
@@ -1,94 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
* `renderTimeoutMs` and `maxBodyBytes` are resolved here, into the bundle. `port` and `host` are not:
|
|
5
|
-
* they stay env-overridable at runtime, so the default is applied where the address is resolved
|
|
6
|
-
* (`deploy/listen.ts` for a server bundle, `cli/dev.ts` for the dev server) and this is what both read.
|
|
2
|
+
* The framework's built-in defaults. `port` and `host` are not config fields — they come from `PORT` / `HOST`
|
|
3
|
+
* or `--port` wherever the address is resolved, and this is what those fall back to.
|
|
7
4
|
*/
|
|
8
5
|
export const SERVER_DEFAULTS = {
|
|
9
|
-
renderTimeoutMs: 10_000,
|
|
10
|
-
maxBodyBytes: 1024 * 1024, // 1 MiB, matching Next.js's server-action body-size limit.
|
|
11
6
|
port: 3000,
|
|
12
7
|
host: '0.0.0.0',
|
|
13
8
|
};
|
|
9
|
+
/** The highest port a TCP listener can bind. `0` is the lowest, and means "any free port". */
|
|
10
|
+
const MAX_PORT = 65535;
|
|
14
11
|
/**
|
|
15
|
-
*
|
|
16
|
-
* {@link RSHonoConfig.cspDirectives} can override entries individually.
|
|
12
|
+
* Reads a port out of `--port` or `PORT`, so both are read the same way wherever the address is resolved.
|
|
17
13
|
*
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
export const CSP_DEFAULTS = {
|
|
22
|
-
'default-src': "'self'",
|
|
23
|
-
'script-src': "'self'",
|
|
24
|
-
'style-src': "'self' 'unsafe-inline'",
|
|
25
|
-
'img-src': "'self' data:",
|
|
26
|
-
'connect-src': "'self'",
|
|
27
|
-
// Not covered by default-src, and each closes an injection route of its own: a stray <base>
|
|
28
|
-
// retargeting every relative URL, plugin content, framing (clickjacking), off-site form posts.
|
|
29
|
-
'base-uri': "'self'",
|
|
30
|
-
'object-src': "'none'",
|
|
31
|
-
'frame-ancestors': "'none'",
|
|
32
|
-
'form-action': "'self'",
|
|
33
|
-
};
|
|
34
|
-
const UNITS = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3 };
|
|
35
|
-
/** Parse a {@link RSHonoConfig.bodySizeLimit} value into a byte count (`false`/`0` → `0`, disabling the cap). */
|
|
36
|
-
export function parseByteSize(value) {
|
|
37
|
-
if (value === undefined)
|
|
38
|
-
return undefined;
|
|
39
|
-
if (value === false)
|
|
40
|
-
return 0;
|
|
41
|
-
if (typeof value === 'number')
|
|
42
|
-
return value;
|
|
43
|
-
const match = /^\s*(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?\s*$/i.exec(value);
|
|
44
|
-
if (!match) {
|
|
45
|
-
throw new Error(`[rshono] invalid bodySizeLimit ${JSON.stringify(value)} — use e.g. '1mb', 1048576, or false.`);
|
|
46
|
-
}
|
|
47
|
-
return Math.floor(Number(match[1]) * UNITS[(match[2] ?? 'b').toLowerCase()]);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Normalize a config `allowedOrigins` entry (full origin or bare host) to a lowercase `URL.host`,
|
|
51
|
-
* ready for a direct comparison against a parsed `Origin` header's host.
|
|
14
|
+
* A blank value is `undefined` — "unset", falling through to {@link SERVER_DEFAULTS}. An empty `PORT` is
|
|
15
|
+
* common in CI images and container templates, and the other reading of it is a silent failure: `Number('')`
|
|
16
|
+
* is `0`, which binds a random free port and then reports success.
|
|
52
17
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* fails the build instead of quietly disabling the allowlist entry it was meant to add.
|
|
18
|
+
* @param source how to name the value in the error — the flag or the variable it came from.
|
|
19
|
+
* @throws RangeError for anything present that is not a port, rather than letting `NaN` reach Node as a raw
|
|
20
|
+
* `ERR_SOCKET_BAD_PORT` with a bundler frame in the stack.
|
|
57
21
|
*/
|
|
58
|
-
function
|
|
59
|
-
const
|
|
60
|
-
if (!
|
|
61
|
-
|
|
22
|
+
export function parsePort(value, source) {
|
|
23
|
+
const text = value?.trim();
|
|
24
|
+
if (!text)
|
|
25
|
+
return undefined;
|
|
26
|
+
// Digits only: `Number` alone would also accept `0x50`, `1e3`, `+80` and `3.0`, none of which anyone
|
|
27
|
+
// typed meaning the port they would get.
|
|
28
|
+
const port = /^\d+$/.test(text) ? Number(text) : Number.NaN;
|
|
29
|
+
if (Number.isNaN(port) || port > MAX_PORT) {
|
|
30
|
+
throw new RangeError(`invalid ${source} ${JSON.stringify(value)} — expected an integer between 0 and ${MAX_PORT}.`);
|
|
62
31
|
}
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
/** Drop directives the user blanked out, so `cspDirectives: { 'frame-ancestors': '' }` removes one. */
|
|
66
|
-
function resolveCspDirectives(overrides) {
|
|
67
|
-
return Object.fromEntries(Object.entries({ ...CSP_DEFAULTS, ...overrides }).filter(([, value]) => value.trim() !== ''));
|
|
32
|
+
return port;
|
|
68
33
|
}
|
|
69
34
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* `
|
|
73
|
-
* shouldn't have to: `trustProxy` is forced on under `rshono dev`, where the framework's own proxy
|
|
74
|
-
* is the only way in (it sets the forwarded headers itself and binds to localhost).
|
|
35
|
+
* Resolves the user's config into the {@link ServerConfig} baked into the bundle. `isDev` is a build-time input
|
|
36
|
+
* rather than a config field because it decides one thing the user should not have to: `trustProxy` is forced on
|
|
37
|
+
* under `rshono dev`, where the framework's own localhost proxy is the only way in.
|
|
75
38
|
*/
|
|
76
|
-
export function resolveServerConfig(config, { isDev }) {
|
|
39
|
+
export function resolveServerConfig(config, { isDev, outDir, envBindings = false }) {
|
|
77
40
|
return {
|
|
78
41
|
isDev,
|
|
79
|
-
renderTimeoutMs: config.renderTimeout ?? SERVER_DEFAULTS.renderTimeoutMs,
|
|
80
42
|
trustProxy: isDev || (config.trustProxy ?? false),
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
checkOrigin: config.checkOrigin ?? true,
|
|
84
|
-
allowedOrigins: (config.allowedOrigins ?? [])
|
|
85
|
-
.map((entry) => entry.trim())
|
|
86
|
-
.filter(Boolean)
|
|
87
|
-
.map(normalizeOrigin),
|
|
88
|
-
maxBodyBytes: parseByteSize(config.bodySizeLimit) ?? SERVER_DEFAULTS.maxBodyBytes,
|
|
89
|
-
compress: config.compress ?? true,
|
|
90
|
-
port: config.port,
|
|
91
|
-
host: config.host,
|
|
43
|
+
outDir,
|
|
44
|
+
envBindings,
|
|
92
45
|
};
|
|
93
46
|
}
|
|
94
47
|
//# sourceMappingURL=server-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/server/server-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/server/server-config.ts"],"names":[],"mappings":"AA6BA;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,SAAS;CACP,CAAC;AAEX,8FAA8F;AAC9F,MAAM,QAAQ,GAAG,KAAK,CAAC;AAEvB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,KAAyB,EAAE,MAAc;IACjE,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,qGAAqG;IACrG,yCAAyC;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IAC5D,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,QAAQ,EAAE,CAAC;QAC1C,MAAM,IAAI,UAAU,CAAC,WAAW,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,wCAAwC,QAAQ,GAAG,CAAC,CAAC;IACtH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAoB,EACpB,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,GAAG,KAAK,EAA6D;IAEjG,OAAO;QACL,KAAK;QACL,UAAU,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,KAAK,CAAC;QACjD,MAAM;QACN,WAAW;KACZ,CAAC;AACJ,CAAC","sourcesContent":["import type { RshonoConfig } from '../config.js';\n\n/**\n * The framework settings the server bundle needs at request time, fully resolved — produced once by\n * {@link resolveServerConfig} and compiled in as the `__RSHONO_CONFIG__` literal. There is no runtime env-var\n * interface for these, and nothing here is a per-request concern: those are Hono middleware in `src/server.ts`.\n */\nexport interface ServerConfig {\n /**\n * `true` when the bundle came from `rshono dev`. Baked in rather than read from `process.env.NODE_ENV`,\n * because a deploy target need not have a `process`.\n */\n isDev: boolean;\n /** Honour `X-Forwarded-Host` / `-Proto` when resolving the browser-facing URL. Forced on in dev. */\n trustProxy: boolean;\n /**\n * The output directory this bundle was written to, relative to the project root. Carried into the bundle\n * because `deploy/filesystem.ts` locates the assets and prerendered pages beside it at runtime, and only the\n * compiler knows which directory it used.\n */\n outDir: string;\n /**\n * Whether this target's platform supplies per-request bindings as the second argument to\n * `app.fetch(request, env)`. Baked in because only the build knows which preset was selected, and\n * `getRequestContext().env` has to know: see `DeployPreset.envBindings`, which decides it.\n */\n envBindings: boolean;\n}\n\n/**\n * The framework's built-in defaults. `port` and `host` are not config fields — they come from `PORT` / `HOST`\n * or `--port` wherever the address is resolved, and this is what those fall back to.\n */\nexport const SERVER_DEFAULTS = {\n port: 3000,\n host: '0.0.0.0',\n} as const;\n\n/** The highest port a TCP listener can bind. `0` is the lowest, and means \"any free port\". */\nconst MAX_PORT = 65535;\n\n/**\n * Reads a port out of `--port` or `PORT`, so both are read the same way wherever the address is resolved.\n *\n * A blank value is `undefined` — \"unset\", falling through to {@link SERVER_DEFAULTS}. An empty `PORT` is\n * common in CI images and container templates, and the other reading of it is a silent failure: `Number('')`\n * is `0`, which binds a random free port and then reports success.\n *\n * @param source how to name the value in the error — the flag or the variable it came from.\n * @throws RangeError for anything present that is not a port, rather than letting `NaN` reach Node as a raw\n * `ERR_SOCKET_BAD_PORT` with a bundler frame in the stack.\n */\nexport function parsePort(value: string | undefined, source: string): number | undefined {\n const text = value?.trim();\n if (!text) return undefined;\n // Digits only: `Number` alone would also accept `0x50`, `1e3`, `+80` and `3.0`, none of which anyone\n // typed meaning the port they would get.\n const port = /^\\d+$/.test(text) ? Number(text) : Number.NaN;\n if (Number.isNaN(port) || port > MAX_PORT) {\n throw new RangeError(`invalid ${source} ${JSON.stringify(value)} — expected an integer between 0 and ${MAX_PORT}.`);\n }\n return port;\n}\n\n/**\n * Resolves the user's config into the {@link ServerConfig} baked into the bundle. `isDev` is a build-time input\n * rather than a config field because it decides one thing the user should not have to: `trustProxy` is forced on\n * under `rshono dev`, where the framework's own localhost proxy is the only way in.\n */\nexport function resolveServerConfig(\n config: RshonoConfig,\n { isDev, outDir, envBindings = false }: { isDev: boolean; outDir: string; envBindings?: boolean },\n): ServerConfig {\n return {\n isDev,\n trustProxy: isDev || (config.trustProxy ?? false),\n outDir,\n envBindings,\n };\n}\n"]}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Registers `handler` for both `SIGINT` and `SIGTERM` — the two signals a process
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Registers `handler` for both `SIGINT` and `SIGTERM` — the two signals a process manager or `Ctrl-C` uses to
|
|
3
|
+
* ask for a graceful stop.
|
|
4
|
+
*
|
|
5
|
+
* Registered by the server bundle itself, which is why `rshono start` needs no supervisor to forward signals
|
|
6
|
+
* to: it imports the bundle into its own process, so these handlers are already the process's.
|
|
5
7
|
*/
|
|
6
8
|
export declare function onShutdown(handler: (signal: NodeJS.Signals) => void): void;
|
|
7
|
-
//# sourceMappingURL=shutdown.d.ts.map
|
package/dist/server/shutdown.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Registers `handler` for both `SIGINT` and `SIGTERM` — the two signals a process
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Registers `handler` for both `SIGINT` and `SIGTERM` — the two signals a process manager or `Ctrl-C` uses to
|
|
3
|
+
* ask for a graceful stop.
|
|
4
|
+
*
|
|
5
|
+
* Registered by the server bundle itself, which is why `rshono start` needs no supervisor to forward signals
|
|
6
|
+
* to: it imports the bundle into its own process, so these handlers are already the process's.
|
|
5
7
|
*/
|
|
6
8
|
export function onShutdown(handler) {
|
|
7
9
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../../src/server/shutdown.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../../src/server/shutdown.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,OAAyC;IAClE,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAU,EAAE,CAAC;QACpD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC","sourcesContent":["/**\n * Registers `handler` for both `SIGINT` and `SIGTERM` — the two signals a process manager or `Ctrl-C` uses to\n * ask for a graceful stop.\n *\n * Registered by the server bundle itself, which is why `rshono start` needs no supervisor to forward signals\n * to: it imports the bundle into its own process, so these handlers are already the process's.\n */\nexport function onShutdown(handler: (signal: NodeJS.Signals) => void): void {\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.on(signal, () => handler(signal));\n }\n}\n"]}
|