@rangojs/router 0.24.3 → 0.25.0
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 +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/types.d.ts +8 -0
- package/dist/types/client-urls/client-root.d.ts +4 -1
- package/dist/types/client-urls/client-urls.d.ts +28 -2
- package/dist/types/client-urls/navigation.d.ts +6 -8
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +20 -5
- package/dist/types/client-urls/types.d.ts +35 -17
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -6
- package/dist/types/router/router-options.d.ts +4 -7
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +19 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +10 -1
- package/dist/types/rsc/shell-capture.d.ts +7 -0
- package/dist/types/rsc/types.d.ts +5 -0
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/context.d.ts +64 -4
- package/dist/types/server/request-context.d.ts +41 -4
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +41 -4
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +4 -3
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/urls/path-helper.d.ts +5 -0
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6816 -6547
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/caching/SKILL.md +3 -1
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +195 -32
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +32 -10
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +43 -8
- package/skills/prerender/SKILL.md +1 -2
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +85 -21
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +119 -98
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-reverse.ts +4 -17
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +2 -0
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/types.ts +8 -0
- package/src/cache/vercel/vercel-cache-store.ts +6 -1
- package/src/client-urls/client-root.tsx +10 -5
- package/src/client-urls/client-urls.ts +246 -166
- package/src/client-urls/navigation.ts +45 -38
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +286 -75
- package/src/client-urls/types.ts +40 -16
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +27 -7
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +6 -8
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -7
- package/src/router/router-options.ts +4 -7
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +37 -2
- package/src/router/tracing.ts +2 -2
- package/src/router.ts +2 -9
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +83 -41
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +22 -0
- package/src/rsc/shell-capture.ts +11 -0
- package/src/rsc/types.ts +5 -0
- package/src/segment-system.tsx +125 -119
- package/src/server/context.ts +96 -5
- package/src/server/handle-store.ts +3 -2
- package/src/server/request-context.ts +42 -3
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/dispatch.ts +4 -5
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +77 -4
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +4 -3
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/urls/path-helper.ts +16 -3
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
|
@@ -27,11 +27,10 @@ export type RewriteTarget =
|
|
|
27
27
|
|
|
28
28
|
const REWRITE_RULE =
|
|
29
29
|
"rewrite() is for middleware: throw it from router.use() or route " +
|
|
30
|
-
"middleware (middleware() in urls()), before await next()
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"redirect(); for a mutation return a Response from middleware.";
|
|
30
|
+
"middleware (middleware() in urls()), before await next(). Loaders and " +
|
|
31
|
+
"route handlers can't rewrite: throw rewrite() from the route's " +
|
|
32
|
+
"middleware instead. Middleware in loader fetches can't either. In a " +
|
|
33
|
+
"server action use redirect().";
|
|
35
34
|
|
|
36
35
|
export function describeRewriteTarget(target: RewriteTarget): string {
|
|
37
36
|
return "path" in target
|
|
@@ -39,6 +38,17 @@ export function describeRewriteTarget(target: RewriteTarget): string {
|
|
|
39
38
|
: `rewrite(${JSON.stringify(target.name)})`;
|
|
40
39
|
}
|
|
41
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Which PPR shell a rewritten request reads and captures
|
|
43
|
+
* (`rewrite(...).shell()`); without a call, none.
|
|
44
|
+
* - `"visitor"`: its own, keyed by the visitor's URL and the target, captured
|
|
45
|
+
* with the visitor's location and `ctx.rewrittenFrom`.
|
|
46
|
+
* - `"target"`: the target's shared shell. `usePathname()` reads the target's
|
|
47
|
+
* path on a HIT, and values set before the throw are baked in: for a page
|
|
48
|
+
* that renders neither (a lock or promo page).
|
|
49
|
+
*/
|
|
50
|
+
export type RewriteShell = "visitor" | "target";
|
|
51
|
+
|
|
42
52
|
/**
|
|
43
53
|
* The value `rewrite()` returns, thrown by middleware. An Error, so a throw
|
|
44
54
|
* where a rewrite is not supported fails loudly with the rule in its message.
|
|
@@ -46,11 +56,60 @@ export function describeRewriteTarget(target: RewriteTarget): string {
|
|
|
46
56
|
export class Rewrite extends Error {
|
|
47
57
|
name = "Rewrite" as const;
|
|
48
58
|
readonly target: RewriteTarget;
|
|
59
|
+
/** The `shell()` choice; undefined: no shell. */
|
|
60
|
+
readonly shellAudience: RewriteShell | undefined;
|
|
61
|
+
/** `disallowMutations()`: a mutation is refused, not moved, before its action runs. */
|
|
62
|
+
readonly mutationsDisallowed: boolean;
|
|
49
63
|
|
|
50
|
-
constructor(
|
|
64
|
+
constructor(
|
|
65
|
+
target: RewriteTarget,
|
|
66
|
+
shellAudience?: RewriteShell,
|
|
67
|
+
mutationsDisallowed = false,
|
|
68
|
+
) {
|
|
51
69
|
super(`${describeRewriteTarget(target)}: ${REWRITE_RULE}`);
|
|
52
70
|
Object.setPrototypeOf(this, Rewrite.prototype);
|
|
53
71
|
this.target = target;
|
|
72
|
+
this.shellAudience = shellAudience;
|
|
73
|
+
this.mutationsDisallowed = mutationsDisallowed;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A copy that picks the PPR shell of a `ppr` target ({@link RewriteShell}),
|
|
78
|
+
* so a rewrite kept in a module constant keeps its own choice:
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* throw rewrite("item", { id }).shell("visitor");
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
shell(audience: RewriteShell): Rewrite {
|
|
85
|
+
if (audience !== "visitor" && audience !== "target") {
|
|
86
|
+
throw new TypeError(
|
|
87
|
+
`${describeRewriteTarget(this.target)}.shell() takes "visitor" or "target", not ${JSON.stringify(audience)}.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return new Rewrite(this.target, audience, this.mutationsDisallowed);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A copy for a lock: global middleware's rewrite of a mutation (a server
|
|
95
|
+
* action, a form post, any non-GET request) is an error before the action
|
|
96
|
+
* runs, instead of moving the mutation to the target. GET and HEAD rewrite
|
|
97
|
+
* as usual.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* throw rewrite("maintenance").disallowMutations();
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* Route middleware runs after the action, so there it cannot refuse it: the
|
|
104
|
+
* render moves to the target, dev warns, and production logs one error.
|
|
105
|
+
*/
|
|
106
|
+
disallowMutations(): Rewrite {
|
|
107
|
+
if (arguments.length > 0) {
|
|
108
|
+
throw new TypeError(
|
|
109
|
+
`${describeRewriteTarget(this.target)}.disallowMutations() takes no arguments.`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return new Rewrite(this.target, this.shellAudience, true);
|
|
54
113
|
}
|
|
55
114
|
}
|
|
56
115
|
Object.defineProperty(Rewrite.prototype, REWRITE_BRAND, { value: true });
|
|
@@ -107,31 +166,39 @@ function createRewrite(
|
|
|
107
166
|
* ```ts
|
|
108
167
|
* router.use(async (ctx, next) => {
|
|
109
168
|
* if (!locked || ctx.pathname === "/maintenance") return next();
|
|
110
|
-
* throw rewrite("maintenance");
|
|
169
|
+
* throw rewrite("maintenance").disallowMutations();
|
|
111
170
|
* });
|
|
112
171
|
* ```
|
|
113
172
|
*
|
|
114
173
|
* The request runs again as the target (global middleware, route
|
|
115
174
|
* middleware, render) with the same request context: values set before the
|
|
116
175
|
* throw stay readable. `ctx.url` is the target; `ctx.rewrittenFrom` is the
|
|
117
|
-
* visitor's URL.
|
|
176
|
+
* visitor's URL. A mutation that global middleware rewrites moves to the
|
|
177
|
+
* target before its action runs, so the page's forms keep working; a lock
|
|
178
|
+
* refuses it with `.disallowMutations()`. A `ppr` target gets no shell unless
|
|
179
|
+
* `.shell()` picks one ({@link RewriteShell}). See the `/middleware` skill.
|
|
118
180
|
*/
|
|
119
181
|
export const rewrite: RewriteFunction = createRewrite as RewriteFunction;
|
|
120
182
|
|
|
121
|
-
/**
|
|
122
|
-
|
|
183
|
+
/**
|
|
184
|
+
* A dispatch site that cannot honor a rewrite. "mutation" is the refusal
|
|
185
|
+
* `.disallowMutations()` asked for, not a misuse.
|
|
186
|
+
*/
|
|
187
|
+
export type RewriteSite = "mutation" | "body-read" | "loader-fetch";
|
|
123
188
|
|
|
124
189
|
const SITE_MESSAGE: Record<RewriteSite, string> = {
|
|
125
190
|
mutation:
|
|
126
|
-
"
|
|
127
|
-
"or
|
|
128
|
-
|
|
191
|
+
"refused a mutation: .disallowMutations() was set, so a server action " +
|
|
192
|
+
"or form post (any request but GET and HEAD) fails before its action " +
|
|
193
|
+
"runs. To answer it instead, return a Response for mutations from " +
|
|
194
|
+
'middleware, e.g. new Response("Maintenance", { status: 503 }).',
|
|
195
|
+
"body-read":
|
|
196
|
+
"is not supported here: middleware read the request body before it " +
|
|
197
|
+
"threw it, and the request moves to the target with its body for the " +
|
|
198
|
+
"action to read. Read a copy instead: ctx.request.clone().formData().",
|
|
129
199
|
"loader-fetch":
|
|
130
|
-
"middleware threw it during a loader fetch
|
|
131
|
-
"useRefreshLoaders). Return a Response there.",
|
|
132
|
-
intercept:
|
|
133
|
-
"intercept middleware threw it. Rewrite from router.use() or the " +
|
|
134
|
-
"route's middleware(), or return a Response.",
|
|
200
|
+
"is not supported here: middleware threw it during a loader fetch " +
|
|
201
|
+
"(useFetchLoader, load(), useRefreshLoaders). Return a Response there.",
|
|
135
202
|
};
|
|
136
203
|
|
|
137
204
|
/** The error a rewrite becomes at a site that cannot honor it. */
|
|
@@ -140,7 +207,7 @@ export function rewriteNotSupported(
|
|
|
140
207
|
site: RewriteSite,
|
|
141
208
|
): Error {
|
|
142
209
|
return new Error(
|
|
143
|
-
`${describeRewriteTarget(rewrite.target)}
|
|
210
|
+
`${describeRewriteTarget(rewrite.target)} ${SITE_MESSAGE[site]}`,
|
|
144
211
|
{ cause: rewrite },
|
|
145
212
|
);
|
|
146
213
|
}
|
package/src/route-types.ts
CHANGED
|
@@ -160,7 +160,7 @@ export type IncludeItem = {
|
|
|
160
160
|
* include cannot collide with siblings declared outside it.
|
|
161
161
|
*/
|
|
162
162
|
includeScope?: string;
|
|
163
|
-
};
|
|
163
|
+
} & import("./server/context.js").IncludeScopePosition;
|
|
164
164
|
};
|
|
165
165
|
|
|
166
166
|
/**
|
|
@@ -221,7 +221,6 @@ export type ParallelUseItem =
|
|
|
221
221
|
| NotFoundBoundaryItem
|
|
222
222
|
| TransitionItem;
|
|
223
223
|
export type InterceptUseItem =
|
|
224
|
-
| MiddlewareItem
|
|
225
224
|
| LoaderItem
|
|
226
225
|
| LoadingItem
|
|
227
226
|
| LayoutItem
|
package/src/router/find-match.ts
CHANGED
|
@@ -21,6 +21,30 @@ function cloneMatchResult<TEnv>(
|
|
|
21
21
|
return r ? { ...r, params: { ...r.params } } : null;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function describeUnownedTrieRoute(
|
|
25
|
+
pathname: string,
|
|
26
|
+
routeKey: string,
|
|
27
|
+
sp: string,
|
|
28
|
+
entries: RouteEntry[],
|
|
29
|
+
): string {
|
|
30
|
+
const includes = entries
|
|
31
|
+
.filter((e) => e.staticPrefix === sp)
|
|
32
|
+
.map((e) => {
|
|
33
|
+
const prefix = (e as { _lazyPrefix?: string })._lazyPrefix;
|
|
34
|
+
if (prefix === undefined) return "root routes";
|
|
35
|
+
const names = e.lazyContext?.namePrefix;
|
|
36
|
+
return `include("${prefix}")${names ? ` naming "${names}.*"` : ""}`;
|
|
37
|
+
});
|
|
38
|
+
return (
|
|
39
|
+
`[@rangojs/router] The route trie matched "${pathname}" to route ` +
|
|
40
|
+
`"${routeKey}" under static prefix "${sp}", but no include there ` +
|
|
41
|
+
`registered that name at runtime (checked: ${includes.join(", ")}). ` +
|
|
42
|
+
`Build discovery and the runtime named this route differently, so it ` +
|
|
43
|
+
`cannot render. If this follows a route edit, reload the page; if it ` +
|
|
44
|
+
`persists, please report it with your route configuration.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
24
48
|
export interface FindMatchDeps<TEnv = any> {
|
|
25
49
|
routesEntries: RouteEntry<TEnv>[];
|
|
26
50
|
// Returns a Promise only when it had to resolve an async include provider
|
|
@@ -100,7 +124,7 @@ export function createFindMatch<TEnv = any>(
|
|
|
100
124
|
// this loop may evaluate a candidate that is NOT the route's owner.
|
|
101
125
|
// Remember the failure and keep scanning: if another candidate owns
|
|
102
126
|
// the route, this sibling's failure is correctly isolated (its
|
|
103
|
-
//
|
|
127
|
+
// in-flight import was cleared, so a later request retries). But if NO
|
|
104
128
|
// candidate ends up owning it, the failing one WAS the owner — a
|
|
105
129
|
// real, trie-matched route whose module failed to import — and we
|
|
106
130
|
// rethrow below as a 5xx rather than silently 404ing (see finding 3:
|
|
@@ -130,6 +154,18 @@ export function createFindMatch<TEnv = any>(
|
|
|
130
154
|
throw candidateError;
|
|
131
155
|
}
|
|
132
156
|
|
|
157
|
+
// No candidate owns the trie's key (#1104): dev throws, production keeps the 404 fallback.
|
|
158
|
+
if (!entry && fallbackEntry && process.env.NODE_ENV !== "production") {
|
|
159
|
+
throw new Error(
|
|
160
|
+
describeUnownedTrieRoute(
|
|
161
|
+
pathname,
|
|
162
|
+
trieResult.routeKey,
|
|
163
|
+
trieResult.sp,
|
|
164
|
+
deps.routesEntries,
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
133
169
|
if (!entry) entry = fallbackEntry;
|
|
134
170
|
|
|
135
171
|
if (!entry) {
|
|
@@ -20,6 +20,9 @@ import {
|
|
|
20
20
|
import {
|
|
21
21
|
assertCachedHeaderWriteAllowed,
|
|
22
22
|
assertNonCacheableReadAllowed,
|
|
23
|
+
scopeReverse,
|
|
24
|
+
type ReverseScope,
|
|
25
|
+
type ScopableReverse,
|
|
23
26
|
} from "../server/context.js";
|
|
24
27
|
import {
|
|
25
28
|
guardRequestHeaders,
|
|
@@ -117,22 +120,8 @@ function resolveRouteName(
|
|
|
117
120
|
? currentRoutePrefix.substring(0, lastDot)
|
|
118
121
|
: currentRoutePrefix;
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (routeMap[prefixedName] !== undefined) {
|
|
123
|
-
return routeMap[prefixedName];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Walk up parent prefixes for nested includes
|
|
127
|
-
let currentPrefix = prefix;
|
|
128
|
-
while (currentPrefix.includes(".")) {
|
|
129
|
-
const parentDot = currentPrefix.lastIndexOf(".");
|
|
130
|
-
currentPrefix = currentPrefix.substring(0, parentDot);
|
|
131
|
-
const parentPrefixedName = `${currentPrefix}.${lookupName}`;
|
|
132
|
-
if (routeMap[parentPrefixedName] !== undefined) {
|
|
133
|
-
return routeMap[parentPrefixedName];
|
|
134
|
-
}
|
|
135
|
-
}
|
|
123
|
+
const scoped = resolveInPrefix(lookupName, routeMap, prefix);
|
|
124
|
+
if (scoped !== undefined) return scoped;
|
|
136
125
|
|
|
137
126
|
// Fallback: try bare name at root scope only.
|
|
138
127
|
// Routes inside { name: "" } mounts are at root scope — their dot-local
|
|
@@ -159,6 +148,43 @@ function resolveRouteName(
|
|
|
159
148
|
return routeMap[name];
|
|
160
149
|
}
|
|
161
150
|
|
|
151
|
+
/**
|
|
152
|
+
* `${prefix}.${lookupName}`, then the same name under each enclosing include
|
|
153
|
+
* prefix (nested includes reach their parents' routes).
|
|
154
|
+
*/
|
|
155
|
+
function resolveInPrefix(
|
|
156
|
+
lookupName: string,
|
|
157
|
+
routeMap: Record<string, string>,
|
|
158
|
+
prefix: string,
|
|
159
|
+
): string | undefined {
|
|
160
|
+
let currentPrefix = prefix;
|
|
161
|
+
for (;;) {
|
|
162
|
+
const pattern = routeMap[`${currentPrefix}.${lookupName}`];
|
|
163
|
+
if (pattern !== undefined) return pattern;
|
|
164
|
+
const parentDot = currentPrefix.lastIndexOf(".");
|
|
165
|
+
if (parentDot === -1) return undefined;
|
|
166
|
+
currentPrefix = currentPrefix.substring(0, parentDot);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Dot-local name resolved from a segment's own include scope (ReverseScope,
|
|
172
|
+
* server/context.ts) instead of the matched route's name: a layout, parallel
|
|
173
|
+
* slot or their loader in a parent module links within that module on every
|
|
174
|
+
* route below it.
|
|
175
|
+
*/
|
|
176
|
+
function resolveScopedName(
|
|
177
|
+
lookupName: string,
|
|
178
|
+
routeMap: Record<string, string>,
|
|
179
|
+
scope: ReverseScope,
|
|
180
|
+
): string | undefined {
|
|
181
|
+
if (scope.prefix) {
|
|
182
|
+
const scoped = resolveInPrefix(lookupName, routeMap, scope.prefix);
|
|
183
|
+
if (scoped !== undefined) return scoped;
|
|
184
|
+
}
|
|
185
|
+
return scope.rootScoped ? routeMap[lookupName] : undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
162
188
|
function createPrerenderPassthroughFn(
|
|
163
189
|
build: boolean,
|
|
164
190
|
isPassthroughRoute: boolean,
|
|
@@ -180,6 +206,26 @@ function createPrerenderPassthroughFn(
|
|
|
180
206
|
};
|
|
181
207
|
}
|
|
182
208
|
|
|
209
|
+
/**
|
|
210
|
+
* The ctx a layout's or parallel slot's handler gets: a prototype child of
|
|
211
|
+
* the request's one handler ctx with its own `reverse` in the segment's
|
|
212
|
+
* ReverseScope, so `.name` resolves in the module that declared the segment.
|
|
213
|
+
* The scope rides the object, not the call frame: it holds after an `await`
|
|
214
|
+
* and in a component the ctx is handed to. Every other field reads through
|
|
215
|
+
* live (`_currentSegmentId` as resolution moves it, ctx.set values).
|
|
216
|
+
* `_reverseScope` keys a "use cache" call that takes this ctx (cache-runtime.ts).
|
|
217
|
+
*/
|
|
218
|
+
export function withReverseScope<C>(
|
|
219
|
+
ctx: C,
|
|
220
|
+
scope: ReverseScope | undefined,
|
|
221
|
+
): C {
|
|
222
|
+
if (scope === undefined) return ctx;
|
|
223
|
+
const scoped = Object.create(ctx as object);
|
|
224
|
+
scoped.reverse = scopeReverse((ctx as any).reverse, scope);
|
|
225
|
+
scoped._reverseScope = scope;
|
|
226
|
+
return scoped;
|
|
227
|
+
}
|
|
228
|
+
|
|
183
229
|
/**
|
|
184
230
|
* Create a reverse function for URL generation from route names.
|
|
185
231
|
* Used by both HandlerContext and MiddlewareContext.
|
|
@@ -188,41 +234,40 @@ function createPrerenderPassthroughFn(
|
|
|
188
234
|
* generation. This enables auto-filling mount params from include() prefixes:
|
|
189
235
|
* inner handlers can call ctx.reverse(".sibling") without explicitly passing
|
|
190
236
|
* params that are already known from the current URL match.
|
|
191
|
-
* Explicitly passed hrefParams take priority over currentParams.
|
|
237
|
+
* Explicitly passed hrefParams take priority over currentParams. A required
|
|
238
|
+
* param that neither supplies throws `Missing param`.
|
|
192
239
|
*/
|
|
193
240
|
export function createReverseFunction(
|
|
194
241
|
routeMap: Record<string, string>,
|
|
195
242
|
currentRoutePrefix?: string,
|
|
196
243
|
currentParams?: Record<string, string>,
|
|
197
244
|
rootScoped?: boolean,
|
|
198
|
-
):
|
|
199
|
-
name
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
name,
|
|
207
|
-
routeMap,
|
|
208
|
-
currentRoutePrefix,
|
|
209
|
-
rootScoped,
|
|
210
|
-
);
|
|
245
|
+
): ScopableReverse {
|
|
246
|
+
return (name, hrefParams, search, segmentScope) => {
|
|
247
|
+
// A dot-local name from a segment's handler or loader resolves in the
|
|
248
|
+
// segment's own include scope (withReverseScope); everything else from
|
|
249
|
+
// the route's name.
|
|
250
|
+
const scope = name.startsWith(".") ? segmentScope : undefined;
|
|
251
|
+
const pattern = scope
|
|
252
|
+
? resolveScopedName(name.slice(1), routeMap, scope)
|
|
253
|
+
: resolveRouteName(name, routeMap, currentRoutePrefix, rootScoped);
|
|
211
254
|
|
|
212
255
|
if (pattern === undefined) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
256
|
+
const where = scope
|
|
257
|
+
? ` (segment scope: ${scope.prefix ?? "root"})`
|
|
258
|
+
: currentRoutePrefix
|
|
259
|
+
? ` (current route: ${currentRoutePrefix})`
|
|
260
|
+
: "";
|
|
261
|
+
throw new Error(`Unknown route: "${name}"${where}`);
|
|
216
262
|
}
|
|
217
263
|
|
|
218
|
-
// Merge current request params as defaults, explicit params override
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
264
|
+
// Merge current request params as defaults, explicit params override.
|
|
265
|
+
// Always substitute, even with no params at all (middleware and
|
|
266
|
+
// response-route reverse pass neither): a route with a required param
|
|
267
|
+
// must throw `Missing param`, never return its raw `:param` pattern.
|
|
268
|
+
const effectiveParams = { ...currentParams, ...hrefParams };
|
|
222
269
|
|
|
223
|
-
let result = effectiveParams
|
|
224
|
-
? substitutePatternParams(pattern, effectiveParams, name)
|
|
225
|
-
: pattern;
|
|
270
|
+
let result = substitutePatternParams(pattern, effectiveParams, name);
|
|
226
271
|
|
|
227
272
|
// Append search params as query string
|
|
228
273
|
if (search) {
|