@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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/AGENTS.md +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Prefetch Cache
|
|
3
3
|
*
|
|
4
|
-
* In-memory cache storing
|
|
5
|
-
* on subsequent navigation.
|
|
4
|
+
* In-memory cache storing eagerly-decoded prefetch payloads for instant,
|
|
5
|
+
* already-warm cache hits on subsequent navigation. A prefetch fetches the
|
|
6
|
+
* RSC partial AND decodes it (createFromFetch) up front — decoding the Flight
|
|
7
|
+
* stream resolves the route's client references, so the route's JS chunks are
|
|
8
|
+
* imported during prefetch rather than on click. The decoded payload is reused
|
|
9
|
+
* verbatim by navigation, so a prefetched click loads no new code. Two key
|
|
10
|
+
* scopes are in play:
|
|
6
11
|
* - Wildcard (default): built by `buildPrefetchKey(rangoState, target)` —
|
|
7
12
|
* shape `rangoState\0/target?...`. Shared across all source pages and
|
|
8
13
|
* invalidated automatically when Rango state bumps (deploy or
|
|
@@ -17,8 +22,8 @@
|
|
|
17
22
|
* from other pages.
|
|
18
23
|
*
|
|
19
24
|
* Also tracks in-flight prefetch promises. Each promise resolves to the
|
|
20
|
-
*
|
|
21
|
-
* still-downloading prefetch without
|
|
25
|
+
* decoded prefetch entry (or null), letting navigation adopt a
|
|
26
|
+
* still-downloading prefetch without issuing a duplicate request. A
|
|
22
27
|
* single promise can be registered under multiple alias keys (see
|
|
23
28
|
* `setInflightPromiseWithAliases`) so same-source navigations adopt via
|
|
24
29
|
* their source key while cross-source ones fall through to the wildcard
|
|
@@ -26,23 +31,70 @@
|
|
|
26
31
|
*
|
|
27
32
|
* Replaces the previous browser HTTP cache approach which was unreliable
|
|
28
33
|
* due to response draining race conditions and browser inconsistencies.
|
|
34
|
+
*
|
|
35
|
+
* State here lives in module-level singletons (cache, inflight, generation,
|
|
36
|
+
* cacheTTL, etc.) rather than a per-instance factory. This is correct because
|
|
37
|
+
* exactly one router is live per document — an SPA navigation crossing a
|
|
38
|
+
* host-router boundary forces a full document reload — so the singletons are
|
|
39
|
+
* effectively per-document. Unit tests reset them via clearPrefetchCache().
|
|
29
40
|
*/
|
|
30
41
|
|
|
31
42
|
import { abortAllPrefetches } from "./queue.js";
|
|
32
43
|
import { invalidateRangoState } from "../rango-state.js";
|
|
44
|
+
import type { RscPayload } from "../types.js";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A prefetch that has been fetched AND eagerly decoded. Storing the decoded
|
|
48
|
+
* payload (not the raw Response) is what makes a prefetched navigation "warm":
|
|
49
|
+
* decoding the Flight stream during prefetch pulls the route's client chunks,
|
|
50
|
+
* so the click reuses ready elements and loads no new JS.
|
|
51
|
+
*/
|
|
52
|
+
export interface DecodedPrefetch {
|
|
53
|
+
/** The eagerly-decoded RSC payload. Reused verbatim by navigation. */
|
|
54
|
+
payload: Promise<RscPayload>;
|
|
55
|
+
/**
|
|
56
|
+
* Resolves when the underlying RSC stream finishes draining. Navigation
|
|
57
|
+
* forwards this as its streamComplete so scroll/revalidation gating is
|
|
58
|
+
* unchanged from the fresh-fetch path.
|
|
59
|
+
*/
|
|
60
|
+
streamComplete: Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Prefetch scope as tagged by the server via `X-RSC-Prefetch-Scope`.
|
|
63
|
+
* `"source"` means the response is source-page-sensitive and must not be
|
|
64
|
+
* reused by a navigation from a different page — navigation enforces this
|
|
65
|
+
* when it adopted an inflight entry through the wildcard key.
|
|
66
|
+
*/
|
|
67
|
+
scope: "source" | "wildcard";
|
|
68
|
+
/**
|
|
69
|
+
* Synchronously-readable flag, flipped to true when `streamComplete` resolves
|
|
70
|
+
* (the entire RSC stream has drained). Navigation reads this at click time to
|
|
71
|
+
* tell a FULLY warmed prefetch (payload commits without suspending → safe to
|
|
72
|
+
* commit in a startTransition, no fallback flash) from a partially-warmed one
|
|
73
|
+
* (still streaming → stream its fallbacks like a cold load). Starts false.
|
|
74
|
+
*/
|
|
75
|
+
complete: boolean;
|
|
76
|
+
}
|
|
33
77
|
|
|
34
|
-
// Default TTL: 5 minutes. Overridden by initPrefetchCache() with
|
|
35
|
-
// the server-configured prefetchCacheTTL from router options.
|
|
36
|
-
// 0 disables the in-memory cache entirely.
|
|
37
78
|
let cacheTTL = 300_000;
|
|
38
79
|
|
|
80
|
+
// Max stored entries before FIFO eviction. Mirrors DEFAULT_PREFETCH_CACHE_SIZE
|
|
81
|
+
// (router/prefetch-limits.ts); kept as a local literal so the client bundle
|
|
82
|
+
// doesn't pull in router-layer code, matching the cacheTTL default above.
|
|
83
|
+
// Overridden at startup by initPrefetchCache from server metadata.
|
|
84
|
+
let maxPrefetchCacheSize = 100;
|
|
85
|
+
|
|
39
86
|
/**
|
|
40
|
-
* Initialize the prefetch cache with the configured TTL.
|
|
41
|
-
* Called once at app startup with the
|
|
42
|
-
*
|
|
87
|
+
* Initialize the prefetch cache with the configured TTL and max size.
|
|
88
|
+
* Called once at app startup with the values from server metadata. Each
|
|
89
|
+
* argument is applied only when provided, so a caller can set just the TTL.
|
|
90
|
+
* A TTL of 0 disables the in-memory cache and all prefetching. A size below 1
|
|
91
|
+
* is ignored (the default is kept) — disabling prefetch is the TTL's job.
|
|
43
92
|
*/
|
|
44
|
-
export function initPrefetchCache(ttlMs
|
|
45
|
-
cacheTTL = ttlMs;
|
|
93
|
+
export function initPrefetchCache(ttlMs?: number, maxSize?: number): void {
|
|
94
|
+
if (ttlMs !== undefined) cacheTTL = ttlMs;
|
|
95
|
+
if (maxSize !== undefined && Number.isFinite(maxSize) && maxSize >= 1) {
|
|
96
|
+
maxPrefetchCacheSize = Math.floor(maxSize);
|
|
97
|
+
}
|
|
46
98
|
}
|
|
47
99
|
|
|
48
100
|
/**
|
|
@@ -52,34 +104,31 @@ export function initPrefetchCache(ttlMs: number): void {
|
|
|
52
104
|
export function isPrefetchCacheDisabled(): boolean {
|
|
53
105
|
return cacheTTL <= 0;
|
|
54
106
|
}
|
|
55
|
-
const MAX_PREFETCH_CACHE_SIZE = 50;
|
|
56
107
|
|
|
57
108
|
interface PrefetchCacheEntry {
|
|
58
|
-
|
|
109
|
+
entry: DecodedPrefetch;
|
|
59
110
|
timestamp: number;
|
|
60
111
|
}
|
|
61
112
|
|
|
62
113
|
const cache = new Map<string, PrefetchCacheEntry>();
|
|
63
114
|
const inflight = new Set<string>();
|
|
64
115
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
116
|
+
// Watermark: the smallest entry timestamp currently in `cache`, i.e. a lower
|
|
117
|
+
// bound on when the oldest entry expires (timestamp + cacheTTL). storePrefetch's
|
|
118
|
+
// eager expiry sweep is skipped while `now - earliestTimestamp <= cacheTTL` — the
|
|
119
|
+
// oldest entry cannot be expired yet, so nothing would be evicted. Deletions
|
|
120
|
+
// (consume/has/remove) may leave it stale-LOW, which only forces a harmless extra
|
|
121
|
+
// sweep that recomputes it exactly; it is never stale-high, so a skipped sweep
|
|
122
|
+
// can never retain an expired entry. consume/has still TTL-check lazily, so a
|
|
123
|
+
// skipped sweep never serves a stale entry. Infinity when the cache is empty.
|
|
124
|
+
let earliestTimestamp = Infinity;
|
|
125
|
+
|
|
126
|
+
const inflightPromises = new Map<string, Promise<DecodedPrefetch | null>>();
|
|
71
127
|
|
|
72
|
-
/**
|
|
73
|
-
* Alias map for in-flight promises registered under multiple keys (see
|
|
74
|
-
* dual inflight in prefetch/fetch.ts). Records each key's sibling set so
|
|
75
|
-
* that consuming or clearing any one key atomically removes every alias —
|
|
76
|
-
* guaranteeing a single consumer for the shared Response stream.
|
|
77
|
-
*/
|
|
78
128
|
const inflightAliases = new Map<string, string[]>();
|
|
79
129
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// response (the data may be stale due to a server action invalidation).
|
|
130
|
+
const adoptedKeys = new Set<string>();
|
|
131
|
+
|
|
83
132
|
let generation = 0;
|
|
84
133
|
|
|
85
134
|
/**
|
|
@@ -152,14 +201,14 @@ export function hasPrefetch(key: string): boolean {
|
|
|
152
201
|
}
|
|
153
202
|
|
|
154
203
|
/**
|
|
155
|
-
* Consume a cached prefetch
|
|
156
|
-
* One-time consumption: the entry is deleted after retrieval.
|
|
204
|
+
* Consume a cached, eagerly-decoded prefetch. Returns null if not found or
|
|
205
|
+
* expired. One-time consumption: the entry is deleted after retrieval.
|
|
157
206
|
* Returns null when caching is disabled (TTL <= 0).
|
|
158
207
|
*
|
|
159
208
|
* Does NOT check in-flight prefetches — use consumeInflightPrefetch()
|
|
160
|
-
* for that (returns a Promise instead of a
|
|
209
|
+
* for that (returns a Promise instead of a resolved entry).
|
|
161
210
|
*/
|
|
162
|
-
export function consumePrefetch(key: string):
|
|
211
|
+
export function consumePrefetch(key: string): DecodedPrefetch | null {
|
|
163
212
|
if (cacheTTL <= 0) return null;
|
|
164
213
|
const entry = cache.get(key);
|
|
165
214
|
if (!entry) return null;
|
|
@@ -168,13 +217,14 @@ export function consumePrefetch(key: string): Response | null {
|
|
|
168
217
|
return null;
|
|
169
218
|
}
|
|
170
219
|
cache.delete(key);
|
|
171
|
-
return entry.
|
|
220
|
+
return entry.entry;
|
|
172
221
|
}
|
|
173
222
|
|
|
174
223
|
/**
|
|
175
224
|
* Consume an in-flight prefetch promise. Returns null if no prefetch is
|
|
176
|
-
* in-flight for this key. The returned Promise resolves to the
|
|
177
|
-
*
|
|
225
|
+
* in-flight for this key. The returned Promise resolves to the decoded
|
|
226
|
+
* prefetch entry (or null if the fetch failed/was aborted, or carried a
|
|
227
|
+
* control header the navigation must re-fetch to honor).
|
|
178
228
|
*
|
|
179
229
|
* One-time consumption: the promise entry is removed (along with any
|
|
180
230
|
* sibling aliases registered via `setInflightPromiseWithAliases`) so a
|
|
@@ -188,49 +238,90 @@ export function consumePrefetch(key: string): Response | null {
|
|
|
188
238
|
*/
|
|
189
239
|
export function consumeInflightPrefetch(
|
|
190
240
|
key: string,
|
|
191
|
-
): Promise<
|
|
241
|
+
): Promise<DecodedPrefetch | null> | null {
|
|
192
242
|
const promise = inflightPromises.get(key);
|
|
193
243
|
if (!promise) return null;
|
|
194
244
|
// Remove the promise under every alias so a second consumer cannot
|
|
195
|
-
// adopt the same stream and race on the body
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
245
|
+
// adopt the same stream and race on the body, and mark every alias as
|
|
246
|
+
// adopted so the pending `storePrefetch` (which resolves later, after this
|
|
247
|
+
// adoption) does not leave the now-owned, single-use entry in the cache map.
|
|
248
|
+
// `inflightAliases` is intentionally preserved — `clearPrefetchInflight()` in
|
|
249
|
+
// the fetch's `.finally()` still needs it to clear every inflight flag and
|
|
250
|
+
// adopted marker; deleting here would strand the sibling's flag forever.
|
|
251
|
+
forEachAlias(key, (k) => {
|
|
252
|
+
inflightPromises.delete(k);
|
|
253
|
+
adoptedKeys.add(k);
|
|
254
|
+
});
|
|
200
255
|
return promise;
|
|
201
256
|
}
|
|
202
257
|
|
|
203
258
|
/**
|
|
204
|
-
* Store
|
|
205
|
-
* The response should be a clone() of the original so the caller can
|
|
206
|
-
* still consume the body. The clone's body streams independently.
|
|
259
|
+
* Store an eagerly-decoded prefetch in the in-memory cache.
|
|
207
260
|
*
|
|
208
261
|
* Skips storage if the generation has changed since the fetch started
|
|
209
262
|
* (a server action invalidated the cache mid-flight).
|
|
210
263
|
*/
|
|
211
264
|
export function storePrefetch(
|
|
212
265
|
key: string,
|
|
213
|
-
|
|
266
|
+
entry: DecodedPrefetch,
|
|
214
267
|
fetchGeneration: number,
|
|
215
268
|
): void {
|
|
216
269
|
if (cacheTTL <= 0) return;
|
|
217
270
|
if (fetchGeneration !== generation) return;
|
|
218
271
|
|
|
219
|
-
//
|
|
272
|
+
// If a navigation already adopted this prefetch's in-flight promise, it owns
|
|
273
|
+
// the single-use entry (and has drained its handle generator). Do NOT also
|
|
274
|
+
// publish it to the cache map, or a later navigation would be served the
|
|
275
|
+
// exhausted entry and lose that route's handles. Clear the marker (under all
|
|
276
|
+
// aliases) now that the decision is made.
|
|
277
|
+
if (adoptedKeys.has(key)) {
|
|
278
|
+
forEachAlias(key, (k) => adoptedKeys.delete(k));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Evict expired entries — but only when the watermark says the oldest entry
|
|
283
|
+
// could have expired, so a burst of stores no longer walks the whole map each
|
|
284
|
+
// time. The sweep recomputes the exact watermark from the survivors.
|
|
220
285
|
const now = Date.now();
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
286
|
+
if (now - earliestTimestamp > cacheTTL) {
|
|
287
|
+
let min = Infinity;
|
|
288
|
+
for (const [k, cached] of cache) {
|
|
289
|
+
if (now - cached.timestamp > cacheTTL) {
|
|
290
|
+
cache.delete(k);
|
|
291
|
+
} else if (cached.timestamp < min) {
|
|
292
|
+
min = cached.timestamp;
|
|
293
|
+
}
|
|
224
294
|
}
|
|
295
|
+
earliestTimestamp = min;
|
|
225
296
|
}
|
|
226
297
|
|
|
227
298
|
// FIFO eviction if at capacity
|
|
228
|
-
if (cache.size >=
|
|
299
|
+
if (cache.size >= maxPrefetchCacheSize) {
|
|
229
300
|
const oldest = cache.keys().next().value;
|
|
230
301
|
if (oldest) cache.delete(oldest);
|
|
231
302
|
}
|
|
232
303
|
|
|
233
|
-
cache.set(key, {
|
|
304
|
+
cache.set(key, { entry, timestamp: now });
|
|
305
|
+
// The new entry is the newest, so it only moves the watermark when the cache
|
|
306
|
+
// was empty (Infinity); otherwise the older watermark stands.
|
|
307
|
+
if (now < earliestTimestamp) earliestTimestamp = now;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Remove a single stored prefetch entry. Used to evict an entry whose body
|
|
312
|
+
* stream stalled after headers arrived (its payload / streamComplete never
|
|
313
|
+
* settle), so future prefetches and navigation refetch instead of dedupe-ing
|
|
314
|
+
* against — and awaiting — a stuck entry.
|
|
315
|
+
*
|
|
316
|
+
* Identity-guarded: only evicts when the entry CURRENTLY stored under `key` is
|
|
317
|
+
* the exact `entry` we published. A generation check alone is insufficient —
|
|
318
|
+
* after this entry is consumed (consumePrefetch) and a fresh prefetch
|
|
319
|
+
* republishes under the SAME key in the SAME generation, a gen-only guard would
|
|
320
|
+
* delete that valid newer entry. Reference identity drops only our own stalled
|
|
321
|
+
* entry.
|
|
322
|
+
*/
|
|
323
|
+
export function removePrefetch(key: string, entry: DecodedPrefetch): void {
|
|
324
|
+
if (cache.get(key)?.entry === entry) cache.delete(key);
|
|
234
325
|
}
|
|
235
326
|
|
|
236
327
|
/**
|
|
@@ -245,12 +336,9 @@ export function markPrefetchInflight(key: string): void {
|
|
|
245
336
|
inflight.add(key);
|
|
246
337
|
}
|
|
247
338
|
|
|
248
|
-
/**
|
|
249
|
-
* Store the in-flight Promise for a prefetch so navigation can reuse it.
|
|
250
|
-
*/
|
|
251
339
|
export function setInflightPromise(
|
|
252
340
|
key: string,
|
|
253
|
-
promise: Promise<
|
|
341
|
+
promise: Promise<DecodedPrefetch | null>,
|
|
254
342
|
): void {
|
|
255
343
|
inflightPromises.set(key, promise);
|
|
256
344
|
}
|
|
@@ -263,7 +351,7 @@ export function setInflightPromise(
|
|
|
263
351
|
*/
|
|
264
352
|
export function setInflightPromiseWithAliases(
|
|
265
353
|
keys: string[],
|
|
266
|
-
promise: Promise<
|
|
354
|
+
promise: Promise<DecodedPrefetch | null>,
|
|
267
355
|
): void {
|
|
268
356
|
for (const k of keys) {
|
|
269
357
|
inflightPromises.set(k, promise);
|
|
@@ -276,39 +364,18 @@ export function clearPrefetchInflight(key: string): void {
|
|
|
276
364
|
inflight.delete(k);
|
|
277
365
|
inflightPromises.delete(k);
|
|
278
366
|
inflightAliases.delete(k);
|
|
367
|
+
adoptedKeys.delete(k);
|
|
279
368
|
});
|
|
280
369
|
}
|
|
281
370
|
|
|
282
|
-
/**
|
|
283
|
-
* Invalidate all prefetch state. Called when server actions mutate data.
|
|
284
|
-
* Clears the in-memory cache, cancels in-flight prefetches, and rotates
|
|
285
|
-
* the Rango state key so CDN-cached responses are also invalidated.
|
|
286
|
-
*
|
|
287
|
-
* Uses abortAllPrefetches (hard cancel) because in-flight responses
|
|
288
|
-
* may contain stale data after a mutation.
|
|
289
|
-
*/
|
|
290
371
|
export function clearPrefetchCache(): void {
|
|
291
372
|
generation++;
|
|
292
373
|
inflight.clear();
|
|
293
374
|
inflightPromises.clear();
|
|
294
375
|
inflightAliases.clear();
|
|
376
|
+
adoptedKeys.clear();
|
|
295
377
|
cache.clear();
|
|
378
|
+
earliestTimestamp = Infinity;
|
|
296
379
|
abortAllPrefetches();
|
|
297
380
|
invalidateRangoState();
|
|
298
381
|
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Drop all in-memory prefetch state for this tab without rotating rango-state.
|
|
302
|
-
*
|
|
303
|
-
* Use for local-only invalidations (e.g. app switch in this tab) where
|
|
304
|
-
* other tabs should NOT observe a state rotation. Unlike clearPrefetchCache,
|
|
305
|
-
* does not call invalidateRangoState, so the shared X-Rango-State token
|
|
306
|
-
* stays intact and siblings in the old app keep their prefetches.
|
|
307
|
-
*/
|
|
308
|
-
export function clearPrefetchCacheLocal(): void {
|
|
309
|
-
generation++;
|
|
310
|
-
inflight.clear();
|
|
311
|
-
inflightPromises.clear();
|
|
312
|
-
cache.clear();
|
|
313
|
-
abortAllPrefetches();
|
|
314
|
-
}
|