@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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Static files for the Bun and Deno servers: the client directory indexed once at start (both
|
|
2
|
+
// runtimes implement `node:fs` / `node:path`), looked up per GET/HEAD by URL path. Same rules as the
|
|
3
|
+
// node adapter: below the base only, no `\0` or dot segments (except a root `.well-known/`), hashed
|
|
4
|
+
// `<base>/assets/` immutable, everything else `no-cache` + weak ETag (304 on `If-None-Match`),
|
|
5
|
+
// precompressed `.br` / `.gz` siblings when the client accepts them. Directory pages
|
|
6
|
+
// (`a/index.html`, `a.html`, as the static adapter writes them) answer at `/a` and `/a/`.
|
|
7
|
+
import { readdirSync, realpathSync, statSync } from "node:fs";
|
|
8
|
+
import { extname, join, relative, sep } from "node:path";
|
|
9
|
+
const MIME = {
|
|
10
|
+
".html": "text/html; charset=utf-8",
|
|
11
|
+
".js": "text/javascript; charset=utf-8",
|
|
12
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
13
|
+
".css": "text/css; charset=utf-8",
|
|
14
|
+
".json": "application/json; charset=utf-8",
|
|
15
|
+
".map": "application/json; charset=utf-8",
|
|
16
|
+
".svg": "image/svg+xml",
|
|
17
|
+
".txt": "text/plain; charset=utf-8",
|
|
18
|
+
".xml": "application/xml; charset=utf-8",
|
|
19
|
+
".webmanifest": "application/manifest+json",
|
|
20
|
+
".wasm": "application/wasm",
|
|
21
|
+
".png": "image/png",
|
|
22
|
+
".jpg": "image/jpeg",
|
|
23
|
+
".jpeg": "image/jpeg",
|
|
24
|
+
".gif": "image/gif",
|
|
25
|
+
".webp": "image/webp",
|
|
26
|
+
".avif": "image/avif",
|
|
27
|
+
".ico": "image/x-icon",
|
|
28
|
+
".woff2": "font/woff2",
|
|
29
|
+
".woff": "font/woff",
|
|
30
|
+
};
|
|
31
|
+
/** Index `client` (served under `base`); returns the per-request lookup. */
|
|
32
|
+
export function staticFiles(client, base, immutable) {
|
|
33
|
+
const files = new Map();
|
|
34
|
+
let root;
|
|
35
|
+
try {
|
|
36
|
+
root = realpathSync(client);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return () => null;
|
|
40
|
+
}
|
|
41
|
+
const entries = readdirSync(root, { recursive: true, withFileTypes: true });
|
|
42
|
+
const names = new Set(entries.filter((entry) => entry.isFile()).map((entry) => join(entry.parentPath, entry.name)));
|
|
43
|
+
for (const full of names) {
|
|
44
|
+
const key = relative(root, full).split(sep).join("/");
|
|
45
|
+
const segments = key.split("/");
|
|
46
|
+
if (segments.some((segment, i) => segment.startsWith(".") && !(i === 0 && segment === ".well-known")))
|
|
47
|
+
continue;
|
|
48
|
+
if (/\.(br|gz)$/.test(key) && names.has(full.slice(0, -3)))
|
|
49
|
+
continue;
|
|
50
|
+
const info = statSync(full);
|
|
51
|
+
const url = base + "/" + key;
|
|
52
|
+
const file = {
|
|
53
|
+
path: full,
|
|
54
|
+
type: MIME[extname(key).toLowerCase()] ?? "application/octet-stream",
|
|
55
|
+
etag: `W/"${info.size.toString(16)}-${Math.floor(info.mtimeMs).toString(16)}"`,
|
|
56
|
+
modified: info.mtime.toUTCString(),
|
|
57
|
+
immutable: immutable.some((prefix) => url.startsWith(prefix)),
|
|
58
|
+
...(names.has(full + ".br") ? { br: full + ".br" } : {}),
|
|
59
|
+
...(names.has(full + ".gz") ? { gz: full + ".gz" } : {}),
|
|
60
|
+
};
|
|
61
|
+
files.set(url, file);
|
|
62
|
+
const page = /^(.*?)(?:\/?index)?\.html$/.exec(key);
|
|
63
|
+
if (page && key !== "404.html" && key !== "200.html") {
|
|
64
|
+
const at = page[1] ? base + "/" + page[1] : base;
|
|
65
|
+
for (const alias of [at || "/", at + "/"])
|
|
66
|
+
if (!files.has(alias))
|
|
67
|
+
files.set(alias, file);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return (request, pathname) => {
|
|
71
|
+
let decoded;
|
|
72
|
+
try {
|
|
73
|
+
decoded = decodeURIComponent(pathname);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (decoded.includes("\0"))
|
|
79
|
+
return null;
|
|
80
|
+
const file = files.get(decoded);
|
|
81
|
+
if (!file)
|
|
82
|
+
return null;
|
|
83
|
+
const match = request.headers.get("if-none-match");
|
|
84
|
+
if (match && match.split(",").some((tag) => tag.trim() === "*" || tag.trim().replace(/^W\//, "") === file.etag.slice(2)))
|
|
85
|
+
return 304;
|
|
86
|
+
const headers = {
|
|
87
|
+
"Content-Type": file.type,
|
|
88
|
+
"Cache-Control": file.immutable ? "public, max-age=31536000, immutable" : "no-cache",
|
|
89
|
+
ETag: file.etag,
|
|
90
|
+
"Last-Modified": file.modified,
|
|
91
|
+
};
|
|
92
|
+
if (file.br || file.gz) {
|
|
93
|
+
headers.Vary = "Accept-Encoding";
|
|
94
|
+
const accept = request.headers.get("accept-encoding") ?? "";
|
|
95
|
+
const encoding = file.br && /\bbr\b/.test(accept) ? "br" : file.gz && /\bgzip\b/.test(accept) ? "gzip" : null;
|
|
96
|
+
if (encoding) {
|
|
97
|
+
headers["Content-Encoding"] = encoding;
|
|
98
|
+
return { path: encoding === "br" ? file.br : file.gz, headers };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { path: file.path, headers };
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface IdleMemoryOptions {
|
|
2
|
+
/** Quiet time (ms) after the last request ends before the first check. Default 1000. */
|
|
3
|
+
delay?: number;
|
|
4
|
+
/** Time (ms) between a nudge and the next check: two memory-reducer ticks (8 s each) plus slack. Default 17000. */
|
|
5
|
+
recheck?: number;
|
|
6
|
+
/** Nudges per idle period before accepting the current heap as settled. Default 5 (inside V8's ~110 s smoothing window). */
|
|
7
|
+
attempts?: number;
|
|
8
|
+
/** Committed V8 heap bytes (default `getHeapStatistics().total_heap_size`). Tests replace it. */
|
|
9
|
+
committed?: () => number;
|
|
10
|
+
/** The embedder allocation (default `new Script("")`). Tests replace it. */
|
|
11
|
+
nudge?: () => void;
|
|
12
|
+
}
|
|
13
|
+
export interface IdleMemory {
|
|
14
|
+
/** A request started (counter only). */
|
|
15
|
+
request(): void;
|
|
16
|
+
/** The last in-flight request ended. */
|
|
17
|
+
idle(): void;
|
|
18
|
+
/** Stop for good (server closing). */
|
|
19
|
+
stop(): void;
|
|
20
|
+
}
|
|
21
|
+
/** True on Node; Bun (JSC) and Deno have different heaps and are left alone. */
|
|
22
|
+
export declare const idleMemorySupported: () => boolean;
|
|
23
|
+
export declare function idleMemory(inflight: () => number, options?: IdleMemoryOptions): IdleMemory;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Idle memory return for the Node adapter: lets V8's memory reducer shrink the heap once a server goes
|
|
2
|
+
// quiet after traffic. Node-only (`node:v8`, `node:vm`).
|
|
3
|
+
//
|
|
4
|
+
// Why: V8's memory reducer starts its shrinking GCs only when young, old AND embedder (cppgc) allocation
|
|
5
|
+
// rates are all "low" (`Heap::HasLowAllocationRate`). A rate of exactly 0 counts as high
|
|
6
|
+
// (`ComputeMutatorUtilizationImpl` returns 0 for a zero mutator speed). A Node process that has never
|
|
7
|
+
// allocated a cppgc object has an embedder rate of exactly 0 forever, so after a traffic spike the
|
|
8
|
+
// reducer reports "high alloc" on every 8 s tick and the committed heap + pooled pages stay resident until
|
|
9
|
+
// its 100 s watchdog fires (measured: Node 24 / V8 13.6 and Node 26 / V8 14.6). One tiny embedder
|
|
10
|
+
// allocation (a `vm.Script`, whose wrapper is a 56-byte cppgc object since Node 22) makes the smoothed
|
|
11
|
+
// embedder rate non-zero; one reducer tick later it has decayed to near zero, which is "low", and the
|
|
12
|
+
// reducer runs as V8 intends. See docs/design/research/2026-10-07-v8-memory-reducer-embedder-rate.md.
|
|
13
|
+
//
|
|
14
|
+
// Cost model: per request only a counter increment (`request()`) and, when the last in-flight request
|
|
15
|
+
// ends, an `idle()` call that arms a timer if none is pending. At most one unref'd timer exists; while
|
|
16
|
+
// requests keep coming it fires at most once per `delay` and re-arms only when the server is idle at that
|
|
17
|
+
// moment. Nothing runs on a server that has never served a request, and an idle server stops after the
|
|
18
|
+
// heap shrank, after `attempts` tries, or when committed memory is not above the last settled level.
|
|
19
|
+
import { getHeapStatistics } from "node:v8";
|
|
20
|
+
import { Script } from "node:vm";
|
|
21
|
+
const MiB = 1024 * 1024;
|
|
22
|
+
/** True when committed memory grew past the settled level by V8's own memory-reducer re-arm rule (×1.1 or +10 MiB). */
|
|
23
|
+
const grown = (committed, settled) => committed >= Math.max(settled * 1.1, settled + 10 * MiB);
|
|
24
|
+
/** True on Node; Bun (JSC) and Deno have different heaps and are left alone. */
|
|
25
|
+
export const idleMemorySupported = () => {
|
|
26
|
+
const versions = process.versions;
|
|
27
|
+
return typeof versions.v8 === "string" && versions.bun === undefined && versions.deno === undefined;
|
|
28
|
+
};
|
|
29
|
+
export function idleMemory(inflight, options = {}) {
|
|
30
|
+
const { delay = 1000, recheck = 17_000, attempts = 5 } = options;
|
|
31
|
+
const committed = options.committed ?? (() => getHeapStatistics().total_heap_size);
|
|
32
|
+
const nudge = options.nudge ??
|
|
33
|
+
(() => {
|
|
34
|
+
new Script("");
|
|
35
|
+
});
|
|
36
|
+
let timer = null, stopped = false, seen = 0, // requests started (wraps harmlessly: only compared for equality)
|
|
37
|
+
mark = 0, // `seen` when the pending timer was armed
|
|
38
|
+
attempt = 0, before = 0, // committed bytes at the last nudge
|
|
39
|
+
settled = committed(); // committed bytes once the heap was last left as is (boot, shrink, give-up)
|
|
40
|
+
const arm = (ms) => {
|
|
41
|
+
mark = seen;
|
|
42
|
+
timer = setTimeout(check, ms);
|
|
43
|
+
timer.unref();
|
|
44
|
+
};
|
|
45
|
+
const settle = (bytes) => {
|
|
46
|
+
settled = bytes;
|
|
47
|
+
attempt = 0;
|
|
48
|
+
};
|
|
49
|
+
function check() {
|
|
50
|
+
timer = null;
|
|
51
|
+
if (stopped)
|
|
52
|
+
return;
|
|
53
|
+
if (inflight() > 0) {
|
|
54
|
+
// Busy: the next `idle()` arms again.
|
|
55
|
+
attempt = 0;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (seen !== mark) {
|
|
59
|
+
// Requests came and went: wait for a full quiet `delay`.
|
|
60
|
+
attempt = 0;
|
|
61
|
+
arm(delay);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const now = committed();
|
|
65
|
+
if (attempt > 0 && now < before * 0.9)
|
|
66
|
+
return settle(now); // the reducer ran: memory returned
|
|
67
|
+
if (!grown(now, settled))
|
|
68
|
+
return settle(Math.min(settled, now)); // nothing worth returning
|
|
69
|
+
if (attempt >= attempts)
|
|
70
|
+
return settle(now); // live set grew: accept it until the next spike
|
|
71
|
+
before = now;
|
|
72
|
+
attempt++;
|
|
73
|
+
nudge();
|
|
74
|
+
arm(recheck);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
request: () => {
|
|
78
|
+
seen = (seen + 1) | 0;
|
|
79
|
+
},
|
|
80
|
+
idle: () => {
|
|
81
|
+
if (timer === null && !stopped)
|
|
82
|
+
arm(delay);
|
|
83
|
+
},
|
|
84
|
+
stop: () => {
|
|
85
|
+
stopped = true;
|
|
86
|
+
if (timer !== null)
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
timer = null;
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GeneratedEntry } from "../server/app.js";
|
|
2
|
+
import type { BuildManifests } from "./assemble.js";
|
|
3
|
+
import { type FetchOptions } from "./fetch.js";
|
|
4
|
+
export interface NetlifyOptions extends Omit<FetchOptions, "serveAssets"> {
|
|
5
|
+
/**
|
|
6
|
+
* Edge Functions run before static files: GET/HEAD requests for these URL paths return `undefined`,
|
|
7
|
+
* which hands the request on to Netlify's static file (the packaged edge function lists the
|
|
8
|
+
* publish directory's files). Functions v2 use `preferStatic` instead.
|
|
9
|
+
*/
|
|
10
|
+
staticPaths?: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export type NetlifyHandler = (request: Request, context?: unknown) => Promise<Response> | undefined;
|
|
13
|
+
export declare function create(entry: GeneratedEntry, build: BuildManifests, options?: NetlifyOptions): NetlifyHandler;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createFetch } from "./fetch.js";
|
|
2
|
+
export function create(entry, build, options = {}) {
|
|
3
|
+
const { staticPaths, ...fetchOptions } = options;
|
|
4
|
+
const app = createFetch(entry, build, {
|
|
5
|
+
// Netlify's context carries the client address it saw.
|
|
6
|
+
address: (_request, _env, context) => context?.ip,
|
|
7
|
+
...fetchOptions,
|
|
8
|
+
});
|
|
9
|
+
const files = staticPaths?.length ? new Set(staticPaths) : null;
|
|
10
|
+
return (request, context) => {
|
|
11
|
+
if (files && (request.method === "GET" || request.method === "HEAD") && files.has(new URL(request.url).pathname))
|
|
12
|
+
return undefined;
|
|
13
|
+
return app.fetch(request, undefined, context);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
2
|
+
import type { BodyLimit } from "../server/security.js";
|
|
3
|
+
import type { ErrorInfo } from "../server/handler.js";
|
|
4
|
+
import type { AppConfig } from "../server/app.js";
|
|
5
|
+
/** Per-request context passed to the handler. */
|
|
6
|
+
export interface RequestContext {
|
|
7
|
+
/** Fresh CSP nonce (base64) for inline scripts the handler emits. */
|
|
8
|
+
readonly nonce: string;
|
|
9
|
+
/** Client address, when known. */
|
|
10
|
+
readonly address?: string;
|
|
11
|
+
}
|
|
12
|
+
/** A request handler (web `Request` → `Response`) served by the Node adapter. */
|
|
13
|
+
export type Handler = (request: Request, context: RequestContext) => Response | Promise<Response>;
|
|
14
|
+
/** Options of `serve()`. */
|
|
15
|
+
export interface ServeOptions {
|
|
16
|
+
handler: Handler;
|
|
17
|
+
/** Built client directory served before the handler (e.g. `dist/client`). */
|
|
18
|
+
client?: string;
|
|
19
|
+
/** Paths (URL pathname) whose files are content-hashed: `Cache-Control: immutable`. Default: `<base>/assets/`. */
|
|
20
|
+
immutable?: (pathname: string) => boolean;
|
|
21
|
+
/** URL base path (`/app`): client files are served under it (`/app/assets/x.js` → `<client>/assets/x.js`); other paths reach the handler. */
|
|
22
|
+
base?: string;
|
|
23
|
+
port?: number;
|
|
24
|
+
host?: string;
|
|
25
|
+
/**
|
|
26
|
+
* How `client` files are found. Default `"index"`: the directory is indexed once at start (real
|
|
27
|
+
* paths, sizes, precompressed `.br`/`.gz` siblings), so page requests cost a map lookup; files added
|
|
28
|
+
* later are not served. `"fs"`: look each GET up on disk (a directory that changes while serving).
|
|
29
|
+
*/
|
|
30
|
+
static?: "index" | "fs";
|
|
31
|
+
/** Compress responses by `Accept-Encoding` (default true). */
|
|
32
|
+
compress?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Send `103 Early Hints` when the app asks (`createApp({ earlyHints })`; `start({ earlyHints })` sets both), to
|
|
35
|
+
* HTTP/1.1 clients only (1xx are not allowed for 1.0). Browsers use 103s over HTTP/2+ only: behind a proxy or CDN
|
|
36
|
+
* that forwards them (nginx `early_hints`, Cloudflare, Fastly). Default false.
|
|
37
|
+
*/
|
|
38
|
+
earlyHints?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Bind with `SO_REUSEPORT` (Node ≥ 22.12): several processes listen on the same `port` and the kernel balances new
|
|
41
|
+
* connections between them. Linux and FreeBSD only (elsewhere `serve()` throws: macOS does not balance, Windows
|
|
42
|
+
* refuses). With `start({ workers })` each worker then binds its own socket instead of taking connections from the
|
|
43
|
+
* primary. Needs a fixed `port` (0 throws). Default false.
|
|
44
|
+
*/
|
|
45
|
+
reusePort?: boolean;
|
|
46
|
+
/** Milliseconds to drain in-flight requests on close (default 10000). */
|
|
47
|
+
grace?: number;
|
|
48
|
+
/** Log unexpected failures (default console.error); app failures get `info` (`digest`, `phase`, `route`). */
|
|
49
|
+
onError?: (error: unknown, request?: Request, info?: ErrorInfo) => void;
|
|
50
|
+
/** Extra headers on every response; defaults add nosniff + referrer policy. */
|
|
51
|
+
headers?: Record<string, string>;
|
|
52
|
+
/**
|
|
53
|
+
* Fixed public origin (`https://shop.example`) for request URLs, regardless of `Host`: set it behind
|
|
54
|
+
* a proxy so the handler's same-origin checks compare with what the browser sees.
|
|
55
|
+
*/
|
|
56
|
+
origin?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Off by default: `X-Forwarded-*` headers are ignored (clients can send any). Behind reverse proxies
|
|
59
|
+
* you control, say which hops to trust; the client address is then the `X-Forwarded-For` entry the
|
|
60
|
+
* outermost trusted proxy appended (entries left of it are client-supplied), and protocol / host come
|
|
61
|
+
* from `X-Forwarded-Proto` / `X-Forwarded-Host` at the same hop (their leftmost value when shorter).
|
|
62
|
+
* - `true` or `1`: one proxy (the peer connecting to this server); `n`: the last `n` hops.
|
|
63
|
+
* - `(address, hop) => boolean`: trust per hop from the socket inward (hop 0 = the peer), like
|
|
64
|
+
* Express `trust proxy`; the first untrusted address is the client. Ignored for the origin when
|
|
65
|
+
* `origin` is set.
|
|
66
|
+
*/
|
|
67
|
+
trustProxy?: boolean | number | ((address: string, hop: number) => boolean);
|
|
68
|
+
/**
|
|
69
|
+
* Return memory to the OS when the server goes idle after traffic (default true on Node). V8's memory
|
|
70
|
+
* reducer never runs in a process without embedder (cppgc) allocations, so a heap grown by a spike
|
|
71
|
+
* stays resident for ~100 s; after 1 s without requests the adapter makes one tiny embedder allocation
|
|
72
|
+
* (re-checked every 17 s, at most 5 times) while committed heap is above its settled level. No work
|
|
73
|
+
* per request beyond a counter; one unref'd timer at most. `false` turns it off.
|
|
74
|
+
*/
|
|
75
|
+
idleMemory?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A running server returned by `start()`: this process's own HTTP server (`ServerServing`, also what `serve()` and
|
|
79
|
+
* `start()` without `workers` return), or the primary of `start({ workers })` (`PrimaryServing`, no server).
|
|
80
|
+
* Narrow on `server` or `workers`.
|
|
81
|
+
*/
|
|
82
|
+
export type Serving = ServerServing | PrimaryServing;
|
|
83
|
+
/** A server listening in this process: `serve()`, `start()` with one worker, and each `start({ workers })` worker. */
|
|
84
|
+
export interface ServerServing {
|
|
85
|
+
readonly server: Server;
|
|
86
|
+
readonly url: string;
|
|
87
|
+
readonly workers?: undefined;
|
|
88
|
+
/** Stop accepting, report not-ready, drain within `grace`, then destroy remaining sockets. */
|
|
89
|
+
close(): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
/** The primary process of `start({ workers })`: forks the workers and serves nothing itself. */
|
|
92
|
+
export interface PrimaryServing {
|
|
93
|
+
readonly server?: undefined;
|
|
94
|
+
readonly url: string;
|
|
95
|
+
/** How many worker processes serve. */
|
|
96
|
+
readonly workers: number;
|
|
97
|
+
/** Drain every worker (each within its `grace`), then resolve once all exited. */
|
|
98
|
+
close(): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
/** Pick `br` or `gzip` from an Accept-Encoding header: the higher `q` (a tie: `br`); q=0 excludes. */
|
|
101
|
+
export declare function negotiate(header: string | null | undefined): "br" | "gzip" | null;
|
|
102
|
+
/** `If-None-Match` (a list, `*`, weak or strong tags) matches `etag` (weak comparison, as for GET). */
|
|
103
|
+
export declare function etagMatches(header: string | undefined, etag: string): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* A single `Range: bytes=…` of a `size`-byte file: `[start, end]` (inclusive), `"unsatisfiable"`
|
|
106
|
+
* (416), or null to send the whole file (no header, several ranges, another unit, malformed).
|
|
107
|
+
*/
|
|
108
|
+
export declare function byteRange(header: string | undefined, size: number): [number, number] | "unsatisfiable" | null;
|
|
109
|
+
/** Create the Node request listener (exposed for custom servers and tests). */
|
|
110
|
+
export declare function createListener(options: ServeOptions): {
|
|
111
|
+
listener: (req: IncomingMessage, res: ServerResponse) => void;
|
|
112
|
+
drain: () => void;
|
|
113
|
+
idle: () => Promise<void>;
|
|
114
|
+
};
|
|
115
|
+
/** Listen on `port` (0 = ephemeral). Resolves once listening. */
|
|
116
|
+
export declare function serve(options: ServeOptions): Promise<ServerServing>;
|
|
117
|
+
/** Close on SIGTERM/SIGINT once; exits the process after draining. */
|
|
118
|
+
export declare function closeOnSignals(serving: Serving, signals?: NodeJS.Signals[]): void;
|
|
119
|
+
/** Options of `start()`: serve a build output directory. */
|
|
120
|
+
export interface StartOptions extends Omit<ServeOptions, "handler" | "client" | "earlyHints"> {
|
|
121
|
+
/** Build output with `client/` and `server/` (default `dist`). */
|
|
122
|
+
dist?: string;
|
|
123
|
+
/** Out-of-order streaming for pending `Async` boundaries. */
|
|
124
|
+
stream?: {
|
|
125
|
+
deadline?: number;
|
|
126
|
+
};
|
|
127
|
+
/** i18n: per-locale client builds (`client/<locale>/`, see `loadBuild`); their `assets/` are immutable. */
|
|
128
|
+
locales?: readonly string[];
|
|
129
|
+
/** Request body limit for unsafe methods (413 past it); default 1 MiB, multipart 10 MiB. */
|
|
130
|
+
maxBody?: BodyLimit;
|
|
131
|
+
/** i18n: error-page locale when the request has none settled (see `loadBuild`). */
|
|
132
|
+
resolveLocale?: (request: Request) => string | undefined | Promise<string | undefined>;
|
|
133
|
+
/** HTML pages' Content-Security-Policy (`createApp` `csp`): default `strictCSP`; `{ trustedTypes, directives }` (`strictCSP` with them), a `(nonce) => policy` function, or `false` (none). */
|
|
134
|
+
csp?: AppConfig["csp"];
|
|
135
|
+
/** Preload planner (`createApp` `preload`): `background` byte budget of idle-time visible prefetch, `lean` (nothing before an interaction); per page when a function. */
|
|
136
|
+
preload?: AppConfig["preload"];
|
|
137
|
+
/** Route rules (`createApp` `routeRules`; default: the entry's, `rivetRouter({ routeRules })`): headers + in-process page cache per path pattern; stored pages are served before a Request is built. */
|
|
138
|
+
routeRules?: AppConfig["routeRules"];
|
|
139
|
+
/** The route rules' page store (`createApp` `routeCache`: bounds, `origin`; default `origin`: this adapter's). */
|
|
140
|
+
routeCache?: AppConfig["routeCache"];
|
|
141
|
+
/** Cross-request fragment cache (`createApp` `renderCache`, `createRenderCache()` from `@rivetjs/runtime/server-cache`). */
|
|
142
|
+
renderCache?: AppConfig["renderCache"];
|
|
143
|
+
/**
|
|
144
|
+
* Worker processes (node:cluster): `"auto"` = `os.availableParallelism()`, default 1 (this process serves). With
|
|
145
|
+
* more, this process becomes the primary: it forks that many copies of the running script, each serving the build,
|
|
146
|
+
* restarts crashed ones and resolves with `{ url, workers, close }` (close drains every worker). Connections are
|
|
147
|
+
* handed out round robin by the primary, or balanced by the kernel with `reusePort` (Linux). The route cache is per
|
|
148
|
+
* worker; `revalidate()` reaches every worker through the primary. Call `start()` at the top level of the script
|
|
149
|
+
* (workers run it again).
|
|
150
|
+
*/
|
|
151
|
+
workers?: number | "auto";
|
|
152
|
+
/** Early hints (`createApp` `earlyHints`): `103 Early Hints` + `Link` headers for pages (stylesheets, build-asset fonts, `links`; `runtime: true` adds the resume entry). Default off. */
|
|
153
|
+
earlyHints?: AppConfig["earlyHints"];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Serve a production build of a file-routed app: resume manifest, entry stylesheets, client assets. Without `workers`
|
|
157
|
+
* (or `workers: 1`) resolves with this process's server; with `workers` resolves with a `ServerServing` in each
|
|
158
|
+
* worker and a `PrimaryServing` (no `server`) in the primary.
|
|
159
|
+
*/
|
|
160
|
+
export declare function start(options?: StartOptions & {
|
|
161
|
+
workers?: 1;
|
|
162
|
+
}): Promise<ServerServing>;
|
|
163
|
+
export declare function start(options?: StartOptions): Promise<Serving>;
|