@rivetjs/router 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +10 -2
- package/adapters/assemble.d.ts +32 -0
- package/adapters/assemble.js +32 -0
- package/adapters/build.d.ts +24 -0
- package/adapters/build.js +43 -0
- package/adapters/bun.d.ts +18 -0
- package/adapters/bun.js +44 -0
- package/adapters/cloudflare.d.ts +5 -0
- package/adapters/cloudflare.js +8 -0
- package/adapters/cluster.d.ts +7 -0
- package/adapters/cluster.js +130 -0
- package/adapters/csr.d.ts +12 -0
- package/adapters/csr.js +11 -0
- package/adapters/deno.d.ts +13 -0
- package/adapters/deno.js +35 -0
- package/adapters/fetch.d.ts +55 -0
- package/adapters/fetch.js +72 -0
- package/adapters/files.d.ts +7 -0
- package/adapters/files.js +103 -0
- package/adapters/idle-memory.d.ts +23 -0
- package/adapters/idle-memory.js +91 -0
- package/adapters/netlify.d.ts +13 -0
- package/adapters/netlify.js +15 -0
- package/adapters/node.d.ts +163 -0
- package/adapters/node.js +940 -0
- package/adapters/pack.d.ts +39 -0
- package/adapters/pack.js +360 -0
- package/adapters/server-target.d.ts +17 -0
- package/adapters/server-target.js +8 -0
- package/adapters/static.d.ts +84 -0
- package/adapters/static.js +231 -0
- package/adapters/target-build.d.ts +92 -0
- package/adapters/target-build.js +118 -0
- package/adapters/vercel-edge.d.ts +7 -0
- package/adapters/vercel-edge.js +9 -0
- package/adapters/vercel.d.ts +33 -0
- package/adapters/vercel.js +45 -0
- package/build/codegen.d.ts +84 -0
- package/build/codegen.js +920 -0
- package/build/dev-errors.d.ts +19 -0
- package/build/dev-errors.js +236 -0
- package/build/migrate.d.ts +15 -0
- package/build/migrate.js +146 -0
- package/build/route-css.d.ts +54 -0
- package/build/route-css.js +216 -0
- package/build/routes.d.ts +50 -0
- package/build/routes.js +202 -0
- package/build/sync.d.ts +20 -0
- package/build/sync.js +16 -0
- package/build/vite.d.ts +195 -0
- package/build/vite.js +703 -0
- package/cli/rivet.d.ts +2 -0
- package/cli/rivet.js +100 -0
- package/client/active.d.ts +6 -0
- package/client/active.js +22 -0
- package/client/announce.d.ts +2 -0
- package/client/announce.js +21 -0
- package/client/dev.d.ts +41 -0
- package/client/dev.js +74 -0
- package/client/index.d.ts +152 -0
- package/client/index.js +680 -0
- package/client/layouts.d.ts +110 -0
- package/client/layouts.js +310 -0
- package/client/leave.d.ts +40 -0
- package/client/leave.js +75 -0
- package/client/link.d.ts +48 -0
- package/client/link.jsx +65 -0
- package/client/live.d.ts +57 -0
- package/client/live.js +170 -0
- package/client/navigate.d.ts +71 -0
- package/client/navigate.js +120 -0
- package/client/navigation.d.ts +28 -0
- package/client/navigation.js +14 -0
- package/client/params.d.ts +16 -0
- package/client/params.js +28 -0
- package/client/route-context.d.ts +32 -0
- package/client/route-context.js +21 -0
- package/client/scroll.d.ts +16 -0
- package/client/scroll.js +80 -0
- package/client/search.d.ts +8 -0
- package/client/search.js +17 -0
- package/client/tailwind.css +8 -0
- package/client/transition-helpers.d.ts +28 -0
- package/client/transition-helpers.js +63 -0
- package/client/transitions.css +90 -0
- package/client/transitions.d.ts +21 -0
- package/client/transitions.js +156 -0
- package/index.d.ts +138 -0
- package/index.js +7 -0
- package/package.json +156 -4
- package/server/app.d.ts +169 -0
- package/server/app.js +302 -0
- package/server/body.d.ts +20 -0
- package/server/body.js +8 -0
- package/server/early-hints.d.ts +34 -0
- package/server/early-hints.js +63 -0
- package/server/handler.d.ts +196 -0
- package/server/handler.js +623 -0
- package/server/index.d.ts +9 -0
- package/server/index.js +6 -0
- package/server/inline-styles.d.ts +21 -0
- package/server/inline-styles.js +28 -0
- package/server/module.d.ts +14 -0
- package/server/module.js +78 -0
- package/server/render.d.ts +94 -0
- package/server/render.js +489 -0
- package/server/route-cache.d.ts +175 -0
- package/server/route-cache.js +742 -0
- package/server/security.d.ts +18 -0
- package/server/security.js +47 -0
- package/server/strict-csp.d.ts +7 -0
- package/server/strict-csp.js +18 -0
- package/server-only.d.ts +1 -0
- package/server-only.js +3 -0
- package/shared/actions.d.ts +10 -0
- package/shared/actions.js +18 -0
- package/shared/base.d.ts +8 -0
- package/shared/base.js +20 -0
- package/shared/cookie.d.ts +7 -0
- package/shared/cookie.js +32 -0
- package/shared/csp-directives.d.ts +9 -0
- package/shared/csp-directives.js +85 -0
- package/shared/csp-hash.d.ts +39 -0
- package/shared/csp-hash.js +121 -0
- package/shared/csp.d.ts +26 -0
- package/shared/csp.js +13 -0
- package/shared/data.d.ts +175 -0
- package/shared/data.js +22 -0
- package/shared/errors.d.ts +31 -0
- package/shared/errors.js +51 -0
- package/shared/head.d.ts +57 -0
- package/shared/head.js +242 -0
- package/shared/nonce.d.ts +2 -0
- package/shared/nonce.js +23 -0
- package/shared/prefetch-fallback.min.d.ts +1 -0
- package/shared/prefetch-fallback.min.js +2 -0
- package/shared/prefetch.d.ts +54 -0
- package/shared/prefetch.js +74 -0
- package/shared/redirect.d.ts +6 -0
- package/shared/redirect.js +25 -0
- package/shared/slash.d.ts +8 -0
- package/shared/slash.js +24 -0
package/package.json
CHANGED
|
@@ -1,6 +1,158 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rivetjs/router",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "File routing for Rivet: loaders, actions, guards, a web-standard request handler, Node / static / edge adapters.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"bin": {
|
|
9
|
+
"rivet": "./cli/rivet.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./index.d.ts",
|
|
14
|
+
"default": "./index.js"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"types": "./server/index.d.ts",
|
|
18
|
+
"default": "./server/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./route-cache": {
|
|
21
|
+
"types": "./server/route-cache.d.ts",
|
|
22
|
+
"default": "./server/route-cache.js"
|
|
23
|
+
},
|
|
24
|
+
"./server-only": {
|
|
25
|
+
"types": "./server-only.d.ts",
|
|
26
|
+
"default": "./server-only.js"
|
|
27
|
+
},
|
|
28
|
+
"./client": {
|
|
29
|
+
"types": "./client/index.d.ts",
|
|
30
|
+
"development": "./client/dev.js",
|
|
31
|
+
"default": "./client/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./route-context": {
|
|
34
|
+
"types": "./client/route-context.d.ts",
|
|
35
|
+
"default": "./client/route-context.js"
|
|
36
|
+
},
|
|
37
|
+
"./leave": {
|
|
38
|
+
"types": "./client/leave.d.ts",
|
|
39
|
+
"default": "./client/leave.js"
|
|
40
|
+
},
|
|
41
|
+
"./navigation": {
|
|
42
|
+
"types": "./client/navigation.d.ts",
|
|
43
|
+
"default": "./client/navigation.js"
|
|
44
|
+
},
|
|
45
|
+
"./search": {
|
|
46
|
+
"types": "./client/search.d.ts",
|
|
47
|
+
"default": "./client/search.js"
|
|
48
|
+
},
|
|
49
|
+
"./transitions": {
|
|
50
|
+
"types": "./client/transitions.d.ts",
|
|
51
|
+
"default": "./client/transitions.js"
|
|
52
|
+
},
|
|
53
|
+
"./prefetch": {
|
|
54
|
+
"types": "./shared/prefetch.d.ts",
|
|
55
|
+
"default": "./shared/prefetch.js"
|
|
56
|
+
},
|
|
57
|
+
"./link": {
|
|
58
|
+
"types": "./client/link.d.ts",
|
|
59
|
+
"default": "./client/link.jsx"
|
|
60
|
+
},
|
|
61
|
+
"./transitions.css": "./client/transitions.css",
|
|
62
|
+
"./tailwind.css": "./client/tailwind.css",
|
|
63
|
+
"./routes": {
|
|
64
|
+
"types": "./build/routes.d.ts",
|
|
65
|
+
"default": "./build/routes.js"
|
|
66
|
+
},
|
|
67
|
+
"./node": {
|
|
68
|
+
"types": "./adapters/node.d.ts",
|
|
69
|
+
"default": "./adapters/node.js"
|
|
70
|
+
},
|
|
71
|
+
"./fetch": {
|
|
72
|
+
"types": "./adapters/fetch.d.ts",
|
|
73
|
+
"default": "./adapters/fetch.js"
|
|
74
|
+
},
|
|
75
|
+
"./static": {
|
|
76
|
+
"types": "./adapters/static.d.ts",
|
|
77
|
+
"default": "./adapters/static.js"
|
|
78
|
+
},
|
|
79
|
+
"./csr": {
|
|
80
|
+
"types": "./adapters/csr.d.ts",
|
|
81
|
+
"default": "./adapters/csr.js"
|
|
82
|
+
},
|
|
83
|
+
"./vercel": {
|
|
84
|
+
"types": "./adapters/vercel.d.ts",
|
|
85
|
+
"default": "./adapters/vercel.js"
|
|
86
|
+
},
|
|
87
|
+
"./vercel-edge": {
|
|
88
|
+
"types": "./adapters/vercel-edge.d.ts",
|
|
89
|
+
"default": "./adapters/vercel-edge.js"
|
|
90
|
+
},
|
|
91
|
+
"./netlify": {
|
|
92
|
+
"types": "./adapters/netlify.d.ts",
|
|
93
|
+
"default": "./adapters/netlify.js"
|
|
94
|
+
},
|
|
95
|
+
"./cloudflare": {
|
|
96
|
+
"types": "./adapters/cloudflare.d.ts",
|
|
97
|
+
"default": "./adapters/cloudflare.js"
|
|
98
|
+
},
|
|
99
|
+
"./bun": {
|
|
100
|
+
"types": "./adapters/bun.d.ts",
|
|
101
|
+
"default": "./adapters/bun.js"
|
|
102
|
+
},
|
|
103
|
+
"./deno": {
|
|
104
|
+
"types": "./adapters/deno.d.ts",
|
|
105
|
+
"default": "./adapters/deno.js"
|
|
106
|
+
},
|
|
107
|
+
"./head": {
|
|
108
|
+
"types": "./shared/head.d.ts",
|
|
109
|
+
"default": "./shared/head.js"
|
|
110
|
+
},
|
|
111
|
+
"./vite": {
|
|
112
|
+
"types": "./build/vite.d.ts",
|
|
113
|
+
"default": "./build/vite.js"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"dependencies": {
|
|
117
|
+
"@rivetjs/runtime": "0.1.0-alpha.0",
|
|
118
|
+
"@rivetjs/reactivity": "0.1.0-alpha.0",
|
|
119
|
+
"@rivetjs/compiler": "0.1.0-alpha.0"
|
|
120
|
+
},
|
|
121
|
+
"engines": {
|
|
122
|
+
"node": ">=24"
|
|
123
|
+
},
|
|
124
|
+
"peerDependencies": {
|
|
125
|
+
"vite": "^8.3.2"
|
|
126
|
+
},
|
|
127
|
+
"peerDependenciesMeta": {
|
|
128
|
+
"vite": {
|
|
129
|
+
"optional": true
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"repository": {
|
|
133
|
+
"type": "git",
|
|
134
|
+
"url": "git+https://github.com/N1XUS/rivetjs.git",
|
|
135
|
+
"directory": "libs/router"
|
|
136
|
+
},
|
|
137
|
+
"homepage": "https://github.com/N1XUS/rivetjs/tree/main/libs/router#readme",
|
|
138
|
+
"bugs": {
|
|
139
|
+
"url": "https://github.com/N1XUS/rivetjs/issues"
|
|
140
|
+
},
|
|
141
|
+
"publishConfig": {
|
|
142
|
+
"access": "public"
|
|
143
|
+
},
|
|
144
|
+
"files": [
|
|
145
|
+
"LICENSE",
|
|
146
|
+
"README.md",
|
|
147
|
+
"adapters",
|
|
148
|
+
"build",
|
|
149
|
+
"cli",
|
|
150
|
+
"client",
|
|
151
|
+
"index.d.ts",
|
|
152
|
+
"index.js",
|
|
153
|
+
"server",
|
|
154
|
+
"server-only.d.ts",
|
|
155
|
+
"server-only.js",
|
|
156
|
+
"shared"
|
|
157
|
+
]
|
|
158
|
+
}
|
package/server/app.d.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { ServerValue } from "@rivetjs/runtime/server";
|
|
2
|
+
import type { ResumeManifest } from "@rivetjs/runtime/server-resume";
|
|
3
|
+
import type { RenderCache } from "@rivetjs/runtime/server-cache";
|
|
4
|
+
import type { ActionResult, Head, HeadSink, LoaderDeps } from "../shared/data.js";
|
|
5
|
+
import { type CSPOptions } from "../shared/csp.js";
|
|
6
|
+
import { type InlineStyleManifest } from "./inline-styles.js";
|
|
7
|
+
import type { TrailingSlash } from "../shared/slash.js";
|
|
8
|
+
import { type AppHandler, type AppRoute, type BodyLimit, type ErrorInfo } from "./handler.js";
|
|
9
|
+
import { type DocumentShell } from "./render.js";
|
|
10
|
+
import type { EarlyHintsOptions } from "./early-hints.js";
|
|
11
|
+
import type { RouteCacheOptions, RouteRules } from "./route-cache.js";
|
|
12
|
+
/** How a dev page renders (`AppConfig.devResume`). */
|
|
13
|
+
export type DevResumeMode = "check" | "resume";
|
|
14
|
+
/** What the generated page factory receives. */
|
|
15
|
+
export interface PageInput {
|
|
16
|
+
readonly data: readonly unknown[];
|
|
17
|
+
readonly url: URL;
|
|
18
|
+
readonly head: Head | undefined;
|
|
19
|
+
readonly error?: {
|
|
20
|
+
readonly status: number;
|
|
21
|
+
readonly message: string;
|
|
22
|
+
};
|
|
23
|
+
/** Component head collector (entries generated with `@rivetjs/kit`); the app provides it through `ServerData`. */
|
|
24
|
+
readonly headSink?: HeadSink;
|
|
25
|
+
/** The form action this request performed (no-JS POST): pages read it with `actionResult()`. */
|
|
26
|
+
readonly action?: ActionResult;
|
|
27
|
+
/** Layouts whose loader read the request URL (client navigation reloads them on any URL change). */
|
|
28
|
+
readonly byURL?: readonly number[];
|
|
29
|
+
/** Loaders' `event.depends()` keys (absent when none called it): resumed with the layout state for `invalidate(key)`. */
|
|
30
|
+
readonly deps?: LoaderDeps;
|
|
31
|
+
}
|
|
32
|
+
/** Shape of the generated `.rivet/entry.server.tsx` module. */
|
|
33
|
+
export interface GeneratedEntry {
|
|
34
|
+
readonly routes: readonly AppRoute[];
|
|
35
|
+
readonly universal: Readonly<Record<string, () => Promise<[number, {
|
|
36
|
+
head?: unknown;
|
|
37
|
+
}][]>>>;
|
|
38
|
+
page(input: PageInput): () => ServerValue;
|
|
39
|
+
/** The app's document shell, when codegen was given one. */
|
|
40
|
+
readonly document?: DocumentShell;
|
|
41
|
+
/** `createApp` from the same bundle as the app (one runtime instance). */
|
|
42
|
+
readonly createApp?: typeof createApp;
|
|
43
|
+
/** Generated with component head (`@rivetjs/kit`): pages hold the document start until the shell rendered. */
|
|
44
|
+
readonly headComponents?: boolean;
|
|
45
|
+
/** URL base path the route patterns carry (`""` or e.g. `/app`; `rivetRouter({ base })` / Vite `base`). */
|
|
46
|
+
readonly base?: string;
|
|
47
|
+
/** `rivetRouter({ trailingSlash })` when `"never"` / `"always"` (the handler's 308s use the bundle's build-time constant; the static adapter reads this). */
|
|
48
|
+
readonly trailingSlash?: Exclude<TrailingSlash, "ignore">;
|
|
49
|
+
/** `rivetRouter({ preload: { background } })`: the default `preload.background` of this app's pages. */
|
|
50
|
+
readonly preloadBackground?: number;
|
|
51
|
+
/** Document prefetch on (`rivetRouter({ prefetch })`): the default `preload.ahead` of this app's pages. */
|
|
52
|
+
readonly fetchAhead?: boolean;
|
|
53
|
+
/** `rivetRouter({ routeRules })`: the app's route rules (JSON data; `createApp({ routeRules })` replaces them). */
|
|
54
|
+
readonly routeRules?: RouteRules;
|
|
55
|
+
}
|
|
56
|
+
/** Config of the server app built from the generated routes entry. */
|
|
57
|
+
export interface AppConfig {
|
|
58
|
+
readonly entry: GeneratedEntry;
|
|
59
|
+
/** Document shell; defaults to the entry's `document`. */
|
|
60
|
+
readonly document?: DocumentShell;
|
|
61
|
+
/** Stylesheet URLs passed to the document. */
|
|
62
|
+
readonly styles?: readonly string[];
|
|
63
|
+
/**
|
|
64
|
+
* Per-route stylesheets (route modules' CSS), linked after `styles`: the client build's
|
|
65
|
+
* `.vite/rivet-route-css.json` (see `loadBuild`) or, in dev, a lookup of the route's module graph.
|
|
66
|
+
*/
|
|
67
|
+
readonly routeStyles?: Readonly<Record<string, readonly string[]>> | ((routeId: string) => readonly string[] | Promise<readonly string[]>);
|
|
68
|
+
/**
|
|
69
|
+
* Stylesheets pages may write inline instead of linking: the client build's `.vite/rivet-inline-css.json`
|
|
70
|
+
* (`rivetRouter({ inlineStylesheets })`, see `loadBuild`), decided per page from its `styles` + `routeStyles` list
|
|
71
|
+
* (`inlineStylesFor`). Dev links every sheet.
|
|
72
|
+
*/
|
|
73
|
+
readonly inlineStyles?: InlineStyleManifest;
|
|
74
|
+
/** Document defaults below every route (level −1): e.g. `{ titleTemplate: "%s · Shop", meta: [...] }`. */
|
|
75
|
+
readonly head?: Head;
|
|
76
|
+
/** Production resume manifest; omit in dev (see `devResume`). */
|
|
77
|
+
readonly manifest?: ResumeManifest;
|
|
78
|
+
readonly dev?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Dev (no manifest): module scripts that client-render the page (e.g. the Vite client and the
|
|
81
|
+
* generated `entry.dev.tsx`). The app renders inside `[data-rivet-dev]` with its route data embedded.
|
|
82
|
+
* A function gets the page's locale (i18n), e.g. to load that locale's dev module graph.
|
|
83
|
+
*/
|
|
84
|
+
readonly devScripts?: readonly string[] | ((locale: string | undefined) => readonly string[]);
|
|
85
|
+
/**
|
|
86
|
+
* Dev server (`rivetRouter` sets it; no production `manifest`): pages render through the resume
|
|
87
|
+
* recorder with this dev manifest, so serialization and chunk errors surface as in production.
|
|
88
|
+
* `mode` (per page locale when a function): `"check"` leaves the recorded state and loader out of
|
|
89
|
+
* the page and the browser client-renders (`devScripts`, embedded data); `"resume"` serves the
|
|
90
|
+
* page resumable like production, inside `[data-rivet-dev="resume"]` followed by `devScripts`.
|
|
91
|
+
*/
|
|
92
|
+
readonly devResume?: {
|
|
93
|
+
readonly manifest: ResumeManifest;
|
|
94
|
+
readonly mode: DevResumeMode | ((locale: string | undefined) => DevResumeMode);
|
|
95
|
+
};
|
|
96
|
+
readonly stream?: {
|
|
97
|
+
deadline?: number;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Preload planner. `background`: byte budget (minified) of `visible` chunks' static closures also prefetched at idle,
|
|
101
|
+
* in plan order (default 1 MiB, `0` off; Save-Data / 2G never). A function decides per page URL (`undefined`: default),
|
|
102
|
+
* e.g. `0` for embedded frames that a page shows many of. `lean` (per page URL when a function): nothing loads before
|
|
103
|
+
* the first interaction — no scheduled prefetch (eager / idle / visible / background) and no router fetch-ahead;
|
|
104
|
+
* handlers load on intent (pointer dwell, focus, touch) and interaction, as under Save-Data.
|
|
105
|
+
*/
|
|
106
|
+
readonly preload?: {
|
|
107
|
+
readonly background?: number | ((url: URL) => number | undefined);
|
|
108
|
+
readonly lean?: boolean | ((url: URL) => boolean | undefined);
|
|
109
|
+
/**
|
|
110
|
+
* Islands-only lean pages of a file-router app (bridge D-lean: the router's region + link listeners deferred until
|
|
111
|
+
* the first link intent / click) still fetch the runtime + router chunks ahead (low priority after `load`, fetched,
|
|
112
|
+
* never evaluated). On `resume.chunks: "route"` builds every page also fetches its own handler chunks ahead the
|
|
113
|
+
* same way. Default: the app's document prefetch (`rivetRouter({ prefetch })` on → true). Ignored with `lean`.
|
|
114
|
+
*/
|
|
115
|
+
readonly ahead?: boolean;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Content-Security-Policy for HTML pages, given the response's nonce (inline scripts carry it).
|
|
119
|
+
* Default: strict nonce policy (`strictCSP`); `CSPOptions` (`{ trustedTypes, directives }`): `strictCSP` with them (directives checked at startup);
|
|
120
|
+
* `false` sends none. A `headers()` CSP wins.
|
|
121
|
+
*/
|
|
122
|
+
readonly csp?: false | CSPOptions | ((nonce: string) => string);
|
|
123
|
+
/** Server failures (`digest` set for 5xx responses, matching the message the client sees; `phase`/`route` where it failed). */
|
|
124
|
+
onError?(error: unknown, request: Request, info?: ErrorInfo): void;
|
|
125
|
+
/**
|
|
126
|
+
* i18n: locales in compiler variant order and per-locale resume manifests. A page renders the
|
|
127
|
+
* variant and manifest of `locals.locale` (set by a guard, e.g. `i18nGuard`); `locals.dir` reaches
|
|
128
|
+
* the document as `dir`. Error pages use the request's `locals` too; without a locale there
|
|
129
|
+
* (unmatched URLs, guards that failed first) `resolve(request)` decides, else a first path segment
|
|
130
|
+
* naming a locale, else the first locale.
|
|
131
|
+
*/
|
|
132
|
+
readonly i18n?: {
|
|
133
|
+
readonly locales: readonly string[];
|
|
134
|
+
readonly manifests?: Readonly<Record<string, ResumeManifest>>;
|
|
135
|
+
resolve?(request: Request): string | undefined | Promise<string | undefined>;
|
|
136
|
+
};
|
|
137
|
+
/** Request body limit for unsafe methods (413 past it); default 1 MiB, multipart 10 MiB. */
|
|
138
|
+
readonly maxBody?: BodyLimit;
|
|
139
|
+
/**
|
|
140
|
+
* Route rules by path pattern (`{ "/blog/**": { swr: 60 }, "/about": { isr: true, headers: { … } } }`): response headers,
|
|
141
|
+
* and an in-process cache of rendered pages (fresh, stale-while-revalidate, ISR; ETag / Last-Modified → 304).
|
|
142
|
+
* Defaults to the entry's (`rivetRouter({ routeRules })`). The rules module loads only when there are rules.
|
|
143
|
+
* See `server/route-cache.ts` / docs "Caching".
|
|
144
|
+
*/
|
|
145
|
+
readonly routeRules?: RouteRules;
|
|
146
|
+
/** The route rules' page store: `maxBytes` (default 32 MiB), `maxEntries` (default 5000), `origin` (the public origin pages are stored for; `isr` needs it). */
|
|
147
|
+
readonly routeCache?: RouteCacheOptions;
|
|
148
|
+
/**
|
|
149
|
+
* Cross-request fragment cache (`createRenderCache()` from `@rivetjs/runtime/server-cache`): every page renders inside
|
|
150
|
+
* its scope (`withRenderCache`: locale variant, nonce, abort signal, resumable), so `cached()` / `shell()` / compiler
|
|
151
|
+
* `_cached` components hit across requests. Node-only (AsyncLocalStorage; Workers: `nodejs_als`); loaded only when set.
|
|
152
|
+
*/
|
|
153
|
+
readonly renderCache?: RenderCache;
|
|
154
|
+
/**
|
|
155
|
+
* Early hints for HTML pages (production only): linked stylesheets, the head's build-asset font preloads (learned
|
|
156
|
+
* per route from its last render), `links` (e.g. preconnects), and with `runtime: true` the resume runtime entry. Sent as `103 Early
|
|
157
|
+
* Hints` by the node adapter before guards and loaders run (`start({ earlyHints })`), and as a `Link` header on every
|
|
158
|
+
* page response (proxies such as Cloudflare turn it into 103s). Browsers act on 103s over HTTP/2+ only. Pages served
|
|
159
|
+
* from the route cache get the stored `Link` header but no 103 (no render runs): it helps uncached pages. Default off.
|
|
160
|
+
*/
|
|
161
|
+
readonly earlyHints?: boolean | EarlyHintsOptions;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Merge heads outer → inner: inner wins per key (title; meta by name/property/http-equiv/itemprop
|
|
165
|
+
* + media; one canonical; alternates by hreflang; other links by rel + href; scripts by id/src).
|
|
166
|
+
*/
|
|
167
|
+
export declare function mergeHeads(heads: readonly (Head | undefined)[]): Head | undefined;
|
|
168
|
+
/** Create the app's request handler. */
|
|
169
|
+
export declare function createApp(config: AppConfig): AppHandler;
|
package/server/app.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { headTags, rankMerge } from "../shared/head.js";
|
|
2
|
+
import { nonceCSP } from "../shared/csp.js";
|
|
3
|
+
import { inlineStylesFor } from "./inline-styles.js";
|
|
4
|
+
import { escapeJSON } from "@rivetjs/runtime/resume-format";
|
|
5
|
+
import { createHandler, _digest, encodeData } from "./handler.js";
|
|
6
|
+
import { renderDocument } from "./render.js";
|
|
7
|
+
// `route-cache.ts` `ROUTE_CACHE` (same registered symbol; that module loads only with rules).
|
|
8
|
+
const ROUTE_CACHE = Symbol.for("rivet.routeCache");
|
|
9
|
+
// `early-hints.ts` `EARLY_HINTS` (same registered symbol; that module loads only with `earlyHints`).
|
|
10
|
+
const EARLY_HINTS = Symbol.for("rivet.earlyHints");
|
|
11
|
+
const RENDER_FAILED = Symbol.for("rivet.renderFailed");
|
|
12
|
+
/** Route export levels: layouts outermost first, then the page (undefined entries skipped). */
|
|
13
|
+
const ranked = (heads, defaults) => [
|
|
14
|
+
...(defaults ? [{ level: -1, phase: 0, seq: 0, head: defaults }] : []),
|
|
15
|
+
...heads.flatMap((head, level) => (head ? [{ level, phase: 0, seq: 0, head }] : [])),
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Merge heads outer → inner: inner wins per key (title; meta by name/property/http-equiv/itemprop
|
|
19
|
+
* + media; one canonical; alternates by hreflang; other links by rel + href; scripts by id/src).
|
|
20
|
+
*/
|
|
21
|
+
export function mergeHeads(heads) {
|
|
22
|
+
return rankMerge(ranked(heads));
|
|
23
|
+
}
|
|
24
|
+
/** Component head collector of one request: entries until the hold releases, then late entries render in place. */
|
|
25
|
+
function createSink(nonce, late) {
|
|
26
|
+
let seq = 0, closed = false;
|
|
27
|
+
const entries = [];
|
|
28
|
+
return {
|
|
29
|
+
nonce,
|
|
30
|
+
entries,
|
|
31
|
+
close: () => void (closed = true),
|
|
32
|
+
push(level, head, component) {
|
|
33
|
+
if (!closed) {
|
|
34
|
+
entries.push({ level, phase: 1, seq: seq++, head });
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
// After the shell: only body-valid tags (links, scripts, styles) still apply, where they render.
|
|
38
|
+
if (head.title !== undefined || head.titleTemplate !== undefined || head.meta?.length || head.html || head.body || head.base)
|
|
39
|
+
late(`${component ?? "A head component"} rendered after the shell (inside a streamed Async boundary): title/meta/html changes are ignored there`);
|
|
40
|
+
return (headTags({ ...(head.links ? { links: head.links } : {}), ...(head.scripts ? { scripts: head.scripts } : {}), ...(head.styles ? { styles: head.styles } : {}) }, { nonce }) || undefined);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Create the app's request handler. */
|
|
45
|
+
export function createApp(config) {
|
|
46
|
+
const { entry, manifest, dev = false, styles = [], routeStyles } = config;
|
|
47
|
+
const inlineFor = config.inlineStyles ? inlineStylesFor(config.inlineStyles) : undefined;
|
|
48
|
+
const option = config.csp ?? nonceCSP;
|
|
49
|
+
let csp = typeof option === "object" ? (nonce) => nonceCSP(nonce, option) : option;
|
|
50
|
+
// `directives`: the merge module loads at startup (pay-per-use, dynamic import) and checks them once —
|
|
51
|
+
// a mistake rejects at startup (unhandled: the process fails like a synchronous throw would); requests
|
|
52
|
+
// arriving before it settles wait for it, later ones take the synchronous path.
|
|
53
|
+
let directives = typeof option === "object" && option.directives
|
|
54
|
+
? import("./strict-csp.js").then(({ strictCSP }) => {
|
|
55
|
+
strictCSP("", option);
|
|
56
|
+
csp = (nonce) => strictCSP(nonce, option);
|
|
57
|
+
directives = null;
|
|
58
|
+
})
|
|
59
|
+
: null;
|
|
60
|
+
// Route export contributions behind each merged head (rank merge with component entries).
|
|
61
|
+
const levels = new WeakMap();
|
|
62
|
+
// Early hints: the per-route Link values live in `early-hints.ts`, loaded at startup only with `earlyHints`.
|
|
63
|
+
const hintOption = dev ? undefined : config.earlyHints;
|
|
64
|
+
let hinter;
|
|
65
|
+
let hintLoading = hintOption
|
|
66
|
+
? import("./early-hints.js").then(({ createHinter }) => {
|
|
67
|
+
hinter = createHinter(hintOption, { styles, routeStyles, inlineFor, base: entry.base ?? "", runtime: manifest?.runtime });
|
|
68
|
+
hintLoading = null;
|
|
69
|
+
})
|
|
70
|
+
: null;
|
|
71
|
+
const warned = new Set();
|
|
72
|
+
const document = config.document ?? entry.document;
|
|
73
|
+
if (!document)
|
|
74
|
+
throw new TypeError("createApp: no document shell (pass `document` or generate the entry with one)");
|
|
75
|
+
const scriptAttrs = (nonce) => (nonce !== undefined ? ` nonce="${nonce.replace(/[^\w+/=-]/g, "")}"` : "");
|
|
76
|
+
// Dev pages: what the data endpoint would answer for this URL, so the client mount needs no request.
|
|
77
|
+
const devAround = (page, status, nonce, locale, mode) => {
|
|
78
|
+
const list = typeof config.devScripts === "function" ? config.devScripts(locale) : (config.devScripts ?? []);
|
|
79
|
+
const scripts = list.map((src) => `<script type="module" src="${src}"${scriptAttrs(nonce)}></script>`).join("");
|
|
80
|
+
// Resumed dev pages: the dev entry only takes over (client render) on the first hot update.
|
|
81
|
+
if (mode === "resume")
|
|
82
|
+
return { start: `<div data-rivet-dev="resume" style="display:contents">`, end: `</div>${scripts}` };
|
|
83
|
+
const body = page.error
|
|
84
|
+
? { error: page.error }
|
|
85
|
+
: {
|
|
86
|
+
data: page.data,
|
|
87
|
+
status,
|
|
88
|
+
...(page.head ? { head: page.head } : {}),
|
|
89
|
+
...(page.action ? { action: page.action } : {}),
|
|
90
|
+
...(page.deps ? { deps: page.deps } : {}),
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
start: `<div data-rivet-dev="" style="display:contents">`,
|
|
94
|
+
end: `</div><script type="application/json" id="rivet-dev-data">${escapeJSON(encodeData(body))}</script>${scripts}`,
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
const respond = (page, status, headers, nonce, request, locals, routeId) => {
|
|
98
|
+
if (directives || fragments || hintLoading)
|
|
99
|
+
return Promise.all([directives, fragments, hintLoading]).then(() => respond(page, status, headers, nonce, request, locals, routeId));
|
|
100
|
+
const extra = routeId === undefined || !routeStyles ? undefined : typeof routeStyles === "function" ? routeStyles(routeId) : routeStyles[routeId];
|
|
101
|
+
if (extra && "then" in extra)
|
|
102
|
+
return extra.then((list) => render(page, status, headers, nonce, request, locals, list, routeId));
|
|
103
|
+
return render(page, status, headers, nonce, request, locals, extra, routeId);
|
|
104
|
+
};
|
|
105
|
+
// Failures after the document started (the status is sent): reported with a digest, which the page's failure marker carries.
|
|
106
|
+
const report = config.onError ?? ((error, _request, info) => console.error("[rivet] digest " + info?.digest + ":", error));
|
|
107
|
+
const render = (page, status, headers, nonce, request, locals, extra, routeId) => {
|
|
108
|
+
const pageStyles = extra?.length ? [...styles, ...extra] : styles;
|
|
109
|
+
const inlineStyles = inlineFor?.(pageStyles);
|
|
110
|
+
let sink;
|
|
111
|
+
if (entry.headComponents) {
|
|
112
|
+
sink = createSink(nonce, (message) => {
|
|
113
|
+
if (dev)
|
|
114
|
+
console.error("[rivet] " + message);
|
|
115
|
+
// Once per message (component + text: bounded by the app's code), not per URL (unbounded).
|
|
116
|
+
else if (!warned.has(message))
|
|
117
|
+
(warned.add(message), console.warn("[rivet] " + message));
|
|
118
|
+
});
|
|
119
|
+
page = { ...page, headSink: sink };
|
|
120
|
+
}
|
|
121
|
+
const signal = request.signal;
|
|
122
|
+
if (hinter && routeId !== undefined) {
|
|
123
|
+
if (!sink)
|
|
124
|
+
hinter.learn(routeId, page.head);
|
|
125
|
+
const links = hinter.links(routeId);
|
|
126
|
+
if (links.length && !headers.has("link"))
|
|
127
|
+
headers.set("link", links.join(", "));
|
|
128
|
+
}
|
|
129
|
+
if (csp && nonce !== undefined && !headers.has("content-security-policy"))
|
|
130
|
+
headers.set("content-security-policy", csp(nonce));
|
|
131
|
+
const i18n = config.i18n;
|
|
132
|
+
const locale = i18n && typeof locals?.locale === "string" && i18n.locales.includes(locals.locale) ? locals.locale : undefined;
|
|
133
|
+
const variant = locale ? i18n.locales.indexOf(locale) : 0;
|
|
134
|
+
const built = (i18n && i18n.manifests?.[locale ?? i18n.locales[0]]) || manifest;
|
|
135
|
+
const devMode = built || !config.devResume ? undefined : typeof config.devResume.mode === "function" ? config.devResume.mode(locale) : config.devResume.mode;
|
|
136
|
+
const pageManifest = built || config.devResume?.manifest;
|
|
137
|
+
const dir = locals?.dir === "rtl" || locals?.dir === "ltr" ? locals.dir : undefined;
|
|
138
|
+
const exports = page.head ? (levels.get(page.head) ?? ranked([page.head], config.head)) : ranked([], config.head);
|
|
139
|
+
// Root layout component entries stay in the browser (that layout never re-renders there).
|
|
140
|
+
const persistent = new Set();
|
|
141
|
+
const option = config.preload?.background ?? entry.preloadBackground;
|
|
142
|
+
const background = typeof option === "function" ? option(page.url) : option;
|
|
143
|
+
const leanOption = config.preload?.lean;
|
|
144
|
+
const lean = typeof leanOption === "function" ? leanOption(page.url) : leanOption;
|
|
145
|
+
const ahead = config.preload?.ahead ?? entry.fetchAhead;
|
|
146
|
+
const run = () => renderDocument(entry.page(page), {
|
|
147
|
+
head: page.head,
|
|
148
|
+
nonce,
|
|
149
|
+
url: page.url,
|
|
150
|
+
status,
|
|
151
|
+
styles: pageStyles,
|
|
152
|
+
...(inlineStyles ? { inlineStyles } : {}),
|
|
153
|
+
...(locale ? { locale } : {}),
|
|
154
|
+
...(dir ? { dir } : {}),
|
|
155
|
+
...(sink ? { managed: { persistent } } : {}),
|
|
156
|
+
}, {
|
|
157
|
+
document,
|
|
158
|
+
...(sink
|
|
159
|
+
? {
|
|
160
|
+
hold: {
|
|
161
|
+
head: () => {
|
|
162
|
+
sink.close();
|
|
163
|
+
for (const { level, head } of sink.entries)
|
|
164
|
+
if (level === 0)
|
|
165
|
+
for (const entry of [...(head.meta ?? []), ...(head.links ?? []), ...(head.scripts ?? [])])
|
|
166
|
+
persistent.add(entry);
|
|
167
|
+
const merged = rankMerge([...exports, ...sink.entries]);
|
|
168
|
+
if (routeId !== undefined)
|
|
169
|
+
hinter?.learn(routeId, merged);
|
|
170
|
+
return merged;
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
: {}),
|
|
175
|
+
...(pageManifest ? { manifest: pageManifest } : {}),
|
|
176
|
+
...(variant ? { variant } : {}),
|
|
177
|
+
...(config.stream ? { stream: config.stream } : {}),
|
|
178
|
+
...(background !== undefined || lean || ahead
|
|
179
|
+
? { preload: { ...(background !== undefined ? { background } : {}), ...(lean ? { lean } : {}), ...(ahead ? { ahead } : {}) } }
|
|
180
|
+
: {}),
|
|
181
|
+
signal,
|
|
182
|
+
...(nonce !== undefined ? { nonce } : {}),
|
|
183
|
+
onError: (error) => {
|
|
184
|
+
// Route rules: a page that rendered an error fallback is not stored (`route-cache.ts` `RENDER_FAILED`).
|
|
185
|
+
request[RENDER_FAILED] = true;
|
|
186
|
+
const digest = _digest();
|
|
187
|
+
report(error, request, { digest, phase: "render", ...(routeId !== undefined ? { route: routeId } : {}) });
|
|
188
|
+
return digest;
|
|
189
|
+
},
|
|
190
|
+
...(config.devScripts && !built ? { around: devAround(page, status, nonce, locale, devMode) } : {}),
|
|
191
|
+
...(devMode === "check" ? { discardState: true } : {}),
|
|
192
|
+
}, { status, headers });
|
|
193
|
+
return withCache
|
|
194
|
+
? withCache({
|
|
195
|
+
cache: config.renderCache,
|
|
196
|
+
...(variant ? { variant } : {}),
|
|
197
|
+
...(nonce !== undefined ? { nonce } : {}),
|
|
198
|
+
signal,
|
|
199
|
+
resumable: pageManifest !== undefined,
|
|
200
|
+
}, run)
|
|
201
|
+
: run();
|
|
202
|
+
};
|
|
203
|
+
// `renderCache`: the scope module (AsyncLocalStorage) loads at startup, pay-per-use like `directives`.
|
|
204
|
+
let withCache;
|
|
205
|
+
let fragments = config.renderCache
|
|
206
|
+
? import("@rivetjs/runtime/server-cache").then((module) => {
|
|
207
|
+
withCache = module.withRenderCache;
|
|
208
|
+
fragments = null;
|
|
209
|
+
})
|
|
210
|
+
: null;
|
|
211
|
+
const app = createHandler({
|
|
212
|
+
routes: entry.routes,
|
|
213
|
+
dev,
|
|
214
|
+
// Every locale's build shares one runtime; any manifest names the built-in decoders' chunk.
|
|
215
|
+
builtins: () => (manifest ?? (config.i18n?.manifests && Object.values(config.i18n.manifests)[0]) ?? config.devResume?.manifest)?.builtins,
|
|
216
|
+
...(config.onError ? { onError: config.onError } : {}),
|
|
217
|
+
...(config.maxBody !== undefined ? { maxBody: config.maxBody } : {}),
|
|
218
|
+
...(hintOption
|
|
219
|
+
? {
|
|
220
|
+
earlyHints(route, request) {
|
|
221
|
+
const send = request[EARLY_HINTS];
|
|
222
|
+
if (send) {
|
|
223
|
+
const links = hinter?.links(route.id) ?? [];
|
|
224
|
+
if (links.length)
|
|
225
|
+
send(links);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
: {}),
|
|
230
|
+
async head(route, data, match) {
|
|
231
|
+
const modules = await entry.universal[route.id]?.();
|
|
232
|
+
if (!modules)
|
|
233
|
+
return config.head ? rankMerge(ranked([], config.head)) : undefined;
|
|
234
|
+
const heads = [];
|
|
235
|
+
for (const [index, module] of modules) {
|
|
236
|
+
const head = module.head;
|
|
237
|
+
heads[index] = typeof head === "function" ? head({ data: data[index], params: match.params, url: match.url }) : head;
|
|
238
|
+
}
|
|
239
|
+
const contributions = ranked(heads, config.head);
|
|
240
|
+
const merged = rankMerge(contributions);
|
|
241
|
+
if (merged)
|
|
242
|
+
levels.set(merged, contributions);
|
|
243
|
+
return merged;
|
|
244
|
+
},
|
|
245
|
+
// A layout whose `head` is a function of its data reruns its loader (the merged head needs it).
|
|
246
|
+
async keepable(route) {
|
|
247
|
+
const modules = await entry.universal[route.id]?.();
|
|
248
|
+
const out = [];
|
|
249
|
+
for (const [index, module] of modules ?? [])
|
|
250
|
+
if (typeof module.head === "function")
|
|
251
|
+
out[index] = false;
|
|
252
|
+
return out;
|
|
253
|
+
},
|
|
254
|
+
render: (input) => respond({
|
|
255
|
+
data: input.data,
|
|
256
|
+
url: input.url,
|
|
257
|
+
head: input.head,
|
|
258
|
+
...(input.action ? { action: input.action } : {}),
|
|
259
|
+
...(input.byURL ? { byURL: input.byURL } : {}),
|
|
260
|
+
...(input.deps ? { deps: input.deps } : {}),
|
|
261
|
+
}, input.status, input.headers, input.nonce, input.request, input.locals, input.route.id),
|
|
262
|
+
renderError: async (input) => respond({
|
|
263
|
+
// The outermost layouts' data (error pages render inside the layouts that loaded).
|
|
264
|
+
data: input.layouts ?? [],
|
|
265
|
+
url: input.url,
|
|
266
|
+
head: { title: input.status === 404 ? "Not found" : "Error" },
|
|
267
|
+
error: { status: input.status, message: input.message },
|
|
268
|
+
}, input.status, input.headers, input.nonce, input.request, await errorLocals(input.locals, input.request, input.url)),
|
|
269
|
+
});
|
|
270
|
+
const given = config.routeRules ?? entry.routeRules;
|
|
271
|
+
if (!given || !Object.keys(given).length)
|
|
272
|
+
return app;
|
|
273
|
+
// Dev: headers only (a stored page would hide edits).
|
|
274
|
+
const rules = dev
|
|
275
|
+
? Object.fromEntries(Object.entries(given).map(([pattern, rule]) => [pattern, rule.headers ? { headers: rule.headers } : {}]))
|
|
276
|
+
: given;
|
|
277
|
+
// Route rules: their module loads at startup (pay-per-use); malformed rules reject there (unhandled: the process
|
|
278
|
+
// fails like a synchronous throw would). Requests before it settles wait for it.
|
|
279
|
+
const policy = {
|
|
280
|
+
policy: (nonce) => (csp ? csp(nonce) : false),
|
|
281
|
+
// The default / `CSPOptions` nonce policy becomes hashes in stored pages; a `csp` function keeps its nonce per page.
|
|
282
|
+
hash: config.csp === undefined ? {} : typeof config.csp === "object" ? config.csp : null,
|
|
283
|
+
};
|
|
284
|
+
let cached = null;
|
|
285
|
+
const loading = import("./route-cache.js").then(({ withRouteRules }) => (cached = withRouteRules(app, rules, policy, entry.base ?? "", config.routeCache)));
|
|
286
|
+
const handler = (request, context) => (cached ? cached(request, context) : loading.then((ready) => ready(request, context)));
|
|
287
|
+
// The node adapter's fast path (a stored page before a Request is built), once the module loaded.
|
|
288
|
+
Object.defineProperty(handler, ROUTE_CACHE, { get: () => (cached ? cached[ROUTE_CACHE] : undefined) });
|
|
289
|
+
return handler;
|
|
290
|
+
// Error pages without a settled locale: the app's resolver, else the URL's locale prefix.
|
|
291
|
+
async function errorLocals(locals, request, url) {
|
|
292
|
+
const i18n = config.i18n;
|
|
293
|
+
if (!i18n || (typeof locals.locale === "string" && i18n.locales.includes(locals.locale)))
|
|
294
|
+
return locals;
|
|
295
|
+
let locale = await i18n.resolve?.(request);
|
|
296
|
+
if (!locale || !i18n.locales.includes(locale)) {
|
|
297
|
+
const first = url.pathname.split("/")[1]?.toLowerCase();
|
|
298
|
+
locale = i18n.locales.find((l) => l.toLowerCase() === first);
|
|
299
|
+
}
|
|
300
|
+
return locale ? { ...locals, locale } : locals;
|
|
301
|
+
}
|
|
302
|
+
}
|
package/server/body.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Where a directly attached page body goes. Web-standard: no Node types. */
|
|
2
|
+
export interface BodySink {
|
|
3
|
+
/**
|
|
4
|
+
* One chunk: a view into a pooled slab. Call `done` once its bytes are no longer needed (written out,
|
|
5
|
+
* copied, or dropped) — the slab is reused after every handed-off view of it is done. Returns a wait
|
|
6
|
+
* when the sink applies backpressure; throws once the sink is closed (the render then ends).
|
|
7
|
+
*/
|
|
8
|
+
write(chunk: Uint8Array, done: () => void): Promise<void> | undefined;
|
|
9
|
+
/** The body is complete. */
|
|
10
|
+
end(): void;
|
|
11
|
+
/** The render failed after the Response was handed out. */
|
|
12
|
+
error(error: unknown): void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Own property of `renderPage` / `renderDocument` Responses: `(sink) => boolean`. True: every body byte
|
|
16
|
+
* (already rendered and later) goes to `sink` and the Response's `body` stream stays unread forever;
|
|
17
|
+
* false: the stream was already read (a copied Response, a consumer that started) — read the body.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DIRECT_BODY: unique symbol;
|
|
20
|
+
export type DirectBody = (sink: BodySink) => boolean;
|
package/server/body.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Direct page bodies: a host adapter (node) takes a rendered page's bytes straight from the renderer's
|
|
2
|
+
// slabs instead of reading the web `ReadableStream` (no queue, read promises or per-chunk races).
|
|
3
|
+
/**
|
|
4
|
+
* Own property of `renderPage` / `renderDocument` Responses: `(sink) => boolean`. True: every body byte
|
|
5
|
+
* (already rendered and later) goes to `sink` and the Response's `body` stream stays unread forever;
|
|
6
|
+
* false: the stream was already read (a copied Response, a consumer that started) — read the body.
|
|
7
|
+
*/
|
|
8
|
+
export const DIRECT_BODY = Symbol.for("rivet.body");
|