@rangojs/router 0.0.0-experimental.002d056c
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 +9 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +5153 -0
- package/package.json +177 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +253 -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 +638 -0
- package/src/browser/navigation-client.ts +261 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +582 -0
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +368 -0
- package/src/browser/react/NavigationProvider.tsx +413 -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 +464 -0
- package/src/browser/scroll-restoration.ts +397 -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 +547 -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 +479 -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 +982 -0
- package/src/cache/cf/index.ts +29 -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 +44 -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 +281 -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 +160 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +269 -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 +193 -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 +749 -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 +320 -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 +1242 -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 +291 -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 +1006 -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 +237 -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 +920 -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 +109 -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 +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +48 -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 +363 -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 +266 -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 +445 -0
- package/src/vite/router-discovery.ts +777 -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,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Revalidation Logic
|
|
3
|
+
*
|
|
4
|
+
* Evaluates whether segments should revalidate based on params, actions, and custom functions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ResolvedSegment, HandlerContext } from "../types";
|
|
8
|
+
import type { ActionContext } from "./types";
|
|
9
|
+
import {
|
|
10
|
+
debugLog,
|
|
11
|
+
pushRevalidationTraceEntry,
|
|
12
|
+
isTraceActive,
|
|
13
|
+
} from "./logging.js";
|
|
14
|
+
import type { RevalidationTraceEntry } from "./logging.js";
|
|
15
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
16
|
+
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
17
|
+
|
|
18
|
+
function paramsEqual(
|
|
19
|
+
a: Record<string, string>,
|
|
20
|
+
b: Record<string, string>,
|
|
21
|
+
): boolean {
|
|
22
|
+
if (a === b) return true;
|
|
23
|
+
|
|
24
|
+
const keysA = Object.keys(a);
|
|
25
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
26
|
+
|
|
27
|
+
for (const key of keysA) {
|
|
28
|
+
if (a[key] !== b[key]) return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Options for revalidation evaluation
|
|
36
|
+
*/
|
|
37
|
+
interface EvaluateRevalidationOptions<TEnv> {
|
|
38
|
+
/** Current segment to evaluate */
|
|
39
|
+
segment: ResolvedSegment;
|
|
40
|
+
/** Previous route params (from route match, not segment) */
|
|
41
|
+
prevParams: Record<string, string>;
|
|
42
|
+
/** Lazy function to get previous segment if needed */
|
|
43
|
+
getPrevSegment: (() => Promise<ResolvedSegment | undefined>) | null;
|
|
44
|
+
/** Current request */
|
|
45
|
+
request: Request;
|
|
46
|
+
/** Previous URL */
|
|
47
|
+
prevUrl: URL;
|
|
48
|
+
/** Next URL */
|
|
49
|
+
nextUrl: URL;
|
|
50
|
+
/** Custom revalidation functions */
|
|
51
|
+
revalidations: Array<{ name: string; fn: any }>;
|
|
52
|
+
/** Current route key */
|
|
53
|
+
routeKey: string;
|
|
54
|
+
/** Handler context */
|
|
55
|
+
context: HandlerContext<any, TEnv>;
|
|
56
|
+
/** Action context if triggered by action */
|
|
57
|
+
actionContext?: ActionContext;
|
|
58
|
+
/** If true, this is a stale cache revalidation request */
|
|
59
|
+
stale?: boolean;
|
|
60
|
+
/** Trace source hint for the revalidation trace */
|
|
61
|
+
traceSource?: RevalidationTraceEntry["source"];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Evaluate if a segment should revalidate using soft/hard decision pattern
|
|
66
|
+
* Optimized to use prevParams directly and avoid building previous segments
|
|
67
|
+
*/
|
|
68
|
+
export async function evaluateRevalidation<TEnv>(
|
|
69
|
+
options: EvaluateRevalidationOptions<TEnv>,
|
|
70
|
+
): Promise<boolean> {
|
|
71
|
+
const {
|
|
72
|
+
segment,
|
|
73
|
+
prevParams,
|
|
74
|
+
getPrevSegment,
|
|
75
|
+
request,
|
|
76
|
+
prevUrl,
|
|
77
|
+
nextUrl,
|
|
78
|
+
revalidations,
|
|
79
|
+
routeKey,
|
|
80
|
+
context,
|
|
81
|
+
actionContext,
|
|
82
|
+
stale,
|
|
83
|
+
traceSource,
|
|
84
|
+
} = options;
|
|
85
|
+
const nextParams = segment.params || {};
|
|
86
|
+
const paramsChanged = !paramsEqual(nextParams, prevParams);
|
|
87
|
+
const searchChanged = prevUrl.search !== nextUrl.search;
|
|
88
|
+
|
|
89
|
+
// Trace helper: push a structured entry to the request-scoped trace buffer.
|
|
90
|
+
// Guarded by isTraceActive() so object construction is skipped in production.
|
|
91
|
+
function pushTrace(
|
|
92
|
+
defaultVal: boolean,
|
|
93
|
+
finalVal: boolean,
|
|
94
|
+
reason: string,
|
|
95
|
+
): void {
|
|
96
|
+
if (!isTraceActive()) return;
|
|
97
|
+
pushRevalidationTraceEntry({
|
|
98
|
+
segmentId: segment.id,
|
|
99
|
+
segmentType: segment.type,
|
|
100
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
101
|
+
source: traceSource ?? "segment-resolution",
|
|
102
|
+
defaultShouldRevalidate: defaultVal,
|
|
103
|
+
finalShouldRevalidate: finalVal,
|
|
104
|
+
reason,
|
|
105
|
+
customRevalidators: revalidations.length || undefined,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Calculate default revalidation based on segment type and request method
|
|
110
|
+
let defaultShouldRevalidate: boolean;
|
|
111
|
+
let defaultReason: string;
|
|
112
|
+
|
|
113
|
+
if (request.method === "POST") {
|
|
114
|
+
// Actions: revalidate segments that belong to the route, skip parent chain
|
|
115
|
+
if (segment.type === "route") {
|
|
116
|
+
// Route segment always revalidates on actions
|
|
117
|
+
defaultShouldRevalidate = true;
|
|
118
|
+
defaultReason = "action:route-segment";
|
|
119
|
+
} else if (segment.type === "loader") {
|
|
120
|
+
// Loaders always revalidate on actions - they often contain action-sensitive data
|
|
121
|
+
// (e.g., cart count after add-to-cart action)
|
|
122
|
+
defaultShouldRevalidate = true;
|
|
123
|
+
defaultReason = "action:loader-segment";
|
|
124
|
+
} else if (segment.belongsToRoute) {
|
|
125
|
+
// Segment belongs to route (orphan layouts/parallels) - revalidate
|
|
126
|
+
defaultShouldRevalidate = true;
|
|
127
|
+
defaultReason = "action:belongs-to-route";
|
|
128
|
+
} else {
|
|
129
|
+
// Parent chain segment (shared layouts/parallels) - don't revalidate
|
|
130
|
+
defaultShouldRevalidate = false;
|
|
131
|
+
defaultReason = "action:parent-chain-skip";
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// Navigation (GET): Conservative defaults to minimize unnecessary revalidations
|
|
135
|
+
// Only the route segment revalidates by default - all others require explicit opt-in
|
|
136
|
+
|
|
137
|
+
if (segment.type === "route") {
|
|
138
|
+
// Route segments revalidate when path params OR search params change.
|
|
139
|
+
// Search params (e.g., ?page=2&sort=price) are server-parsed via ctx.search,
|
|
140
|
+
// so the handler must re-execute to produce updated content.
|
|
141
|
+
const routeChanged = paramsChanged || searchChanged;
|
|
142
|
+
defaultShouldRevalidate = routeChanged;
|
|
143
|
+
defaultReason = paramsChanged
|
|
144
|
+
? "nav:params-changed"
|
|
145
|
+
: searchChanged
|
|
146
|
+
? "nav:search-changed"
|
|
147
|
+
: "nav:params-unchanged";
|
|
148
|
+
if (routeChanged) {
|
|
149
|
+
debugLog("revalidation", "route revalidating", {
|
|
150
|
+
segmentId: segment.id,
|
|
151
|
+
paramsChanged,
|
|
152
|
+
searchChanged,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
} else if (segment.belongsToRoute && (paramsChanged || searchChanged)) {
|
|
156
|
+
// Children of the route path (loaders, orphan layouts/parallels)
|
|
157
|
+
// revalidate when path params or search params change
|
|
158
|
+
defaultShouldRevalidate = true;
|
|
159
|
+
defaultReason = paramsChanged
|
|
160
|
+
? "nav:route-child-params-changed"
|
|
161
|
+
: "nav:route-child-search-changed";
|
|
162
|
+
debugLog("revalidation", "route child revalidating", {
|
|
163
|
+
segmentId: segment.id,
|
|
164
|
+
segmentType: segment.type,
|
|
165
|
+
paramsChanged,
|
|
166
|
+
searchChanged,
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
// Parent layouts and parallels default to no revalidation
|
|
170
|
+
// Cannot assume these segments depend on params without explicit declaration
|
|
171
|
+
// Use custom revalidation functions to opt-in when needed
|
|
172
|
+
defaultShouldRevalidate = false;
|
|
173
|
+
defaultReason = "nav:non-route-skip";
|
|
174
|
+
debugLog("revalidation", "non-route segment skipped by default", {
|
|
175
|
+
segmentId: segment.id,
|
|
176
|
+
segmentType: segment.type,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// No custom revalidations defined - return default behavior without prev segment
|
|
182
|
+
if (revalidations.length === 0) {
|
|
183
|
+
if (defaultShouldRevalidate) {
|
|
184
|
+
debugLog("revalidation", "default revalidate=true", {
|
|
185
|
+
segmentId: segment.id,
|
|
186
|
+
prevParams,
|
|
187
|
+
nextParams,
|
|
188
|
+
});
|
|
189
|
+
} else {
|
|
190
|
+
debugLog("revalidation", "default revalidate=false", {
|
|
191
|
+
segmentId: segment.id,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
pushTrace(defaultShouldRevalidate, defaultShouldRevalidate, defaultReason);
|
|
195
|
+
return defaultShouldRevalidate;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Custom revalidations exist - may need full prev segment
|
|
199
|
+
// Lazy load prev segment only if getPrevSegment provided
|
|
200
|
+
const prevSegment = getPrevSegment ? await getPrevSegment() : null;
|
|
201
|
+
|
|
202
|
+
// Execute revalidation functions with soft/hard decision pattern
|
|
203
|
+
let currentSuggestion = defaultShouldRevalidate;
|
|
204
|
+
|
|
205
|
+
// Compute public route names (filtered: undefined for auto-generated routes)
|
|
206
|
+
const toRouteName =
|
|
207
|
+
routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
|
|
208
|
+
const reqCtx = _getRequestContext();
|
|
209
|
+
const prevRouteKey = reqCtx?._prevRouteKey;
|
|
210
|
+
const fromRouteName =
|
|
211
|
+
prevRouteKey && !isAutoGeneratedRouteName(prevRouteKey)
|
|
212
|
+
? prevRouteKey
|
|
213
|
+
: undefined;
|
|
214
|
+
|
|
215
|
+
for (const { name, fn } of revalidations) {
|
|
216
|
+
const result = fn({
|
|
217
|
+
currentParams: prevSegment?.params || prevParams, // Use segment params if available, else route params
|
|
218
|
+
currentUrl: prevUrl,
|
|
219
|
+
nextParams,
|
|
220
|
+
nextUrl,
|
|
221
|
+
defaultShouldRevalidate: currentSuggestion,
|
|
222
|
+
context,
|
|
223
|
+
// Segment metadata (which segment is being evaluated)
|
|
224
|
+
segmentType: segment.type,
|
|
225
|
+
layoutName: segment.layoutName,
|
|
226
|
+
slotName: segment.slot,
|
|
227
|
+
// Action context (only populated when triggered by server action)
|
|
228
|
+
actionId: actionContext?.actionId,
|
|
229
|
+
actionUrl: actionContext?.actionUrl,
|
|
230
|
+
actionResult: actionContext?.actionResult,
|
|
231
|
+
formData: actionContext?.formData,
|
|
232
|
+
method: request.method, // GET for navigation, POST for actions
|
|
233
|
+
routeName: toRouteName, // Navigation target route name (filtered)
|
|
234
|
+
fromRouteName, // Navigation source route name (filtered)
|
|
235
|
+
toRouteName, // Navigation target route name (filtered)
|
|
236
|
+
// Stale cache context (only true for background revalidation after stale cache render)
|
|
237
|
+
stale,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Check return type:
|
|
241
|
+
// - boolean: hard decision, short-circuit immediately
|
|
242
|
+
// - { defaultShouldRevalidate: boolean }: soft decision, update suggestion and continue
|
|
243
|
+
// - null/undefined: use default behavior (equivalent to returning { defaultShouldRevalidate })
|
|
244
|
+
if (typeof result === "boolean") {
|
|
245
|
+
// Hard decision - short-circuit
|
|
246
|
+
debugLog("revalidation", "hard decision", {
|
|
247
|
+
segmentId: segment.id,
|
|
248
|
+
revalidator: name,
|
|
249
|
+
revalidate: result,
|
|
250
|
+
});
|
|
251
|
+
pushTrace(defaultShouldRevalidate, result, `hard:${name}`);
|
|
252
|
+
return result;
|
|
253
|
+
} else if (
|
|
254
|
+
result &&
|
|
255
|
+
typeof result === "object" &&
|
|
256
|
+
"defaultShouldRevalidate" in result
|
|
257
|
+
) {
|
|
258
|
+
// Soft decision - update suggestion and continue
|
|
259
|
+
currentSuggestion = result.defaultShouldRevalidate;
|
|
260
|
+
debugLog("revalidation", "soft decision", {
|
|
261
|
+
segmentId: segment.id,
|
|
262
|
+
revalidator: name,
|
|
263
|
+
revalidate: currentSuggestion,
|
|
264
|
+
});
|
|
265
|
+
} else if (result === null || result === undefined) {
|
|
266
|
+
// Defer to default - equivalent to { defaultShouldRevalidate: currentSuggestion }
|
|
267
|
+
// This means "I don't care, use whatever the default is"
|
|
268
|
+
debugLog("revalidation", "deferred to current default", {
|
|
269
|
+
segmentId: segment.id,
|
|
270
|
+
revalidator: name,
|
|
271
|
+
revalidate: currentSuggestion,
|
|
272
|
+
});
|
|
273
|
+
// currentSuggestion stays the same, continue to next function
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// All revalidators completed - use final suggestion
|
|
278
|
+
debugLog("revalidation", "final decision", {
|
|
279
|
+
segmentId: segment.id,
|
|
280
|
+
revalidate: currentSuggestion,
|
|
281
|
+
});
|
|
282
|
+
const softNames = revalidations.map((r) => r.name).join(",");
|
|
283
|
+
pushTrace(
|
|
284
|
+
defaultShouldRevalidate,
|
|
285
|
+
currentSuggestion,
|
|
286
|
+
`soft-chain:${softNames}`,
|
|
287
|
+
);
|
|
288
|
+
return currentSuggestion;
|
|
289
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Context using AsyncLocalStorage
|
|
3
|
+
*
|
|
4
|
+
* Provides clean dependency injection for router middleware without parameter drilling.
|
|
5
|
+
* All closure functions from createRouter() are made available via getRouterContext().
|
|
6
|
+
*/
|
|
7
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
8
|
+
import type { CacheScope } from "../cache/cache-scope.js";
|
|
9
|
+
import type {
|
|
10
|
+
EntryData,
|
|
11
|
+
InterceptEntry,
|
|
12
|
+
InterceptSelectorContext,
|
|
13
|
+
MetricsStore,
|
|
14
|
+
} from "../server/context.js";
|
|
15
|
+
import type {
|
|
16
|
+
HandlerContext,
|
|
17
|
+
ResolvedSegment,
|
|
18
|
+
ShouldRevalidateFn,
|
|
19
|
+
} from "../types.js";
|
|
20
|
+
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
21
|
+
import type { TelemetrySink } from "./telemetry.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Revalidation context passed to segment resolution
|
|
25
|
+
*/
|
|
26
|
+
export interface RevalidationContext {
|
|
27
|
+
clientSegmentIds: Set<string>;
|
|
28
|
+
prevParams: Record<string, string>;
|
|
29
|
+
request: Request;
|
|
30
|
+
prevUrl: URL;
|
|
31
|
+
nextUrl: URL;
|
|
32
|
+
routeKey: string;
|
|
33
|
+
actionContext?: {
|
|
34
|
+
actionId?: string;
|
|
35
|
+
actionUrl?: URL;
|
|
36
|
+
actionResult?: any;
|
|
37
|
+
formData?: FormData;
|
|
38
|
+
};
|
|
39
|
+
stale: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Result from intercept lookup
|
|
44
|
+
*/
|
|
45
|
+
export interface InterceptResult {
|
|
46
|
+
intercept: InterceptEntry;
|
|
47
|
+
entry: EntryData;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Router context available via AsyncLocalStorage
|
|
52
|
+
*
|
|
53
|
+
* Contains closure functions from createRouter() that middleware needs access to.
|
|
54
|
+
* Instead of passing 20+ parameters, middleware calls getRouterContext() to access them.
|
|
55
|
+
*/
|
|
56
|
+
export interface RouterContext<TEnv = any> {
|
|
57
|
+
// Route matching
|
|
58
|
+
findMatch: (pathname: string) => RouteMatchResult | null;
|
|
59
|
+
|
|
60
|
+
// Manifest loading
|
|
61
|
+
loadManifest: (
|
|
62
|
+
entry: any,
|
|
63
|
+
routeKey: string,
|
|
64
|
+
pathname: string,
|
|
65
|
+
metricsStore?: MetricsStore,
|
|
66
|
+
isSSR?: boolean,
|
|
67
|
+
) => Promise<EntryData>;
|
|
68
|
+
|
|
69
|
+
// Entry traversal
|
|
70
|
+
traverseBack: (entry: EntryData) => Generator<EntryData>;
|
|
71
|
+
|
|
72
|
+
// Handler context creation
|
|
73
|
+
createHandlerContext: (
|
|
74
|
+
params: Record<string, string>,
|
|
75
|
+
request: Request,
|
|
76
|
+
searchParams: URLSearchParams,
|
|
77
|
+
pathname: string,
|
|
78
|
+
url: URL,
|
|
79
|
+
bindings?: any,
|
|
80
|
+
routeMap?: Record<string, string>,
|
|
81
|
+
routeName?: string,
|
|
82
|
+
responseType?: string,
|
|
83
|
+
isPassthroughRoute?: boolean,
|
|
84
|
+
) => HandlerContext<any, TEnv>;
|
|
85
|
+
|
|
86
|
+
// Loader setup
|
|
87
|
+
setupLoaderAccess: (
|
|
88
|
+
ctx: HandlerContext<any, TEnv>,
|
|
89
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
90
|
+
) => void;
|
|
91
|
+
|
|
92
|
+
setupLoaderAccessSilent: (
|
|
93
|
+
ctx: HandlerContext<any, TEnv>,
|
|
94
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
95
|
+
) => void;
|
|
96
|
+
|
|
97
|
+
// Context access
|
|
98
|
+
getContext: () => {
|
|
99
|
+
getOrCreateStore: (key: string) => any;
|
|
100
|
+
runWithStore: <T>(
|
|
101
|
+
store: any,
|
|
102
|
+
namespace: string,
|
|
103
|
+
parent: any,
|
|
104
|
+
fn: () => T,
|
|
105
|
+
) => T;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Metrics
|
|
109
|
+
getMetricsStore: () => MetricsStore | undefined;
|
|
110
|
+
|
|
111
|
+
// Cache
|
|
112
|
+
createCacheScope: (
|
|
113
|
+
cacheConfig: any,
|
|
114
|
+
parent: CacheScope | null,
|
|
115
|
+
) => CacheScope | null;
|
|
116
|
+
|
|
117
|
+
// Intercept detection
|
|
118
|
+
findInterceptForRoute: (
|
|
119
|
+
routeKey: string,
|
|
120
|
+
parentEntry: EntryData | null,
|
|
121
|
+
selectorContext: InterceptSelectorContext,
|
|
122
|
+
isAction: boolean,
|
|
123
|
+
) => InterceptResult | null;
|
|
124
|
+
|
|
125
|
+
// Segment resolution (with revalidation)
|
|
126
|
+
resolveAllSegmentsWithRevalidation: (
|
|
127
|
+
entries: EntryData[],
|
|
128
|
+
routeKey: string,
|
|
129
|
+
params: Record<string, string>,
|
|
130
|
+
handlerContext: HandlerContext<any, TEnv>,
|
|
131
|
+
clientSegmentSet: Set<string>,
|
|
132
|
+
prevParams: Record<string, string>,
|
|
133
|
+
request: Request,
|
|
134
|
+
prevUrl: URL,
|
|
135
|
+
nextUrl: URL,
|
|
136
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
137
|
+
actionContext: any | undefined,
|
|
138
|
+
interceptResult: InterceptResult | null,
|
|
139
|
+
localRouteName: string,
|
|
140
|
+
pathname: string,
|
|
141
|
+
stale?: boolean,
|
|
142
|
+
) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
|
|
143
|
+
|
|
144
|
+
// Generator-based segment resolution (for pipeline)
|
|
145
|
+
resolveAllSegmentsWithRevalidationGenerator?: (
|
|
146
|
+
entries: EntryData[],
|
|
147
|
+
routeKey: string,
|
|
148
|
+
params: Record<string, string>,
|
|
149
|
+
handlerContext: HandlerContext<any, TEnv>,
|
|
150
|
+
clientSegmentSet: Set<string>,
|
|
151
|
+
prevParams: Record<string, string>,
|
|
152
|
+
request: Request,
|
|
153
|
+
prevUrl: URL,
|
|
154
|
+
nextUrl: URL,
|
|
155
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
156
|
+
actionContext?: any,
|
|
157
|
+
) => AsyncGenerator<ResolvedSegment | { __type: "id"; id: string }>;
|
|
158
|
+
|
|
159
|
+
// Intercept resolution
|
|
160
|
+
resolveInterceptEntry: (
|
|
161
|
+
intercept: InterceptEntry,
|
|
162
|
+
entry: EntryData,
|
|
163
|
+
params: Record<string, string>,
|
|
164
|
+
handlerContext: HandlerContext<any, TEnv>,
|
|
165
|
+
belongsToRoute: boolean,
|
|
166
|
+
revalidationContext?: RevalidationContext,
|
|
167
|
+
) => Promise<ResolvedSegment[]>;
|
|
168
|
+
|
|
169
|
+
// Collect with markers
|
|
170
|
+
collectWithMarkers?: <T>(
|
|
171
|
+
gen: AsyncGenerator<T | { __type: "id"; id: string }>,
|
|
172
|
+
) => Promise<{ items: T[]; matchedIds: string[] }>;
|
|
173
|
+
|
|
174
|
+
// Revalidation evaluation
|
|
175
|
+
evaluateRevalidation: (params: {
|
|
176
|
+
segment: ResolvedSegment;
|
|
177
|
+
prevParams: Record<string, string>;
|
|
178
|
+
getPrevSegment: (() => Promise<ResolvedSegment | undefined>) | null;
|
|
179
|
+
request: Request;
|
|
180
|
+
prevUrl: URL;
|
|
181
|
+
nextUrl: URL;
|
|
182
|
+
revalidations: Array<{ name: string; fn: any }>;
|
|
183
|
+
routeKey: string;
|
|
184
|
+
context: HandlerContext<any, TEnv>;
|
|
185
|
+
actionContext?: any;
|
|
186
|
+
stale?: boolean;
|
|
187
|
+
traceSource?:
|
|
188
|
+
| "segment-resolution"
|
|
189
|
+
| "cache-hit"
|
|
190
|
+
| "loader"
|
|
191
|
+
| "parallel"
|
|
192
|
+
| "orphan-layout"
|
|
193
|
+
| "route-handler"
|
|
194
|
+
| "layout-handler"
|
|
195
|
+
| "intercept-loader";
|
|
196
|
+
}) => Promise<boolean>;
|
|
197
|
+
|
|
198
|
+
// Request context
|
|
199
|
+
getRequestContext: () =>
|
|
200
|
+
| {
|
|
201
|
+
waitUntil: (fn: () => Promise<void>) => void;
|
|
202
|
+
_handleStore?: any;
|
|
203
|
+
}
|
|
204
|
+
| undefined;
|
|
205
|
+
|
|
206
|
+
// Simple segment resolution (without revalidation - for full match)
|
|
207
|
+
resolveAllSegments: (
|
|
208
|
+
entries: EntryData[],
|
|
209
|
+
routeKey: string,
|
|
210
|
+
params: Record<string, string>,
|
|
211
|
+
handlerContext: HandlerContext<any, TEnv>,
|
|
212
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
213
|
+
) => Promise<ResolvedSegment[]>;
|
|
214
|
+
|
|
215
|
+
// Generator-based simple resolution
|
|
216
|
+
resolveAllSegmentsGenerator?: (
|
|
217
|
+
entries: EntryData[],
|
|
218
|
+
routeKey: string,
|
|
219
|
+
params: Record<string, string>,
|
|
220
|
+
handlerContext: HandlerContext<any, TEnv>,
|
|
221
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
222
|
+
) => AsyncGenerator<ResolvedSegment | { __type: "id"; id: string }>;
|
|
223
|
+
|
|
224
|
+
// Collect segments from generator
|
|
225
|
+
collectSegmentsFromGenerator?: <T>(
|
|
226
|
+
gen: AsyncGenerator<T | { __type: "id"; id: string }>,
|
|
227
|
+
) => Promise<T[]>;
|
|
228
|
+
|
|
229
|
+
// Handle store
|
|
230
|
+
createHandleStore: () => any;
|
|
231
|
+
|
|
232
|
+
// Loaders-only resolution (for full match cache hit - no revalidation)
|
|
233
|
+
resolveLoadersOnly?: (
|
|
234
|
+
entries: EntryData[],
|
|
235
|
+
handlerContext: HandlerContext<any, TEnv>,
|
|
236
|
+
) => Promise<ResolvedSegment[]>;
|
|
237
|
+
|
|
238
|
+
// Loaders-only resolution (for cache hit scenarios)
|
|
239
|
+
resolveLoadersOnlyWithRevalidation?: (
|
|
240
|
+
entries: EntryData[],
|
|
241
|
+
handlerContext: HandlerContext<any, TEnv>,
|
|
242
|
+
clientSegmentSet: Set<string>,
|
|
243
|
+
prevParams: Record<string, string>,
|
|
244
|
+
request: Request,
|
|
245
|
+
prevUrl: URL,
|
|
246
|
+
nextUrl: URL,
|
|
247
|
+
routeKey: string,
|
|
248
|
+
actionContext?: any,
|
|
249
|
+
stale?: boolean,
|
|
250
|
+
) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
|
|
251
|
+
|
|
252
|
+
// Entry revalidation map
|
|
253
|
+
buildEntryRevalidateMap?: (
|
|
254
|
+
entries: EntryData[],
|
|
255
|
+
) => Map<string, { revalidate: ShouldRevalidateFn[] }>;
|
|
256
|
+
|
|
257
|
+
// Telemetry sink (optional, no-op when undefined)
|
|
258
|
+
telemetry?: TelemetrySink;
|
|
259
|
+
|
|
260
|
+
// Request ID for telemetry span correlation (set per-request in match handlers)
|
|
261
|
+
requestId?: string;
|
|
262
|
+
|
|
263
|
+
// Intercept loaders only (for cache hit + intercept scenarios)
|
|
264
|
+
resolveInterceptLoadersOnly?: (
|
|
265
|
+
intercept: InterceptEntry,
|
|
266
|
+
entry: EntryData,
|
|
267
|
+
params: Record<string, string>,
|
|
268
|
+
handlerContext: HandlerContext<any, TEnv>,
|
|
269
|
+
belongsToRoute: boolean,
|
|
270
|
+
revalidationContext: {
|
|
271
|
+
clientSegmentIds: Set<string>;
|
|
272
|
+
prevParams: Record<string, string>;
|
|
273
|
+
request: Request;
|
|
274
|
+
prevUrl: URL;
|
|
275
|
+
nextUrl: URL;
|
|
276
|
+
routeKey: string;
|
|
277
|
+
actionContext?: any;
|
|
278
|
+
stale?: boolean;
|
|
279
|
+
},
|
|
280
|
+
) => Promise<{
|
|
281
|
+
loaderDataPromise: Promise<any[]> | any[];
|
|
282
|
+
loaderIds: string[];
|
|
283
|
+
} | null>;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// AsyncLocalStorage instance for router context
|
|
287
|
+
const routerContext = new AsyncLocalStorage<RouterContext<any>>();
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get router dependencies from AsyncLocalStorage context
|
|
291
|
+
*
|
|
292
|
+
* @throws Error if called outside of router context (runWithRouterContext)
|
|
293
|
+
*/
|
|
294
|
+
export function getRouterContext<TEnv = any>(): RouterContext<TEnv> {
|
|
295
|
+
const deps = routerContext.getStore();
|
|
296
|
+
if (!deps) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
"getRouterContext() called outside of router context. " +
|
|
299
|
+
"Ensure code is running inside runWithRouterContext().",
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return deps as RouterContext<TEnv>;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Run a function with router dependencies available via getRouterContext()
|
|
307
|
+
*
|
|
308
|
+
* All async code within fn() can call getRouterContext() to access router closures.
|
|
309
|
+
* This works across async boundaries thanks to AsyncLocalStorage.
|
|
310
|
+
*
|
|
311
|
+
* @param deps Router dependencies to make available
|
|
312
|
+
* @param fn Function to run with dependencies available
|
|
313
|
+
* @returns Result of fn()
|
|
314
|
+
*/
|
|
315
|
+
export function runWithRouterContext<T, TEnv = any>(
|
|
316
|
+
deps: RouterContext<TEnv>,
|
|
317
|
+
fn: () => T,
|
|
318
|
+
): T {
|
|
319
|
+
return routerContext.run(deps, fn);
|
|
320
|
+
}
|