@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7d061845
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 +120 -25
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2138 -841
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +68 -21
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +26 -4
- package/skills/layout/SKILL.md +6 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +15 -9
- package/skills/migrate-nextjs/SKILL.md +4 -2
- package/skills/migrate-react-router/SKILL.md +5 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +12 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +242 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +33 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +816 -0
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +86 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +65 -9
- package/src/browser/navigation-client.ts +45 -25
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +52 -26
- package/src/browser/prefetch/cache.ts +124 -26
- package/src/browser/prefetch/fetch.ts +114 -38
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +18 -13
- package/src/browser/react/NavigationProvider.tsx +72 -31
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +22 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +64 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +21 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +2 -1
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- 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-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +54 -13
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +10 -8
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +26 -13
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +9 -4
- package/src/index.ts +16 -6
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +21 -6
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -39
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +253 -265
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +43 -15
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/handler-context.ts +21 -41
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +3 -3
- package/src/router/loader-resolution.ts +19 -2
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +53 -32
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +15 -26
- package/src/router/middleware.ts +99 -84
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +27 -6
- package/src/router/segment-resolution/revalidation.ts +147 -106
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +38 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +28 -69
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +35 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +17 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +8 -2
- package/src/search-params.ts +4 -4
- package/src/segment-system.tsx +122 -56
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +118 -51
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +20 -42
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +1 -1
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +332 -0
- package/src/testing/flight.entry.ts +46 -0
- package/src/testing/flight.ts +224 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +304 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +42 -0
- package/src/testing/render-handler.ts +267 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +341 -0
- package/src/testing/run-middleware.ts +188 -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 +270 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +68 -50
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +5 -6
- package/src/types/request-scope.ts +126 -0
- package/src/types/segments.ts +35 -1
- package/src/urls/include-helper.ts +10 -53
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +11 -3
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +101 -51
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +67 -26
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +54 -30
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +496 -486
- package/src/vite/plugins/performance-tracks.ts +29 -25
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +116 -29
- package/src/vite/router-discovery.ts +750 -100
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +5 -4
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { TrieNode, TrieLeaf } from "../build/route-trie.js";
|
|
9
|
+
import { safeDecodeURIComponent } from "./url-params.js";
|
|
9
10
|
|
|
10
11
|
export interface TrieMatchResult {
|
|
11
12
|
/** Route name */
|
|
@@ -14,7 +15,9 @@ export interface TrieMatchResult {
|
|
|
14
15
|
sp: string;
|
|
15
16
|
/** Matched route params */
|
|
16
17
|
params: Record<string, string>;
|
|
17
|
-
/** Optional param names
|
|
18
|
+
/** Optional param names declared on the route. Absent params are omitted
|
|
19
|
+
* from `params` (read as `undefined`), matching the
|
|
20
|
+
* `ExtractParams<"/:locale?/...">` type. */
|
|
18
21
|
optionalParams?: string[];
|
|
19
22
|
/** Ancestry shortCodes for layout pruning */
|
|
20
23
|
ancestry: string[];
|
|
@@ -173,20 +176,25 @@ function validateAndBuild(
|
|
|
173
176
|
originalPathname: string,
|
|
174
177
|
pathnameHasTrailingSlash: boolean,
|
|
175
178
|
): TrieMatchResult | null {
|
|
176
|
-
// Build named params by zipping leaf.pa with positional paramValues
|
|
179
|
+
// Build named params by zipping leaf.pa with positional paramValues.
|
|
180
|
+
// Params are URL-decoded at this boundary so ctx.params holds the values
|
|
181
|
+
// apps expect (matching Express/React Router) and round-trip cleanly
|
|
182
|
+
// through ctx.reverse.
|
|
177
183
|
const params: Record<string, string> = {};
|
|
178
184
|
if (leaf.pa) {
|
|
179
185
|
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
180
|
-
params[leaf.pa[i]] = paramValues[i];
|
|
186
|
+
params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
|
|
181
187
|
}
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
// Add wildcard param (wildcard leaves have pn from TrieNode.w type)
|
|
185
191
|
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
186
|
-
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
192
|
+
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
193
|
+
safeDecodeURIComponent(wildcardValue);
|
|
187
194
|
}
|
|
188
195
|
|
|
189
|
-
// Validate constraints
|
|
196
|
+
// Validate constraints against decoded values so constraint lists can be
|
|
197
|
+
// written in decoded form (e.g. ["en-GB", "en US"]).
|
|
190
198
|
if (leaf.cv) {
|
|
191
199
|
for (const paramName in leaf.cv) {
|
|
192
200
|
const allowed = leaf.cv[paramName]!;
|
|
@@ -197,14 +205,11 @@ function validateAndBuild(
|
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
+
// Optional params that weren't matched are left absent from `params` so
|
|
209
|
+
// `ctx.params.locale` reads as `undefined`, matching the
|
|
210
|
+
// `ExtractParams<"/:locale?/...">` type (`{ locale?: string }`). Both
|
|
211
|
+
// internal consumers — the constraint check above and `reverse()` —
|
|
212
|
+
// already treat missing/undefined as the absent form.
|
|
208
213
|
|
|
209
214
|
// Trailing slash handling
|
|
210
215
|
const tsMode = leaf.ts as "never" | "always" | "ignore" | undefined;
|
package/src/router/types.ts
CHANGED
|
@@ -98,6 +98,14 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
98
98
|
) => ReactNode | NotFoundBoundaryHandler | null;
|
|
99
99
|
notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
100
100
|
callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
|
|
101
|
+
/**
|
|
102
|
+
* Router-level default for the per-segment `transition({ viewTransition })`
|
|
103
|
+
* flag, from createRouter({ viewTransition }). Resolved into each segment's
|
|
104
|
+
* transition config during resolution (only `false` is stamped) so the render
|
|
105
|
+
* gate reads the boundary decision off the segment on both server and client.
|
|
106
|
+
* Undefined is treated as "auto" (wrap).
|
|
107
|
+
*/
|
|
108
|
+
viewTransitionDefault?: "auto" | false;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL param encode/decode at the route boundary.
|
|
3
|
+
*
|
|
4
|
+
* Extraction (decode): regex/trie matchers keep param values URL-encoded;
|
|
5
|
+
* `safeDecodeURIComponent` turns them back into raw strings so `ctx.params`
|
|
6
|
+
* matches the contract apps expect (Express/React Router/Fastify/Koa) and
|
|
7
|
+
* round-trips through reverse stay stable. Malformed %-encoding is
|
|
8
|
+
* preserved as-is so a broken URL doesn't crash matching.
|
|
9
|
+
*
|
|
10
|
+
* Reversal (encode): `encodePathSegment` escapes only what RFC 3986
|
|
11
|
+
* requires for a path segment — `/`, `?`, `#`, space, control chars,
|
|
12
|
+
* non-ASCII — and leaves pchar sub-delims (`@ : $ & + , ; =` and friends)
|
|
13
|
+
* readable. `encodeURIComponent` over-encodes for path segments, which
|
|
14
|
+
* makes generated URLs harder for humans to read in the address bar
|
|
15
|
+
* (e.g. mailbox IDs like `ivo@example.com` would become
|
|
16
|
+
* `ivo%40example.com` even though `@` is path-legal).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export function safeDecodeURIComponent(raw: string): string {
|
|
20
|
+
if (raw === "" || raw.indexOf("%") === -1) return raw;
|
|
21
|
+
try {
|
|
22
|
+
return decodeURIComponent(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// encodeURIComponent over-encodes for path segments. After running it,
|
|
29
|
+
// un-encode the pchar sub-delims + (`:` / `@`) so the resulting URL
|
|
30
|
+
// keeps human-readable characters that are legal in a path segment.
|
|
31
|
+
// Everything dangerous — `/ ? # %` and space/control/non-ASCII — stays
|
|
32
|
+
// encoded.
|
|
33
|
+
const PATH_SAFE_ESCAPES: Record<string, string> = {
|
|
34
|
+
"%3A": ":",
|
|
35
|
+
"%40": "@",
|
|
36
|
+
"%24": "$",
|
|
37
|
+
"%26": "&",
|
|
38
|
+
"%2B": "+",
|
|
39
|
+
"%2C": ",",
|
|
40
|
+
"%3B": ";",
|
|
41
|
+
"%3D": "=",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function encodePathSegment(value: string): string {
|
|
45
|
+
return encodeURIComponent(value).replace(
|
|
46
|
+
/%(?:3A|40|24|26|2B|2C|3B|3D)/gi,
|
|
47
|
+
(match) => PATH_SAFE_ESCAPES[match.toUpperCase()] ?? match,
|
|
48
|
+
);
|
|
49
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -22,10 +22,9 @@ import type { UrlPatterns } from "./urls.js";
|
|
|
22
22
|
import type { UrlBuilder } from "./urls/pattern-types.js";
|
|
23
23
|
import { urls } from "./urls.js";
|
|
24
24
|
import {
|
|
25
|
-
EntryData,
|
|
26
|
-
InterceptSelectorContext,
|
|
25
|
+
type EntryData,
|
|
27
26
|
getContext,
|
|
28
|
-
|
|
27
|
+
RangoContext,
|
|
29
28
|
type MetricsStore,
|
|
30
29
|
} from "./server/context";
|
|
31
30
|
import { createHandleStore, type HandleStore } from "./server/handle-store.js";
|
|
@@ -57,6 +56,7 @@ import { buildDebugManifest } from "./router/debug-manifest.js";
|
|
|
57
56
|
|
|
58
57
|
import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
|
|
59
58
|
import { createHandlerContext } from "./router/handler-context.js";
|
|
59
|
+
import { normalizeBasename } from "./router/basename.js";
|
|
60
60
|
import {
|
|
61
61
|
setupLoaderAccess,
|
|
62
62
|
setupLoaderAccessSilent,
|
|
@@ -91,13 +91,10 @@ import {
|
|
|
91
91
|
RouterRegistry,
|
|
92
92
|
nextRouterAutoId,
|
|
93
93
|
} from "./router/router-registry.js";
|
|
94
|
+
import type { RangoOptions, RootLayoutProps } from "./router/router-options.js";
|
|
94
95
|
import type {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} from "./router/router-options.js";
|
|
98
|
-
import type {
|
|
99
|
-
RSCRouter,
|
|
100
|
-
RSCRouterInternal,
|
|
96
|
+
Rango,
|
|
97
|
+
RangoInternal,
|
|
101
98
|
RouterRequestInput,
|
|
102
99
|
} from "./router/router-interfaces.js";
|
|
103
100
|
|
|
@@ -116,22 +113,22 @@ import {
|
|
|
116
113
|
// Re-export public types and values from extracted modules
|
|
117
114
|
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
|
|
118
115
|
export type {
|
|
119
|
-
|
|
116
|
+
RangoOptions,
|
|
120
117
|
RootLayoutProps,
|
|
121
118
|
SSRStreamMode,
|
|
122
119
|
SSROptions,
|
|
123
120
|
ResolveStreamingContext,
|
|
124
121
|
} from "./router/router-options.js";
|
|
125
122
|
export type {
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
Rango,
|
|
124
|
+
RangoInternal,
|
|
128
125
|
RouterRequestInput,
|
|
129
126
|
} from "./router/router-interfaces.js";
|
|
130
127
|
export { toInternal } from "./router/router-interfaces.js";
|
|
131
128
|
|
|
132
129
|
export function createRouter<TEnv = any>(
|
|
133
|
-
options:
|
|
134
|
-
):
|
|
130
|
+
options: RangoOptions<TEnv> = {},
|
|
131
|
+
): Rango<TEnv, {}> {
|
|
135
132
|
const {
|
|
136
133
|
id: userProvidedId,
|
|
137
134
|
$$id: injectedId,
|
|
@@ -159,14 +156,23 @@ export function createRouter<TEnv = any>(
|
|
|
159
156
|
timeouts: timeoutsOption,
|
|
160
157
|
onTimeout,
|
|
161
158
|
originCheck: originCheckOption,
|
|
159
|
+
viewTransition: viewTransitionOption = "auto",
|
|
160
|
+
debugCacheSignal: debugCacheSignalOption = false,
|
|
162
161
|
} = options;
|
|
163
162
|
|
|
163
|
+
// Debug cache signal gate (DEVELOPMENT/TEST ONLY). Enabled by the
|
|
164
|
+
// debugCacheSignal option OR the RANGO_TEST_SIGNALS=1 env flag. When off,
|
|
165
|
+
// no X-Rango-Cache header is emitted and output is byte-identical.
|
|
166
|
+
const cacheSignalEnabled =
|
|
167
|
+
debugCacheSignalOption ||
|
|
168
|
+
(typeof process !== "undefined" &&
|
|
169
|
+
(process as { env?: Record<string, string | undefined> }).env
|
|
170
|
+
?.RANGO_TEST_SIGNALS === "1");
|
|
171
|
+
|
|
164
172
|
// Normalize basename: ensure leading slash, strip trailing slash.
|
|
165
|
-
// A bare "/" is equivalent to no basename.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
? "/" + basenameOption.replace(/^\/+|\/+$/g, "")
|
|
169
|
-
: undefined;
|
|
173
|
+
// A bare "/" is equivalent to no basename. Shared with the testing
|
|
174
|
+
// primitives via normalizeBasename so they can never drift.
|
|
175
|
+
const basename = normalizeBasename(basenameOption);
|
|
170
176
|
|
|
171
177
|
// Resolve telemetry sink (no-op when not configured)
|
|
172
178
|
const telemetry = resolveSink(telemetrySink);
|
|
@@ -538,6 +544,7 @@ export function createRouter<TEnv = any>(
|
|
|
538
544
|
findNearestNotFoundBoundary,
|
|
539
545
|
notFoundComponent: notFound,
|
|
540
546
|
callOnError,
|
|
547
|
+
viewTransitionDefault: viewTransitionOption,
|
|
541
548
|
};
|
|
542
549
|
|
|
543
550
|
// Match API dependencies
|
|
@@ -665,6 +672,7 @@ export function createRouter<TEnv = any>(
|
|
|
665
672
|
findMatch,
|
|
666
673
|
findInterceptForRoute,
|
|
667
674
|
telemetry: telemetrySink,
|
|
675
|
+
cacheSignalEnabled,
|
|
668
676
|
});
|
|
669
677
|
|
|
670
678
|
const { match, matchPartial, matchError, previewMatch } = matchHandlers;
|
|
@@ -674,7 +682,7 @@ export function createRouter<TEnv = any>(
|
|
|
674
682
|
* The type system tracks accumulated routes through the builder chain
|
|
675
683
|
* Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
|
|
676
684
|
*/
|
|
677
|
-
const router:
|
|
685
|
+
const router: RangoInternal<TEnv, {}> = {
|
|
678
686
|
__brand: RSC_ROUTER_BRAND,
|
|
679
687
|
id: routerId,
|
|
680
688
|
basename,
|
|
@@ -722,7 +730,7 @@ export function createRouter<TEnv = any>(
|
|
|
722
730
|
};
|
|
723
731
|
|
|
724
732
|
let handlerResult: AllUseItems[] = [];
|
|
725
|
-
|
|
733
|
+
RangoContext.run(
|
|
726
734
|
{
|
|
727
735
|
manifest,
|
|
728
736
|
patterns: routePatterns,
|
|
@@ -1000,6 +1008,13 @@ export function createRouter<TEnv = any>(
|
|
|
1000
1008
|
// Expose basename for runtime manifest generation
|
|
1001
1009
|
__basename: basename,
|
|
1002
1010
|
|
|
1011
|
+
// Expose router-level boundary defaults for build-time clientChunks
|
|
1012
|
+
// discovery (so a "use client" default boundary lands in app-fallback).
|
|
1013
|
+
// These are createRouter options, never pushed onto EntryData.
|
|
1014
|
+
__defaultErrorBoundary: defaultErrorBoundary,
|
|
1015
|
+
__defaultNotFoundBoundary: defaultNotFoundBoundary,
|
|
1016
|
+
__notFound: notFound,
|
|
1017
|
+
|
|
1003
1018
|
// RSC request handler (lazily created on first call)
|
|
1004
1019
|
fetch: (() => {
|
|
1005
1020
|
// Handler is created on first call and reused
|
|
@@ -1046,9 +1061,9 @@ export function createRouter<TEnv = any>(
|
|
|
1046
1061
|
|
|
1047
1062
|
// If urls option was provided, auto-register them
|
|
1048
1063
|
if (typeof urlsOption === "function") {
|
|
1049
|
-
return router.routes(urlsOption) as
|
|
1064
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1050
1065
|
} else if (urlsOption) {
|
|
1051
|
-
return router.routes(urlsOption) as
|
|
1066
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1052
1067
|
}
|
|
1053
1068
|
|
|
1054
1069
|
return router;
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* RSC rendering) so they can be standalone modules without closure coupling.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { RangoInternal } from "../router/router-interfaces.js";
|
|
10
10
|
import type { ErrorPhase } from "../types.js";
|
|
11
11
|
import type { InvokeOnErrorContext } from "../router/error-handling.js";
|
|
12
12
|
import type { RSCDependencies, LoadSSRModule } from "./types.js";
|
|
13
13
|
import type { SSRStreamMode } from "../router/router-options.js";
|
|
14
14
|
|
|
15
15
|
export interface HandlerContext<TEnv = unknown> {
|
|
16
|
-
router:
|
|
16
|
+
router: RangoInternal<TEnv, any>;
|
|
17
17
|
version: string;
|
|
18
18
|
renderToReadableStream: RSCDependencies["renderToReadableStream"];
|
|
19
19
|
decodeReply: RSCDependencies["decodeReply"];
|
package/src/rsc/handler.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { createElement } from "react";
|
|
11
|
-
import {
|
|
11
|
+
import { isRouteNotFoundError } from "../errors.js";
|
|
12
12
|
import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
|
|
13
13
|
import {
|
|
14
14
|
runWithRequestContext,
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
interceptRedirectForPartial,
|
|
32
32
|
buildRouteMiddlewareEntries,
|
|
33
33
|
} from "./helpers.js";
|
|
34
|
+
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
34
35
|
import {
|
|
35
36
|
handleResponseRoute,
|
|
36
37
|
type ResponseRouteMatch,
|
|
@@ -56,6 +57,7 @@ import {
|
|
|
56
57
|
getRouterTrie,
|
|
57
58
|
} from "../route-map-builder.js";
|
|
58
59
|
import type { HandlerContext } from "./handler-context.js";
|
|
60
|
+
import type { SegmentCacheStore } from "../cache/types.js";
|
|
59
61
|
import { buildRouterTrieFromUrlpatterns } from "./manifest-init.js";
|
|
60
62
|
import { handleProgressiveEnhancement } from "./progressive-enhancement.js";
|
|
61
63
|
import {
|
|
@@ -64,7 +66,10 @@ import {
|
|
|
64
66
|
type ActionContinuation,
|
|
65
67
|
} from "./server-action.js";
|
|
66
68
|
import { handleLoaderFetch } from "./loader-fetch.js";
|
|
67
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
checkRequestOrigin,
|
|
71
|
+
ORIGIN_CHECK_PHASE_BY_MODE,
|
|
72
|
+
} from "./origin-guard.js";
|
|
68
73
|
import { handleRscRendering } from "./rsc-rendering.js";
|
|
69
74
|
import {
|
|
70
75
|
withTimeout,
|
|
@@ -81,6 +86,7 @@ import {
|
|
|
81
86
|
startSSRSetup,
|
|
82
87
|
getSSRSetup,
|
|
83
88
|
mayNeedSSR,
|
|
89
|
+
isRscRequest,
|
|
84
90
|
SSR_SETUP_VAR,
|
|
85
91
|
} from "./ssr-setup.js";
|
|
86
92
|
import {
|
|
@@ -352,7 +358,7 @@ export function createRSCHandler<
|
|
|
352
358
|
// Resolve cache store configuration
|
|
353
359
|
// Priority: options.cache (handler override) > router.cache (router default)
|
|
354
360
|
// Store is enabled only if: config provided, enabled, and no ?__no_cache query param
|
|
355
|
-
let cacheStore
|
|
361
|
+
let cacheStore: SegmentCacheStore | undefined;
|
|
356
362
|
const cacheOption = options.cache ?? router.cache;
|
|
357
363
|
if (cacheOption && !url.searchParams.has("__no_cache")) {
|
|
358
364
|
const cacheConfig =
|
|
@@ -533,7 +539,9 @@ export function createRSCHandler<
|
|
|
533
539
|
}
|
|
534
540
|
|
|
535
541
|
const fullTiming = timingParts.join(", ");
|
|
536
|
-
if (fullTiming
|
|
542
|
+
if (fullTiming && !isWebSocketUpgradeResponse(response)) {
|
|
543
|
+
response.headers.set("Server-Timing", fullTiming);
|
|
544
|
+
}
|
|
537
545
|
|
|
538
546
|
return response;
|
|
539
547
|
});
|
|
@@ -593,10 +601,7 @@ export function createRSCHandler<
|
|
|
593
601
|
routerId: router.id,
|
|
594
602
|
});
|
|
595
603
|
} catch (error) {
|
|
596
|
-
if (
|
|
597
|
-
error instanceof RouteNotFoundError ||
|
|
598
|
-
(error instanceof Error && error.name === "RouteNotFoundError")
|
|
599
|
-
) {
|
|
604
|
+
if (isRouteNotFoundError(error)) {
|
|
600
605
|
// Let the render path handle 404 — match()/matchPartial() will
|
|
601
606
|
// re-throw RouteNotFoundError and the catch block in
|
|
602
607
|
// executeRenderWithMiddleware renders the not-found page.
|
|
@@ -647,14 +652,7 @@ export function createRSCHandler<
|
|
|
647
652
|
}
|
|
648
653
|
|
|
649
654
|
// ---- 3. Origin guard (gate for action/loader/PE modes) ----
|
|
650
|
-
const originPhase
|
|
651
|
-
plan.mode === "action"
|
|
652
|
-
? "action"
|
|
653
|
-
: plan.mode === "loader"
|
|
654
|
-
? "loader"
|
|
655
|
-
: plan.mode === "pe-render"
|
|
656
|
-
? "pe-form"
|
|
657
|
-
: null;
|
|
655
|
+
const originPhase = ORIGIN_CHECK_PHASE_BY_MODE[plan.mode];
|
|
658
656
|
if (originPhase) {
|
|
659
657
|
const originResult = await checkRequestOrigin(
|
|
660
658
|
request,
|
|
@@ -804,7 +802,7 @@ export function createRSCHandler<
|
|
|
804
802
|
);
|
|
805
803
|
}
|
|
806
804
|
const response = responseOutcome.result;
|
|
807
|
-
if (plan.negotiated) {
|
|
805
|
+
if (plan.negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
808
806
|
response.headers.append("Vary", "Accept");
|
|
809
807
|
}
|
|
810
808
|
return response;
|
|
@@ -921,47 +919,17 @@ export function createRSCHandler<
|
|
|
921
919
|
);
|
|
922
920
|
}
|
|
923
921
|
|
|
924
|
-
//
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
plan.
|
|
931
|
-
|
|
932
|
-
request,
|
|
933
|
-
env,
|
|
934
|
-
url,
|
|
935
|
-
variables,
|
|
936
|
-
nonce,
|
|
937
|
-
handleStore,
|
|
938
|
-
isPartial,
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// PE that fell through (handleProgressiveEnhancement returned null)
|
|
943
|
-
// falls back to full render
|
|
944
|
-
if (plan.mode === "pe-render") {
|
|
945
|
-
return executeRenderWithMiddleware(
|
|
946
|
-
plan.route.routeMiddleware,
|
|
947
|
-
false,
|
|
948
|
-
plan.route.routeKey,
|
|
949
|
-
routeReverse,
|
|
950
|
-
request,
|
|
951
|
-
env,
|
|
952
|
-
url,
|
|
953
|
-
variables,
|
|
954
|
-
nonce,
|
|
955
|
-
handleStore,
|
|
956
|
-
false,
|
|
957
|
-
);
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// Redirect plan that wasn't handled above (full-page redirect — let
|
|
961
|
-
// the pipeline handle it via match() which returns { redirect: url })
|
|
922
|
+
// Full render, partial render, fallen-through PE, and full-page redirect all
|
|
923
|
+
// render through the same middleware-wrapped path. Only full/partial-render
|
|
924
|
+
// carry negotiation + the partial flag; pe/redirect render plainly.
|
|
925
|
+
const isPartial = plan.mode === "partial-render";
|
|
926
|
+
const negotiated =
|
|
927
|
+
plan.mode === "full-render" || plan.mode === "partial-render"
|
|
928
|
+
? plan.negotiated
|
|
929
|
+
: false;
|
|
962
930
|
return executeRenderWithMiddleware(
|
|
963
931
|
plan.route.routeMiddleware,
|
|
964
|
-
|
|
932
|
+
negotiated,
|
|
965
933
|
plan.route.routeKey,
|
|
966
934
|
routeReverse,
|
|
967
935
|
request,
|
|
@@ -970,7 +938,7 @@ export function createRSCHandler<
|
|
|
970
938
|
variables,
|
|
971
939
|
nonce,
|
|
972
940
|
handleStore,
|
|
973
|
-
|
|
941
|
+
isPartial,
|
|
974
942
|
);
|
|
975
943
|
}
|
|
976
944
|
|
|
@@ -1014,7 +982,7 @@ export function createRSCHandler<
|
|
|
1014
982
|
nonce,
|
|
1015
983
|
);
|
|
1016
984
|
}
|
|
1017
|
-
if (negotiated) {
|
|
985
|
+
if (negotiated && !isWebSocketUpgradeResponse(response)) {
|
|
1018
986
|
response.headers.append("Vary", "Accept");
|
|
1019
987
|
}
|
|
1020
988
|
return response;
|
|
@@ -1050,10 +1018,7 @@ export function createRSCHandler<
|
|
|
1050
1018
|
}
|
|
1051
1019
|
|
|
1052
1020
|
// Render 404 page for unmatched routes
|
|
1053
|
-
|
|
1054
|
-
error instanceof RouteNotFoundError ||
|
|
1055
|
-
(error instanceof Error && error.name === "RouteNotFoundError");
|
|
1056
|
-
if (isRouteNotFound) {
|
|
1021
|
+
if (isRouteNotFoundError(error)) {
|
|
1057
1022
|
callOnError(error, "routing", {
|
|
1058
1023
|
request,
|
|
1059
1024
|
url,
|
|
@@ -1100,13 +1065,7 @@ export function createRSCHandler<
|
|
|
1100
1065
|
},
|
|
1101
1066
|
});
|
|
1102
1067
|
|
|
1103
|
-
|
|
1104
|
-
isPartial ||
|
|
1105
|
-
(!request.headers.get("accept")?.includes("text/html") &&
|
|
1106
|
-
!url.searchParams.has("__html")) ||
|
|
1107
|
-
url.searchParams.has("__rsc");
|
|
1108
|
-
|
|
1109
|
-
if (isRscRequest) {
|
|
1068
|
+
if (isRscRequest(request, url, isPartial)) {
|
|
1110
1069
|
return createResponseWithMergedHeaders(rscStream, {
|
|
1111
1070
|
status: 404,
|
|
1112
1071
|
headers: { "content-type": "text/x-component;charset=utf-8" },
|
package/src/rsc/helpers.ts
CHANGED
|
@@ -8,8 +8,67 @@ import {
|
|
|
8
8
|
_getRequestContext,
|
|
9
9
|
getLocationState,
|
|
10
10
|
} from "../server/request-context.js";
|
|
11
|
+
import type { RequestContext } from "../server/request-context.js";
|
|
11
12
|
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
13
|
+
import { isRedirectResponse } from "../response-utils.js";
|
|
12
14
|
import type { MiddlewareEntry, MiddlewareFn } from "../router/middleware.js";
|
|
15
|
+
import { formatCacheSignalHeader } from "../router/telemetry.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* DEVELOPMENT/TEST ONLY. When the debug cache signal gate is on,
|
|
19
|
+
* match/matchPartial populate ctx._cacheSignal. Emit it as the X-Rango-Cache
|
|
20
|
+
* header. When the gate is off, ctx._cacheSignal is undefined and NOTHING is
|
|
21
|
+
* attached — output is byte-identical to the default. Header mutation failures
|
|
22
|
+
* are swallowed so immutable Response headers (e.g. protocol-switch) are safe.
|
|
23
|
+
*/
|
|
24
|
+
function applyCacheSignalHeader(target: Headers, ctx: RequestContext): void {
|
|
25
|
+
const signal = ctx._cacheSignal;
|
|
26
|
+
if (!signal || signal.length === 0) return;
|
|
27
|
+
try {
|
|
28
|
+
target.set("X-Rango-Cache", formatCacheSignalHeader(signal));
|
|
29
|
+
} catch {
|
|
30
|
+
// Headers immutable — skip.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Copy stub headers from the request context onto a target Headers instance:
|
|
36
|
+
* append Set-Cookie entries, set everything else only if absent. Header
|
|
37
|
+
* mutation failures are swallowed so the same logic works against Response
|
|
38
|
+
* headers that may be immutable (e.g. Cloudflare protocol-switch responses).
|
|
39
|
+
*/
|
|
40
|
+
function applyStubHeaders(target: Headers, stub: Headers): void {
|
|
41
|
+
stub.forEach((value, name) => {
|
|
42
|
+
try {
|
|
43
|
+
if (name.toLowerCase() === "set-cookie") {
|
|
44
|
+
target.append(name, value);
|
|
45
|
+
} else if (!target.has(name)) {
|
|
46
|
+
target.set(name, value);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Headers immutable — skip.
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Drain ctx._onResponseCallbacks onto a response. Swapping the array before
|
|
56
|
+
* iteration prevents re-entrant registrations from double-firing and matches
|
|
57
|
+
* the contract that each callback runs at most once per request.
|
|
58
|
+
*/
|
|
59
|
+
function drainOnResponseCallbacks(
|
|
60
|
+
ctx: RequestContext,
|
|
61
|
+
response: Response,
|
|
62
|
+
): Response {
|
|
63
|
+
const callbacks = ctx._onResponseCallbacks;
|
|
64
|
+
if (callbacks.length === 0) return response;
|
|
65
|
+
ctx._onResponseCallbacks = [];
|
|
66
|
+
let result = response;
|
|
67
|
+
for (const callback of callbacks) {
|
|
68
|
+
result = callback(result) ?? result;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
13
72
|
|
|
14
73
|
/**
|
|
15
74
|
* Check if a request body has content to decode
|
|
@@ -39,40 +98,24 @@ export function createResponseWithMergedHeaders(
|
|
|
39
98
|
return new Response(body, init);
|
|
40
99
|
}
|
|
41
100
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// merge points (e.g. executeMiddleware) do not duplicate them.
|
|
101
|
+
// Delete Set-Cookie from the stub after consuming so downstream merge
|
|
102
|
+
// points (e.g. executeMiddleware) don't duplicate them.
|
|
45
103
|
const mergedHeaders = new Headers(init.headers);
|
|
46
|
-
ctx.res.headers
|
|
47
|
-
if (name.toLowerCase() === "set-cookie") {
|
|
48
|
-
mergedHeaders.append(name, value);
|
|
49
|
-
} else if (!mergedHeaders.has(name)) {
|
|
50
|
-
// Only set if not already present in init.headers
|
|
51
|
-
mergedHeaders.set(name, value);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
104
|
+
applyStubHeaders(mergedHeaders, ctx.res.headers);
|
|
54
105
|
ctx.res.headers.delete("set-cookie");
|
|
106
|
+
applyCacheSignalHeader(mergedHeaders, ctx);
|
|
55
107
|
|
|
56
|
-
//
|
|
57
|
-
//
|
|
108
|
+
// ctx.res.status overrides init.status when explicitly set (e.g. 404 for
|
|
109
|
+
// notFound, 500 for error). Default ctx.res.status is 200.
|
|
58
110
|
const status = ctx.res.status !== 200 ? ctx.res.status : init.status;
|
|
59
111
|
|
|
60
|
-
|
|
112
|
+
const response = new Response(body, {
|
|
61
113
|
...init,
|
|
62
114
|
status,
|
|
63
115
|
headers: mergedHeaders,
|
|
64
116
|
});
|
|
65
117
|
|
|
66
|
-
|
|
67
|
-
// Drain the array so that downstream callers (e.g. finalizeResponse)
|
|
68
|
-
// do not re-execute the same callbacks on this response.
|
|
69
|
-
const callbacks = ctx._onResponseCallbacks;
|
|
70
|
-
ctx._onResponseCallbacks = [];
|
|
71
|
-
for (const callback of callbacks) {
|
|
72
|
-
response = callback(response) ?? response;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return response;
|
|
118
|
+
return drainOnResponseCallbacks(ctx, response);
|
|
76
119
|
}
|
|
77
120
|
|
|
78
121
|
/**
|
|
@@ -122,10 +165,10 @@ export function interceptRedirectForPartial(
|
|
|
122
165
|
locationState?: Record<string, unknown>,
|
|
123
166
|
) => Response,
|
|
124
167
|
): Response | null {
|
|
125
|
-
|
|
126
|
-
if (!(response.status >= 300 && response.status < 400 && redirectUrl)) {
|
|
168
|
+
if (!isRedirectResponse(response)) {
|
|
127
169
|
return null;
|
|
128
170
|
}
|
|
171
|
+
const redirectUrl = response.headers.get("Location")!;
|
|
129
172
|
const locationState = getLocationState();
|
|
130
173
|
let intercepted: Response;
|
|
131
174
|
if (locationState) {
|
|
@@ -175,24 +218,29 @@ export function buildRouteMiddlewareEntries<TEnv>(
|
|
|
175
218
|
}
|
|
176
219
|
|
|
177
220
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
221
|
+
* Merge stub headers from the request context onto an existing Response in
|
|
222
|
+
* place, then drain onResponse callbacks. Used when a Response cannot flow
|
|
223
|
+
* through `new Response()` — status 101 is outside the constructor's
|
|
224
|
+
* 200-599 range, and the Cloudflare-specific `webSocket` property would be
|
|
225
|
+
* lost on reconstruction.
|
|
183
226
|
*/
|
|
184
|
-
export function
|
|
227
|
+
export function mergeStubHeadersAndFinalize(response: Response): Response {
|
|
185
228
|
const ctx = _getRequestContext();
|
|
186
|
-
if (!ctx
|
|
187
|
-
return response;
|
|
188
|
-
}
|
|
229
|
+
if (!ctx) return response;
|
|
189
230
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
231
|
+
applyStubHeaders(response.headers, ctx.res.headers);
|
|
232
|
+
ctx.res.headers.delete("set-cookie");
|
|
233
|
+
|
|
234
|
+
return drainOnResponseCallbacks(ctx, response);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Run onResponse callbacks on an existing Response. Used by code paths that
|
|
239
|
+
* bypass createResponseWithMergedHeaders (e.g. middleware short-circuits)
|
|
240
|
+
* but still need ctx.onResponse() callbacks to fire.
|
|
241
|
+
*/
|
|
242
|
+
export function finalizeResponse(response: Response): Response {
|
|
243
|
+
const ctx = _getRequestContext();
|
|
244
|
+
if (!ctx) return response;
|
|
245
|
+
return drainOnResponseCallbacks(ctx, response);
|
|
198
246
|
}
|