@rangojs/router 0.18.0 → 0.19.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/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 +31 -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 +24 -4
- 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 +89 -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 +960 -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 +64 -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 +48 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +197 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +105 -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 +28 -3
- 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 +150 -25
- 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 +189 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +123 -84
- 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
|
/**
|
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
linkRecordedTags,
|
|
15
15
|
linkValueTags,
|
|
16
16
|
loaderTagSetsArmed,
|
|
17
|
+
markTagSetStart,
|
|
17
18
|
readValueTags,
|
|
18
19
|
tagLoaderValue,
|
|
19
20
|
} from "../cache/cache-tag.js";
|
|
21
|
+
import { executionStart } from "../cache/tag-invalidation.js";
|
|
20
22
|
import type {
|
|
21
23
|
ResolvedSegment,
|
|
22
24
|
HandlerContext,
|
|
@@ -396,6 +398,12 @@ function createLoaderExecutor<TEnv>(
|
|
|
396
398
|
* payload's handle-stream lifetime.
|
|
397
399
|
*/
|
|
398
400
|
silentHandles?: boolean;
|
|
401
|
+
/**
|
|
402
|
+
* A stale refresh's executor (setupLoaderAccess _runLoaderIsolated): its
|
|
403
|
+
* runs' pushes are diverted from the page's store, so a run's end says
|
|
404
|
+
* nothing about the page's replayed values (HandleStore.settleLoaderRun).
|
|
405
|
+
*/
|
|
406
|
+
detached?: boolean;
|
|
399
407
|
/**
|
|
400
408
|
* loaderId -> the cache() tags of that loader's binding in this request
|
|
401
409
|
* (setupLoaderAccess _bindLoaderCacheTags): an execution of it outside
|
|
@@ -410,6 +418,8 @@ function createLoaderExecutor<TEnv>(
|
|
|
410
418
|
// Capture RequestContext eagerly for cookie access (ALS protection on Cloudflare)
|
|
411
419
|
const reqCtxRef = _getRequestContext();
|
|
412
420
|
const silentHandles = executorOptions?.silentHandles === true;
|
|
421
|
+
// A run's end reports to the page's store only when its pushes reach it.
|
|
422
|
+
const reportsRunEnd = !silentHandles && executorOptions?.detached !== true;
|
|
413
423
|
const internal = ctx as InternalHandlerContext<any, TEnv>;
|
|
414
424
|
|
|
415
425
|
// Dependency graph: loaderId -> set of loader IDs it directly depends on.
|
|
@@ -712,10 +722,17 @@ function createLoaderExecutor<TEnv>(
|
|
|
712
722
|
//
|
|
713
723
|
// The CONSUMPTION-LANE RULE: a handler-consumed loader's value is a
|
|
714
724
|
// BAKED copy in every shared artifact (cache(), "use cache", the PPR
|
|
715
|
-
// shell). The cache
|
|
716
|
-
// capture
|
|
725
|
+
// shell). The cache() guard permits its identity reads; a "use cache"
|
|
726
|
+
// body it was entered in and a PPR capture do not (server/context.ts
|
|
727
|
+
// guardIdentityRead).
|
|
717
728
|
|
|
718
729
|
const recordedTags = recordTags ? new Set<string>() : undefined;
|
|
730
|
+
// A loader-cache write that stores or reads this value gates on the
|
|
731
|
+
// earliest start behind it (#977, earliestRecordedStart).
|
|
732
|
+
if (recordedTags) markTagSetStart(recordedTags, executionStart());
|
|
733
|
+
const runEndStore = reportsRunEnd
|
|
734
|
+
? (reqCtxRef ?? _getRequestContext())?._handleStore
|
|
735
|
+
: undefined;
|
|
719
736
|
const promise = observePhase(PHASES.loader(loader.$$id), () =>
|
|
720
737
|
Promise.resolve(
|
|
721
738
|
runInsideLoaderBodyScope(
|
|
@@ -725,6 +742,9 @@ function createLoaderExecutor<TEnv>(
|
|
|
725
742
|
),
|
|
726
743
|
).finally(() => {
|
|
727
744
|
pendingLoaders.delete(loader.$$id);
|
|
745
|
+
// Its pushes are made: values replayed for it that none replaced
|
|
746
|
+
// go (a hole that throws or skips its push shows none).
|
|
747
|
+
runEndStore?.settleLoaderRun(loader.$$id);
|
|
728
748
|
}),
|
|
729
749
|
);
|
|
730
750
|
if (recordedTags) {
|
|
@@ -809,7 +829,12 @@ export function setupLoaderAccess<TEnv>(
|
|
|
809
829
|
_loaderCacheOverrides: { value: undefined },
|
|
810
830
|
}),
|
|
811
831
|
new Map(),
|
|
812
|
-
|
|
832
|
+
{
|
|
833
|
+
get bindingTags() {
|
|
834
|
+
return executor.bindingTags;
|
|
835
|
+
},
|
|
836
|
+
detached: true,
|
|
837
|
+
},
|
|
813
838
|
)(loader, null);
|
|
814
839
|
|
|
815
840
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|