@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126
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 +6 -4
- package/dist/bin/rango.js +3 -4
- package/dist/vite/index.js +315 -68
- package/package.json +19 -18
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/hooks/SKILL.md +2 -2
- package/skills/route/SKILL.md +6 -0
- package/skills/server-actions/SKILL.md +25 -1
- package/skills/testing/SKILL.md +17 -17
- package/skills/testing/cache-prerender.md +29 -3
- package/skills/testing/flight.md +13 -10
- package/skills/testing/render-handler.md +3 -0
- package/skills/testing/server-tree.md +1 -1
- package/skills/testing/setup.md +1 -1
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +10 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/navigation-store-handle.ts +3 -4
- package/src/browser/navigation-store.ts +0 -39
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +23 -84
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +2 -23
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -45
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +2 -1
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +10 -3
- package/src/browser/server-action-bridge.ts +51 -3
- package/src/browser/types.ts +23 -5
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/index.ts +8 -9
- package/src/build/route-trie.ts +46 -11
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +48 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +72 -45
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +10 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +0 -52
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/types.ts +0 -98
- package/src/client.rsc.tsx +4 -22
- package/src/client.tsx +19 -32
- package/src/context-var.ts +12 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +2 -12
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +0 -16
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +27 -2
- package/src/index.ts +7 -0
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +4 -15
- package/src/loader.ts +3 -9
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +23 -30
- package/src/prerender.ts +34 -0
- package/src/redirect-origin.ts +100 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-map-builder.ts +0 -16
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -31
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +25 -23
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +0 -43
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +96 -179
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +77 -60
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +5 -56
- package/src/router/prerender-match.ts +56 -51
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +14 -62
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +10 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +35 -23
- package/src/router/segment-resolution/revalidation.ts +188 -283
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +0 -22
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +66 -45
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +8 -11
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +20 -4
- package/src/rsc/helpers.ts +71 -3
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/origin-guard.ts +9 -15
- package/src/rsc/progressive-enhancement.ts +10 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-route-handler.ts +23 -18
- package/src/rsc/rsc-rendering.ts +2 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +34 -29
- package/src/rsc/types.ts +6 -3
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +29 -92
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +2 -27
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +1 -24
- package/src/testing/dispatch.ts +43 -6
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/flight-matchers.ts +0 -13
- package/src/testing/flight-normalize.ts +3 -30
- package/src/testing/flight.ts +46 -48
- package/src/testing/generated-routes.ts +1 -41
- package/src/testing/index.ts +1 -21
- package/src/testing/internal/context.ts +3 -45
- package/src/testing/internal/seed-vars.ts +0 -26
- package/src/testing/render-handler.ts +31 -61
- package/src/testing/render-route.tsx +75 -103
- package/src/testing/run-loader.ts +0 -96
- package/src/testing/run-middleware.ts +0 -26
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/error-types.ts +25 -89
- package/src/types/global-namespace.ts +4 -14
- package/src/types/handler-context.ts +28 -9
- package/src/types/index.ts +0 -10
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +28 -18
- package/src/vite/discovery/prerender-collection.ts +2 -4
- package/src/vite/discovery/state.ts +5 -0
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +35 -9
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +2 -108
- package/src/vite/router-discovery.ts +9 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
package/src/router/middleware.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
/// <reference types="vite/types/importMeta.d.ts" />
|
|
2
|
-
/**
|
|
3
|
-
* Middleware Execution
|
|
4
|
-
*
|
|
5
|
-
* True middleware that wraps the entire RSC handler.
|
|
6
|
-
* - `await next()` returns actual Response
|
|
7
|
-
* - Can modify response headers
|
|
8
|
-
* - Can catch errors from RSC rendering
|
|
9
|
-
* - Forgiving API: if middleware doesn't return, original response is used
|
|
10
|
-
*/
|
|
11
2
|
|
|
12
3
|
import { contextGet, contextSet } from "../context-var.js";
|
|
13
4
|
import { safeDecodeURIComponent } from "./url-params.js";
|
|
@@ -21,28 +12,28 @@ import type {
|
|
|
21
12
|
ResponseHolder,
|
|
22
13
|
} from "./middleware-types.js";
|
|
23
14
|
import { _getRequestContext } from "../server/request-context.js";
|
|
15
|
+
import {
|
|
16
|
+
EXTERNAL_REDIRECT_MARKER,
|
|
17
|
+
isExternalRedirect,
|
|
18
|
+
markExternalRedirect,
|
|
19
|
+
} from "../redirect-origin.js";
|
|
24
20
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
25
21
|
import { appendMetric, createMetricsStore } from "./metrics.js";
|
|
26
22
|
import { stripInternalParams } from "./handler-context.js";
|
|
27
23
|
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
28
24
|
|
|
29
|
-
// Re-export types
|
|
25
|
+
// Re-export types consumed through this module's path.
|
|
30
26
|
export type {
|
|
31
27
|
CookieOptions,
|
|
32
|
-
CollectedMiddleware,
|
|
33
|
-
MiddlewareCollectableEntry,
|
|
34
28
|
MiddlewareContext,
|
|
35
29
|
MiddlewareEntry,
|
|
36
30
|
MiddlewareFn,
|
|
37
|
-
ResponseHolder,
|
|
38
31
|
} from "./middleware-types.js";
|
|
39
|
-
export { parseCookies, serializeCookie } from "./middleware-cookies.js";
|
|
40
32
|
|
|
41
33
|
const MIDDLEWARE_METRIC_DEPTH = 1;
|
|
42
|
-
/** Ignore post-next() durations below this threshold (measurement noise). */
|
|
43
34
|
const POST_METRIC_MIN_DURATION_MS = 0.01;
|
|
44
35
|
|
|
45
|
-
function
|
|
36
|
+
function getMiddlewareMetricLabel<TEnv>(
|
|
46
37
|
entry: MiddlewareEntry<TEnv>,
|
|
47
38
|
ordinal: number,
|
|
48
39
|
): string {
|
|
@@ -50,23 +41,12 @@ function getMiddlewareMetricBase<TEnv>(
|
|
|
50
41
|
const scope = entry.pattern ?? "*";
|
|
51
42
|
|
|
52
43
|
if (handlerName) {
|
|
53
|
-
return
|
|
44
|
+
return `middleware:${handlerName}@${scope}`;
|
|
54
45
|
}
|
|
55
46
|
|
|
56
|
-
return
|
|
47
|
+
return `middleware:${scope}#${ordinal + 1}`;
|
|
57
48
|
}
|
|
58
49
|
|
|
59
|
-
function getMiddlewareMetricLabel<TEnv>(
|
|
60
|
-
entry: MiddlewareEntry<TEnv>,
|
|
61
|
-
ordinal: number,
|
|
62
|
-
): string {
|
|
63
|
-
return `middleware:${getMiddlewareMetricBase(entry, ordinal)}`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Parse a route pattern into regex and param names
|
|
68
|
-
* Supports: *, /path, /path/*, /path/:param, /path/:param/*
|
|
69
|
-
*/
|
|
70
50
|
export function parsePattern(pattern: string): {
|
|
71
51
|
regex: RegExp;
|
|
72
52
|
paramNames: string[];
|
|
@@ -261,9 +241,13 @@ export function createMiddlewareContext<TEnv>(
|
|
|
261
241
|
|
|
262
242
|
reverse:
|
|
263
243
|
reverse ??
|
|
264
|
-
((
|
|
244
|
+
((
|
|
245
|
+
name: string,
|
|
246
|
+
_params?: Record<string, string>,
|
|
247
|
+
_search?: Record<string, unknown>,
|
|
248
|
+
) => {
|
|
265
249
|
throw new Error(
|
|
266
|
-
`ctx.reverse() is not available
|
|
250
|
+
`ctx.reverse(${JSON.stringify(name)}) is not available: no route map is bound to this middleware context.`,
|
|
267
251
|
);
|
|
268
252
|
}),
|
|
269
253
|
|
|
@@ -316,6 +300,10 @@ function mergeStubHeaders(
|
|
|
316
300
|
stubOverridesNonCookie: boolean,
|
|
317
301
|
): void {
|
|
318
302
|
stub.forEach((value, name) => {
|
|
303
|
+
// The reserved external-redirect marker is internal and never a trust
|
|
304
|
+
// signal; never copy a stub value (e.g. a stray ctx.header() call) onto a
|
|
305
|
+
// browser-facing response. The opt-in is the out-of-band brand.
|
|
306
|
+
if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
319
307
|
if (name.toLowerCase() === "set-cookie") {
|
|
320
308
|
target.append(name, value);
|
|
321
309
|
} else if (stubOverridesNonCookie || !target.has(name)) {
|
|
@@ -341,12 +329,50 @@ function mergeReqCtxStub(
|
|
|
341
329
|
}
|
|
342
330
|
}
|
|
343
331
|
reqCtx.res.headers.forEach((value, name) => {
|
|
332
|
+
// Never propagate the reserved external-redirect marker (see mergeStubHeaders).
|
|
333
|
+
if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
344
334
|
if (name !== "set-cookie" && !target.has(name)) {
|
|
345
335
|
target.set(name, value);
|
|
346
336
|
}
|
|
347
337
|
});
|
|
348
338
|
}
|
|
349
339
|
|
|
340
|
+
// Clone `base` with stub headers merged into a fresh Headers (the clone keeps
|
|
341
|
+
// the body mutable for post-next() modifications). Set-Cookie is always
|
|
342
|
+
// appended; other headers obey stubOverridesNonCookie (see mergeStubHeaders).
|
|
343
|
+
// mergeReqCtx folds in RequestContext stub cookies/headers; the intercept
|
|
344
|
+
// short-circuit path passes false (its reqCtx headers are not merged here),
|
|
345
|
+
// which is the one deliberate divergence between the call sites.
|
|
346
|
+
function mergeResponse(
|
|
347
|
+
base: Response,
|
|
348
|
+
stub: Headers,
|
|
349
|
+
opts: { stubOverridesNonCookie: boolean; mergeReqCtx: boolean },
|
|
350
|
+
): Response {
|
|
351
|
+
const mergedHeaders = new Headers(base.headers);
|
|
352
|
+
// The reserved external-redirect marker is never a trust signal and must never
|
|
353
|
+
// reach the browser. The guard strips it on 3xx redirects; strip it here too so
|
|
354
|
+
// a forged value cannot ride a non-3xx middleware response (which the 3xx-only
|
|
355
|
+
// guard would not touch) to the client. The opt-in is the out-of-band brand.
|
|
356
|
+
mergedHeaders.delete(EXTERNAL_REDIRECT_MARKER);
|
|
357
|
+
mergeStubHeaders(mergedHeaders, stub, opts.stubOverridesNonCookie);
|
|
358
|
+
if (opts.mergeReqCtx) {
|
|
359
|
+
mergeReqCtxStub(mergedHeaders, _getRequestContext());
|
|
360
|
+
}
|
|
361
|
+
const merged = new Response(base.body, {
|
|
362
|
+
status: base.status,
|
|
363
|
+
statusText: base.statusText,
|
|
364
|
+
headers: mergedHeaders,
|
|
365
|
+
});
|
|
366
|
+
// Transfer the out-of-band external-redirect brand across this rebuild: a
|
|
367
|
+
// middleware short-circuit `return redirect(url, { external: true })` reaches
|
|
368
|
+
// the open-redirect guard only after this merge, and the brand lives on the
|
|
369
|
+
// Response object, not in its headers.
|
|
370
|
+
if (isExternalRedirect(base)) {
|
|
371
|
+
markExternalRedirect(merged);
|
|
372
|
+
}
|
|
373
|
+
return merged;
|
|
374
|
+
}
|
|
375
|
+
|
|
350
376
|
/**
|
|
351
377
|
* Execute middleware chain
|
|
352
378
|
*
|
|
@@ -355,7 +381,7 @@ function mergeReqCtxStub(
|
|
|
355
381
|
* - `ctx.headers` available before and after `await next()`
|
|
356
382
|
* - `ctx.header()` shorthand for setting a single header
|
|
357
383
|
* - Forgiving: if middleware doesn't return, uses the downstream response
|
|
358
|
-
* - Short-circuit: return Response to stop chain
|
|
384
|
+
* - Short-circuit: return OR throw a Response to stop chain
|
|
359
385
|
* - Error catching: try/catch around `next()` works
|
|
360
386
|
*/
|
|
361
387
|
export async function executeMiddleware<TEnv>(
|
|
@@ -385,20 +411,16 @@ export async function executeMiddleware<TEnv>(
|
|
|
385
411
|
// End of chain - call actual RSC handler
|
|
386
412
|
const response = await finalHandler();
|
|
387
413
|
|
|
388
|
-
const mergedHeaders = new Headers(response.headers);
|
|
389
|
-
mergeStubHeaders(mergedHeaders, stubResponse.headers, true);
|
|
390
|
-
mergeReqCtxStub(mergedHeaders, _getRequestContext());
|
|
391
|
-
|
|
392
414
|
if (isWebSocketUpgradeResponse(response)) {
|
|
393
415
|
responseHolder.response = response;
|
|
394
416
|
return response;
|
|
395
417
|
}
|
|
396
418
|
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
419
|
+
// Chain ran to completion: stub headers overwrite (stubOverridesNonCookie)
|
|
420
|
+
// and reqCtx stub headers are merged in.
|
|
421
|
+
responseHolder.response = mergeResponse(response, stubResponse.headers, {
|
|
422
|
+
stubOverridesNonCookie: true,
|
|
423
|
+
mergeReqCtx: true,
|
|
402
424
|
});
|
|
403
425
|
|
|
404
426
|
return responseHolder.response;
|
|
@@ -490,20 +512,18 @@ export async function executeMiddleware<TEnv>(
|
|
|
490
512
|
|
|
491
513
|
// Explicit return takes precedence (middleware short-circuit).
|
|
492
514
|
// Merge stub headers (from ctx.header before this point) and
|
|
493
|
-
// RequestContext stub headers (from
|
|
515
|
+
// RequestContext stub headers (from cookies().set()) into the
|
|
494
516
|
// returned Response so they are not lost.
|
|
495
517
|
if (result instanceof Response) {
|
|
496
518
|
if (isWebSocketUpgradeResponse(result)) {
|
|
497
519
|
responseHolder.response = result;
|
|
498
520
|
return result;
|
|
499
521
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
statusText: result.statusText,
|
|
506
|
-
headers: mergedHeaders,
|
|
522
|
+
// Explicit short-circuit: the returned Response's own headers win
|
|
523
|
+
// (stubOverridesNonCookie=false); reqCtx stub headers still merge in.
|
|
524
|
+
const merged = mergeResponse(result, stubResponse.headers, {
|
|
525
|
+
stubOverridesNonCookie: false,
|
|
526
|
+
mergeReqCtx: true,
|
|
507
527
|
});
|
|
508
528
|
responseHolder.response = merged;
|
|
509
529
|
return merged;
|
|
@@ -564,7 +584,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
564
584
|
*
|
|
565
585
|
* Intercepts use a shared stubResponse from the request context. This function:
|
|
566
586
|
* - Runs middleware in sequence with a simple next() chain
|
|
567
|
-
* - Returns Response if any middleware short-circuits (returns Response or redirects BEFORE next())
|
|
587
|
+
* - Returns Response if any middleware short-circuits (returns OR throws a Response, or redirects, BEFORE next())
|
|
568
588
|
* - Returns null if all middleware calls next() - headers set after next() remain on stubResponse
|
|
569
589
|
*
|
|
570
590
|
* @param middlewares - Array of middleware functions
|
|
@@ -658,15 +678,13 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
658
678
|
});
|
|
659
679
|
|
|
660
680
|
if (hasStubHeaders) {
|
|
661
|
-
//
|
|
662
|
-
//
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
statusText: response.statusText,
|
|
669
|
-
headers: mergedHeaders,
|
|
681
|
+
// Only fill in missing headers — the returned Response's explicit headers
|
|
682
|
+
// take precedence (stubOverridesNonCookie=false), matching executeMiddleware.
|
|
683
|
+
// mergeReqCtx=false: the intercept path deliberately does NOT merge reqCtx
|
|
684
|
+
// stub headers here (pinned by intercept-middleware-headers.test.ts).
|
|
685
|
+
return mergeResponse(response, stubResponse.headers, {
|
|
686
|
+
stubOverridesNonCookie: false,
|
|
687
|
+
mergeReqCtx: false,
|
|
670
688
|
});
|
|
671
689
|
}
|
|
672
690
|
return response;
|
|
@@ -707,7 +725,6 @@ export async function executeLoaderMiddleware<TEnv>(
|
|
|
707
725
|
regex: null,
|
|
708
726
|
paramNames: [],
|
|
709
727
|
handler,
|
|
710
|
-
mountPrefix: null,
|
|
711
728
|
} as MiddlewareEntry<TEnv>,
|
|
712
729
|
params,
|
|
713
730
|
}));
|
|
@@ -1,58 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Navigation Snapshot
|
|
3
|
-
*
|
|
4
|
-
* Pure data type representing the navigation-specific state for partial requests.
|
|
5
|
-
* Consolidates the header parsing, previous-route matching, intercept-context
|
|
6
|
-
* detection, and segment ID filtering that previously lived inline in
|
|
7
|
-
* createMatchContextForPartial (match-api.ts).
|
|
8
|
-
*
|
|
9
|
-
* resolveNavigation() is the factory: given a request + URL + current route key,
|
|
10
|
-
* it returns a NavigationSnapshot (or null if no previous URL).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
14
2
|
|
|
15
|
-
/**
|
|
16
|
-
* Snapshot of navigation state for a partial (navigation/action) request.
|
|
17
|
-
*
|
|
18
|
-
* Contains the "where are we coming from?" data: previous route, intercept
|
|
19
|
-
* source, client segment state, and derived flags.
|
|
20
|
-
*/
|
|
21
3
|
export interface NavigationSnapshot {
|
|
22
|
-
/** Previous page URL (from X-RSC-Router-Client-Path or Referer) */
|
|
23
4
|
prevUrl: URL;
|
|
24
|
-
/** Params from the previous route match */
|
|
25
5
|
prevParams: Record<string, string>;
|
|
26
|
-
/** Previous route match result (null if prev URL doesn't match any route) */
|
|
27
6
|
prevMatch: RouteMatchResult | null;
|
|
28
7
|
|
|
29
|
-
/** URL used as intercept context source */
|
|
30
8
|
interceptContextUrl: URL;
|
|
31
|
-
/** Route match for the intercept context URL */
|
|
32
9
|
interceptContextMatch: RouteMatchResult | null;
|
|
33
10
|
|
|
34
|
-
/** Raw segment IDs the client currently has */
|
|
35
11
|
clientSegmentIds: string[];
|
|
36
|
-
/** Set version for O(1) lookup */
|
|
37
12
|
clientSegmentSet: Set<string>;
|
|
38
|
-
/** Segment IDs filtered to remove parallel (.@) and loader (D\d+.) entries */
|
|
39
13
|
filteredSegmentIds: string[];
|
|
40
14
|
|
|
41
|
-
/** Whether client considers its cache stale */
|
|
42
15
|
stale: boolean;
|
|
43
16
|
|
|
44
|
-
/** Whether the intercept context route is the same as the current route */
|
|
45
17
|
isSameRouteNavigation: boolean;
|
|
46
18
|
|
|
47
|
-
/** Effective "from" URL (intercept source URL when present, else prevUrl) */
|
|
48
19
|
effectiveFromUrl: URL;
|
|
49
|
-
/** Effective "from" match (intercept source match when present, else prevMatch) */
|
|
50
20
|
effectiveFromMatch: RouteMatchResult | null;
|
|
51
21
|
|
|
52
|
-
/** Whether an intercept source header was present */
|
|
53
22
|
hasInterceptSource: boolean;
|
|
54
23
|
|
|
55
|
-
/** Whether an HMR request header was present */
|
|
56
24
|
isHmr: boolean;
|
|
57
25
|
}
|
|
58
26
|
|
|
@@ -60,23 +28,12 @@ export interface ResolveNavigationDeps {
|
|
|
60
28
|
findMatch: (pathname: string) => RouteMatchResult | null;
|
|
61
29
|
}
|
|
62
30
|
|
|
63
|
-
/**
|
|
64
|
-
* Resolve navigation state from a partial request.
|
|
65
|
-
*
|
|
66
|
-
* Returns null if no previous URL is available (required for partial navigation).
|
|
67
|
-
*
|
|
68
|
-
* @param request - The incoming HTTP request
|
|
69
|
-
* @param url - Parsed URL of the request
|
|
70
|
-
* @param currentRouteKey - Route key of the current (target) route match
|
|
71
|
-
* @param deps - Dependencies (findMatch)
|
|
72
|
-
*/
|
|
73
31
|
export function resolveNavigation(
|
|
74
32
|
request: Request,
|
|
75
33
|
url: URL,
|
|
76
34
|
currentRouteKey: string,
|
|
77
35
|
deps: ResolveNavigationDeps,
|
|
78
36
|
): NavigationSnapshot | null {
|
|
79
|
-
// Parse client state from RSC request params/headers
|
|
80
37
|
const clientSegmentIds =
|
|
81
38
|
url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
|
|
82
39
|
const stale = url.searchParams.get("_rsc_stale") === "true";
|
|
@@ -92,7 +49,6 @@ export function resolveNavigation(
|
|
|
92
49
|
return null;
|
|
93
50
|
}
|
|
94
51
|
|
|
95
|
-
// Parse previous URL
|
|
96
52
|
let prevUrl: URL;
|
|
97
53
|
try {
|
|
98
54
|
prevUrl = new URL(previousUrl, url.origin);
|
|
@@ -100,7 +56,6 @@ export function resolveNavigation(
|
|
|
100
56
|
return null;
|
|
101
57
|
}
|
|
102
58
|
|
|
103
|
-
// Parse intercept context URL
|
|
104
59
|
let interceptContextUrl: URL;
|
|
105
60
|
try {
|
|
106
61
|
interceptContextUrl = interceptSourceUrl
|
|
@@ -110,14 +65,12 @@ export function resolveNavigation(
|
|
|
110
65
|
interceptContextUrl = prevUrl;
|
|
111
66
|
}
|
|
112
67
|
|
|
113
|
-
// Match previous and intercept context routes
|
|
114
68
|
const prevMatch = deps.findMatch(prevUrl.pathname);
|
|
115
69
|
const prevParams = prevMatch?.params || {};
|
|
116
70
|
const interceptContextMatch = interceptSourceUrl
|
|
117
71
|
? deps.findMatch(interceptContextUrl.pathname)
|
|
118
72
|
: prevMatch;
|
|
119
73
|
|
|
120
|
-
// Derived state
|
|
121
74
|
const isSameRouteNavigation = !!(
|
|
122
75
|
interceptContextMatch && interceptContextMatch.routeKey === currentRouteKey
|
|
123
76
|
);
|
|
@@ -128,7 +81,6 @@ export function resolveNavigation(
|
|
|
128
81
|
? interceptContextMatch
|
|
129
82
|
: prevMatch;
|
|
130
83
|
|
|
131
|
-
// Filter segment IDs: remove parallel (.@) and loader (D\d+.) entries
|
|
132
84
|
const filteredSegmentIds = clientSegmentIds.filter((id) => {
|
|
133
85
|
if (id.includes(".@")) return false;
|
|
134
86
|
if (/D\d+\./.test(id)) return false;
|
|
@@ -155,9 +107,6 @@ export function resolveNavigation(
|
|
|
155
107
|
};
|
|
156
108
|
}
|
|
157
109
|
|
|
158
|
-
/**
|
|
159
|
-
* Test helper: create a NavigationSnapshot with sensible defaults and overrides.
|
|
160
|
-
*/
|
|
161
110
|
export function createNavigationSnapshot(
|
|
162
111
|
overrides?: Partial<NavigationSnapshot>,
|
|
163
112
|
): NavigationSnapshot {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared route-param comparison helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shallow equality for two route-param records. Same-reference is a fast path;
|
|
7
|
+
* otherwise compares key count then each value.
|
|
8
|
+
*/
|
|
9
|
+
export function paramsEqual(
|
|
10
|
+
a: Record<string, string>,
|
|
11
|
+
b: Record<string, string>,
|
|
12
|
+
): boolean {
|
|
13
|
+
if (a === b) return true;
|
|
14
|
+
|
|
15
|
+
const keysA = Object.keys(a);
|
|
16
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
17
|
+
|
|
18
|
+
for (const key of keysA) {
|
|
19
|
+
if (a[key] !== b[key]) return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
@@ -33,13 +33,6 @@ export interface ParsedSegment {
|
|
|
33
33
|
*/
|
|
34
34
|
export function parsePattern(pattern: string): ParsedSegment[] {
|
|
35
35
|
const segments: ParsedSegment[] = [];
|
|
36
|
-
// Match: /segment where segment can be:
|
|
37
|
-
// - static text
|
|
38
|
-
// - :param
|
|
39
|
-
// - :param?
|
|
40
|
-
// - :param(a|b)
|
|
41
|
-
// - :param(a|b)?
|
|
42
|
-
// - *
|
|
43
36
|
const segmentRegex =
|
|
44
37
|
/\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([^/]*)|(\*)|([^/]+))/g;
|
|
45
38
|
|
|
@@ -81,7 +74,6 @@ export function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
81
74
|
export interface CompiledPattern {
|
|
82
75
|
regex: RegExp;
|
|
83
76
|
paramNames: string[];
|
|
84
|
-
optionalParams: Set<string>;
|
|
85
77
|
hasTrailingSlash: boolean;
|
|
86
78
|
/**
|
|
87
79
|
* Param-name → allowed values for constrained params (e.g. `:lang(en|gb)`).
|
|
@@ -149,7 +141,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
149
141
|
|
|
150
142
|
const segments = parsePattern(normalizedPattern);
|
|
151
143
|
const paramNames: string[] = [];
|
|
152
|
-
const optionalParams = new Set<string>();
|
|
153
144
|
let constraints: Record<string, string[]> | undefined;
|
|
154
145
|
|
|
155
146
|
let regexPattern = "";
|
|
@@ -171,7 +162,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
171
162
|
}
|
|
172
163
|
|
|
173
164
|
if (segment.optional) {
|
|
174
|
-
optionalParams.add(segment.value);
|
|
175
165
|
// Optional: make the whole /segment optional
|
|
176
166
|
regexPattern += `(?:/${valuePattern}${suffixPattern})?`;
|
|
177
167
|
} else {
|
|
@@ -183,7 +173,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
183
173
|
}
|
|
184
174
|
}
|
|
185
175
|
|
|
186
|
-
// Handle root path
|
|
187
176
|
if (regexPattern === "") {
|
|
188
177
|
regexPattern = "/";
|
|
189
178
|
}
|
|
@@ -210,7 +199,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
210
199
|
return {
|
|
211
200
|
regex: new RegExp(`^${regexPattern}$`),
|
|
212
201
|
paramNames,
|
|
213
|
-
optionalParams,
|
|
214
202
|
hasTrailingSlash,
|
|
215
203
|
...(constraints ? { constraints } : {}),
|
|
216
204
|
};
|
|
@@ -285,7 +273,6 @@ function buildParamsFromMatch(
|
|
|
285
273
|
export function extractStaticPrefix(pattern: string): string {
|
|
286
274
|
if (!pattern || pattern === "/") return "";
|
|
287
275
|
|
|
288
|
-
// Find the first occurrence of : or *
|
|
289
276
|
const paramIndex = pattern.indexOf(":");
|
|
290
277
|
const wildcardIndex = pattern.indexOf("*");
|
|
291
278
|
|
|
@@ -299,16 +286,13 @@ export function extractStaticPrefix(pattern: string): string {
|
|
|
299
286
|
}
|
|
300
287
|
|
|
301
288
|
if (cutIndex === -1) {
|
|
302
|
-
// No params or wildcards - entire pattern is static
|
|
303
289
|
return pattern;
|
|
304
290
|
}
|
|
305
291
|
|
|
306
292
|
if (cutIndex === 0) {
|
|
307
|
-
// Pattern starts with : or * - no static prefix
|
|
308
293
|
return "";
|
|
309
294
|
}
|
|
310
295
|
|
|
311
|
-
// Find the last / before the param
|
|
312
296
|
const lastSlash = pattern.lastIndexOf("/", cutIndex - 1);
|
|
313
297
|
if (lastSlash === -1 || lastSlash === 0) {
|
|
314
298
|
return "";
|
|
@@ -337,8 +321,8 @@ export function joinPrefix(base: string | undefined, prefix: string): string {
|
|
|
337
321
|
*
|
|
338
322
|
* Note: Optional params that are absent in the path are omitted from the
|
|
339
323
|
* returned `params` (read as `undefined`), matching the trie matcher and
|
|
340
|
-
* the `ExtractParams<"/:locale?/...">` type. Use the pattern definition
|
|
341
|
-
*
|
|
324
|
+
* the `ExtractParams<"/:locale?/...">` type. Use the pattern definition to
|
|
325
|
+
* determine which keys are optional.
|
|
342
326
|
*
|
|
343
327
|
* Trailing slash handling (priority order):
|
|
344
328
|
* 1. Per-route `trailingSlash` config from route()
|
|
@@ -356,7 +340,6 @@ export interface RouteMatchResult<TEnv = any> {
|
|
|
356
340
|
entry: RouteEntry<TEnv>;
|
|
357
341
|
routeKey: string;
|
|
358
342
|
params: Record<string, string>;
|
|
359
|
-
optionalParams: Set<string>;
|
|
360
343
|
redirectTo?: string;
|
|
361
344
|
/** Route has pre-rendered data available (from trie) */
|
|
362
345
|
pr?: true;
|
|
@@ -435,8 +418,6 @@ export function findMatch<TEnv>(
|
|
|
435
418
|
: pathname + "/";
|
|
436
419
|
|
|
437
420
|
for (const entry of routesEntries) {
|
|
438
|
-
// Short-circuit: skip entry if pathname doesn't start with static prefix
|
|
439
|
-
// staticPrefix is pre-computed at registration time, so this is O(1)
|
|
440
421
|
if (entry.staticPrefix && !pathname.startsWith(entry.staticPrefix)) {
|
|
441
422
|
if (effectiveDebug) {
|
|
442
423
|
debugStats.entriesSkipped++;
|
|
@@ -448,8 +429,6 @@ export function findMatch<TEnv>(
|
|
|
448
429
|
continue;
|
|
449
430
|
}
|
|
450
431
|
|
|
451
|
-
// Check if this is a lazy entry that needs evaluation
|
|
452
|
-
// When staticPrefix matches but routes are not yet populated, signal caller to evaluate
|
|
453
432
|
if (entry.lazy && !entry.lazyEvaluated) {
|
|
454
433
|
if (effectiveDebug) {
|
|
455
434
|
debugLog("findMatch", "lazy entry requires evaluation", {
|
|
@@ -470,7 +449,6 @@ export function findMatch<TEnv>(
|
|
|
470
449
|
debugStats.routesChecked++;
|
|
471
450
|
}
|
|
472
451
|
|
|
473
|
-
// Join prefix and pattern, handling edge cases
|
|
474
452
|
let fullPattern: string;
|
|
475
453
|
if (entry.prefix === "" || entry.prefix === "/") {
|
|
476
454
|
fullPattern = pattern;
|
|
@@ -480,19 +458,12 @@ export function findMatch<TEnv>(
|
|
|
480
458
|
fullPattern = entry.prefix + pattern;
|
|
481
459
|
}
|
|
482
460
|
|
|
483
|
-
const {
|
|
484
|
-
|
|
485
|
-
paramNames,
|
|
486
|
-
optionalParams,
|
|
487
|
-
hasTrailingSlash,
|
|
488
|
-
constraints,
|
|
489
|
-
} = getCompiledPattern(fullPattern);
|
|
461
|
+
const { regex, paramNames, hasTrailingSlash, constraints } =
|
|
462
|
+
getCompiledPattern(fullPattern);
|
|
490
463
|
|
|
491
|
-
// Get trailing slash mode for this route (per-route config or pattern-based)
|
|
492
464
|
const trailingSlashMode: TrailingSlashMode | undefined =
|
|
493
465
|
entry.trailingSlash?.[routeKey];
|
|
494
466
|
|
|
495
|
-
// Prerender flag from entry metadata (set by urls() for prerender handlers)
|
|
496
467
|
const prFlag = entry.prerenderRouteKeys?.has(routeKey)
|
|
497
468
|
? { pr: true as const }
|
|
498
469
|
: {};
|
|
@@ -500,13 +471,10 @@ export function findMatch<TEnv>(
|
|
|
500
471
|
? { pt: true as const }
|
|
501
472
|
: {};
|
|
502
473
|
|
|
503
|
-
// Try exact match first
|
|
504
474
|
const match = regex.exec(pathname);
|
|
505
475
|
if (match) {
|
|
506
476
|
const params = buildParamsFromMatch(match, paramNames);
|
|
507
477
|
|
|
508
|
-
// Validate constraints against decoded values; a failure falls
|
|
509
|
-
// through to the next route so other patterns can still match.
|
|
510
478
|
if (!satisfiesConstraints(params, constraints)) {
|
|
511
479
|
continue;
|
|
512
480
|
}
|
|
@@ -519,29 +487,24 @@ export function findMatch<TEnv>(
|
|
|
519
487
|
});
|
|
520
488
|
}
|
|
521
489
|
|
|
522
|
-
// Check if trailing slash mode requires redirect even on exact match
|
|
523
490
|
if (
|
|
524
491
|
trailingSlashMode === "always" &&
|
|
525
492
|
!pathnameHasTrailingSlash &&
|
|
526
493
|
pathname !== "/"
|
|
527
494
|
) {
|
|
528
|
-
// Mode says always have trailing slash, but pathname doesn't have it
|
|
529
495
|
return {
|
|
530
496
|
entry,
|
|
531
497
|
routeKey,
|
|
532
498
|
params,
|
|
533
|
-
optionalParams,
|
|
534
499
|
redirectTo: pathname + "/",
|
|
535
500
|
...prFlag,
|
|
536
501
|
...ptFlag,
|
|
537
502
|
};
|
|
538
503
|
} else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
|
|
539
|
-
// Mode says never have trailing slash, but pathname has it
|
|
540
504
|
return {
|
|
541
505
|
entry,
|
|
542
506
|
routeKey,
|
|
543
507
|
params,
|
|
544
|
-
optionalParams,
|
|
545
508
|
redirectTo: pathname.slice(0, -1),
|
|
546
509
|
...prFlag,
|
|
547
510
|
...ptFlag,
|
|
@@ -552,13 +515,11 @@ export function findMatch<TEnv>(
|
|
|
552
515
|
entry,
|
|
553
516
|
routeKey,
|
|
554
517
|
params,
|
|
555
|
-
optionalParams,
|
|
556
518
|
...prFlag,
|
|
557
519
|
...ptFlag,
|
|
558
520
|
};
|
|
559
521
|
}
|
|
560
522
|
|
|
561
|
-
// Try alternate pathname (opposite trailing slash)
|
|
562
523
|
const altMatch = regex.exec(alternatePathname);
|
|
563
524
|
if (altMatch) {
|
|
564
525
|
const params = buildParamsFromMatch(altMatch, paramNames);
|
|
@@ -567,25 +528,20 @@ export function findMatch<TEnv>(
|
|
|
567
528
|
continue;
|
|
568
529
|
}
|
|
569
530
|
|
|
570
|
-
// Determine redirect behavior based on mode
|
|
571
531
|
if (trailingSlashMode === "ignore") {
|
|
572
|
-
// Match without redirect
|
|
573
532
|
return {
|
|
574
533
|
entry,
|
|
575
534
|
routeKey,
|
|
576
535
|
params,
|
|
577
|
-
optionalParams,
|
|
578
536
|
...prFlag,
|
|
579
537
|
...ptFlag,
|
|
580
538
|
};
|
|
581
539
|
} else if (trailingSlashMode === "never") {
|
|
582
|
-
// Redirect to no trailing slash
|
|
583
540
|
if (pathnameHasTrailingSlash) {
|
|
584
541
|
return {
|
|
585
542
|
entry,
|
|
586
543
|
routeKey,
|
|
587
544
|
params,
|
|
588
|
-
optionalParams,
|
|
589
545
|
redirectTo: alternatePathname,
|
|
590
546
|
...prFlag,
|
|
591
547
|
...ptFlag,
|
|
@@ -595,18 +551,15 @@ export function findMatch<TEnv>(
|
|
|
595
551
|
entry,
|
|
596
552
|
routeKey,
|
|
597
553
|
params,
|
|
598
|
-
optionalParams,
|
|
599
554
|
...prFlag,
|
|
600
555
|
...ptFlag,
|
|
601
556
|
};
|
|
602
557
|
} else if (trailingSlashMode === "always") {
|
|
603
|
-
// Redirect to with trailing slash
|
|
604
558
|
if (!pathnameHasTrailingSlash) {
|
|
605
559
|
return {
|
|
606
560
|
entry,
|
|
607
561
|
routeKey,
|
|
608
562
|
params,
|
|
609
|
-
optionalParams,
|
|
610
563
|
redirectTo: alternatePathname,
|
|
611
564
|
...prFlag,
|
|
612
565
|
...ptFlag,
|
|
@@ -616,13 +569,10 @@ export function findMatch<TEnv>(
|
|
|
616
569
|
entry,
|
|
617
570
|
routeKey,
|
|
618
571
|
params,
|
|
619
|
-
optionalParams,
|
|
620
572
|
...prFlag,
|
|
621
573
|
...ptFlag,
|
|
622
574
|
};
|
|
623
575
|
} else {
|
|
624
|
-
// No explicit mode - use pattern-based detection
|
|
625
|
-
// Redirect to canonical form (what the pattern defines)
|
|
626
576
|
const canonicalPath = hasTrailingSlash
|
|
627
577
|
? alternatePathname
|
|
628
578
|
: pathname.slice(0, -1);
|
|
@@ -630,7 +580,6 @@ export function findMatch<TEnv>(
|
|
|
630
580
|
entry,
|
|
631
581
|
routeKey,
|
|
632
582
|
params,
|
|
633
|
-
optionalParams,
|
|
634
583
|
redirectTo: canonicalPath,
|
|
635
584
|
...prFlag,
|
|
636
585
|
...ptFlag,
|
|
@@ -651,7 +600,7 @@ export function* traverseBack(entry: EntryData): Generator<EntryData> {
|
|
|
651
600
|
let current: EntryData | null = entry;
|
|
652
601
|
const items = [] as EntryData[];
|
|
653
602
|
while (current !== null) {
|
|
654
|
-
items.push(current);
|
|
603
|
+
items.push(current);
|
|
655
604
|
current = current.parent;
|
|
656
605
|
}
|
|
657
606
|
for (let i = items.length - 1; i >= 0; i--) {
|