@rangojs/router 0.0.0-experimental.0f44aca1
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 +5 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +5214 -0
- package/package.json +176 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +220 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +645 -0
- package/src/browser/navigation-client.ts +215 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +550 -0
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +360 -0
- package/src/browser/react/NavigationProvider.tsx +386 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +431 -0
- package/src/browser/scroll-restoration.ts +400 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +538 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +540 -0
- package/src/cache/cf/index.ts +25 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +43 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +275 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +267 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +192 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +316 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1239 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1002 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +914 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +365 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Lookup Middleware
|
|
3
|
+
*
|
|
4
|
+
* First middleware in the pipeline. Checks cache before segment resolution.
|
|
5
|
+
*
|
|
6
|
+
* FLOW DIAGRAM
|
|
7
|
+
* ============
|
|
8
|
+
*
|
|
9
|
+
* source (empty)
|
|
10
|
+
* |
|
|
11
|
+
* v
|
|
12
|
+
* +---------------------+
|
|
13
|
+
* | Is action request? |──yes──> yield* source (pass through)
|
|
14
|
+
* +---------------------+
|
|
15
|
+
* | no
|
|
16
|
+
* v
|
|
17
|
+
* +---------------------+
|
|
18
|
+
* | Cache enabled? |──no───> yield* source (pass through)
|
|
19
|
+
* +---------------------+
|
|
20
|
+
* | yes
|
|
21
|
+
* v
|
|
22
|
+
* +---------------------+
|
|
23
|
+
* | Lookup cache |
|
|
24
|
+
* | (pathname, params) |
|
|
25
|
+
* +---------------------+
|
|
26
|
+
* |
|
|
27
|
+
* +-----+-----+
|
|
28
|
+
* | |
|
|
29
|
+
* miss hit
|
|
30
|
+
* | |
|
|
31
|
+
* v v
|
|
32
|
+
* yield* Set state.cacheHit = true
|
|
33
|
+
* source Set state.shouldRevalidate
|
|
34
|
+
* | |
|
|
35
|
+
* | v
|
|
36
|
+
* | +---------------------------+
|
|
37
|
+
* | | For each cached segment: |
|
|
38
|
+
* | | - Apply revalidation |
|
|
39
|
+
* | | - Set component = null |
|
|
40
|
+
* | | if client has it |
|
|
41
|
+
* | +---------------------------+
|
|
42
|
+
* | |
|
|
43
|
+
* | v
|
|
44
|
+
* | +---------------------------+
|
|
45
|
+
* | | Resolve fresh loaders | <-- Loaders are NEVER cached
|
|
46
|
+
* | | (always fresh data) |
|
|
47
|
+
* | +---------------------------+
|
|
48
|
+
* | |
|
|
49
|
+
* | v
|
|
50
|
+
* | yield cached segments
|
|
51
|
+
* | yield fresh loader segments
|
|
52
|
+
* | |
|
|
53
|
+
* +-----------+
|
|
54
|
+
* |
|
|
55
|
+
* v
|
|
56
|
+
* next middleware
|
|
57
|
+
*
|
|
58
|
+
*
|
|
59
|
+
* CACHE BEHAVIOR
|
|
60
|
+
* ==============
|
|
61
|
+
*
|
|
62
|
+
* Cache HIT:
|
|
63
|
+
* - state.cacheHit = true signals downstream middleware to skip
|
|
64
|
+
* - Cached segments have their components nullified if client already has them
|
|
65
|
+
* - Loaders are always re-resolved for fresh data
|
|
66
|
+
* - state.shouldRevalidate triggers background SWR if cache was stale
|
|
67
|
+
*
|
|
68
|
+
* Cache MISS:
|
|
69
|
+
* - Passes through to segment-resolution middleware
|
|
70
|
+
* - No segments yielded from this middleware
|
|
71
|
+
*
|
|
72
|
+
* Loaders:
|
|
73
|
+
* - NEVER cached by design
|
|
74
|
+
* - Always resolved fresh on every request
|
|
75
|
+
* - Ensures data freshness even with cached UI components
|
|
76
|
+
*
|
|
77
|
+
*
|
|
78
|
+
* REVALIDATION RULES
|
|
79
|
+
* ==================
|
|
80
|
+
*
|
|
81
|
+
* Each cached segment is evaluated against its revalidation rules:
|
|
82
|
+
*
|
|
83
|
+
* 1. No rules defined -> use default (skip if client has segment)
|
|
84
|
+
* 2. Rules return false -> skip re-render (nullify component)
|
|
85
|
+
* 3. Rules return true -> re-render (keep component)
|
|
86
|
+
*
|
|
87
|
+
* Revalidation context includes:
|
|
88
|
+
* - Previous/next URL and params
|
|
89
|
+
* - Request object
|
|
90
|
+
* - Action context (if POST)
|
|
91
|
+
*/
|
|
92
|
+
import type { ResolvedSegment } from "../../types.js";
|
|
93
|
+
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
94
|
+
import { getRouterContext } from "../router-context.js";
|
|
95
|
+
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
96
|
+
import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
|
|
97
|
+
import type { PrerenderStore, PrerenderEntry } from "../../prerender/store.js";
|
|
98
|
+
import type { HandleStore } from "../../server/handle-store.js";
|
|
99
|
+
import {
|
|
100
|
+
getRequestContext,
|
|
101
|
+
_getRequestContext,
|
|
102
|
+
} from "../../server/request-context.js";
|
|
103
|
+
|
|
104
|
+
// Lazily initialized prerender store singleton and dynamically imported deps.
|
|
105
|
+
// Dynamic imports prevent pulling in @vitejs/plugin-rsc/rsc virtual module at
|
|
106
|
+
// top-level, which breaks vitest (only URLs with file:, data:, node: schemes).
|
|
107
|
+
let prerenderStoreInstance: PrerenderStore | null | undefined;
|
|
108
|
+
let _deserializeSegments:
|
|
109
|
+
| typeof import("../../cache/segment-codec.js").deserializeSegments
|
|
110
|
+
| undefined;
|
|
111
|
+
let _restoreHandles:
|
|
112
|
+
| typeof import("../../cache/handle-snapshot.js").restoreHandles
|
|
113
|
+
| undefined;
|
|
114
|
+
let _hashParams:
|
|
115
|
+
| typeof import("../../prerender/param-hash.js").hashParams
|
|
116
|
+
| undefined;
|
|
117
|
+
let _lazyGetRequestContext:
|
|
118
|
+
| typeof import("../../server/request-context.js").getRequestContext
|
|
119
|
+
| undefined;
|
|
120
|
+
|
|
121
|
+
function paramsEqual(
|
|
122
|
+
a: Record<string, string>,
|
|
123
|
+
b: Record<string, string>,
|
|
124
|
+
): boolean {
|
|
125
|
+
if (a === b) return true;
|
|
126
|
+
|
|
127
|
+
const keysA = Object.keys(a);
|
|
128
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
129
|
+
|
|
130
|
+
for (const key of keysA) {
|
|
131
|
+
if (a[key] !== b[key]) return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function ensurePrerenderDeps() {
|
|
138
|
+
if (!_deserializeSegments) {
|
|
139
|
+
const [codec, snapshot, paramHash, reqCtx, store] = await Promise.all([
|
|
140
|
+
import("../../cache/segment-codec.js"),
|
|
141
|
+
import("../../cache/handle-snapshot.js"),
|
|
142
|
+
import("../../prerender/param-hash.js"),
|
|
143
|
+
import("../../server/request-context.js"),
|
|
144
|
+
import("../../prerender/store.js"),
|
|
145
|
+
]);
|
|
146
|
+
_deserializeSegments = codec.deserializeSegments;
|
|
147
|
+
_restoreHandles = snapshot.restoreHandles;
|
|
148
|
+
_hashParams = paramHash.hashParams;
|
|
149
|
+
_lazyGetRequestContext = reqCtx.getRequestContext;
|
|
150
|
+
if (prerenderStoreInstance === undefined) {
|
|
151
|
+
prerenderStoreInstance = store.createPrerenderStore();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Shared yield logic for prerender and static handler store entries.
|
|
158
|
+
* Deserializes segments, replays handle data, yields segments with partial
|
|
159
|
+
* navigation nullification, and resolves fresh loaders.
|
|
160
|
+
*/
|
|
161
|
+
async function* yieldFromStore<TEnv>(
|
|
162
|
+
entry: PrerenderEntry,
|
|
163
|
+
ctx: MatchContext<TEnv>,
|
|
164
|
+
state: MatchPipelineState,
|
|
165
|
+
pipelineStart: number,
|
|
166
|
+
handleStoreRef?: HandleStore,
|
|
167
|
+
): AsyncGenerator<ResolvedSegment> {
|
|
168
|
+
const { resolveLoadersOnlyWithRevalidation, resolveLoadersOnly } =
|
|
169
|
+
getRouterContext<TEnv>();
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
!_deserializeSegments ||
|
|
173
|
+
!_restoreHandles ||
|
|
174
|
+
!_hashParams ||
|
|
175
|
+
!_lazyGetRequestContext
|
|
176
|
+
) {
|
|
177
|
+
throw new Error("yieldFromStore called before ensurePrerenderDeps");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const segments = await _deserializeSegments(entry.segments);
|
|
181
|
+
|
|
182
|
+
// Replay handle data (same as runtime cache hit path).
|
|
183
|
+
// Prefer the eagerly-captured handleStoreRef to avoid ALS disruption in workerd.
|
|
184
|
+
const handleStore = handleStoreRef ?? _lazyGetRequestContext()?._handleStore;
|
|
185
|
+
if (handleStore) {
|
|
186
|
+
_restoreHandles(entry.handles, handleStore);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
state.cacheHit = true;
|
|
190
|
+
state.cacheSource = "prerender";
|
|
191
|
+
state.cachedSegments = segments;
|
|
192
|
+
state.cachedMatchedIds = segments.map((s) => s.id);
|
|
193
|
+
|
|
194
|
+
// For partial navigation, nullify components the client already has
|
|
195
|
+
// so parent layouts stay live (client keeps its existing versions).
|
|
196
|
+
// When params changed (e.g., different guide slug), the segments have
|
|
197
|
+
// different content, so we must NOT nullify.
|
|
198
|
+
const paramsChanged =
|
|
199
|
+
!ctx.isFullMatch && !paramsEqual(ctx.matched.params, ctx.prevParams);
|
|
200
|
+
for (const segment of segments) {
|
|
201
|
+
if (
|
|
202
|
+
!ctx.isFullMatch &&
|
|
203
|
+
!paramsChanged &&
|
|
204
|
+
ctx.clientSegmentSet.has(segment.id)
|
|
205
|
+
) {
|
|
206
|
+
segment.component = null;
|
|
207
|
+
segment.loading = undefined;
|
|
208
|
+
}
|
|
209
|
+
yield segment;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Resolve loaders fresh (loaders are never pre-rendered/cached)
|
|
213
|
+
if (ctx.isFullMatch) {
|
|
214
|
+
if (resolveLoadersOnly) {
|
|
215
|
+
const loaderSegments = await ctx.Store.run(() =>
|
|
216
|
+
resolveLoadersOnly(ctx.entries, ctx.handlerContext),
|
|
217
|
+
);
|
|
218
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
219
|
+
for (const segment of loaderSegments) {
|
|
220
|
+
yield segment;
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
if (resolveLoadersOnlyWithRevalidation) {
|
|
227
|
+
const loaderResult = await ctx.Store.run(() =>
|
|
228
|
+
resolveLoadersOnlyWithRevalidation(
|
|
229
|
+
ctx.entries,
|
|
230
|
+
ctx.handlerContext,
|
|
231
|
+
ctx.clientSegmentSet,
|
|
232
|
+
ctx.prevParams,
|
|
233
|
+
ctx.request,
|
|
234
|
+
ctx.prevUrl,
|
|
235
|
+
ctx.url,
|
|
236
|
+
ctx.routeKey,
|
|
237
|
+
ctx.actionContext,
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
state.matchedIds = [
|
|
241
|
+
...state.cachedMatchedIds!,
|
|
242
|
+
...loaderResult.matchedIds,
|
|
243
|
+
];
|
|
244
|
+
for (const segment of loaderResult.segments) {
|
|
245
|
+
yield segment;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const ms = ctx.metricsStore;
|
|
253
|
+
if (ms) {
|
|
254
|
+
ms.metrics.push({
|
|
255
|
+
label: "pipeline:cache-lookup",
|
|
256
|
+
duration: performance.now() - pipelineStart,
|
|
257
|
+
startTime: pipelineStart - ms.requestStart,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Async generator middleware type
|
|
264
|
+
*/
|
|
265
|
+
export type GeneratorMiddleware<T> = (
|
|
266
|
+
source: AsyncGenerator<T>,
|
|
267
|
+
) => AsyncGenerator<T>;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Creates cache lookup middleware
|
|
271
|
+
*
|
|
272
|
+
* Checks cache for segments. If cache hit:
|
|
273
|
+
* - Applies revalidation to determine which segments need re-rendering
|
|
274
|
+
* - Resolves loaders fresh (loaders are NOT cached by design)
|
|
275
|
+
* - Sets state.cacheHit = true
|
|
276
|
+
* - Sets state.shouldRevalidate if SWR needed
|
|
277
|
+
* - Yields cached segments + fresh loader segments
|
|
278
|
+
*
|
|
279
|
+
* If cache miss:
|
|
280
|
+
* - Passes through to next middleware
|
|
281
|
+
*/
|
|
282
|
+
export function withCacheLookup<TEnv>(
|
|
283
|
+
ctx: MatchContext<TEnv>,
|
|
284
|
+
state: MatchPipelineState,
|
|
285
|
+
): GeneratorMiddleware<ResolvedSegment> {
|
|
286
|
+
return async function* (
|
|
287
|
+
source: AsyncGenerator<ResolvedSegment>,
|
|
288
|
+
): AsyncGenerator<ResolvedSegment> {
|
|
289
|
+
const pipelineStart = performance.now();
|
|
290
|
+
const ms = ctx.metricsStore;
|
|
291
|
+
|
|
292
|
+
// Eagerly capture the HandleStore before any async operations.
|
|
293
|
+
// In workerd/Cloudflare, dynamic imports and fetch() inside the pipeline
|
|
294
|
+
// can disrupt AsyncLocalStorage, causing getRequestContext() to return
|
|
295
|
+
// undefined afterward. Capturing the reference early ensures handle replay
|
|
296
|
+
// and handler handle-push work regardless of ALS state.
|
|
297
|
+
const handleStoreRef = _getRequestContext()?._handleStore;
|
|
298
|
+
|
|
299
|
+
const {
|
|
300
|
+
evaluateRevalidation,
|
|
301
|
+
buildEntryRevalidateMap,
|
|
302
|
+
resolveLoadersOnlyWithRevalidation,
|
|
303
|
+
resolveLoadersOnly,
|
|
304
|
+
} = getRouterContext<TEnv>();
|
|
305
|
+
|
|
306
|
+
// Prerender lookup: check build-time cached data before runtime cache.
|
|
307
|
+
// Prerender data is available regardless of runtime cache configuration.
|
|
308
|
+
if (!ctx.isAction && ctx.matched.pr) {
|
|
309
|
+
await ensurePrerenderDeps();
|
|
310
|
+
if (prerenderStoreInstance) {
|
|
311
|
+
const paramHash = _hashParams!(ctx.matched.params);
|
|
312
|
+
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
313
|
+
(entry) =>
|
|
314
|
+
entry.type === "route" &&
|
|
315
|
+
entry.prerenderDef?.options?.passthrough === true,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
if (ctx.isIntercept) {
|
|
319
|
+
// Intercept navigation: try intercept-specific prerender entry
|
|
320
|
+
const entry = await prerenderStoreInstance.get(
|
|
321
|
+
ctx.matched.routeKey,
|
|
322
|
+
paramHash + "/i",
|
|
323
|
+
{
|
|
324
|
+
pathname: ctx.pathname,
|
|
325
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
if (entry) {
|
|
329
|
+
yield* yieldFromStore(
|
|
330
|
+
entry,
|
|
331
|
+
ctx,
|
|
332
|
+
state,
|
|
333
|
+
pipelineStart,
|
|
334
|
+
handleStoreRef,
|
|
335
|
+
);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
// No intercept prerender -- fall through to normal pipeline
|
|
339
|
+
// (skip non-intercept prerender to let intercept-resolution run)
|
|
340
|
+
} else {
|
|
341
|
+
// Normal navigation: existing behavior
|
|
342
|
+
const entry = await prerenderStoreInstance.get(
|
|
343
|
+
ctx.matched.routeKey,
|
|
344
|
+
paramHash,
|
|
345
|
+
{
|
|
346
|
+
pathname: ctx.pathname,
|
|
347
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
348
|
+
},
|
|
349
|
+
);
|
|
350
|
+
if (entry) {
|
|
351
|
+
yield* yieldFromStore(
|
|
352
|
+
entry,
|
|
353
|
+
ctx,
|
|
354
|
+
state,
|
|
355
|
+
pipelineStart,
|
|
356
|
+
handleStoreRef,
|
|
357
|
+
);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Dev-mode static handler interception for non-Node.js runtimes.
|
|
365
|
+
// __PRERENDER_DEV_URL is set by the Vite plugin when the RSC environment
|
|
366
|
+
// lacks a Node.js module runner (e.g. workerd, Deno workers). In those
|
|
367
|
+
// runtimes, handlers that depend on Node APIs like node:fs can't run
|
|
368
|
+
// in-process. We redirect them to the /__rsc_prerender endpoint which
|
|
369
|
+
// resolves segments in a Node.js temp server, same as prerender routes.
|
|
370
|
+
// In Node.js dev mode this variable is undefined -- handlers run
|
|
371
|
+
// in-process where Node APIs work, so no interception is needed.
|
|
372
|
+
if (!ctx.isAction && !ctx.matched.pr && globalThis.__PRERENDER_DEV_URL) {
|
|
373
|
+
const hasStatic = ctx.entries.some(
|
|
374
|
+
(e) =>
|
|
375
|
+
(e.type === "layout" ||
|
|
376
|
+
e.type === "route" ||
|
|
377
|
+
e.type === "parallel") &&
|
|
378
|
+
e.isStaticPrerender,
|
|
379
|
+
);
|
|
380
|
+
if (hasStatic) {
|
|
381
|
+
await ensurePrerenderDeps();
|
|
382
|
+
if (prerenderStoreInstance) {
|
|
383
|
+
const paramHash = _hashParams!(ctx.matched.params);
|
|
384
|
+
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
385
|
+
(entry) =>
|
|
386
|
+
entry.type === "route" &&
|
|
387
|
+
entry.prerenderDef?.options?.passthrough === true,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
if (ctx.isIntercept) {
|
|
391
|
+
const entry = await prerenderStoreInstance.get(
|
|
392
|
+
ctx.matched.routeKey,
|
|
393
|
+
paramHash + "/i",
|
|
394
|
+
{
|
|
395
|
+
pathname: ctx.pathname,
|
|
396
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
if (entry) {
|
|
400
|
+
yield* yieldFromStore(
|
|
401
|
+
entry,
|
|
402
|
+
ctx,
|
|
403
|
+
state,
|
|
404
|
+
pipelineStart,
|
|
405
|
+
handleStoreRef,
|
|
406
|
+
);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
// No intercept prerender -- fall through to normal pipeline
|
|
410
|
+
} else {
|
|
411
|
+
const entry = await prerenderStoreInstance.get(
|
|
412
|
+
ctx.matched.routeKey,
|
|
413
|
+
paramHash,
|
|
414
|
+
{
|
|
415
|
+
pathname: ctx.pathname,
|
|
416
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
417
|
+
},
|
|
418
|
+
);
|
|
419
|
+
if (entry) {
|
|
420
|
+
yield* yieldFromStore(
|
|
421
|
+
entry,
|
|
422
|
+
ctx,
|
|
423
|
+
state,
|
|
424
|
+
pipelineStart,
|
|
425
|
+
handleStoreRef,
|
|
426
|
+
);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Skip cache during actions
|
|
435
|
+
if (ctx.isAction || !ctx.cacheScope?.enabled) {
|
|
436
|
+
// Cache miss - pass through to segment resolution
|
|
437
|
+
yield* source;
|
|
438
|
+
if (ms) {
|
|
439
|
+
ms.metrics.push({
|
|
440
|
+
label: "pipeline:cache-lookup",
|
|
441
|
+
duration: performance.now() - pipelineStart,
|
|
442
|
+
startTime: pipelineStart - ms.requestStart,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Lookup cache
|
|
449
|
+
const cacheResult = await ctx.cacheScope.lookupRoute(
|
|
450
|
+
ctx.pathname,
|
|
451
|
+
ctx.matched.params,
|
|
452
|
+
ctx.isIntercept,
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
if (!cacheResult) {
|
|
456
|
+
// Cache miss - pass through to segment resolution
|
|
457
|
+
yield* source;
|
|
458
|
+
if (ms) {
|
|
459
|
+
ms.metrics.push({
|
|
460
|
+
label: "pipeline:cache-lookup",
|
|
461
|
+
duration: performance.now() - pipelineStart,
|
|
462
|
+
startTime: pipelineStart - ms.requestStart,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Cache HIT
|
|
469
|
+
state.cacheHit = true;
|
|
470
|
+
state.cacheSource = "runtime";
|
|
471
|
+
state.shouldRevalidate = cacheResult.shouldRevalidate;
|
|
472
|
+
state.cachedSegments = cacheResult.segments;
|
|
473
|
+
state.cachedMatchedIds = cacheResult.segments.map((s) => s.id);
|
|
474
|
+
|
|
475
|
+
// Apply revalidation to cached segments.
|
|
476
|
+
// For full matches or empty client segment sets, this map is unnecessary:
|
|
477
|
+
// we never run segment-level revalidation and can stream segments directly.
|
|
478
|
+
const canCheckSegmentRevalidation =
|
|
479
|
+
!ctx.isFullMatch &&
|
|
480
|
+
ctx.clientSegmentSet.size > 0 &&
|
|
481
|
+
!!buildEntryRevalidateMap;
|
|
482
|
+
const entryRevalidateMap = canCheckSegmentRevalidation
|
|
483
|
+
? buildEntryRevalidateMap(ctx.entries)
|
|
484
|
+
: undefined;
|
|
485
|
+
|
|
486
|
+
for (const segment of cacheResult.segments) {
|
|
487
|
+
// Skip segments client doesn't have - they need their component
|
|
488
|
+
if (!ctx.clientSegmentSet.has(segment.id)) {
|
|
489
|
+
if (isTraceActive()) {
|
|
490
|
+
pushRevalidationTraceEntry({
|
|
491
|
+
segmentId: segment.id,
|
|
492
|
+
segmentType: segment.type,
|
|
493
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
494
|
+
source: "cache-hit",
|
|
495
|
+
defaultShouldRevalidate: true,
|
|
496
|
+
finalShouldRevalidate: true,
|
|
497
|
+
reason: "new-segment",
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
yield segment;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Skip intercept segments - they're handled separately
|
|
505
|
+
if (segment.namespace?.startsWith("intercept:")) {
|
|
506
|
+
yield segment;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Look up revalidation rules for this segment
|
|
511
|
+
const entryInfo = entryRevalidateMap?.get(segment.id);
|
|
512
|
+
if (!entryInfo || entryInfo.revalidate.length === 0) {
|
|
513
|
+
// No revalidation rules, use default behavior (skip if client has)
|
|
514
|
+
if (isTraceActive()) {
|
|
515
|
+
pushRevalidationTraceEntry({
|
|
516
|
+
segmentId: segment.id,
|
|
517
|
+
segmentType: segment.type,
|
|
518
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
519
|
+
source: "cache-hit",
|
|
520
|
+
defaultShouldRevalidate: false,
|
|
521
|
+
finalShouldRevalidate: false,
|
|
522
|
+
reason: "cached-no-rules",
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
segment.component = null;
|
|
526
|
+
segment.loading = undefined;
|
|
527
|
+
yield segment;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Evaluate revalidation rules
|
|
532
|
+
const shouldRevalidate = await evaluateRevalidation({
|
|
533
|
+
segment,
|
|
534
|
+
prevParams: ctx.prevParams,
|
|
535
|
+
getPrevSegment: null,
|
|
536
|
+
request: ctx.request,
|
|
537
|
+
prevUrl: ctx.prevUrl,
|
|
538
|
+
nextUrl: ctx.url,
|
|
539
|
+
revalidations: entryInfo.revalidate.map((fn, i) => ({
|
|
540
|
+
name: `revalidate${i}`,
|
|
541
|
+
fn,
|
|
542
|
+
})),
|
|
543
|
+
routeKey: ctx.routeKey,
|
|
544
|
+
context: ctx.handlerContext,
|
|
545
|
+
actionContext: ctx.actionContext,
|
|
546
|
+
stale: cacheResult.shouldRevalidate || undefined,
|
|
547
|
+
traceSource: "cache-hit",
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const routerCtx = getRouterContext<TEnv>();
|
|
551
|
+
if (routerCtx.telemetry) {
|
|
552
|
+
const tSink = resolveSink(routerCtx.telemetry);
|
|
553
|
+
safeEmit(tSink, {
|
|
554
|
+
type: "revalidation.decision",
|
|
555
|
+
timestamp: performance.now(),
|
|
556
|
+
requestId: routerCtx.requestId,
|
|
557
|
+
segmentId: segment.id,
|
|
558
|
+
pathname: ctx.pathname,
|
|
559
|
+
routeKey: ctx.routeKey,
|
|
560
|
+
shouldRevalidate,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (!shouldRevalidate) {
|
|
565
|
+
// Client has it, no revalidation needed
|
|
566
|
+
segment.component = null;
|
|
567
|
+
segment.loading = undefined;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
yield segment;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Resolve loaders fresh (loaders are NOT cached by default)
|
|
574
|
+
// This ensures fresh data even on cache hit
|
|
575
|
+
const Store = ctx.Store;
|
|
576
|
+
|
|
577
|
+
if (ctx.isFullMatch) {
|
|
578
|
+
// Full match (document request) - simple loader resolution without revalidation
|
|
579
|
+
if (resolveLoadersOnly) {
|
|
580
|
+
const loaderSegments = await Store.run(() =>
|
|
581
|
+
resolveLoadersOnly(ctx.entries, ctx.handlerContext),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// Update state - full match doesn't track matchedIds separately
|
|
585
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
586
|
+
|
|
587
|
+
// Yield fresh loader segments
|
|
588
|
+
for (const segment of loaderSegments) {
|
|
589
|
+
yield segment;
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
// Partial match (navigation) - loader resolution with revalidation
|
|
596
|
+
if (resolveLoadersOnlyWithRevalidation) {
|
|
597
|
+
const loaderResult = await Store.run(() =>
|
|
598
|
+
resolveLoadersOnlyWithRevalidation(
|
|
599
|
+
ctx.entries,
|
|
600
|
+
ctx.handlerContext,
|
|
601
|
+
ctx.clientSegmentSet,
|
|
602
|
+
ctx.prevParams,
|
|
603
|
+
ctx.request,
|
|
604
|
+
ctx.prevUrl,
|
|
605
|
+
ctx.url,
|
|
606
|
+
ctx.routeKey,
|
|
607
|
+
ctx.actionContext,
|
|
608
|
+
cacheResult.shouldRevalidate || undefined,
|
|
609
|
+
),
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Update state with fresh loader matchedIds
|
|
613
|
+
state.matchedIds = [
|
|
614
|
+
...state.cachedMatchedIds!,
|
|
615
|
+
...loaderResult.matchedIds,
|
|
616
|
+
];
|
|
617
|
+
|
|
618
|
+
// Yield fresh loader segments
|
|
619
|
+
for (const segment of loaderResult.segments) {
|
|
620
|
+
yield segment;
|
|
621
|
+
}
|
|
622
|
+
} else {
|
|
623
|
+
state.matchedIds = state.cachedMatchedIds!;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (ms) {
|
|
627
|
+
ms.metrics.push({
|
|
628
|
+
label: "pipeline:cache-lookup",
|
|
629
|
+
duration: performance.now() - pipelineStart,
|
|
630
|
+
startTime: pipelineStart - ms.requestStart,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
}
|