@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
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
*/
|
|
94
94
|
import type { ResolvedSegment } from "../../types.js";
|
|
95
95
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
96
|
-
import { getRouterContext } from "../router-context.js";
|
|
97
|
-
import {
|
|
96
|
+
import { getRouterContext, type RouterContext } from "../router-context.js";
|
|
97
|
+
import { observeEvent } from "../instrument.js";
|
|
98
98
|
import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
|
|
99
99
|
import { treeHasStreaming } from "./segment-resolution.js";
|
|
100
100
|
import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
|
|
@@ -103,6 +103,7 @@ import {
|
|
|
103
103
|
getRequestContext,
|
|
104
104
|
_getRequestContext,
|
|
105
105
|
} from "../../server/request-context.js";
|
|
106
|
+
import { paramsEqual } from "../params-util.js";
|
|
106
107
|
|
|
107
108
|
// Lazily initialized prerender store singleton and dynamically imported deps.
|
|
108
109
|
// Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
|
|
@@ -114,6 +115,9 @@ let _deserializeSegments:
|
|
|
114
115
|
let _restoreHandles:
|
|
115
116
|
| typeof import("../../cache/handle-snapshot.js").restoreHandles
|
|
116
117
|
| undefined;
|
|
118
|
+
let _decodeHandles:
|
|
119
|
+
| typeof import("../../cache/handle-snapshot.js").decodeHandles
|
|
120
|
+
| undefined;
|
|
117
121
|
let _hashParams:
|
|
118
122
|
| typeof import("../../prerender/param-hash.js").hashParams
|
|
119
123
|
| undefined;
|
|
@@ -121,22 +125,6 @@ let _lazyGetRequestContext:
|
|
|
121
125
|
| typeof import("../../server/request-context.js").getRequestContext
|
|
122
126
|
| undefined;
|
|
123
127
|
|
|
124
|
-
function paramsEqual(
|
|
125
|
-
a: Record<string, string>,
|
|
126
|
-
b: Record<string, string>,
|
|
127
|
-
): boolean {
|
|
128
|
-
if (a === b) return true;
|
|
129
|
-
|
|
130
|
-
const keysA = Object.keys(a);
|
|
131
|
-
if (keysA.length !== Object.keys(b).length) return false;
|
|
132
|
-
|
|
133
|
-
for (const key of keysA) {
|
|
134
|
-
if (a[key] !== b[key]) return false;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
128
|
async function ensurePrerenderDeps() {
|
|
141
129
|
if (!_deserializeSegments) {
|
|
142
130
|
const [codec, snapshot, paramHash, reqCtx, store] = await Promise.all([
|
|
@@ -148,6 +136,7 @@ async function ensurePrerenderDeps() {
|
|
|
148
136
|
]);
|
|
149
137
|
_deserializeSegments = codec.deserializeSegments;
|
|
150
138
|
_restoreHandles = snapshot.restoreHandles;
|
|
139
|
+
_decodeHandles = snapshot.decodeHandles;
|
|
151
140
|
_hashParams = paramHash.hashParams;
|
|
152
141
|
_lazyGetRequestContext = reqCtx.getRequestContext;
|
|
153
142
|
if (prerenderStoreInstance === undefined) {
|
|
@@ -161,69 +150,21 @@ async function ensurePrerenderDeps() {
|
|
|
161
150
|
* Deserializes segments, replays handle data, yields segments with partial
|
|
162
151
|
* navigation nullification, and resolves fresh loaders.
|
|
163
152
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
153
|
+
// Resolve loaders fresh on a cache hit (loaders are NEVER cached) and yield
|
|
154
|
+
// the loader segments, updating state.matchedIds and pushing the loader-resolve
|
|
155
|
+
// + cache-hit metrics. Shared verbatim by the prerender-store path
|
|
156
|
+
// (yieldFromStore) and the runtime cache-hit path (withCacheLookup). The router
|
|
157
|
+
// resolve fns are passed in (captured early by the callers) rather than re-read
|
|
158
|
+
// from getRouterContext() here, because that is ALS-backed and the callers run
|
|
159
|
+
// awaits before reaching this point (workerd can disrupt ALS mid-pipeline).
|
|
160
|
+
async function* resolveFreshLoadersAndYield<TEnv>(
|
|
166
161
|
ctx: MatchContext<TEnv>,
|
|
167
162
|
state: MatchPipelineState,
|
|
168
163
|
pipelineStart: number,
|
|
169
|
-
|
|
164
|
+
ms: MatchContext<TEnv>["metricsStore"],
|
|
165
|
+
resolveLoadersOnly: RouterContext<TEnv>["resolveLoadersOnly"],
|
|
166
|
+
resolveLoadersOnlyWithRevalidation: RouterContext<TEnv>["resolveLoadersOnlyWithRevalidation"],
|
|
170
167
|
): AsyncGenerator<ResolvedSegment> {
|
|
171
|
-
const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
|
|
172
|
-
getRouterContext<TEnv>();
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
!_deserializeSegments ||
|
|
176
|
-
!_restoreHandles ||
|
|
177
|
-
!_hashParams ||
|
|
178
|
-
!_lazyGetRequestContext
|
|
179
|
-
) {
|
|
180
|
-
throw new Error("yieldFromStore called before ensurePrerenderDeps");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const segments = await _deserializeSegments(entry.segments);
|
|
184
|
-
|
|
185
|
-
// Replay handle data (same as runtime cache hit path).
|
|
186
|
-
// Prefer the eagerly-captured handleStoreRef to avoid ALS disruption in workerd.
|
|
187
|
-
const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
|
|
188
|
-
if (handleStore) {
|
|
189
|
-
_restoreHandles(entry.handles, handleStore);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
state.cacheHit = true;
|
|
193
|
-
state.cacheSource = "prerender";
|
|
194
|
-
state.cachedSegments = segments;
|
|
195
|
-
state.cachedMatchedIds = segments.map((s) => s.id);
|
|
196
|
-
|
|
197
|
-
// Set streaming flag (once) and resolve render barrier.
|
|
198
|
-
const reqCtx = handleStoreRef ? undefined : _lazyGetRequestContext?.();
|
|
199
|
-
const barrierReqCtx = reqCtx ?? _getRequestContext();
|
|
200
|
-
if (barrierReqCtx) {
|
|
201
|
-
if (barrierReqCtx._treeHasStreaming === undefined) {
|
|
202
|
-
barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
203
|
-
}
|
|
204
|
-
barrierReqCtx._resolveRenderBarrier(segments);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// For partial navigation, nullify components the client already has
|
|
208
|
-
// so parent layouts stay live (client keeps its existing versions).
|
|
209
|
-
// When params changed (e.g., different guide slug), the segments have
|
|
210
|
-
// different content, so we must NOT nullify.
|
|
211
|
-
const paramsChanged =
|
|
212
|
-
!ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
|
|
213
|
-
for (const segment of segments) {
|
|
214
|
-
if (
|
|
215
|
-
!ctx.isFullMatch &&
|
|
216
|
-
!paramsChanged &&
|
|
217
|
-
ctx.clientSegmentSet.has(segment.id)
|
|
218
|
-
) {
|
|
219
|
-
segment.component = null;
|
|
220
|
-
segment.loading = undefined;
|
|
221
|
-
}
|
|
222
|
-
yield segment;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Resolve loaders fresh (loaders are never pre-rendered/cached)
|
|
226
|
-
const ms = ctx.metricsStore;
|
|
227
168
|
const loaderStart = performance.now();
|
|
228
169
|
|
|
229
170
|
if (ctx.isFullMatch) {
|
|
@@ -251,6 +192,8 @@ async function* yieldFromStore<TEnv>(
|
|
|
251
192
|
ctx.url,
|
|
252
193
|
ctx.routeKey,
|
|
253
194
|
ctx.actionContext,
|
|
195
|
+
// Loaders are never cached in the segment cache, so segment staleness
|
|
196
|
+
// must not propagate; browser-sent staleness (ctx.stale) still must.
|
|
254
197
|
ctx.stale || undefined,
|
|
255
198
|
),
|
|
256
199
|
);
|
|
@@ -282,6 +225,83 @@ async function* yieldFromStore<TEnv>(
|
|
|
282
225
|
}
|
|
283
226
|
}
|
|
284
227
|
|
|
228
|
+
async function* yieldFromStore<TEnv>(
|
|
229
|
+
entry: PrerenderEntry,
|
|
230
|
+
ctx: MatchContext<TEnv>,
|
|
231
|
+
state: MatchPipelineState,
|
|
232
|
+
pipelineStart: number,
|
|
233
|
+
handleStoreRef?: HandleStore,
|
|
234
|
+
): AsyncGenerator<ResolvedSegment> {
|
|
235
|
+
const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
|
|
236
|
+
getRouterContext<TEnv>();
|
|
237
|
+
|
|
238
|
+
if (
|
|
239
|
+
!_deserializeSegments ||
|
|
240
|
+
!_restoreHandles ||
|
|
241
|
+
!_decodeHandles ||
|
|
242
|
+
!_hashParams ||
|
|
243
|
+
!_lazyGetRequestContext
|
|
244
|
+
) {
|
|
245
|
+
throw new Error("yieldFromStore called before ensurePrerenderDeps");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const segments = await _deserializeSegments(entry.segments);
|
|
249
|
+
|
|
250
|
+
// Replay handle data (same as runtime cache hit path). entry.handles is a
|
|
251
|
+
// Flight-encoded string ("" when none) — decode before restore so
|
|
252
|
+
// Promise/ReactNode handle values are revived, not the corrupted JSON form.
|
|
253
|
+
const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
|
|
254
|
+
if (handleStore && entry.handles) {
|
|
255
|
+
const handlesRecord = await _decodeHandles(entry.handles);
|
|
256
|
+
if (handlesRecord) {
|
|
257
|
+
_restoreHandles(handlesRecord, handleStore);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
state.cacheHit = true;
|
|
262
|
+
state.cacheSource = "prerender";
|
|
263
|
+
state.cachedSegments = segments;
|
|
264
|
+
state.cachedMatchedIds = segments.map((s) => s.id);
|
|
265
|
+
|
|
266
|
+
// Set streaming flag (once) and resolve render barrier.
|
|
267
|
+
const reqCtx = handleStoreRef ? undefined : _lazyGetRequestContext?.();
|
|
268
|
+
const barrierReqCtx = reqCtx ?? _getRequestContext();
|
|
269
|
+
if (barrierReqCtx) {
|
|
270
|
+
if (barrierReqCtx._treeHasStreaming === undefined) {
|
|
271
|
+
barrierReqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
272
|
+
}
|
|
273
|
+
barrierReqCtx._resolveRenderBarrier(segments);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// For partial navigation, nullify components the client already has
|
|
277
|
+
// so parent layouts stay live (client keeps its existing versions).
|
|
278
|
+
// When params changed (e.g., different guide slug), the segments have
|
|
279
|
+
// different content, so we must NOT nullify.
|
|
280
|
+
const paramsChanged =
|
|
281
|
+
!ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
|
|
282
|
+
for (const segment of segments) {
|
|
283
|
+
if (
|
|
284
|
+
!ctx.isFullMatch &&
|
|
285
|
+
!paramsChanged &&
|
|
286
|
+
ctx.clientSegmentSet.has(segment.id)
|
|
287
|
+
) {
|
|
288
|
+
segment.component = null;
|
|
289
|
+
segment.loading = undefined;
|
|
290
|
+
}
|
|
291
|
+
yield segment;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Resolve loaders fresh (loaders are never pre-rendered/cached).
|
|
295
|
+
yield* resolveFreshLoadersAndYield(
|
|
296
|
+
ctx,
|
|
297
|
+
state,
|
|
298
|
+
pipelineStart,
|
|
299
|
+
ctx.metricsStore,
|
|
300
|
+
resolveLoadersOnly,
|
|
301
|
+
resolveLoadersOnlyWithRevalidation,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
285
305
|
/**
|
|
286
306
|
* Look up a prerendered (build-time cached) entry for the current route and, on
|
|
287
307
|
* a hit, yield its segments. Returns true when an entry was served (the caller
|
|
@@ -358,10 +378,6 @@ export function withCacheLookup<TEnv>(
|
|
|
358
378
|
resolveLoadersOnly,
|
|
359
379
|
} = getRouterContext<TEnv>();
|
|
360
380
|
|
|
361
|
-
// Prerender lookup: check build-time cached data before runtime cache.
|
|
362
|
-
// Prerender data is available regardless of runtime cache configuration.
|
|
363
|
-
// Skip for HMR requests — the dev prerender endpoint reads from a stale
|
|
364
|
-
// RouterRegistry snapshot; rendering fresh ensures edits are visible.
|
|
365
381
|
const isHmr = !!ctx.request.headers.get("X-RSC-HMR");
|
|
366
382
|
if (!ctx.isAction && !isHmr && ctx.matched.pr) {
|
|
367
383
|
await ensurePrerenderDeps();
|
|
@@ -376,14 +392,6 @@ export function withCacheLookup<TEnv>(
|
|
|
376
392
|
}
|
|
377
393
|
}
|
|
378
394
|
|
|
379
|
-
// Dev-mode static handler interception for non-Node.js runtimes.
|
|
380
|
-
// __PRERENDER_DEV_URL is set by the Vite plugin when the RSC environment
|
|
381
|
-
// lacks a Node.js module runner (e.g. workerd, Deno workers). In those
|
|
382
|
-
// runtimes, handlers that depend on Node APIs like node:fs can't run
|
|
383
|
-
// in-process. We redirect them to the /__rsc_prerender endpoint which
|
|
384
|
-
// resolves segments in a Node.js temp server, same as prerender routes.
|
|
385
|
-
// In Node.js dev mode this variable is undefined -- handlers run
|
|
386
|
-
// in-process where Node APIs work, so no interception is needed.
|
|
387
395
|
if (!ctx.isAction && !ctx.matched.pr && globalThis.__PRERENDER_DEV_URL) {
|
|
388
396
|
const hasStatic = ctx.entries.some(
|
|
389
397
|
(e) =>
|
|
@@ -406,9 +414,7 @@ export function withCacheLookup<TEnv>(
|
|
|
406
414
|
}
|
|
407
415
|
}
|
|
408
416
|
|
|
409
|
-
// Skip cache during actions
|
|
410
417
|
if (ctx.isAction || !ctx.cacheScope?.enabled) {
|
|
411
|
-
// Cache miss - pass through to segment resolution
|
|
412
418
|
yield* source;
|
|
413
419
|
if (ms) {
|
|
414
420
|
ms.metrics.push({
|
|
@@ -420,7 +426,6 @@ export function withCacheLookup<TEnv>(
|
|
|
420
426
|
return;
|
|
421
427
|
}
|
|
422
428
|
|
|
423
|
-
// Lookup cache
|
|
424
429
|
const cacheResult = await ctx.cacheScope.lookupRoute(
|
|
425
430
|
ctx.pathname,
|
|
426
431
|
ctx.matched.params,
|
|
@@ -428,7 +433,6 @@ export function withCacheLookup<TEnv>(
|
|
|
428
433
|
);
|
|
429
434
|
|
|
430
435
|
if (!cacheResult) {
|
|
431
|
-
// Cache miss - pass through to segment resolution
|
|
432
436
|
yield* source;
|
|
433
437
|
if (ms) {
|
|
434
438
|
ms.metrics.push({
|
|
@@ -440,16 +444,12 @@ export function withCacheLookup<TEnv>(
|
|
|
440
444
|
return;
|
|
441
445
|
}
|
|
442
446
|
|
|
443
|
-
// Cache HIT
|
|
444
447
|
state.cacheHit = true;
|
|
445
448
|
state.cacheSource = "runtime";
|
|
446
449
|
state.shouldRevalidate = cacheResult.shouldRevalidate;
|
|
447
450
|
state.cachedSegments = cacheResult.segments;
|
|
448
451
|
state.cachedMatchedIds = cacheResult.segments.map((s) => s.id);
|
|
449
452
|
|
|
450
|
-
// Apply revalidation to cached segments.
|
|
451
|
-
// For full matches or empty client segment sets, this map is unnecessary:
|
|
452
|
-
// we never run segment-level revalidation and can stream segments directly.
|
|
453
453
|
const canCheckSegmentRevalidation =
|
|
454
454
|
!ctx.isFullMatch &&
|
|
455
455
|
ctx.clientSegmentSet.size > 0 &&
|
|
@@ -459,7 +459,6 @@ export function withCacheLookup<TEnv>(
|
|
|
459
459
|
: undefined;
|
|
460
460
|
|
|
461
461
|
for (const segment of cacheResult.segments) {
|
|
462
|
-
// Skip segments client doesn't have - they need their component
|
|
463
462
|
if (!ctx.clientSegmentSet.has(segment.id)) {
|
|
464
463
|
if (isTraceActive()) {
|
|
465
464
|
pushRevalidationTraceEntry({
|
|
@@ -476,19 +475,13 @@ export function withCacheLookup<TEnv>(
|
|
|
476
475
|
continue;
|
|
477
476
|
}
|
|
478
477
|
|
|
479
|
-
// Skip intercept segments - they're handled separately
|
|
480
478
|
if (segment.namespace?.startsWith("intercept:")) {
|
|
481
479
|
yield segment;
|
|
482
480
|
continue;
|
|
483
481
|
}
|
|
484
482
|
|
|
485
|
-
// Look up revalidation rules for this segment
|
|
486
483
|
const entryInfo = entryRevalidateMap?.get(segment.id);
|
|
487
484
|
|
|
488
|
-
// Even without explicit revalidation rules, route segments and their
|
|
489
|
-
// children must re-render when params or search params change — the
|
|
490
|
-
// handler reads ctx.params/ctx.searchParams so different values produce
|
|
491
|
-
// different content. Matches evaluateRevalidation's default logic.
|
|
492
485
|
const searchChanged = ctx.prevUrl.search !== ctx.url.search;
|
|
493
486
|
const routeParamsChanged = !paramsEqual(
|
|
494
487
|
ctx.matched.params,
|
|
@@ -502,7 +495,6 @@ export function withCacheLookup<TEnv>(
|
|
|
502
495
|
|
|
503
496
|
if (!entryInfo || entryInfo.revalidate.length === 0) {
|
|
504
497
|
if (shouldDefaultRevalidate) {
|
|
505
|
-
// Params or search params changed — must re-render even without custom rules
|
|
506
498
|
if (isTraceActive()) {
|
|
507
499
|
pushRevalidationTraceEntry({
|
|
508
500
|
segmentId: segment.id,
|
|
@@ -519,7 +511,6 @@ export function withCacheLookup<TEnv>(
|
|
|
519
511
|
yield segment;
|
|
520
512
|
continue;
|
|
521
513
|
}
|
|
522
|
-
// No revalidation rules, use default behavior (skip if client has)
|
|
523
514
|
if (isTraceActive()) {
|
|
524
515
|
pushRevalidationTraceEntry({
|
|
525
516
|
segmentId: segment.id,
|
|
@@ -537,7 +528,6 @@ export function withCacheLookup<TEnv>(
|
|
|
537
528
|
continue;
|
|
538
529
|
}
|
|
539
530
|
|
|
540
|
-
// Evaluate revalidation rules
|
|
541
531
|
const shouldRevalidate = await evaluateRevalidation({
|
|
542
532
|
segment,
|
|
543
533
|
prevParams: ctx.prevParams,
|
|
@@ -556,22 +546,16 @@ export function withCacheLookup<TEnv>(
|
|
|
556
546
|
traceSource: "cache-hit",
|
|
557
547
|
});
|
|
558
548
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
pathname: ctx.pathname,
|
|
568
|
-
routeKey: ctx.routeKey,
|
|
569
|
-
shouldRevalidate,
|
|
570
|
-
});
|
|
571
|
-
}
|
|
549
|
+
observeEvent({
|
|
550
|
+
type: "revalidation.decision",
|
|
551
|
+
timestamp: performance.now(),
|
|
552
|
+
segmentId: segment.id,
|
|
553
|
+
pathname: ctx.pathname,
|
|
554
|
+
routeKey: ctx.routeKey,
|
|
555
|
+
shouldRevalidate,
|
|
556
|
+
});
|
|
572
557
|
|
|
573
558
|
if (!shouldRevalidate) {
|
|
574
|
-
// Client has it, no revalidation needed
|
|
575
559
|
segment.component = null;
|
|
576
560
|
segment.loading = undefined;
|
|
577
561
|
}
|
|
@@ -579,7 +563,6 @@ export function withCacheLookup<TEnv>(
|
|
|
579
563
|
yield segment;
|
|
580
564
|
}
|
|
581
565
|
|
|
582
|
-
// Set streaming flag (once) and resolve render barrier.
|
|
583
566
|
const barrierReqCtx = _getRequestContext();
|
|
584
567
|
if (barrierReqCtx) {
|
|
585
568
|
if (barrierReqCtx._treeHasStreaming === undefined) {
|
|
@@ -588,77 +571,15 @@ export function withCacheLookup<TEnv>(
|
|
|
588
571
|
barrierReqCtx._resolveRenderBarrier(cacheResult.segments);
|
|
589
572
|
}
|
|
590
573
|
|
|
591
|
-
// Resolve loaders fresh (loaders are
|
|
592
|
-
//
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
);
|
|
602
|
-
|
|
603
|
-
// Update state - full match doesn't track matchedIds separately
|
|
604
|
-
state.matchedIds = state.cachedMatchedIds!;
|
|
605
|
-
|
|
606
|
-
// Yield fresh loader segments
|
|
607
|
-
for (const segment of loaderSegments) {
|
|
608
|
-
yield segment;
|
|
609
|
-
}
|
|
610
|
-
} else {
|
|
611
|
-
state.matchedIds = state.cachedMatchedIds!;
|
|
612
|
-
}
|
|
613
|
-
} else {
|
|
614
|
-
// Partial match (navigation) - loader resolution with revalidation
|
|
615
|
-
if (resolveLoadersOnlyWithRevalidation) {
|
|
616
|
-
const loaderResult = await Store.run(() =>
|
|
617
|
-
resolveLoadersOnlyWithRevalidation(
|
|
618
|
-
ctx.entries,
|
|
619
|
-
ctx.handlerContext,
|
|
620
|
-
ctx.clientSegmentSet,
|
|
621
|
-
ctx.prevParams,
|
|
622
|
-
ctx.request,
|
|
623
|
-
ctx.prevUrl,
|
|
624
|
-
ctx.url,
|
|
625
|
-
ctx.routeKey,
|
|
626
|
-
ctx.actionContext,
|
|
627
|
-
// Loaders are never cached in the segment cache, so segment
|
|
628
|
-
// staleness (cacheResult.shouldRevalidate) must not propagate.
|
|
629
|
-
// But browser-sent staleness (ctx.stale) — indicating an action
|
|
630
|
-
// happened in this or another tab — must still reach loaders.
|
|
631
|
-
ctx.stale || undefined,
|
|
632
|
-
),
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
// Update state with fresh loader matchedIds
|
|
636
|
-
state.matchedIds = [
|
|
637
|
-
...state.cachedMatchedIds!,
|
|
638
|
-
...loaderResult.matchedIds,
|
|
639
|
-
];
|
|
640
|
-
|
|
641
|
-
// Yield fresh loader segments
|
|
642
|
-
for (const segment of loaderResult.segments) {
|
|
643
|
-
yield segment;
|
|
644
|
-
}
|
|
645
|
-
} else {
|
|
646
|
-
state.matchedIds = state.cachedMatchedIds!;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
if (ms) {
|
|
650
|
-
const loaderEnd = performance.now();
|
|
651
|
-
ms.metrics.push({
|
|
652
|
-
label: "pipeline:loader-resolve",
|
|
653
|
-
duration: loaderEnd - loaderStart,
|
|
654
|
-
startTime: loaderStart - ms.requestStart,
|
|
655
|
-
depth: 1,
|
|
656
|
-
});
|
|
657
|
-
ms.metrics.push({
|
|
658
|
-
label: "pipeline:cache-hit",
|
|
659
|
-
duration: loaderEnd - pipelineStart,
|
|
660
|
-
startTime: pipelineStart - ms.requestStart,
|
|
661
|
-
});
|
|
662
|
-
}
|
|
574
|
+
// Resolve loaders fresh (loaders are never cached). Shared with the
|
|
575
|
+
// prerender-store path via resolveFreshLoadersAndYield.
|
|
576
|
+
yield* resolveFreshLoadersAndYield(
|
|
577
|
+
ctx,
|
|
578
|
+
state,
|
|
579
|
+
pipelineStart,
|
|
580
|
+
ms,
|
|
581
|
+
resolveLoadersOnly,
|
|
582
|
+
resolveLoadersOnlyWithRevalidation,
|
|
583
|
+
);
|
|
663
584
|
};
|
|
664
585
|
}
|
|
@@ -101,7 +101,10 @@
|
|
|
101
101
|
* - Non-GET request (only GET requests are cacheable)
|
|
102
102
|
*/
|
|
103
103
|
import type { ResolvedSegment } from "../../types.js";
|
|
104
|
-
import {
|
|
104
|
+
import {
|
|
105
|
+
getRequestContext,
|
|
106
|
+
runWithRequestContext,
|
|
107
|
+
} from "../../server/request-context.js";
|
|
105
108
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
106
109
|
import { getRouterContext } from "../router-context.js";
|
|
107
110
|
import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
@@ -123,21 +126,14 @@ export function withCacheStore<TEnv>(
|
|
|
123
126
|
): AsyncGenerator<ResolvedSegment> {
|
|
124
127
|
const ms = ctx.metricsStore;
|
|
125
128
|
|
|
126
|
-
// Collect all segments while passing them through
|
|
127
129
|
const allSegments: ResolvedSegment[] = [];
|
|
128
130
|
for await (const segment of source) {
|
|
129
131
|
allSegments.push(segment);
|
|
130
132
|
yield segment;
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
// Measure own work only (after source iteration completes)
|
|
134
135
|
const ownStart = performance.now();
|
|
135
136
|
|
|
136
|
-
// Skip caching if:
|
|
137
|
-
// 1. Cache miss but cache scope is disabled
|
|
138
|
-
// 2. This is an action (actions don't cache)
|
|
139
|
-
// 3. Cache was already hit (no need to re-cache)
|
|
140
|
-
// 4. Non-GET request (only cache GET requests)
|
|
141
137
|
if (
|
|
142
138
|
!ctx.cacheScope?.enabled ||
|
|
143
139
|
ctx.isAction ||
|
|
@@ -162,13 +158,17 @@ export function withCacheStore<TEnv>(
|
|
|
162
158
|
createHandleStore,
|
|
163
159
|
} = getRouterContext<TEnv>();
|
|
164
160
|
|
|
165
|
-
//
|
|
166
|
-
|
|
161
|
+
// On a fresh intercept miss the intercept slot segments flow through
|
|
162
|
+
// `source` into allSegments AND are also recorded on state.interceptSegments.
|
|
163
|
+
// Dedup by id so each segment is cached once — cacheRoute serializes the
|
|
164
|
+
// array verbatim (no id-dedup), so an un-deduped append doubles the intercept
|
|
165
|
+
// segment in the cached entry, re-rendering the slot twice on the next hit.
|
|
166
|
+
const seenSegmentIds = new Set(allSegments.map((s) => s.id));
|
|
167
|
+
const allSegmentsToCache = [
|
|
168
|
+
...allSegments,
|
|
169
|
+
...state.interceptSegments.filter((s) => !seenSegmentIds.has(s.id)),
|
|
170
|
+
];
|
|
167
171
|
|
|
168
|
-
// Check if any non-loader segments have null components from revalidation
|
|
169
|
-
// skip (client already had them). Segments where the handler intentionally
|
|
170
|
-
// returned null are not revalidation skips — re-rendering them will still
|
|
171
|
-
// produce null, so proactive caching would be wasted work.
|
|
172
172
|
const hasNullComponents = allSegmentsToCache.some(
|
|
173
173
|
(s) =>
|
|
174
174
|
s.component === null &&
|
|
@@ -180,14 +180,23 @@ export function withCacheStore<TEnv>(
|
|
|
180
180
|
if (!requestCtx) return;
|
|
181
181
|
|
|
182
182
|
const cacheScope = ctx.cacheScope;
|
|
183
|
+
|
|
184
|
+
// Record the route's segment-DSL cache tags into the request tag union NOW,
|
|
185
|
+
// synchronously in the pipeline. The actual store write (cacheRoute) runs in
|
|
186
|
+
// requestCtx.waitUntil() below — and the proactive path re-resolves the whole
|
|
187
|
+
// tree first — so cacheRoute's own tag recording races the document cache's
|
|
188
|
+
// post-body-drain snapshot of _requestTags. On a first-write miss the document
|
|
189
|
+
// tag union could miss these tags and revalidateTag()/updateTag() would not
|
|
190
|
+
// invalidate the cached document. Recording here (before the snapshot) closes
|
|
191
|
+
// the window for both the direct and proactive write paths; the duplicate
|
|
192
|
+
// record inside cacheRoute is idempotent.
|
|
193
|
+
cacheScope.recordTags(requestCtx);
|
|
194
|
+
|
|
183
195
|
const reqId = INTERNAL_RANGO_DEBUG
|
|
184
196
|
? getOrCreateRequestId(ctx.request)
|
|
185
197
|
: undefined;
|
|
186
198
|
|
|
187
|
-
// Register onResponse callback to skip caching for non-200 responses
|
|
188
|
-
// Note: error/notFound status codes are set elsewhere (not caching-specific)
|
|
189
199
|
requestCtx.onResponse((response) => {
|
|
190
|
-
// Only cache successful responses
|
|
191
200
|
if (response.status !== 200) {
|
|
192
201
|
debugLog("cacheStore", "skipping cache for non-200 response", {
|
|
193
202
|
status: response.status,
|
|
@@ -197,10 +206,7 @@ export function withCacheStore<TEnv>(
|
|
|
197
206
|
}
|
|
198
207
|
|
|
199
208
|
if (hasNullComponents) {
|
|
200
|
-
// Proactive caching: render all segments fresh in background
|
|
201
|
-
// This ensures cache has complete components for future requests
|
|
202
209
|
requestCtx.waitUntil(async () => {
|
|
203
|
-
// Prevent background metrics from polluting foreground timeline.
|
|
204
210
|
const savedMetrics = ctx.Store.metrics;
|
|
205
211
|
ctx.Store.metrics = undefined;
|
|
206
212
|
|
|
@@ -208,15 +214,9 @@ export function withCacheStore<TEnv>(
|
|
|
208
214
|
debugLog("cacheStore", "proactive caching started", {
|
|
209
215
|
pathname: ctx.pathname,
|
|
210
216
|
});
|
|
211
|
-
// Swap to a fresh HandleStore so handle.push() calls from
|
|
212
|
-
// proactive resolution are captured (not silenced). The original
|
|
213
|
-
// store's stream is already sent by waitUntil time.
|
|
214
|
-
// cacheRoute reads from requestCtx._handleStore, so this ensures
|
|
215
|
-
// complete handle data (e.g. breadcrumbs) is cached.
|
|
216
217
|
const originalHandleStore = requestCtx._handleStore;
|
|
217
218
|
requestCtx._handleStore = createHandleStore();
|
|
218
219
|
try {
|
|
219
|
-
// Create fresh context for proactive caching
|
|
220
220
|
const proactiveHandlerContext = createHandlerContext(
|
|
221
221
|
ctx.matched.params,
|
|
222
222
|
ctx.request,
|
|
@@ -231,36 +231,49 @@ export function withCacheStore<TEnv>(
|
|
|
231
231
|
);
|
|
232
232
|
const proactiveLoaderPromises = new Map<string, Promise<any>>();
|
|
233
233
|
|
|
234
|
-
// Use normal loader access so handle data is captured
|
|
235
234
|
setupLoaderAccess(proactiveHandlerContext, proactiveLoaderPromises);
|
|
236
235
|
|
|
237
|
-
// Re-resolve ALL segments without revalidation.
|
|
238
|
-
// Skip DSL loaders — they are never cached (cacheRoute filters them)
|
|
239
|
-
// and are always resolved fresh on each request.
|
|
240
236
|
const Store = ctx.Store;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
237
|
+
// Re-establish the request-context ALS around the re-render. Store
|
|
238
|
+
// is a different ALS (DSL build context); on workerd a waitUntil
|
|
239
|
+
// task runs detached from the request's I/O context, so a handler/
|
|
240
|
+
// component that reads the ambient getRequestContext() during this
|
|
241
|
+
// background re-render would otherwise throw "called outside of a
|
|
242
|
+
// request context".
|
|
243
|
+
const freshSegments = await runWithRequestContext(requestCtx, () =>
|
|
244
|
+
Store.run(() =>
|
|
245
|
+
resolveAllSegments(
|
|
246
|
+
ctx.entries,
|
|
247
|
+
ctx.routeKey,
|
|
248
|
+
ctx.matched.params,
|
|
249
|
+
proactiveHandlerContext,
|
|
250
|
+
proactiveLoaderPromises,
|
|
251
|
+
{ skipLoaders: true },
|
|
252
|
+
),
|
|
249
253
|
),
|
|
250
254
|
);
|
|
251
255
|
|
|
252
|
-
// Also resolve intercept segments fresh if applicable
|
|
253
256
|
let freshInterceptSegments: ResolvedSegment[] = [];
|
|
254
257
|
if (ctx.interceptResult) {
|
|
255
|
-
freshInterceptSegments = await
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
freshInterceptSegments = await runWithRequestContext(
|
|
259
|
+
requestCtx,
|
|
260
|
+
() =>
|
|
261
|
+
Store.run(() =>
|
|
262
|
+
resolveInterceptEntry(
|
|
263
|
+
ctx.interceptResult!.intercept,
|
|
264
|
+
ctx.interceptResult!.entry,
|
|
265
|
+
ctx.matched.params,
|
|
266
|
+
proactiveHandlerContext,
|
|
267
|
+
true, // belongsToRoute
|
|
268
|
+
// No revalidationContext = render fresh
|
|
269
|
+
undefined,
|
|
270
|
+
// Skip intercept middleware: the foreground already ran it
|
|
271
|
+
// before the response was sent. Re-running here (post-
|
|
272
|
+
// response, background) would fire side effects twice and a
|
|
273
|
+
// short-circuit Response would silently abort this write.
|
|
274
|
+
{ skipMiddleware: true },
|
|
275
|
+
),
|
|
276
|
+
),
|
|
264
277
|
);
|
|
265
278
|
}
|
|
266
279
|
|
|
@@ -301,8 +314,6 @@ export function withCacheStore<TEnv>(
|
|
|
301
314
|
}
|
|
302
315
|
});
|
|
303
316
|
} else {
|
|
304
|
-
// All segments have components - cache directly
|
|
305
|
-
// Schedule caching in waitUntil since cacheRoute is now async (key resolution)
|
|
306
317
|
if (INTERNAL_RANGO_DEBUG) {
|
|
307
318
|
console.log(
|
|
308
319
|
`[RSC CacheStore][req:${reqId}] Direct cache path: scheduling cacheRoute for ${ctx.pathname} (${allSegmentsToCache.length} segments, hasNullComponents=${hasNullComponents})`,
|