@rangojs/router 0.16.0 → 0.18.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 +255 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +132 -19
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +54 -13
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +220 -44
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +9 -3
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
- package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
- package/dist/types/rsc/shell-capture.d.ts +151 -19
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +17 -0
- package/dist/types/server/context.d.ts +139 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +182 -53
- package/dist/types/ssr/index.d.ts +24 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +28 -1
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/pattern-types.d.ts +22 -18
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1420 -1348
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +75 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +306 -124
- package/skills/caching/SKILL.md +530 -98
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +65 -38
- package/skills/cloudflare/SKILL.md +87 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +75 -57
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +52 -31
- package/skills/deployment-caching/SKILL.md +11 -3
- package/skills/document-cache/SKILL.md +98 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +55 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +34 -17
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +39 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +386 -128
- package/skills/middleware/SKILL.md +129 -26
- package/skills/migrate-nextjs/SKILL.md +156 -70
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +51 -39
- package/skills/migrate-react-router/component-migration.md +28 -7
- package/skills/migrate-react-router/data-and-actions.md +21 -6
- package/skills/migrate-react-router/route-mapping.md +19 -10
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +92 -14
- package/skills/parallel/SKILL.md +88 -47
- package/skills/ppr/SKILL.md +866 -379
- package/skills/prerender/SKILL.md +210 -108
- package/skills/rango/SKILL.md +115 -65
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +170 -60
- package/skills/router-setup/SKILL.md +420 -240
- package/skills/scripts/SKILL.md +39 -21
- package/skills/server-actions/SKILL.md +231 -59
- package/skills/shell-manifest/SKILL.md +28 -14
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +73 -33
- package/skills/testing/cache-prerender.md +60 -33
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +26 -6
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/serve-shell-request.md +188 -0
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +155 -30
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +137 -50
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +224 -83
- package/skills/vercel/SKILL.md +35 -13
- package/skills/view-transitions/SKILL.md +42 -21
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +6 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +23 -8
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +264 -68
- package/src/cache/cache-scope.ts +419 -43
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +133 -28
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +108 -22
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +225 -44
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +73 -22
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +121 -33
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +71 -28
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +141 -43
- package/src/router/loader-resolution.ts +115 -39
- package/src/router/match-api.ts +40 -26
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +110 -10
- package/src/router/match-middleware/cache-store.ts +48 -101
- package/src/router/middleware-types.ts +9 -6
- package/src/router/middleware.ts +75 -13
- package/src/router/prerender-match.ts +59 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +62 -21
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +407 -76
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +208 -56
- package/src/router/segment-resolution/mask-nested.ts +138 -44
- package/src/router/segment-resolution/revalidation.ts +61 -16
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +608 -225
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +73 -6
- package/src/rsc/shell-capture.ts +1068 -296
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +18 -0
- package/src/server/context.ts +300 -32
- package/src/server/cookie-store.ts +87 -61
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +297 -67
- package/src/ssr/index.tsx +136 -44
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +68 -6
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +28 -1
- package/src/types/loader-types.ts +8 -6
- package/src/urls/pattern-types.ts +22 -18
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
- package/src/vite/router-discovery.ts +14 -4
package/src/reverse.ts
CHANGED
|
@@ -207,6 +207,43 @@ export type ScopedReverseFunction<
|
|
|
207
207
|
): string;
|
|
208
208
|
};
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Reverse function that resolves global route names only: no include() scope,
|
|
212
|
+
* no param auto-fill.
|
|
213
|
+
*
|
|
214
|
+
* This is the `ctx.reverse` of middleware (`MiddlewareContext`) and
|
|
215
|
+
* response-route handlers (`ResponseHandlerContext`). On a live request both
|
|
216
|
+
* get `createReverseFunction(routeMap)` with no route name or params (the
|
|
217
|
+
* middleware callers in rsc/handler.ts and rsc/loader-fetch.ts,
|
|
218
|
+
* handleResponseRoute, router/intercept-resolution.ts; mirrored by
|
|
219
|
+
* testing/run-middleware.ts and testing/dispatch.ts), so a dot-local `.name`
|
|
220
|
+
* has no scope to resolve against and throws `Unknown route`. Build-time PPR
|
|
221
|
+
* shell capture (prerender/build-shell-capture.ts) gives global and route
|
|
222
|
+
* middleware the same global-only reverse.
|
|
223
|
+
*
|
|
224
|
+
* - Route map with a string index signature (no generated map, the permissive
|
|
225
|
+
* `Record<string, string>` fallback): any name except a dot-prefixed literal.
|
|
226
|
+
* `Exclude` is required because `keyof` collapses to `string` there, so the
|
|
227
|
+
* global overloads of `ScopedReverseFunction` would accept `.name` too.
|
|
228
|
+
* - Concrete route map: the global overloads of `ScopedReverseFunction`, with
|
|
229
|
+
* name and param validation. The empty local map (`{}`) leaves no dot-prefixed
|
|
230
|
+
* overload that can match.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* reverse("blog.post", { slug: "hello" }) // ok: global route + params
|
|
235
|
+
* reverse(".post", { slug: "hello" }) // compile error: no include() scope
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export type GlobalReverseFunction<TGlobalRoutes> =
|
|
239
|
+
string extends keyof TGlobalRoutes
|
|
240
|
+
? <TName extends string>(
|
|
241
|
+
name: Exclude<TName, `.${string}`>,
|
|
242
|
+
params?: Record<string, string>,
|
|
243
|
+
search?: Record<string, unknown>,
|
|
244
|
+
) => string
|
|
245
|
+
: ScopedReverseFunction<{}, TGlobalRoutes>;
|
|
246
|
+
|
|
210
247
|
/**
|
|
211
248
|
* Extract local routes type from UrlPatterns
|
|
212
249
|
* Used with scopedReverse() to get the routes type from patterns
|
|
@@ -290,11 +327,14 @@ export type { RouteResponse } from "./urls.js";
|
|
|
290
327
|
* Get a locally-typed reverse function from ctx.reverse for composable modules.
|
|
291
328
|
*
|
|
292
329
|
* This is a type-only cast - ctx.reverse already resolves names at runtime.
|
|
293
|
-
*
|
|
294
|
-
*
|
|
330
|
+
* Only the module's LOCAL routes are typed: `.name` and bare `name` both
|
|
331
|
+
* validate against the local map (the global map parameter defaults to it).
|
|
332
|
+
* Always dot-prefix local names — a bare name type-checks against the local
|
|
333
|
+
* map but resolves as a global name at runtime. For global names, call
|
|
334
|
+
* ctx.reverse directly.
|
|
295
335
|
*
|
|
296
336
|
* @param reverse - The ctx.reverse function from HandlerContext
|
|
297
|
-
* @returns The same reverse function, typed with
|
|
337
|
+
* @returns The same reverse function, typed with the module's local routes
|
|
298
338
|
*
|
|
299
339
|
* @example
|
|
300
340
|
* ```typescript
|
|
@@ -305,7 +345,7 @@ export type { RouteResponse } from "./urls.js";
|
|
|
305
345
|
*
|
|
306
346
|
* reverse(".index"); // ✓ Local route
|
|
307
347
|
* reverse(".post", { slug: "x" }); // ✓ Local with params
|
|
308
|
-
* reverse("shop.cart");
|
|
348
|
+
* ctx.reverse("shop.cart"); // Global route: use ctx.reverse
|
|
309
349
|
*
|
|
310
350
|
* return <BlogIndex />;
|
|
311
351
|
* }, { name: "index" }),
|
|
@@ -44,7 +44,11 @@ import type {
|
|
|
44
44
|
UseItems,
|
|
45
45
|
} from "../route-types.js";
|
|
46
46
|
import type { RouteHelpers } from "./helpers-types.js";
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
resolveHandlerUse,
|
|
49
|
+
mergeHandlerUse,
|
|
50
|
+
validateInterceptUseItems,
|
|
51
|
+
} from "./resolve-handler-use.js";
|
|
48
52
|
import { ALL_USE_ITEM_TYPES } from "./use-item-types.js";
|
|
49
53
|
|
|
50
54
|
/**
|
|
@@ -131,16 +135,37 @@ const isOrphan = (result: AllUseItems[]): boolean =>
|
|
|
131
135
|
* pointer so it leaves the middleware/parent-pointer chain (LOAD-BEARING — see
|
|
132
136
|
* docs/tree-structure.md) and push it onto the parent's layout[] so it renders
|
|
133
137
|
* as a wrapper. Used by cache()/middleware()/transition(); layout() runs extra
|
|
134
|
-
* validation and registers inline.
|
|
138
|
+
* validation and registers inline. `orphanOwner` keeps the owner reachable for
|
|
139
|
+
* boundary lookup only (router/error-handling.ts).
|
|
135
140
|
*/
|
|
136
141
|
const attachOrphanSibling = (
|
|
137
142
|
parent: EntryData | null,
|
|
138
143
|
entry: EntryData,
|
|
139
144
|
): void => {
|
|
140
145
|
entry.parent = null;
|
|
141
|
-
if (parent && "layout" in parent)
|
|
146
|
+
if (parent && "layout" in parent) {
|
|
147
|
+
parent.layout.push(entry);
|
|
148
|
+
entry.orphanOwner = parent;
|
|
149
|
+
}
|
|
142
150
|
};
|
|
143
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The route whose use() list `parent` sits in: `parent` itself, or the route
|
|
154
|
+
* above a chain of layout-typed wrappers (layout(), middleware()/transition()
|
|
155
|
+
* wrapper form). Inside a path those wrappers never hold routes (path-in-path
|
|
156
|
+
* is rejected), so the route is the only cacheable unit there. An intercept()
|
|
157
|
+
* scope (its temporary parent: a spread of the declaring entry with an own
|
|
158
|
+
* `cache` sink) stops the walk, so its cache() stays rejected by
|
|
159
|
+
* validateInterceptUseItems instead of configuring the path around it.
|
|
160
|
+
*/
|
|
161
|
+
function enclosingRoute(parent: EntryData | null): EntryData | undefined {
|
|
162
|
+
for (let entry = parent; entry; entry = entry.parent) {
|
|
163
|
+
if (entry.type === "route") return entry;
|
|
164
|
+
if (entry.type !== "layout" || "cache" in entry) return undefined;
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
144
169
|
/**
|
|
145
170
|
* Run `fn` with `ctx.parent` temporarily redirected to `temp` — a satellite
|
|
146
171
|
* entry that captures the attachments declared by a use() callback — restoring
|
|
@@ -179,7 +204,7 @@ const revalidate: RouteHelpers<any, any>["revalidate"] = (fn) => {
|
|
|
179
204
|
* When an error occurs during rendering of this segment or its children,
|
|
180
205
|
* the fallback will be rendered instead. The fallback can be:
|
|
181
206
|
* - A static ReactNode (e.g., <ErrorPage />)
|
|
182
|
-
* - A handler function that receives error
|
|
207
|
+
* - A handler function that receives `{ error }` (server fallbacks get no reset)
|
|
183
208
|
*
|
|
184
209
|
* Error boundaries catch errors from:
|
|
185
210
|
* - Middleware execution
|
|
@@ -195,11 +220,11 @@ const revalidate: RouteHelpers<any, any>["revalidate"] = (fn) => {
|
|
|
195
220
|
*
|
|
196
221
|
* // Or with handler for dynamic error UI:
|
|
197
222
|
* route("products.detail", ProductDetail, () => [
|
|
198
|
-
* errorBoundary(({ error
|
|
223
|
+
* errorBoundary(({ error }) => (
|
|
199
224
|
* <div>
|
|
200
225
|
* <h2>Product failed to load</h2>
|
|
201
226
|
* <p>{error.message}</p>
|
|
202
|
-
* <
|
|
227
|
+
* <a href="/products">Back to products</a>
|
|
203
228
|
* </div>
|
|
204
229
|
* )),
|
|
205
230
|
* ])
|
|
@@ -317,6 +342,30 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
317
342
|
const name = `$${cacheIndex}`;
|
|
318
343
|
const cacheConfig = { options };
|
|
319
344
|
|
|
345
|
+
// Among a path's children, cache() configures that path: the route entry
|
|
346
|
+
// carries the config, so buildEntriesAndCacheScope (router/route-snapshot.ts)
|
|
347
|
+
// makes the route the boundary and its own segments (handler, layouts,
|
|
348
|
+
// parallels) the cached unit. Both forms leave ctx.parent unchanged, so
|
|
349
|
+
// every sibling and wrapped item still attaches where it was. Issue #912: a
|
|
350
|
+
// cache entry here was never an ancestor of the route (no scope, no store
|
|
351
|
+
// write). The same holds inside a routeless layout()/middleware()/
|
|
352
|
+
// transition() wrapper in a path (issue #918): a route's record is
|
|
353
|
+
// all-or-nothing, so the path is the only thing such a cache() can cache.
|
|
354
|
+
const route = enclosingRoute(ctx.parent);
|
|
355
|
+
if (route && ctx.parent) {
|
|
356
|
+
route.cache = cacheConfig;
|
|
357
|
+
if (!children) return { name, type: "cache" } as CacheItem;
|
|
358
|
+
const uses = runAndValidateUseItems(
|
|
359
|
+
store,
|
|
360
|
+
`${ctx.namespace}.${cacheIndex}`,
|
|
361
|
+
ctx.parent,
|
|
362
|
+
children,
|
|
363
|
+
"cache",
|
|
364
|
+
"children",
|
|
365
|
+
);
|
|
366
|
+
return { name, type: "cache", uses } as CacheItem;
|
|
367
|
+
}
|
|
368
|
+
|
|
320
369
|
// If no children, create an orphan cache entry (like orphan layouts)
|
|
321
370
|
// This allows cache() to wrap subsequent siblings
|
|
322
371
|
if (!children) {
|
|
@@ -491,7 +540,19 @@ const middleware: RouteHelpers<any, any>["middleware"] = (...args: any[]) => {
|
|
|
491
540
|
"children",
|
|
492
541
|
);
|
|
493
542
|
|
|
494
|
-
if (isOrphan(result))
|
|
543
|
+
if (isOrphan(result)) {
|
|
544
|
+
// Its middleware runs for every route of the enclosing entry
|
|
545
|
+
// (collectRouteMiddleware walks orphans), so the wrapper scopes nothing;
|
|
546
|
+
// the flat form says what runs. Issue #918: the nested layout() was
|
|
547
|
+
// dropped at render.
|
|
548
|
+
invariant(
|
|
549
|
+
!result.some((item) => item?.type === "layout"),
|
|
550
|
+
`middleware(fn, () => [...]) with no routes inside cannot contain layout() [${namespace}]. ` +
|
|
551
|
+
"The middleware runs for every route of the enclosing path or layout either way; " +
|
|
552
|
+
"list them as siblings instead: middleware(fn), layout(...)",
|
|
553
|
+
);
|
|
554
|
+
attachOrphanSibling(ctx.parent, entry);
|
|
555
|
+
}
|
|
495
556
|
|
|
496
557
|
return {
|
|
497
558
|
name: namespace,
|
|
@@ -714,15 +775,12 @@ const intercept = (
|
|
|
714
775
|
const prefixedRouteName =
|
|
715
776
|
isLocal && namePrefix ? `${namePrefix}.${bareRouteName}` : bareRouteName;
|
|
716
777
|
|
|
717
|
-
// Create intercept entry with its own loaders/
|
|
778
|
+
// Create intercept entry with its own loaders/middleware/when
|
|
718
779
|
const entry: InterceptEntry = {
|
|
719
780
|
slotName: slotName as `@${string}`,
|
|
720
781
|
routeName: prefixedRouteName,
|
|
721
782
|
handler,
|
|
722
783
|
middleware: [],
|
|
723
|
-
revalidate: [],
|
|
724
|
-
errorBoundary: [],
|
|
725
|
-
notFoundBoundary: [],
|
|
726
784
|
loader: [],
|
|
727
785
|
when: [], // Selector conditions for conditional interception
|
|
728
786
|
};
|
|
@@ -735,25 +793,44 @@ const intercept = (
|
|
|
735
793
|
entry.when.push(...selectors);
|
|
736
794
|
}
|
|
737
795
|
|
|
738
|
-
// Merge handler.use defaults with explicit use
|
|
796
|
+
// Merge handler.use defaults with explicit use. mountSite null: the merged
|
|
797
|
+
// items are validated once below, so both sources get the same message.
|
|
739
798
|
const handlerUseFn = resolveHandlerUse(handler);
|
|
740
|
-
const mergedUse = mergeHandlerUse(handlerUseFn, useFn,
|
|
799
|
+
const mergedUse = mergeHandlerUse(handlerUseFn, useFn, null);
|
|
741
800
|
|
|
742
|
-
// Run merged use callback to collect loaders,
|
|
801
|
+
// Run merged use callback to collect loaders, middleware, etc.
|
|
743
802
|
if (mergedUse) {
|
|
744
803
|
// Capture layout() calls into a temporary array
|
|
745
804
|
const capturedLayouts: EntryData[] = [];
|
|
746
805
|
|
|
747
|
-
// Temporary parent so middleware/loader
|
|
748
|
-
//
|
|
806
|
+
// Temporary parent so middleware/loader attach to the intercept entry;
|
|
807
|
+
// the loading get/set accessor mirrors writes onto `entry`. It is a
|
|
808
|
+
// shallow spread of the enclosing entry, so every field a rejected helper
|
|
809
|
+
// writes to points at a throwaway here: a helper called but not returned
|
|
810
|
+
// from use() never lands on the enclosing layout. cache() writes into
|
|
811
|
+
// `layout` (capturedLayouts), or into `cache` when the enclosing entry
|
|
812
|
+
// is a route.
|
|
813
|
+
//
|
|
814
|
+
// Why an intercept rejects these (validateInterceptUseItems):
|
|
815
|
+
// - revalidate(): an intercept only evaluates its loaders' revalidate()
|
|
816
|
+
// (router/intercept-resolution.ts).
|
|
817
|
+
// - errorBoundary()/notFoundBoundary(): error lookup walks EntryData
|
|
818
|
+
// parent chains (router/error-handling.ts), which never include an
|
|
819
|
+
// InterceptEntry; intercept loader errors resolve against the declaring
|
|
820
|
+
// entry's boundaries instead.
|
|
821
|
+
// - cache(): an intercept navigation is cached under the target route's
|
|
822
|
+
// cache() scope, with its own "intercept:" key (cache/cache-scope.ts).
|
|
749
823
|
const tempParent = {
|
|
750
824
|
...ctx.parent,
|
|
751
825
|
middleware: entry.middleware,
|
|
752
|
-
revalidate: entry.revalidate,
|
|
753
|
-
errorBoundary: entry.errorBoundary,
|
|
754
|
-
notFoundBoundary: entry.notFoundBoundary,
|
|
755
826
|
loader: entry.loader,
|
|
756
|
-
layout: capturedLayouts,
|
|
827
|
+
layout: capturedLayouts,
|
|
828
|
+
revalidate: [],
|
|
829
|
+
errorBoundary: [],
|
|
830
|
+
notFoundBoundary: [],
|
|
831
|
+
parallel: {},
|
|
832
|
+
intercept: [],
|
|
833
|
+
cache: undefined,
|
|
757
834
|
get loading() {
|
|
758
835
|
return entry.loading;
|
|
759
836
|
},
|
|
@@ -762,19 +839,29 @@ const intercept = (
|
|
|
762
839
|
},
|
|
763
840
|
};
|
|
764
841
|
|
|
765
|
-
const result =
|
|
766
|
-
mergedUse()?.flat(3),
|
|
842
|
+
const result = validateUseItems(
|
|
843
|
+
withParent(ctx, tempParent as EntryData, () => mergedUse()?.flat(3)),
|
|
844
|
+
namespace,
|
|
845
|
+
"intercept",
|
|
846
|
+
"use",
|
|
847
|
+
);
|
|
848
|
+
validateInterceptUseItems(
|
|
849
|
+
result,
|
|
850
|
+
capturedLayouts,
|
|
851
|
+
slotName,
|
|
852
|
+
routeName,
|
|
853
|
+
tempParent,
|
|
767
854
|
);
|
|
768
855
|
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
856
|
+
// The modal chrome is the first returned layout() item. Match it by id so
|
|
857
|
+
// an entry pushed into capturedLayouts without being returned is never
|
|
858
|
+
// taken as the chrome. It carries no use() items (validated above).
|
|
859
|
+
const chromeItem = result.find((item) => item?.type === "layout");
|
|
860
|
+
const chrome =
|
|
861
|
+
chromeItem && capturedLayouts.find((l) => l.id === chromeItem.name);
|
|
862
|
+
if (chrome) {
|
|
863
|
+
entry.layout = chrome.handler as ReactNode | Handler<any, any, any>;
|
|
775
864
|
}
|
|
776
|
-
|
|
777
|
-
validateUseItems(result, namespace, "intercept", "use");
|
|
778
865
|
}
|
|
779
866
|
|
|
780
867
|
ctx.parent.intercept.push(entry);
|
|
@@ -822,12 +909,13 @@ const loader: RouteHelpers<any, any>["loader"] = (
|
|
|
822
909
|
// resolved here, at DSL-evaluation time, for the same reason
|
|
823
910
|
// loading({ ssr: false }) is (below) — per-isSSR entry caching makes the
|
|
824
911
|
// flag request-mode-correct with no isSSR threading; see LoaderEntry.
|
|
912
|
+
// `bake` is the lane, the same on both evaluations.
|
|
825
913
|
const loaderEntry: LoaderEntry = {
|
|
826
914
|
loader: loaderDef,
|
|
827
915
|
revalidate: [] as ShouldRevalidateFn<any, any>[],
|
|
828
|
-
...(optionsGiven?.ssr === false &&
|
|
829
|
-
|
|
830
|
-
:
|
|
916
|
+
...(optionsGiven?.ssr === false && { bake: true as const }),
|
|
917
|
+
...(optionsGiven?.ssr === false &&
|
|
918
|
+
ctx.isSSR && { awaitBeforeFlush: true as const }),
|
|
831
919
|
};
|
|
832
920
|
|
|
833
921
|
// Merge handler.use defaults (attached to the loader definition) with explicit use
|
|
@@ -180,10 +180,10 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
180
180
|
* intercept("@modal", "card", () => <CardModal />),
|
|
181
181
|
* ])
|
|
182
182
|
*
|
|
183
|
-
* // With loaders and revalidation
|
|
183
|
+
* // With loaders and revalidation (revalidate() goes on the loader;
|
|
184
|
+
* // an intercept-level revalidate() throws)
|
|
184
185
|
* intercept("@modal", "card", () => <CardModal />, () => [
|
|
185
|
-
* loader(CardModalLoader),
|
|
186
|
-
* revalidate(() => false),
|
|
186
|
+
* loader(CardModalLoader, () => [revalidate(() => false)]),
|
|
187
187
|
* ])
|
|
188
188
|
*
|
|
189
189
|
* // Conditional activation via the config object's `when` selector
|
|
@@ -205,7 +205,11 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
205
205
|
* @param handler - Component or handler for intercepted render
|
|
206
206
|
* @param config - Optional InterceptConfig (e.g. `{ when }`), or the use
|
|
207
207
|
* callback directly when there is no config
|
|
208
|
-
* @param use - Optional callback for
|
|
208
|
+
* @param use - Optional callback for loader(), middleware(), loading(),
|
|
209
|
+
* transition() and a layout() modal chrome without use() items.
|
|
210
|
+
* revalidate() (goes on the loader), errorBoundary()/notFoundBoundary()
|
|
211
|
+
* (go on the enclosing layout) and cache() (goes on the target route)
|
|
212
|
+
* throw here.
|
|
209
213
|
*/
|
|
210
214
|
intercept: {
|
|
211
215
|
// Local: dot-prefixed, params inferred from local route definition
|
|
@@ -285,6 +289,9 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
285
289
|
* ({ defaultShouldRevalidate: true })
|
|
286
290
|
* )
|
|
287
291
|
* ```
|
|
292
|
+
* Not valid directly inside intercept() use() (throws at definition time):
|
|
293
|
+
* intercepts only revalidate their loaders, so attach it there with
|
|
294
|
+
* `loader(Def, () => [revalidate(...)])`.
|
|
288
295
|
* @param fn - Function returning either:
|
|
289
296
|
* - `boolean` (hard decision — short-circuits the chain),
|
|
290
297
|
* - `{ defaultShouldRevalidate: boolean }` (soft — updates the suggestion
|
|
@@ -352,16 +359,19 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
352
359
|
* ```typescript
|
|
353
360
|
* errorBoundary(<ErrorFallback />)
|
|
354
361
|
*
|
|
355
|
-
* // With dynamic error handler
|
|
356
|
-
* errorBoundary(({ error
|
|
362
|
+
* // With dynamic error handler (server fallback: receives only { error })
|
|
363
|
+
* errorBoundary(({ error }) => (
|
|
357
364
|
* <div>
|
|
358
365
|
* <h2>Something went wrong</h2>
|
|
359
366
|
* <p>{error.message}</p>
|
|
360
|
-
* <
|
|
367
|
+
* <a href="/">Go home</a>
|
|
361
368
|
* </div>
|
|
362
369
|
* ))
|
|
363
370
|
* ```
|
|
364
|
-
*
|
|
371
|
+
* Not valid inside intercept() use() (throws at definition time): an
|
|
372
|
+
* intercept has no boundary of its own, and its loader errors resolve
|
|
373
|
+
* against the enclosing layout/path boundary.
|
|
374
|
+
* @param fallback - Static JSX or handler receiving `{ error }`
|
|
365
375
|
*/
|
|
366
376
|
errorBoundary: (
|
|
367
377
|
fallback: ReactNode | ErrorBoundaryHandler,
|
|
@@ -379,6 +389,9 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
379
389
|
* </div>
|
|
380
390
|
* ))
|
|
381
391
|
* ```
|
|
392
|
+
* Not valid inside intercept() use() (throws at definition time): an
|
|
393
|
+
* intercept has no boundary of its own, and notFound() from its loaders
|
|
394
|
+
* resolves against the enclosing layout/path boundary.
|
|
382
395
|
* @param fallback - Static JSX or handler receiving not-found info
|
|
383
396
|
*/
|
|
384
397
|
notFoundBoundary: (
|
|
@@ -396,6 +409,9 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
396
409
|
* Note: Loaders are NOT cached by default. Use cache() inside loader()
|
|
397
410
|
* to explicitly opt-in to loader caching.
|
|
398
411
|
*
|
|
412
|
+
* Not valid inside intercept() use() (throws at definition time): an
|
|
413
|
+
* intercept navigation is cached under the target route's cache() scope.
|
|
414
|
+
*
|
|
399
415
|
* ```typescript
|
|
400
416
|
* // Using app-level defaults (ttl inherited from store.defaults)
|
|
401
417
|
* cache(() => [
|
|
@@ -430,6 +446,12 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
430
446
|
* route("archive/:year"), // uses KV store
|
|
431
447
|
* ])
|
|
432
448
|
*
|
|
449
|
+
* // Among a path's children: caches that path (handler, layouts, parallels)
|
|
450
|
+
* path("/product/:id", ProductPage, { name: "product" }, () => [
|
|
451
|
+
* cache({ ttl: 60 }),
|
|
452
|
+
* layout(<ProductChrome />), // cached with the path
|
|
453
|
+
* ])
|
|
454
|
+
*
|
|
433
455
|
* // Opt-in loader caching
|
|
434
456
|
* route("product/:id", ProductHandler, () => [
|
|
435
457
|
* loader(ProductLoader), // NOT cached (default)
|
|
@@ -459,7 +481,9 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
459
481
|
* content while the new loader resolves instead of flashing the route's
|
|
460
482
|
* loading() skeleton (see segment-system.tsx inTransitionScope). Held
|
|
461
483
|
* useLoader readers of the re-run loaders report isLoading: true until
|
|
462
|
-
* the new data commits (use-loader.tsx).
|
|
484
|
+
* the new data commits (use-loader.tsx). The pin is per loader family
|
|
485
|
+
* (`$$id`), not per segment — a layout reader of the same createLoader
|
|
486
|
+
* the child is re-running is flagged too. This is also the precondition
|
|
463
487
|
* for any view-transition animation.
|
|
464
488
|
* 2. <ViewTransition> (experimental React only): the segment content is also
|
|
465
489
|
* wrapped in React's <ViewTransition>, so the held swap cross-fades/morphs.
|
|
@@ -91,13 +91,12 @@ const MOUNT_SITE_ALLOWED_TYPES: Record<string, Set<string>> = {
|
|
|
91
91
|
"notFoundBoundary",
|
|
92
92
|
"transition",
|
|
93
93
|
]),
|
|
94
|
+
// intercept() checks its whole merged use() (explicit and handler.use)
|
|
95
|
+
// against this set via validateInterceptUseItems below.
|
|
94
96
|
intercept: new Set([
|
|
95
97
|
"middleware",
|
|
96
|
-
"revalidate",
|
|
97
98
|
"loader",
|
|
98
99
|
"loading",
|
|
99
|
-
"errorBoundary",
|
|
100
|
-
"notFoundBoundary",
|
|
101
100
|
"layout",
|
|
102
101
|
"route",
|
|
103
102
|
"transition",
|
|
@@ -106,6 +105,120 @@ const MOUNT_SITE_ALLOWED_TYPES: Record<string, Set<string>> = {
|
|
|
106
105
|
loader: new Set(["revalidate", "cache"]),
|
|
107
106
|
};
|
|
108
107
|
|
|
108
|
+
const INTERCEPT_BOUNDARY_HINT =
|
|
109
|
+
"put it on the enclosing layout or path; it handles the intercept's handler and loader errors";
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Where each item intercept() rejects goes instead. Why an intercept cannot
|
|
113
|
+
* hold them is documented at intercept() in dsl-helpers.ts.
|
|
114
|
+
*/
|
|
115
|
+
const INTERCEPT_REJECTION_HINTS: Record<string, string> = {
|
|
116
|
+
revalidate:
|
|
117
|
+
"attach it to the intercept's loader: loader(YourLoader, () => [revalidate(...)])",
|
|
118
|
+
errorBoundary: INTERCEPT_BOUNDARY_HINT,
|
|
119
|
+
notFoundBoundary: INTERCEPT_BOUNDARY_HINT,
|
|
120
|
+
cache:
|
|
121
|
+
'put cache() on the target route (intercept navigations get their own "intercept:" cache key) or use "use cache" in the handler',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function rejectInterceptItem(
|
|
125
|
+
what: string,
|
|
126
|
+
hint: string,
|
|
127
|
+
slotName: string,
|
|
128
|
+
routeName: string,
|
|
129
|
+
): never {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`${what} is not valid inside intercept("${slotName}", "${routeName}") use() (including the handler's .use): ${hint}.`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate every item an intercept's merged use() returned (explicit use()
|
|
137
|
+
* and handler.use) against MOUNT_SITE_ALLOWED_TYPES.intercept, plus two
|
|
138
|
+
* structural rules:
|
|
139
|
+
* - a nested layout() is only the modal chrome (intercept() keeps its
|
|
140
|
+
* handler), so it may not carry use() items of its own;
|
|
141
|
+
* - a cache entry in `capturedLayouts` means cache() ran in the scope even if
|
|
142
|
+
* it was not returned; its orphan form re-parents the following siblings
|
|
143
|
+
* onto that entry, so it is rejected the same way. Inside a path, cache()
|
|
144
|
+
* writes `sinks.cache` instead (dsl-helpers.ts cache()).
|
|
145
|
+
*/
|
|
146
|
+
export function validateInterceptUseItems(
|
|
147
|
+
items: AllUseItems[],
|
|
148
|
+
capturedLayouts: readonly { type: string }[],
|
|
149
|
+
slotName: string,
|
|
150
|
+
routeName: string,
|
|
151
|
+
sinks: {
|
|
152
|
+
readonly revalidate: readonly unknown[];
|
|
153
|
+
readonly errorBoundary: readonly unknown[];
|
|
154
|
+
readonly notFoundBoundary: readonly unknown[];
|
|
155
|
+
readonly intercept: readonly unknown[];
|
|
156
|
+
readonly parallel: Readonly<Record<string, unknown>>;
|
|
157
|
+
/** Set by cache() when the intercept is declared inside a path. */
|
|
158
|
+
readonly cache?: unknown;
|
|
159
|
+
},
|
|
160
|
+
): void {
|
|
161
|
+
const allowed = MOUNT_SITE_ALLOWED_TYPES.intercept!;
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
if (item == null) continue;
|
|
164
|
+
const type = (item as { type: string }).type;
|
|
165
|
+
if (!allowed.has(type)) {
|
|
166
|
+
rejectInterceptItem(
|
|
167
|
+
`${type}()`,
|
|
168
|
+
INTERCEPT_REJECTION_HINTS[type] ??
|
|
169
|
+
`allowed items are ${[...allowed].join(", ")}`,
|
|
170
|
+
slotName,
|
|
171
|
+
routeName,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (type === "layout" && (item as { uses?: unknown[] }).uses?.length) {
|
|
175
|
+
rejectInterceptItem(
|
|
176
|
+
"layout() with its own use() items",
|
|
177
|
+
"put the modal chrome in the layout component and attach loaders to the intercept itself",
|
|
178
|
+
slotName,
|
|
179
|
+
routeName,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// A rejected helper called without being returned still wrote into its
|
|
184
|
+
// throwaway field on the temporary parent; reject it the same way.
|
|
185
|
+
for (const type of [
|
|
186
|
+
"revalidate",
|
|
187
|
+
"errorBoundary",
|
|
188
|
+
"notFoundBoundary",
|
|
189
|
+
"intercept",
|
|
190
|
+
] as const) {
|
|
191
|
+
if (sinks[type].length > 0) {
|
|
192
|
+
rejectInterceptItem(
|
|
193
|
+
`${type}()`,
|
|
194
|
+
INTERCEPT_REJECTION_HINTS[type] ??
|
|
195
|
+
`allowed items are ${[...allowed].join(", ")}`,
|
|
196
|
+
slotName,
|
|
197
|
+
routeName,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (Object.keys(sinks.parallel).length > 0) {
|
|
202
|
+
rejectInterceptItem(
|
|
203
|
+
"parallel()",
|
|
204
|
+
`allowed items are ${[...allowed].join(", ")}`,
|
|
205
|
+
slotName,
|
|
206
|
+
routeName,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (
|
|
210
|
+
capturedLayouts.some((l) => l.type === "cache") ||
|
|
211
|
+
sinks.cache !== undefined
|
|
212
|
+
) {
|
|
213
|
+
rejectInterceptItem(
|
|
214
|
+
"cache()",
|
|
215
|
+
INTERCEPT_REJECTION_HINTS.cache!,
|
|
216
|
+
slotName,
|
|
217
|
+
routeName,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
109
222
|
/**
|
|
110
223
|
* Validate that items from handler.use() are valid for the given mount site.
|
|
111
224
|
* Throws a descriptive error if any item is not allowed.
|
|
@@ -135,7 +248,7 @@ export function validateHandlerUseItems(
|
|
|
135
248
|
export function mergeHandlerUse(
|
|
136
249
|
handlerUse: (() => any[]) | undefined,
|
|
137
250
|
explicitUse: (() => any[]) | undefined,
|
|
138
|
-
mountSite: string,
|
|
251
|
+
mountSite: string | null,
|
|
139
252
|
): (() => any[]) | undefined {
|
|
140
253
|
if (!handlerUse && !explicitUse) return undefined;
|
|
141
254
|
// Validation asymmetry (intentional, pre-1.0): only handler.use() items are
|
|
@@ -144,17 +257,19 @@ export function mergeHandlerUse(
|
|
|
144
257
|
// branch here and the merged branch, so a structurally-valid-but-prohibited
|
|
145
258
|
// item (e.g. middleware() inside a parallel slot) is not rejected at this seam.
|
|
146
259
|
// Documented rather than enforced for now; revisit before 1.0 (#569).
|
|
260
|
+
// intercept() is the exception: it passes mountSite null and validates the
|
|
261
|
+
// merged items itself (validateInterceptUseItems).
|
|
147
262
|
if (!handlerUse) return explicitUse;
|
|
148
263
|
if (!explicitUse) {
|
|
149
264
|
return () => {
|
|
150
265
|
const items = handlerUse().flat(3);
|
|
151
|
-
validateHandlerUseItems(items, mountSite);
|
|
266
|
+
if (mountSite) validateHandlerUseItems(items, mountSite);
|
|
152
267
|
return items;
|
|
153
268
|
};
|
|
154
269
|
}
|
|
155
270
|
return () => {
|
|
156
271
|
const hItems = handlerUse().flat(3);
|
|
157
|
-
validateHandlerUseItems(hItems, mountSite);
|
|
272
|
+
if (mountSite) validateHandlerUseItems(hItems, mountSite);
|
|
158
273
|
return [...hItems, ...explicitUse()];
|
|
159
274
|
};
|
|
160
275
|
}
|
package/src/route-types.ts
CHANGED
|
@@ -222,11 +222,8 @@ export type ParallelUseItem =
|
|
|
222
222
|
| TransitionItem;
|
|
223
223
|
export type InterceptUseItem =
|
|
224
224
|
| MiddlewareItem
|
|
225
|
-
| RevalidateItem
|
|
226
225
|
| LoaderItem
|
|
227
226
|
| LoadingItem
|
|
228
|
-
| ErrorBoundaryItem
|
|
229
|
-
| NotFoundBoundaryItem
|
|
230
227
|
| LayoutItem
|
|
231
228
|
| RouteItem
|
|
232
229
|
| TransitionItem;
|
|
@@ -5,8 +5,9 @@ import MapRootLayout from "../server/root-layout.js";
|
|
|
5
5
|
import type { RouteEntry, TrailingSlashMode } from "../types";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Build a serialized manifest from
|
|
9
|
-
* Used by the `debugManifest()` method on the router instance.
|
|
8
|
+
* Build a serialized manifest from the eager route entries for debug
|
|
9
|
+
* inspection. Used by the `debugManifest()` method on the router instance.
|
|
10
|
+
* include() children are not expanded (lazy placeholders are skipped).
|
|
10
11
|
*/
|
|
11
12
|
export async function buildDebugManifest<TEnv = any>(
|
|
12
13
|
routesEntries: RouteEntry<TEnv>[],
|
|
@@ -14,6 +15,13 @@ export async function buildDebugManifest<TEnv = any>(
|
|
|
14
15
|
const manifest = new Map<string, EntryData>();
|
|
15
16
|
|
|
16
17
|
for (const entry of routesEntries) {
|
|
18
|
+
// Lazy include placeholders are not mounts: a top-level one carries the
|
|
19
|
+
// parent urls() handler (router.ts routes()), so re-running it re-registers
|
|
20
|
+
// the parent's routes into the shared manifest (path() duplicate-name
|
|
21
|
+
// invariant). A nested one (lazy-includes.ts runExpansion()) is only valid
|
|
22
|
+
// under its captured include context.
|
|
23
|
+
if (entry.lazy) continue;
|
|
24
|
+
|
|
17
25
|
const Store = {
|
|
18
26
|
manifest,
|
|
19
27
|
namespace: `debug.M${entry.mountIndex}`,
|