@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
|
@@ -105,31 +105,67 @@ export function invokeOnError<TEnv = any>(
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* First non-undefined `pick(orphan)` over the routeless entries in
|
|
110
|
+
* `entry.layout` and, recursively, in theirs: depth-first in render order
|
|
111
|
+
* (resolveOrphanLayout in segment-resolution/fresh.ts). Since #922 a routeless
|
|
112
|
+
* entry nested in another renders (a layout() after a bare cache() marker, a
|
|
113
|
+
* layout() in a routeless wrapper); a one-level scan missed the boundaries and
|
|
114
|
+
* intercepts it declares (#926).
|
|
115
|
+
*
|
|
116
|
+
* `skip` is the entry an upward walk came from. Only a bare cache() marker is
|
|
117
|
+
* both in its layout's layout[] and in the chain, and its subtree was already
|
|
118
|
+
* scanned at its chain position.
|
|
119
|
+
*/
|
|
120
|
+
export function findInOrphans<T>(
|
|
121
|
+
entry: EntryData,
|
|
122
|
+
pick: (orphan: EntryData) => T | undefined,
|
|
123
|
+
skip?: EntryData | null,
|
|
124
|
+
): T | undefined {
|
|
125
|
+
for (const orphan of entry.layout ?? []) {
|
|
126
|
+
if (orphan === skip) continue;
|
|
127
|
+
const found = pick(orphan) ?? findInOrphans(orphan, pick);
|
|
128
|
+
if (found !== undefined) return found;
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** `findInOrphans` pick: the orphan when it declares an errorBoundary(). */
|
|
134
|
+
export const withErrorBoundary = (entry: EntryData): EntryData | undefined =>
|
|
135
|
+
entry.errorBoundary && entry.errorBoundary.length > 0 ? entry : undefined;
|
|
136
|
+
|
|
137
|
+
const withNotFoundBoundary = (entry: EntryData): EntryData | undefined =>
|
|
138
|
+
entry.notFoundBoundary && entry.notFoundBoundary.length > 0
|
|
139
|
+
? entry
|
|
140
|
+
: undefined;
|
|
141
|
+
|
|
108
142
|
/**
|
|
109
143
|
* Find the nearest error boundary by walking up the entry chain
|
|
110
144
|
* Also checks sibling layouts (orphan layouts) for error boundaries
|
|
111
145
|
* Returns the first fallback found, or the default error boundary if configured
|
|
146
|
+
*
|
|
147
|
+
* An orphan's parent is null, so from an orphan the walk continues at its
|
|
148
|
+
* orphanOwner (EntryData). Without that step, errors from an intercept declared
|
|
149
|
+
* in a routeless layout, or from that layout's loaders, stopped at the orphan
|
|
150
|
+
* and skipped every ancestor boundary (#898).
|
|
112
151
|
*/
|
|
113
152
|
export function findNearestErrorBoundary(
|
|
114
153
|
entry: EntryData | null,
|
|
115
154
|
defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler,
|
|
116
155
|
): ReactNode | ErrorBoundaryHandler | null {
|
|
117
156
|
let current: EntryData | null = entry;
|
|
157
|
+
let prev: EntryData | null = null;
|
|
118
158
|
|
|
119
159
|
while (current) {
|
|
120
160
|
if (current.errorBoundary && current.errorBoundary.length > 0) {
|
|
121
161
|
return current.errorBoundary[current.errorBoundary.length - 1];
|
|
122
162
|
}
|
|
123
163
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
|
|
127
|
-
return orphan.errorBoundary[orphan.errorBoundary.length - 1];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
164
|
+
const orphan = findInOrphans(current, withErrorBoundary, prev);
|
|
165
|
+
if (orphan) return orphan.errorBoundary[orphan.errorBoundary.length - 1];
|
|
131
166
|
|
|
132
|
-
|
|
167
|
+
prev = current;
|
|
168
|
+
current = current.parent ?? current.orphanOwner ?? null;
|
|
133
169
|
}
|
|
134
170
|
|
|
135
171
|
// Return default error boundary if configured
|
|
@@ -145,6 +181,7 @@ export function findNearestNotFoundBoundary(
|
|
|
145
181
|
defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler,
|
|
146
182
|
): ReactNode | NotFoundBoundaryHandler | null {
|
|
147
183
|
let current: EntryData | null = entry;
|
|
184
|
+
let prev: EntryData | null = null;
|
|
148
185
|
|
|
149
186
|
while (current) {
|
|
150
187
|
if (current.notFoundBoundary && current.notFoundBoundary.length > 0) {
|
|
@@ -154,15 +191,14 @@ export function findNearestNotFoundBoundary(
|
|
|
154
191
|
// Check orphan layouts mirroring findNearestErrorBoundary: notFoundBoundary
|
|
155
192
|
// attaches identically (onto parent.notFoundBoundary), and an orphan layout
|
|
156
193
|
// (parent=null) is reachable only via this scan. First sibling is "outer".
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return orphan.notFoundBoundary[orphan.notFoundBoundary.length - 1];
|
|
161
|
-
}
|
|
162
|
-
}
|
|
194
|
+
const orphan = findInOrphans(current, withNotFoundBoundary, prev);
|
|
195
|
+
if (orphan) {
|
|
196
|
+
return orphan.notFoundBoundary[orphan.notFoundBoundary.length - 1];
|
|
163
197
|
}
|
|
164
198
|
|
|
165
|
-
|
|
199
|
+
// Orphan: continue at its owner, as findNearestErrorBoundary does.
|
|
200
|
+
prev = current;
|
|
201
|
+
current = current.parent ?? current.orphanOwner ?? null;
|
|
166
202
|
}
|
|
167
203
|
|
|
168
204
|
// Return default notFound boundary if configured
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { HandlerContext, InternalHandlerContext } from "../types";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
_getRequestContext,
|
|
10
|
+
type RequestContext,
|
|
11
|
+
} from "../server/request-context.js";
|
|
9
12
|
import { getSearchSchema, isRouteRootScoped } from "../route-map-builder.js";
|
|
10
13
|
import { parseSearchParams, serializeSearchParams } from "../search-params.js";
|
|
11
14
|
import {
|
|
@@ -16,8 +19,9 @@ import {
|
|
|
16
19
|
} from "../context-var.js";
|
|
17
20
|
import {
|
|
18
21
|
assertCachedHeaderWriteAllowed,
|
|
19
|
-
|
|
22
|
+
assertNonCacheableReadAllowed,
|
|
20
23
|
} from "../server/context.js";
|
|
24
|
+
import { assertThemeReadAllowed } from "../server/cookie-store.js";
|
|
21
25
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
22
26
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
23
27
|
import { PRERENDER_PASSTHROUGH } from "../prerender.js";
|
|
@@ -29,6 +33,60 @@ import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
|
29
33
|
// once at module load instead of per createHandlerContext (per-request) call.
|
|
30
34
|
const MUTATING_HEADERS_METHODS = new Set(["set", "append", "delete"]);
|
|
31
35
|
|
|
36
|
+
/** The request context a handler ctx's `theme` getter reads (THEME_DESCRIPTOR). */
|
|
37
|
+
const THEME_REQUEST_CONTEXT = Symbol("rango:theme-request-context");
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* ctx.theme is the visitor's theme cookie, guarded like cookies() (#971,
|
|
41
|
+
* assertThemeReadAllowed): a getter, so creating the ctx reads nothing and
|
|
42
|
+
* only a handler that reads the theme refuses a ppr capture or throws on a
|
|
43
|
+
* cache() miss. Non-enumerable, so serializing the ctx does not read it:
|
|
44
|
+
* React's dev Flight debug info JSON-stringifies server component props and
|
|
45
|
+
* replayed console.log args, and an enumerable getter read there refused the
|
|
46
|
+
* capture of a route that never read the theme. The trade: a spread or
|
|
47
|
+
* Object.assign copy of the ctx does not carry `theme`. One shared getter
|
|
48
|
+
* with the request context on a symbol key: a getter closure per ctx gives
|
|
49
|
+
* every ctx its own property shape (V8 dictionary mode). The guard reads the
|
|
50
|
+
* ambient context, as cookies() does, and falls back to the ctx's own when
|
|
51
|
+
* there is none.
|
|
52
|
+
*/
|
|
53
|
+
const THEME_DESCRIPTOR: PropertyDescriptor = {
|
|
54
|
+
get(this: { [THEME_REQUEST_CONTEXT]?: RequestContext }) {
|
|
55
|
+
const requestContext = this[THEME_REQUEST_CONTEXT];
|
|
56
|
+
if (!requestContext?._themeConfig) return undefined;
|
|
57
|
+
assertThemeReadAllowed(_getRequestContext() ?? requestContext, "ctx.theme");
|
|
58
|
+
return requestContext._readTheme();
|
|
59
|
+
},
|
|
60
|
+
enumerable: false,
|
|
61
|
+
configurable: true,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* `headers` whose mutating methods run `assertWrite(method)` first. Shared by
|
|
66
|
+
* the handler ctx (createHandlerContext) and the middleware ctx
|
|
67
|
+
* (createMiddlewareContext).
|
|
68
|
+
*/
|
|
69
|
+
export function guardHeaderWrites(
|
|
70
|
+
headers: Headers,
|
|
71
|
+
assertWrite: (method: string | symbol) => void,
|
|
72
|
+
): Headers {
|
|
73
|
+
return new Proxy(headers, {
|
|
74
|
+
get(target, prop, receiver) {
|
|
75
|
+
const value = Reflect.get(target, prop, receiver);
|
|
76
|
+
if (typeof value === "function") {
|
|
77
|
+
if (MUTATING_HEADERS_METHODS.has(prop as string)) {
|
|
78
|
+
return (...args: any[]) => {
|
|
79
|
+
assertWrite(prop);
|
|
80
|
+
return value.apply(target, args);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return value.bind(target);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
32
90
|
/**
|
|
33
91
|
* Strip internal _rsc* query params from a URL.
|
|
34
92
|
* Returns a new URL with only user-facing params.
|
|
@@ -224,23 +282,10 @@ export function createHandlerContext<TEnv>(
|
|
|
224
282
|
// assertCachedHeaderWriteAllowed, the unified #713 guard).
|
|
225
283
|
// Uses lazy `ctx` reference (assigned below) — only the specific handler ctx
|
|
226
284
|
// is stamped by cache-runtime, not the shared request context.
|
|
227
|
-
// MUTATING_HEADERS_METHODS is hoisted to module scope (constant, read-only).
|
|
228
285
|
let ctx: InternalHandlerContext<any, TEnv>;
|
|
229
|
-
const guardedHeaders =
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (typeof value === "function") {
|
|
233
|
-
if (MUTATING_HEADERS_METHODS.has(prop as string)) {
|
|
234
|
-
return (...args: any[]) => {
|
|
235
|
-
assertNotInsideCacheExec(ctx, "headers");
|
|
236
|
-
assertCachedHeaderWriteAllowed("ctx.headers", prop);
|
|
237
|
-
return value.apply(target, args);
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
return value.bind(target);
|
|
241
|
-
}
|
|
242
|
-
return value;
|
|
243
|
-
},
|
|
286
|
+
const guardedHeaders = guardHeaderWrites(stubResponse.headers, (prop) => {
|
|
287
|
+
assertNotInsideCacheExec(ctx, "headers");
|
|
288
|
+
assertCachedHeaderWriteAllowed("ctx.headers", prop);
|
|
244
289
|
});
|
|
245
290
|
|
|
246
291
|
ctx = {
|
|
@@ -263,14 +308,10 @@ export function createHandlerContext<TEnv>(
|
|
|
263
308
|
executionContext: requestContext?.executionContext,
|
|
264
309
|
_variables: variables,
|
|
265
310
|
get: ((keyOrVar: any) => {
|
|
266
|
-
// Read-time guard: non-cacheable var inside cache() →
|
|
267
|
-
// Works for both ContextVar tokens and string keys.
|
|
268
|
-
if (isNonCacheable(variables, keyOrVar)
|
|
269
|
-
|
|
270
|
-
`ctx.get() for a non-cacheable variable cannot be called inside a cache() boundary. ` +
|
|
271
|
-
`The variable was created with { cache: false } or set with { cache: false }, ` +
|
|
272
|
-
`and its value would be stale on cache hit. Move the read outside the cached scope.`,
|
|
273
|
-
);
|
|
311
|
+
// Read-time guard: non-cacheable var inside "use cache" or cache() →
|
|
312
|
+
// throw. Works for both ContextVar tokens and string keys.
|
|
313
|
+
if (isNonCacheable(variables, keyOrVar)) {
|
|
314
|
+
assertNonCacheableReadAllowed(keyOrVar, _getRequestContext());
|
|
274
315
|
}
|
|
275
316
|
return contextGet(variables, keyOrVar);
|
|
276
317
|
}) as HandlerContext<any, TEnv>["get"],
|
|
@@ -286,8 +327,7 @@ export function createHandlerContext<TEnv>(
|
|
|
286
327
|
use: () => {
|
|
287
328
|
throw new Error("ctx.use() called before loaders were initialized");
|
|
288
329
|
},
|
|
289
|
-
//
|
|
290
|
-
theme: requestContext?.theme,
|
|
330
|
+
// theme: THEME_DESCRIPTOR, defined after this literal.
|
|
291
331
|
setTheme: requestContext?.setTheme,
|
|
292
332
|
// Location state support (delegates to request context)
|
|
293
333
|
setLocationState(entries) {
|
|
@@ -316,6 +356,9 @@ export function createHandlerContext<TEnv>(
|
|
|
316
356
|
_responseType: responseType,
|
|
317
357
|
_routeName: routeName,
|
|
318
358
|
};
|
|
359
|
+
(ctx as { [THEME_REQUEST_CONTEXT]?: unknown })[THEME_REQUEST_CONTEXT] =
|
|
360
|
+
requestContext;
|
|
361
|
+
Object.defineProperty(ctx, "theme", THEME_DESCRIPTOR);
|
|
319
362
|
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
320
363
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
321
364
|
return ctx;
|
package/src/router/instrument.ts
CHANGED
|
@@ -28,7 +28,14 @@
|
|
|
28
28
|
* is a direct call — no wrapper, no timestamp, no allocation.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
_getRequestContext,
|
|
33
|
+
type RequestContext,
|
|
34
|
+
} from "../server/request-context.js";
|
|
35
|
+
import {
|
|
36
|
+
recordTagOwnersArmed,
|
|
37
|
+
runInSegmentTagScope,
|
|
38
|
+
} from "../cache/cache-tag.js";
|
|
32
39
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
33
40
|
import { getRouterContext } from "./router-context.js";
|
|
34
41
|
import { resolveSink, safeEmit, type TelemetryEvent } from "./telemetry.js";
|
|
@@ -352,11 +359,56 @@ export function observeHandler<C, R>(
|
|
|
352
359
|
handler: (ctx: C) => R,
|
|
353
360
|
ctx: C,
|
|
354
361
|
): R {
|
|
355
|
-
|
|
362
|
+
return observeHandlerIn(_getRequestContext(), id, handler, ctx);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function observeHandlerIn<C, R>(
|
|
366
|
+
reqCtx: RequestContext | undefined,
|
|
367
|
+
id: string,
|
|
368
|
+
handler: (ctx: C) => R,
|
|
369
|
+
ctx: C,
|
|
370
|
+
): R {
|
|
371
|
+
const tracing = reqCtx?._tracing;
|
|
356
372
|
if (!tracing || tracing.phases.handler === false) return handler(ctx);
|
|
357
373
|
return observePhase(PHASES.handler(id), () => handler(ctx));
|
|
358
374
|
}
|
|
359
375
|
|
|
376
|
+
/**
|
|
377
|
+
* observeHandler, inside segment `segmentId`'s tag scope, so the tags the
|
|
378
|
+
* handler records reach the route's cache() record (#957, cache-tag.ts
|
|
379
|
+
* runInSegmentTagScope). `spanId` is observeHandler's `id`. A request that
|
|
380
|
+
* cannot write a record takes observeHandler's path as is, with the same one
|
|
381
|
+
* request-context read: no scope, no closure.
|
|
382
|
+
*/
|
|
383
|
+
export function observeSegmentHandler<C, R>(
|
|
384
|
+
segmentId: string,
|
|
385
|
+
spanId: string,
|
|
386
|
+
handler: (ctx: C) => R,
|
|
387
|
+
ctx: C,
|
|
388
|
+
): R {
|
|
389
|
+
const reqCtx = _getRequestContext();
|
|
390
|
+
if (!recordTagOwnersArmed(reqCtx)) {
|
|
391
|
+
return observeHandlerIn(reqCtx, spanId, handler, ctx);
|
|
392
|
+
}
|
|
393
|
+
return observeInSegmentScope(segmentId, reqCtx, spanId, handler, ctx);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Separate from observeSegmentHandler: a closure there would make V8
|
|
397
|
+
// allocate its captured-variable context on every call, armed or not.
|
|
398
|
+
function observeInSegmentScope<C, R>(
|
|
399
|
+
segmentId: string,
|
|
400
|
+
reqCtx: RequestContext | undefined,
|
|
401
|
+
spanId: string,
|
|
402
|
+
handler: (ctx: C) => R,
|
|
403
|
+
ctx: C,
|
|
404
|
+
): R {
|
|
405
|
+
return runInSegmentTagScope(
|
|
406
|
+
segmentId,
|
|
407
|
+
(c) => observeHandlerIn(reqCtx, spanId, handler, c),
|
|
408
|
+
ctx,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
360
412
|
/**
|
|
361
413
|
* Emit one discrete telemetry event (the event-shaped counterpart to
|
|
362
414
|
* observePhase). Resolves the sink from the active router context and stamps the
|
|
@@ -26,8 +26,13 @@ import {
|
|
|
26
26
|
warnOnStreamedResponse,
|
|
27
27
|
buildLoaderErrorContext,
|
|
28
28
|
} from "./segment-resolution.js";
|
|
29
|
+
import { catchSegmentError } from "./segment-resolution/helpers.js";
|
|
30
|
+
import { findInOrphans } from "./error-handling.js";
|
|
31
|
+
import { resolveLoaderData } from "./segment-resolution/loader-cache.js";
|
|
29
32
|
import type { SegmentResolutionDeps } from "./types.js";
|
|
30
33
|
import { debugLog } from "./logging.js";
|
|
34
|
+
import { getRouterContext } from "./router-context.js";
|
|
35
|
+
import { runInSegmentTagScope } from "../cache/cache-tag.js";
|
|
31
36
|
import {
|
|
32
37
|
RangoContext,
|
|
33
38
|
latchPprHeaderScopeForEntries,
|
|
@@ -101,15 +106,20 @@ export function collectInterceptTargetNames(
|
|
|
101
106
|
fromEntry: EntryData | null,
|
|
102
107
|
): string[] {
|
|
103
108
|
const names = new Set<string>();
|
|
109
|
+
// Tolerate partial entries (unit-test mocks omit the arrays); real
|
|
110
|
+
// registration always populates layout/intercept.
|
|
111
|
+
const add = (source: EntryData): undefined => {
|
|
112
|
+
for (const intercept of source.intercept ?? []) {
|
|
113
|
+
names.add(intercept.routeName);
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
};
|
|
104
117
|
let current: EntryData | null = fromEntry;
|
|
118
|
+
let prev: EntryData | null = null;
|
|
105
119
|
while (current) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
for (const intercept of source.intercept ?? []) {
|
|
110
|
-
names.add(intercept.routeName);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
120
|
+
add(current);
|
|
121
|
+
findInOrphans(current, add, prev);
|
|
122
|
+
prev = current;
|
|
113
123
|
current = current.parent;
|
|
114
124
|
}
|
|
115
125
|
return [...names];
|
|
@@ -121,27 +131,62 @@ export function findInterceptForRoute(
|
|
|
121
131
|
selectorContext: InterceptSelectorContext | null = null,
|
|
122
132
|
isAction: boolean = false,
|
|
123
133
|
): { intercept: InterceptEntry; entry: EntryData } | null {
|
|
134
|
+
const match = (source: EntryData) => {
|
|
135
|
+
for (const intercept of source.intercept) {
|
|
136
|
+
if (
|
|
137
|
+
intercept.routeName === targetRouteKey &&
|
|
138
|
+
evaluateInterceptWhen(intercept, selectorContext, isAction)
|
|
139
|
+
) {
|
|
140
|
+
return { intercept, entry: source };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
};
|
|
124
145
|
let current: EntryData | null = fromEntry;
|
|
146
|
+
let prev: EntryData | null = null;
|
|
125
147
|
|
|
126
148
|
while (current) {
|
|
127
|
-
// current first, then its
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
intercept.routeName === targetRouteKey &&
|
|
132
|
-
evaluateInterceptWhen(intercept, selectorContext, isAction)
|
|
133
|
-
) {
|
|
134
|
-
return { intercept, entry: source };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
149
|
+
// current first, then its orphans (nested ones included) in render order.
|
|
150
|
+
const found = match(current) ?? findInOrphans(current, match, prev);
|
|
151
|
+
if (found) return found;
|
|
138
152
|
|
|
153
|
+
prev = current;
|
|
139
154
|
current = current.parent;
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
return null;
|
|
143
158
|
}
|
|
144
159
|
|
|
160
|
+
/**
|
|
161
|
+
* An intercept handler's throw or notFound() renders the declaring entry's
|
|
162
|
+
* boundary as the slot component (catchSegmentError, as for route handlers on
|
|
163
|
+
* partial navigations: 500/404 status, onError "handler"). Responses and every
|
|
164
|
+
* error on the background re-render (skipMiddleware) rethrow, so a failed
|
|
165
|
+
* re-render aborts its cache write instead of storing the fallback.
|
|
166
|
+
*/
|
|
167
|
+
function renderInterceptHandlerError<TEnv>(
|
|
168
|
+
error: unknown,
|
|
169
|
+
parentEntry: EntryData,
|
|
170
|
+
params: Record<string, string>,
|
|
171
|
+
context: HandlerContext<any, TEnv>,
|
|
172
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
173
|
+
skipMiddleware: boolean | undefined,
|
|
174
|
+
): ReactNode {
|
|
175
|
+
if (error instanceof Response || skipMiddleware) throw error;
|
|
176
|
+
return catchSegmentError(
|
|
177
|
+
error,
|
|
178
|
+
parentEntry,
|
|
179
|
+
params,
|
|
180
|
+
deps,
|
|
181
|
+
{
|
|
182
|
+
...buildLoaderErrorContext(context),
|
|
183
|
+
isPartial: true,
|
|
184
|
+
telemetry: getRouterContext()?.telemetry,
|
|
185
|
+
},
|
|
186
|
+
context.pathname,
|
|
187
|
+
).component;
|
|
188
|
+
}
|
|
189
|
+
|
|
145
190
|
/**
|
|
146
191
|
* Resolve an intercept entry and emit segment with the slot name.
|
|
147
192
|
*/
|
|
@@ -210,8 +255,8 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
210
255
|
const loaderIds: string[] = [];
|
|
211
256
|
|
|
212
257
|
for (let i = 0; i < interceptEntry.loader.length; i++) {
|
|
213
|
-
const
|
|
214
|
-
|
|
258
|
+
const loaderEntry = interceptEntry.loader[i];
|
|
259
|
+
const { loader, revalidate: loaderRevalidateFns } = loaderEntry;
|
|
215
260
|
const segmentId = `${parentEntry.shortCode}.${interceptEntry.slotName}D${i}.${loader.$$id}`;
|
|
216
261
|
|
|
217
262
|
if (revalidationContext) {
|
|
@@ -273,7 +318,11 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
273
318
|
loaderIds.push(loader.$$id);
|
|
274
319
|
loaderPromises.push(
|
|
275
320
|
deps.wrapLoaderPromise(
|
|
276
|
-
|
|
321
|
+
// The DSL binding's funnel, as for route loaders: a loader's own
|
|
322
|
+
// cache() applies here too.
|
|
323
|
+
runInsideLoaderScope(() =>
|
|
324
|
+
resolveLoaderData(loaderEntry, context, context.pathname),
|
|
325
|
+
),
|
|
277
326
|
parentEntry,
|
|
278
327
|
segmentId,
|
|
279
328
|
context.pathname,
|
|
@@ -284,26 +333,61 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
284
333
|
);
|
|
285
334
|
}
|
|
286
335
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
336
|
+
// The intercept segment's id, and the tag scope its handler and layout
|
|
337
|
+
// record in (#957, cache-tag.ts runInSegmentTagScope).
|
|
338
|
+
const slotSegmentId = `${parentEntry.shortCode}.${interceptEntry.slotName}`;
|
|
339
|
+
let handlerResult: ReactNode;
|
|
340
|
+
try {
|
|
341
|
+
const handler = interceptEntry.handler;
|
|
342
|
+
handlerResult =
|
|
343
|
+
typeof handler === "function"
|
|
344
|
+
? handleHandlerResult(
|
|
345
|
+
runInSegmentTagScope(slotSegmentId, handler, context),
|
|
346
|
+
)
|
|
347
|
+
: handler;
|
|
348
|
+
} catch (error) {
|
|
349
|
+
handlerResult = renderInterceptHandlerError(
|
|
350
|
+
error,
|
|
351
|
+
parentEntry,
|
|
352
|
+
params,
|
|
353
|
+
context,
|
|
354
|
+
deps,
|
|
355
|
+
options?.skipMiddleware,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
// Consumed only after the layout and loader awaits below; a rejection in that
|
|
359
|
+
// window would be an unhandledRejection (process crash on Node's default
|
|
360
|
+
// mode). The later await / trackHandler still observes it.
|
|
361
|
+
if (handlerResult instanceof Promise) handlerResult.catch(() => {});
|
|
291
362
|
|
|
292
363
|
let layoutElement: ReactNode | undefined;
|
|
293
364
|
if (interceptEntry.layout) {
|
|
294
|
-
|
|
295
|
-
|
|
365
|
+
const layout = interceptEntry.layout;
|
|
366
|
+
if (typeof layout === "function") {
|
|
367
|
+
const layoutResult = await runInSegmentTagScope(
|
|
368
|
+
slotSegmentId,
|
|
369
|
+
layout,
|
|
370
|
+
context,
|
|
371
|
+
);
|
|
296
372
|
if (layoutResult instanceof Response) {
|
|
297
373
|
throw layoutResult;
|
|
298
374
|
}
|
|
299
375
|
layoutElement = layoutResult;
|
|
300
376
|
} else {
|
|
301
|
-
layoutElement =
|
|
377
|
+
layoutElement = layout;
|
|
302
378
|
}
|
|
303
379
|
}
|
|
304
380
|
|
|
305
381
|
let component: ReactNode;
|
|
306
382
|
let loaderDataPromise: Promise<any[]> | any[] | undefined;
|
|
383
|
+
// A streamed (loading()) handler resolves after the 200 has started, so its
|
|
384
|
+
// rejection stays with the client; track it like route handlers do
|
|
385
|
+
// (onError report, handle-store completion).
|
|
386
|
+
const trackStreamedHandler = (promise: Promise<unknown>): ReactNode =>
|
|
387
|
+
deps.trackHandler(promise, {
|
|
388
|
+
segmentId: `${parentEntry.shortCode}.${interceptEntry.slotName}`,
|
|
389
|
+
segmentType: "parallel",
|
|
390
|
+
}) as ReactNode;
|
|
307
391
|
|
|
308
392
|
if (interceptEntry.loading && loaderPromises.length > 0) {
|
|
309
393
|
if (handlerResult instanceof Promise) {
|
|
@@ -314,24 +398,34 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
314
398
|
}
|
|
315
399
|
component =
|
|
316
400
|
handlerResult instanceof Promise
|
|
317
|
-
? handlerResult
|
|
401
|
+
? trackStreamedHandler(handlerResult)
|
|
318
402
|
: (Promise.resolve(handlerResult) as ReactNode);
|
|
319
403
|
loaderDataPromise = Promise.all(loaderPromises);
|
|
320
|
-
} else if (
|
|
321
|
-
|
|
322
|
-
component =
|
|
323
|
-
handlerResult instanceof Promise ? await handlerResult : handlerResult;
|
|
404
|
+
} else if (interceptEntry.loading && handlerResult instanceof Promise) {
|
|
405
|
+
component = trackStreamedHandler(handlerResult);
|
|
324
406
|
} else {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
407
|
+
if (loaderPromises.length > 0) {
|
|
408
|
+
loaderDataPromise = await Promise.all(loaderPromises);
|
|
409
|
+
}
|
|
410
|
+
component = handlerResult;
|
|
411
|
+
if (handlerResult instanceof Promise) {
|
|
412
|
+
try {
|
|
413
|
+
component = await handlerResult;
|
|
414
|
+
} catch (error) {
|
|
415
|
+
component = renderInterceptHandlerError(
|
|
416
|
+
error,
|
|
417
|
+
parentEntry,
|
|
418
|
+
params,
|
|
419
|
+
context,
|
|
420
|
+
deps,
|
|
421
|
+
options?.skipMiddleware,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
331
425
|
}
|
|
332
426
|
|
|
333
427
|
const interceptSegment = {
|
|
334
|
-
id:
|
|
428
|
+
id: slotSegmentId,
|
|
335
429
|
namespace: `intercept:${interceptEntry.routeName}`,
|
|
336
430
|
type: "parallel" as const,
|
|
337
431
|
index: 0,
|
|
@@ -401,8 +495,8 @@ export async function resolveInterceptLoadersOnly<TEnv>(
|
|
|
401
495
|
} = revalidationContext;
|
|
402
496
|
|
|
403
497
|
for (let i = 0; i < interceptEntry.loader.length; i++) {
|
|
404
|
-
const
|
|
405
|
-
|
|
498
|
+
const loaderEntry = interceptEntry.loader[i];
|
|
499
|
+
const { loader, revalidate: loaderRevalidateFns } = loaderEntry;
|
|
406
500
|
const segmentId = `${parentEntry.shortCode}.${interceptEntry.slotName}D${i}.${loader.$$id}`;
|
|
407
501
|
|
|
408
502
|
const interceptSegmentId = `${parentEntry.shortCode}.${interceptEntry.slotName}`;
|
|
@@ -451,7 +545,11 @@ export async function resolveInterceptLoadersOnly<TEnv>(
|
|
|
451
545
|
loaderIds.push(loader.$$id);
|
|
452
546
|
loaderPromises.push(
|
|
453
547
|
deps.wrapLoaderPromise(
|
|
454
|
-
|
|
548
|
+
// The DSL binding's funnel, as for route loaders: a loader's own
|
|
549
|
+
// cache() applies here too.
|
|
550
|
+
runInsideLoaderScope(() =>
|
|
551
|
+
resolveLoaderData(loaderEntry, context, context.pathname),
|
|
552
|
+
),
|
|
455
553
|
parentEntry,
|
|
456
554
|
segmentId,
|
|
457
555
|
context.pathname,
|