@rangojs/router 0.0.0-experimental.122 → 0.0.0-experimental.125
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/bin/rango.js +10 -6
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +55 -48
- package/package.json +61 -21
- package/skills/caching/SKILL.md +2 -1
- package/skills/hooks/SKILL.md +40 -29
- package/skills/host-router/SKILL.md +16 -2
- package/skills/intercept/SKILL.md +4 -2
- package/skills/layout/SKILL.md +11 -6
- package/skills/loader/SKILL.md +6 -2
- package/skills/middleware/SKILL.md +4 -2
- package/skills/migrate-nextjs/SKILL.md +3 -1
- package/skills/parallel/SKILL.md +9 -4
- package/skills/rango/SKILL.md +12 -0
- package/skills/route/SKILL.md +10 -2
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +89 -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 +118 -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/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +14 -1
- package/src/browser/navigation-client.ts +14 -1
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +26 -51
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +1 -83
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/fetch.ts +7 -0
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -99
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- 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 -51
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- 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 +0 -2
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +12 -4
- package/src/browser/server-action-bridge.ts +77 -15
- package/src/browser/types.ts +7 -2
- package/src/browser/validate-redirect-origin.ts +4 -5
- package/src/build/route-trie.ts +3 -0
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +27 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +94 -46
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +11 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +2 -48
- package/src/cache/profile-registry.ts +7 -3
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/types.ts +0 -98
- package/src/client.rsc.tsx +1 -22
- package/src/client.tsx +14 -38
- package/src/component-utils.ts +19 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +28 -18
- package/src/handles/MetaTags.tsx +0 -14
- 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 +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +42 -3
- package/src/index.ts +31 -1
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- 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 +23 -30
- package/src/prerender.ts +58 -3
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +11 -1
- package/src/route-map-builder.ts +0 -16
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -30
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +3 -2
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- 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 +1 -54
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -21
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-cookies.ts +0 -13
- package/src/router/middleware-types.ts +0 -115
- package/src/router/middleware.ts +7 -30
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +1 -33
- package/src/router/prerender-match.ts +33 -45
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +5 -58
- package/src/router/router-context.ts +0 -26
- package/src/router/router-interfaces.ts +7 -0
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +10 -13
- package/src/router/segment-resolution/revalidation.ts +5 -42
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/state-cookie-name.ts +33 -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 +63 -40
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +40 -9
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/helpers.ts +34 -0
- package/src/rsc/origin-guard.ts +0 -12
- package/src/rsc/progressive-enhancement.ts +4 -1
- package/src/rsc/rsc-rendering.ts +4 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +30 -28
- package/src/rsc/types.ts +2 -1
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/cookie-store.ts +52 -1
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +74 -77
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +119 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +581 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +127 -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 +186 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +98 -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 +311 -0
- package/src/testing/render-route.tsx +504 -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/error-types.ts +25 -89
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +16 -13
- package/src/types/index.ts +0 -10
- 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 +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +6 -7
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +3 -1
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/use-cache-transform.ts +0 -36
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +1 -108
- package/src/vite/router-discovery.ts +2 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
package/src/router/telemetry.ts
CHANGED
|
@@ -14,10 +14,6 @@
|
|
|
14
14
|
* - revalidation.decision (revalidation evaluation)
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Event types
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
17
|
interface BaseEvent {
|
|
22
18
|
/** Monotonic timestamp from performance.now() */
|
|
23
19
|
timestamp: number;
|
|
@@ -90,6 +86,34 @@ export interface HandlerErrorEvent extends BaseEvent {
|
|
|
90
86
|
params?: Record<string, string>;
|
|
91
87
|
}
|
|
92
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Per-segment (or coarse route-level) cache status carried on the
|
|
91
|
+
* cache.decision telemetry event and the X-Rango-Cache debug header.
|
|
92
|
+
*
|
|
93
|
+
* v1 is COARSE: the router's pipeline tracks cache decisions at the
|
|
94
|
+
* route/entry level (cacheHit/cacheSource/shouldRevalidate), not per
|
|
95
|
+
* individual segment. The `segments` array therefore contains a single
|
|
96
|
+
* route-level entry keyed by the route key. The shape is forward-compatible
|
|
97
|
+
* with genuine per-segment status if the pipeline later exposes it.
|
|
98
|
+
*/
|
|
99
|
+
export type CacheSegmentStatus =
|
|
100
|
+
| "hit"
|
|
101
|
+
| "miss"
|
|
102
|
+
| "stale"
|
|
103
|
+
| "prerendered"
|
|
104
|
+
| "passthrough";
|
|
105
|
+
|
|
106
|
+
export interface CacheSegmentSignal {
|
|
107
|
+
/** Segment id (v1: the route key, since status is route-level). */
|
|
108
|
+
id: string;
|
|
109
|
+
/** Segment type (v1: "route" for the coarse route-level entry). */
|
|
110
|
+
type: string;
|
|
111
|
+
/** Resolved cache status for this segment. */
|
|
112
|
+
cacheStatus: CacheSegmentStatus;
|
|
113
|
+
/** Whether stale-while-revalidate was triggered for this segment. */
|
|
114
|
+
shouldRevalidate?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
93
117
|
export interface CacheDecisionEvent extends BaseEvent {
|
|
94
118
|
type: "cache.decision";
|
|
95
119
|
pathname: string;
|
|
@@ -98,6 +122,12 @@ export interface CacheDecisionEvent extends BaseEvent {
|
|
|
98
122
|
/** Whether stale-while-revalidate was triggered */
|
|
99
123
|
shouldRevalidate: boolean;
|
|
100
124
|
source?: "runtime" | "prerender";
|
|
125
|
+
/**
|
|
126
|
+
* Optional per-segment (v1: coarse route-level) cache status. Present only
|
|
127
|
+
* when telemetry or the debug cache signal is enabled. Optional so existing
|
|
128
|
+
* sinks are unaffected.
|
|
129
|
+
*/
|
|
130
|
+
segments?: CacheSegmentSignal[];
|
|
101
131
|
}
|
|
102
132
|
|
|
103
133
|
export interface RevalidationDecisionEvent extends BaseEvent {
|
|
@@ -141,9 +171,70 @@ export type TelemetryEvent =
|
|
|
141
171
|
| OriginCheckRejectedEvent;
|
|
142
172
|
|
|
143
173
|
// ---------------------------------------------------------------------------
|
|
144
|
-
//
|
|
174
|
+
// Cache signal derivation (coarse, route-level)
|
|
145
175
|
// ---------------------------------------------------------------------------
|
|
146
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Derive the coarse, route-level cache status from pipeline cache state.
|
|
179
|
+
*
|
|
180
|
+
* v1 mapping (route-level — see CacheSegmentSignal):
|
|
181
|
+
* - prerender hit -> "prerendered"
|
|
182
|
+
* - runtime hit + shouldRevalidate (SWR) -> "stale"
|
|
183
|
+
* - runtime hit -> "hit"
|
|
184
|
+
* - no hit -> "miss"
|
|
185
|
+
*
|
|
186
|
+
* Note: "passthrough" is a build-time prerender concept (a route opts out of
|
|
187
|
+
* being prerendered for some params). At runtime a passthrough route renders
|
|
188
|
+
* fresh and is indistinguishable from a normal miss in the pipeline state, so
|
|
189
|
+
* v1 reports it as "miss". The "passthrough" status remains in the type union
|
|
190
|
+
* for forward compatibility.
|
|
191
|
+
*/
|
|
192
|
+
export function deriveCacheStatus(state: {
|
|
193
|
+
cacheHit: boolean;
|
|
194
|
+
cacheSource?: "runtime" | "prerender";
|
|
195
|
+
shouldRevalidate?: boolean;
|
|
196
|
+
}): CacheSegmentStatus {
|
|
197
|
+
if (state.cacheHit) {
|
|
198
|
+
if (state.cacheSource === "prerender") return "prerendered";
|
|
199
|
+
if (state.shouldRevalidate) return "stale";
|
|
200
|
+
return "hit";
|
|
201
|
+
}
|
|
202
|
+
return "miss";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Build the coarse route-level cache signal array (a single entry keyed by
|
|
207
|
+
* the route key). Used for both the cache.decision telemetry event and the
|
|
208
|
+
* X-Rango-Cache debug header.
|
|
209
|
+
*/
|
|
210
|
+
export function buildCacheSignalSegments(
|
|
211
|
+
routeKey: string,
|
|
212
|
+
state: {
|
|
213
|
+
cacheHit: boolean;
|
|
214
|
+
cacheSource?: "runtime" | "prerender";
|
|
215
|
+
shouldRevalidate?: boolean;
|
|
216
|
+
},
|
|
217
|
+
): CacheSegmentSignal[] {
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
id: routeKey,
|
|
221
|
+
type: "route",
|
|
222
|
+
cacheStatus: deriveCacheStatus(state),
|
|
223
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Serialize cache signal segments into the X-Rango-Cache header value:
|
|
230
|
+
* `<segId>=<status>, <segId2>=<status2>`.
|
|
231
|
+
*/
|
|
232
|
+
export function formatCacheSignalHeader(
|
|
233
|
+
segments: CacheSegmentSignal[],
|
|
234
|
+
): string {
|
|
235
|
+
return segments.map((s) => `${s.id}=${s.cacheStatus}`).join(", ");
|
|
236
|
+
}
|
|
237
|
+
|
|
147
238
|
/**
|
|
148
239
|
* Telemetry sink receives structured lifecycle events from the router.
|
|
149
240
|
* Implement this interface to integrate with any observability backend.
|
|
@@ -154,10 +245,6 @@ export interface TelemetrySink {
|
|
|
154
245
|
emit(event: TelemetryEvent): void;
|
|
155
246
|
}
|
|
156
247
|
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
// No-op singleton (zero-cost disabled state)
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
|
|
161
248
|
const noopSink: TelemetrySink = {
|
|
162
249
|
emit() {},
|
|
163
250
|
};
|
|
@@ -185,12 +272,6 @@ export function safeEmit(sink: TelemetrySink, event: TelemetryEvent): void {
|
|
|
185
272
|
}
|
|
186
273
|
}
|
|
187
274
|
|
|
188
|
-
// ---------------------------------------------------------------------------
|
|
189
|
-
// Request ID extraction (for span correlation)
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
|
|
192
|
-
// Per-request memoization so the same Request object always maps to the
|
|
193
|
-
// same ID. WeakMap allows GC when the Request is no longer referenced.
|
|
194
275
|
const requestIds = new WeakMap<Request, string>();
|
|
195
276
|
let telemetryRequestCounter = 0;
|
|
196
277
|
|
|
@@ -224,10 +305,6 @@ export function getRequestId(request: Request): string {
|
|
|
224
305
|
return id;
|
|
225
306
|
}
|
|
226
307
|
|
|
227
|
-
// ---------------------------------------------------------------------------
|
|
228
|
-
// Console sink (built-in, replaces ad-hoc console.log debug traces)
|
|
229
|
-
// ---------------------------------------------------------------------------
|
|
230
|
-
|
|
231
308
|
/**
|
|
232
309
|
* Built-in console sink that logs events in a structured format.
|
|
233
310
|
* Designed as the default sink for development / debugging.
|
package/src/router/timeout.ts
CHANGED
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
* a Promise.race mechanism, returning 504 on expiry.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Public types
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
9
|
export interface RouterTimeouts {
|
|
14
10
|
/** Timeout for server action execution (ms). */
|
|
15
11
|
actionMs?: number;
|
|
@@ -35,10 +31,6 @@ export type OnTimeoutCallback<TEnv = any> = (
|
|
|
35
31
|
ctx: TimeoutContext<TEnv>,
|
|
36
32
|
) => Response | Promise<Response>;
|
|
37
33
|
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Internal resolved form
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
34
|
export interface ResolvedTimeouts {
|
|
43
35
|
actionMs: number | undefined;
|
|
44
36
|
renderStartMs: number | undefined;
|
|
@@ -63,10 +55,6 @@ export function resolveTimeouts(
|
|
|
63
55
|
};
|
|
64
56
|
}
|
|
65
57
|
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
// Error class
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
|
|
70
58
|
export class RouterTimeoutError extends Error {
|
|
71
59
|
override name = "RouterTimeoutError" as const;
|
|
72
60
|
phase: TimeoutPhase;
|
|
@@ -81,10 +69,6 @@ export class RouterTimeoutError extends Error {
|
|
|
81
69
|
}
|
|
82
70
|
}
|
|
83
71
|
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// Race helper
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
72
|
type TimeoutResult<T> =
|
|
89
73
|
| { result: T; timedOut: false }
|
|
90
74
|
| { timedOut: true; durationMs: number };
|
|
@@ -129,10 +113,6 @@ export async function withTimeout<T>(
|
|
|
129
113
|
}
|
|
130
114
|
}
|
|
131
115
|
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
// Default response
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
|
|
136
116
|
/**
|
|
137
117
|
* Create the default 504 response for a timed-out request.
|
|
138
118
|
* Includes `X-Rango-Timeout-Phase` header for observability.
|
|
@@ -43,14 +43,12 @@ export function tryTrieMatch(
|
|
|
43
43
|
): TrieMatchResult | null {
|
|
44
44
|
if (!trie) return null;
|
|
45
45
|
|
|
46
|
-
// Split pathname into segments, filtering empty strings from leading/trailing slashes
|
|
47
46
|
const pathnameHasTrailingSlash =
|
|
48
47
|
pathname.length > 1 && pathname.endsWith("/");
|
|
49
48
|
const normalizedPath = pathnameHasTrailingSlash
|
|
50
49
|
? pathname.slice(0, -1)
|
|
51
50
|
: pathname;
|
|
52
51
|
|
|
53
|
-
// Handle root path
|
|
54
52
|
if (normalizedPath === "" || normalizedPath === "/") {
|
|
55
53
|
if (trie.r) {
|
|
56
54
|
return validateAndBuild(
|
|
@@ -77,10 +75,8 @@ export function tryTrieMatch(
|
|
|
77
75
|
return null;
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
// Remove leading slash and split
|
|
81
78
|
const segments = normalizedPath.slice(1).split("/");
|
|
82
79
|
|
|
83
|
-
// Try exact match with normalized path (no trailing slash)
|
|
84
80
|
const result = walkTrie(trie, segments, 0, []);
|
|
85
81
|
if (result) {
|
|
86
82
|
return validateAndBuild(
|
|
@@ -102,8 +98,58 @@ interface WalkResult {
|
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
101
|
+
* Check a leaf's constraints (leaf.cv) against already-resolved named params.
|
|
102
|
+
* Empty/undefined values are exempt (optional params that were not bound).
|
|
103
|
+
*/
|
|
104
|
+
function constraintsSatisfied(
|
|
105
|
+
leaf: TrieLeaf,
|
|
106
|
+
params: Record<string, string>,
|
|
107
|
+
): boolean {
|
|
108
|
+
if (!leaf.cv) return true;
|
|
109
|
+
for (const paramName in leaf.cv) {
|
|
110
|
+
const allowed = leaf.cv[paramName]!;
|
|
111
|
+
const value = params[paramName];
|
|
112
|
+
if (value !== undefined && value !== "" && !allowed.includes(value)) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Constraint check for a candidate terminal DURING the walk. Builds the named
|
|
121
|
+
* params from positional walk values (decoded the same way validateAndBuild
|
|
122
|
+
* does) and validates leaf.cv. Returning false lets walkTrie unwind to a
|
|
123
|
+
* lower-priority sibling instead of committing to a leaf that would only be
|
|
124
|
+
* rejected post-walk — that post-walk rejection is what forced the regex
|
|
125
|
+
* fallback (and its false "trie gap" R3 warning) for perfectly valid configs.
|
|
126
|
+
*/
|
|
127
|
+
function leafConstraintsPass(
|
|
128
|
+
leaf: TrieLeaf,
|
|
129
|
+
paramValues: string[],
|
|
130
|
+
wildcardValue: string | undefined,
|
|
131
|
+
): boolean {
|
|
132
|
+
if (!leaf.cv) return true;
|
|
133
|
+
const params: Record<string, string> = {};
|
|
134
|
+
if (leaf.pa) {
|
|
135
|
+
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
136
|
+
params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
140
|
+
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
141
|
+
safeDecodeURIComponent(wildcardValue);
|
|
142
|
+
}
|
|
143
|
+
return constraintsSatisfied(leaf, params);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Walk the trie by segments with priority: static > suffix-param > param >
|
|
148
|
+
* wildcard (Priority 1-4 below; matches the canonical M4 ordering in
|
|
149
|
+
* docs/internal/matching-and-lazy-discovery.md).
|
|
150
|
+
* Uses backtracking to try all possible matches. Per-leaf constraints are
|
|
151
|
+
* enforced at each candidate terminal so a constraint miss backtracks to a
|
|
152
|
+
* lower-priority sibling rather than aborting the whole match.
|
|
107
153
|
*/
|
|
108
154
|
function walkTrie(
|
|
109
155
|
node: TrieNode,
|
|
@@ -111,9 +157,8 @@ function walkTrie(
|
|
|
111
157
|
index: number,
|
|
112
158
|
paramValues: string[],
|
|
113
159
|
): WalkResult | null {
|
|
114
|
-
// All segments consumed: check for terminal
|
|
115
160
|
if (index === segments.length) {
|
|
116
|
-
if (node.r) {
|
|
161
|
+
if (node.r && leafConstraintsPass(node.r, paramValues, undefined)) {
|
|
117
162
|
return { leaf: node.r, paramValues: [...paramValues] };
|
|
118
163
|
}
|
|
119
164
|
// A wildcard at this node matches the bare prefix with an empty remainder
|
|
@@ -122,7 +167,7 @@ function walkTrie(
|
|
|
122
167
|
// so without this a request to the wildcard's own prefix misses the trie
|
|
123
168
|
// and the regex fallback emits a corrupt redirect. A static terminal
|
|
124
169
|
// (node.r) still wins.
|
|
125
|
-
if (node.w) {
|
|
170
|
+
if (node.w && leafConstraintsPass(node.w, paramValues, "")) {
|
|
126
171
|
return { leaf: node.w, paramValues: [...paramValues], wildcardValue: "" };
|
|
127
172
|
}
|
|
128
173
|
return null;
|
|
@@ -131,13 +176,11 @@ function walkTrie(
|
|
|
131
176
|
const segment = segments[index];
|
|
132
177
|
const staticChild = node.s?.[segment];
|
|
133
178
|
|
|
134
|
-
// Priority 1: Static match
|
|
135
179
|
if (staticChild) {
|
|
136
180
|
const result = walkTrie(staticChild, segments, index + 1, paramValues);
|
|
137
181
|
if (result) return result;
|
|
138
182
|
}
|
|
139
183
|
|
|
140
|
-
// Priority 2: Suffix-param match (e.g., :productId.html)
|
|
141
184
|
if (node.xp) {
|
|
142
185
|
for (const suffix in node.xp) {
|
|
143
186
|
if (segment.endsWith(suffix) && segment.length > suffix.length) {
|
|
@@ -155,7 +198,6 @@ function walkTrie(
|
|
|
155
198
|
}
|
|
156
199
|
}
|
|
157
200
|
|
|
158
|
-
// Priority 3: Param match
|
|
159
201
|
if (node.p) {
|
|
160
202
|
paramValues.push(segment);
|
|
161
203
|
const result = walkTrie(node.p.c, segments, index + 1, paramValues);
|
|
@@ -163,14 +205,15 @@ function walkTrie(
|
|
|
163
205
|
if (result) return result;
|
|
164
206
|
}
|
|
165
207
|
|
|
166
|
-
// Priority 4: Wildcard match (consumes rest)
|
|
167
208
|
if (node.w) {
|
|
168
209
|
const rest = joinRemainingSegments(segments, index);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
210
|
+
if (leafConstraintsPass(node.w, paramValues, rest)) {
|
|
211
|
+
return {
|
|
212
|
+
leaf: node.w,
|
|
213
|
+
paramValues: [...paramValues],
|
|
214
|
+
wildcardValue: rest,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
174
217
|
}
|
|
175
218
|
|
|
176
219
|
return null;
|
|
@@ -196,10 +239,6 @@ function validateAndBuild(
|
|
|
196
239
|
originalPathname: string,
|
|
197
240
|
pathnameHasTrailingSlash: boolean,
|
|
198
241
|
): TrieMatchResult | null {
|
|
199
|
-
// Build named params by zipping leaf.pa with positional paramValues.
|
|
200
|
-
// Params are URL-decoded at this boundary so ctx.params holds the values
|
|
201
|
-
// apps expect (matching Express/React Router) and round-trip cleanly
|
|
202
|
-
// through ctx.reverse.
|
|
203
242
|
const params: Record<string, string> = {};
|
|
204
243
|
if (leaf.pa) {
|
|
205
244
|
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
@@ -207,31 +246,15 @@ function validateAndBuild(
|
|
|
207
246
|
}
|
|
208
247
|
}
|
|
209
248
|
|
|
210
|
-
// Add wildcard param (wildcard leaves have pn from TrieNode.w type)
|
|
211
249
|
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
212
250
|
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
213
251
|
safeDecodeURIComponent(wildcardValue);
|
|
214
252
|
}
|
|
215
253
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (leaf.cv) {
|
|
219
|
-
for (const paramName in leaf.cv) {
|
|
220
|
-
const allowed = leaf.cv[paramName]!;
|
|
221
|
-
const value = params[paramName];
|
|
222
|
-
if (value !== undefined && value !== "" && !allowed.includes(value)) {
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
254
|
+
if (!constraintsSatisfied(leaf, params)) {
|
|
255
|
+
return null;
|
|
226
256
|
}
|
|
227
257
|
|
|
228
|
-
// Optional params that weren't matched are left absent from `params` so
|
|
229
|
-
// `ctx.params.locale` reads as `undefined`, matching the
|
|
230
|
-
// `ExtractParams<"/:locale?/...">` type (`{ locale?: string }`). Both
|
|
231
|
-
// internal consumers — the constraint check above and `reverse()` —
|
|
232
|
-
// already treat missing/undefined as the absent form.
|
|
233
|
-
|
|
234
|
-
// Trailing slash handling
|
|
235
258
|
const tsMode = leaf.ts as "never" | "always" | "ignore" | undefined;
|
|
236
259
|
let redirectTo: string | undefined;
|
|
237
260
|
|
package/src/router/types.ts
CHANGED
|
@@ -22,27 +22,11 @@ import type {
|
|
|
22
22
|
ShouldRevalidateFn,
|
|
23
23
|
} from "../types";
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* Result of resolving loaders with revalidation
|
|
27
|
-
* Contains both segments to render and all matched segment IDs
|
|
28
|
-
*/
|
|
29
|
-
export interface LoaderRevalidationResult {
|
|
30
|
-
segments: ResolvedSegment[];
|
|
31
|
-
matchedIds: string[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Result of resolving segments with revalidation
|
|
36
|
-
* Contains both segments to render and all matched segment IDs
|
|
37
|
-
*/
|
|
38
25
|
export interface SegmentRevalidationResult {
|
|
39
26
|
segments: ResolvedSegment[];
|
|
40
27
|
matchedIds: string[];
|
|
41
28
|
}
|
|
42
29
|
|
|
43
|
-
/**
|
|
44
|
-
* Action context type for revalidation
|
|
45
|
-
*/
|
|
46
30
|
export type ActionContext = {
|
|
47
31
|
actionId?: string;
|
|
48
32
|
actionUrl?: URL;
|
|
@@ -50,23 +34,6 @@ export type ActionContext = {
|
|
|
50
34
|
formData?: FormData;
|
|
51
35
|
};
|
|
52
36
|
|
|
53
|
-
/**
|
|
54
|
-
* Dependencies passed to segment resolution functions
|
|
55
|
-
* These are created within createRouter and passed to extracted utilities
|
|
56
|
-
*/
|
|
57
|
-
export interface RouterDependencies<TEnv> {
|
|
58
|
-
findNearestErrorBoundary: (
|
|
59
|
-
entry: EntryData | null,
|
|
60
|
-
) => ReactNode | ErrorBoundaryHandler | null;
|
|
61
|
-
findNearestNotFoundBoundary: (
|
|
62
|
-
entry: EntryData | null,
|
|
63
|
-
) => ReactNode | NotFoundBoundaryHandler | null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Dependencies injected from createRouter closure into extracted segment resolution functions.
|
|
68
|
-
* These are the closure-bound helpers that cannot be imported directly.
|
|
69
|
-
*/
|
|
70
37
|
export interface SegmentResolutionDeps<TEnv = any> {
|
|
71
38
|
wrapLoaderPromise: <T>(
|
|
72
39
|
promise: Promise<T>,
|
|
@@ -108,21 +75,6 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
108
75
|
viewTransitionDefault?: "auto" | false;
|
|
109
76
|
}
|
|
110
77
|
|
|
111
|
-
/**
|
|
112
|
-
* Dependencies injected from createRouter closure into extracted intercept resolution functions.
|
|
113
|
-
*/
|
|
114
|
-
export interface InterceptResolutionDeps<TEnv = any> {
|
|
115
|
-
wrapLoaderPromise: SegmentResolutionDeps<TEnv>["wrapLoaderPromise"];
|
|
116
|
-
evaluateInterceptWhen: (
|
|
117
|
-
intercept: InterceptEntry,
|
|
118
|
-
selectorContext: InterceptSelectorContext | null,
|
|
119
|
-
isAction: boolean,
|
|
120
|
-
) => boolean;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Dependencies injected from createRouter closure into extracted match API functions.
|
|
125
|
-
*/
|
|
126
78
|
export interface MatchApiDeps<TEnv = any> {
|
|
127
79
|
findMatch: (pathname: string, ms?: any) => any;
|
|
128
80
|
getMetricsStore: () => any;
|
|
@@ -137,23 +89,13 @@ export interface MatchApiDeps<TEnv = any> {
|
|
|
137
89
|
getRouteMap: () => Record<string, string>;
|
|
138
90
|
}
|
|
139
91
|
|
|
140
|
-
/**
|
|
141
|
-
* Title descriptor types for template support
|
|
142
|
-
*/
|
|
143
92
|
export type TitleDescriptor =
|
|
144
93
|
| string
|
|
145
94
|
| { template: string; default: string } // For layouts - template applied to child titles
|
|
146
|
-
| { absolute: string };
|
|
95
|
+
| { absolute: string };
|
|
147
96
|
|
|
148
|
-
/**
|
|
149
|
-
* Unset descriptor to remove inherited meta
|
|
150
|
-
* Key format matches getMetaKey output: "title", "name:description", "property:og:image"
|
|
151
|
-
*/
|
|
152
97
|
export type UnsetDescriptor = { unset: string };
|
|
153
98
|
|
|
154
|
-
/**
|
|
155
|
-
* Base meta descriptor types (sync values)
|
|
156
|
-
*/
|
|
157
99
|
export type MetaDescriptorBase =
|
|
158
100
|
| { charSet: "utf-8" }
|
|
159
101
|
| { title: TitleDescriptor }
|
|
@@ -165,10 +107,6 @@ export type MetaDescriptorBase =
|
|
|
165
107
|
| UnsetDescriptor
|
|
166
108
|
| { [name: string]: unknown };
|
|
167
109
|
|
|
168
|
-
/**
|
|
169
|
-
* Meta descriptor that can be sync or async.
|
|
170
|
-
* Use Promise<MetaDescriptorBase> for streaming meta that resolves after initial render.
|
|
171
|
-
*/
|
|
172
110
|
export type MetaDescriptor = MetaDescriptorBase | Promise<MetaDescriptorBase>;
|
|
173
111
|
|
|
174
112
|
type LdJsonObject = { [Key in string]: LdJsonValue } & {
|
package/src/router/url-params.ts
CHANGED
|
@@ -25,11 +25,6 @@ export function safeDecodeURIComponent(raw: string): string {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
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
28
|
const PATH_SAFE_ESCAPES: Record<string, string> = {
|
|
34
29
|
"%3A": ":",
|
|
35
30
|
"%40": "@",
|
package/src/router.ts
CHANGED
|
@@ -57,6 +57,7 @@ import { buildDebugManifest } from "./router/debug-manifest.js";
|
|
|
57
57
|
|
|
58
58
|
import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
|
|
59
59
|
import { createHandlerContext } from "./router/handler-context.js";
|
|
60
|
+
import { normalizeBasename } from "./router/basename.js";
|
|
60
61
|
import {
|
|
61
62
|
setupLoaderAccess,
|
|
62
63
|
setupLoaderAccessSilent,
|
|
@@ -110,6 +111,7 @@ import {
|
|
|
110
111
|
matchForPrerender as _matchForPrerender,
|
|
111
112
|
renderStaticSegment as _renderStaticSegment,
|
|
112
113
|
} from "./router/prerender-match.js";
|
|
114
|
+
import { resolveStateCookieName } from "./router/state-cookie-name.js";
|
|
113
115
|
|
|
114
116
|
// Re-export public types and values from extracted modules
|
|
115
117
|
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
|
|
@@ -149,6 +151,7 @@ export function createRouter<TEnv = any>(
|
|
|
149
151
|
nonce,
|
|
150
152
|
version,
|
|
151
153
|
prefetchCacheTTL: prefetchCacheTTLOption,
|
|
154
|
+
stateCookiePrefix: stateCookiePrefixOption,
|
|
152
155
|
warmup: warmupOption,
|
|
153
156
|
allowDebugManifest: allowDebugManifestOption = false,
|
|
154
157
|
telemetry: telemetrySink,
|
|
@@ -158,14 +161,22 @@ export function createRouter<TEnv = any>(
|
|
|
158
161
|
onTimeout,
|
|
159
162
|
originCheck: originCheckOption,
|
|
160
163
|
viewTransition: viewTransitionOption = "auto",
|
|
164
|
+
debugCacheSignal: debugCacheSignalOption = false,
|
|
161
165
|
} = options;
|
|
162
166
|
|
|
167
|
+
// Debug cache signal gate (DEVELOPMENT/TEST ONLY). Enabled by the
|
|
168
|
+
// debugCacheSignal option OR the RANGO_TEST_SIGNALS=1 env flag. When off,
|
|
169
|
+
// no X-Rango-Cache header is emitted and output is byte-identical.
|
|
170
|
+
const cacheSignalEnabled =
|
|
171
|
+
debugCacheSignalOption ||
|
|
172
|
+
(typeof process !== "undefined" &&
|
|
173
|
+
(process as { env?: Record<string, string | undefined> }).env
|
|
174
|
+
?.RANGO_TEST_SIGNALS === "1");
|
|
175
|
+
|
|
163
176
|
// Normalize basename: ensure leading slash, strip trailing slash.
|
|
164
|
-
// A bare "/" is equivalent to no basename.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
? "/" + basenameOption.replace(/^\/+|\/+$/g, "")
|
|
168
|
-
: undefined;
|
|
177
|
+
// A bare "/" is equivalent to no basename. Shared with the testing
|
|
178
|
+
// primitives via normalizeBasename so they can never drift.
|
|
179
|
+
const basename = normalizeBasename(basenameOption);
|
|
169
180
|
|
|
170
181
|
// Resolve telemetry sink (no-op when not configured)
|
|
171
182
|
const telemetry = resolveSink(telemetrySink);
|
|
@@ -209,6 +220,14 @@ export function createRouter<TEnv = any>(
|
|
|
209
220
|
const routerId =
|
|
210
221
|
userProvidedId ?? injectedId ?? `router_${nextRouterAutoId()}`;
|
|
211
222
|
|
|
223
|
+
// Resolve the rango state cookie name once, here, so the two cookie writers
|
|
224
|
+
// (the client document.cookie writer and the server Set-Cookie writer)
|
|
225
|
+
// consume one pre-composed name and cannot drift.
|
|
226
|
+
const resolvedStateCookieName = resolveStateCookieName(
|
|
227
|
+
stateCookiePrefixOption,
|
|
228
|
+
routerId,
|
|
229
|
+
);
|
|
230
|
+
|
|
212
231
|
// Resolve prefetch cache TTL (default: 300 seconds / 5 minutes)
|
|
213
232
|
// Clamp to a non-negative integer for valid Cache-Control max-age.
|
|
214
233
|
const rawTTL =
|
|
@@ -255,9 +274,14 @@ export function createRouter<TEnv = any>(
|
|
|
255
274
|
invokeOnError(onError, error, phase, context, "Router");
|
|
256
275
|
}
|
|
257
276
|
|
|
258
|
-
// Validate document is a client component
|
|
277
|
+
// Validate document is a client component. Under a test runner the "use
|
|
278
|
+
// client" transform has not run, so a real exported document has no marker;
|
|
279
|
+
// allowServerInTest lets the router construct in a bare unit test (for
|
|
280
|
+
// dispatch / assertGeneratedRoutesMatch) while a real build still throws.
|
|
259
281
|
if (documentOption !== undefined) {
|
|
260
|
-
assertClientComponent(documentOption, "document"
|
|
282
|
+
assertClientComponent(documentOption, "document", {
|
|
283
|
+
allowServerInTest: true,
|
|
284
|
+
});
|
|
261
285
|
}
|
|
262
286
|
|
|
263
287
|
// Use default document if none provided (keeps internal name as rootLayout)
|
|
@@ -667,6 +691,7 @@ export function createRouter<TEnv = any>(
|
|
|
667
691
|
findMatch,
|
|
668
692
|
findInterceptForRoute,
|
|
669
693
|
telemetry: telemetrySink,
|
|
694
|
+
cacheSignalEnabled,
|
|
670
695
|
});
|
|
671
696
|
|
|
672
697
|
const { match, matchPartial, matchError, previewMatch } = matchHandlers;
|
|
@@ -938,6 +963,10 @@ export function createRouter<TEnv = any>(
|
|
|
938
963
|
prefetchCacheControl,
|
|
939
964
|
prefetchCacheTTL,
|
|
940
965
|
|
|
966
|
+
// Expose the resolved rango state cookie name for the server-side writer
|
|
967
|
+
// (invalidateClientCache) and for shipping to the client in metadata.
|
|
968
|
+
resolvedStateCookieName,
|
|
969
|
+
|
|
941
970
|
// Expose warmup enabled flag for handler and client
|
|
942
971
|
warmupEnabled,
|
|
943
972
|
|
|
@@ -1029,8 +1058,10 @@ export function createRouter<TEnv = any>(
|
|
|
1029
1058
|
if (!handler) {
|
|
1030
1059
|
// Lazy import deferred to first request to avoid dev mode issues
|
|
1031
1060
|
const { createRSCHandler } = await import("./rsc/handler.js");
|
|
1032
|
-
// Cast:
|
|
1033
|
-
//
|
|
1061
|
+
// Cast: createRSCHandler receives `router as any`, which erases TEnv
|
|
1062
|
+
// and infers its handler as RouterRequestInput<unknown>. Re-narrow the
|
|
1063
|
+
// returned handler to RouterRequestInput<TEnv> so the call below stays
|
|
1064
|
+
// typed. (The handler already accepts (request, RouterRequestInput).)
|
|
1034
1065
|
handler = createRSCHandler({
|
|
1035
1066
|
router: router as any,
|
|
1036
1067
|
cache,
|