@rangojs/router 0.18.0 → 0.19.1
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/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +2 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +37 -0
- package/dist/types/cache/cache-key-utils.d.ts +44 -13
- package/dist/types/cache/cache-policy.d.ts +1 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +117 -35
- package/dist/types/cache/cache-tag.d.ts +48 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
- package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +40 -8
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-snapshot.d.ts +88 -141
- package/dist/types/cache/tag-invalidation.d.ts +38 -14
- package/dist/types/cache/types.d.ts +68 -61
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
- package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +14 -7
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
- package/dist/types/rsc/shell-capture.d.ts +78 -81
- package/dist/types/rsc/shell-serve.d.ts +19 -48
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +126 -61
- package/dist/types/server/cookie-store.d.ts +30 -18
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +66 -45
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +7 -6
- package/dist/types/testing/shell-status.d.ts +34 -22
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +3 -4
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/vite/index.js +962 -97
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +76 -49
- package/skills/caching/SKILL.md +167 -69
- package/skills/client-urls/SKILL.md +1 -1
- package/skills/comparison/references/framework-comparison.md +8 -5
- package/skills/document-cache/SKILL.md +24 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +98 -6
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +52 -21
- package/skills/middleware/SKILL.md +18 -0
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/observability/SKILL.md +6 -5
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +129 -77
- package/skills/prerender/SKILL.md +3 -3
- package/skills/rango/SKILL.md +25 -6
- package/skills/route/SKILL.md +2 -0
- package/skills/testing/SKILL.md +27 -25
- package/skills/testing/cache-prerender.md +9 -9
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +3 -2
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +14 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +8 -4
- package/skills/typesafety/env-and-bindings.md +8 -0
- package/skills/use-cache/SKILL.md +67 -19
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +52 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +4 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +54 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +202 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +209 -13
- package/src/cache/cf/cf-cache-store.ts +152 -76
- package/src/cache/cf/cf-shell-frame.ts +2 -2
- package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +52 -9
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-snapshot.ts +130 -348
- package/src/cache/tag-invalidation.ts +57 -51
- package/src/cache/types.ts +71 -67
- package/src/cache/vercel/vercel-cache-store.ts +61 -58
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +155 -146
- package/src/route-definition/dsl-helpers.ts +2 -0
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +10 -7
- package/src/router/intercept-resolution.ts +11 -1
- package/src/router/loader-resolution.ts +42 -5
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +62 -34
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +9 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +35 -19
- package/src/router/match-middleware/cache-store.ts +15 -9
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +0 -4
- package/src/router/segment-resolution/loader-cache.ts +152 -26
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +15 -78
- package/src/router/segment-resolution/mask-nested.ts +47 -22
- package/src/router/segment-resolution/revalidation.ts +0 -3
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +30 -10
- package/src/rsc/handler.ts +12 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +7 -30
- package/src/rsc/response-cache-serve.ts +68 -80
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +206 -252
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +22 -20
- package/src/rsc/shell-capture-constants.ts +136 -20
- package/src/rsc/shell-capture.ts +352 -589
- package/src/rsc/shell-serve.ts +73 -109
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +2 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +263 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +134 -87
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +10 -7
- package/src/testing/shell-status.ts +60 -56
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +3 -4
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +0 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +75 -5
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -33,12 +33,12 @@ import {
|
|
|
33
33
|
deriveShellCaptureContext,
|
|
34
34
|
captureAndStoreShell,
|
|
35
35
|
settleCaptureRecord,
|
|
36
|
-
withinDeadline,
|
|
37
36
|
delay,
|
|
38
|
-
SHELL_CAPTURE_MAX_WAIT_MS,
|
|
39
37
|
SHELL_CAPTURE_RETRY_DELAY_MS,
|
|
40
38
|
type ShellCaptureDescriptor,
|
|
41
39
|
} from "../rsc/shell-capture.js";
|
|
40
|
+
import { SHELL_CAPTURE_MAX_WAIT_MS } from "../rsc/shell-capture-constants.js";
|
|
41
|
+
import { raceDeadline } from "../cache/background-task.js";
|
|
42
42
|
import { buildFullPayload } from "../rsc/full-payload.js";
|
|
43
43
|
import { buildRouteMiddlewareEntries } from "../rsc/helpers.js";
|
|
44
44
|
import type { RscPayload, SSRModule } from "../rsc/types.js";
|
|
@@ -221,82 +221,81 @@ async function attemptBuildCapture(
|
|
|
221
221
|
tags: opts.tags,
|
|
222
222
|
captureTimeout: opts.captureTimeout,
|
|
223
223
|
store: collector as any,
|
|
224
|
-
debug: opts.debug,
|
|
225
224
|
maxSnapshotBytes: opts.maxSnapshotBytes,
|
|
226
225
|
};
|
|
227
226
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
227
|
+
const result = await runWithRequestContext(
|
|
228
|
+
baseCtx,
|
|
229
|
+
async (): Promise<
|
|
230
|
+
| BuildCaptureRunResult
|
|
231
|
+
| { outcome: "route-mismatch"; matchedRouteName?: string }
|
|
232
|
+
> => {
|
|
233
|
+
const preview =
|
|
234
|
+
typeof router.previewMatch === "function"
|
|
235
|
+
? await router.previewMatch(request, { env })
|
|
236
|
+
: undefined;
|
|
237
|
+
// These preview-based mismatches exit before any middleware/envelope runs,
|
|
238
|
+
// so nothing consumes a response.
|
|
239
|
+
if (preview === null) {
|
|
240
|
+
return { outcome: "route-mismatch" };
|
|
241
|
+
}
|
|
242
|
+
if (preview?.routeKey && preview.routeKey !== opts.routeName) {
|
|
243
|
+
return {
|
|
244
|
+
outcome: "route-mismatch",
|
|
245
|
+
matchedRouteName: preview.routeKey,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
244
248
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
if (preview?.routeKey) {
|
|
250
|
+
setRequestContextParams(preview.params ?? {}, preview.routeKey);
|
|
251
|
+
}
|
|
248
252
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
// Global-only, like the live request's middleware reverse
|
|
254
|
+
// (rsc/handler.ts: createReverseFunction(getRequiredRouteMap())): no
|
|
255
|
+
// include() scope, no param auto-fill. A scoped reverse here resolved
|
|
256
|
+
// `.name` at build while the live request threw `Unknown route`.
|
|
257
|
+
const middlewareReverse = createReverseFunction(getGlobalRouteMap());
|
|
254
258
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
259
|
+
const runCapture = () =>
|
|
260
|
+
runBuildCaptureFinal({
|
|
261
|
+
baseCtx,
|
|
262
|
+
descriptor,
|
|
263
|
+
env,
|
|
264
|
+
opts,
|
|
265
|
+
request,
|
|
266
|
+
router,
|
|
267
|
+
url,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const routeMiddleware =
|
|
271
|
+
preview?.routeMiddleware && preview.routeMiddleware.length > 0
|
|
272
|
+
? buildRouteMiddlewareEntries(preview.routeMiddleware)
|
|
273
|
+
: [];
|
|
274
|
+
const runRouteMiddleware = () =>
|
|
275
|
+
runBuildMiddlewareEnvelope(
|
|
276
|
+
routeMiddleware,
|
|
277
|
+
request,
|
|
278
|
+
env,
|
|
279
|
+
variables,
|
|
280
|
+
runCapture,
|
|
281
|
+
middlewareReverse,
|
|
282
|
+
baseCtx,
|
|
283
|
+
);
|
|
271
284
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
? buildRouteMiddlewareEntries(preview.routeMiddleware)
|
|
285
|
+
const globalMiddleware = Array.isArray(router.middleware)
|
|
286
|
+
? matchMiddleware(url.pathname, router.middleware)
|
|
275
287
|
: [];
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
routeMiddleware,
|
|
288
|
+
return runBuildMiddlewareEnvelope(
|
|
289
|
+
globalMiddleware,
|
|
279
290
|
request,
|
|
280
291
|
env,
|
|
281
292
|
variables,
|
|
282
|
-
|
|
293
|
+
runRouteMiddleware,
|
|
283
294
|
middlewareReverse,
|
|
284
295
|
baseCtx,
|
|
285
296
|
);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
? matchMiddleware(url.pathname, router.middleware)
|
|
289
|
-
: [];
|
|
290
|
-
return runBuildMiddlewareEnvelope(
|
|
291
|
-
globalMiddleware,
|
|
292
|
-
request,
|
|
293
|
-
env,
|
|
294
|
-
variables,
|
|
295
|
-
runRouteMiddleware,
|
|
296
|
-
middlewareReverse,
|
|
297
|
-
baseCtx,
|
|
298
|
-
);
|
|
299
|
-
});
|
|
297
|
+
},
|
|
298
|
+
);
|
|
300
299
|
|
|
301
300
|
const outcome = result.outcome;
|
|
302
301
|
if (outcome === "stored" && collected !== null) {
|
|
@@ -304,9 +303,11 @@ async function attemptBuildCapture(
|
|
|
304
303
|
return { outcome, entry: hit.entry, tags: hit.tags };
|
|
305
304
|
}
|
|
306
305
|
if (outcome === "route-mismatch") {
|
|
307
|
-
return { outcome, matchedRouteName:
|
|
306
|
+
return { outcome, matchedRouteName: result.matchedRouteName };
|
|
307
|
+
}
|
|
308
|
+
if (outcome === "no-shell" && "timedOut" in result && result.timedOut) {
|
|
309
|
+
return { outcome, timedOut: true };
|
|
308
310
|
}
|
|
309
|
-
if (outcome === "no-shell" && timedOut) return { outcome, timedOut: true };
|
|
310
311
|
return { outcome };
|
|
311
312
|
}
|
|
312
313
|
|
|
@@ -315,6 +316,10 @@ type BuildShellCaptureOutcome = BuildShellCaptureResult["outcome"];
|
|
|
315
316
|
interface BuildCaptureRunResult {
|
|
316
317
|
outcome: BuildShellCaptureOutcome;
|
|
317
318
|
response: Response;
|
|
319
|
+
/** On route-mismatch: what this router's match landed on. */
|
|
320
|
+
matchedRouteName?: string;
|
|
321
|
+
/** On no-shell: the attempt ran out of ppr.captureTimeout. */
|
|
322
|
+
timedOut?: true;
|
|
318
323
|
}
|
|
319
324
|
|
|
320
325
|
interface BuildCaptureFinalOptions {
|
|
@@ -325,9 +330,6 @@ interface BuildCaptureFinalOptions {
|
|
|
325
330
|
request: Request;
|
|
326
331
|
router: any;
|
|
327
332
|
url: URL;
|
|
328
|
-
setMismatchedRouteName(routeName: string | undefined): void;
|
|
329
|
-
/** The attempt ran out of ppr.captureTimeout (BuildShellCaptureResult.timedOut). */
|
|
330
|
-
markTimedOut(): void;
|
|
331
333
|
}
|
|
332
334
|
|
|
333
335
|
async function runBuildMiddlewareEnvelope<TEnv>(
|
|
@@ -392,92 +394,99 @@ async function runBuildCaptureFinal(
|
|
|
392
394
|
swr: opts.swr,
|
|
393
395
|
});
|
|
394
396
|
const { derivedCtx, freshHandleStore } = derivation;
|
|
397
|
+
// The capture generation starts before the match, as at runtime
|
|
398
|
+
// (ShellCacheEntry.createdAt; the build-shell read-through compares tag
|
|
399
|
+
// invalidations against it).
|
|
400
|
+
const captureStartedAt = Date.now();
|
|
395
401
|
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return "no-shell"
|
|
402
|
+
const result = await runWithRequestContext(
|
|
403
|
+
derivedCtx,
|
|
404
|
+
async (): Promise<Omit<BuildCaptureRunResult, "response">> => {
|
|
405
|
+
// Same one deadline as the runtime capture: the match, the record-first
|
|
406
|
+
// step, then the prerender with what is left.
|
|
407
|
+
const deadline =
|
|
408
|
+
Date.now() + (opts.captureTimeout ?? SHELL_CAPTURE_MAX_WAIT_MS);
|
|
409
|
+
let match: MatchResult;
|
|
410
|
+
try {
|
|
411
|
+
const matched = await raceDeadline<MatchResult>(
|
|
412
|
+
router.match(request, { env }),
|
|
413
|
+
deadline,
|
|
414
|
+
);
|
|
415
|
+
if (!matched.done) return { outcome: "no-shell", timedOut: true };
|
|
416
|
+
match = matched.value;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (isPlainPathMiss(error, opts.urlPath)) {
|
|
419
|
+
return { outcome: "route-mismatch" };
|
|
420
|
+
}
|
|
421
|
+
throw error;
|
|
410
422
|
}
|
|
411
|
-
match
|
|
412
|
-
|
|
413
|
-
if (isPlainPathMiss(error, opts.urlPath)) {
|
|
414
|
-
return "route-mismatch" as const;
|
|
423
|
+
if (match.routeName !== opts.routeName) {
|
|
424
|
+
return { outcome: "route-mismatch", matchedRouteName: match.routeName };
|
|
415
425
|
}
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
if (match.routeName !== opts.routeName) {
|
|
419
|
-
options.setMismatchedRouteName(match.routeName);
|
|
420
|
-
return "route-mismatch" as const;
|
|
421
|
-
}
|
|
422
|
-
if (match.redirect) return "redirect" as const;
|
|
426
|
+
if (match.redirect) return { outcome: "redirect" };
|
|
423
427
|
|
|
424
|
-
|
|
428
|
+
setRequestContextParams(match.params, match.routeName);
|
|
425
429
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
430
|
+
// Same record-first step as the runtime capture. A prerendered URL's
|
|
431
|
+
// match comes from the prerender store (no handler runs, no doc record),
|
|
432
|
+
// which settleCaptureRecord reports as `prerender`.
|
|
433
|
+
const settled = await settleCaptureRecord(
|
|
434
|
+
match,
|
|
435
|
+
derivation,
|
|
436
|
+
descriptor,
|
|
437
|
+
deadline,
|
|
438
|
+
);
|
|
439
|
+
if (settled.kind === "refused") {
|
|
440
|
+
return { outcome: derivedCtx._dynamic ? "dynamic" : "refused" };
|
|
441
|
+
}
|
|
442
|
+
if (settled.kind === "timeout") {
|
|
443
|
+
return { outcome: "no-shell", timedOut: true };
|
|
444
|
+
}
|
|
442
445
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
446
|
+
const payload = buildFullPayload(
|
|
447
|
+
settled.match,
|
|
448
|
+
// buildFullPayload reads only ctx.router.* and ctx.version.
|
|
449
|
+
{
|
|
450
|
+
router,
|
|
451
|
+
version: opts.buildVersion,
|
|
452
|
+
} as unknown as HandlerContext<any>,
|
|
453
|
+
url,
|
|
454
|
+
derivedCtx,
|
|
455
|
+
freshHandleStore,
|
|
456
|
+
);
|
|
457
|
+
const rscStream = renderToReadableStream<RscPayload>(payload, {
|
|
458
|
+
onError: (error: unknown) => {
|
|
459
|
+
if (opts.debug) {
|
|
460
|
+
console.warn(
|
|
461
|
+
`[rango] shell capture render error for ${opts.urlPath}:`,
|
|
462
|
+
error,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
});
|
|
461
467
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
468
|
+
const captureOutcome = await captureAndStoreShell(
|
|
469
|
+
{ captureShellHTML: opts.captureShellHTML } as SSRModule,
|
|
470
|
+
rscStream,
|
|
471
|
+
derivedCtx,
|
|
472
|
+
{ ...descriptor, captureTimeout: Math.max(1, deadline - Date.now()) },
|
|
473
|
+
captureStartedAt,
|
|
474
|
+
);
|
|
475
|
+
if (derivedCtx._dynamic) return { outcome: "dynamic" };
|
|
476
|
+
if (captureOutcome === "no-shell" && Date.now() >= deadline) {
|
|
477
|
+
return { outcome: "no-shell", timedOut: true };
|
|
478
|
+
}
|
|
479
|
+
// A route cache() record that ran out mid-capture: the in-place retry
|
|
480
|
+
// (captureShellForBuild) reads or renders a newer one.
|
|
481
|
+
return {
|
|
482
|
+
outcome: captureOutcome === "expired" ? "no-shell" : captureOutcome,
|
|
483
|
+
};
|
|
484
|
+
},
|
|
485
|
+
);
|
|
477
486
|
|
|
478
487
|
return {
|
|
479
|
-
|
|
480
|
-
response: responseForBuildCaptureOutcome(outcome),
|
|
488
|
+
...result,
|
|
489
|
+
response: responseForBuildCaptureOutcome(result.outcome),
|
|
481
490
|
};
|
|
482
491
|
}
|
|
483
492
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { assertTransitionWhenShape } from "../transition-when-ref.js";
|
|
2
3
|
import type {
|
|
3
4
|
PartialCacheOptions,
|
|
4
5
|
Handler,
|
|
@@ -1007,6 +1008,7 @@ const transition = (
|
|
|
1007
1008
|
const { store, ctx } = requireDslContext(
|
|
1008
1009
|
"transition() must be called inside urls()",
|
|
1009
1010
|
);
|
|
1011
|
+
assertTransitionWhenShape(config.when);
|
|
1010
1012
|
|
|
1011
1013
|
// Allocate a single index for this transition() call (used in all paths),
|
|
1012
1014
|
// mirroring cache() — the child form uses it for the name, the wrapper form
|
|
@@ -188,7 +188,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
188
188
|
*
|
|
189
189
|
* // Conditional activation via the config object's `when` selector
|
|
190
190
|
* intercept("@modal", "card", <CardModal />, {
|
|
191
|
-
* when: ({ from }) => from.pathname.startsWith("/board"),
|
|
191
|
+
* when: ({ from }) => from.url.pathname.startsWith("/board"),
|
|
192
192
|
* })
|
|
193
193
|
*
|
|
194
194
|
* // Config + other use-items: config is arg 4, use is arg 5
|
|
@@ -196,7 +196,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
196
196
|
* "@modal",
|
|
197
197
|
* "card",
|
|
198
198
|
* <CardModal />,
|
|
199
|
-
* { when: ({ from }) => from.pathname.startsWith("/board") },
|
|
199
|
+
* { when: ({ from }) => from.url.pathname.startsWith("/board") },
|
|
200
200
|
* () => [loader(CardDetailLoader)],
|
|
201
201
|
* )
|
|
202
202
|
* ```
|
|
@@ -501,12 +501,12 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
501
501
|
* transition({}) is startTransition + ViewTransition under the default and
|
|
502
502
|
* startTransition only when the router sets viewTransition: false.
|
|
503
503
|
*
|
|
504
|
-
* Conditional hold: pass `when
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
504
|
+
* Conditional hold: pass `when` to gate the navigation in the browser,
|
|
505
|
+
* inline (the build hoists the literal into a client module) or as a
|
|
506
|
+
* "use client" export. It runs once per
|
|
507
|
+
* navigation with { kind, from, to, isAction, action }, never on the server;
|
|
508
|
+
* false makes the commit urgent (no hold, no view transition). This is
|
|
509
|
+
* distinct from intercept()'s match-time `when` selector.
|
|
510
510
|
*
|
|
511
511
|
* ```typescript
|
|
512
512
|
* // Attach to a single route
|
|
@@ -525,15 +525,15 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
525
525
|
* transition({ viewTransition: false }),
|
|
526
526
|
* ])
|
|
527
527
|
*
|
|
528
|
-
* // Hold only when
|
|
528
|
+
* // Hold only when arriving from the list (a browser predicate):
|
|
529
529
|
* path("/product/:id", ProductPage, { name: "product" }, () => [
|
|
530
|
-
* transition({ when: (
|
|
530
|
+
* transition({ when: ({ from }) => from.routeName === "products.list" }),
|
|
531
531
|
* ])
|
|
532
532
|
* ```
|
|
533
533
|
* @param config - ViewTransition configuration (enter, exit, update, share,
|
|
534
534
|
* default, name), `viewTransition: "auto" | false` to toggle the router
|
|
535
535
|
* boundary (createRouter({ viewTransition }) sets the app-wide default), and
|
|
536
|
-
* `when
|
|
536
|
+
* `when` to gate the transition per navigation (a browser predicate)
|
|
537
537
|
* @param children - Optional callback returning child routes to wrap
|
|
538
538
|
*/
|
|
539
539
|
transition: {
|
package/src/route-name.ts
CHANGED
|
@@ -32,6 +32,18 @@ export function isAutoGeneratedRouteName(name: string): boolean {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The route name user code may see: a user-defined name, or undefined for an
|
|
37
|
+
* internal one (unnamed path(), hidden include scope). Internal names are
|
|
38
|
+
* route keys, not API, and must not leak into transition({ when }) or
|
|
39
|
+
* intercept({ when }) locations.
|
|
40
|
+
*/
|
|
41
|
+
export function publicRouteName(
|
|
42
|
+
routeKey: string | undefined,
|
|
43
|
+
): string | undefined {
|
|
44
|
+
return routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
/**
|
|
36
48
|
* Validate that a user-provided route name does not collide with
|
|
37
49
|
* reserved internal prefixes. Checks every dot-separated segment,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* RSC routes and response routes (JSON, text, image, stream, etc.).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { requestHeaders } from "../server/request-headers.js";
|
|
9
10
|
import type { EntryData } from "../server/context.js";
|
|
10
11
|
import type { NegotiateVariant } from "../build/route-trie.js";
|
|
11
12
|
import type { CollectedMiddleware } from "./middleware-types.js";
|
|
@@ -245,7 +246,9 @@ export async function negotiateRoute(
|
|
|
245
246
|
return null;
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
const acceptEntries = parseAcceptTypes(
|
|
249
|
+
const acceptEntries = parseAcceptTypes(
|
|
250
|
+
requestHeaders(request).get("accept") || "",
|
|
251
|
+
);
|
|
249
252
|
|
|
250
253
|
// Variants carry the variant's own pa (positional param names); the synthetic
|
|
251
254
|
// primary/RSC candidates have none (their params are already keyed correctly).
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createElement, type ReactNode } from "react";
|
|
9
|
-
import type { EntryData } from "../server/context";
|
|
9
|
+
import type { EntryData } from "../server/context.js";
|
|
10
|
+
import { runIdentityExempt } from "../cache/cache-exec-scope.js";
|
|
10
11
|
import type {
|
|
11
12
|
ResolvedSegment,
|
|
12
13
|
ErrorInfo,
|
|
@@ -92,7 +93,9 @@ export function invokeOnError<TEnv = any>(
|
|
|
92
93
|
};
|
|
93
94
|
|
|
94
95
|
try {
|
|
95
|
-
|
|
96
|
+
// An observer: a request header it logs is not rendered, so the identity
|
|
97
|
+
// guards let it read one inside a capture or a cached scope (#976).
|
|
98
|
+
const result = runIdentityExempt(() => onError(errorContext));
|
|
96
99
|
// If onError returns a promise, catch any rejections
|
|
97
100
|
if (result instanceof Promise) {
|
|
98
101
|
result.catch((callbackError) => {
|
|
@@ -21,7 +21,10 @@ import {
|
|
|
21
21
|
assertCachedHeaderWriteAllowed,
|
|
22
22
|
assertNonCacheableReadAllowed,
|
|
23
23
|
} from "../server/context.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
guardRequestHeaders,
|
|
26
|
+
readGuardedTheme,
|
|
27
|
+
} from "../server/cookie-store.js";
|
|
25
28
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
26
29
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
27
30
|
import { PRERENDER_PASSTHROUGH } from "../prerender.js";
|
|
@@ -38,7 +41,7 @@ const THEME_REQUEST_CONTEXT = Symbol("rango:theme-request-context");
|
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* ctx.theme is the visitor's theme cookie, guarded like cookies() (#971,
|
|
41
|
-
*
|
|
44
|
+
* readGuardedTheme): a getter, so creating the ctx reads nothing and
|
|
42
45
|
* only a handler that reads the theme refuses a ppr capture or throws on a
|
|
43
46
|
* cache() miss. Non-enumerable, so serializing the ctx does not read it:
|
|
44
47
|
* React's dev Flight debug info JSON-stringifies server component props and
|
|
@@ -52,10 +55,7 @@ const THEME_REQUEST_CONTEXT = Symbol("rango:theme-request-context");
|
|
|
52
55
|
*/
|
|
53
56
|
const THEME_DESCRIPTOR: PropertyDescriptor = {
|
|
54
57
|
get(this: { [THEME_REQUEST_CONTEXT]?: RequestContext }) {
|
|
55
|
-
|
|
56
|
-
if (!requestContext?._themeConfig) return undefined;
|
|
57
|
-
assertThemeReadAllowed(_getRequestContext() ?? requestContext, "ctx.theme");
|
|
58
|
-
return requestContext._readTheme();
|
|
58
|
+
return readGuardedTheme(this[THEME_REQUEST_CONTEXT], "ctx.theme");
|
|
59
59
|
},
|
|
60
60
|
enumerable: false,
|
|
61
61
|
configurable: true,
|
|
@@ -295,7 +295,10 @@ export function createHandlerContext<TEnv>(
|
|
|
295
295
|
requestContext?.dynamic();
|
|
296
296
|
},
|
|
297
297
|
dev: false,
|
|
298
|
-
request
|
|
298
|
+
// Usually the request context's (guarded at creation). A request the
|
|
299
|
+
// router builds and matches (the progressive-enhancement re-render, a
|
|
300
|
+
// navigation-only ppr capture) is guarded here, before any handler runs.
|
|
301
|
+
request: guardRequestHeaders(request),
|
|
299
302
|
searchParams,
|
|
300
303
|
search: searchSchema ? resolvedSearchParams : {},
|
|
301
304
|
pathname,
|
|
@@ -64,6 +64,8 @@ function latchPprHeaderScopeForInterceptTarget(
|
|
|
64
64
|
* Check if an intercept's when conditions are satisfied.
|
|
65
65
|
* All when() functions must return true for the intercept to activate.
|
|
66
66
|
* If no when() conditions are defined, the intercept always activates.
|
|
67
|
+
* A selector that throws yields the conservative default, no intercept (the
|
|
68
|
+
* full page renders), and is logged; it never fails the request.
|
|
67
69
|
*
|
|
68
70
|
* During action revalidation, when() is NOT evaluated.
|
|
69
71
|
*/
|
|
@@ -84,7 +86,15 @@ export function evaluateInterceptWhen(
|
|
|
84
86
|
return false;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
try {
|
|
90
|
+
return intercept.when.every((fn) => fn(selectorContext));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(
|
|
93
|
+
`[rango] intercept({ when }) for route "${selectorContext.to.routeName ?? intercept.routeName}" (${intercept.slotName}) threw; rendering the full page without the intercept.`,
|
|
94
|
+
error,
|
|
95
|
+
);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
/**
|