@rangojs/router 0.0.0-experimental.10
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/CLAUDE.md +43 -0
- package/README.md +19 -0
- package/dist/bin/rango.js +227 -0
- package/dist/vite/index.js +3039 -0
- package/package.json +171 -0
- package/skills/caching/SKILL.md +191 -0
- package/skills/debug-manifest/SKILL.md +108 -0
- package/skills/document-cache/SKILL.md +180 -0
- package/skills/fonts/SKILL.md +165 -0
- package/skills/hooks/SKILL.md +442 -0
- package/skills/intercept/SKILL.md +190 -0
- package/skills/layout/SKILL.md +213 -0
- package/skills/links/SKILL.md +180 -0
- package/skills/loader/SKILL.md +246 -0
- package/skills/middleware/SKILL.md +202 -0
- package/skills/mime-routes/SKILL.md +124 -0
- package/skills/parallel/SKILL.md +228 -0
- package/skills/prerender/SKILL.md +283 -0
- package/skills/rango/SKILL.md +54 -0
- package/skills/response-routes/SKILL.md +358 -0
- package/skills/route/SKILL.md +173 -0
- package/skills/router-setup/SKILL.md +346 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +78 -0
- package/skills/typesafety/SKILL.md +394 -0
- package/src/__internal.ts +175 -0
- package/src/bin/rango.ts +24 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +913 -0
- package/src/browser/navigation-client.ts +165 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +600 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +346 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/mount-context.ts +32 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +203 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +140 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +352 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/segment-structure-assert.ts +67 -0
- package/src/browser/server-action-bridge.ts +762 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +478 -0
- package/src/build/generate-manifest.ts +377 -0
- package/src/build/generate-route-types.ts +828 -0
- package/src/build/index.ts +36 -0
- package/src/build/route-trie.ts +239 -0
- package/src/cache/cache-scope.ts +563 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +392 -0
- package/src/client.rsc.tsx +83 -0
- package/src/client.tsx +643 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/debug.ts +233 -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 +295 -0
- package/src/handle.ts +130 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/host/cookie-handler.ts +159 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +56 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +330 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +138 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +202 -0
- package/src/href-context.ts +33 -0
- package/src/index.rsc.ts +121 -0
- package/src/index.ts +165 -0
- package/src/loader.rsc.ts +207 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/prerender/param-hash.ts +35 -0
- package/src/prerender/store.ts +40 -0
- package/src/prerender.ts +156 -0
- package/src/reverse.ts +267 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +193 -0
- package/src/route-definition.ts +1431 -0
- package/src/route-map-builder.ts +242 -0
- package/src/route-types.ts +220 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/intercept-resolution.ts +387 -0
- package/src/router/loader-resolution.ts +327 -0
- package/src/router/manifest.ts +216 -0
- package/src/router/match-api.ts +621 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +382 -0
- package/src/router/match-middleware/cache-store.ts +276 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +281 -0
- package/src/router/match-middleware/segment-resolution.ts +184 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +213 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.ts +791 -0
- package/src/router/pattern-matching.ts +407 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +301 -0
- package/src/router/segment-resolution.ts +1315 -0
- package/src/router/trie-matching.ts +172 -0
- package/src/router/types.ts +163 -0
- package/src/router.gen.ts +6 -0
- package/src/router.ts +2423 -0
- package/src/rsc/handler.ts +1443 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +236 -0
- package/src/segment-system.tsx +442 -0
- package/src/server/context.ts +466 -0
- package/src/server/handle-store.ts +229 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +171 -0
- package/src/ssr/index.tsx +296 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +59 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1757 -0
- package/src/urls.gen.ts +8 -0
- package/src/urls.ts +1282 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +426 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/expose-prerender-handler-id.ts +429 -0
- package/src/vite/index.ts +2068 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +114 -0
|
@@ -0,0 +1,621 @@
|
|
|
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 {
|
|
10
|
+
RouteNotFoundError,
|
|
11
|
+
} from "../errors";
|
|
12
|
+
import {
|
|
13
|
+
createErrorInfo,
|
|
14
|
+
createErrorSegment,
|
|
15
|
+
findNearestErrorBoundary as findErrorBoundary,
|
|
16
|
+
} from "./error-handling.js";
|
|
17
|
+
import { createHandlerContext } from "./handler-context.js";
|
|
18
|
+
import { setupLoaderAccess } from "./loader-resolution.js";
|
|
19
|
+
import { loadManifest, clearManifestCache } from "./manifest.js";
|
|
20
|
+
import { collectRouteMiddleware } from "./middleware.js";
|
|
21
|
+
import { traverseBack } from "./pattern-matching.js";
|
|
22
|
+
import { DefaultErrorFallback } from "../default-error-boundary.js";
|
|
23
|
+
import {
|
|
24
|
+
EntryData,
|
|
25
|
+
LoaderEntry,
|
|
26
|
+
getContext,
|
|
27
|
+
InterceptSelectorContext,
|
|
28
|
+
type MetricsStore,
|
|
29
|
+
} from "../server/context";
|
|
30
|
+
import {
|
|
31
|
+
getGlobalRouteMap,
|
|
32
|
+
} from "../route-map-builder.js";
|
|
33
|
+
import type {
|
|
34
|
+
ErrorBoundaryHandler,
|
|
35
|
+
ErrorInfo,
|
|
36
|
+
HandlerContext,
|
|
37
|
+
MatchResult,
|
|
38
|
+
ResolvedSegment,
|
|
39
|
+
} from "../types";
|
|
40
|
+
import type { ReactNode } from "react";
|
|
41
|
+
import type { MatchContext, ActionContext as MatchActionContext } from "./match-context.js";
|
|
42
|
+
import type { MatchApiDeps, ActionContext } from "./types.js";
|
|
43
|
+
import type { InterceptEntry } from "../server/context";
|
|
44
|
+
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
45
|
+
import { getRequestContext } from "../server/request-context.js";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create match context for full requests (document/SSR).
|
|
49
|
+
*/
|
|
50
|
+
export async function createMatchContextForFull<TEnv>(
|
|
51
|
+
request: Request,
|
|
52
|
+
env: TEnv,
|
|
53
|
+
deps: MatchApiDeps<TEnv>,
|
|
54
|
+
findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
|
|
55
|
+
): Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }> {
|
|
56
|
+
const url = new URL(request.url);
|
|
57
|
+
const pathname = url.pathname;
|
|
58
|
+
|
|
59
|
+
const metricsStore = deps.getMetricsStore();
|
|
60
|
+
|
|
61
|
+
const routeMatchStart = metricsStore ? performance.now() : 0;
|
|
62
|
+
const matched = deps.findMatch(pathname, metricsStore);
|
|
63
|
+
if (metricsStore) {
|
|
64
|
+
metricsStore.metrics.push({
|
|
65
|
+
label: "route-matching",
|
|
66
|
+
duration: performance.now() - routeMatchStart,
|
|
67
|
+
startTime: routeMatchStart - metricsStore.requestStart,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!matched) {
|
|
72
|
+
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
73
|
+
cause: { pathname, method: request.method },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (matched.redirectTo) {
|
|
78
|
+
return {
|
|
79
|
+
type: "redirect",
|
|
80
|
+
redirectUrl: matched.redirectTo + url.search,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const manifestStart = metricsStore ? performance.now() : 0;
|
|
85
|
+
const manifestEntry = await loadManifest(
|
|
86
|
+
matched.entry,
|
|
87
|
+
matched.routeKey,
|
|
88
|
+
pathname,
|
|
89
|
+
metricsStore,
|
|
90
|
+
true,
|
|
91
|
+
);
|
|
92
|
+
if (metricsStore) {
|
|
93
|
+
metricsStore.metrics.push({
|
|
94
|
+
label: "manifest-loading",
|
|
95
|
+
duration: performance.now() - manifestStart,
|
|
96
|
+
startTime: manifestStart - metricsStore.requestStart,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const routeMiddleware = collectRouteMiddleware(
|
|
101
|
+
traverseBack(manifestEntry),
|
|
102
|
+
matched.params,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const bindings = (env as any)?.Bindings ?? env;
|
|
106
|
+
|
|
107
|
+
const handlerContext = createHandlerContext(
|
|
108
|
+
matched.params,
|
|
109
|
+
request,
|
|
110
|
+
url.searchParams,
|
|
111
|
+
pathname,
|
|
112
|
+
url,
|
|
113
|
+
bindings,
|
|
114
|
+
getGlobalRouteMap(),
|
|
115
|
+
matched.routeKey,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const loaderPromises = new Map<string, Promise<any>>();
|
|
119
|
+
setupLoaderAccess(handlerContext, loaderPromises);
|
|
120
|
+
|
|
121
|
+
const Store = getContext().getOrCreateStore(matched.routeKey);
|
|
122
|
+
Store.run = <T>(fn: () => T | Promise<T>) =>
|
|
123
|
+
getContext().runWithStore(
|
|
124
|
+
Store,
|
|
125
|
+
Store.namespace || "#router",
|
|
126
|
+
Store.parent,
|
|
127
|
+
fn,
|
|
128
|
+
);
|
|
129
|
+
if (metricsStore) {
|
|
130
|
+
Store.metrics = metricsStore;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const entries = [...traverseBack(manifestEntry)];
|
|
134
|
+
let cacheScope: CacheScope | null = null;
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (entry.cache) {
|
|
137
|
+
cacheScope = createCacheScope(entry.cache, cacheScope);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
request,
|
|
143
|
+
url,
|
|
144
|
+
pathname,
|
|
145
|
+
env,
|
|
146
|
+
bindings,
|
|
147
|
+
clientSegmentIds: [],
|
|
148
|
+
clientSegmentSet: new Set(),
|
|
149
|
+
stale: false,
|
|
150
|
+
prevUrl: url,
|
|
151
|
+
prevParams: {},
|
|
152
|
+
prevMatch: null,
|
|
153
|
+
matched,
|
|
154
|
+
manifestEntry,
|
|
155
|
+
entries,
|
|
156
|
+
routeKey: matched.routeKey,
|
|
157
|
+
localRouteName: matched.routeKey.includes(".")
|
|
158
|
+
? matched.routeKey.split(".").pop()!
|
|
159
|
+
: matched.routeKey,
|
|
160
|
+
handlerContext,
|
|
161
|
+
loaderPromises,
|
|
162
|
+
routeMap: getGlobalRouteMap(),
|
|
163
|
+
metricsStore,
|
|
164
|
+
Store,
|
|
165
|
+
interceptContextMatch: null,
|
|
166
|
+
interceptSelectorContext: {
|
|
167
|
+
from: url,
|
|
168
|
+
to: url,
|
|
169
|
+
params: matched.params,
|
|
170
|
+
request,
|
|
171
|
+
env,
|
|
172
|
+
segments: { path: [], ids: [] },
|
|
173
|
+
},
|
|
174
|
+
isSameRouteNavigation: false,
|
|
175
|
+
interceptResult: null,
|
|
176
|
+
cacheScope,
|
|
177
|
+
isIntercept: false,
|
|
178
|
+
actionContext: undefined,
|
|
179
|
+
isAction: false,
|
|
180
|
+
routeMiddleware,
|
|
181
|
+
isFullMatch: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create match context for partial requests (navigation/actions).
|
|
187
|
+
*/
|
|
188
|
+
export async function createMatchContextForPartial<TEnv>(
|
|
189
|
+
request: Request,
|
|
190
|
+
env: TEnv,
|
|
191
|
+
deps: MatchApiDeps<TEnv>,
|
|
192
|
+
findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
|
|
193
|
+
actionContext?: ActionContext,
|
|
194
|
+
): Promise<MatchContext<TEnv> | null> {
|
|
195
|
+
const url = new URL(request.url);
|
|
196
|
+
const pathname = url.pathname;
|
|
197
|
+
|
|
198
|
+
const requestStartTime = performance.now();
|
|
199
|
+
const metricsStore = deps.getMetricsStore();
|
|
200
|
+
|
|
201
|
+
const clientSegmentIds =
|
|
202
|
+
url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
|
|
203
|
+
const stale = url.searchParams.get("_rsc_stale") === "true";
|
|
204
|
+
const previousUrl =
|
|
205
|
+
request.headers.get("X-RSC-Router-Client-Path") ||
|
|
206
|
+
request.headers.get("Referer");
|
|
207
|
+
const interceptSourceUrl = request.headers.get(
|
|
208
|
+
"X-RSC-Router-Intercept-Source",
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// HMR: clear manifest cache so stale handler references are discarded
|
|
212
|
+
if (request.headers.get("X-RSC-HMR")) {
|
|
213
|
+
clearManifestCache();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!previousUrl) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const prevUrl = new URL(previousUrl, url.origin);
|
|
221
|
+
const interceptContextUrl = interceptSourceUrl
|
|
222
|
+
? new URL(interceptSourceUrl, url.origin)
|
|
223
|
+
: prevUrl;
|
|
224
|
+
|
|
225
|
+
const routeMatchStart = metricsStore ? performance.now() : 0;
|
|
226
|
+
const prevMatch = deps.findMatch(prevUrl.pathname);
|
|
227
|
+
const prevParams = prevMatch?.params || {};
|
|
228
|
+
const interceptContextMatch = interceptSourceUrl
|
|
229
|
+
? deps.findMatch(interceptContextUrl.pathname)
|
|
230
|
+
: prevMatch;
|
|
231
|
+
|
|
232
|
+
const matched = deps.findMatch(pathname, metricsStore);
|
|
233
|
+
|
|
234
|
+
if (metricsStore) {
|
|
235
|
+
metricsStore.metrics.push({
|
|
236
|
+
label: "route-matching",
|
|
237
|
+
duration: performance.now() - routeMatchStart,
|
|
238
|
+
startTime: routeMatchStart - metricsStore.requestStart,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!matched) {
|
|
243
|
+
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
244
|
+
cause: { pathname, method: request.method, previousUrl },
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (matched.redirectTo) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (prevMatch && prevMatch.entry !== matched.entry && !matched.pr) {
|
|
253
|
+
console.log(
|
|
254
|
+
`[Router.matchPartial] Route group changed: ${prevMatch.routeKey} → ${matched.routeKey}`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const manifestStart = metricsStore ? performance.now() : 0;
|
|
259
|
+
const manifestEntry = await loadManifest(
|
|
260
|
+
matched.entry,
|
|
261
|
+
matched.routeKey,
|
|
262
|
+
pathname,
|
|
263
|
+
metricsStore,
|
|
264
|
+
false,
|
|
265
|
+
);
|
|
266
|
+
if (metricsStore) {
|
|
267
|
+
metricsStore.metrics.push({
|
|
268
|
+
label: "manifest-loading",
|
|
269
|
+
duration: performance.now() - manifestStart,
|
|
270
|
+
startTime: manifestStart - metricsStore.requestStart,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const routeMiddleware = collectRouteMiddleware(
|
|
275
|
+
traverseBack(manifestEntry),
|
|
276
|
+
matched.params,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const bindings = (env as any)?.Bindings ?? env;
|
|
280
|
+
const handlerContext = createHandlerContext(
|
|
281
|
+
matched.params,
|
|
282
|
+
request,
|
|
283
|
+
url.searchParams,
|
|
284
|
+
pathname,
|
|
285
|
+
url,
|
|
286
|
+
bindings,
|
|
287
|
+
getGlobalRouteMap(),
|
|
288
|
+
matched.routeKey,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const clientSegmentSet = new Set(clientSegmentIds);
|
|
292
|
+
console.log(
|
|
293
|
+
`[Router.matchPartial] Client segments:`,
|
|
294
|
+
Array.from(clientSegmentSet),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const loaderPromises = new Map<string, Promise<any>>();
|
|
298
|
+
setupLoaderAccess(handlerContext, loaderPromises);
|
|
299
|
+
|
|
300
|
+
const Store = getContext().getOrCreateStore(matched.routeKey);
|
|
301
|
+
Store.run = <T>(fn: () => T | Promise<T>) =>
|
|
302
|
+
getContext().runWithStore(
|
|
303
|
+
Store,
|
|
304
|
+
Store.namespace || "#router",
|
|
305
|
+
Store.parent,
|
|
306
|
+
fn,
|
|
307
|
+
);
|
|
308
|
+
if (metricsStore) {
|
|
309
|
+
Store.metrics = metricsStore;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const isSameRouteNavigation = !!(
|
|
313
|
+
interceptContextMatch &&
|
|
314
|
+
interceptContextMatch.routeKey === matched.routeKey
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
if (interceptSourceUrl) {
|
|
318
|
+
console.log(`[Router.matchPartial] Intercept context detected:`);
|
|
319
|
+
console.log(` - Current URL: ${pathname}`);
|
|
320
|
+
console.log(` - Intercept source: ${interceptSourceUrl}`);
|
|
321
|
+
console.log(` - Context match: ${interceptContextMatch?.routeKey}`);
|
|
322
|
+
console.log(` - Current route: ${matched.routeKey}`);
|
|
323
|
+
console.log(` - Same route navigation: ${isSameRouteNavigation}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const localRouteName = matched.routeKey.includes(".")
|
|
327
|
+
? matched.routeKey.split(".").pop()!
|
|
328
|
+
: matched.routeKey;
|
|
329
|
+
|
|
330
|
+
const filteredSegmentIds = clientSegmentIds.filter((id) => {
|
|
331
|
+
if (id.includes(".@")) return false;
|
|
332
|
+
if (/D\d+\./.test(id)) return false;
|
|
333
|
+
return true;
|
|
334
|
+
});
|
|
335
|
+
const interceptSelectorContext: InterceptSelectorContext = {
|
|
336
|
+
from: prevUrl,
|
|
337
|
+
to: url,
|
|
338
|
+
params: matched.params,
|
|
339
|
+
request,
|
|
340
|
+
env,
|
|
341
|
+
segments: {
|
|
342
|
+
path: prevUrl.pathname.split("/").filter(Boolean),
|
|
343
|
+
ids: filteredSegmentIds,
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
const isAction = !!actionContext;
|
|
347
|
+
|
|
348
|
+
const clientHasInterceptSegments = [...clientSegmentSet].some((id) =>
|
|
349
|
+
id.includes(".@"),
|
|
350
|
+
);
|
|
351
|
+
const skipInterceptForAction = isAction && !clientHasInterceptSegments;
|
|
352
|
+
const interceptResult =
|
|
353
|
+
isSameRouteNavigation || skipInterceptForAction
|
|
354
|
+
? null
|
|
355
|
+
: findInterceptForRoute(
|
|
356
|
+
matched.routeKey,
|
|
357
|
+
manifestEntry.parent,
|
|
358
|
+
interceptSelectorContext,
|
|
359
|
+
isAction,
|
|
360
|
+
) ||
|
|
361
|
+
(localRouteName !== matched.routeKey
|
|
362
|
+
? findInterceptForRoute(
|
|
363
|
+
localRouteName,
|
|
364
|
+
manifestEntry.parent,
|
|
365
|
+
interceptSelectorContext,
|
|
366
|
+
isAction,
|
|
367
|
+
)
|
|
368
|
+
: null);
|
|
369
|
+
|
|
370
|
+
if (
|
|
371
|
+
isSameRouteNavigation &&
|
|
372
|
+
manifestEntry.type === "route" &&
|
|
373
|
+
interceptSourceUrl
|
|
374
|
+
) {
|
|
375
|
+
console.log(
|
|
376
|
+
`[Router.matchPartial] Leaving intercept - forcing route segment render: ${manifestEntry.shortCode}`,
|
|
377
|
+
);
|
|
378
|
+
clientSegmentSet.delete(manifestEntry.shortCode);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const entries = [...traverseBack(manifestEntry)];
|
|
382
|
+
let cacheScope: CacheScope | null = null;
|
|
383
|
+
for (const entry of entries) {
|
|
384
|
+
if (entry.cache) {
|
|
385
|
+
cacheScope = createCacheScope(entry.cache, cacheScope);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const isIntercept = !!interceptResult;
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
request,
|
|
393
|
+
url,
|
|
394
|
+
pathname,
|
|
395
|
+
env,
|
|
396
|
+
bindings,
|
|
397
|
+
clientSegmentIds,
|
|
398
|
+
clientSegmentSet,
|
|
399
|
+
stale,
|
|
400
|
+
prevUrl,
|
|
401
|
+
prevParams,
|
|
402
|
+
prevMatch,
|
|
403
|
+
matched,
|
|
404
|
+
manifestEntry,
|
|
405
|
+
entries,
|
|
406
|
+
routeKey: matched.routeKey,
|
|
407
|
+
localRouteName,
|
|
408
|
+
handlerContext,
|
|
409
|
+
loaderPromises,
|
|
410
|
+
routeMap: getGlobalRouteMap(),
|
|
411
|
+
metricsStore,
|
|
412
|
+
Store,
|
|
413
|
+
interceptContextMatch,
|
|
414
|
+
interceptSelectorContext,
|
|
415
|
+
isSameRouteNavigation,
|
|
416
|
+
interceptResult,
|
|
417
|
+
cacheScope,
|
|
418
|
+
isIntercept,
|
|
419
|
+
actionContext,
|
|
420
|
+
isAction,
|
|
421
|
+
routeMiddleware,
|
|
422
|
+
isFullMatch: false,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Match an error to the nearest error boundary and return error segments.
|
|
428
|
+
*/
|
|
429
|
+
export async function matchError<TEnv>(
|
|
430
|
+
request: Request,
|
|
431
|
+
_context: TEnv,
|
|
432
|
+
error: unknown,
|
|
433
|
+
deps: MatchApiDeps<TEnv>,
|
|
434
|
+
defaultErrorBoundary: ReactNode | ErrorBoundaryHandler | undefined,
|
|
435
|
+
segmentType: ErrorInfo["segmentType"] = "route",
|
|
436
|
+
): Promise<MatchResult | null> {
|
|
437
|
+
const url = new URL(request.url);
|
|
438
|
+
const pathname = url.pathname;
|
|
439
|
+
|
|
440
|
+
console.log(`[Router.matchError] Matching error for ${pathname}`);
|
|
441
|
+
|
|
442
|
+
const matched = deps.findMatch(pathname);
|
|
443
|
+
if (!matched) {
|
|
444
|
+
console.warn(`[Router.matchError] No route matched for ${pathname}`);
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const manifestEntry = await loadManifest(
|
|
449
|
+
matched.entry,
|
|
450
|
+
matched.routeKey,
|
|
451
|
+
pathname,
|
|
452
|
+
undefined,
|
|
453
|
+
false,
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const findNearestErrorBoundary = (entry: EntryData | null) =>
|
|
457
|
+
findErrorBoundary(entry, defaultErrorBoundary);
|
|
458
|
+
|
|
459
|
+
const fallback = findNearestErrorBoundary(manifestEntry);
|
|
460
|
+
const useDefaultFallback = !fallback;
|
|
461
|
+
|
|
462
|
+
const errorInfo = createErrorInfo(
|
|
463
|
+
error,
|
|
464
|
+
manifestEntry.shortCode || "unknown",
|
|
465
|
+
segmentType,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
let entryWithBoundary: EntryData | null = null;
|
|
469
|
+
let current: EntryData | null = manifestEntry;
|
|
470
|
+
while (current) {
|
|
471
|
+
if (current.errorBoundary && current.errorBoundary.length > 0) {
|
|
472
|
+
entryWithBoundary = current;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (current.layout && current.layout.length > 0) {
|
|
477
|
+
for (const orphan of current.layout) {
|
|
478
|
+
if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
|
|
479
|
+
entryWithBoundary = orphan;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (entryWithBoundary) break;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
current = current.parent;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
let boundaryEntry: EntryData;
|
|
490
|
+
let outletEntry: EntryData;
|
|
491
|
+
|
|
492
|
+
if (entryWithBoundary) {
|
|
493
|
+
boundaryEntry = entryWithBoundary;
|
|
494
|
+
|
|
495
|
+
outletEntry = manifestEntry;
|
|
496
|
+
current = manifestEntry;
|
|
497
|
+
|
|
498
|
+
while (current) {
|
|
499
|
+
if (current.parent === boundaryEntry) {
|
|
500
|
+
outletEntry = current;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (current.parent && current.parent.layout) {
|
|
505
|
+
if (current.parent.layout.includes(boundaryEntry)) {
|
|
506
|
+
outletEntry = current;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
current = current.parent;
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
let rootEntry = manifestEntry;
|
|
515
|
+
while (rootEntry.parent) {
|
|
516
|
+
rootEntry = rootEntry.parent;
|
|
517
|
+
}
|
|
518
|
+
boundaryEntry = rootEntry;
|
|
519
|
+
outletEntry = rootEntry;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const matchedIds: string[] = [];
|
|
523
|
+
|
|
524
|
+
current = boundaryEntry;
|
|
525
|
+
const stack: {
|
|
526
|
+
shortCode: string;
|
|
527
|
+
loaderEntries: LoaderEntry[];
|
|
528
|
+
}[] = [];
|
|
529
|
+
while (current) {
|
|
530
|
+
if (current.shortCode) {
|
|
531
|
+
stack.push({
|
|
532
|
+
shortCode: current.shortCode,
|
|
533
|
+
loaderEntries: current.loader || [],
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
current = current.parent;
|
|
537
|
+
}
|
|
538
|
+
for (const item of stack.reverse()) {
|
|
539
|
+
matchedIds.push(item.shortCode);
|
|
540
|
+
for (let i = 0; i < item.loaderEntries.length; i++) {
|
|
541
|
+
const loaderId = item.loaderEntries[i].loader?.$$id || "unknown";
|
|
542
|
+
matchedIds.push(`${item.shortCode}D${i}.${loaderId}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const reqCtx = getRequestContext();
|
|
547
|
+
if (reqCtx) {
|
|
548
|
+
reqCtx.res = new Response(null, {
|
|
549
|
+
status: 500,
|
|
550
|
+
headers: reqCtx.res.headers,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const effectiveFallback = fallback || DefaultErrorFallback;
|
|
555
|
+
const errorSegment = createErrorSegment(
|
|
556
|
+
errorInfo,
|
|
557
|
+
effectiveFallback,
|
|
558
|
+
outletEntry,
|
|
559
|
+
matched.params,
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
if (useDefaultFallback) {
|
|
563
|
+
console.log(
|
|
564
|
+
`[Router.matchError] Using default error boundary (no user-defined boundary found)`,
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
console.log(
|
|
569
|
+
`[Router.matchError] Boundary: ${boundaryEntry.shortCode}, outlet replaced: ${outletEntry.shortCode}`,
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
segments: [errorSegment],
|
|
574
|
+
matched: matchedIds,
|
|
575
|
+
diff: [errorSegment.id],
|
|
576
|
+
params: matched.params,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Preview match - returns route middleware without segment resolution.
|
|
582
|
+
*/
|
|
583
|
+
export async function previewMatch<TEnv>(
|
|
584
|
+
request: Request,
|
|
585
|
+
context: TEnv,
|
|
586
|
+
deps: MatchApiDeps<TEnv>,
|
|
587
|
+
): Promise<{
|
|
588
|
+
routeMiddleware?: Array<{
|
|
589
|
+
handler: import("./middleware.js").MiddlewareFn;
|
|
590
|
+
params: Record<string, string>;
|
|
591
|
+
}>;
|
|
592
|
+
} | null> {
|
|
593
|
+
const url = new URL(request.url);
|
|
594
|
+
const pathname = url.pathname;
|
|
595
|
+
|
|
596
|
+
const matched = deps.findMatch(pathname);
|
|
597
|
+
if (!matched) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (matched.redirectTo) {
|
|
602
|
+
return { routeMiddleware: undefined };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const manifestEntry = await loadManifest(
|
|
606
|
+
matched.entry,
|
|
607
|
+
matched.routeKey,
|
|
608
|
+
pathname,
|
|
609
|
+
undefined,
|
|
610
|
+
false,
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
const routeMiddleware = collectRouteMiddleware(
|
|
614
|
+
traverseBack(manifestEntry),
|
|
615
|
+
matched.params,
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
return {
|
|
619
|
+
routeMiddleware: routeMiddleware.length > 0 ? routeMiddleware : undefined,
|
|
620
|
+
};
|
|
621
|
+
}
|