@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80
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 +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +4960 -935
- package/package.json +70 -60
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +764 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -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/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +87 -11
- package/src/browser/react/context.ts +11 -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 +6 -1
- 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 +30 -126
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- 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 +76 -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 +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -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 +418 -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 +618 -0
- package/src/build/route-types/scan-filter.ts +85 -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 +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -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 +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +135 -301
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +251 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- 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 +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +77 -8
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -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 +108 -93
- package/src/router/match-middleware/cache-lookup.ts +460 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +748 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1379 -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 +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +391 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +356 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -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 +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -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 +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -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 -53
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -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 +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -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/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +918 -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 +221 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- 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/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Match API
|
|
3
|
+
*
|
|
4
|
+
* Extracted from createRouter closure. Contains match context creation functions
|
|
5
|
+
* and the matchError function for error boundary resolution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CacheScope, createCacheScope } from "../cache/cache-scope.js";
|
|
9
|
+
import { RouteNotFoundError } from "../errors";
|
|
10
|
+
import {
|
|
11
|
+
createErrorInfo,
|
|
12
|
+
createErrorSegment,
|
|
13
|
+
findNearestErrorBoundary as findErrorBoundary,
|
|
14
|
+
} from "./error-handling.js";
|
|
15
|
+
import {
|
|
16
|
+
createHandlerContext,
|
|
17
|
+
stripInternalParams,
|
|
18
|
+
} from "./handler-context.js";
|
|
19
|
+
import { setupLoaderAccess } from "./loader-resolution.js";
|
|
20
|
+
import { loadManifest, clearManifestCache } from "./manifest.js";
|
|
21
|
+
import { collectRouteMiddleware } from "./middleware.js";
|
|
22
|
+
import { traverseBack } from "./pattern-matching.js";
|
|
23
|
+
import { DefaultErrorFallback } from "../default-error-boundary.js";
|
|
24
|
+
import {
|
|
25
|
+
EntryData,
|
|
26
|
+
LoaderEntry,
|
|
27
|
+
getContext,
|
|
28
|
+
InterceptSelectorContext,
|
|
29
|
+
} from "../server/context";
|
|
30
|
+
import type { ErrorBoundaryHandler, ErrorInfo, MatchResult } from "../types";
|
|
31
|
+
import type { ReactNode } from "react";
|
|
32
|
+
import type { MatchContext } from "./match-context.js";
|
|
33
|
+
import type { MatchApiDeps, ActionContext } from "./types.js";
|
|
34
|
+
import {
|
|
35
|
+
getRequestContext,
|
|
36
|
+
setRequestContextPrevRouteKey,
|
|
37
|
+
} from "../server/request-context.js";
|
|
38
|
+
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
39
|
+
import type { DefaultRouteName } from "../types/global-namespace.js";
|
|
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";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create match context for full requests (document/SSR).
|
|
50
|
+
*/
|
|
51
|
+
export async function createMatchContextForFull<TEnv>(
|
|
52
|
+
request: Request,
|
|
53
|
+
env: TEnv,
|
|
54
|
+
deps: MatchApiDeps<TEnv>,
|
|
55
|
+
findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
|
|
56
|
+
): Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }> {
|
|
57
|
+
const url = new URL(request.url);
|
|
58
|
+
const pathname = url.pathname;
|
|
59
|
+
|
|
60
|
+
const metricsStore = deps.getMetricsStore();
|
|
61
|
+
|
|
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
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!result) {
|
|
71
|
+
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
72
|
+
cause: { pathname, method: request.method },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (result.type === "redirect") {
|
|
77
|
+
return {
|
|
78
|
+
type: "redirect",
|
|
79
|
+
redirectUrl: result.redirectTo + url.search,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const snapshot = result.snapshot;
|
|
84
|
+
|
|
85
|
+
const { matched } = snapshot;
|
|
86
|
+
|
|
87
|
+
// Backward compat: downstream middleware reads matched.pt
|
|
88
|
+
if (snapshot.isPassthrough) {
|
|
89
|
+
matched.pt = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Clean URL without internal _rsc* params for userland access
|
|
93
|
+
const cleanUrl = stripInternalParams(url);
|
|
94
|
+
|
|
95
|
+
const handlerContext = createHandlerContext(
|
|
96
|
+
matched.params,
|
|
97
|
+
request,
|
|
98
|
+
cleanUrl.searchParams,
|
|
99
|
+
pathname,
|
|
100
|
+
cleanUrl,
|
|
101
|
+
env,
|
|
102
|
+
deps.getRouteMap(),
|
|
103
|
+
matched.routeKey,
|
|
104
|
+
matched.responseType,
|
|
105
|
+
matched.pt === true,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const loaderPromises = new Map<string, Promise<any>>();
|
|
109
|
+
setupLoaderAccess(handlerContext, loaderPromises);
|
|
110
|
+
|
|
111
|
+
const Store = getContext().getOrCreateStore(matched.routeKey);
|
|
112
|
+
Store.run = <T>(fn: () => T | Promise<T>) =>
|
|
113
|
+
getContext().runWithStore(
|
|
114
|
+
Store,
|
|
115
|
+
Store.namespace || "#router",
|
|
116
|
+
Store.parent,
|
|
117
|
+
fn,
|
|
118
|
+
);
|
|
119
|
+
if (metricsStore) {
|
|
120
|
+
Store.metrics = metricsStore;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
request,
|
|
125
|
+
url: cleanUrl,
|
|
126
|
+
pathname,
|
|
127
|
+
env,
|
|
128
|
+
clientSegmentIds: [],
|
|
129
|
+
clientSegmentSet: new Set(),
|
|
130
|
+
stale: false,
|
|
131
|
+
prevUrl: cleanUrl,
|
|
132
|
+
prevParams: {},
|
|
133
|
+
prevMatch: null,
|
|
134
|
+
matched,
|
|
135
|
+
manifestEntry: snapshot.manifestEntry,
|
|
136
|
+
entries: snapshot.entries,
|
|
137
|
+
routeKey: matched.routeKey,
|
|
138
|
+
localRouteName: snapshot.localRouteName,
|
|
139
|
+
handlerContext,
|
|
140
|
+
loaderPromises,
|
|
141
|
+
routeMap: deps.getRouteMap(),
|
|
142
|
+
metricsStore,
|
|
143
|
+
Store,
|
|
144
|
+
interceptContextMatch: null,
|
|
145
|
+
interceptSelectorContext: {
|
|
146
|
+
from: cleanUrl,
|
|
147
|
+
to: cleanUrl,
|
|
148
|
+
params: matched.params,
|
|
149
|
+
request,
|
|
150
|
+
env,
|
|
151
|
+
segments: { path: [], ids: [] },
|
|
152
|
+
toRouteName:
|
|
153
|
+
matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
|
|
154
|
+
? (matched.routeKey as DefaultRouteName)
|
|
155
|
+
: undefined,
|
|
156
|
+
},
|
|
157
|
+
isSameRouteNavigation: false,
|
|
158
|
+
interceptResult: null,
|
|
159
|
+
cacheScope: snapshot.cacheScope,
|
|
160
|
+
isIntercept: false,
|
|
161
|
+
actionContext: undefined,
|
|
162
|
+
isAction: false,
|
|
163
|
+
routeMiddleware: snapshot.routeMiddleware,
|
|
164
|
+
isFullMatch: true,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create match context for partial requests (navigation/actions).
|
|
170
|
+
*/
|
|
171
|
+
export async function createMatchContextForPartial<TEnv>(
|
|
172
|
+
request: Request,
|
|
173
|
+
env: TEnv,
|
|
174
|
+
deps: MatchApiDeps<TEnv>,
|
|
175
|
+
findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
|
|
176
|
+
actionContext?: ActionContext,
|
|
177
|
+
): Promise<MatchContext<TEnv> | null> {
|
|
178
|
+
const url = new URL(request.url);
|
|
179
|
+
const pathname = url.pathname;
|
|
180
|
+
|
|
181
|
+
const metricsStore = deps.getMetricsStore();
|
|
182
|
+
|
|
183
|
+
const isHmr = !!request.headers.get("X-RSC-HMR");
|
|
184
|
+
|
|
185
|
+
// HMR: clear manifest cache so stale handler references are discarded
|
|
186
|
+
if (isHmr) {
|
|
187
|
+
clearManifestCache();
|
|
188
|
+
}
|
|
189
|
+
|
|
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;
|
|
202
|
+
|
|
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
|
+
});
|
|
213
|
+
|
|
214
|
+
if (!result) {
|
|
215
|
+
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
216
|
+
cause: { pathname, method: request.method },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (result.type === "redirect") {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
snapshot = result.snapshot;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const { matched } = snapshot;
|
|
228
|
+
|
|
229
|
+
// Backward compat: downstream middleware reads matched.pt
|
|
230
|
+
if (snapshot.isPassthrough) {
|
|
231
|
+
matched.pt = true;
|
|
232
|
+
}
|
|
233
|
+
|
|
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;
|
|
240
|
+
}
|
|
241
|
+
|
|
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).
|
|
246
|
+
if (metricsStore) {
|
|
247
|
+
const isReuse = !!classifiedRoute;
|
|
248
|
+
metricsStore.metrics.push({
|
|
249
|
+
label: isReuse ? "route-matching:nav" : "route-matching",
|
|
250
|
+
duration: performance.now() - routeMatchStart,
|
|
251
|
+
startTime: routeMatchStart - metricsStore.requestStart,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
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
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Clean URL without internal _rsc* params for userland access
|
|
263
|
+
const cleanUrl = stripInternalParams(url);
|
|
264
|
+
|
|
265
|
+
const handlerContext = createHandlerContext(
|
|
266
|
+
matched.params,
|
|
267
|
+
request,
|
|
268
|
+
cleanUrl.searchParams,
|
|
269
|
+
pathname,
|
|
270
|
+
cleanUrl,
|
|
271
|
+
env,
|
|
272
|
+
deps.getRouteMap(),
|
|
273
|
+
matched.routeKey,
|
|
274
|
+
matched.responseType,
|
|
275
|
+
matched.pt === true,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
debugLog("matchPartial", "client segments", {
|
|
279
|
+
segments: Array.from(nav.clientSegmentSet),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const loaderPromises = new Map<string, Promise<any>>();
|
|
283
|
+
setupLoaderAccess(handlerContext, loaderPromises);
|
|
284
|
+
|
|
285
|
+
const Store = getContext().getOrCreateStore(matched.routeKey);
|
|
286
|
+
Store.run = <T>(fn: () => T | Promise<T>) =>
|
|
287
|
+
getContext().runWithStore(
|
|
288
|
+
Store,
|
|
289
|
+
Store.namespace || "#router",
|
|
290
|
+
Store.parent,
|
|
291
|
+
fn,
|
|
292
|
+
);
|
|
293
|
+
if (metricsStore) {
|
|
294
|
+
Store.metrics = metricsStore;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (nav.hasInterceptSource) {
|
|
298
|
+
debugLog("matchPartial.intercept", "intercept context detected", {
|
|
299
|
+
currentUrl: pathname,
|
|
300
|
+
interceptSource: nav.interceptContextUrl.href,
|
|
301
|
+
contextRoute: nav.interceptContextMatch?.routeKey,
|
|
302
|
+
currentRoute: matched.routeKey,
|
|
303
|
+
sameRouteNavigation: nav.isSameRouteNavigation,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Store previous route key on the request context for revalidation
|
|
308
|
+
// fromRouteName. Uses effectiveFromMatch so intercept-source navigations
|
|
309
|
+
// see the intercept origin route, not the plain previous URL route.
|
|
310
|
+
setRequestContextPrevRouteKey(nav.effectiveFromMatch?.routeKey);
|
|
311
|
+
|
|
312
|
+
const interceptSelectorContext: InterceptSelectorContext = {
|
|
313
|
+
from: nav.effectiveFromUrl,
|
|
314
|
+
to: cleanUrl,
|
|
315
|
+
params: matched.params,
|
|
316
|
+
request,
|
|
317
|
+
env,
|
|
318
|
+
segments: {
|
|
319
|
+
path: nav.effectiveFromUrl.pathname.split("/").filter(Boolean),
|
|
320
|
+
ids: nav.filteredSegmentIds,
|
|
321
|
+
},
|
|
322
|
+
fromRouteName:
|
|
323
|
+
nav.effectiveFromMatch?.routeKey &&
|
|
324
|
+
!isAutoGeneratedRouteName(nav.effectiveFromMatch.routeKey)
|
|
325
|
+
? (nav.effectiveFromMatch.routeKey as DefaultRouteName)
|
|
326
|
+
: undefined,
|
|
327
|
+
toRouteName:
|
|
328
|
+
matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
|
|
329
|
+
? (matched.routeKey as DefaultRouteName)
|
|
330
|
+
: undefined,
|
|
331
|
+
};
|
|
332
|
+
const isAction = !!actionContext;
|
|
333
|
+
|
|
334
|
+
const clientHasInterceptSegments = [...nav.clientSegmentSet].some((id) =>
|
|
335
|
+
id.includes(".@"),
|
|
336
|
+
);
|
|
337
|
+
const skipInterceptForAction = isAction && !clientHasInterceptSegments;
|
|
338
|
+
const interceptResult =
|
|
339
|
+
nav.isSameRouteNavigation || skipInterceptForAction
|
|
340
|
+
? null
|
|
341
|
+
: findInterceptForRoute(
|
|
342
|
+
matched.routeKey,
|
|
343
|
+
snapshot.manifestEntry.parent,
|
|
344
|
+
interceptSelectorContext,
|
|
345
|
+
isAction,
|
|
346
|
+
) ||
|
|
347
|
+
(snapshot.localRouteName !== matched.routeKey
|
|
348
|
+
? findInterceptForRoute(
|
|
349
|
+
snapshot.localRouteName,
|
|
350
|
+
snapshot.manifestEntry.parent,
|
|
351
|
+
interceptSelectorContext,
|
|
352
|
+
isAction,
|
|
353
|
+
)
|
|
354
|
+
: null);
|
|
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
|
+
|
|
360
|
+
if (
|
|
361
|
+
nav.isSameRouteNavigation &&
|
|
362
|
+
snapshot.manifestEntry.type === "route" &&
|
|
363
|
+
nav.hasInterceptSource
|
|
364
|
+
) {
|
|
365
|
+
debugLog("matchPartial.intercept", "forcing route segment render", {
|
|
366
|
+
segmentId: snapshot.manifestEntry.shortCode,
|
|
367
|
+
});
|
|
368
|
+
clientSegmentSet.delete(snapshot.manifestEntry.shortCode);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const isIntercept = !!interceptResult;
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
request,
|
|
375
|
+
url: cleanUrl,
|
|
376
|
+
pathname,
|
|
377
|
+
env,
|
|
378
|
+
clientSegmentIds: nav.clientSegmentIds,
|
|
379
|
+
clientSegmentSet,
|
|
380
|
+
stale: nav.stale,
|
|
381
|
+
prevUrl: nav.prevUrl,
|
|
382
|
+
prevParams: nav.prevParams,
|
|
383
|
+
prevMatch: nav.prevMatch,
|
|
384
|
+
matched,
|
|
385
|
+
manifestEntry: snapshot.manifestEntry,
|
|
386
|
+
entries: snapshot.entries,
|
|
387
|
+
routeKey: matched.routeKey,
|
|
388
|
+
localRouteName: snapshot.localRouteName,
|
|
389
|
+
handlerContext,
|
|
390
|
+
loaderPromises,
|
|
391
|
+
routeMap: deps.getRouteMap(),
|
|
392
|
+
metricsStore,
|
|
393
|
+
Store,
|
|
394
|
+
interceptContextMatch: nav.interceptContextMatch,
|
|
395
|
+
interceptSelectorContext,
|
|
396
|
+
isSameRouteNavigation: nav.isSameRouteNavigation,
|
|
397
|
+
interceptResult,
|
|
398
|
+
cacheScope: snapshot.cacheScope,
|
|
399
|
+
isIntercept,
|
|
400
|
+
actionContext,
|
|
401
|
+
isAction,
|
|
402
|
+
routeMiddleware: snapshot.routeMiddleware,
|
|
403
|
+
isFullMatch: false,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Match an error to the nearest error boundary and return error segments.
|
|
409
|
+
*/
|
|
410
|
+
export async function matchError<TEnv>(
|
|
411
|
+
request: Request,
|
|
412
|
+
_context: TEnv,
|
|
413
|
+
error: unknown,
|
|
414
|
+
deps: MatchApiDeps<TEnv>,
|
|
415
|
+
defaultErrorBoundary: ReactNode | ErrorBoundaryHandler | undefined,
|
|
416
|
+
segmentType: ErrorInfo["segmentType"] = "route",
|
|
417
|
+
): Promise<MatchResult | null> {
|
|
418
|
+
const url = new URL(request.url);
|
|
419
|
+
const pathname = url.pathname;
|
|
420
|
+
|
|
421
|
+
debugLog("matchError", "matching error", { pathname });
|
|
422
|
+
|
|
423
|
+
const matched = deps.findMatch(pathname);
|
|
424
|
+
if (!matched) {
|
|
425
|
+
debugWarn("matchError", "no route matched", { pathname });
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const manifestEntry = await loadManifest(
|
|
430
|
+
matched.entry,
|
|
431
|
+
matched.routeKey,
|
|
432
|
+
pathname,
|
|
433
|
+
undefined,
|
|
434
|
+
false,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const findNearestErrorBoundary = (entry: EntryData | null) =>
|
|
438
|
+
findErrorBoundary(entry, defaultErrorBoundary);
|
|
439
|
+
|
|
440
|
+
const fallback = findNearestErrorBoundary(manifestEntry);
|
|
441
|
+
const useDefaultFallback = !fallback;
|
|
442
|
+
|
|
443
|
+
const errorInfo = createErrorInfo(
|
|
444
|
+
error,
|
|
445
|
+
manifestEntry.shortCode || "unknown",
|
|
446
|
+
segmentType,
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
let entryWithBoundary: EntryData | null = null;
|
|
450
|
+
let current: EntryData | null = manifestEntry;
|
|
451
|
+
while (current) {
|
|
452
|
+
if (current.errorBoundary && current.errorBoundary.length > 0) {
|
|
453
|
+
entryWithBoundary = current;
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (current.layout && current.layout.length > 0) {
|
|
458
|
+
for (const orphan of current.layout) {
|
|
459
|
+
if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
|
|
460
|
+
entryWithBoundary = orphan;
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (entryWithBoundary) break;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
current = current.parent;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let boundaryEntry: EntryData;
|
|
471
|
+
let outletEntry: EntryData;
|
|
472
|
+
|
|
473
|
+
if (entryWithBoundary) {
|
|
474
|
+
boundaryEntry = entryWithBoundary;
|
|
475
|
+
|
|
476
|
+
outletEntry = manifestEntry;
|
|
477
|
+
current = manifestEntry;
|
|
478
|
+
|
|
479
|
+
while (current) {
|
|
480
|
+
if (current.parent === boundaryEntry) {
|
|
481
|
+
outletEntry = current;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (current.parent && current.parent.layout) {
|
|
486
|
+
if (current.parent.layout.includes(boundaryEntry)) {
|
|
487
|
+
outletEntry = current;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
current = current.parent;
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
let rootEntry = manifestEntry;
|
|
496
|
+
while (rootEntry.parent) {
|
|
497
|
+
rootEntry = rootEntry.parent;
|
|
498
|
+
}
|
|
499
|
+
boundaryEntry = rootEntry;
|
|
500
|
+
outletEntry = rootEntry;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const matchedIds: string[] = [];
|
|
504
|
+
|
|
505
|
+
current = boundaryEntry;
|
|
506
|
+
const stack: {
|
|
507
|
+
shortCode: string;
|
|
508
|
+
loaderEntries: LoaderEntry[];
|
|
509
|
+
}[] = [];
|
|
510
|
+
while (current) {
|
|
511
|
+
if (current.shortCode) {
|
|
512
|
+
stack.push({
|
|
513
|
+
shortCode: current.shortCode,
|
|
514
|
+
loaderEntries: current.loader || [],
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
current = current.parent;
|
|
518
|
+
}
|
|
519
|
+
for (const item of stack.reverse()) {
|
|
520
|
+
matchedIds.push(item.shortCode);
|
|
521
|
+
for (let i = 0; i < item.loaderEntries.length; i++) {
|
|
522
|
+
const loaderId = item.loaderEntries[i].loader?.$$id || "unknown";
|
|
523
|
+
matchedIds.push(`${item.shortCode}D${i}.${loaderId}`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const reqCtx = getRequestContext();
|
|
528
|
+
if (reqCtx) {
|
|
529
|
+
reqCtx._setStatus(500);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const effectiveFallback = fallback || DefaultErrorFallback;
|
|
533
|
+
const errorSegment = createErrorSegment(
|
|
534
|
+
errorInfo,
|
|
535
|
+
effectiveFallback,
|
|
536
|
+
outletEntry,
|
|
537
|
+
matched.params,
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
if (useDefaultFallback) {
|
|
541
|
+
debugLog("matchError", "using default error boundary");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
debugLog("matchError", "resolved boundary", {
|
|
545
|
+
boundarySegmentId: boundaryEntry.shortCode,
|
|
546
|
+
outletSegmentId: outletEntry.shortCode,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
segments: [errorSegment],
|
|
551
|
+
matched: matchedIds,
|
|
552
|
+
diff: [errorSegment.id],
|
|
553
|
+
params: matched.params,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* - request, url, pathname: The incoming HTTP request
|
|
46
46
|
*
|
|
47
47
|
* Environment:
|
|
48
|
-
* - env
|
|
48
|
+
* - env: Server environment (Cloudflare bindings, etc.)
|
|
49
49
|
*
|
|
50
50
|
* Client State (from RSC request headers):
|
|
51
51
|
* - clientSegmentIds: Segments the client currently has
|
|
@@ -140,7 +140,6 @@ export interface MatchContext<TEnv = any> {
|
|
|
140
140
|
|
|
141
141
|
// Environment
|
|
142
142
|
env: TEnv;
|
|
143
|
-
bindings: TEnv;
|
|
144
143
|
|
|
145
144
|
// Client state
|
|
146
145
|
clientSegmentIds: string[];
|
|
@@ -163,7 +162,7 @@ export interface MatchContext<TEnv = any> {
|
|
|
163
162
|
handlerContext: HandlerContext<any, TEnv>;
|
|
164
163
|
loaderPromises: Map<string, Promise<any>>;
|
|
165
164
|
|
|
166
|
-
// Route map for server-side ctx.
|
|
165
|
+
// Route map for server-side ctx.reverse() resolution
|
|
167
166
|
routeMap: Record<string, string>;
|
|
168
167
|
|
|
169
168
|
// Metrics
|
|
@@ -211,6 +210,9 @@ export interface MatchPipelineState {
|
|
|
211
210
|
// Whether cache should be revalidated (SWR)
|
|
212
211
|
shouldRevalidate?: boolean;
|
|
213
212
|
|
|
213
|
+
// Source of cache hit ("runtime" or "prerender")
|
|
214
|
+
cacheSource?: "runtime" | "prerender";
|
|
215
|
+
|
|
214
216
|
// Resolved segments from pipeline
|
|
215
217
|
segments: ResolvedSegment[];
|
|
216
218
|
matchedIds: string[];
|