@rangojs/router 0.20.0 → 0.21.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 (178) hide show
  1. package/dist/bin/rango.js +26 -14
  2. package/dist/types/browser/event-controller.d.ts +32 -0
  3. package/dist/types/browser/history-state.d.ts +16 -4
  4. package/dist/types/browser/navigation-transaction.d.ts +7 -0
  5. package/dist/types/browser/rango-state.d.ts +3 -1
  6. package/dist/types/browser/react/context.d.ts +11 -0
  7. package/dist/types/browser/react/location-state-shared.d.ts +84 -10
  8. package/dist/types/browser/react/location-state.d.ts +7 -2
  9. package/dist/types/browser/react/subscribe-to-nothing.d.ts +7 -0
  10. package/dist/types/cache/cache-scope.d.ts +4 -5
  11. package/dist/types/cache/cf/cf-cache-store.d.ts +27 -6
  12. package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
  13. package/dist/types/cache/handle-snapshot.d.ts +36 -50
  14. package/dist/types/cache/index.d.ts +2 -0
  15. package/dist/types/cache/shell-snapshot.d.ts +19 -3
  16. package/dist/types/cache/types.d.ts +21 -10
  17. package/dist/types/cache/vercel/vercel-cache-store.d.ts +28 -16
  18. package/dist/types/client-urls/navigation.d.ts +7 -1
  19. package/dist/types/client-urls/optimistic-location.d.ts +10 -4
  20. package/dist/types/handles/deferred-resolution.d.ts +11 -1
  21. package/dist/types/prerender/build-shell-capture.d.ts +8 -6
  22. package/dist/types/router/match-middleware/cache-lookup.d.ts +1 -1
  23. package/dist/types/router/navigation-snapshot.d.ts +8 -1
  24. package/dist/types/router/router-interfaces.d.ts +2 -1
  25. package/dist/types/router/router-options.d.ts +17 -7
  26. package/dist/types/router/segment-resolution/loader-cache.d.ts +21 -9
  27. package/dist/types/router-versions.d.ts +39 -0
  28. package/dist/types/rsc/handler-context.d.ts +5 -0
  29. package/dist/types/rsc/shell-build-manifest.d.ts +3 -2
  30. package/dist/types/rsc/types.d.ts +4 -6
  31. package/dist/types/server/build-version-table.d.ts +44 -0
  32. package/dist/types/server/handle-store.d.ts +87 -46
  33. package/dist/types/server/request-context.d.ts +22 -8
  34. package/dist/types/testing/build-versions.d.ts +32 -0
  35. package/dist/types/testing/dom.entry.d.ts +1 -1
  36. package/dist/types/testing/flight.entry.d.ts +1 -1
  37. package/dist/types/testing/index.d.ts +2 -0
  38. package/dist/types/testing/internal/seed-vars.d.ts +7 -2
  39. package/dist/types/testing/location-state-key.d.ts +5 -1
  40. package/dist/types/testing/render-route.d.ts +77 -6
  41. package/dist/types/testing/serve-shell-request.d.ts +21 -0
  42. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  43. package/dist/types/types/handler-context.d.ts +8 -5
  44. package/dist/types/vite/discovery/build-versions.d.ts +393 -0
  45. package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
  46. package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
  47. package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
  48. package/dist/types/vite/discovery/state.d.ts +14 -0
  49. package/dist/types/vite/encryption-key.d.ts +18 -0
  50. package/dist/types/vite/plugin-types.d.ts +27 -0
  51. package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
  52. package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
  53. package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
  54. package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
  55. package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
  56. package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
  57. package/dist/types/vite/router-discovery.d.ts +6 -0
  58. package/dist/types/vite/utils/client-chunks.d.ts +2 -0
  59. package/dist/types/vite/utils/compare-strings.d.ts +7 -0
  60. package/dist/types/vite/utils/package-resolution.d.ts +8 -0
  61. package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
  62. package/dist/vite/index.js +1537 -444
  63. package/package.json +1 -2
  64. package/skills/cache-guide/SKILL.md +84 -20
  65. package/skills/caching/SKILL.md +16 -5
  66. package/skills/client-urls/SKILL.md +16 -0
  67. package/skills/cloudflare/SKILL.md +2 -1
  68. package/skills/comparison/references/framework-comparison.md +16 -12
  69. package/skills/hooks/SKILL.md +3 -0
  70. package/skills/hooks/handle-and-actions.md +6 -0
  71. package/skills/hooks/navigation.md +28 -0
  72. package/skills/hooks/state.md +209 -8
  73. package/skills/hooks/urls.md +40 -1
  74. package/skills/host-router/SKILL.md +13 -0
  75. package/skills/intercept/SKILL.md +6 -0
  76. package/skills/loader/SKILL.md +19 -1
  77. package/skills/middleware/SKILL.md +2 -0
  78. package/skills/ppr/SKILL.md +119 -40
  79. package/skills/prerender/SKILL.md +7 -3
  80. package/skills/rango/SKILL.md +8 -7
  81. package/skills/router-setup/SKILL.md +15 -1
  82. package/skills/testing/SKILL.md +30 -26
  83. package/skills/testing/client-components.md +193 -8
  84. package/skills/testing/handles.md +2 -0
  85. package/skills/testing/loader.md +1 -1
  86. package/skills/testing/middleware.md +1 -1
  87. package/skills/testing/render-handler.md +18 -18
  88. package/skills/testing/serve-shell-request.md +63 -14
  89. package/skills/testing/server-actions.md +17 -17
  90. package/skills/testing/server-tree.md +1 -1
  91. package/skills/typesafety/generated-files-and-cli.md +1 -1
  92. package/skills/use-cache/SKILL.md +16 -5
  93. package/skills/vercel/SKILL.md +3 -3
  94. package/src/bin/rango.ts +1 -1
  95. package/src/browser/event-controller.ts +75 -2
  96. package/src/browser/history-state.ts +39 -17
  97. package/src/browser/navigation-bridge.ts +41 -18
  98. package/src/browser/navigation-transaction.ts +15 -10
  99. package/src/browser/partial-update.ts +10 -2
  100. package/src/browser/rango-state.ts +3 -1
  101. package/src/browser/react/Link.tsx +2 -3
  102. package/src/browser/react/NavigationProvider.tsx +25 -1
  103. package/src/browser/react/context.ts +13 -0
  104. package/src/browser/react/location-state-shared.ts +200 -34
  105. package/src/browser/react/location-state.ts +40 -75
  106. package/src/browser/react/subscribe-to-nothing.ts +7 -0
  107. package/src/browser/react/use-handle.ts +20 -1
  108. package/src/browser/rsc-router.tsx +33 -11
  109. package/src/browser/server-action-bridge.ts +17 -1
  110. package/src/cache/cache-scope.ts +18 -8
  111. package/src/cache/cf/cf-cache-store.ts +75 -55
  112. package/src/cache/cf/cf-cache-types.ts +12 -4
  113. package/src/cache/handle-snapshot.ts +69 -87
  114. package/src/cache/index.ts +4 -0
  115. package/src/cache/shell-snapshot.ts +35 -5
  116. package/src/cache/types.ts +21 -10
  117. package/src/cache/vercel/vercel-cache-store.ts +48 -20
  118. package/src/client-urls/client-root.tsx +1 -0
  119. package/src/client-urls/navigation.ts +10 -0
  120. package/src/client-urls/optimistic-location.ts +10 -4
  121. package/src/handles/deferred-resolution.ts +31 -0
  122. package/src/prerender/build-shell-capture.ts +11 -9
  123. package/src/router/manifest.ts +2 -0
  124. package/src/router/match-middleware/cache-lookup.ts +11 -18
  125. package/src/router/navigation-snapshot.ts +25 -6
  126. package/src/router/request-classification.ts +15 -12
  127. package/src/router/router-interfaces.ts +2 -1
  128. package/src/router/router-options.ts +17 -7
  129. package/src/router/segment-resolution/loader-cache.ts +95 -60
  130. package/src/router-versions.ts +60 -0
  131. package/src/rsc/full-payload.ts +6 -2
  132. package/src/rsc/handler-context.ts +5 -0
  133. package/src/rsc/handler.ts +14 -4
  134. package/src/rsc/rsc-rendering.ts +12 -1
  135. package/src/rsc/shell-build-manifest.ts +10 -7
  136. package/src/rsc/shell-capture.ts +36 -26
  137. package/src/rsc/types.ts +4 -6
  138. package/src/server/build-version-table.ts +115 -0
  139. package/src/server/handle-store.ts +344 -127
  140. package/src/server/request-context.ts +62 -11
  141. package/src/ssr/ssr-root.tsx +16 -4
  142. package/src/testing/build-versions.ts +64 -0
  143. package/src/testing/dispatch.ts +6 -0
  144. package/src/testing/dom.entry.ts +1 -0
  145. package/src/testing/flight.entry.ts +1 -0
  146. package/src/testing/index.ts +3 -0
  147. package/src/testing/internal/context.ts +2 -1
  148. package/src/testing/internal/seed-vars.ts +14 -2
  149. package/src/testing/location-state-key.ts +18 -8
  150. package/src/testing/render-handler.ts +2 -1
  151. package/src/testing/render-route.tsx +277 -43
  152. package/src/testing/serve-shell-request.ts +80 -11
  153. package/src/testing/vitest-stubs/version.ts +4 -1
  154. package/src/types/handler-context.ts +8 -5
  155. package/src/vite/discovery/build-versions.ts +1281 -0
  156. package/src/vite/discovery/installed-packages.ts +159 -0
  157. package/src/vite/discovery/prerender-collection.ts +38 -1
  158. package/src/vite/discovery/router-versions-phase.ts +336 -0
  159. package/src/vite/discovery/shell-prerender-phase.ts +28 -38
  160. package/src/vite/discovery/state.ts +19 -0
  161. package/src/vite/discovery/virtual-module-codegen.ts +7 -2
  162. package/src/vite/encryption-key.ts +101 -9
  163. package/src/vite/plugin-types.ts +28 -0
  164. package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
  165. package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
  166. package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
  167. package/src/vite/plugins/expose-internal-ids.ts +12 -3
  168. package/src/vite/plugins/stable-rsc-output.ts +147 -0
  169. package/src/vite/plugins/version-plugin.ts +19 -24
  170. package/src/vite/plugins/version.d.ts +17 -2
  171. package/src/vite/plugins/virtual-entries.ts +31 -5
  172. package/src/vite/rango.ts +28 -3
  173. package/src/vite/router-discovery.ts +82 -13
  174. package/src/vite/utils/client-chunks.ts +5 -1
  175. package/src/vite/utils/compare-strings.ts +9 -0
  176. package/src/vite/utils/package-resolution.ts +9 -0
  177. package/src/vite/utils/prerender-utils.ts +4 -1
  178. package/src/vite/utils/shared-utils.ts +1 -1
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Per-router cache versions, computed from the built server output.
3
+ *
4
+ * Every cache key and stored PPR shell is tied to a version. A router's
5
+ * versions are hashes of the code that router can run, so the same code gives
6
+ * the same versions and a new deployment finds the entries the previous one
7
+ * wrote. Design and measurements: docs/design/per-app-cache-version.md.
8
+ *
9
+ * Two steps, both pure over data the build hooks record:
10
+ *
11
+ * 1. Membership ({@link resolveRouterMembers}): which server modules can a
12
+ * router run. Walked on the MODULE graph, not the chunk graph, because the
13
+ * build has registries that name every app's code (plugin-rsc's
14
+ * server-reference map, the loader manifest, the routes manifest). A walk
15
+ * that followed them would put every router's code in every router's set.
16
+ * 2. Hashing ({@link computeRouterVersions}): the bytes on disk of the chunks
17
+ * holding those modules, read AFTER postprocessBundle rewrote them, with
18
+ * everything taken out that says where or how the build ran instead of what
19
+ * it built ({@link digestBundle}).
20
+ *
21
+ * One rule covers what the build cannot determine (a file it lists and cannot
22
+ * read, an import it cannot resolve): the build fails, or the version changes
23
+ * on every build. A version never silently stays the same.
24
+ */
25
+ import { type RouterVersionsTable } from "../../router-versions.js";
26
+ /** Import edges of one module, as the bundler resolved them. */
27
+ export interface BuildModuleEdges {
28
+ readonly imports: readonly string[];
29
+ readonly dynamicImports: readonly string[];
30
+ /**
31
+ * The module encrypts inline-action bound arguments, so what it renders can
32
+ * only be decrypted with the build's encryption key.
33
+ */
34
+ readonly encryptsBoundArgs?: boolean;
35
+ }
36
+ export interface BuildChunk {
37
+ readonly fileName: string;
38
+ /** The chunk's name without its content hash. */
39
+ readonly name: string;
40
+ readonly moduleIds: readonly string[];
41
+ /** Specifiers the chunk imports that the bundle does not contain. */
42
+ readonly externalImports?: readonly string[];
43
+ }
44
+ export interface BuildAsset {
45
+ readonly fileName: string;
46
+ /** The asset's name without its content hash. */
47
+ readonly name: string;
48
+ }
49
+ /** The files one environment's bundle emits. */
50
+ export interface BundleFiles {
51
+ readonly chunks: readonly BuildChunk[];
52
+ readonly assets: readonly BuildAsset[];
53
+ }
54
+ /** The RSC environment's bundle, from its real (not scan) build pass. */
55
+ export interface ServerBuildGraph extends BundleFiles {
56
+ readonly modules: ReadonlyMap<string, BuildModuleEdges>;
57
+ }
58
+ /** The client environment's module graph and emitted file names. */
59
+ export interface ClientBuildGraph {
60
+ readonly modules: ReadonlyMap<string, BuildModuleEdges>;
61
+ readonly fileNames: readonly string[];
62
+ }
63
+ /** One discovered router: its runtime id and the module that creates it. */
64
+ export interface RouterSource {
65
+ readonly id: string;
66
+ /** Absolute path of the file calling createRouter(), when known. */
67
+ readonly moduleId: string | undefined;
68
+ }
69
+ /**
70
+ * One piece of data the build produced that a router's output depends on and
71
+ * that none of the router's chunks holds. Named by the digest of its bytes.
72
+ *
73
+ * The kinds feed different versions, by where the data can end up at run
74
+ * time:
75
+ *
76
+ * - A Static payload is a segment of an ordinary route, so a `cache()` entry
77
+ * of that route stores it. It is part of the DATA version.
78
+ * - A clientUrls() projection is what the server materializes a client-defined
79
+ * route group from: its loaders, loading states, transitions and route ids.
80
+ * It ships in the routes registry (virtual-module-codegen.ts), which a
81
+ * lazily mounted app's chunks do not include. DATA version.
82
+ * - A server component's stylesheet URLs are read at run time from plugin-rsc's
83
+ * assets manifest and rendered as `<link>`s into the router's Flight, so
84
+ * they are in cached payloads while no server chunk holds them. DATA version.
85
+ * - A Prerender payload is served from the build's own store and never
86
+ * written to the segment cache (withCacheLookup returns before the cache
87
+ * scope, match-middleware/cache-lookup.ts). Only stored HTML holds it: a PPR
88
+ * shell or a document-cache response of the route. It is part of the
89
+ * DOCUMENT version only.
90
+ */
91
+ export interface BuildDataRecord {
92
+ readonly kind: "prerender" | "static" | "client-urls" | "server-css";
93
+ /**
94
+ * Prerender manifest key, Static handler id, clientUrls() module, or the
95
+ * module a `serverResources` entry is keyed by.
96
+ */
97
+ readonly key: string;
98
+ /** sha256 of the payload, the projection, or the entry. */
99
+ readonly digest: string;
100
+ /** Router that rendered it (prerender). */
101
+ readonly routerId?: string;
102
+ /**
103
+ * Module the data belongs to (static: the handler's; client-urls: the
104
+ * clientUrls() module; server-css: the module that renders the links).
105
+ * Owned by {@link ownersOf}.
106
+ */
107
+ readonly moduleId?: string;
108
+ }
109
+ export interface RouterMembers {
110
+ /** Server modules the router can run. */
111
+ readonly modules: ReadonlySet<string>;
112
+ /** Chunk files holding any of them, plus the router's route manifest. */
113
+ readonly chunkFiles: ReadonlySet<string>;
114
+ }
115
+ /** The slice of a plugin context the graph recorders read. */
116
+ interface ModuleGraphContext {
117
+ getModuleIds(): Iterable<string>;
118
+ getModuleInfo(id: string): {
119
+ importedIds?: readonly string[];
120
+ dynamicallyImportedIds?: readonly string[];
121
+ code?: string | null;
122
+ } | null;
123
+ }
124
+ /** The slice of an output bundle the graph recorders read. */
125
+ type OutputBundleLike = Record<string, {
126
+ type: string;
127
+ fileName: string;
128
+ name?: string;
129
+ names?: readonly string[];
130
+ moduleIds?: readonly string[];
131
+ imports?: readonly string[];
132
+ dynamicImports?: readonly string[];
133
+ }>;
134
+ /** The chunks and assets of a bundle. Call from generateBundle. */
135
+ export declare function recordBundleFiles(bundle: OutputBundleLike): BundleFiles;
136
+ /** Call from the RSC environment's generateBundle. */
137
+ export declare function recordServerGraph(context: ModuleGraphContext, bundle: OutputBundleLike): ServerBuildGraph;
138
+ /** Call from the client environment's generateBundle. */
139
+ export declare function recordClientGraph(context: ModuleGraphContext, bundle: OutputBundleLike): ClientBuildGraph;
140
+ /** plugin-rsc's build-time map of every server reference in the app. */
141
+ export declare const SERVER_REFERENCES_MODULE_ID = "\0virtual:vite-rsc/server-references";
142
+ /**
143
+ * plugin-rsc writes the key to this file next to the RSC entry and has chunks
144
+ * import it at run time (its `rsc:encryption-key` renderChunk), so the key is
145
+ * in no chunk and in no bundle graph. The file exists only when a rendered
146
+ * chunk reads the key.
147
+ */
148
+ export declare const ENCRYPTION_KEY_FILE = "__vite_rsc_encryption_key.js";
149
+ /**
150
+ * Work out, per router, the server modules it can run and the chunk files
151
+ * holding them.
152
+ *
153
+ * The walk starts at the module calling createRouter() AND at every module
154
+ * that statically imports it, directly or through other static imports. What
155
+ * runs with a router is not only what its own module imports: the modules
156
+ * importing it attach middleware and handlers (`router.use(...)` in an app
157
+ * module) and wrap its fetch (the mounted handler). The climb stops at a
158
+ * dynamic import, which is how a host mounts an app it does not run with; so
159
+ * routers that one module imports statically share that module's code, and
160
+ * only lazily mounted apps are independent of each other.
161
+ *
162
+ * A router whose module the build cannot find is left out of the result; the
163
+ * runtime then serves it with the whole-build versions
164
+ * ({@link DEFAULT_ROUTER_VERSIONS_KEY}).
165
+ *
166
+ * Membership is per module, the hash is per chunk file. A chunk that holds one
167
+ * reached module is hashed whole, so a router's version also covers whatever
168
+ * the bundler placed next to its code: the conservative direction.
169
+ */
170
+ export declare function resolveRouterMembers(server: ServerBuildGraph, client: ClientBuildGraph | undefined, routers: readonly RouterSource[]): Map<string, RouterMembers>;
171
+ export declare function sha256(value: string | Uint8Array): string;
172
+ /**
173
+ * A module id without the build machine's checkout directory: root-relative
174
+ * inside the project, and from the outermost node_modules for an installed
175
+ * package. The outermost, not the innermost: two installed versions of one
176
+ * package differ only in the directories between (`.pnpm/foo@1…/node_modules/foo`,
177
+ * a nested `a/node_modules/foo`), and must not get the same id. A pnpm store
178
+ * directory is reduced to `name@version` ({@link PNPM_PEER_SUFFIX}).
179
+ *
180
+ * A virtual id can carry a file path in its query. plugin-rsc's server CSS
181
+ * module is `virtual:vite-rsc/css?type=rsc&id=<URI-encoded absolute importer>`,
182
+ * so those values are made portable too.
183
+ */
184
+ export declare function portableModuleId(id: string, projectRoot: string): string;
185
+ /** A sourcemap describes a file; it is not code the server runs. */
186
+ export declare function isSourceMap(fileName: string): boolean;
187
+ /**
188
+ * Drop the body of the server-reference map from a chunk's source.
189
+ *
190
+ * The map lists every `"use server"` module of the build with its export
191
+ * names, and it sits in a chunk every router runs (plugin-rsc's server
192
+ * runtime imports it). Hashed as is, the map would put every app's action
193
+ * list into every app's version. Its text is derived: each module it names is
194
+ * hashed by the routers that reach it (resolveRouterMembers), so nothing the
195
+ * map says is lost by skipping it.
196
+ *
197
+ * Works on the region comments, so a minified server build (which has none)
198
+ * keeps the map in every router's hash: more clearing, never less.
199
+ */
200
+ export declare function stripServerReferenceMap(source: string): string;
201
+ /**
202
+ * Take the module paths out of a chunk's region comments.
203
+ *
204
+ * The bundler prints one before each module's code, and the path says where
205
+ * the build ran, not what it built: it is relative to the directory `vite
206
+ * build` was started from (`vite build apps/web` from a repository root
207
+ * prints other paths than `vite build` in the app), it holds the pnpm store
208
+ * directory with its peer suffix, and a virtual id is printed verbatim,
209
+ * absolute paths in its query included. The code under the comment is hashed
210
+ * either way.
211
+ */
212
+ export declare function stripRegionPaths(source: string): string;
213
+ /** One thing a version is a hash of: its name and the digest of its bytes. */
214
+ export type VersionInput = readonly [name: string, digest: string];
215
+ /** One bundle file, digested once for every router. */
216
+ export interface DigestedFile {
217
+ readonly fileName: string;
218
+ /**
219
+ * What the file is, independent of its content: the un-hashed name plus, for
220
+ * a chunk, a digest of the module ids inside it. Stands in for the file's
221
+ * hashed name wherever a router that owns the file refers to it.
222
+ */
223
+ readonly identity: string;
224
+ /**
225
+ * Digest of the file's bytes with every reference to a bundle file replaced
226
+ * by one token ({@link FILE_REFERENCE}), so a content hash in a file name
227
+ * never reaches it.
228
+ */
229
+ readonly base: string;
230
+ /** The files those references named, in order of appearance. */
231
+ readonly references: readonly string[];
232
+ /** Specifiers the file imports that the bundle does not contain. */
233
+ readonly externalImports: readonly string[];
234
+ }
235
+ /**
236
+ * Digest every file of a bundle, as it is on disk.
237
+ *
238
+ * Three things are taken out of a chunk's text first, each because it says
239
+ * where or how the build ran: the server-reference map's body
240
+ * ({@link stripServerReferenceMap}), the paths in region comments
241
+ * ({@link stripRegionPaths}), and the hashed names of bundle files. A name
242
+ * carries the content hash of its file, and every chunk importing the file
243
+ * has the name in its bytes, so one router's change would otherwise move the
244
+ * digest of every chunk naming its chunk. Only names with a content hash are
245
+ * replaced; a fixed name such as the entry's "index.js" is the same in every
246
+ * build. An asset is data (a route manifest staged as a text module can be
247
+ * megabytes): hashed as bytes, never scanned.
248
+ *
249
+ * Throws when the bundle lists a file that is not on disk: its bytes would
250
+ * silently drop out of every version.
251
+ */
252
+ export declare function digestBundle(bundle: BundleFiles, projectRoot: string, read: (fileName: string) => Uint8Array | undefined, label: string): DigestedFile[];
253
+ /** How the build reads what the bundles leave external. */
254
+ export interface ExternalResolver {
255
+ /**
256
+ * What is installed for a package: `name@version` of the package and of
257
+ * everything it depends on (installed-packages.ts). The chunk only says
258
+ * `from "pkg"`, whichever version that is. `undefined` when the package
259
+ * cannot be found.
260
+ */
261
+ readonly describePackage: (name: string) => string | undefined;
262
+ /** The bytes of a file at an absolute path; `undefined` when it is absent. */
263
+ readonly readFile: (path: string) => Uint8Array | undefined;
264
+ /** Unique to this build: the digest of an input that cannot be determined. */
265
+ readonly buildId: string;
266
+ }
267
+ export type ExternalImport = {
268
+ readonly kind: "provided";
269
+ } | {
270
+ readonly kind: "package";
271
+ readonly name: string;
272
+ } | {
273
+ readonly kind: "file";
274
+ } | {
275
+ readonly kind: "unknown";
276
+ };
277
+ /**
278
+ * What an import a bundle leaves external is. THE rule for all of them, and
279
+ * the only place it is decided:
280
+ *
281
+ * - `provided`: the version does not cover it. A runtime builtin (`node:fs`,
282
+ * bare `fs`, `cloudflare:workers`, `bun:sqlite`), a `data:` URL (its content
283
+ * is the specifier, which is in the chunk), or a marker of another plugin
284
+ * whose file is a bundle output the hash already covers.
285
+ * - `package`: an installed package. The version covers what is installed.
286
+ * - `file`: a relative or absolute path. The version covers the file's bytes.
287
+ * - `unknown`: anything else (`https:`, `npm:`, a name npm would reject).
288
+ *
289
+ * A package that is not installed, a path that does not resolve and every
290
+ * `unknown` import get a value unique to the build, and the build names them:
291
+ * the routers importing them get a new version on every build. Nothing the
292
+ * build cannot read is left out of a version.
293
+ */
294
+ export declare function classifyExternal(specifier: string): ExternalImport;
295
+ export interface ComputeRouterVersionsInput {
296
+ readonly projectRoot: string;
297
+ readonly server: ServerBuildGraph;
298
+ readonly client: ClientBuildGraph;
299
+ /** The SSR environment's bundle, with the files plugin-rsc writes next to it. */
300
+ readonly ssr: BundleFiles;
301
+ readonly routers: readonly RouterSource[];
302
+ readonly buildData: readonly BuildDataRecord[];
303
+ /** Read a file of the RSC output directory; undefined when it is absent. */
304
+ readonly readServerFile: (fileName: string) => Uint8Array | undefined;
305
+ /** Read a file of the SSR output directory; undefined when it is absent. */
306
+ readonly readSsrFile: (fileName: string) => Uint8Array | undefined;
307
+ /** Vite's `base`, a prefix of every asset URL in stored HTML. */
308
+ readonly base: string;
309
+ /**
310
+ * plugin-rsc's `serverResources` from the built assets manifest: the
311
+ * root-relative id of a server module that imports CSS, to the text of its
312
+ * entry (the stylesheet URLs).
313
+ */
314
+ readonly serverResources?: ReadonlyMap<string, string>;
315
+ readonly externals: ExternalResolver;
316
+ }
317
+ export interface RouterVersionDetail {
318
+ readonly routerId: string;
319
+ readonly data: string;
320
+ readonly document: string;
321
+ /**
322
+ * Everything the data version is a hash of, in hashed order: `file
323
+ * <identity>` for a server file, `static <id>` for a Static payload,
324
+ * `client-urls <module>` for a clientUrls() projection, `server-css
325
+ * <module>` for a server component's stylesheets, `external <import>` for
326
+ * what the bundle left external, `encryption-key` for the key. Two builds'
327
+ * lists differ exactly where the version's inputs differ.
328
+ */
329
+ readonly dataInputs: readonly VersionInput[];
330
+ /**
331
+ * What the document version adds to the data version: `ssr-and-client` (the
332
+ * SSR output and the client asset names) and `prerender <key>` for a
333
+ * Prerender payload.
334
+ */
335
+ readonly documentInputs: readonly VersionInput[];
336
+ /** The key is part of the router's data version. */
337
+ readonly usesEncryptionKey: boolean;
338
+ }
339
+ /**
340
+ * What stored HTML depends on besides a router's server code, one set for the
341
+ * build (the SSR output is not split by router). Hashed as the
342
+ * `ssr-and-client` input of every document version.
343
+ */
344
+ export interface SsrAndClientInputs {
345
+ /** Vite's `base`, a prefix of every asset URL in stored HTML. */
346
+ readonly base: string;
347
+ /**
348
+ * The SSR output, file by file ({@link digestBundle}). It renders the HTML,
349
+ * and its own code can change without the client's.
350
+ */
351
+ readonly ssr: readonly VersionInput[];
352
+ /** What the SSR output leaves external ({@link classifyExternal}). */
353
+ readonly externals: readonly VersionInput[];
354
+ /**
355
+ * The client asset names. A prelude and an open tab both hold them, and the
356
+ * names carry the bundler's content hashes.
357
+ */
358
+ readonly client: readonly string[];
359
+ }
360
+ export interface ComputedRouterVersions {
361
+ readonly table: RouterVersionsTable;
362
+ /** One entry per attributed router, then the whole-build entry. */
363
+ readonly details: readonly RouterVersionDetail[];
364
+ /** What every document version covers besides its router's data version. */
365
+ readonly ssrAndClient: SsrAndClientInputs;
366
+ /** Server files no attributed router owns, by identity. */
367
+ readonly unownedFiles: readonly string[];
368
+ /** Imports the build could not determine ({@link classifyExternal}). */
369
+ readonly undetermined: readonly string[];
370
+ }
371
+ /**
372
+ * Compute every router's data and document version, and the whole-build pair.
373
+ *
374
+ * A router's data version covers: its chunk files, the bundle assets those
375
+ * files name (a route manifest staged as a text module), what those files
376
+ * leave external, and the inputs it owns that no chunk holds
377
+ * ({@link ownersOf}). Its document version is the data version plus the SSR
378
+ * output, the client asset names, `base` and its Prerender payloads.
379
+ */
380
+ export declare function computeRouterVersions(input: ComputeRouterVersionsInput): ComputedRouterVersions;
381
+ /**
382
+ * The chunk files of a bundle that hold the `@rangojs/router:version` module,
383
+ * where the placeholder has to be replaced. Recorded for every environment:
384
+ * the module is emitted wherever something imports it (a second worker that
385
+ * uses a cache store), and an unreplaced placeholder throws on load.
386
+ */
387
+ export declare function recordVersionModuleFiles(bundle: OutputBundleLike): string[];
388
+ /**
389
+ * Replace the version module's placeholder with the table. Returns undefined
390
+ * when `source` does not hold the placeholder.
391
+ */
392
+ export declare function fillRouterVersions(source: string, table: RouterVersionsTable): string | undefined;
393
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * What is installed for a package a server build leaves external.
3
+ *
4
+ * The node preset bundles only packages that need the React server condition
5
+ * (plugin-rsc's `noExternal`); every other dependency stays an import the
6
+ * deployed process resolves from node_modules. The built chunk then says
7
+ * `from "pkg"` whichever version is installed, so a lockfile bump changes what
8
+ * the server renders and nothing the cache versions hash
9
+ * (discovery/build-versions.ts). This module supplies the missing input: the
10
+ * `name@version` of the package and of everything it depends on, as installed
11
+ * next to the project at build time.
12
+ *
13
+ * A registry package's code is fixed by its version. A linked package (a
14
+ * workspace package, resolved to a directory outside node_modules) is not.
15
+ * Vite bundles those unless the config lists one in `resolve.external`; then
16
+ * it comes through here and its files are digested instead.
17
+ */
18
+ /**
19
+ * Returns a function describing an installed package by name: the sorted
20
+ * `name@version` lines of the package and its transitive dependencies
21
+ * (dependencies, optionalDependencies and peerDependencies, each resolved from
22
+ * the package that declares it), with a digest of its files for a linked
23
+ * one, or `undefined` when the package is not installed. Results are kept per
24
+ * name for the life of the returned function.
25
+ */
26
+ export declare function createInstalledPackageDescriber(projectRoot: string): (name: string) => string | undefined;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Post-build cache version phase.
3
+ *
4
+ * Runs from the buildApp post hook, after every environment bundle is written
5
+ * and postprocessBundle has rewritten the RSC chunks, and before the shell
6
+ * capture phase, which stamps its entries with the document versions computed
7
+ * here. Reads the bytes that ship, computes each router's versions
8
+ * (build-versions.ts) and writes the table into the built
9
+ * `@rangojs/router:version` module in place of its placeholder.
10
+ */
11
+ import { type RouterVersionsTable } from "../../router-versions.js";
12
+ import type { DiscoveryState } from "./state.js";
13
+ /** Minimal builder surface the post-build phases read. */
14
+ export interface BuilderLike {
15
+ config?: {
16
+ base?: string;
17
+ };
18
+ environments?: Record<string, {
19
+ config?: {
20
+ build?: {
21
+ outDir?: string;
22
+ };
23
+ };
24
+ } | undefined>;
25
+ }
26
+ /**
27
+ * An environment's output directory, or Vite's default layout without one.
28
+ * Resolved against the root: a relative `build.outDir` is relative to it, not
29
+ * to the directory `vite build <root>` was run from.
30
+ */
31
+ export declare function environmentOutDir(builder: BuilderLike | undefined, projectRoot: string, environment: string): string;
32
+ /**
33
+ * Where the build leaves what each version was computed from. Outside the
34
+ * output directories, so it never ships.
35
+ */
36
+ export declare const VERSIONS_REPORT_FILE: string;
37
+ /**
38
+ * The `serverResources` map of plugin-rsc's assets manifest
39
+ * (`export default { ..., serverResources: { "<module>": { js, css } } }`):
40
+ * each server module that imports CSS, to the source text of its entry. Text,
41
+ * not a parsed value: with `renderBuiltUrl` an entry holds runtime expressions.
42
+ * Throws when the manifest is not that shape, so a plugin-rsc that moves it
43
+ * fails the build instead of dropping stylesheets from the versions.
44
+ */
45
+ export declare function readServerResources(manifest: string): Map<string, string>;
46
+ /**
47
+ * Compute the versions, write them into the built version module and return
48
+ * the table (`undefined` outside a build). Fails the build when an input was
49
+ * not recorded or cannot be read: a version computed without it would stay
50
+ * the same when it changes.
51
+ */
52
+ export declare function runRouterVersionsPhase(s: DiscoveryState, builder: BuilderLike | undefined): RouterVersionsTable | undefined;
@@ -18,23 +18,13 @@
18
18
  * or never consulted the prerender store is SKIPPED loudly — the route keeps
