@rangojs/router 0.20.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.
- package/dist/bin/rango.js +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +29 -4
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +12 -6
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +55 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/plugin-types.d.ts +27 -0
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +167 -22
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugin-types.ts +28 -0
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/src/vite/utils/client-chunks.ts +5 -1
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
package/src/cache/types.ts
CHANGED
|
@@ -42,6 +42,18 @@ export interface CacheGetResult {
|
|
|
42
42
|
shouldRevalidate: boolean;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Where an entry a store writes during one request can be read by a later
|
|
47
|
+
* request. `router.prerender()` warms a route only when the app store answers
|
|
48
|
+
* `"global"` or `"regional"`: a warm fills the store where it runs, which
|
|
49
|
+
* serves other traffic only when the store is shared beyond that place.
|
|
50
|
+
* - `"global"`: every location reads the same entries.
|
|
51
|
+
* - `"regional"`: each region has its own copy; a warm fills the region it
|
|
52
|
+
* runs in.
|
|
53
|
+
* - `"local"`: only the process, isolate or edge location that wrote it.
|
|
54
|
+
*/
|
|
55
|
+
export type CacheStoreScope = "global" | "regional" | "local";
|
|
56
|
+
|
|
45
57
|
/**
|
|
46
58
|
* Low-level segment cache store interface.
|
|
47
59
|
*
|
|
@@ -49,9 +61,22 @@ export interface CacheGetResult {
|
|
|
49
61
|
* The store deals with serialized data - RSC serialization is handled
|
|
50
62
|
* by the cache provider layer.
|
|
51
63
|
*
|
|
64
|
+
* A store that outlives the process has to keep one build from reading what
|
|
65
|
+
* other code wrote. The built-in stores prefix their keys with the serving
|
|
66
|
+
* router's cache versions; a custom store reads the same pair with
|
|
67
|
+
* `getCacheVersions()` (`@rangojs/router/cache`): `data` for segment entries
|
|
68
|
+
* and items, `document` for responses and shells, and no version on its
|
|
69
|
+
* tag-invalidation records, so an invalidation reaches every version.
|
|
70
|
+
*
|
|
52
71
|
* @typeParam TEnv - Platform bindings type (e.g., Cloudflare env)
|
|
53
72
|
*/
|
|
54
73
|
export interface SegmentCacheStore<TEnv = unknown> {
|
|
74
|
+
/**
|
|
75
|
+
* Where an entry this store writes can be read ({@link CacheStoreScope}).
|
|
76
|
+
* Absent: unknown to the router, treated as `"local"`.
|
|
77
|
+
*/
|
|
78
|
+
readonly scope?: CacheStoreScope;
|
|
79
|
+
|
|
55
80
|
/**
|
|
56
81
|
* The store honors getShell(..., { claimRevalidation: false }) without
|
|
57
82
|
* claiming an SWR lock. Navigation replay requires this opt-in because it
|
|
@@ -161,6 +186,11 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
161
186
|
* @param ttl - Time-to-live in seconds
|
|
162
187
|
* @param swr - Optional stale-while-revalidate window in seconds
|
|
163
188
|
* @param tags - Optional cache tags for invalidation
|
|
189
|
+
*
|
|
190
|
+
* The built-in stores read the start of the render from the `Response`
|
|
191
|
+
* object itself (#1068). A custom store, or a wrapper that rebuilds the
|
|
192
|
+
* `Response` before delegating, stamps write time. The write gate
|
|
193
|
+
* (`predatesInvalidation`) runs before this call either way.
|
|
164
194
|
*/
|
|
165
195
|
putResponse?(
|
|
166
196
|
key: string,
|
|
@@ -415,13 +445,14 @@ export interface ShellCacheEntry {
|
|
|
415
445
|
/** React.version captured at prerender time; the read-time invalidation gate. */
|
|
416
446
|
reactVersion: string;
|
|
417
447
|
/**
|
|
418
|
-
* Build version captured at prerender time
|
|
419
|
-
* the
|
|
420
|
-
*
|
|
448
|
+
* Build version captured at prerender time: the RSC handler's `version`,
|
|
449
|
+
* which is the router's DOCUMENT version (router-versions.ts) — a hash of
|
|
450
|
+
* its server code and the client asset names by default, a stamp bumped on
|
|
451
|
+
* dev RSC-module edits. The second read-time gate: a persistent shared
|
|
421
452
|
* store (KV/runtime-cache) survives deploys, and an app-code change that
|
|
422
453
|
* keeps the same React version would otherwise leave a stale-build
|
|
423
|
-
* prelude+postponed live under the same key. A
|
|
424
|
-
* putShell received it.
|
|
454
|
+
* prelude+postponed live under the same key. A deploy that changes neither
|
|
455
|
+
* keeps the entry. A custom store returns it as putShell received it.
|
|
425
456
|
*/
|
|
426
457
|
buildVersion: string;
|
|
427
458
|
/**
|
|
@@ -443,9 +474,10 @@ export interface ShellCacheEntry {
|
|
|
443
474
|
* bake-lane loader pins (pruned to what a HIT reads, issue #941). Replaying
|
|
444
475
|
* them on a HIT keeps the freshly rendered hydration payload equal to the
|
|
445
476
|
* frozen prelude after the underlying cache entries have drifted; every
|
|
446
|
-
* other read on a HIT, a hole's included, reads the store.
|
|
447
|
-
*
|
|
448
|
-
* a tombstone
|
|
477
|
+
* other read on a HIT, a hole's included, reads the store. A
|
|
478
|
+
* prerender-served capture holds a `handles` record instead of a doc record
|
|
479
|
+
* (ShellSnapshotHandlesValue). Empty for a tombstone. A custom store
|
|
480
|
+
* returns it as putShell received it. See
|
|
449
481
|
* docs/design/ppr-shell-resume.md ("the capture data snapshot").
|
|
450
482
|
*/
|
|
451
483
|
snapshot: ShellSnapshotRecord[];
|
|
@@ -606,12 +638,26 @@ export interface ShellReadStats {
|
|
|
606
638
|
|
|
607
639
|
/**
|
|
608
640
|
* The families a shell snapshot pins: the doc segment record (recorded by
|
|
609
|
-
* RecordingShellStore)
|
|
610
|
-
* (lane rule: see resolveLoaderData, loader-cache.ts)
|
|
611
|
-
* pinned: a HIT's holes, and a bake-lane
|
|
612
|
-
* read the store.
|
|
641
|
+
* RecordingShellStore), the settled CONTAINER of each bake-lane loader
|
|
642
|
+
* (lane rule: see resolveLoaderData, loader-cache.ts), and a prerender-served
|
|
643
|
+
* entry's `handles` record. No cache read is pinned: a HIT's holes, and a bake-lane
|
|
644
|
+
* loader body that runs on the HIT, read the store.
|
|
613
645
|
*/
|
|
614
|
-
export type ShellSnapshotFamily = "segment" | "loader";
|
|
646
|
+
export type ShellSnapshotFamily = "segment" | "loader" | "handles";
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* The stored form of the `handles` family: the handle arrays of a
|
|
650
|
+
* prerender-served capture that hold a loader-owned push, in the doc record's
|
|
651
|
+
* format (CachedEntryData `handles` + `handleOwners`). A Prerender + ppr
|
|
652
|
+
* route's handler layer comes from the prerender store, whose build-time
|
|
653
|
+
* entry has no loader push, so the entry keeps these for the HIT to restore
|
|
654
|
+
* as a doc record's (issue #1057, yieldFromStore in cache-lookup.ts). An
|
|
655
|
+
* entry written before it (v0.21) has none and serves as it did.
|
|
656
|
+
*/
|
|
657
|
+
export type ShellSnapshotHandlesValue = Pick<
|
|
658
|
+
CachedEntryData,
|
|
659
|
+
"handles" | "handleOwners"
|
|
660
|
+
>;
|
|
615
661
|
|
|
616
662
|
/**
|
|
617
663
|
* The stored form of a loader-family snapshot value: the bake-lane loader's
|
|
@@ -646,17 +692,18 @@ export interface ShellSnapshotLoaderValue {
|
|
|
646
692
|
}
|
|
647
693
|
|
|
648
694
|
/**
|
|
649
|
-
* One snapshot record: the doc segment record
|
|
650
|
-
* `value` carries it in its
|
|
651
|
-
* through a JSON-serializing store
|
|
652
|
-
* ShellCacheEntry:
|
|
695
|
+
* One snapshot record: the doc segment record, a bake-lane loader pin, or a
|
|
696
|
+
* prerender-served entry's handle record. `value` carries it in its
|
|
697
|
+
* stored/serialized shape so it round-trips through a JSON-serializing store
|
|
698
|
+
* (KV, CF, Vercel) with the rest of the ShellCacheEntry:
|
|
653
699
|
* - `segment` -> {@link CachedEntryData} (already JSON-able)
|
|
654
700
|
* - `loader` -> {@link ShellSnapshotLoaderValue}
|
|
701
|
+
* - `handles` -> {@link ShellSnapshotHandlesValue}
|
|
655
702
|
*/
|
|
656
703
|
export interface ShellSnapshotRecord {
|
|
657
704
|
family: ShellSnapshotFamily;
|
|
658
705
|
key: string;
|
|
659
|
-
value: CachedEntryData | ShellSnapshotLoaderValue;
|
|
706
|
+
value: CachedEntryData | ShellSnapshotLoaderValue | ShellSnapshotHandlesValue;
|
|
660
707
|
}
|
|
661
708
|
|
|
662
709
|
/**
|
|
@@ -671,6 +718,17 @@ export interface CacheItemOptions {
|
|
|
671
718
|
swr?: number;
|
|
672
719
|
/** Cache tags for invalidation */
|
|
673
720
|
tags?: string[];
|
|
721
|
+
/**
|
|
722
|
+
* Ms since epoch when the execution that produced the value started (#1068).
|
|
723
|
+
* A store stamps a tagged entry with it instead of its write time. Later
|
|
724
|
+
* requests then reject an entry an invalidation landed under in
|
|
725
|
+
* CFCacheStore with KV markers; MemorySegmentCacheStore refuses the write.
|
|
726
|
+
* Not rejected: VercelCacheStore (a read compares the stamp with the
|
|
727
|
+
* invalidating request's mask only) and CFCacheStore L1 hits in purge mode
|
|
728
|
+
* or without KV. Absent or not positive: write time. A custom store may
|
|
729
|
+
* ignore it.
|
|
730
|
+
*/
|
|
731
|
+
startedAt?: number;
|
|
674
732
|
}
|
|
675
733
|
|
|
676
734
|
/**
|
|
@@ -718,7 +776,12 @@ export interface CachedEntryData {
|
|
|
718
776
|
expiresAt: number;
|
|
719
777
|
/** Cache tags for invalidation */
|
|
720
778
|
tags?: string[];
|
|
721
|
-
/**
|
|
779
|
+
/**
|
|
780
|
+
* Timestamp (ms since epoch) the entry's tags are valid as of, for
|
|
781
|
+
* distributed invalidation. The writer sets it to the start of the render
|
|
782
|
+
* that produced the record; a store stamps write time when it is absent
|
|
783
|
+
* (#1068). Which reads reject on it: see `CacheItemOptions.startedAt`.
|
|
784
|
+
*/
|
|
722
785
|
taggedAt?: number;
|
|
723
786
|
}
|
|
724
787
|
|
|
@@ -761,10 +824,13 @@ export type SegmentHandleData = Record<string, unknown[]>;
|
|
|
761
824
|
* index (null: not owned). Only a PPR shell capture writes owners: the
|
|
762
825
|
* settled pushes of the loader bodies it ran (an `ssr: false` loader's own,
|
|
763
826
|
* the loaders it awaits, and its own cache() replays), which the record keeps
|
|
764
|
-
* because the prelude rendered them. A
|
|
765
|
-
* owner's value
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
827
|
+
* because the prelude rendered them. A restore of the record
|
|
828
|
+
* (handle-snapshot.ts restoreHandles) keeps an owner's value where the
|
|
829
|
+
* request serves that loader from the pin stored with the record
|
|
830
|
+
* (HandleStore.pushRestored: it stands against a run of the loader), on a
|
|
831
|
+
* document HIT and on a navigation replay alike. Every other owner's value
|
|
832
|
+
* is a placeholder (pushPlaceholder): a live-lane loader's, and every
|
|
833
|
+
* owner's where the record has no pin for it. The loader's run, or its own
|
|
834
|
+
* cache() entry, replaces it.
|
|
769
835
|
*/
|
|
770
836
|
export type HandleOwners = Record<string, Record<string, (string | null)[]>>;
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* 3. The platform guardrails: the 2 MB per-item ceiling (oversized writes
|
|
24
24
|
* silently no-op on Vercel, so we skip + report), the per-item tag cap, and
|
|
25
25
|
* cross-deploy non-reconciliation (TTL/tag updates are not reconciled across
|
|
26
|
-
* deployments -
|
|
26
|
+
* deployments - every key carries the serving router's cache version, see
|
|
27
|
+
* toStoreKey).
|
|
27
28
|
*
|
|
28
29
|
* Dependency stance: this module imports NOTHING from `@vercel/functions`. The
|
|
29
30
|
* runtime cache handle (and `waitUntil`) are injected through the constructor and
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
*/
|
|
35
36
|
|
|
36
37
|
import type {
|
|
38
|
+
CacheStoreScope,
|
|
37
39
|
SegmentCacheStore,
|
|
38
40
|
CachedEntryData,
|
|
39
41
|
CacheDefaults,
|
|
@@ -52,6 +54,10 @@ import {
|
|
|
52
54
|
_getRequestContext,
|
|
53
55
|
type RequestContext,
|
|
54
56
|
} from "../../server/request-context.js";
|
|
57
|
+
import {
|
|
58
|
+
versionKeyPrefix,
|
|
59
|
+
type KeyVersion,
|
|
60
|
+
} from "../../server/build-version-table.js";
|
|
55
61
|
import { isPerClientSignalHeader } from "../../browser/cookie-name.js";
|
|
56
62
|
import {
|
|
57
63
|
resolveTtl,
|
|
@@ -101,6 +107,7 @@ import {
|
|
|
101
107
|
type MarkerFetch,
|
|
102
108
|
type MarkerMemoOutcome,
|
|
103
109
|
} from "../isolate-tag-memo.js";
|
|
110
|
+
import { responseStartedAt, taggedStamp } from "../invalidation-order.js";
|
|
104
111
|
|
|
105
112
|
/**
|
|
106
113
|
* Minimal structural shape of the Vercel Runtime Cache returned by `getCache()`
|
|
@@ -170,6 +177,19 @@ const REVALIDATION_LOCK_MS = 30_000;
|
|
|
170
177
|
* the suffix; `rg:` namespaces every Rango entry. `h` is the PPR shell tier. */
|
|
171
178
|
type CacheFamily = "s" | "i" | "r" | "h" | "tm";
|
|
172
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Which of the serving router's versions each family's keys carry
|
|
182
|
+
* (versionKeyPrefix). A tag marker carries none: an invalidation applies to
|
|
183
|
+
* every version, including one a rollback brings back.
|
|
184
|
+
*/
|
|
185
|
+
const FAMILY_VERSION: Record<CacheFamily, KeyVersion> = {
|
|
186
|
+
s: "data",
|
|
187
|
+
i: "data",
|
|
188
|
+
r: "document",
|
|
189
|
+
h: "document",
|
|
190
|
+
tm: null,
|
|
191
|
+
};
|
|
192
|
+
|
|
173
193
|
/**
|
|
174
194
|
* TTL for tag-invalidation marker entries ("tm" family), written by
|
|
175
195
|
* invalidateTags for isTagsInvalidatedSince (the build-shell read-through,
|
|
@@ -177,7 +197,7 @@ type CacheFamily = "s" | "i" | "r" | "h" | "tm";
|
|
|
177
197
|
* The platform's expireTag() DELETES tagged entries (no queryable
|
|
178
198
|
* history), so the markers are rango's own record of "tag X was invalidated
|
|
179
199
|
* at T". One year: runtime tagged-shell retention is capped to this lifetime,
|
|
180
|
-
* while
|
|
200
|
+
* while a build shell is replaced by the next build's. An expired marker
|
|
181
201
|
* therefore cannot resurrect a runtime shell that outlived its invalidation.
|
|
182
202
|
*/
|
|
183
203
|
const TAG_MARKER_TTL_SECONDS = 365 * 24 * 60 * 60;
|
|
@@ -299,14 +319,20 @@ export type VercelCacheDebug =
|
|
|
299
319
|
export interface VercelCacheStoreOptions<TEnv = unknown> {
|
|
300
320
|
/**
|
|
301
321
|
* The Vercel Runtime Cache handle - `getCache()` from `@vercel/functions`.
|
|
302
|
-
* Required.
|
|
303
|
-
* entries across deployments, since Vercel does not reconcile TTL/tags between
|
|
304
|
-
* deploys:
|
|
322
|
+
* Required.
|
|
305
323
|
*
|
|
306
324
|
* ```ts
|
|
307
325
|
* import { getCache } from "@vercel/functions";
|
|
308
|
-
* new VercelCacheStore({ cache: getCache(
|
|
326
|
+
* new VercelCacheStore({ cache: getCache() });
|
|
309
327
|
* ```
|
|
328
|
+
*
|
|
329
|
+
* Vercel does not reconcile entries between deployments; the store does it
|
|
330
|
+
* through its keys, which carry the serving router's cache versions (see
|
|
331
|
+
* `version`). So a deploy that changes a router's code stops reading that
|
|
332
|
+
* router's old entries, and a deploy that does not keeps them. A namespace
|
|
333
|
+
* that changes per deployment (`getCache({ namespace: VERCEL_DEPLOYMENT_ID })`)
|
|
334
|
+
* still works and clears the cache on every deploy, which is what you want
|
|
335
|
+
* only if you need that.
|
|
310
336
|
*/
|
|
311
337
|
cache: VercelRuntimeCache;
|
|
312
338
|
|
|
@@ -336,9 +362,15 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
|
|
|
336
362
|
) => string | Promise<string>;
|
|
337
363
|
|
|
338
364
|
/**
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
365
|
+
* Version override, folded into every versioned key as `v/{version}/...`.
|
|
366
|
+
* When set, this exact value is used for cached data and stored HTML alike;
|
|
367
|
+
* changing it makes everything this store wrote before unreachable.
|
|
368
|
+
*
|
|
369
|
+
* Leave it unset for the default: the store keys with the versions of the
|
|
370
|
+
* router serving the request, as CFCacheStore does. Segments and `"use
|
|
371
|
+
* cache"` items use the router's data version; responses and PPR shells use
|
|
372
|
+
* its document version (see `createRouter({ version })`). Tag markers are
|
|
373
|
+
* never versioned, so an invalidation applies to every version.
|
|
342
374
|
*/
|
|
343
375
|
version?: string;
|
|
344
376
|
|
|
@@ -424,9 +456,15 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
424
456
|
return typeof value === "object" && value !== null;
|
|
425
457
|
}
|
|
426
458
|
|
|
427
|
-
/**
|
|
428
|
-
|
|
429
|
-
|
|
459
|
+
/**
|
|
460
|
+
* The envelope's `ta` stamp, on tagged entries only: the producing execution's
|
|
461
|
+
* start when the writer passed it, else write time (#1068). Only the request
|
|
462
|
+
* mask reads it (expireTag keeps no queryable history), so it decides whether
|
|
463
|
+
* the invalidating request's own late write is masked.
|
|
464
|
+
*/
|
|
465
|
+
function tagStamp(tags: string[], startedAt?: number): { ta?: number } {
|
|
466
|
+
const ta = taggedStamp(tags, startedAt);
|
|
467
|
+
return ta === undefined ? {} : { ta };
|
|
430
468
|
}
|
|
431
469
|
|
|
432
470
|
/**
|
|
@@ -447,8 +485,8 @@ function tagStamp(tags: string[]): { ta?: number } {
|
|
|
447
485
|
* that propagates expireTag rejections does get the strict guarantee, which is
|
|
448
486
|
* what the unit suite exercises.)
|
|
449
487
|
*
|
|
450
|
-
* Key hashing / family isolation. The store namespaces its
|
|
451
|
-
* `rg:{s|i|r}:{key}` so segment, `"use cache"`, and
|
|
488
|
+
* Key hashing / family isolation. The store namespaces its value tiers as
|
|
489
|
+
* `rg:{s|i|r|h}:{key}` so segment, `"use cache"`, response and shell entries occupy
|
|
452
490
|
* disjoint keyspaces. That separation is only as strong as the cache's
|
|
453
491
|
* `keyHashFunction`: `getCache`'s default is djb2, which folds every key to 32
|
|
454
492
|
* bits (8 hex chars), so at a large live-key count a collision can let one
|
|
@@ -464,11 +502,9 @@ function tagStamp(tags: string[]): { ta?: number } {
|
|
|
464
502
|
*
|
|
465
503
|
* // One handle per process: getCache() resolves the platform cache on every
|
|
466
504
|
* // call, and the PPR shell and tag-marker memos are kept per handle.
|
|
467
|
-
* //
|
|
468
|
-
* //
|
|
469
|
-
* const runtimeCache = getCache(
|
|
470
|
-
* namespace: process.env.VERCEL_DEPLOYMENT_ID,
|
|
471
|
-
* });
|
|
505
|
+
* // No deployment id in the namespace: the store's keys carry the serving
|
|
506
|
+
* // router's cache versions, so a deploy keeps what its code did not change.
|
|
507
|
+
* const runtimeCache = getCache();
|
|
472
508
|
*
|
|
473
509
|
* export const router = createRouter({
|
|
474
510
|
* cache: () => ({
|
|
@@ -484,6 +520,12 @@ function tagStamp(tags: string[]): { ta?: number } {
|
|
|
484
520
|
export class VercelCacheStore<
|
|
485
521
|
TEnv = unknown,
|
|
486
522
|
> implements SegmentCacheStore<TEnv> {
|
|
523
|
+
/**
|
|
524
|
+
* SegmentCacheStore.scope: the Runtime Cache is per region. A
|
|
525
|
+
* router.prerender() warm fills the region it runs in, which is all traffic
|
|
526
|
+
* only when the project's functions run in one region.
|
|
527
|
+
*/
|
|
528
|
+
readonly scope: CacheStoreScope = "regional";
|
|
487
529
|
readonly supportsPassiveShellReads: true = true;
|
|
488
530
|
readonly defaults?: CacheDefaults;
|
|
489
531
|
readonly keyGenerator?: (
|
|
@@ -493,7 +535,8 @@ export class VercelCacheStore<
|
|
|
493
535
|
|
|
494
536
|
private readonly cache: VercelRuntimeCache;
|
|
495
537
|
private readonly waitUntil?: (promise: Promise<unknown>) => void;
|
|
496
|
-
|
|
538
|
+
/** The `version` option: used for every versioned family when set. */
|
|
539
|
+
private readonly explicitVersion?: string;
|
|
497
540
|
private readonly maxItemBytes: number;
|
|
498
541
|
private readonly memo: ResolvedShellMemoOptions;
|
|
499
542
|
private readonly handleMemos: VercelHandleMemos;
|
|
@@ -518,7 +561,7 @@ export class VercelCacheStore<
|
|
|
518
561
|
this.waitUntil = options.waitUntil;
|
|
519
562
|
this.defaults = options.defaults;
|
|
520
563
|
this.keyGenerator = options.keyGenerator;
|
|
521
|
-
this.
|
|
564
|
+
this.explicitVersion = options.version;
|
|
522
565
|
this.maxItemBytes = options.maxItemBytes ?? VERCEL_MAX_ITEM_BYTES;
|
|
523
566
|
this.maxShellEntryBytes = Math.floor((this.maxItemBytes * 3) / 4);
|
|
524
567
|
this.memo = resolveShellMemoOptions(options.memo, {
|
|
@@ -620,7 +663,7 @@ export class VercelCacheStore<
|
|
|
620
663
|
d,
|
|
621
664
|
s: staleAt,
|
|
622
665
|
e: expiresAt,
|
|
623
|
-
...tagStamp(safeTags),
|
|
666
|
+
...tagStamp(safeTags, data.taggedAt),
|
|
624
667
|
};
|
|
625
668
|
await this.write(
|
|
626
669
|
this.toStoreKey(key, "s"),
|
|
@@ -768,7 +811,7 @@ export class VercelCacheStore<
|
|
|
768
811
|
s: staleAt,
|
|
769
812
|
e: expiresAt,
|
|
770
813
|
t: safeTags.length > 0 ? safeTags : undefined,
|
|
771
|
-
...tagStamp(safeTags),
|
|
814
|
+
...tagStamp(safeTags, responseStartedAt(response)),
|
|
772
815
|
};
|
|
773
816
|
await this.write(
|
|
774
817
|
this.toStoreKey(key, "r"),
|
|
@@ -874,7 +917,7 @@ export class VercelCacheStore<
|
|
|
874
917
|
s: staleAt,
|
|
875
918
|
e: expiresAt,
|
|
876
919
|
t: safeTags.length > 0 ? safeTags : undefined,
|
|
877
|
-
...tagStamp(safeTags),
|
|
920
|
+
...tagStamp(safeTags, options?.startedAt),
|
|
878
921
|
};
|
|
879
922
|
await this.write(
|
|
880
923
|
this.toStoreKey(key, "i"),
|
|
@@ -1469,8 +1512,7 @@ export class VercelCacheStore<
|
|
|
1469
1512
|
// cross-family one reads as corrupt. See the class doc for passing a wider
|
|
1470
1513
|
// hash (sha256) when many keys are live.
|
|
1471
1514
|
private toStoreKey(key: string, family: CacheFamily): string {
|
|
1472
|
-
|
|
1473
|
-
return `${versionPrefix}rg:${family}:${key}`;
|
|
1515
|
+
return `${versionKeyPrefix(this.explicitVersion, FAMILY_VERSION[family])}rg:${family}:${key}`;
|
|
1474
1516
|
}
|
|
1475
1517
|
|
|
1476
1518
|
private async write(
|
|
@@ -17,6 +17,8 @@ export interface ClientUrlNavigationIntent {
|
|
|
17
17
|
/** Absolute destination pathname (mount included) and search ("?..." or ""). */
|
|
18
18
|
readonly pathname: string;
|
|
19
19
|
readonly search: string;
|
|
20
|
+
/** `history.state` the navigation will push (OptimisticLocation.state). */
|
|
21
|
+
readonly state: unknown;
|
|
20
22
|
/**
|
|
21
23
|
* transition({ when }) gated this navigation off at the swap: the
|
|
22
24
|
* destination presents urgently instead of in a transition lane.
|
|
@@ -37,6 +39,8 @@ export interface ClientUrlDestination {
|
|
|
37
39
|
* params minus these are the mount's (include prefix) params.
|
|
38
40
|
*/
|
|
39
41
|
readonly originLocalParamNames: readonly string[];
|
|
42
|
+
/** `history.state` the navigation will push (ClientUrlNavigationIntent.state). */
|
|
43
|
+
readonly state: unknown;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
interface ActiveClientUrlGroup {
|
|
@@ -124,6 +128,8 @@ export function registerClientUrlGroup(
|
|
|
124
128
|
export function beginClientUrlNavigation(
|
|
125
129
|
targetUrl: URL,
|
|
126
130
|
signal: AbortSignal,
|
|
131
|
+
/** Builds the entry state; called only when the group presents the target. */
|
|
132
|
+
buildState?: () => unknown,
|
|
127
133
|
decideTransition?: (destination: ClientUrlDestination) => boolean,
|
|
128
134
|
): ClientUrlNavigationPresentation | null {
|
|
129
135
|
const group = activeGroup;
|
|
@@ -149,6 +155,8 @@ export function beginClientUrlNavigation(
|
|
|
149
155
|
: undefined;
|
|
150
156
|
if (canonicalName && activeInterceptTargets.has(canonicalName)) return null;
|
|
151
157
|
|
|
158
|
+
const state = buildState?.();
|
|
159
|
+
|
|
152
160
|
// A same-route intent never swaps (ClientUrlsRoot), so transition({ when })
|
|
153
161
|
// decides at the canonical commit; a cross-route one decides here, at the
|
|
154
162
|
// navigation's first presentation.
|
|
@@ -163,6 +171,7 @@ export function beginClientUrlNavigation(
|
|
|
163
171
|
routeName: canonicalName,
|
|
164
172
|
when: typeof when === "function" ? when : undefined,
|
|
165
173
|
originLocalParamNames: Object.keys(originMatch?.params ?? {}),
|
|
174
|
+
state,
|
|
166
175
|
});
|
|
167
176
|
}
|
|
168
177
|
|
|
@@ -171,6 +180,7 @@ export function beginClientUrlNavigation(
|
|
|
171
180
|
params: match.params,
|
|
172
181
|
pathname: targetUrl.pathname,
|
|
173
182
|
search: targetUrl.search,
|
|
183
|
+
state,
|
|
174
184
|
transitionGatedOff,
|
|
175
185
|
};
|
|
176
186
|
group.intent = intent;
|
|
@@ -6,16 +6,22 @@ import { createContext, type Context } from "react";
|
|
|
6
6
|
* Route identity of an optimistically rendered clientUrls() destination:
|
|
7
7
|
* the values the local trie match produced for the URL the user navigated
|
|
8
8
|
* to. Provided by ClientUrlsRoot around the optimistic branch ONLY, so
|
|
9
|
-
* useParams / usePathname / useSearchParams inside that
|
|
10
|
-
* route being rendered while the same hooks in chrome
|
|
11
|
-
* committed location until the canonical response commits
|
|
12
|
-
* which case the branch — and these values — are discarded).
|
|
9
|
+
* useParams / usePathname / useSearchParams / useLocationState inside that
|
|
10
|
+
* branch describe the route being rendered while the same hooks in chrome
|
|
11
|
+
* outside it keep the committed location until the canonical response commits
|
|
12
|
+
* (or redirects, in which case the branch — and these values — are discarded).
|
|
13
13
|
*/
|
|
14
14
|
export interface OptimisticLocation {
|
|
15
15
|
readonly params: Readonly<Record<string, string>>;
|
|
16
16
|
readonly pathname: string;
|
|
17
17
|
/** Search string including the leading "?" (or ""). */
|
|
18
18
|
readonly search: string;
|
|
19
|
+
/**
|
|
20
|
+
* `history.state` the navigation will push: its `state` option, without
|
|
21
|
+
* what the server adds (that arrives with the canonical commit). History
|
|
22
|
+
* still holds the entry being left, so useLocationState reads this instead.
|
|
23
|
+
*/
|
|
24
|
+
readonly state: unknown;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export const OptimisticLocationContext: Context<OptimisticLocation | null> =
|
package/src/client.tsx
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import {
|
|
4
4
|
Component,
|
|
5
5
|
createElement,
|
|
6
|
+
isValidElement,
|
|
6
7
|
useContext,
|
|
7
8
|
useMemo,
|
|
8
9
|
Suspense,
|
|
10
|
+
type ComponentProps,
|
|
9
11
|
type ReactNode,
|
|
10
12
|
} from "react";
|
|
11
13
|
// Not `export * as Html`: plugin-rsc's "use client" proxy transform only
|
|
@@ -19,12 +21,15 @@ import {
|
|
|
19
21
|
type ResolvedSegment,
|
|
20
22
|
} from "./types";
|
|
21
23
|
import {
|
|
24
|
+
AuditedLoaderBoundary,
|
|
25
|
+
AuditedRouteContent,
|
|
22
26
|
RouteContentWrapper,
|
|
23
27
|
LoaderBoundary,
|
|
24
28
|
} from "./route-content-wrapper.js";
|
|
25
|
-
import { OutletProvider } from "./outlet-provider.js";
|
|
29
|
+
import { AuditedOutletProvider, OutletProvider } from "./outlet-provider.js";
|
|
30
|
+
import { INTERNAL_RANGO_SUSPENSE_AUDIT } from "./internal-suspense-audit.js";
|
|
26
31
|
import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
27
|
-
import {
|
|
32
|
+
import { getBoundaryContent } from "./segment-boundary-content.js";
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
35
|
* Render the content for a named parallel/intercept slot segment.
|
|
@@ -39,7 +44,7 @@ function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
|
39
44
|
const content: ReactNode =
|
|
40
45
|
segment.loading || segment.component instanceof Promise ? (
|
|
41
46
|
<RouteContentWrapper
|
|
42
|
-
content={
|
|
47
|
+
content={getBoundaryContent(segment.component)}
|
|
43
48
|
fallback={segment.loading}
|
|
44
49
|
segmentId={segment.id}
|
|
45
50
|
/>
|
|
@@ -83,6 +88,10 @@ function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
|
83
88
|
result = content;
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
if (process.env.NODE_ENV !== "production" && INTERNAL_RANGO_SUSPENSE_AUDIT) {
|
|
92
|
+
result = auditedSlot(result);
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
if (segment.mountPath) {
|
|
87
96
|
return (
|
|
88
97
|
<MountContextProvider value={segment.mountPath}>
|
|
@@ -94,6 +103,34 @@ function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
|
94
103
|
return result;
|
|
95
104
|
}
|
|
96
105
|
|
|
106
|
+
// Dev only (suspense-audit.ts): the boundaries renderSlotContent built, as
|
|
107
|
+
// their audited components with the same props. After the fact, not a choice
|
|
108
|
+
// per JSX element: a build must fold to the product elements alone (see
|
|
109
|
+
// AuditedRouteContent, route-content-wrapper.tsx).
|
|
110
|
+
function auditedSlot(node: ReactNode): ReactNode {
|
|
111
|
+
if (!isValidElement(node)) return node;
|
|
112
|
+
const props = node.props as Record<string, any>;
|
|
113
|
+
if (node.type === OutletProvider) {
|
|
114
|
+
return createElement(AuditedOutletProvider, {
|
|
115
|
+
...(props as ComponentProps<typeof OutletProvider>),
|
|
116
|
+
content: auditedSlot(props.content),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (node.type === LoaderBoundary) {
|
|
120
|
+
return createElement(AuditedLoaderBoundary, {
|
|
121
|
+
...(props as ComponentProps<typeof LoaderBoundary>),
|
|
122
|
+
children: auditedSlot(props.children),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (node.type === RouteContentWrapper) {
|
|
126
|
+
return createElement(
|
|
127
|
+
AuditedRouteContent,
|
|
128
|
+
props as ComponentProps<typeof RouteContentWrapper>,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
|
|
97
134
|
function useSlotSegment(
|
|
98
135
|
context: OutletContextValue | null,
|
|
99
136
|
name: `@${string}` | undefined,
|
package/src/errors.ts
CHANGED
|
@@ -356,6 +356,18 @@ export function invariant(condition: any, message: string): asserts condition {
|
|
|
356
356
|
* @param error - Error to sanitize
|
|
357
357
|
* @returns Response suitable for sending to client
|
|
358
358
|
*/
|
|
359
|
+
/**
|
|
360
|
+
* Vite replaces import.meta.env.DEV at compile time. The fallback covers
|
|
361
|
+
* non-Vite environments (plain Node, test runners without Vite transforms).
|
|
362
|
+
* SECURITY: fail closed — default to production when the environment is ambiguous.
|
|
363
|
+
*/
|
|
364
|
+
export function isDevEnvironment(): boolean {
|
|
365
|
+
return (
|
|
366
|
+
(import.meta as any).env?.DEV ??
|
|
367
|
+
globalThis.process?.env?.NODE_ENV === "development"
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
359
371
|
export function sanitizeError(error: unknown): Response {
|
|
360
372
|
// CRITICAL: Consume stack trace to prevent memory leaks
|
|
361
373
|
if (error && typeof error === "object" && "stack" in error) {
|
|
@@ -367,12 +379,7 @@ export function sanitizeError(error: unknown): Response {
|
|
|
367
379
|
return error;
|
|
368
380
|
}
|
|
369
381
|
|
|
370
|
-
|
|
371
|
-
// non-Vite environments (plain Node, test runners without Vite transforms).
|
|
372
|
-
// SECURITY: fail closed — default to production when the environment is ambiguous.
|
|
373
|
-
const isDev =
|
|
374
|
-
(import.meta as any).env?.DEV ??
|
|
375
|
-
globalThis.process?.env?.NODE_ENV === "development";
|
|
382
|
+
const isDev = isDevEnvironment();
|
|
376
383
|
|
|
377
384
|
if (isDev) {
|
|
378
385
|
// Development: Send full error details for debugging
|