@rivetjs/router 0.0.0-stage → 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/adapters/assemble.d.ts +32 -0
  4. package/adapters/assemble.js +32 -0
  5. package/adapters/build.d.ts +24 -0
  6. package/adapters/build.js +43 -0
  7. package/adapters/bun.d.ts +18 -0
  8. package/adapters/bun.js +44 -0
  9. package/adapters/cloudflare.d.ts +5 -0
  10. package/adapters/cloudflare.js +8 -0
  11. package/adapters/cluster.d.ts +7 -0
  12. package/adapters/cluster.js +130 -0
  13. package/adapters/csr.d.ts +12 -0
  14. package/adapters/csr.js +11 -0
  15. package/adapters/deno.d.ts +13 -0
  16. package/adapters/deno.js +35 -0
  17. package/adapters/fetch.d.ts +55 -0
  18. package/adapters/fetch.js +72 -0
  19. package/adapters/files.d.ts +20 -0
  20. package/adapters/files.js +155 -0
  21. package/adapters/idle-memory.d.ts +23 -0
  22. package/adapters/idle-memory.js +91 -0
  23. package/adapters/netlify.d.ts +13 -0
  24. package/adapters/netlify.js +15 -0
  25. package/adapters/node.d.ts +180 -0
  26. package/adapters/node.js +986 -0
  27. package/adapters/pack.d.ts +39 -0
  28. package/adapters/pack.js +360 -0
  29. package/adapters/server-target.d.ts +17 -0
  30. package/adapters/server-target.js +8 -0
  31. package/adapters/static.d.ts +84 -0
  32. package/adapters/static.js +231 -0
  33. package/adapters/target-build.d.ts +95 -0
  34. package/adapters/target-build.js +118 -0
  35. package/adapters/vercel-edge.d.ts +7 -0
  36. package/adapters/vercel-edge.js +9 -0
  37. package/adapters/vercel.d.ts +35 -0
  38. package/adapters/vercel.js +45 -0
  39. package/build/codegen.d.ts +97 -0
  40. package/build/codegen.js +1037 -0
  41. package/build/dev-errors.d.ts +23 -0
  42. package/build/dev-errors.js +247 -0
  43. package/build/lazy-rows.d.ts +19 -0
  44. package/build/lazy-rows.js +59 -0
  45. package/build/migrate.d.ts +31 -0
  46. package/build/migrate.js +385 -0
  47. package/build/reach.d.ts +60 -0
  48. package/build/reach.js +302 -0
  49. package/build/route-css.d.ts +54 -0
  50. package/build/route-css.js +216 -0
  51. package/build/routes.d.ts +55 -0
  52. package/build/routes.js +219 -0
  53. package/build/sync.d.ts +20 -0
  54. package/build/sync.js +16 -0
  55. package/build/vite.d.ts +208 -0
  56. package/build/vite.js +861 -0
  57. package/cli/rivet.d.ts +2 -0
  58. package/cli/rivet.js +101 -0
  59. package/client/active.d.ts +6 -0
  60. package/client/active.js +22 -0
  61. package/client/announce.d.ts +2 -0
  62. package/client/announce.js +21 -0
  63. package/client/dev.d.ts +41 -0
  64. package/client/dev.js +91 -0
  65. package/client/index.d.ts +156 -0
  66. package/client/index.js +714 -0
  67. package/client/layouts.d.ts +110 -0
  68. package/client/layouts.js +310 -0
  69. package/client/leave.d.ts +40 -0
  70. package/client/leave.js +75 -0
  71. package/client/link.d.ts +48 -0
  72. package/client/link.jsx +65 -0
  73. package/client/live.d.ts +57 -0
  74. package/client/live.js +170 -0
  75. package/client/navigate.d.ts +71 -0
  76. package/client/navigate.js +120 -0
  77. package/client/navigation.d.ts +28 -0
  78. package/client/navigation.js +14 -0
  79. package/client/params.d.ts +16 -0
  80. package/client/params.js +28 -0
  81. package/client/route-context.d.ts +32 -0
  82. package/client/route-context.js +21 -0
  83. package/client/scroll.d.ts +16 -0
  84. package/client/scroll.js +80 -0
  85. package/client/search.d.ts +8 -0
  86. package/client/search.js +17 -0
  87. package/client/tailwind.css +8 -0
  88. package/client/transition-helpers.d.ts +28 -0
  89. package/client/transition-helpers.js +63 -0
  90. package/client/transitions.css +90 -0
  91. package/client/transitions.d.ts +21 -0
  92. package/client/transitions.js +156 -0
  93. package/index.d.ts +135 -0
  94. package/index.js +8 -0
  95. package/package.json +156 -4
  96. package/server/app.d.ts +175 -0
  97. package/server/app.js +335 -0
  98. package/server/body.d.ts +20 -0
  99. package/server/body.js +8 -0
  100. package/server/deferred.d.ts +12 -0
  101. package/server/deferred.js +60 -0
  102. package/server/early-hints.d.ts +34 -0
  103. package/server/early-hints.js +63 -0
  104. package/server/handler.d.ts +240 -0
  105. package/server/handler.js +661 -0
  106. package/server/index.d.ts +9 -0
  107. package/server/index.js +6 -0
  108. package/server/inline-styles.d.ts +21 -0
  109. package/server/inline-styles.js +28 -0
  110. package/server/module.d.ts +14 -0
  111. package/server/module.js +96 -0
  112. package/server/render.d.ts +96 -0
  113. package/server/render.js +492 -0
  114. package/server/route-cache.d.ts +245 -0
  115. package/server/route-cache.js +813 -0
  116. package/server/security.d.ts +18 -0
  117. package/server/security.js +47 -0
  118. package/server/shell-build.d.ts +3 -0
  119. package/server/shell-build.js +2 -0
  120. package/server/shell-cache.d.ts +8 -0
  121. package/server/shell-cache.js +836 -0
  122. package/server/strict-csp.d.ts +7 -0
  123. package/server/strict-csp.js +18 -0
  124. package/server-only.d.ts +1 -0
  125. package/server-only.js +3 -0
  126. package/shared/actions.d.ts +10 -0
  127. package/shared/actions.js +18 -0
  128. package/shared/base.d.ts +8 -0
  129. package/shared/base.js +20 -0
  130. package/shared/cookie.d.ts +7 -0
  131. package/shared/cookie.js +32 -0
  132. package/shared/csp-directives.d.ts +9 -0
  133. package/shared/csp-directives.js +85 -0
  134. package/shared/csp-hash.d.ts +39 -0
  135. package/shared/csp-hash.js +121 -0
  136. package/shared/csp.d.ts +26 -0
  137. package/shared/csp.js +13 -0
  138. package/shared/data.d.ts +175 -0
  139. package/shared/data.js +22 -0
  140. package/shared/errors.d.ts +31 -0
  141. package/shared/errors.js +51 -0
  142. package/shared/head.d.ts +57 -0
  143. package/shared/head.js +242 -0
  144. package/shared/nonce.d.ts +2 -0
  145. package/shared/nonce.js +23 -0
  146. package/shared/prefetch-fallback.min.d.ts +1 -0
  147. package/shared/prefetch-fallback.min.js +2 -0
  148. package/shared/prefetch.d.ts +54 -0
  149. package/shared/prefetch.js +74 -0
  150. package/shared/redirect.d.ts +6 -0
  151. package/shared/redirect.js +25 -0
  152. package/shared/server-exports.d.ts +78 -0
  153. package/shared/server-exports.js +77 -0
  154. package/shared/slash.d.ts +8 -0
  155. package/shared/slash.js +24 -0
