@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30
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 +883 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4655 -747
- package/package.json +78 -50
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +54 -25
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +23 -21
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +390 -63
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +133 -10
- package/skills/layout/SKILL.md +102 -5
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +366 -29
- package/skills/middleware/SKILL.md +173 -36
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +80 -3
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +86 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +227 -14
- package/skills/router-setup/SKILL.md +225 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +12 -11
- package/skills/typesafety/SKILL.md +401 -75
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +10 -4
- 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 +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +20 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +201 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +267 -317
- 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 +173 -73
- package/src/browser/react/NavigationProvider.tsx +138 -27
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- 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 +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +49 -65
- package/src/browser/react/use-href.tsx +20 -188
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +27 -78
- 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 +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +111 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +504 -584
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +92 -57
- 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 +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +191 -13
- 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 +72 -122
- package/src/client.rsc.tsx +10 -15
- package/src/client.tsx +114 -135
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +34 -19
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/meta.ts +30 -13
- 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 +135 -49
- package/src/index.rsc.ts +182 -17
- package/src/index.ts +238 -24
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +27 -142
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- 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 +41 -29
- package/src/route-content-wrapper.tsx +9 -11
- 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 -1388
- package/src/route-map-builder.ts +241 -112
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +70 -9
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +371 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +155 -32
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -29
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +321 -30
- package/src/router/prerender-match.ts +400 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- 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 +1241 -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 +77 -3
- package/src/router.ts +688 -3656
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +786 -760
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +5 -25
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -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 +40 -14
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +57 -61
- package/src/server/context.ts +202 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +422 -70
- package/src/server.ts +36 -120
- package/src/ssr/index.tsx +157 -26
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- 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 -1577
- 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 -726
- package/src/use-loader.tsx +85 -77
- 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 +11 -782
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- 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/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
- 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/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -3
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -357
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -92,12 +92,178 @@
|
|
|
92
92
|
import type { ResolvedSegment } from "../../types.js";
|
|
93
93
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
94
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
|
+
}
|
|
95
261
|
|
|
96
262
|
/**
|
|
97
263
|
* Async generator middleware type
|
|
98
264
|
*/
|
|
99
265
|
export type GeneratorMiddleware<T> = (
|
|
100
|
-
source: AsyncGenerator<T
|
|
266
|
+
source: AsyncGenerator<T>,
|
|
101
267
|
) => AsyncGenerator<T>;
|
|
102
268
|
|
|
103
269
|
/**
|
|
@@ -115,11 +281,21 @@ export type GeneratorMiddleware<T> = (
|
|
|
115
281
|
*/
|
|
116
282
|
export function withCacheLookup<TEnv>(
|
|
117
283
|
ctx: MatchContext<TEnv>,
|
|
118
|
-
state: MatchPipelineState
|
|
284
|
+
state: MatchPipelineState,
|
|
119
285
|
): GeneratorMiddleware<ResolvedSegment> {
|
|
120
286
|
return async function* (
|
|
121
|
-
source: AsyncGenerator<ResolvedSegment
|
|
287
|
+
source: AsyncGenerator<ResolvedSegment>,
|
|
122
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
|
+
|
|
123
299
|
const {
|
|
124
300
|
evaluateRevalidation,
|
|
125
301
|
buildEntryRevalidateMap,
|
|
@@ -127,10 +303,145 @@ export function withCacheLookup<TEnv>(
|
|
|
127
303
|
resolveLoadersOnly,
|
|
128
304
|
} = getRouterContext<TEnv>();
|
|
129
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
|
+
|
|
130
434
|
// Skip cache during actions
|
|
131
435
|
if (ctx.isAction || !ctx.cacheScope?.enabled) {
|
|
132
436
|
// Cache miss - pass through to segment resolution
|
|
133
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
|
+
}
|
|
134
445
|
return;
|
|
135
446
|
}
|
|
136
447
|
|
|
@@ -138,27 +449,54 @@ export function withCacheLookup<TEnv>(
|
|
|
138
449
|
const cacheResult = await ctx.cacheScope.lookupRoute(
|
|
139
450
|
ctx.pathname,
|
|
140
451
|
ctx.matched.params,
|
|
141
|
-
ctx.isIntercept
|
|
452
|
+
ctx.isIntercept,
|
|
142
453
|
);
|
|
143
454
|
|
|
144
455
|
if (!cacheResult) {
|
|
145
456
|
// Cache miss - pass through to segment resolution
|
|
146
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
|
+
}
|
|
147
465
|
return;
|
|
148
466
|
}
|
|
149
467
|
|
|
150
468
|
// Cache HIT
|
|
151
469
|
state.cacheHit = true;
|
|
470
|
+
state.cacheSource = "runtime";
|
|
152
471
|
state.shouldRevalidate = cacheResult.shouldRevalidate;
|
|
153
472
|
state.cachedSegments = cacheResult.segments;
|
|
154
473
|
state.cachedMatchedIds = cacheResult.segments.map((s) => s.id);
|
|
155
474
|
|
|
156
|
-
// Apply revalidation to cached segments
|
|
157
|
-
|
|
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;
|
|
158
485
|
|
|
159
486
|
for (const segment of cacheResult.segments) {
|
|
160
487
|
// Skip segments client doesn't have - they need their component
|
|
161
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
|
+
}
|
|
162
500
|
yield segment;
|
|
163
501
|
continue;
|
|
164
502
|
}
|
|
@@ -173,6 +511,17 @@ export function withCacheLookup<TEnv>(
|
|
|
173
511
|
const entryInfo = entryRevalidateMap?.get(segment.id);
|
|
174
512
|
if (!entryInfo || entryInfo.revalidate.length === 0) {
|
|
175
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
|
+
}
|
|
176
525
|
segment.component = null;
|
|
177
526
|
segment.loading = undefined;
|
|
178
527
|
yield segment;
|
|
@@ -194,8 +543,24 @@ export function withCacheLookup<TEnv>(
|
|
|
194
543
|
routeKey: ctx.routeKey,
|
|
195
544
|
context: ctx.handlerContext,
|
|
196
545
|
actionContext: ctx.actionContext,
|
|
546
|
+
stale: cacheResult.shouldRevalidate || undefined,
|
|
547
|
+
traceSource: "cache-hit",
|
|
197
548
|
});
|
|
198
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
|
+
|
|
199
564
|
if (!shouldRevalidate) {
|
|
200
565
|
// Client has it, no revalidation needed
|
|
201
566
|
segment.component = null;
|
|
@@ -213,7 +578,7 @@ export function withCacheLookup<TEnv>(
|
|
|
213
578
|
// Full match (document request) - simple loader resolution without revalidation
|
|
214
579
|
if (resolveLoadersOnly) {
|
|
215
580
|
const loaderSegments = await Store.run(() =>
|
|
216
|
-
resolveLoadersOnly(ctx.entries, ctx.handlerContext)
|
|
581
|
+
resolveLoadersOnly(ctx.entries, ctx.handlerContext),
|
|
217
582
|
);
|
|
218
583
|
|
|
219
584
|
// Update state - full match doesn't track matchedIds separately
|
|
@@ -239,8 +604,9 @@ export function withCacheLookup<TEnv>(
|
|
|
239
604
|
ctx.prevUrl,
|
|
240
605
|
ctx.url,
|
|
241
606
|
ctx.routeKey,
|
|
242
|
-
ctx.actionContext
|
|
243
|
-
|
|
607
|
+
ctx.actionContext,
|
|
608
|
+
cacheResult.shouldRevalidate || undefined,
|
|
609
|
+
),
|
|
244
610
|
);
|
|
245
611
|
|
|
246
612
|
// Update state with fresh loader matchedIds
|
|
@@ -257,5 +623,12 @@ export function withCacheLookup<TEnv>(
|
|
|
257
623
|
state.matchedIds = state.cachedMatchedIds!;
|
|
258
624
|
}
|
|
259
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
|
+
}
|
|
260
633
|
};
|
|
261
634
|
}
|
|
@@ -104,6 +104,7 @@ import type { ResolvedSegment } from "../../types.js";
|
|
|
104
104
|
import { getRequestContext } from "../../server/request-context.js";
|
|
105
105
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
106
106
|
import { getRouterContext } from "../router-context.js";
|
|
107
|
+
import { debugLog, debugWarn } from "../logging.js";
|
|
107
108
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
108
109
|
|
|
109
110
|
/**
|
|
@@ -119,6 +120,9 @@ export function withCacheStore<TEnv>(
|
|
|
119
120
|
return async function* (
|
|
120
121
|
source: AsyncGenerator<ResolvedSegment>,
|
|
121
122
|
): AsyncGenerator<ResolvedSegment> {
|
|
123
|
+
const pipelineStart = performance.now();
|
|
124
|
+
const ms = ctx.metricsStore;
|
|
125
|
+
|
|
122
126
|
// Collect all segments while passing them through
|
|
123
127
|
const allSegments: ResolvedSegment[] = [];
|
|
124
128
|
for await (const segment of source) {
|
|
@@ -137,14 +141,22 @@ export function withCacheStore<TEnv>(
|
|
|
137
141
|
state.cacheHit ||
|
|
138
142
|
ctx.request.method !== "GET"
|
|
139
143
|
) {
|
|
144
|
+
if (ms) {
|
|
145
|
+
ms.metrics.push({
|
|
146
|
+
label: "pipeline:cache-store",
|
|
147
|
+
duration: performance.now() - pipelineStart,
|
|
148
|
+
startTime: pipelineStart - ms.requestStart,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
140
151
|
return;
|
|
141
152
|
}
|
|
142
153
|
|
|
143
154
|
const {
|
|
144
155
|
createHandlerContext,
|
|
145
|
-
|
|
156
|
+
setupLoaderAccess,
|
|
146
157
|
resolveAllSegments,
|
|
147
158
|
resolveInterceptEntry,
|
|
159
|
+
createHandleStore,
|
|
148
160
|
} = getRouterContext<TEnv>();
|
|
149
161
|
|
|
150
162
|
// Combine main segments with intercept segments
|
|
@@ -166,9 +178,10 @@ export function withCacheStore<TEnv>(
|
|
|
166
178
|
requestCtx.onResponse((response) => {
|
|
167
179
|
// Only cache successful responses
|
|
168
180
|
if (response.status !== 200) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
debugLog("cacheStore", "skipping cache for non-200 response", {
|
|
182
|
+
status: response.status,
|
|
183
|
+
pathname: ctx.pathname,
|
|
184
|
+
});
|
|
172
185
|
return response;
|
|
173
186
|
}
|
|
174
187
|
|
|
@@ -176,29 +189,34 @@ export function withCacheStore<TEnv>(
|
|
|
176
189
|
// Proactive caching: render all segments fresh in background
|
|
177
190
|
// This ensures cache has complete components for future requests
|
|
178
191
|
requestCtx.waitUntil(async () => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
192
|
+
debugLog("cacheStore", "proactive caching started", {
|
|
193
|
+
pathname: ctx.pathname,
|
|
194
|
+
});
|
|
195
|
+
// Swap to a fresh HandleStore so handle.push() calls from
|
|
196
|
+
// proactive resolution are captured (not silenced). The original
|
|
197
|
+
// store's stream is already sent by waitUntil time.
|
|
198
|
+
// cacheRoute reads from requestCtx._handleStore, so this ensures
|
|
199
|
+
// complete handle data (e.g. breadcrumbs) is cached.
|
|
200
|
+
const originalHandleStore = requestCtx._handleStore;
|
|
201
|
+
requestCtx._handleStore = createHandleStore();
|
|
182
202
|
try {
|
|
183
203
|
// Create fresh context for proactive caching
|
|
184
|
-
// This prevents handle data from polluting the response stream
|
|
185
204
|
const proactiveHandlerContext = createHandlerContext(
|
|
186
205
|
ctx.matched.params,
|
|
187
206
|
ctx.request,
|
|
188
207
|
ctx.url.searchParams,
|
|
189
208
|
ctx.pathname,
|
|
190
209
|
ctx.url,
|
|
191
|
-
ctx.
|
|
210
|
+
ctx.env,
|
|
192
211
|
ctx.routeMap,
|
|
193
|
-
ctx.matched.routeKey
|
|
212
|
+
ctx.matched.routeKey,
|
|
213
|
+
ctx.matched.responseType,
|
|
214
|
+
ctx.matched.pt === true,
|
|
194
215
|
);
|
|
195
216
|
const proactiveLoaderPromises = new Map<string, Promise<any>>();
|
|
196
217
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
proactiveHandlerContext,
|
|
200
|
-
proactiveLoaderPromises,
|
|
201
|
-
);
|
|
218
|
+
// Use normal loader access so handle data is captured
|
|
219
|
+
setupLoaderAccess(proactiveHandlerContext, proactiveLoaderPromises);
|
|
202
220
|
|
|
203
221
|
// Re-resolve ALL segments without revalidation
|
|
204
222
|
const Store = ctx.Store;
|
|
@@ -231,20 +249,23 @@ export function withCacheStore<TEnv>(
|
|
|
231
249
|
...freshSegments,
|
|
232
250
|
...freshInterceptSegments,
|
|
233
251
|
];
|
|
252
|
+
requestCtx._handleStore.seal();
|
|
234
253
|
await cacheScope.cacheRoute(
|
|
235
254
|
ctx.pathname,
|
|
236
255
|
ctx.matched.params,
|
|
237
256
|
completeSegments,
|
|
238
257
|
ctx.isIntercept,
|
|
239
258
|
);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
);
|
|
259
|
+
debugLog("cacheStore", "proactive caching complete", {
|
|
260
|
+
pathname: ctx.pathname,
|
|
261
|
+
});
|
|
243
262
|
} catch (error) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
error,
|
|
247
|
-
);
|
|
263
|
+
debugWarn("cacheStore", "proactive caching failed", {
|
|
264
|
+
pathname: ctx.pathname,
|
|
265
|
+
error: String(error),
|
|
266
|
+
});
|
|
267
|
+
} finally {
|
|
268
|
+
requestCtx._handleStore = originalHandleStore;
|
|
248
269
|
}
|
|
249
270
|
});
|
|
250
271
|
} else {
|
|
@@ -262,5 +283,13 @@ export function withCacheStore<TEnv>(
|
|
|
262
283
|
|
|
263
284
|
return response;
|
|
264
285
|
});
|
|
286
|
+
|
|
287
|
+
if (ms) {
|
|
288
|
+
ms.metrics.push({
|
|
289
|
+
label: "pipeline:cache-store",
|
|
290
|
+
duration: performance.now() - pipelineStart,
|
|
291
|
+
startTime: pipelineStart - ms.requestStart,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
265
294
|
};
|
|
266
295
|
}
|