@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
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 +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
runWithRequestContext,
|
|
6
6
|
type RequestContext,
|
|
7
7
|
} from "../server/request-context.js";
|
|
8
|
-
import { contextGet, contextSet } from "../context-var.js";
|
|
8
|
+
import { contextGet, contextSet, hasContextVars } from "../context-var.js";
|
|
9
9
|
import {
|
|
10
10
|
createPrerenderContext,
|
|
11
11
|
createStaticContext,
|
|
12
12
|
createReverseFunction,
|
|
13
13
|
} from "./handler-context.js";
|
|
14
|
-
import {
|
|
14
|
+
import { detectPrerenderPassthrough } from "../prerender.js";
|
|
15
15
|
import { isRouteRootScoped } from "../route-map-builder.js";
|
|
16
16
|
import { setupBuildUse } from "./loader-resolution.js";
|
|
17
17
|
import { loadManifest } from "./manifest.js";
|
|
@@ -59,11 +59,16 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
59
59
|
devMode?: boolean,
|
|
60
60
|
): Promise<{
|
|
61
61
|
segments: SerializedSegmentData[];
|
|
62
|
-
|
|
62
|
+
/** RSC-encoded handle map ("" when none) — see handle-snapshot.ts. Encoded in
|
|
63
|
+
* the producer (where the Flight codec resolves) so the node-side build/dev
|
|
64
|
+
* sinks can persist it without touching the codec. */
|
|
65
|
+
handles: string;
|
|
63
66
|
routeName: string;
|
|
64
67
|
params: Record<string, string>;
|
|
65
68
|
interceptSegments?: SerializedSegmentData[];
|
|
66
|
-
|
|
69
|
+
/** RSC-encoded MERGED (main + intercept) handle map for the intercept artifact;
|
|
70
|
+
* the sinks store it as-is (no longer merge raw records). */
|
|
71
|
+
interceptHandles?: string;
|
|
67
72
|
passthrough?: true;
|
|
68
73
|
} | null> {
|
|
69
74
|
// 1. Find the matching route entry
|
|
@@ -77,6 +82,17 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
77
82
|
// Build RouterContext for loadManifest/traverseBack
|
|
78
83
|
const routerCtx = deps.buildRouterContext();
|
|
79
84
|
|
|
85
|
+
// Passthrough sentinel result: an unknown-param Passthrough route falls
|
|
86
|
+
// through to the live handler at runtime, so no artifact is baked. A fresh
|
|
87
|
+
// object is returned per call (no site mutates or identity-compares it).
|
|
88
|
+
const passthroughResult = () => ({
|
|
89
|
+
segments: [],
|
|
90
|
+
handles: "",
|
|
91
|
+
routeName: matched.routeKey,
|
|
92
|
+
params: matchedParams,
|
|
93
|
+
passthrough: true as const,
|
|
94
|
+
});
|
|
95
|
+
|
|
80
96
|
return runWithRouterContext(routerCtx, async () => {
|
|
81
97
|
// 2. Load the manifest entry tree
|
|
82
98
|
const manifestEntry = await loadManifest(
|
|
@@ -126,7 +142,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
126
142
|
get env() {
|
|
127
143
|
if (buildEnv !== undefined) return buildEnv;
|
|
128
144
|
throw new Error(
|
|
129
|
-
"[
|
|
145
|
+
"[rango] ctx.env is not available during dev-mode getParams(). " +
|
|
130
146
|
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
131
147
|
);
|
|
132
148
|
},
|
|
@@ -140,19 +156,10 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
140
156
|
);
|
|
141
157
|
});
|
|
142
158
|
if (!isKnown) {
|
|
143
|
-
return
|
|
144
|
-
segments: [],
|
|
145
|
-
handles: {},
|
|
146
|
-
routeName: matched.routeKey,
|
|
147
|
-
params: matchedParams,
|
|
148
|
-
passthrough: true as const,
|
|
149
|
-
};
|
|
159
|
+
return passthroughResult();
|
|
150
160
|
}
|
|
151
161
|
// Preserve vars set by getParams() for the render context
|
|
152
|
-
if (
|
|
153
|
-
Object.keys(probeBuildVars).length > 0 ||
|
|
154
|
-
Object.getOwnPropertySymbols(probeBuildVars).length > 0
|
|
155
|
-
) {
|
|
162
|
+
if (hasContextVars(probeBuildVars)) {
|
|
156
163
|
devProbeBuildVars = probeBuildVars;
|
|
157
164
|
}
|
|
158
165
|
} catch (err: any) {
|
|
@@ -160,13 +167,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
160
167
|
// Skip errors are intentional — treat as passthrough.
|
|
161
168
|
// All other errors propagate so dev surfaces them.
|
|
162
169
|
if (err?.name === "Skip") {
|
|
163
|
-
return
|
|
164
|
-
segments: [],
|
|
165
|
-
handles: {},
|
|
166
|
-
routeName: matched.routeKey,
|
|
167
|
-
params: matchedParams,
|
|
168
|
-
passthrough: true as const,
|
|
169
|
-
};
|
|
170
|
+
return passthroughResult();
|
|
170
171
|
}
|
|
171
172
|
throw err;
|
|
172
173
|
}
|
|
@@ -206,11 +207,14 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
206
207
|
header: () => {},
|
|
207
208
|
setStatus: () => {},
|
|
208
209
|
_setStatus: () => {},
|
|
210
|
+
_rotateStateCookie: () => {},
|
|
211
|
+
_setKeepCacheDirective: () => {},
|
|
209
212
|
use: (() => {
|
|
210
213
|
throw new Error("use() not available during pre-rendering");
|
|
211
214
|
}) as any,
|
|
212
215
|
method: "GET",
|
|
213
216
|
_handleStore: handleStore,
|
|
217
|
+
_requestTags: new Set<string>(),
|
|
214
218
|
waitUntil: () => {},
|
|
215
219
|
onResponse: () => {},
|
|
216
220
|
_onResponseCallbacks: [],
|
|
@@ -256,17 +260,13 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
256
260
|
{ skipLoaders: true },
|
|
257
261
|
);
|
|
258
262
|
|
|
259
|
-
// 9. Detect passthrough sentinel: handler returned ctx.passthrough()
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
params: matchedParams,
|
|
267
|
-
passthrough: true as const,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
263
|
+
// 9. Detect passthrough sentinel: handler returned ctx.passthrough().
|
|
264
|
+
// When the route declares loading(), the handler result is deferred so the
|
|
265
|
+
// component is a thenable resolving to the sentinel — detectPrerenderPassthrough
|
|
266
|
+
// resolves thenables before testing (a sync check would miss it and bake a
|
|
267
|
+
// corrupt artifact).
|
|
268
|
+
if (await detectPrerenderPassthrough(allSegments)) {
|
|
269
|
+
return passthroughResult();
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
// 10. Filter out any loader segments (belt-and-suspenders)
|
|
@@ -278,16 +278,22 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
278
278
|
|
|
279
279
|
// 12. Serialize segments using the cache serializer
|
|
280
280
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
281
|
+
const { encodeHandles } = await import("../cache/handle-snapshot.js");
|
|
281
282
|
const serializedSegments = await serializeSegments(nonLoaderSegments);
|
|
282
283
|
|
|
283
|
-
// 13. Collect handle data per segment (skip segments with no handle data)
|
|
284
|
-
|
|
284
|
+
// 13. Collect handle data per segment (skip segments with no handle data).
|
|
285
|
+
// Encoded through the Flight codec (not stored raw) so Promise/ReactNode
|
|
286
|
+
// handle values survive the JSON-serialized build artifact / dev wire —
|
|
287
|
+
// the same fix the runtime cache uses. Encode happens here, in the RSC
|
|
288
|
+
// environment where the codec resolves; the node-side sinks only persist.
|
|
289
|
+
const handlesRecord: Record<string, SegmentHandleData> = {};
|
|
285
290
|
for (const seg of nonLoaderSegments) {
|
|
286
291
|
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
287
292
|
if (Object.keys(segHandles).length > 0) {
|
|
288
|
-
|
|
293
|
+
handlesRecord[seg.id] = segHandles;
|
|
289
294
|
}
|
|
290
295
|
}
|
|
296
|
+
const handles = await encodeHandles(handlesRecord);
|
|
291
297
|
|
|
292
298
|
// Use the trie-level route key (e.g., "docs", "docs.article")
|
|
293
299
|
const routeName = matched.routeKey;
|
|
@@ -297,7 +303,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
297
303
|
// evaluation -- we pre-render all intercepts unconditionally and let
|
|
298
304
|
// runtime matching decide which to serve.
|
|
299
305
|
let interceptSegments: SerializedSegmentData[] | undefined;
|
|
300
|
-
let interceptHandles:
|
|
306
|
+
let interceptHandles: string | undefined;
|
|
301
307
|
|
|
302
308
|
const foundIntercepts: {
|
|
303
309
|
intercept: InterceptEntry;
|
|
@@ -305,24 +311,14 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
305
311
|
}[] = [];
|
|
306
312
|
let current: EntryData | null = manifestEntry;
|
|
307
313
|
while (current) {
|
|
308
|
-
|
|
309
|
-
|
|
314
|
+
// Flatten the entry and its sibling layouts into one source list, the
|
|
315
|
+
// same traversal findInterceptForRoute uses; the build keeps ALL matches
|
|
316
|
+
// (not just the innermost) and skips when(). intercept/layout are
|
|
317
|
+
// non-optional arrays, so empty ones are a no-op here.
|
|
318
|
+
for (const source of [current, ...current.layout]) {
|
|
319
|
+
for (const ic of source.intercept) {
|
|
310
320
|
if (ic.routeName === matched.routeKey) {
|
|
311
|
-
foundIntercepts.push({ intercept: ic, entry:
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (current.layout && current.layout.length > 0) {
|
|
316
|
-
for (const siblingLayout of current.layout) {
|
|
317
|
-
if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
|
|
318
|
-
for (const ic of siblingLayout.intercept) {
|
|
319
|
-
if (ic.routeName === matched.routeKey) {
|
|
320
|
-
foundIntercepts.push({
|
|
321
|
-
intercept: ic,
|
|
322
|
-
entry: siblingLayout,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
}
|
|
321
|
+
foundIntercepts.push({ intercept: ic, entry: source });
|
|
326
322
|
}
|
|
327
323
|
}
|
|
328
324
|
}
|
|
@@ -333,6 +329,18 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
333
329
|
const interceptResolvedSegments: typeof nonLoaderSegments = [];
|
|
334
330
|
|
|
335
331
|
for (const { intercept, entry: parentEntry } of foundIntercepts) {
|
|
332
|
+
// setupBuildUse keys handle pushes by ctx._currentSegmentId. The main
|
|
333
|
+
// resolveAllSegments pass left it on the route's segment id, so pin it
|
|
334
|
+
// to THIS intercept's slot id before resolving the handler/layout --
|
|
335
|
+
// otherwise the intercept's ctx.use() handle pushes land in the wrong
|
|
336
|
+
// bucket and getDataForSegment(seg.id) below drops them from the baked
|
|
337
|
+
// artifact. Re-pinned per iteration so multiple intercepts targeting
|
|
338
|
+
// the same route each get their own id (mirrors fresh.ts parallel-slot
|
|
339
|
+
// pinning).
|
|
340
|
+
const interceptSegmentId = `${parentEntry.shortCode}.${intercept.slotName}`;
|
|
341
|
+
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
342
|
+
interceptSegmentId;
|
|
343
|
+
|
|
336
344
|
// Resolve handler
|
|
337
345
|
const handlerRaw =
|
|
338
346
|
typeof intercept.handler === "function"
|
|
@@ -359,7 +367,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
359
367
|
}
|
|
360
368
|
|
|
361
369
|
interceptResolvedSegments.push({
|
|
362
|
-
id:
|
|
370
|
+
id: interceptSegmentId,
|
|
363
371
|
namespace: `intercept:${intercept.routeName}`,
|
|
364
372
|
type: "parallel" as const,
|
|
365
373
|
index: 0,
|
|
@@ -379,13 +387,20 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
379
387
|
interceptSegments = await serializeSegments(
|
|
380
388
|
interceptResolvedSegments,
|
|
381
389
|
);
|
|
382
|
-
|
|
390
|
+
const interceptHandlesRecord: Record<string, SegmentHandleData> = {};
|
|
383
391
|
for (const seg of interceptResolvedSegments) {
|
|
384
392
|
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
385
393
|
if (Object.keys(segHandles).length > 0) {
|
|
386
|
-
|
|
394
|
+
interceptHandlesRecord[seg.id] = segHandles;
|
|
387
395
|
}
|
|
388
396
|
}
|
|
397
|
+
// The intercept artifact serves main + intercept segments together, so
|
|
398
|
+
// encode the MERGED handle map here (the sinks no longer merge raw
|
|
399
|
+
// records — they store this pre-encoded string as-is).
|
|
400
|
+
interceptHandles = await encodeHandles({
|
|
401
|
+
...handlesRecord,
|
|
402
|
+
...interceptHandlesRecord,
|
|
403
|
+
});
|
|
389
404
|
}
|
|
390
405
|
}
|
|
391
406
|
|
|
@@ -414,7 +429,7 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
414
429
|
routeName?: string,
|
|
415
430
|
buildEnv?: TEnv,
|
|
416
431
|
devMode?: boolean,
|
|
417
|
-
): Promise<{ encoded: string; handles:
|
|
432
|
+
): Promise<{ encoded: string; handles: string } | null> {
|
|
418
433
|
const syntheticUrl = new URL("http://prerender/");
|
|
419
434
|
const syntheticRequest = new Request(syntheticUrl);
|
|
420
435
|
|
|
@@ -442,11 +457,14 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
442
457
|
header: () => {},
|
|
443
458
|
setStatus: () => {},
|
|
444
459
|
_setStatus: () => {},
|
|
460
|
+
_rotateStateCookie: () => {},
|
|
461
|
+
_setKeepCacheDirective: () => {},
|
|
445
462
|
use: (() => {
|
|
446
463
|
throw new Error("use() not available during static pre-rendering");
|
|
447
464
|
}) as any,
|
|
448
465
|
method: "GET",
|
|
449
466
|
_handleStore: handleStore,
|
|
467
|
+
_requestTags: new Set<string>(),
|
|
450
468
|
waitUntil: () => {},
|
|
451
469
|
onResponse: () => {},
|
|
452
470
|
_onResponseCallbacks: [],
|
|
@@ -479,23 +497,41 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
479
497
|
setupBuildUse(buildCtx);
|
|
480
498
|
|
|
481
499
|
const raw = await handler(buildCtx);
|
|
482
|
-
|
|
500
|
+
|
|
501
|
+
// Static handlers must return a ReactNode. A returned Response (e.g. an
|
|
502
|
+
// accidental redirect()) would otherwise be serialized as a corrupt build
|
|
503
|
+
// artifact; fail loudly instead. The fresh/revalidation paths route handler
|
|
504
|
+
// results through handleHandlerResult, which throws Responses; the static
|
|
505
|
+
// build path bypasses that, so guard here.
|
|
506
|
+
if (raw instanceof Response) {
|
|
507
|
+
throw new TypeError(
|
|
508
|
+
`Static handler "${routeName}" returned a Response. Static handlers must return a ReactNode; ` +
|
|
509
|
+
`Responses (redirects, file responses) are not supported during static pre-rendering.`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
483
512
|
|
|
484
513
|
const segment: ResolvedSegment = {
|
|
485
514
|
id: handlerId,
|
|
486
515
|
namespace: handlerId,
|
|
487
516
|
type: "layout",
|
|
488
517
|
index: 0,
|
|
489
|
-
component,
|
|
518
|
+
component: raw,
|
|
490
519
|
params: {},
|
|
491
520
|
belongsToRoute: false,
|
|
492
521
|
};
|
|
493
522
|
|
|
494
523
|
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
524
|
+
const { encodeHandleValue } = await import("../cache/handle-snapshot.js");
|
|
495
525
|
const [serialized] = await serializeSegments([segment]);
|
|
496
526
|
|
|
497
|
-
// Collect handle data pushed during rendering
|
|
498
|
-
|
|
527
|
+
// Collect handle data pushed during rendering and Flight-encode it (so
|
|
528
|
+
// Promise/ReactNode handle values survive the JSON build artifact). "" when
|
|
529
|
+
// nothing was pushed.
|
|
530
|
+
const segHandles = handleStore.getDataForSegment(handlerId);
|
|
531
|
+
const handles =
|
|
532
|
+
Object.keys(segHandles).length > 0
|
|
533
|
+
? await encodeHandleValue(segHandles)
|
|
534
|
+
: "";
|
|
499
535
|
|
|
500
536
|
return { encoded: serialized.encoded, handles };
|
|
501
537
|
});
|
|
@@ -67,9 +67,11 @@ export async function previewMatch<TEnv = any>(
|
|
|
67
67
|
responseType: negotiation.responseType,
|
|
68
68
|
handler: negotiation.handler,
|
|
69
69
|
params: matched.params,
|
|
70
|
-
negotiated: true,
|
|
71
70
|
manifestEntry: negotiation.manifestEntry,
|
|
72
71
|
routeKey: matched.routeKey,
|
|
72
|
+
// omitted unless a variant negotiated, preserving the prior public
|
|
73
|
+
// shape (absent for plain response routes, not negotiated:false)
|
|
74
|
+
...(negotiation.negotiated ? { negotiated: true } : {}),
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -23,10 +23,6 @@ import { negotiateRoute } from "./content-negotiation.js";
|
|
|
23
23
|
import { stripInternalParams } from "./handler-context.js";
|
|
24
24
|
import { resolveRoute, type RouteSnapshot } from "./route-snapshot.js";
|
|
25
25
|
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// RequestPlan — discriminated union
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
26
|
interface RedirectPlan<TEnv = any> {
|
|
31
27
|
mode: "redirect";
|
|
32
28
|
route: RouteSnapshot<TEnv>;
|
|
@@ -40,6 +36,12 @@ interface VersionMismatchPlan<TEnv = any> {
|
|
|
40
36
|
reloadUrl: string;
|
|
41
37
|
}
|
|
42
38
|
|
|
39
|
+
interface AppSwitchReloadPlan {
|
|
40
|
+
mode: "app-switch";
|
|
41
|
+
/** Clean target URL (internal _rsc_* params stripped) to navigate to. */
|
|
42
|
+
reloadUrl: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
43
45
|
interface ResponseRoutePlan<TEnv = any> {
|
|
44
46
|
mode: "response";
|
|
45
47
|
route: RouteSnapshot<TEnv>;
|
|
@@ -86,6 +88,7 @@ interface PartialRenderPlan<TEnv = any> {
|
|
|
86
88
|
export type RequestPlan<TEnv = any> =
|
|
87
89
|
| RedirectPlan<TEnv>
|
|
88
90
|
| VersionMismatchPlan<TEnv>
|
|
91
|
+
| AppSwitchReloadPlan
|
|
89
92
|
| ResponseRoutePlan<TEnv>
|
|
90
93
|
| LoaderFetchPlan<TEnv>
|
|
91
94
|
| PeRenderPlan<TEnv>
|
|
@@ -94,12 +97,13 @@ export type RequestPlan<TEnv = any> =
|
|
|
94
97
|
| PartialRenderPlan<TEnv>;
|
|
95
98
|
|
|
96
99
|
/**
|
|
97
|
-
* Plans that have passed the terminal-check gate (version-mismatch
|
|
98
|
-
* and are ready for execution. Always have a
|
|
100
|
+
* Plans that have passed the terminal-check gate (version-mismatch and
|
|
101
|
+
* app-switch reloads handled) and are ready for execution. Always have a
|
|
102
|
+
* `route` field.
|
|
99
103
|
*/
|
|
100
104
|
export type ExecutableRequestPlan<TEnv = any> = Exclude<
|
|
101
105
|
RequestPlan<TEnv>,
|
|
102
|
-
VersionMismatchPlan<TEnv>
|
|
106
|
+
VersionMismatchPlan<TEnv> | AppSwitchReloadPlan
|
|
103
107
|
>;
|
|
104
108
|
|
|
105
109
|
/**
|
|
@@ -116,10 +120,6 @@ export type {
|
|
|
116
120
|
PartialRenderPlan,
|
|
117
121
|
};
|
|
118
122
|
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
// classifyRequest — the single authoritative classification step
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
|
|
123
123
|
export interface ClassifyRequestDeps<TEnv = any> {
|
|
124
124
|
findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
|
|
125
125
|
routerVersion: string;
|
|
@@ -149,15 +149,12 @@ export async function classifyRequest<TEnv = any>(
|
|
|
149
149
|
const isAction =
|
|
150
150
|
request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
|
|
151
151
|
|
|
152
|
-
// Version mismatch — runs BEFORE route resolution so stale clients
|
|
153
|
-
// requesting removed routes get a reload, not a 404.
|
|
154
152
|
const clientVersion = url.searchParams.get("_rsc_v");
|
|
155
153
|
if (
|
|
156
154
|
deps.routerVersion &&
|
|
157
155
|
clientVersion &&
|
|
158
156
|
clientVersion !== deps.routerVersion
|
|
159
157
|
) {
|
|
160
|
-
// Strip internal _rsc_* params so the browser reloads to a clean URL
|
|
161
158
|
let reloadUrl = stripInternalParams(url).toString();
|
|
162
159
|
if (isAction) {
|
|
163
160
|
const referer = request.headers.get("referer");
|
|
@@ -167,9 +164,7 @@ export async function classifyRequest<TEnv = any>(
|
|
|
167
164
|
if (refererUrl.origin === url.origin) {
|
|
168
165
|
reloadUrl = referer;
|
|
169
166
|
}
|
|
170
|
-
} catch {
|
|
171
|
-
// Malformed referer, fall back to stripped url
|
|
172
|
-
}
|
|
167
|
+
} catch {}
|
|
173
168
|
}
|
|
174
169
|
}
|
|
175
170
|
|
|
@@ -179,9 +174,18 @@ export async function classifyRequest<TEnv = any>(
|
|
|
179
174
|
};
|
|
180
175
|
}
|
|
181
176
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
const clientRouterId = url.searchParams.get("_rsc_rid");
|
|
178
|
+
if (
|
|
179
|
+
clientRouterId &&
|
|
180
|
+
clientRouterId !== deps.routerId &&
|
|
181
|
+
url.searchParams.has("_rsc_partial")
|
|
182
|
+
) {
|
|
183
|
+
return {
|
|
184
|
+
mode: "app-switch",
|
|
185
|
+
reloadUrl: stripInternalParams(url).toString(),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
185
189
|
const result = await resolveRoute<TEnv>(pathname, {
|
|
186
190
|
findMatch: deps.findMatch,
|
|
187
191
|
lite: true,
|
|
@@ -193,7 +197,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
193
197
|
});
|
|
194
198
|
}
|
|
195
199
|
|
|
196
|
-
// Redirect
|
|
197
200
|
if (result.type === "redirect") {
|
|
198
201
|
const snapshot: RouteSnapshot<TEnv> = {
|
|
199
202
|
matched: result as any,
|
|
@@ -215,7 +218,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
215
218
|
|
|
216
219
|
const snapshot = result.snapshot;
|
|
217
220
|
|
|
218
|
-
// Response route — non-RSC short-circuit (JSON, streaming, etc.)
|
|
219
221
|
const responseResult = await classifyResponseRoute(
|
|
220
222
|
request,
|
|
221
223
|
pathname,
|
|
@@ -225,7 +227,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
225
227
|
return responseResult;
|
|
226
228
|
}
|
|
227
229
|
|
|
228
|
-
// Mode detection from request signals
|
|
229
230
|
const actionId =
|
|
230
231
|
request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
|
|
231
232
|
const isLoaderFetch = url.searchParams.has("_rsc_loader");
|
|
@@ -243,7 +244,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
243
244
|
return { mode: "loader", route: snapshot };
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
// PE detection: POST with form content-type, but not a server action
|
|
247
247
|
const contentType = request.headers.get("content-type") || "";
|
|
248
248
|
const isFormSubmission =
|
|
249
249
|
contentType.includes("multipart/form-data") ||
|
|
@@ -252,22 +252,13 @@ export async function classifyRequest<TEnv = any>(
|
|
|
252
252
|
return { mode: "pe-render", route: snapshot };
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
const clientRouterId = url.searchParams.get("_rsc_rid");
|
|
257
|
-
const isAppSwitch = !!(clientRouterId && clientRouterId !== deps.routerId);
|
|
258
|
-
const isPartial = url.searchParams.has("_rsc_partial") && !isAppSwitch;
|
|
259
|
-
|
|
260
|
-
if (isPartial) {
|
|
255
|
+
if (url.searchParams.has("_rsc_partial")) {
|
|
261
256
|
return { mode: "partial-render", route: snapshot, negotiated };
|
|
262
257
|
}
|
|
263
258
|
|
|
264
259
|
return { mode: "full-render", route: snapshot, negotiated };
|
|
265
260
|
}
|
|
266
261
|
|
|
267
|
-
// ---------------------------------------------------------------------------
|
|
268
|
-
// Content negotiation for response routes
|
|
269
|
-
// ---------------------------------------------------------------------------
|
|
270
|
-
|
|
271
262
|
/**
|
|
272
263
|
* Check if the route is a response route and perform content negotiation
|
|
273
264
|
* if negotiate variants exist. Returns a ResponseRoutePlan if the route
|
|
@@ -278,33 +269,8 @@ async function classifyResponseRoute<TEnv>(
|
|
|
278
269
|
pathname: string,
|
|
279
270
|
snapshot: RouteSnapshot<TEnv>,
|
|
280
271
|
): Promise<ResponseRoutePlan<TEnv> | null> {
|
|
281
|
-
const { manifestEntry, responseType } = snapshot;
|
|
282
|
-
|
|
283
272
|
const negotiation = await negotiateRoute(request, pathname, snapshot);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
route: snapshot,
|
|
288
|
-
...negotiation,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Non-negotiated response route (no variants, or RSC won negotiation)
|
|
293
|
-
if (responseType) {
|
|
294
|
-
const handler =
|
|
295
|
-
manifestEntry.type === "route" ? manifestEntry.handler : undefined;
|
|
296
|
-
if (handler) {
|
|
297
|
-
return {
|
|
298
|
-
mode: "response",
|
|
299
|
-
route: snapshot,
|
|
300
|
-
handler,
|
|
301
|
-
responseType,
|
|
302
|
-
negotiated: false,
|
|
303
|
-
manifestEntry,
|
|
304
|
-
routeMiddleware: snapshot.routeMiddleware,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return null;
|
|
273
|
+
return negotiation
|
|
274
|
+
? { mode: "response", route: snapshot, ...negotiation }
|
|
275
|
+
: null;
|
|
310
276
|
}
|