19
19
  * producer A (runtime capture) semantics, never a wrong-lane bake.
20
20
  */
21
+ import { type RouterVersionsTable } from "../../router-versions.js";
22
+ import { type BuilderLike } from "./router-versions-phase.js";
21
23
  import type { DiscoveryState } from "./state.js";
22
24
  /**
23
- * Minimal builder surface the phase reads: resolved plugins (for the main
24
- * build's version plugin) and per-environment outDirs.
25
+ * @param versionsTable The versions the shipped build serves with
26
+ * (router-versions-phase.ts), `undefined` outside a build. Never the temp
27
+ * server's own version-plugin stamp, which is a dev Date.now() and would
28
+ * fail the serve-side isValidShellHit gate forever.
25
29
  */
26
- interface BuilderLike {
27
- config?: {
28
- base?: string;
29
- plugins?: readonly unknown[];
30
- };
31
- environments?: Record<string, {
32
- config?: {
33
- build?: {
34
- outDir?: string;
35
- };
36
- };
37
- } | undefined>;
38
- }
39
- export declare function runShellPrerenderPhase(s: DiscoveryState, builder: BuilderLike | undefined): Promise<void>;
40
- export {};
30
+ export declare function runShellPrerenderPhase(s: DiscoveryState, builder: BuilderLike | undefined, versionsTable: RouterVersionsTable | undefined): Promise<void>;
@@ -8,6 +8,7 @@
8
8
  import type { ScanFilter } from "../../build/generate-route-types.js";
