@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc
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/README.md +196 -43
- package/dist/bin/rango.js +277 -99
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +2779 -1064
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +243 -21
- package/skills/caching/SKILL.md +155 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +273 -53
- package/skills/middleware/SKILL.md +49 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +197 -6
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +88 -4
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +716 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/__internal.ts +1 -1
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +91 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +102 -16
- package/src/browser/navigation-client.ts +164 -59
- package/src/browser/navigation-store.ts +75 -17
- package/src/browser/navigation-transaction.ts +21 -37
- package/src/browser/partial-update.ts +139 -38
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +110 -33
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +23 -64
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +43 -10
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +191 -74
- package/src/browser/scroll-restoration.ts +41 -14
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +31 -36
- package/src/browser/types.ts +57 -5
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -88
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +76 -49
- package/src/cache/cf/cf-cache-store.ts +501 -18
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +94 -238
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +65 -12
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +12 -5
- package/src/index.ts +61 -11
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +141 -80
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +435 -260
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +110 -34
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +113 -1
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +77 -38
- package/src/router/intercept-resolution.ts +15 -22
- package/src/router/lazy-includes.ts +12 -9
- package/src/router/loader-resolution.ts +174 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -192
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +136 -106
- package/src/router/match-middleware/cache-store.ts +54 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +125 -10
- package/src/router/metrics.ts +7 -2
- package/src/router/middleware-types.ts +21 -34
- package/src/router/middleware.ts +103 -90
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +286 -0
- package/src/router/revalidation.ts +58 -2
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +77 -28
- package/src/router/router-options.ts +76 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +223 -24
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +466 -285
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +9 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +91 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +440 -381
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +18 -2
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +41 -48
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +25 -37
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +17 -3
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +219 -67
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +277 -61
- package/src/server/cookie-store.ts +28 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +204 -60
- package/src/ssr/index.tsx +9 -1
- package/src/static-handler.ts +19 -7
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +255 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +179 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +183 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +194 -72
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +37 -1
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +50 -9
- package/src/urls/path-helper.ts +63 -63
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +487 -44
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +34 -37
- package/src/vite/discovery/discover-routers.ts +105 -51
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +188 -93
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +46 -6
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +6 -0
- package/src/vite/plugin-types.ts +111 -72
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +55 -33
- package/src/vite/plugins/expose-id-utils.ts +24 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +544 -317
- package/src/vite/plugins/performance-tracks.ts +92 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +72 -3
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +265 -226
- package/src/vite/router-discovery.ts +920 -137
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +38 -5
- package/src/vite/utils/shared-utils.ts +109 -27
- package/src/browser/action-response-classifier.ts +0 -99
package/src/router/match-api.ts
CHANGED
|
@@ -22,10 +22,10 @@ import { collectRouteMiddleware } from "./middleware.js";
|
|
|
22
22
|
import { traverseBack } from "./pattern-matching.js";
|
|
23
23
|
import { DefaultErrorFallback } from "../default-error-boundary.js";
|
|
24
24
|
import {
|
|
25
|
-
EntryData,
|
|
26
|
-
LoaderEntry,
|
|
25
|
+
type EntryData,
|
|
26
|
+
type LoaderEntry,
|
|
27
27
|
getContext,
|
|
28
|
-
InterceptSelectorContext,
|
|
28
|
+
type InterceptSelectorContext,
|
|
29
29
|
} from "../server/context";
|
|
30
30
|
import type { ErrorBoundaryHandler, ErrorInfo, MatchResult } from "../types";
|
|
31
31
|
import type { ReactNode } from "react";
|
|
@@ -36,7 +36,14 @@ import {
|
|
|
36
36
|
setRequestContextPrevRouteKey,
|
|
37
37
|
} from "../server/request-context.js";
|
|
38
38
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
39
|
+
import type { DefaultRouteName } from "../types/global-namespace.js";
|
|
39
40
|
import { debugLog, debugWarn } from "./logging.js";
|
|
41
|
+
import {
|
|
42
|
+
resolveRoute,
|
|
43
|
+
ensureFullRouteSnapshot,
|
|
44
|
+
type RouteSnapshot,
|
|
45
|
+
} from "./route-snapshot.js";
|
|
46
|
+
import { resolveNavigation } from "./navigation-snapshot.js";
|
|
40
47
|
|
|
41
48
|
/**
|
|
42
49
|
* Create match context for full requests (document/SSR).
|
|
@@ -52,57 +59,36 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
52
59
|
|
|
53
60
|
const metricsStore = deps.getMetricsStore();
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
63
|
-
}
|
|
62
|
+
// Full renders always resolve fresh with isSSR: true because loadManifest
|
|
63
|
+
// keys its cache on isSSR and stamps Store.isSSR for downstream behavior.
|
|
64
|
+
const result = await resolveRoute<TEnv>(pathname, {
|
|
65
|
+
findMatch: (p) => deps.findMatch(p, metricsStore),
|
|
66
|
+
metricsStore,
|
|
67
|
+
isSSR: true,
|
|
68
|
+
});
|
|
64
69
|
|
|
65
|
-
if (!
|
|
70
|
+
if (!result) {
|
|
66
71
|
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
67
72
|
cause: { pathname, method: request.method },
|
|
68
73
|
});
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
if (
|
|
76
|
+
if (result.type === "redirect") {
|
|
72
77
|
return {
|
|
73
78
|
type: "redirect",
|
|
74
|
-
redirectUrl:
|
|
79
|
+
redirectUrl: result.redirectTo + url.search,
|
|
75
80
|
};
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
const
|
|
79
|
-
const manifestEntry = await loadManifest(
|
|
80
|
-
matched.entry,
|
|
81
|
-
matched.routeKey,
|
|
82
|
-
pathname,
|
|
83
|
-
metricsStore,
|
|
84
|
-
true,
|
|
85
|
-
);
|
|
86
|
-
if (metricsStore) {
|
|
87
|
-
metricsStore.metrics.push({
|
|
88
|
-
label: "manifest-loading",
|
|
89
|
-
duration: performance.now() - manifestStart,
|
|
90
|
-
startTime: manifestStart - metricsStore.requestStart,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
83
|
+
const snapshot = result.snapshot;
|
|
93
84
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
) {
|
|
85
|
+
const { matched } = snapshot;
|
|
86
|
+
|
|
87
|
+
// Backward compat: downstream middleware reads matched.pt
|
|
88
|
+
if (snapshot.isPassthrough) {
|
|
98
89
|
matched.pt = true;
|
|
99
90
|
}
|
|
100
91
|
|
|
101
|
-
const routeMiddleware = collectRouteMiddleware(
|
|
102
|
-
traverseBack(manifestEntry),
|
|
103
|
-
matched.params,
|
|
104
|
-
);
|
|
105
|
-
|
|
106
92
|
// Clean URL without internal _rsc* params for userland access
|
|
107
93
|
const cleanUrl = stripInternalParams(url);
|
|
108
94
|
|
|
@@ -134,14 +120,6 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
134
120
|
Store.metrics = metricsStore;
|
|
135
121
|
}
|
|
136
122
|
|
|
137
|
-
const entries = [...traverseBack(manifestEntry)];
|
|
138
|
-
let cacheScope: CacheScope | null = null;
|
|
139
|
-
for (const entry of entries) {
|
|
140
|
-
if (entry.cache) {
|
|
141
|
-
cacheScope = createCacheScope(entry.cache, cacheScope);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
123
|
return {
|
|
146
124
|
request,
|
|
147
125
|
url: cleanUrl,
|
|
@@ -154,12 +132,10 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
154
132
|
prevParams: {},
|
|
155
133
|
prevMatch: null,
|
|
156
134
|
matched,
|
|
157
|
-
manifestEntry,
|
|
158
|
-
entries,
|
|
135
|
+
manifestEntry: snapshot.manifestEntry,
|
|
136
|
+
entries: snapshot.entries,
|
|
159
137
|
routeKey: matched.routeKey,
|
|
160
|
-
localRouteName:
|
|
161
|
-
? matched.routeKey.split(".").pop()!
|
|
162
|
-
: matched.routeKey,
|
|
138
|
+
localRouteName: snapshot.localRouteName,
|
|
163
139
|
handlerContext,
|
|
164
140
|
loaderPromises,
|
|
165
141
|
routeMap: deps.getRouteMap(),
|
|
@@ -175,16 +151,16 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
175
151
|
segments: { path: [], ids: [] },
|
|
176
152
|
toRouteName:
|
|
177
153
|
matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
|
|
178
|
-
? matched.routeKey
|
|
154
|
+
? (matched.routeKey as DefaultRouteName)
|
|
179
155
|
: undefined,
|
|
180
156
|
},
|
|
181
157
|
isSameRouteNavigation: false,
|
|
182
158
|
interceptResult: null,
|
|
183
|
-
cacheScope,
|
|
159
|
+
cacheScope: snapshot.cacheScope,
|
|
184
160
|
isIntercept: false,
|
|
185
161
|
actionContext: undefined,
|
|
186
162
|
isAction: false,
|
|
187
|
-
routeMiddleware,
|
|
163
|
+
routeMiddleware: snapshot.routeMiddleware,
|
|
188
164
|
isFullMatch: true,
|
|
189
165
|
};
|
|
190
166
|
}
|
|
@@ -204,103 +180,85 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
204
180
|
|
|
205
181
|
const metricsStore = deps.getMetricsStore();
|
|
206
182
|
|
|
207
|
-
const
|
|
208
|
-
url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
|
|
209
|
-
const stale = url.searchParams.get("_rsc_stale") === "true";
|
|
210
|
-
const previousUrl =
|
|
211
|
-
request.headers.get("X-RSC-Router-Client-Path") ||
|
|
212
|
-
request.headers.get("Referer");
|
|
213
|
-
const interceptSourceUrl = request.headers.get(
|
|
214
|
-
"X-RSC-Router-Intercept-Source",
|
|
215
|
-
);
|
|
183
|
+
const isHmr = !!request.headers.get("X-RSC-HMR");
|
|
216
184
|
|
|
217
185
|
// HMR: clear manifest cache so stale handler references are discarded
|
|
218
|
-
if (
|
|
186
|
+
if (isHmr) {
|
|
219
187
|
clearManifestCache();
|
|
220
188
|
}
|
|
221
189
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
190
|
+
// Reuse the classified snapshot when available and not invalidated by HMR.
|
|
191
|
+
// classifyRequest already called resolveRoute(lite) with isSSR=false, which
|
|
192
|
+
// matches the partial path. On HMR, discard to pick up manifest changes.
|
|
193
|
+
const classifiedRoute = isHmr
|
|
194
|
+
? undefined
|
|
195
|
+
: getRequestContext()?._classifiedRoute;
|
|
196
|
+
|
|
197
|
+
// Time route matching. On the reuse path, only nav findMatch calls are new
|
|
198
|
+
// (current-route findMatch and manifest-loading were already timed during
|
|
199
|
+
// classifyRequest via its metricsStore). On the fresh path, all findMatch
|
|
200
|
+
// calls are measured together.
|
|
201
|
+
const routeMatchStart = metricsStore ? performance.now() : 0;
|
|
232
202
|
|
|
233
|
-
let
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
203
|
+
let snapshot: RouteSnapshot<TEnv>;
|
|
204
|
+
if (classifiedRoute && classifiedRoute.manifestEntry) {
|
|
205
|
+
snapshot = ensureFullRouteSnapshot(classifiedRoute);
|
|
206
|
+
} else {
|
|
207
|
+
const result = await resolveRoute<TEnv>(pathname, {
|
|
208
|
+
findMatch: (p) => deps.findMatch(p, metricsStore),
|
|
209
|
+
metricsStore,
|
|
210
|
+
isSSR: false,
|
|
211
|
+
skipRouteMatchMetric: true,
|
|
212
|
+
});
|
|
241
213
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
: prevMatch;
|
|
214
|
+
if (!result) {
|
|
215
|
+
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
216
|
+
cause: { pathname, method: request.method },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
248
219
|
|
|
249
|
-
|
|
220
|
+
if (result.type === "redirect") {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
250
223
|
|
|
251
|
-
|
|
252
|
-
metricsStore.metrics.push({
|
|
253
|
-
label: "route-matching",
|
|
254
|
-
duration: performance.now() - routeMatchStart,
|
|
255
|
-
startTime: routeMatchStart - metricsStore.requestStart,
|
|
256
|
-
});
|
|
224
|
+
snapshot = result.snapshot;
|
|
257
225
|
}
|
|
258
226
|
|
|
259
|
-
|
|
260
|
-
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
261
|
-
cause: { pathname, method: request.method, previousUrl },
|
|
262
|
-
});
|
|
263
|
-
}
|
|
227
|
+
const { matched } = snapshot;
|
|
264
228
|
|
|
265
|
-
|
|
266
|
-
|
|
229
|
+
// Backward compat: downstream middleware reads matched.pt
|
|
230
|
+
if (snapshot.isPassthrough) {
|
|
231
|
+
matched.pt = true;
|
|
267
232
|
}
|
|
268
233
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
234
|
+
// Navigation state (prev + intercept-source findMatch calls)
|
|
235
|
+
const nav = resolveNavigation(request, url, matched.routeKey, {
|
|
236
|
+
findMatch: deps.findMatch,
|
|
237
|
+
});
|
|
238
|
+
if (!nav) {
|
|
239
|
+
return null;
|
|
274
240
|
}
|
|
275
241
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
pathname,
|
|
281
|
-
metricsStore,
|
|
282
|
-
false,
|
|
283
|
-
);
|
|
242
|
+
// Push route-matching metric. On the fresh path this covers all findMatch
|
|
243
|
+
// calls (current + prev + intercept-source). On the reuse path, current-route
|
|
244
|
+
// findMatch was already timed during classification, so this only covers
|
|
245
|
+
// the nav lookups (prev + intercept-source).
|
|
284
246
|
if (metricsStore) {
|
|
247
|
+
const isReuse = !!classifiedRoute;
|
|
285
248
|
metricsStore.metrics.push({
|
|
286
|
-
label: "
|
|
287
|
-
duration: performance.now() -
|
|
288
|
-
startTime:
|
|
249
|
+
label: isReuse ? "route-matching:nav" : "route-matching",
|
|
250
|
+
duration: performance.now() - routeMatchStart,
|
|
251
|
+
startTime: routeMatchStart - metricsStore.requestStart,
|
|
289
252
|
});
|
|
290
253
|
}
|
|
291
254
|
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
255
|
+
if (nav.prevMatch && nav.prevMatch.entry !== matched.entry && !matched.pr) {
|
|
256
|
+
debugLog("matchPartial", "route group changed", {
|
|
257
|
+
from: nav.prevMatch.routeKey,
|
|
258
|
+
to: matched.routeKey,
|
|
259
|
+
});
|
|
297
260
|
}
|
|
298
261
|
|
|
299
|
-
const routeMiddleware = collectRouteMiddleware(
|
|
300
|
-
traverseBack(manifestEntry),
|
|
301
|
-
matched.params,
|
|
302
|
-
);
|
|
303
|
-
|
|
304
262
|
// Clean URL without internal _rsc* params for userland access
|
|
305
263
|
const cleanUrl = stripInternalParams(url);
|
|
306
264
|
|
|
@@ -317,9 +275,8 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
317
275
|
matched.pt === true,
|
|
318
276
|
);
|
|
319
277
|
|
|
320
|
-
const clientSegmentSet = new Set(clientSegmentIds);
|
|
321
278
|
debugLog("matchPartial", "client segments", {
|
|
322
|
-
segments: Array.from(clientSegmentSet),
|
|
279
|
+
segments: Array.from(nav.clientSegmentSet),
|
|
323
280
|
});
|
|
324
281
|
|
|
325
282
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
@@ -337,100 +294,78 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
337
294
|
Store.metrics = metricsStore;
|
|
338
295
|
}
|
|
339
296
|
|
|
340
|
-
|
|
341
|
-
interceptContextMatch && interceptContextMatch.routeKey === matched.routeKey
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
if (interceptSourceUrl) {
|
|
297
|
+
if (nav.hasInterceptSource) {
|
|
345
298
|
debugLog("matchPartial.intercept", "intercept context detected", {
|
|
346
299
|
currentUrl: pathname,
|
|
347
|
-
interceptSource:
|
|
348
|
-
contextRoute: interceptContextMatch?.routeKey,
|
|
300
|
+
interceptSource: nav.interceptContextUrl.href,
|
|
301
|
+
contextRoute: nav.interceptContextMatch?.routeKey,
|
|
349
302
|
currentRoute: matched.routeKey,
|
|
350
|
-
sameRouteNavigation: isSameRouteNavigation,
|
|
303
|
+
sameRouteNavigation: nav.isSameRouteNavigation,
|
|
351
304
|
});
|
|
352
305
|
}
|
|
353
306
|
|
|
354
|
-
const localRouteName = matched.routeKey.includes(".")
|
|
355
|
-
? matched.routeKey.split(".").pop()!
|
|
356
|
-
: matched.routeKey;
|
|
357
|
-
|
|
358
|
-
const filteredSegmentIds = clientSegmentIds.filter((id) => {
|
|
359
|
-
if (id.includes(".@")) return false;
|
|
360
|
-
if (/D\d+\./.test(id)) return false;
|
|
361
|
-
return true;
|
|
362
|
-
});
|
|
363
|
-
const effectiveFromUrl = interceptSourceUrl ? interceptContextUrl : prevUrl;
|
|
364
|
-
const effectiveFromMatch = interceptSourceUrl
|
|
365
|
-
? interceptContextMatch
|
|
366
|
-
: prevMatch;
|
|
367
|
-
|
|
368
307
|
// Store previous route key on the request context for revalidation
|
|
369
308
|
// fromRouteName. Uses effectiveFromMatch so intercept-source navigations
|
|
370
309
|
// see the intercept origin route, not the plain previous URL route.
|
|
371
|
-
setRequestContextPrevRouteKey(effectiveFromMatch?.routeKey);
|
|
310
|
+
setRequestContextPrevRouteKey(nav.effectiveFromMatch?.routeKey);
|
|
372
311
|
|
|
373
312
|
const interceptSelectorContext: InterceptSelectorContext = {
|
|
374
|
-
from: effectiveFromUrl,
|
|
313
|
+
from: nav.effectiveFromUrl,
|
|
375
314
|
to: cleanUrl,
|
|
376
315
|
params: matched.params,
|
|
377
316
|
request,
|
|
378
317
|
env,
|
|
379
318
|
segments: {
|
|
380
|
-
path: effectiveFromUrl.pathname.split("/").filter(Boolean),
|
|
381
|
-
ids: filteredSegmentIds,
|
|
319
|
+
path: nav.effectiveFromUrl.pathname.split("/").filter(Boolean),
|
|
320
|
+
ids: nav.filteredSegmentIds,
|
|
382
321
|
},
|
|
383
322
|
fromRouteName:
|
|
384
|
-
effectiveFromMatch?.routeKey &&
|
|
385
|
-
!isAutoGeneratedRouteName(effectiveFromMatch.routeKey)
|
|
386
|
-
? effectiveFromMatch.routeKey
|
|
323
|
+
nav.effectiveFromMatch?.routeKey &&
|
|
324
|
+
!isAutoGeneratedRouteName(nav.effectiveFromMatch.routeKey)
|
|
325
|
+
? (nav.effectiveFromMatch.routeKey as DefaultRouteName)
|
|
387
326
|
: undefined,
|
|
388
327
|
toRouteName:
|
|
389
328
|
matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
|
|
390
|
-
? matched.routeKey
|
|
329
|
+
? (matched.routeKey as DefaultRouteName)
|
|
391
330
|
: undefined,
|
|
392
331
|
};
|
|
393
332
|
const isAction = !!actionContext;
|
|
394
333
|
|
|
395
|
-
const clientHasInterceptSegments = [...clientSegmentSet].some((id) =>
|
|
334
|
+
const clientHasInterceptSegments = [...nav.clientSegmentSet].some((id) =>
|
|
396
335
|
id.includes(".@"),
|
|
397
336
|
);
|
|
398
337
|
const skipInterceptForAction = isAction && !clientHasInterceptSegments;
|
|
399
338
|
const interceptResult =
|
|
400
|
-
isSameRouteNavigation || skipInterceptForAction
|
|
339
|
+
nav.isSameRouteNavigation || skipInterceptForAction
|
|
401
340
|
? null
|
|
402
341
|
: findInterceptForRoute(
|
|
403
342
|
matched.routeKey,
|
|
404
|
-
manifestEntry.parent,
|
|
343
|
+
snapshot.manifestEntry.parent,
|
|
405
344
|
interceptSelectorContext,
|
|
406
345
|
isAction,
|
|
407
346
|
) ||
|
|
408
|
-
(localRouteName !== matched.routeKey
|
|
347
|
+
(snapshot.localRouteName !== matched.routeKey
|
|
409
348
|
? findInterceptForRoute(
|
|
410
|
-
localRouteName,
|
|
411
|
-
manifestEntry.parent,
|
|
349
|
+
snapshot.localRouteName,
|
|
350
|
+
snapshot.manifestEntry.parent,
|
|
412
351
|
interceptSelectorContext,
|
|
413
352
|
isAction,
|
|
414
353
|
)
|
|
415
354
|
: null);
|
|
416
355
|
|
|
356
|
+
// Make a mutable copy of clientSegmentSet so we can delete entries
|
|
357
|
+
// for same-route navigation forcing
|
|
358
|
+
const clientSegmentSet = new Set(nav.clientSegmentSet);
|
|
359
|
+
|
|
417
360
|
if (
|
|
418
|
-
isSameRouteNavigation &&
|
|
419
|
-
manifestEntry.type === "route" &&
|
|
420
|
-
|
|
361
|
+
nav.isSameRouteNavigation &&
|
|
362
|
+
snapshot.manifestEntry.type === "route" &&
|
|
363
|
+
nav.hasInterceptSource
|
|
421
364
|
) {
|
|
422
365
|
debugLog("matchPartial.intercept", "forcing route segment render", {
|
|
423
|
-
segmentId: manifestEntry.shortCode,
|
|
366
|
+
segmentId: snapshot.manifestEntry.shortCode,
|
|
424
367
|
});
|
|
425
|
-
clientSegmentSet.delete(manifestEntry.shortCode);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const entries = [...traverseBack(manifestEntry)];
|
|
429
|
-
let cacheScope: CacheScope | null = null;
|
|
430
|
-
for (const entry of entries) {
|
|
431
|
-
if (entry.cache) {
|
|
432
|
-
cacheScope = createCacheScope(entry.cache, cacheScope);
|
|
433
|
-
}
|
|
368
|
+
clientSegmentSet.delete(snapshot.manifestEntry.shortCode);
|
|
434
369
|
}
|
|
435
370
|
|
|
436
371
|
const isIntercept = !!interceptResult;
|
|
@@ -440,31 +375,31 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
440
375
|
url: cleanUrl,
|
|
441
376
|
pathname,
|
|
442
377
|
env,
|
|
443
|
-
clientSegmentIds,
|
|
378
|
+
clientSegmentIds: nav.clientSegmentIds,
|
|
444
379
|
clientSegmentSet,
|
|
445
|
-
stale,
|
|
446
|
-
prevUrl,
|
|
447
|
-
prevParams,
|
|
448
|
-
prevMatch,
|
|
380
|
+
stale: nav.stale,
|
|
381
|
+
prevUrl: nav.prevUrl,
|
|
382
|
+
prevParams: nav.prevParams,
|
|
383
|
+
prevMatch: nav.prevMatch,
|
|
449
384
|
matched,
|
|
450
|
-
manifestEntry,
|
|
451
|
-
entries,
|
|
385
|
+
manifestEntry: snapshot.manifestEntry,
|
|
386
|
+
entries: snapshot.entries,
|
|
452
387
|
routeKey: matched.routeKey,
|
|
453
|
-
localRouteName,
|
|
388
|
+
localRouteName: snapshot.localRouteName,
|
|
454
389
|
handlerContext,
|
|
455
390
|
loaderPromises,
|
|
456
391
|
routeMap: deps.getRouteMap(),
|
|
457
392
|
metricsStore,
|
|
458
393
|
Store,
|
|
459
|
-
interceptContextMatch,
|
|
394
|
+
interceptContextMatch: nav.interceptContextMatch,
|
|
460
395
|
interceptSelectorContext,
|
|
461
|
-
isSameRouteNavigation,
|
|
396
|
+
isSameRouteNavigation: nav.isSameRouteNavigation,
|
|
462
397
|
interceptResult,
|
|
463
|
-
cacheScope,
|
|
398
|
+
cacheScope: snapshot.cacheScope,
|
|
464
399
|
isIntercept,
|
|
465
400
|
actionContext,
|
|
466
401
|
isAction,
|
|
467
|
-
routeMiddleware,
|
|
402
|
+
routeMiddleware: snapshot.routeMiddleware,
|
|
468
403
|
isFullMatch: false,
|
|
469
404
|
};
|
|
470
405
|
}
|
|
@@ -615,6 +550,7 @@ export async function matchError<TEnv>(
|
|
|
615
550
|
segments: [errorSegment],
|
|
616
551
|
matched: matchedIds,
|
|
617
552
|
diff: [errorSegment.id],
|
|
553
|
+
resolvedIds: [errorSegment.id],
|
|
618
554
|
params: matched.params,
|
|
619
555
|
};
|
|
620
556
|
}
|
|
@@ -33,10 +33,13 @@ import type { ErrorBoundaryHandler, NotFoundBoundaryHandler } from "../types";
|
|
|
33
33
|
import type { MiddlewareFn } from "./middleware.js";
|
|
34
34
|
import {
|
|
35
35
|
type TelemetrySink,
|
|
36
|
+
type CacheSegmentSignal,
|
|
36
37
|
safeEmit,
|
|
37
38
|
resolveSink,
|
|
38
39
|
getRequestId,
|
|
40
|
+
buildCacheSignalSegments,
|
|
39
41
|
} from "./telemetry.js";
|
|
42
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
40
43
|
|
|
41
44
|
export interface MatchHandlerDeps<TEnv = any> {
|
|
42
45
|
buildRouterContext: () => RouterContext<TEnv>;
|
|
@@ -51,6 +54,12 @@ export interface MatchHandlerDeps<TEnv = any> {
|
|
|
51
54
|
isAction: boolean,
|
|
52
55
|
) => { intercept: InterceptEntry; entry: EntryData } | null;
|
|
53
56
|
telemetry?: TelemetrySink;
|
|
57
|
+
/**
|
|
58
|
+
* DEVELOPMENT/TEST ONLY gate for the X-Rango-Cache debug header. When true,
|
|
59
|
+
* match/matchPartial stash a coarse route-level cache signal on the request
|
|
60
|
+
* context for the response-finalization path to emit. Default off.
|
|
61
|
+
*/
|
|
62
|
+
cacheSignalEnabled?: boolean;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
export interface MatchHandlers<TEnv = any> {
|
|
@@ -113,6 +122,25 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
113
122
|
} = deps;
|
|
114
123
|
const hasTelemetry = !!deps.telemetry;
|
|
115
124
|
const telemetry = resolveSink(deps.telemetry);
|
|
125
|
+
const cacheSignalEnabled = !!deps.cacheSignalEnabled;
|
|
126
|
+
// Compute the coarse cache signal when EITHER telemetry needs it (for the
|
|
127
|
+
// cache.decision event) OR the debug header gate is on. When neither is set,
|
|
128
|
+
// this is never called — zero extra work on the hot path.
|
|
129
|
+
const buildSignal = (
|
|
130
|
+
routeKey: string,
|
|
131
|
+
state: {
|
|
132
|
+
cacheHit: boolean;
|
|
133
|
+
cacheSource?: "runtime" | "prerender";
|
|
134
|
+
shouldRevalidate?: boolean;
|
|
135
|
+
},
|
|
136
|
+
): CacheSegmentSignal[] => buildCacheSignalSegments(routeKey, state);
|
|
137
|
+
// Stash the signal on the request context for the response path to emit as
|
|
138
|
+
// the X-Rango-Cache header. Only when the debug gate is on.
|
|
139
|
+
const recordSignalIfEnabled = (segments: CacheSegmentSignal[]): void => {
|
|
140
|
+
if (!cacheSignalEnabled) return;
|
|
141
|
+
const reqCtx = _getRequestContext();
|
|
142
|
+
if (reqCtx) reqCtx._cacheSignal = segments;
|
|
143
|
+
};
|
|
116
144
|
|
|
117
145
|
async function createMatchContextForFull(
|
|
118
146
|
request: Request,
|
|
@@ -196,6 +224,7 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
196
224
|
segments: [],
|
|
197
225
|
matched: [],
|
|
198
226
|
diff: [],
|
|
227
|
+
resolvedIds: [],
|
|
199
228
|
params: {},
|
|
200
229
|
redirect: result.redirectUrl,
|
|
201
230
|
};
|
|
@@ -207,17 +236,24 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
207
236
|
const state = createPipelineState();
|
|
208
237
|
const pipeline = createMatchPartialPipeline(ctx, state);
|
|
209
238
|
const matchResult = await collectMatchResult(pipeline, ctx, state);
|
|
239
|
+
if (hasTelemetry || cacheSignalEnabled) {
|
|
240
|
+
const signalSegments = buildSignal(ctx.routeKey, state);
|
|
241
|
+
recordSignalIfEnabled(signalSegments);
|
|
242
|
+
if (hasTelemetry) {
|
|
243
|
+
safeEmit(telemetry, {
|
|
244
|
+
type: "cache.decision",
|
|
245
|
+
timestamp: performance.now(),
|
|
246
|
+
requestId,
|
|
247
|
+
pathname,
|
|
248
|
+
routeKey: ctx.routeKey,
|
|
249
|
+
hit: state.cacheHit,
|
|
250
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
251
|
+
source: state.cacheSource,
|
|
252
|
+
segments: signalSegments,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
210
256
|
if (hasTelemetry) {
|
|
211
|
-
safeEmit(telemetry, {
|
|
212
|
-
type: "cache.decision",
|
|
213
|
-
timestamp: performance.now(),
|
|
214
|
-
requestId,
|
|
215
|
-
pathname,
|
|
216
|
-
routeKey: ctx.routeKey,
|
|
217
|
-
hit: state.cacheHit,
|
|
218
|
-
shouldRevalidate: !!state.shouldRevalidate,
|
|
219
|
-
source: state.cacheSource,
|
|
220
|
-
});
|
|
221
257
|
safeEmit(telemetry, {
|
|
222
258
|
type: "request.end",
|
|
223
259
|
timestamp: performance.now(),
|
|
@@ -362,17 +398,24 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
362
398
|
state,
|
|
363
399
|
);
|
|
364
400
|
flushRevalidationTrace();
|
|
401
|
+
if (hasTelemetry || cacheSignalEnabled) {
|
|
402
|
+
const signalSegments = buildSignal(ctx.routeKey, state);
|
|
403
|
+
recordSignalIfEnabled(signalSegments);
|
|
404
|
+
if (hasTelemetry) {
|
|
405
|
+
safeEmit(telemetry, {
|
|
406
|
+
type: "cache.decision",
|
|
407
|
+
timestamp: performance.now(),
|
|
408
|
+
requestId: partialRequestId,
|
|
409
|
+
pathname,
|
|
410
|
+
routeKey: ctx.routeKey,
|
|
411
|
+
hit: state.cacheHit,
|
|
412
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
413
|
+
source: state.cacheSource,
|
|
414
|
+
segments: signalSegments,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
365
418
|
if (hasTelemetry) {
|
|
366
|
-
safeEmit(telemetry, {
|
|
367
|
-
type: "cache.decision",
|
|
368
|
-
timestamp: performance.now(),
|
|
369
|
-
requestId: partialRequestId,
|
|
370
|
-
pathname,
|
|
371
|
-
routeKey: ctx.routeKey,
|
|
372
|
-
hit: state.cacheHit,
|
|
373
|
-
shouldRevalidate: !!state.shouldRevalidate,
|
|
374
|
-
source: state.cacheSource,
|
|
375
|
-
});
|
|
376
419
|
safeEmit(telemetry, {
|
|
377
420
|
type: "request.end",
|
|
378
421
|
timestamp: performance.now(),
|