@@ -0,0 +1,219 @@
1
+ // File-based route table: maps `src/routes/**` files to router patterns, layouts and special pages.
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import { join, relative, sep } from "node:path";
4
+ const PAGE = /\.(tsx|jsx)$/;
5
+ const SERVER = /\.server\.(ts|js)$/;
6
+ /** Plain `.ts`/`.js` route files: endpoint-only routes when they export request handlers. */
7
+ const PLAIN = /\.(ts|js)$/;
8
+ // Request handler kinds; `?` = a re-export from a server-only module (kind known at runtime only).
9
+ const HANDLER = /^(on(Request|Get|Post|Put|Patch|Delete|Options)|\?)$/;
10
+ const IGNORED = /(^|\/)[.]|\.(test|spec)\.[^/]+$|\.d\.ts$/;
11
+ const SPECIAL = new Set(["_layout", "_404", "_error"]);
12
+ /** Parse one path segment of a route file name; null for a path-less `(group)`. */
13
+ function segment(part, file) {
14
+ if (/^\([^()/]+\)$/.test(part))
15
+ return null;
16
+ let match = /^\[\[([A-Za-z_$][\w$]*)\]\]$/.exec(part);
17
+ if (match)
18
+ return { kind: "optional", name: match[1] };
19
+ match = /^\[\.\.\.([A-Za-z_$][\w$]*)\]$/.exec(part);
20
+ if (match)
21
+ return { kind: "splat", name: match[1] };
22
+ match = /^\[([A-Za-z_$][\w$]*)\]$/.exec(part);
23
+ if (match)
24
+ return { kind: "param", name: match[1] };
25
+ if (/[[\]()*:?#\\]/.test(part))
26
+ throw new Error(`Invalid route segment "${part}" in ${file}`);
27
+ return { kind: "static", text: part };
28
+ }
29
+ // Rank per segment: static before param before splat; a route that ends sorts before a splat at
30
+ // the same depth (both match the shorter URL), never against static/param (different lengths).
31
+ const rank = (s) => (s === undefined ? 1.5 : s.kind === "static" ? 0 : s.kind === "splat" ? 2 : 1);
32
+ function compare(a, b) {
33
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
34
+ const d = rank(a[i]) - rank(b[i]);
35
+ if (d)
36
+ return d;
37
+ const x = a[i], y = b[i];
38
+ if (x?.kind === "static" && y?.kind === "static" && x.text !== y.text)
39
+ return x.text < y.text ? -1 : 1;
40
+ }
41
+ return 0;
42
+ }
43
+ const shape = (segments) => "/" + segments.map((s) => (s.kind === "static" ? s.text : s.kind === "splat" ? "*" : ":")).join("/");
44
+ const pattern = (segments) => "/" + segments.map((s) => (s.kind === "static" ? encodeURIComponent(s.text) : (s.kind === "splat" ? "*" : ":") + s.name)).join("/");
45
+ /**
46
+ * Build the route table from file paths relative to the routes directory (posix separators).
47
+ * `exports`: server export kinds per module file (`load`, `onGet`, … the wrapper names, `?` for a re-export
48
+ * from a server-only module, plus the flags `prerender` / `live`; `scanRoutes` reads them): pages/layouts
49
+ * with any are `inline`, plain `.ts` files with a request handler are endpoint routes.
50
+ */
51
+ export function routeTable(files, exports = new Map()) {
52
+ const set = new Set(files.filter((file) => !IGNORED.test(file)));
53
+ const servers = new Map(); // module path without extension → server file
54
+ for (const file of set)
55
+ if (SERVER.test(file))
56
+ servers.set(file.replace(SERVER, ""), file);
57
+ for (const file of set) {
58
+ if (SERVER.test(file) || !PLAIN.test(file) || !exports.get(file)?.some((name) => HANDLER.test(name)))
59
+ continue;
60
+ const base = file.replace(PLAIN, "");
61
+ const other = servers.get(base);
62
+ if (other)
63
+ throw new Error(`${file} and ${other} are both server modules of ${base}; keep one`);
64
+ servers.set(base, file);
65
+ }
66
+ // `live` (a page flag `_serverExportsOf` reports with them) is not a server export.
67
+ const isInline = (page) => (page ? (exports.get(page)?.some((name) => name !== "live") ?? false) : false);
68
+ const isLive = (page) => exports.get(page)?.includes("live") ?? false;
69
+ const special = (folder, name) => {
70
+ const base = (folder ? folder + "/" : "") + name;
71
+ const page = [...set].find((file) => PAGE.test(file) && file.replace(PAGE, "") === base);
72
+ const server = servers.get(base);
73
+ return page || server ? { ...(page ? { page } : {}), ...(server ? { server } : {}), ...(isInline(page) ? { inline: true } : {}) } : undefined;
74
+ };
75
+ const nearest = (folders, name) => {
76
+ for (let i = folders.length - 1; i >= 0; i--) {
77
+ const found = special(folders[i], name)?.page;
78
+ if (found)
79
+ return found;
80
+ }
81
+ return undefined;
82
+ };
83
+ const modules = new Map();
84
+ for (const file of set) {
85
+ const page = PAGE.test(file), server = SERVER.test(file) || (PLAIN.test(file) && servers.get(file.replace(PLAIN, "")) === file);
86
+ if (!page && !server)
87
+ continue;
88
+ const base = file.replace(server ? (SERVER.test(file) ? SERVER : PLAIN) : PAGE, "");
89
+ const name = base.slice(base.lastIndexOf("/") + 1);
90
+ if (name.startsWith("_")) {
91
+ if (!SPECIAL.has(name))
92
+ continue;
93
+ if (server && name !== "_layout")
94
+ throw new Error(`${file}: only _layout may have a server module`);
95
+ continue;
96
+ }
97
+ if (base.split("/").some((part) => part.startsWith("_")))
98
+ continue;
99
+ const entry = modules.get(base) ?? {};
100
+ modules.set(base, server ? { ...entry, server: file } : { ...entry, page: file, ...(isInline(file) ? { inline: true } : {}), ...(isLive(file) ? { live: true } : {}) });
101
+ }
102
+ const drafts = [];
103
+ const seen = new Map();
104
+ for (const [base, module] of [...modules].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
105
+ const parts = base.split("/");
106
+ const folders = [""];
107
+ for (let i = 0; i < parts.length - 1; i++)
108
+ folders.push(parts.slice(0, i + 1).join("/"));
109
+ const parsed = [];
110
+ for (const [i, part] of parts.entries()) {
111
+ if (i === parts.length - 1 && part === "index")
112
+ break;
113
+ const s = segment(part, base);
114
+ if (s)
115
+ parsed.push(s);
116
+ }
117
+ const splat = parsed.findIndex((s) => s.kind === "splat");
118
+ if (splat !== -1 && splat !== parsed.length - 1)
119
+ throw new Error(`${base}: [...${parsed[splat].name}] must be the last segment`);
120
+ const names = parsed.flatMap((s) => (s.kind === "static" ? [] : [s.name]));
121
+ if (new Set(names).size !== names.length)
122
+ throw new Error(`${base}: duplicate parameter name`);
123
+ // Each optional segment doubles the variants: with it (longer) and without.
124
+ let variants = [[]];
125
+ for (const s of parsed)
126
+ variants = s.kind === "optional" ? variants.flatMap((v) => [[...v, { kind: "param", name: s.name }], v]) : variants.map((v) => [...v, s]);
127
+ for (const segments of variants) {
128
+ const key = shape(segments);
129
+ const previous = seen.get(key);
130
+ if (previous !== undefined && previous !== base)
131
+ throw new Error(`Route conflict: ${previous} and ${base} both match ${key}`);
132
+ if (previous === base)
133
+ continue;
134
+ seen.set(key, base);
135
+ drafts.push({ id: base, segments, module, folders });
136
+ }
137
+ }
138
+ drafts.sort((a, b) => compare(a.segments, b.segments));
139
+ const routes = drafts.map(({ id, segments, module, folders }) => {
140
+ const layouts = folders.map((folder) => special(folder, "_layout")).filter((m) => m !== undefined);
141
+ const notFound = nearest(folders, "_404"), error = nearest(folders, "_error");
142
+ return {
143
+ id,
144
+ pattern: pattern(segments),
145
+ params: segments.flatMap((s) => (s.kind === "static" ? [] : [s.name])),
146
+ ...module,
147
+ layouts,
148
+ ...(notFound ? { notFound } : {}),
149
+ ...(error ? { error } : {}),
150
+ };
151
+ });
152
+ const notFound = special("", "_404")?.page, error = special("", "_error")?.page;
153
+ // Unmatched URLs: nearest `_404` by folder, for folders without parameters (groups add no segment).
154
+ const notFounds = [];
155
+ for (const file of set) {
156
+ if (!PAGE.test(file) || !file.endsWith("/_404" + file.slice(file.lastIndexOf("."))))
157
+ continue;
158
+ const parts = file.split("/").slice(0, -1);
159
+ if (parts.some((part) => part.startsWith("[")))
160
+ continue;
161
+ const prefix = "/" +
162
+ parts
163
+ .filter((part) => !/^\(.+\)$/.test(part))
164
+ .map(encodeURIComponent)
165
+ .join("/");
166
+ if (prefix !== "/")
167
+ notFounds.push({ prefix, page: file });
168
+ }
169
+ notFounds.sort((a, b) => b.prefix.length - a.prefix.length || (a.prefix < b.prefix ? -1 : 1));
170
+ return { routes, ...(notFound ? { notFound } : {}), ...(error ? { error } : {}), ...(notFounds.length ? { notFounds } : {}) };
171
+ }
172
+ /** Scan a routes directory and build its table (reads modules for their server exports). */
173
+ export async function scanRoutes(dir) {
174
+ const entries = await readdir(dir, { recursive: true, withFileTypes: true });
175
+ const files = entries.filter((entry) => entry.isFile()).map((entry) => relative(dir, join(entry.parentPath, entry.name)).split(sep).join("/"));
176
+ return routeTable(files, await _serverExportsOf(dir, files));
177
+ }
178
+ // `export const prerender` alone also makes a page carry server exports (a plain flag).
179
+ const PRERENDER = /^\s*export\s+(?:const|let|var)\s+prerender\b/m;
180
+ // `export const live = true`: a live page (reported as `live`, not a server export; universal, kept in the browser).
181
+ const LIVE = /^\s*export\s+const\s+live\s*=\s*true\b/m;
182
+ // Cheap prefilter (no candidate, no parse): a wrapper import, a server-only re-export, or a former magic name (RV3010).
183
+ const CANDIDATE = /@rivetjs\/router["']|\.server\b|\b(server(Load|Guard|Actions|Entries|Headers)|on(Request|Get|Post|Put|Patch|Delete|Options))\b/;
184
+ // Server-split errors that change what the route table would be: untagged magic names, duplicate kinds.
185
+ const TABLE_ERRORS = /^(Untagged server export|Duplicate server export)/;
186
+ /**
187
+ * Server export kinds of route modules (pages, layouts, plain `.ts` files), by file (see `routeTable`);
188
+ * `prerender` and `live` pages are listed with their flag. Throws on a module whose exports misdeclare the
189
+ * route (RV3010 untagged magic name, duplicate kind): the file and the compiler's message.
190
+ */
191
+ export async function _serverExportsOf(dir, files) {
192
+ const { moduleFacts } = await import("@rivetjs/compiler");
193
+ const out = new Map();
194
+ for (const file of files) {
195
+ if (IGNORED.test(file) || SERVER.test(file) || !(PAGE.test(file) || PLAIN.test(file)))
196
+ continue;
197
+ const source = await readFile(join(dir, file), "utf8");
198
+ const live = PAGE.test(file) && LIVE.test(source);
199
+ const prerender = PAGE.test(file) && PRERENDER.test(source);
200
+ const candidates = CANDIDATE.test(source);
201
+ if (!candidates && !live && !prerender)
202
+ continue;
203
+ const kinds = [];
204
+ if (candidates) {
205
+ const facts = moduleFacts(source, file, { serverExports: true });
206
+ const error = facts.serverErrors?.find((e) => TABLE_ERRORS.test(e.message));
207
+ if (error)
208
+ throw new Error(`${join(dir, file)}${error.line === null ? "" : `:${error.line}:${error.column ?? 1}`}: ${error.message}`);
209
+ kinds.push(...(facts.serverKinds ?? []));
210
+ }
211
+ if (prerender)
212
+ kinds.push("prerender");
213
+ if (live)
214
+ kinds.push("live");
215
+ if (kinds.length)
216
+ out.set(file, kinds);
217
+ }
218
+ return out;
219
+ }
@@ -0,0 +1,20 @@
1
+ export interface SyncOptions {
2
+ /** App root (default the current directory). */
3
+ root?: string;
4
+ /** Vite config file (default: Vite's lookup in `root`). */
5
+ configFile?: string;
6
+ /** Vite mode (default `production`). */
7
+ mode?: string;
8
+ }
9
+ export interface SyncResult {
10
+ root: string;
11
+ /** Vite config file used (undefined: none found, nothing generated). */
12
+ configFile: string | undefined;
13
+ /** The written editor manifest, or null when no Rivet plugin ran. */
14
+ manifest: {
15
+ resumeModules?: string[];
16
+ routesDir?: string;
17
+ routes?: unknown[];
18
+ } | null;
19
+ }
20
+ export declare function sync({ root, configFile, mode }?: SyncOptions): Promise<SyncResult>;
package/build/sync.js ADDED
@@ -0,0 +1,16 @@
1
+ // `rivet sync`: write an app's generated `.rivet/` files (route types, generated app, editor
2
+ // manifest) without starting Vite. Resolves the app's own Vite config, so every plugin option
3
+ // (routes dir, resume modules, base, i18n…) applies exactly as in `vite` / `vite build`: the router
4
+ // plugin's `config` hook regenerates `.rivet/`, the compiler plugin's `configResolved` hook writes
5
+ // `.rivet/editor.json`. Run it before `tsc` / `rivet-check` on a fresh clone (`.rivet/` is gitignored).
6
+ import { readFile } from "node:fs/promises";
7
+ import { resolve } from "node:path";
8
+ export async function sync({ root = process.cwd(), configFile, mode = "production" } = {}) {
9
+ const { resolveConfig } = await import("vite");
10
+ const appRoot = resolve(root);
11
+ const config = await resolveConfig({ root: appRoot, mode, logLevel: "warn", ...(configFile ? { configFile: resolve(appRoot, configFile) } : {}) }, "build", mode);
12
+ const manifest = await readFile(resolve(config.root, ".rivet/editor.json"), "utf8")
13
+ .then((text) => JSON.parse(text))
14
+ .catch(() => null);
15
+ return { root: config.root, configFile: config.configFile, manifest };
16
+ }
@@ -0,0 +1,208 @@
1
+ import type { Plugin } from "vite";
2
+ import { type ImagesConfig, type RivetCSSOptions, type RivetOptions, type RivetResumeOptions } from "@rivetjs/compiler/vite";
3
+ import { type RouteTable } from "./routes.js";
4
+ import { type CodegenOptions } from "./codegen.js";
5
+ import { type TrailingSlash } from "../shared/slash.js";
6
+ import { type RouteRules } from "../server/route-cache.js";
7
+ import type { DocumentPrefetch, PrefetchEagerness } from "../shared/prefetch.js";
8
+ export type { InlineStylesheets } from "./route-css.js";
9
+ /** Build-time helper for other Vite plugins (docs-kit example frames): one merged sheet per stylesheet list. */
10
+ export { mergeRouteCSS } from "./route-css.js";
11
+ import { type InlineStylesheets } from "./route-css.js";
12
+ import { type ServerOnlyOption } from "./reach.js";
13
+ import { type AdapterName, type AdapterOptions } from "../adapters/target-build.js";
14
+ /** Options of the `rivetRouter()` Vite plugin. */
15
+ export interface RivetRouterOptions {
16
+ /** Routes directory, root-relative (default `src/routes`). */
17
+ routes?: string;
18
+ /**
19
+ * URL base path the app is served under (`/app`); sets Vite's `base`. Default: Vite's `base`.
20
+ * Route patterns, `<Link to>`, `navigate()`, data requests, prefetch and the adapters follow it.
21
+ */
22
+ base?: string;
23
+ /**
24
+ * Trailing-slash policy. `"ignore"` (default): `/a` and `/a/` serve the same page as requested.
25
+ * `"never"` / `"always"`: one canonical form — page GET/HEAD requests for the other answer 308,
26
+ * `<Link>` / `navigate()` write hrefs in it, the static adapter writes `a.html` (`never`) or
27
+ * `a/index.html`. `/` itself never changes; endpoints and other methods are never redirected.
28
+ */
29
+ trailingSlash?: TrailingSlash;
30
+ /**
31
+ * Preload planner default for this app's pages (`createApp` / adapter `preload` options override it per server):
32
+ * `background` = byte budget (minified closures) of below-the-fold `visible` chunks also prefetched at idle; `0` off.
33
+ * Default 1 MiB. Dev pages keep `visible` chunks viewport-only (dev manifests have no bundle sizes).
34
+ */
35
+ preload?: {
36
+ background?: number;
37
+ };
38
+ /**
39
+ * Route rules by path pattern (`/about`, `/blog/*`, `/docs/**`): `headers`, and `cache` / `swr` / `isr` — rendered pages
40
+ * stored in process (fresh for `maxAge` s, then served stale while one render refreshes them; `isr: true` until
41
+ * `revalidate()`), with ETag / Last-Modified → 304 and `s-maxage` / `stale-while-revalidate` for CDNs. JSON data:
42
+ * the server entry exports it, every adapter applies it (`createApp` / adapter `routeRules` replace it). Dev applies
43
+ * `headers` only. See `@rivetjs/router/route-cache`.
44
+ */
45
+ routeRules?: RouteRules;
46
+ /** Codegen output directory, root-relative (default `.rivet`). */
47
+ outDir?: string;
48
+ /** Root-relative module whose default export is the `DocumentShell` (default `src/document.ts`). */
49
+ document?: string;
50
+ /** Root-relative stylesheets: client build inputs, linked by the document (`styles`). */
51
+ styles?: string[];
52
+ /**
53
+ * Stylesheets written into the page as `<style>` instead of linked (Astro's `build.inlineStylesheets`), production
54
+ * server / static pages, decided per page. `"auto"` (default): all of the page's sheets (global + route) when their
55
+ * brotli sizes add up to at most 6 KB (the page then has no render-blocking stylesheet request), else each sheet of
56
+ * at most Vite's `build.assetsInlineLimit` (4096 raw bytes; a function limit counts as 4096). `{ maxTotalBrotli }`:
57
+ * `auto` with another total (bytes). `"always"`: every sheet; `"never"`: all linked. Sheets with relative `url()` /
58
+ * `@import` references stay linked. Inline styles carry the page's CSP nonce (static pages: hashed into a
59
+ * `style-src` you set).
60
+ */
61
+ inlineStylesheets?: InlineStylesheets;
62
+ /** `server` (default) and `static` resume server-rendered pages; `csr` mounts a single-page app. */
63
+ mode?: NonNullable<CodegenOptions["mode"]>;
64
+ /** Resume options; generated route modules are added to `modules`. */
65
+ resume?: Partial<RivetResumeOptions>;
66
+ /** `?image` / `?jsx` imports (`rivet({ images })`); build with the app builder so server-only images reach the client output. */
67
+ images?: ImagesConfig | true;
68
+ /** DevTools on the dev server when `@rivetjs/devtools` is installed (default true; see `rivet()`). */
69
+ devtools?: boolean;
70
+ /**
71
+ * Framework bridges (`rivet({ bridges })`: `react()` from `@rivetjs/bridge-react/vite`, `preact()` from
72
+ * `@rivetjs/bridge-preact/vite`, …): those frameworks' components render as islands inside the routed pages.
73
+ * Server / static modes only.
74
+ */
75
+ bridges?: RivetOptions["bridges"];
76
+ /**
77
+ * Component head (`@rivetjs/kit` `<Title>`, `<Meta>`, …): `components` holds each page's document
78
+ * start until its shell rendered; on by default when the kit Vite plugin (`kit()`) is installed.
79
+ * `early` keeps route `head` exports only (document start written first).
80
+ */
81
+ head?: "components" | "early";
82
+ /** Scoped styles options passed to `rivet({ css })`. */
83
+ css?: RivetCSSOptions;
84
+ /** Compile-time `<Icon>` from `@rivetjs/kit/icon` (`rivet({ staticIcons })`, default true); `false` keeps every component call. */
85
+ staticIcons?: boolean;
86
+ /**
87
+ * Route transitions (View Transitions) + pending UI. Off by default; `true` = defaults. The
88
+ * transitions module loads on the first navigation intent; the generated resume entry provides
89
+ * the navigation context (a custom `resume.entry` must add `[Navigation, navigation]` itself).
90
+ */
91
+ transitions?: CodegenOptions["transitions"];
92
+ /** Route announcer for screen readers after client navigation (default true; `false` opts out). */
93
+ announce?: boolean;
94
+ /**
95
+ * The app reads search params (`useSearchParams()` / `SearchParams`): the server provides the
96
+ * request's, the resume entry the browser's. Default: detected from the project's sources
97
+ * (`routerUsage`); set `true` when only a published dependency reads them.
98
+ */
99
+ searchParams?: boolean;
100
+ /** The app calls `navigate()` / `invalidate()` / `revalidate()` / `setSearchParams()`. Default: detected (`routerUsage`). */
101
+ navigate?: boolean;
102
+ /**
103
+ * The app uses `@rivetjs/forms` (directly or through `@rivetjs/ui` `Form`): enhanced submits reach the router
104
+ * through `Symbol.for("rivet.router")` (implies `navigate`). Default: detected (`routerUsage`).
105
+ */
106
+ forms?: boolean;
107
+ /** The app reads `useParams()`. Default: detected (`routerUsage`). */
108
+ params?: boolean;
109
+ /** The app renders `<Link active>`: active links are re-marked after client navigation. Default: detected (`routerUsage`). */
110
+ activeLinks?: boolean;
111
+ /**
112
+ * Document prefetch for navigations that load a document (`@rivetjs/router/prefetch`): every page's
113
+ * head gets Speculation Rules (Chromium) + a ~1 kB inline intent fallback. `"document"` / `"prerender"`:
114
+ * also the default for every same-origin link without its own `prefetch` / `data-rivet-prefetch`
115
+ * (plain `<a>` included; `"none"` opts one out). `true`: only links marked `prefetch="document"` /
116
+ * `"prerender"`. Default: detected (`routerUsage`: such a mark in the sources); `false`: off.
117
+ * Server / static modes (ignored in `csr`).
118
+ */
119
+ prefetch?: boolean | DocumentPrefetch;
120
+ /**
121
+ * When Chromium starts a document prefetch / prerender (Speculation Rules `eagerness`):
122
+ * `"conservative"` (pointer / touch down), `"moderate"` (default: also a ~200 ms hover), `"eager"`
123
+ * (sooner). Applies only while `prefetch` is on; the fallback keeps its 65 ms / focus / down timing.
124
+ */
125
+ prefetchEagerness?: PrefetchEagerness;
126
+ /**
127
+ * Which links of a client-routed app take part in document prefetch. Default `true`: `data-rivet-reload` /
128
+ * `rel=external` links and links whose path no route matches (every page carries the route-path RegExp, in the
129
+ * Speculation Rules and in the fallback call). `false`: only `data-rivet-reload` / `rel=external` links — for apps
130
+ * whose document links all carry `reload` (the docs sites: −7.7 KB of route patterns per page).
131
+ */
132
+ prefetchPaths?: boolean;
133
+ /**
134
+ * Client-side navigation on pages with nothing else to resume (Astro's `<ClientRouter />` model).
135
+ * `true`: every resumed page ships the router (link clicks / intent prefetch / back-forward served in
136
+ * place). `false`: the router's listeners alone do not make a page resumable — a page without resumable
137
+ * symbols, client effects or forms ships zero JS (no loader, state script or modulepreloads) and its
138
+ * links load documents; interactive pages still route in place. Default: `false` in `static` mode,
139
+ * `true` in `server` mode (ignored in `csr`).
140
+ */
141
+ clientRouter?: boolean;
142
+ /**
143
+ * Deploy target besides node (name or options object): `vite build --ssr` also emits the target's
144
+ * entry (client manifests inlined, dependencies bundled). `"fetch"`: `dist/server/fetch.js`
145
+ * (`export default { fetch }`, any web-standard runtime). Platforms, packaged after the server build:
146
+ * `"vercel"` (`.vercel/output`, Build Output API v3), `"netlify"` (`.netlify/v1` + `dist/netlify`),
147
+ * `"cloudflare"` (`dist/cloudflare`, Workers + static assets), `"bun"` (`dist/server/bun.js`),
148
+ * `"deno"` (`dist/server/deno.js`).
149
+ */
150
+ adapter?: AdapterName | AdapterOptions;
151
+ /**
152
+ * Dev server. `resume` (default true): pages resume like production (recorder state, inline loader,
153
+ * symbol chunks served by Vite); the first hot update switches the page to client rendering, after
154
+ * which components hot-replace in place. `false`: pages client-render over the server HTML (the
155
+ * recorder still runs, so serialization errors still reach the overlay).
156
+ */
157
+ dev?: {
158
+ resume?: boolean;
159
+ };
160
+ /**
161
+ * Server-only modules besides `*.server.*` files and modules importing `@rivetjs/router/server-only`: module ids /
162
+ * specifiers or RegExps (virtual modules serving server data, e.g. `"virtual:docs/api"`; plugins declare theirs as
163
+ * `api.rivetServerOnly`, `rivet({ serverOnly })` too). Before each build / dev start the router walks every route's
164
+ * browser graph: a page route reaching one through its own module and plain helper modules is a document route (links
165
+ * load its document, its module leaves the client build); a component module, layout or error page reaching one fails
166
+ * the build (RV3011, with the import chain and the routes). `{ modules, reach: "warn" }`: components reaching one only
167
+ * warn, and the pages rendering them become document routes. Server / static modes (csr keeps the import error).
168
+ */
169
+ serverOnly?: ServerOnlyOption;
170
+ }
171
+ /** Router features generated code provides only when the app uses them (`routerUsage`). */
172
+ export interface RouterUsage {
173
+ /** `useSearchParams` / `SearchParams`. */
174
+ searchParams: boolean;
175
+ /** `navigate` / `invalidate` / `revalidate` / `setSearchParams`, or forms (below). */
176
+ navigate: boolean;
177
+ /** `@rivetjs/forms`, or `Form` in a file importing `@rivetjs/ui` (its `Form` is built on forms). */
178
+ forms: boolean;
179
+ /** `useParams`. */
180
+ params: boolean;
181
+ /** `<Link active>` (an `active` attribute on a `<Link>`). */
182
+ activeLinks: boolean;
183
+ /** `prefetch="document"` / `"prerender"` (`<Link>` prop or `data-rivet-prefetch` attribute). */
184
+ prefetch: boolean;
185
+ }
186
+ /**
187
+ * Which router features the project's sources use: every source under `root` (skipping
188
+ * `node_modules`, `dist` and dot directories) plus the sources of linked (workspace / `npm link`)
189
+ * dependencies of `root/package.json`, except `@rivetjs/*` packages. Text based: a name that appears
190
+ * in a file importing `@rivetjs/router` counts (false positives only cost bytes); published
191
+ * dependencies are not scanned (set `rivetRouter({ navigate, params, searchParams })`).
192
+ */
193
+ export declare function routerUsage(root: string, options?: {
194
+ exclude?: readonly string[];
195
+ }): Promise<RouterUsage>;
196
+ /** Regenerate `.rivet/` for a project root; returns the resume modules and whether files changed. */
197
+ export declare function generateRoutes(root: string, options?: Pick<RivetRouterOptions, "routes" | "outDir" | "document" | "mode" | "transitions" | "announce" | "base" | "searchParams" | "navigate" | "forms" | "params" | "activeLinks" | "trailingSlash" | "prefetch" | "prefetchEagerness" | "prefetchPaths" | "preload" | "routeRules" | "clientRouter"> & {
198
+ head?: "components" | undefined;
199
+ documents?: readonly string[];
200
+ derived?: boolean;
201
+ }): Promise<{
202
+ table: RouteTable;
203
+ resumeModules: readonly string[];
204
+ changed: boolean;
205
+ resumeEntry: boolean;
206
+ }>;
207
+ /** The router's Vite plugins: file routes, typed routes, server code split, data requests and build adapters. */
208
+ export declare function rivetRouter(options?: RivetRouterOptions): Plugin[];