@rangojs/router 0.21.0 → 0.22.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.
Files changed (243) hide show
  1. package/dist/bin/rango.js +13 -1
  2. package/dist/types/browser/event-controller.d.ts +30 -13
  3. package/dist/types/browser/navigation-store.d.ts +9 -0
  4. package/dist/types/browser/partial-update.d.ts +2 -1
  5. package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
  6. package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
  7. package/dist/types/browser/react/context.d.ts +14 -0
  8. package/dist/types/browser/rsc-router.d.ts +13 -2
  9. package/dist/types/browser/types.d.ts +10 -0
  10. package/dist/types/build/generate-manifest.d.ts +2 -0
  11. package/dist/types/cache/background-task.d.ts +22 -0
  12. package/dist/types/cache/cache-key-utils.d.ts +50 -5
  13. package/dist/types/cache/cache-policy.d.ts +17 -0
  14. package/dist/types/cache/cache-scope.d.ts +1 -1
  15. package/dist/types/cache/cf/cf-cache-store.d.ts +7 -1
  16. package/dist/types/cache/document-cache.d.ts +8 -5
  17. package/dist/types/cache/handle-snapshot.d.ts +23 -0
  18. package/dist/types/cache/index.d.ts +1 -1
  19. package/dist/types/cache/invalidation-order.d.ts +14 -0
  20. package/dist/types/cache/memory-segment-store.d.ts +33 -1
  21. package/dist/types/cache/shell-snapshot.d.ts +10 -1
  22. package/dist/types/cache/store-scope.d.ts +22 -0
  23. package/dist/types/cache/tag-invalidation.d.ts +7 -6
  24. package/dist/types/cache/types.d.ts +63 -14
  25. package/dist/types/cache/vercel/vercel-cache-store.d.ts +7 -1
  26. package/dist/types/errors.d.ts +6 -0
  27. package/dist/types/handles/deferred-resolution.d.ts +7 -0
  28. package/dist/types/internal-suspense-audit.d.ts +1 -0
  29. package/dist/types/loader-store.d.ts +4 -8
  30. package/dist/types/outlet-provider.d.ts +2 -1
  31. package/dist/types/prerender/cloudflare.d.ts +49 -0
  32. package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
  33. package/dist/types/prerender/index.d.ts +12 -0
  34. package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
  35. package/dist/types/prerender/on-demand.d.ts +272 -0
  36. package/dist/types/prerender/producer-guard.d.ts +33 -0
  37. package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
  38. package/dist/types/prerender/warm-request.d.ts +65 -0
  39. package/dist/types/prerender/warm.d.ts +65 -0
  40. package/dist/types/prerender/writable-store.d.ts +170 -0
  41. package/dist/types/prerender.d.ts +27 -0
  42. package/dist/types/reverse.d.ts +1 -1
  43. package/dist/types/route-content-wrapper.d.ts +3 -1
  44. package/dist/types/router/error-handling.d.ts +2 -0
  45. package/dist/types/router/handler-context.d.ts +1 -1
  46. package/dist/types/router/match-middleware/cache-lookup.d.ts +14 -0
  47. package/dist/types/router/pattern-matching.d.ts +2 -0
  48. package/dist/types/router/prefetch-default.d.ts +4 -4
  49. package/dist/types/router/prerender-match.d.ts +23 -1
  50. package/dist/types/router/route-trie-builder.d.ts +8 -1
  51. package/dist/types/router/router-context.d.ts +1 -0
  52. package/dist/types/router/router-interfaces.d.ts +30 -0
  53. package/dist/types/router/router-options.d.ts +24 -0
  54. package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
  55. package/dist/types/router/segment-resolution/loader-cache.d.ts +2 -1
  56. package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
  57. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
  58. package/dist/types/router/trie-matching.d.ts +2 -0
  59. package/dist/types/rsc/shell-build-manifest.d.ts +6 -4
  60. package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
  61. package/dist/types/rsc/shell-capture.d.ts +14 -1
  62. package/dist/types/rsc/shell-serve.d.ts +2 -2
  63. package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
  64. package/dist/types/rsc/types.d.ts +8 -0
  65. package/dist/types/segment-boundary-content.d.ts +17 -0
  66. package/dist/types/segment-loader-promise.d.ts +2 -6
  67. package/dist/types/server/build-version-table.d.ts +9 -0
  68. package/dist/types/server/context.d.ts +12 -4
  69. package/dist/types/server/handle-store.d.ts +2 -0
  70. package/dist/types/server/request-context.d.ts +63 -2
  71. package/dist/types/suspense-audit-react.d.ts +26 -0
  72. package/dist/types/suspense-audit.d.ts +157 -0
  73. package/dist/types/testing/action-form.d.ts +34 -0
  74. package/dist/types/testing/flight.entry.d.ts +2 -0
  75. package/dist/types/testing/index.d.ts +4 -0
  76. package/dist/types/testing/internal/server-modules.d.ts +8 -0
  77. package/dist/types/testing/serve-shell-request.d.ts +34 -4
  78. package/dist/types/testing/shell-status.d.ts +14 -6
  79. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
  80. package/dist/types/thenable-status.d.ts +6 -0
  81. package/dist/types/types/handler-context.d.ts +7 -0
  82. package/dist/types/types/route-entry.d.ts +5 -0
  83. package/dist/types/urls/path-helper.d.ts +18 -0
  84. package/dist/types/vite/discovery/state.d.ts +11 -0
  85. package/dist/types/vite/inject-client-debug.d.ts +6 -25
  86. package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
  87. package/dist/vite/index.js +179 -496
  88. package/package.json +12 -2
  89. package/skills/cache-guide/SKILL.md +18 -17
  90. package/skills/caching/SKILL.md +40 -4
  91. package/skills/catalog.json +1 -1
  92. package/skills/cloudflare/SKILL.md +131 -0
  93. package/skills/comparison/references/framework-comparison.md +1 -1
  94. package/skills/deployment-caching/SKILL.md +33 -12
  95. package/skills/document-cache/SKILL.md +16 -14
  96. package/skills/hooks/SKILL.md +22 -0
  97. package/skills/hooks/handle-and-actions.md +3 -4
  98. package/skills/hooks/navigation.md +10 -0
  99. package/skills/hooks/state.md +5 -4
  100. package/skills/host-router/SKILL.md +18 -0
  101. package/skills/loader/SKILL.md +6 -4
  102. package/skills/middleware/SKILL.md +1 -1
  103. package/skills/migrate-nextjs/SKILL.md +82 -9
  104. package/skills/ppr/SKILL.md +31 -8
  105. package/skills/prerender/SKILL.md +645 -8
  106. package/skills/rango/SKILL.md +1 -0
  107. package/skills/response-routes/SKILL.md +5 -5
  108. package/skills/route/SKILL.md +29 -0
  109. package/skills/testing/SKILL.md +2 -1
  110. package/skills/testing/cache-prerender.md +18 -17
  111. package/skills/testing/serve-shell-request.md +104 -8
  112. package/skills/use-cache/SKILL.md +9 -5
  113. package/skills/vercel/SKILL.md +2 -0
  114. package/skills/view-transitions/SKILL.md +9 -4
  115. package/src/browser/event-controller.ts +69 -25
  116. package/src/browser/link-interceptor.ts +2 -2
  117. package/src/browser/navigation-bridge.ts +75 -35
  118. package/src/browser/navigation-store.ts +26 -0
  119. package/src/browser/navigation-transaction.ts +9 -2
  120. package/src/browser/network-error-handler.ts +2 -0
  121. package/src/browser/partial-update.ts +48 -12
  122. package/src/browser/prefetch/default-strategy.ts +22 -42
  123. package/src/browser/react/Link.tsx +37 -42
  124. package/src/browser/react/NavigationProvider.tsx +63 -3
  125. package/src/browser/react/context.ts +17 -0
  126. package/src/browser/react/location-state.ts +5 -19
  127. package/src/browser/react/use-action.ts +4 -1
  128. package/src/browser/react/use-handle.ts +4 -20
  129. package/src/browser/react/use-link-status.ts +4 -3
  130. package/src/browser/react/use-navigation.ts +4 -1
  131. package/src/browser/react/use-params.ts +4 -1
  132. package/src/browser/react/use-pathname.ts +4 -1
  133. package/src/browser/react/use-search-params.ts +5 -3
  134. package/src/browser/react/use-segments.ts +36 -54
  135. package/src/browser/rsc-router.tsx +79 -39
  136. package/src/browser/segment-reconciler.ts +3 -3
  137. package/src/browser/server-action-bridge.ts +26 -6
  138. package/src/browser/types.ts +11 -0
  139. package/src/build/generate-manifest.ts +17 -0
  140. package/src/build/merge-full-manifests.ts +5 -0
  141. package/src/build/route-trie.ts +1 -0
  142. package/src/cache/background-task.ts +61 -0
  143. package/src/cache/cache-key-utils.ts +91 -12
  144. package/src/cache/cache-policy.ts +11 -2
  145. package/src/cache/cache-runtime.ts +23 -23
  146. package/src/cache/cache-scope.ts +38 -36
  147. package/src/cache/cf/cf-cache-store.ts +16 -11
  148. package/src/cache/document-cache.ts +35 -14
  149. package/src/cache/handle-snapshot.ts +50 -0
  150. package/src/cache/index.ts +1 -0
  151. package/src/cache/invalidation-order.ts +53 -0
  152. package/src/cache/memory-segment-store.ts +52 -1
  153. package/src/cache/shell-snapshot.ts +17 -0
  154. package/src/cache/store-scope.ts +36 -0
  155. package/src/cache/tag-invalidation.ts +7 -5
  156. package/src/cache/types.ts +69 -14
  157. package/src/cache/vercel/vercel-cache-store.ts +20 -6
  158. package/src/client.tsx +40 -3
  159. package/src/errors.ts +13 -6
  160. package/src/handles/deferred-resolution.ts +7 -2
  161. package/src/internal-suspense-audit.ts +12 -0
  162. package/src/loader-store.ts +4 -9
  163. package/src/outlet-provider.tsx +25 -1
  164. package/src/prerender/cloudflare.ts +130 -0
  165. package/src/prerender/create-prerender-trigger.ts +780 -0
  166. package/src/prerender/index.ts +39 -0
  167. package/src/prerender/memory-prerender-store.ts +115 -0
  168. package/src/prerender/on-demand.ts +339 -0
  169. package/src/prerender/producer-guard.ts +66 -0
  170. package/src/prerender/shell-manifest-key.ts +12 -17
  171. package/src/prerender/warm-request.ts +154 -0
  172. package/src/prerender/warm.ts +193 -0
  173. package/src/prerender/writable-store.ts +278 -0
  174. package/src/prerender.ts +29 -0
  175. package/src/reverse.ts +1 -1
  176. package/src/route-content-wrapper.tsx +107 -13
  177. package/src/router/error-handling.ts +12 -12
  178. package/src/router/find-match.ts +1 -0
  179. package/src/router/handler-context.ts +2 -0
  180. package/src/router/intercept-resolution.ts +15 -8
  181. package/src/router/loader-resolution.ts +2 -10
  182. package/src/router/match-middleware/background-revalidation.ts +2 -0
  183. package/src/router/match-middleware/cache-lookup.ts +230 -38
  184. package/src/router/pattern-matching.ts +14 -0
  185. package/src/router/prefetch-default.ts +4 -4
  186. package/src/router/prerender-match.ts +460 -352
  187. package/src/router/route-trie-builder.ts +9 -0
  188. package/src/router/router-context.ts +1 -0
  189. package/src/router/router-interfaces.ts +33 -0
  190. package/src/router/router-options.ts +30 -0
  191. package/src/router/segment-resolution/fresh.ts +20 -19
  192. package/src/router/segment-resolution/loader-cache.ts +15 -17
  193. package/src/router/segment-resolution/revalidation.ts +34 -24
  194. package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
  195. package/src/router/trie-matching.ts +3 -0
  196. package/src/router.ts +107 -1
  197. package/src/rsc/handler.ts +51 -4
  198. package/src/rsc/progressive-enhancement.ts +5 -1
  199. package/src/rsc/response-cache-serve.ts +10 -4
  200. package/src/rsc/rsc-rendering.ts +53 -11
  201. package/src/rsc/shell-build-manifest.ts +10 -8
  202. package/src/rsc/shell-capture-constants.ts +39 -7
  203. package/src/rsc/shell-capture.ts +108 -19
  204. package/src/rsc/shell-serve.ts +12 -6
  205. package/src/rsc/ssr-module-loader.ts +26 -0
  206. package/src/rsc/types.ts +8 -0
  207. package/src/segment-boundary-content.ts +25 -0
  208. package/src/segment-loader-promise.ts +28 -38
  209. package/src/segment-system.tsx +117 -57
  210. package/src/server/build-version-table.ts +15 -0
  211. package/src/server/context.ts +40 -17
  212. package/src/server/cookie-store.ts +5 -0
  213. package/src/server/handle-store.ts +13 -0
  214. package/src/server/request-context.ts +87 -1
  215. package/src/ssr/ssr-root.tsx +20 -13
  216. package/src/suspense-audit-react.tsx +98 -0
  217. package/src/suspense-audit.ts +1030 -0
  218. package/src/testing/action-form.ts +66 -0
  219. package/src/testing/flight-runtime.d.ts +27 -0
  220. package/src/testing/flight.entry.ts +3 -0
  221. package/src/testing/index.ts +19 -0
  222. package/src/testing/internal/server-modules.ts +32 -0
  223. package/src/testing/render-route.tsx +25 -12
  224. package/src/testing/serve-shell-request.ts +190 -34
  225. package/src/testing/shell-status.ts +17 -6
  226. package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
  227. package/src/thenable-status.ts +10 -0
  228. package/src/types/handler-context.ts +7 -0
  229. package/src/types/route-entry.ts +6 -0
  230. package/src/urls/path-helper.ts +69 -9
  231. package/src/use-loader.tsx +57 -30
  232. package/src/vite/discovery/bundle-postprocess.ts +4 -0
  233. package/src/vite/discovery/discover-routers.ts +15 -0
  234. package/src/vite/discovery/shell-prerender-phase.ts +5 -5
  235. package/src/vite/discovery/state.ts +17 -1
  236. package/src/vite/inject-client-debug.ts +42 -20
  237. package/src/vite/plugins/virtual-entries.ts +8 -1
  238. package/src/vite/router-discovery.ts +14 -0
  239. package/src/vite/utils/bundle-analysis.ts +39 -7
  240. package/dist/types/browser/react/subscribe-to-nothing.d.ts +0 -7
  241. package/dist/types/segment-content-promise.d.ts +0 -13
  242. package/src/browser/react/subscribe-to-nothing.ts +0 -7
  243. package/src/segment-content-promise.ts +0 -67
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Public type surface for on-demand (ISR-style) prerender: the router
3
+ * `prerender` config option, the `router.prerender({ env, ctx })` runner, its typed
4
+ * target, and the inspectable result union.
5
+ *
6
+ * Types only — safe to import from router options, the public interfaces, the
7
+ * testing barrel, and platform adapters without pulling RSC deps.
8
+ */
9
+ import type { ExecutionContext } from "../types/request-scope.js";
10
+ import type { IsEmptyObject, ParamsFor } from "../reverse.js";
11
+ import type { WritablePrerenderStore } from "./writable-store.js";
12
+ import type { ShellCaptureRefusal } from "../rsc/shell-capture-constants.js";
13
+ declare const prerenderTargetBrand: unique symbol;
14
+ /**
15
+ * The target `onRevalidate` receives: a plain `{ route, params }` that goes
16
+ * straight into a queue message. The type-only brand (never present at
17
+ * runtime) lets the runner (`router.prerender({ env })(target)` / `.many()`) accepts it with no cast on a
18
+ * router typed with named routes, while a hand-written `{ route: "typo" }`
19
+ * stays a type error there. After a JSON round trip, assert it back:
20
+ * `JSON.parse(raw) as PrerenderTargetObject`.
21
+ */
22
+ export interface PrerenderTargetObject {
23
+ route: string;
24
+ params: Record<string, string>;
25
+ readonly [prerenderTargetBrand]: true;
26
+ }
27
+ /**
28
+ * Route map used to type the object target. When the fluent `TRoutes` phantom is
29
+ * empty (the common `createRouter().routes(...)` case leaves it `{}`), fall back
30
+ * to the generated global route map — the same fallback `Prerender<"name">` and
31
+ * `reverse` use — so `router.prerender({ route, params })` stays typed without an
32
+ * explicit route-type parameter.
33
+ */
34
+ type PrerenderRouteMap<TRoutes> = keyof TRoutes extends never ? keyof Rango.GeneratedRouteMap extends never ? {} : Rango.GeneratedRouteMap : TRoutes;
35
+ /**
36
+ * Typed object target, derived from the router's route map the same way
37
+ * `reverse` is. No-param routes may omit `params`.
38
+ */
39
+ type PrerenderRouteTarget<TRoutes> = {
40
+ [TName in keyof PrerenderRouteMap<TRoutes> & string]: IsEmptyObject<ParamsFor<PrerenderRouteMap<TRoutes>, TName>> extends true ? {
41
+ route: TName;
42
+ params?: Record<string, never>;
43
+ } : {
44
+ route: TName;
45
+ params: ParamsFor<PrerenderRouteMap<TRoutes>, TName>;
46
+ };
47
+ }[keyof PrerenderRouteMap<TRoutes> & string];
48
+ /**
49
+ * A prerender target: a typed named-route object, a path-like string/URL
50
+ * (`router.reverse()` output composes for free), or the `onRevalidate` target.
51
+ */
52
+ export type PrerenderTarget<TRoutes = {}> = string | URL | PrerenderRouteTarget<TRoutes> | PrerenderTargetObject;
53
+ /** Env + platform capabilities bound once by `router.prerender({ env, ctx })`. */
54
+ export interface PrerenderRuntime<TEnv = any> {
55
+ /** The live env: the producer's `ctx.env` (not the build's `buildEnv`). */
56
+ env: TEnv;
57
+ /** Cloudflare `ExecutionContext`; absent on node/Vercel. */
58
+ ctx?: ExecutionContext;
59
+ /**
60
+ * The origin a warm requests a path or `{ route, params }` target on, for
61
+ * example `"https://shop.example"`. Cache keys carry the host, so a warm
62
+ * must request the host visitors use. A full-URL target uses its own
63
+ * origin; without this option the runner uses the origin of the request it
64
+ * is called from (an action, a route handler, `onRevalidate`), and with
65
+ * neither a warm returns `skipped-no-origin`. An on-demand route's
66
+ * requestless render never needs it.
67
+ */
68
+ origin?: string;
69
+ }
70
+ /** Per-call options of the bound runner. */
71
+ export interface PrerenderRunOptions {
72
+ /** Throw on failure instead of returning an `{ ok: false }` result. */
73
+ throwOnError?: boolean;
74
+ /**
75
+ * Only refresh what is cold or stale (cron-sweep opt-in), and the only
76
+ * path that can return `already-fresh`. On an on-demand route the stored
77
+ * entry is rendered only when stale. On any other route the warm reads its
78
+ * caches normally instead of forcing misses, so a fresh entry is left
79
+ * alone, a stale one refreshes and a missing one is written.
80
+ */
81
+ onlyIfStale?: boolean;
82
+ }
83
+ /** Per-call options of the bound runner's `.many()`. */
84
+ export interface PrerenderManyOptions extends PrerenderRunOptions {
85
+ /** Max concurrent renders in a batch (default 1; any invalid value is 1). */
86
+ concurrency?: number;
87
+ }
88
+ /**
89
+ * What a warm request wrote, by cache. On a result of the warm path, and on
90
+ * an on-demand result whose route was also warmed.
91
+ */
92
+ export interface PrerenderWarmCaches {
93
+ /**
94
+ * Store writes that landed, by store family: `record` (route `cache()`),
95
+ * `item` (`"use cache"` and loader `cache()`), `response` (the document
96
+ * cache and response routes), `shell` (the `ppr` shell).
97
+ */
98
+ writes: {
99
+ record: number;
100
+ item: number;
101
+ response: number;
102
+ shell: number;
103
+ };
104
+ /**
105
+ * The `ppr` shell; absent on a route without `ppr`. `fresh` is
106
+ * `onlyIfStale` finding a servable shell; `not-eligible` is a request the
107
+ * shell path passed on (an active nonce, a store without shells, the
108
+ * route's own `cache()` opt-out, a buffered response).
109
+ */
110
+ shell?: "stored" | "fresh" | "refused" | "no-shell" | "not-eligible" | "skipped-capacity" | "skipped-queue-timeout" | "error";
111
+ /** Why the capture refused to store the shell. */
112
+ refusal?: ShellCaptureRefusal;
113
+ /** The document cache, when `createDocumentCacheMiddleware` ran. */
114
+ document?: "stored" | "not-cacheable";
115
+ }
116
+ /**
117
+ * Inspectable result, one per target. The trigger does not force callers
118
+ * into try/catch; `throwOnError: true` opts into throwing for admin/CI
119
+ * endpoints. `path` says what ran: `on-demand` is the requestless render of a
120
+ * `Prerender(..., { onDemand })` route into the prerender store, `warm` is a
121
+ * request through the router's handler that rewrites the route's runtime
122
+ * caches.
123
+ */
124
+ export type PrerenderResult = {
125
+ ok: true;
126
+ path: "on-demand";
127
+ /**
128
+ * `already-fresh` only occurs with `onlyIfStale: true`.
129
+ *
130
+ * `removed`: a "removed" marker is stored in place of the page, and
131
+ * requests no longer get the page or its build-time entry. It is
132
+ * `ok: true`, so count it in sweep results. Written by
133
+ * `prerender.remove()` (permanent until a refresh without
134
+ * `onlyIfStale`; `tags` empty, no `ttl`), or by a refresh in which a
135
+ * `notFound()` was the only thing that went wrong, anywhere in the
136
+ * render: the route handler, a layout or parallel slot handler, any
137
+ * server component. That marker has the route's `tags` and `ttl` and is
138
+ * rechecked like a page. `onlyIfStale` finding a fresh marker reports
139
+ * `removed` too. When the data source may be failing, throw anything
140
+ * else: that is `render-failed` and keeps the page.
141
+ */
142
+ status: "rendered" | "already-fresh" | "removed";
143
+ target: string;
144
+ routeName: string;
145
+ /** Opaque, for debugging/logs only — not a stable format. */
146
+ key: string;
147
+ tags: string[];
148
+ /** Absent = never stale. */
149
+ ttl?: number;
150
+ /**
151
+ * The warm request that followed the store write. Absent when the app
152
+ * store is not shared, no origin resolved, or nothing was rendered.
153
+ */
154
+ caches?: PrerenderWarmCaches;
155
+ } | {
156
+ ok: true;
157
+ path: "warm";
158
+ /** `already-fresh` only occurs with `onlyIfStale: true`. */
159
+ status: "warmed" | "already-fresh";
160
+ /** The absolute URL that was requested. */
161
+ target: string;
162
+ routeName: string;
163
+ /** The warm response's HTTP status. */
164
+ responseStatus: number;
165
+ caches: PrerenderWarmCaches;
166
+ } | {
167
+ ok: false;
168
+ /** Absent when no route matched: every other result is past the match. */
169
+ path?: "on-demand" | "warm";
170
+ status: "no-match" | "no-store" | "skipped-personalized" | "skipped-unsupported-target" | "skipped-passthrough" | "skipped-not-on-demand" | "skipped-store-not-shared" | "skipped-no-origin" | "shell-not-stored" | "skipped-uncached" | "render-failed" | "store-failed";
171
+ target: string;
172
+ routeName?: string;
173
+ /** The warm response's HTTP status, when a warm request ran. */
174
+ responseStatus?: number;
175
+ caches?: PrerenderWarmCaches;
176
+ error?: unknown;
177
+ };
178
+ /**
179
+ * Per-route on-demand opt-in, carried inside `PrerenderOptions.onDemand`. Any
180
+ * truthy spelling works (literal, spread, imported const): producer retention
181
+ * is driven by the evaluated route manifest, not a textual scan of the call.
182
+ */
183
+ export interface OnDemandRouteConfig {
184
+ /** Soft TTL (seconds) for entries refreshed for this route. Overrides the router `ttl`. */
185
+ ttl?: number;
186
+ /**
187
+ * Tags stamped on the stored entry, addressable via
188
+ * the runner's `markStale()`. A separate namespace from `cacheTag()` /
189
+ * `updateTag()` / `revalidateTag()`, which never reach the prerender store.
190
+ */
191
+ tags?: string[] | ((target: {
192
+ params: Record<string, string>;
193
+ }) => string[]);
194
+ }
195
+ export type OnDemandOption = boolean | OnDemandRouteConfig;
196
+ /**
197
+ * Env-resolved prerender store configuration. Same union shape as the `cache`
198
+ * option: a plain object or a factory resolved per request/per call, never at
199
+ * `createRouter()` time.
200
+ */
201
+ export interface PrerenderConfig<TEnv = any> {
202
+ store: WritablePrerenderStore;
203
+ /**
204
+ * Default soft TTL (seconds) for a route whose `onDemand` sets none. Absent
205
+ * = never stale. Soft: a stale entry keeps serving; staleness only decides
206
+ * whether `onRevalidate` is scheduled. Entries never expire.
207
+ */
208
+ ttl?: number;
209
+ /**
210
+ * Stale-while-revalidate: its presence is the opt-in. Scheduled through
211
+ * `waitUntil` on a stale overlay hit (the stale entry still serves) with
212
+ * the JSON-serializable target and the live env. Runs at most once per
213
+ * stale key per isolate while one is in flight (one running longer than
214
+ * 15 s counts as finished, so a hung call cannot block the key), so
215
+ * `(target, env, ctx) => router.prerender({ env, ctx })(target, { onlyIfStale: true })`
216
+ * is safe in a single process; across isolates, point it at a queue, which
217
+ * owns dedup. Pass `onlyIfStale` in the call that runs later: a job queued
218
+ * for a page that `prerender.remove()` then removed finds the marker and
219
+ * renders nothing, where a plain refresh would bring the page back.
220
+ */
221
+ onRevalidate?: (target: PrerenderTargetObject, env: TEnv,
222
+ /** The stale request's execution context; absent where none exists. */
223
+ ctx?: ExecutionContext) => void | Promise<void>;
224
+ }
225
+ /**
226
+ * @internal Per-request resolution of the prerender config plus the key
227
+ * version. Threaded onto the request context by the RSC handler and read by
228
+ * the serve-path overlay lookup.
229
+ */
230
+ export interface ResolvedPrerender<TEnv = any> {
231
+ /** The resolved config; the store is `config.store`. */
232
+ config: PrerenderConfig<TEnv>;
233
+ routerId: string;
234
+ /** The router's cache version for overlay keys (resolvePrerenderVersion). */
235
+ version: string;
236
+ }
237
+ /**
238
+ * The runner `router.prerender({ env, ctx })` returns: callable per target,
239
+ * plus `.many` / `.remove` / `.markStale`. Typing rides the phantom `TRoutes`
240
+ * accumulator, like `reverse`.
241
+ */
242
+ export interface PrerenderRunner<TRoutes = {}> {
243
+ (target: PrerenderTarget<TRoutes>, options?: PrerenderRunOptions): Promise<PrerenderResult>;
244
+ many(targets: ReadonlyArray<PrerenderTarget<TRoutes>>, options?: PrerenderManyOptions): Promise<PrerenderResult[]>;
245
+ /**
246
+ * Remove an on-demand route's page without rendering: a "removed" marker is
247
+ * stored in place of it. Requests then get a 404 (a `Passthrough` route: its
248
+ * live handler), never the build-time entry. Permanent: no `ttl`, no
249
+ * `onRevalidate`, left by `onlyIfStale`, until a `prerender(target)`
250
+ * without `onlyIfStale` renders the page again. It is a write like a
251
+ * refresh's, and the last write wins. `{ ok: true, status: "removed" }`; a
252
+ * route that is not on-demand is `skipped-not-on-demand`. `.many` is the
253
+ * batch form.
254
+ */
255
+ remove: {
256
+ (target: PrerenderTarget<TRoutes>, options?: Pick<PrerenderRunOptions, "throwOnError">): Promise<PrerenderResult>;
257
+ many(targets: ReadonlyArray<PrerenderTarget<TRoutes>>, options?: Omit<PrerenderManyOptions, "onlyIfStale">): Promise<PrerenderResult[]>;
258
+ };
259
+ /**
260
+ * Mark this router's stored entries carrying one of `tags` stale. Marking only: the
261
+ * entries keep serving, and a stale hit schedules `onRevalidate` when one is
262
+ * configured. Nothing is deleted or re-rendered here.
263
+ */
264
+ markStale(tags: string[]): Promise<void>;
265
+ }
266
+ /**
267
+ * The `router.prerender` function: binds the runtime (`env`, `ctx`) and returns
268
+ * the {@link PrerenderRunner}. Binding does no work; the config factory is
269
+ * resolved per call.
270
+ */
271
+ export type PrerenderFn<TEnv = any, TRoutes = {}> = (runtime: PrerenderRuntime<TEnv>) => PrerenderRunner<TRoutes>;
272
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Requestless-prerender guard.
3
+ *
4
+ * The on-demand producer renders a route with only route params + env — never
5
+ * the triggering request's cookies, headers, geo, or session (see the design's
6
+ * Producer Semantics). Personalized reads during a producer render would bake
7
+ * one visitor's data into a payload shared with everyone for that route.
8
+ *
9
+ * The producer marks its own request context with `_onDemandProducer: true`. The
10
+ * standalone request-scoped helpers a handler reaches for — `cookies()`,
11
+ * `headers()`, `invalidateClientCache()`, `keepClientCache()` — call
12
+ * {@link assertNotInsidePrerenderProducer}, which throws
13
+ * {@link PrerenderPersonalizationError}; the producer catches it and maps to a
14
+ * `skipped-personalized` result, leaving any existing entry untouched. (The
15
+ * producer context's own `ctx.cookie`/`header`/etc. remain the pre-existing
16
+ * build-time no-op stubs over empty synthetic state — the guard covers the
17
+ * standalone helpers, which are the reads that would otherwise see real request
18
+ * data.)
19
+ */
20
+ /** Thrown when a producer render touches request-specific state. */
21
+ export declare class PrerenderPersonalizationError extends Error {
22
+ /** Cross-realm-safe brand (instanceof can fail across bundle/realm boundaries). */
23
+ readonly isPrerenderPersonalizationError: true;
24
+ constructor(api: string);
25
+ }
26
+ export declare function isPrerenderPersonalizationError(value: unknown): value is PrerenderPersonalizationError;
27
+ /**
28
+ * Throw {@link PrerenderPersonalizationError} when `ctx` is a requestless
29
+ * on-demand producer context. A no-op for the live request path and the
30
+ * build/dev prerender paths (which do not set `_onDemandProducer`), so existing
31
+ * callers are unaffected.
32
+ */
33
+ export declare function assertNotInsidePrerenderProducer(ctx: unknown, api: string): void;
@@ -1,18 +1 @@
1
- /**
2
- * Build shell manifest key, shared by the producer (the build's shell
3
- * prerender phase, vite/discovery/shell-prerender-phase.ts) and the consumer
4
- * (the runtime read-through, rsc/shell-build-manifest.ts) so the format
5
- * cannot drift. Dependency-free: the producer runs node-side in the plugin,
6
- * the consumer in the RSC runtime.
7
- *
8
- * PATHNAME-ONLY by design. Host-free because the build knows no request
9
- * host. Router-free because the router id ($$id) is a hash of
10
- * filePath:lineNumber of the TRANSFORMED source, which differs between the
11
- * discovery temp server's dev-style transform chain and the main build's —
12
- * a temp-realm router id can never be looked up by the shipped worker. The
13
- * producer instead detects pathname collisions across routers at build time
14
- * and declines both entries (loudly), keeping the key unambiguous. This is
15
- * a manifest namespace, never a store keyspace: the runtime shell key
16
- * (host + pathname + search + ":shell") stays untouched.
17
- */
18
- export declare function buildShellManifestKey(pathname: string): string;
1
+ export declare function buildShellManifestKey(routerId: string, pathname: string): string;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The mark and record of a `router.prerender()` warm request
3
+ * (docs/design/prerender-every-route.md, "The synthetic request"). The
4
+ * trigger marks the `Request` it dispatches, the handler puts the record on
5
+ * the request context (`RequestContext._prerenderWarm`), and each cache layer
6
+ * reads the context where it reads its store. No runtime imports: the cache
7
+ * layers import this without pulling the trigger.
8
+ */
9
+ import type { HandlerCacheConfig } from "../rsc/types.js";
10
+ import type { ShellCaptureDebugEvent } from "../rsc/shell-capture.js";
11
+ import type { PrerenderWarmCaches } from "./on-demand.js";
12
+ /** What one warm request did: written by the layers, read by the trigger. */
13
+ export interface PrerenderWarmRecord extends PrerenderWarmCaches {
14
+ /**
15
+ * `replace`: every runtime cache read misses and the writes replace the
16
+ * entries. `fill` (`onlyIfStale`): reads are normal; the record only reports.
17
+ */
18
+ mode: "replace" | "fill";
19
+ /**
20
+ * The `createRouter({ cache })` config the trigger gated on its scope. The
21
+ * handler uses it instead of resolving the option again, so the store that
22
+ * was checked is the store that is written.
23
+ */
24
+ cacheConfig: HandlerCacheConfig;
25
+ /** The surface an identity guard refused (server/context.ts guardIdentityRead). */
26
+ identity?: string;
27
+ /**
28
+ * The request's render errors: the handler adopts this list as
29
+ * `RequestContext._renderErrors`, so what the layers push lands here.
30
+ */
31
+ renderErrors: unknown[];
32
+ }
33
+ /** What the helpers read; a derived capture context inherits the record. */
34
+ export interface WarmContext {
35
+ _prerenderWarm?: PrerenderWarmRecord;
36
+ _shellCaptureRun?: boolean;
37
+ }
38
+ export declare function createWarmRecord(mode: PrerenderWarmRecord["mode"], cacheConfig: HandlerCacheConfig): PrerenderWarmRecord;
39
+ /** The mark is the object: a clone or a rebuilt request carries none. */
40
+ export declare function markWarmRequest(request: Request, record: PrerenderWarmRecord): void;
41
+ export declare function readWarmMark(request: Request): PrerenderWarmRecord | undefined;
42
+ /**
43
+ * True when this request must read every runtime cache as a miss. A flag read
44
+ * at each read point, not a store wrapper: a route's or loader's own
45
+ * `cache({ store })` bypasses the app store (CacheScope.getStore), and the
46
+ * "use cache" in-flight dedup lives outside the store, so a wrapper would
47
+ * miss both.
48
+ *
49
+ * False in the warm's own shell capture (`_shellCaptureRun`): it runs after
50
+ * the warm's writes settled and must replay them, or it would render every
51
+ * handler a second time and could bake another generation than the one just
52
+ * written.
53
+ */
54
+ export declare function isWarmReplace(ctx: WarmContext | null | undefined): boolean;
55
+ /** Count a store write that landed. No-op outside a warm. */
56
+ export declare function noteWarmWrite(ctx: WarmContext | null | undefined, family: keyof PrerenderWarmCaches["writes"]): void;
57
+ /** Record the document cache's outcome; its store write counts through noteWarmWrite. */
58
+ export declare function noteWarmDocument(ctx: WarmContext | null | undefined, outcome: NonNullable<PrerenderWarmCaches["document"]>): void;
59
+ /** Record the surface an identity guard (guardIdentityRead) is about to refuse. */
60
+ export declare function noteWarmIdentityRead(ctx: WarmContext | null | undefined, surface: string): void;
61
+ /**
62
+ * Fold one capture event into the record: the last attempt's outcome stands.
63
+ * The skips a forced capture never takes and the `backoff` notice leave it.
64
+ */
65
+ export declare function noteWarmShellEvent(record: PrerenderWarmRecord, event: ShellCaptureDebugEvent): void;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * One `router.prerender()` warm request: build the synthetic visitor request,
3
+ * dispatch it through the router's own handler, wait for everything it
4
+ * scheduled, and read back what it wrote
5
+ * (docs/design/prerender-every-route.md, "The synthetic request"). The handler
6
+ * is an injected `fetch`, so unit tests drive this with a fake.
7
+ */
8
+ import { type CollectingExecutionContext } from "../cache/background-task.js";
9
+ import type { HandlerCacheConfig } from "../rsc/types.js";
10
+ import type { ExecutionContext } from "../types/request-scope.js";
11
+ import type { PrerenderResult, PrerenderWarmCaches } from "./on-demand.js";
12
+ import { type PrerenderWarmRecord } from "./warm-request.js";
13
+ /**
14
+ * How long a warm waits for its response body and background work: a capture
15
+ * that waited out the queue budget, then ran to its hard cap.
16
+ */
17
+ export declare const WARM_SETTLE_BUDGET_MS: number;
18
+ export interface WarmRequestInput<TEnv> {
19
+ /** The absolute URL a visitor requests. */
20
+ url: URL;
21
+ mode: PrerenderWarmRecord["mode"];
22
+ /** The gated `createRouter({ cache })` config; the handler writes to its store. */
23
+ cacheConfig: HandlerCacheConfig;
24
+ env: TEnv;
25
+ /** The context `cacheConfig` was resolved with. */
26
+ ctx: CollectingExecutionContext;
27
+ /** `router.fetch`. */
28
+ fetch: (request: Request, input: {
29
+ env: TEnv;
30
+ ctx: ExecutionContext;
31
+ }) => Promise<Response>;
32
+ /** Tests shorten the wait. */
33
+ settleBudgetMs?: number;
34
+ }
35
+ export interface WarmRequestOutcome {
36
+ record: PrerenderWarmRecord;
37
+ /** Absent when the handler threw before answering. */
38
+ responseStatus?: number;
39
+ /** The handler's throw, a body that failed mid-stream, or a wait that ran out. */
40
+ error?: unknown;
41
+ }
42
+ /**
43
+ * Dispatch one warm: a GET with `accept: text/html` and nothing else, so it
44
+ * lands in the partition a header-less visitor lands in. The response is
45
+ * drained and dropped: what it rendered reaches a visitor only through a
46
+ * store.
47
+ */
48
+ export declare function runWarmRequest<TEnv>(input: WarmRequestInput<TEnv>): Promise<WarmRequestOutcome>;
49
+ /** The per-cache detail a result carries. */
50
+ export declare function warmCaches(record: PrerenderWarmRecord): PrerenderWarmCaches;
51
+ /**
52
+ * A warm's status from what it recorded, first match wins. An identity
53
+ * refusal comes ahead of the render failure it usually also causes (the guard
54
+ * throws). `record.shell` is set only for a `ppr` route (shellServePlan).
55
+ */
56
+ export declare function warmStatus(outcome: WarmRequestOutcome): WarmStatus;
57
+ type WarmStatus = Extract<PrerenderResult, {
58
+ ok: true;
59
+ path: "warm";
60
+ }>["status"] | Extract<PrerenderResult, {
61
+ ok: false;
62
+ }>["status"];
63
+ /** The `PrerenderResult` of a finished warm. */
64
+ export declare function composeWarmResult(outcome: WarmRequestOutcome, target: string, routeName: string): PrerenderResult;
65
+ export {};
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Writable prerender store contract for on-demand (ISR-style) prerender refresh.
3
+ *
4
+ * The bundled build manifest (store.ts) is immutable and read-only. On-demand
5
+ * prerender adds a writable durable overlay in front of it: `router.prerender()`
6
+ * renders a route and writes a versioned envelope here; the serve path reads the
7
+ * overlay before the bundled manifest.
8
+ *
9
+ * This module is types + pure helpers only — no React/RSC/plugin-rsc imports —
10
+ * so it can be imported by the testing barrel and platform adapters alike.
11
+ */
12
+ import type { PrerenderEntry } from "./store.js";
13
+ /**
14
+ * Structured durable-overlay key. Includes the router's cache version so a
15
+ * deploy that changed the router's server code never reads Flight payloads
16
+ * that reference its previous build, and so Cloudflare gradual deployments
17
+ * stay correct (old and new worker versions each read their own namespace).
18
+ */
19
+ export interface PrerenderKey {
20
+ routerId: string;
21
+ /**
22
+ * The owning router's cache version (its data version, or
23
+ * `createRouter({ version })`). Opaque: never set or read by app code; a
24
+ * store keys off it as given.
25
+ */
26
+ version: string;
27
+ routeName: string;
28
+ /** DJB2 8-hex hash of the canonical params (see param-hash.ts). */
29
+ paramHash: string;
30
+ }
31
+ /**
32
+ * @internal What the trigger stamps on a write. TTL is soft staleness metadata
33
+ * (`staleAt`), never a hard store expiry: hard expiry would delete the entry
34
+ * a stale-while-revalidate serve needs, and an expired overlay would fall back
35
+ * to older bundled-manifest content.
36
+ */
37
+ export interface StoredEntryOptions {
38
+ /** Seconds until the entry is considered stale. Absent = never stale. */
39
+ ttl?: number;
40
+ tags: string[];
41
+ /** Canonical params, stored for verify-on-read. */
42
+ params: Record<string, string>;
43
+ }
44
+ /** What a page and a "removed" marker both carry: a store reads either alike. */
45
+ interface StoredEnvelope {
46
+ v: 1;
47
+ meta: {
48
+ /**
49
+ * When the render started, not when the entry was written: a `markStale()`
50
+ * at or after it (strictly after in an in-process store) covers the entry.
51
+ * A `remove()` marker renders nothing and carries its write time.
52
+ */
53
+ storedAt: number;
54
+ /**
55
+ * Absent = never stale. Soft metadata only; controls `onRevalidate`
56
+ * scheduling, not serving. Counts the ttl from the write. A store may
57
+ * lower it (tag markers do).
58
+ */
59
+ staleAt?: number;
60
+ /** The configured ttl in seconds, as a result reports it. Absent = none. */
61
+ ttl?: number;
62
+ tags: string[];
63
+ /** The key's `version` at write time; verified on read. */
64
+ version: string;
65
+ /** Verified against the request's params on read (DJB2 collision guard). */
66
+ params: Record<string, string>;
67
+ };
68
+ }
69
+ type StoredPage = StoredEnvelope & {
70
+ entry: PrerenderEntry;
71
+ removed?: never;
72
+ };
73
+ /**
74
+ * The "removed" marker (tombstone) stored in place of a page, by
75
+ * `prerender.remove()` or by a refresh whose render hit `notFound()` (or
76
+ * declined with `ctx.passthrough()`). A request that finds it is not served
77
+ * the build-time entry below the overlay. No `entry`: a router from before the
78
+ * marker rejects it as malformed (a miss) instead of serving an empty page.
79
+ * `meta` is stamped like a page's for a render outcome, so that marker goes
80
+ * stale and is rechecked; a `remove()` marker has no `staleAt` and no tags.
81
+ */
82
+ type StoredTombstone = StoredEnvelope & {
83
+ removed: true;
84
+ entry?: never;
85
+ };
86
+ /**
87
+ * Versioned envelope the router composes on a refresh or a removal and
88
+ * verifies on every read: a page, or the marker that the page was removed. A
89
+ * store persists either as given and returns it as stored.
90
+ */
91
+ export type PrerenderStoredEntry = StoredPage | StoredTombstone;
92
+ /**
93
+ * The writable durable overlay: plain get/set. Backed in production by a
94
+ * platform adapter (Cloudflare KV, …) and in dev/tests by an in-memory store.
95
+ * The router composes the envelope before `set()` and verifies whatever `get()`
96
+ * returns (shape, `version`, params collision guard), so a store holds no
97
+ * policy of its own.
98
+ *
99
+ * Key everything off the `key` argument (`serializePrerenderKey(key)` carries
100
+ * `key.version`). Never call `getCacheVersions()` here: the trigger's `set()`
101
+ * runs outside the producer's request context, where it returns the
102
+ * whole-build fallback instead of the owning router's version.
103
+ */
104
+ export interface WritablePrerenderStore {
105
+ /**
106
+ * The envelope stored under `key`, or null. Never memoize a miss: a refresh
107
+ * can write the key after a request missed it. May lower `meta.staleAt` (a
108
+ * tag marked stale after the entry was written).
109
+ */
110
+ get(key: PrerenderKey): Promise<PrerenderStoredEntry | null>;
111
+ /**
112
+ * Persist `stored` (a page, or the marker that the page was removed) under
113
+ * `key`, replacing any previous entry. No expiry.
114
+ */
115
+ set(key: PrerenderKey, stored: PrerenderStoredEntry): Promise<void>;
116
+ delete?(key: PrerenderKey): Promise<void>;
117
+ /**
118
+ * Mark the entries of router `routerId` (`PrerenderKey.routerId`) carrying
119
+ * any of `tags` stale (they keep serving). A mark covers an entry whose
120
+ * `meta.storedAt` it is at or after (strictly after for an in-process
121
+ * store), including one `set()` after the mark: a store that marks held
122
+ * entries in place has to remember its marks. Scope it to that router: routers
123
+ * behind a host router share one store, and a tag is only meaningful inside
124
+ * the router whose routes declared it. Optional: without it,
125
+ * `router.prerender().markStale()` is a no-op for this store.
126
+ */
127
+ markStale?(routerId: string, tags: string[]): Promise<void>;
128
+ }
129
+ /**
130
+ * Serialize a {@link PrerenderKey} to the design's string form:
131
+ * `prerender:{routerId}:{version}:{routeName}:{paramHash}`
132
+ */
133
+ export declare function serializePrerenderKey(key: PrerenderKey): string;
134
+ /**
135
+ * @internal Compose the versioned envelope the trigger hands to `store.set()`.
136
+ * `now` is `meta.storedAt`; `staleFrom` (default `now`) is where the ttl
137
+ * starts counting. Both are passed explicitly so tests stay deterministic.
138
+ */
139
+ export declare function composeStoredEntry(key: PrerenderKey, entry: PrerenderEntry, options: StoredEntryOptions, now: number, staleFrom?: number): StoredPage;
140
+ /**
141
+ * @internal Compose the "removed" marker stored in place of a page. Its meta
142
+ * is a page's (composeStoredEntry), so `ttl` and `tags` mean the same: the
143
+ * marker of a render outcome is given the route's and is rechecked like a
144
+ * page; `prerender.remove()` gives neither, and its marker never goes stale.
145
+ */
146
+ export declare function composeStoredTombstone(key: PrerenderKey, options: StoredEntryOptions, now: number, staleFrom?: number): StoredTombstone;
147
+ /**
148
+ * @internal Verify a store's answer is safe to serve for this key: the
149
+ * envelope shape and version tag, the key version, and the stored params
150
+ * against the request's (the 8-hex DJB2 collision guard, param-hash.ts: a
151
+ * runtime write keyed off webhook-supplied params must not serve one page's
152
+ * content under another's URL, nor remove another's page). False means treat
153
+ * it as a miss. Run by the router on every read, so a third-party store
154
+ * cannot skip it.
155
+ */
156
+ export declare function isStoredEntryValidFor(stored: unknown, key: PrerenderKey, params: Record<string, string>): stored is PrerenderStoredEntry;
157
+ /** True once `now` has passed the entry's soft `staleAt`. Never-stale entries never go stale. */
158
+ export declare function isStoredEntryStale(stored: PrerenderStoredEntry, now: number): boolean;
159
+ /**
160
+ * @internal Lower `meta.staleAt` to `at` when it is unset or later. Shared by
161
+ * the stores' tag marking so the rule cannot drift between them.
162
+ */
163
+ export declare function lowerStoredEntryStaleAt(stored: PrerenderStoredEntry, at: number): void;
164
+ /**
165
+ * @internal `store.get(key)` verified for serving (isStoredEntryValidFor). A
166
+ * store error (e.g. a KV outage) reads as a miss: the serve path falls back to
167
+ * the bundled manifest or live handler, and the trigger's stale check renders.
168
+ */
169
+ export declare function readVerifiedStoredEntry(store: WritablePrerenderStore, key: PrerenderKey, params: Record<string, string>): Promise<PrerenderStoredEntry | null>;
170
+ export {};
@@ -81,6 +81,26 @@ export interface PrerenderOptions {
81
81
  * ```
82
82
  */
83
83
  concurrency?: number;
84
+ /**
85
+ * Opt this route into on-demand (ISR-style) prerender refresh via
86
+ * `router.prerender()`. `true` uses the router-level `prerender` defaults; an
87
+ * object overrides them per route.
88
+ *
89
+ * Any truthy value opts in — a literal, a spread, or an imported const:
90
+ * producer retention in the production bundle is driven by the evaluated
91
+ * route manifest (the same source that sets the runtime od flag), not a
92
+ * textual scan of the call.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * export const ProductPage = Prerender(
97
+ * async () => [{ id: "featured" }],
98
+ * async (ctx) => <Product data={await ctx.env.PRODUCTS.get(ctx.params.id)} />,
99
+ * { onDemand: { ttl: 3600, tags: ({ params }) => [`product:${params.id}`] } },
100
+ * );
101
+ * ```
102
+ */
103
+ onDemand?: import("./prerender/on-demand.js").OnDemandOption;
84
104
  }
85
105
  /**
86
106
  * Context passed to Prerender() handlers at build time.
@@ -98,6 +118,13 @@ export interface BuildContext<TParams> {
98
118
  * changing build semantics.
99
119
  */
100
120
  dev: boolean;
121
+ /**
122
+ * True when this render is a requestless on-demand refresh driven by
123
+ * `router.prerender()` (rather than a build-time or dev prerender). The
124
+ * producer is still requestless — cookies/headers are unavailable — but
125
+ * `ctx.env` is the live trigger binding, not the shared build env.
126
+ */
127
+ onDemand: boolean;
101
128
  /**
102
129
  * Build-time environment bindings (KV, D1, etc.) supplied by the Vite plugin.
103
130
  * Only available when `buildEnv` is configured in rango() options.