9
9
  import type { ClientUrlProjection } from "../../client-urls/server-projection.js";
10
10
  export declare const VIRTUAL_ROUTES_MANIFEST_ID = "virtual:rsc-router/routes-manifest";
11
+ export declare const VIRTUAL_LOADER_MANIFEST_ID = "virtual:rsc-router/loader-manifest";
11
12
  export interface PluginOptions {
12
13
  enableBuildPrerender?: boolean;
13
14
  staticRouteTypesGeneration?: boolean;
@@ -143,6 +144,19 @@ export interface DiscoveryState {
143
144
  * buildEnd (aborted build).
144
145
  */
145
146
  shellPhaseTempServer: import("vite").ViteDevServer | null;
147
+ /**
148
+ * Inputs of the per-router cache versions (build-versions.ts), recorded by
149
+ * generateBundle. plugin-rsc builds the RSC and SSR environments twice (a
150
+ * scan pass, then the real pass); each pass overwrites its slot, so the
151
+ * buildApp post hook reads the real pass.
152
+ */
153
+ serverBuildGraph: import("./build-versions.js").ServerBuildGraph | null;
154
+ clientBuildGraph: import("./build-versions.js").ClientBuildGraph | null;
155
+ ssrBundle: import("./build-versions.js").BundleFiles | null;
156
+ /** Per environment, the chunk files holding the version module. */
157
+ versionModuleFiles: Map<string, string[]>;
158
+ /** Prerender and Static payloads the build rendered, by content digest. */
159
+ buildData: import("./build-versions.js").BuildDataRecord[];
146
160
  handlerChunkInfoMap: Map<string, ChunkInfo>;
147
161
  staticHandlerChunkInfoMap: Map<string, ChunkInfo>;
148
162
  rscEntryFileName: string | null;
@@ -1,2 +1,20 @@
1
+ /**
2
+ * Throws unless `value` is the base64 form of an AES key (16, 24 or 32 bytes).
3
+ * `source` names where the value came from, for the message.
4
+ */
5
+ export declare function assertValidEncryptionKey(value: unknown, source: string): void;
6
+ /**
7
+ * Resolve the build's encryption key from rango({ encryptionKey }) or the
8
+ * RANGO_ENCRYPTION_KEY fallback, validating whichever is set. Called by
9
+ * rango() at config time, before any plugin-rsc instance reads the key, so an
10
+ * invalid key fails the config load instead of the first encrypted action.
11
+ * A key generated earlier in the process is kept when neither source is set:
12
+ * the discovery temp server must encrypt with the main build's key.
13
+ */
14
+ export declare function configureEncryptionKey(option: string | undefined): void;
1
15
  export declare function buildEncryptionKey(): string;
16
+ /** False when this build generated its own key (it differs on every build). */
17
+ export declare function isEncryptionKeyStable(): boolean;
2
18
  export declare function defineEncryptionKeyExpr(): string;
19
+ /** @internal Test seam: forget the process-wide key. */
20
+ export declare function resetEncryptionKeyForTests(): void;
@@ -213,6 +213,33 @@ interface RangoBaseOptions {
213
213
  prerender?: {
214
214
  onError?: "fail" | "warn";
215
215
  };
216
+ /**
217
+ * The key inline server actions encrypt their bound arguments with:
218
+ * base64-encoded 32 bytes (`openssl rand -base64 32`). Pass it from the
219
+ * environment, never as a literal in the config file:
220
+ *
221
+ * ```ts
222
+ * rango({ encryptionKey: process.env.RANGO_ENCRYPTION_KEY })
223
+ * ```
224
+ *
225
+ * Why set it: the key is part of the cache version of every router whose
226
+ * server code encrypts with it, because a payload cached under one key
227
+ * carries arguments another key cannot decrypt. Without a stable key rango
228
+ * generates one per build, so those routers get a new version, and a cleared
229
+ * cache, on every deploy. With a stable key their cache survives a deploy
230
+ * that does not change their server code. It also lets a build decrypt
231
+ * arguments an earlier build encrypted (a cached or prerendered payload, an
232
+ * open tab).
233
+ *
234
+ * Validated when `rango()` is called: a value that is not base64 or does not
235
+ * decode to an AES key size (16, 24 or 32 bytes) throws. `undefined` (the
236
+ * variable is unset) falls back to the `RANGO_ENCRYPTION_KEY` environment
237
+ * variable, then to a generated key. Rotating the key clears the cache of
238
+ * the routers that use it.
239
+ *
240
+ * @default process.env.RANGO_ENCRYPTION_KEY, else a key generated per build
241
+ */
242
+ encryptionKey?: string;
216
243
  }
217
244
  /**
218
245
  * Options for Node.js deployment (default)
@@ -1,10 +1,8 @@
1
1
  import type { Plugin } from "vite";
2
2
  /**
3
- * Plugin that auto-injects VERSION, routes-manifest, and loader-manifest into
4
- * custom entry.rsc files. If a custom entry.rsc file uses createRSCHandler but
5
- * doesn't pass version, this transform adds the import and property
6
- * automatically. It also ensures the routes-manifest and loader-manifest
7
- * virtual modules are always imported.
3
+ * Plugin that auto-injects the routes-manifest and loader-manifest imports
4
+ * into a custom entry.rsc file (the Cloudflare preset, where the consumer owns
5
+ * the worker entry).
8
6
  *
9
7
  * The loader-manifest import is what makes fetchable loaders resolvable on a
10
8
  * custom worker entry. The virtual RSC entry (getVirtualEntryRSC) imports the
@@ -18,4 +16,4 @@ import type { Plugin } from "vite";
18
16
  * manifest, hence the production-only failure this fixes.
19
17
  * @internal
20
18
  */
21
- export declare function createVersionInjectorPlugin(rscEntryPath: string | undefined): Plugin;
19
+ export declare function createEntryBootstrapInjectorPlugin(rscEntryPath: string | undefined): Plugin;
@@ -1,5 +1,6 @@
1
1
  import type { CreateExportBinding } from "./types.js";
2
2
  export declare function isExportOnlyFile(code: string, bindings: CreateExportBinding[]): boolean;
3
+ export declare function createCallPattern(fnNames: string[]): RegExp;
3
4
  export declare function countCreateCallsForNames(code: string, fnNames: string[]): number;
4
5
  export declare function offsetToLineColumn(code: string, index: number): {
5
6
  line: number;
@@ -1,5 +1,19 @@
1
1
  import type { Plugin } from "vite";
2
2
  import MagicString from "magic-string";
3
+ /**
4
+ * A router's `$$id`: a hash of its root-relative file and of its position
5
+ * among the file's createRouter() calls.
6
+ *
7
+ * Not of the call's line. `code` is TS-transformed, and a dev server keeps
8
+ * comments there that a build drops, so a line of `code` gave build-time
9
+ * discovery and the bundle different ids for any router with a comment above
10
+ * it (e2e/test-app, tests/cloudflare-basic): the build registered the lazy
11
+ * route manifest under an id no running router had. And a line of the file as
12
+ * written changes with every blank line or comment added above the call,
13
+ * which renamed the state cookie, the route manifest chunk and the router's
14
+ * cache versions for an edit that changed nothing.
15
+ */
16
+ export declare function routerId(filePath: string, callIndex: number): string;
3
17
  export declare function transformRouter(code: string, filePath: string, routerFnNames: string[], absolutePath?: string, warn?: (message: string) => void): {
4
18
  code: string;
5
19
  map: ReturnType<MagicString["generateMap"]>;