@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.d98a8e9d
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 +2154 -861
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/api-client/SKILL.md +211 -0
- 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 +364 -0
- 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 +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +47 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +71 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +243 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +57 -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 +128 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +121 -0
- package/skills/testing/e2e-parity.md +124 -0
- package/skills/testing/flight.md +89 -0
- package/skills/testing/handles.md +127 -0
- package/skills/testing/loader.md +108 -0
- package/skills/testing/middleware.md +97 -0
- package/skills/testing/render-handler.md +102 -0
- package/skills/testing/response-routes.md +94 -0
- package/skills/testing/reverse-and-types.md +83 -0
- package/skills/testing/server-actions.md +89 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -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 +116 -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 +84 -11
- package/src/browser/navigation-client.ts +104 -68
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +64 -26
- package/src/browser/prefetch/cache.ts +183 -44
- package/src/browser/prefetch/fetch.ts +228 -37
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- 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 +32 -1
- package/src/browser/rsc-router.tsx +69 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-reconciler.ts +36 -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 +8 -1
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +95 -25
- 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 +96 -205
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -4
- package/src/handle.ts +32 -14
- 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 -21
- package/src/index.rsc.ts +10 -6
- package/src/index.ts +54 -17
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +25 -7
- package/src/loader.ts +16 -9
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +27 -6
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -36
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +384 -257
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +100 -28
- 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 +26 -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/find-match.ts +54 -6
- package/src/router/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +41 -22
- package/src/router/loader-resolution.ts +82 -36
- package/src/router/manifest.ts +41 -19
- 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 +116 -19
- 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 +40 -16
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +52 -30
- 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/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +57 -61
- 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/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +132 -116
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +175 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +67 -51
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +25 -3
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -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 +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 +326 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +110 -0
- package/src/testing/flight-normalize.ts +38 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +51 -0
- package/src/testing/flight.ts +234 -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 +323 -0
- package/src/testing/render-route.tsx +590 -0
- package/src/testing/run-loader.ts +363 -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 +285 -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 +11 -9
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +35 -2
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +1 -5
- package/src/urls/path-helper-types.ts +41 -7
- 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 +58 -139
- 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 +106 -75
- 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 +8 -59
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +21 -6
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
package/src/router/manifest.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type MetricsStore,
|
|
15
15
|
} from "../server/context";
|
|
16
16
|
import MapRootLayout from "../server/root-layout";
|
|
17
|
+
import { joinPrefix } from "./pattern-matching.js";
|
|
17
18
|
import type { RouteEntry } from "../types";
|
|
18
19
|
import type { UrlPatterns } from "../urls";
|
|
19
20
|
import { VERSION } from "@rangojs/router:version";
|
|
@@ -23,10 +24,17 @@ import { VERSION } from "@rangojs/router:version";
|
|
|
23
24
|
// stable references), so the resulting EntryData tree can be safely cached and reused
|
|
24
25
|
// across requests within the same isolate.
|
|
25
26
|
//
|
|
26
|
-
// Cache is keyed by (VERSION, mountIndex, routeKey, isSSR).
|
|
27
|
+
// Cache is keyed by (VERSION, routerId, mountIndex, routeKey, isSSR). routeKey is
|
|
28
|
+
// REQUIRED in the key: loadManifest() runs the handler with forRoute=routeKey, and
|
|
29
|
+
// path-helper.ts prunes (skips registering) every route except forRoute, so the
|
|
30
|
+
// resulting Store.manifest is pruned to the requested route — NOT the full include.
|
|
31
|
+
// Dropping routeKey would make a sibling route miss and overwrite this entry with its
|
|
32
|
+
// own pruned manifest, so alternating sibling requests would thrash (re-run the
|
|
33
|
+
// handler every time). Running the include handler once per isolate instead of once
|
|
34
|
+
// per route is possible but needs an unpruned manifest cache with prune-on-read — see
|
|
35
|
+
// LP1 in docs/internal/matching-and-lazy-discovery.md. VERSION comes from the
|
|
27
36
|
// @rangojs/router:version virtual module which Vite invalidates on RSC module HMR.
|
|
28
37
|
// When VERSION changes, this module re-evaluates and the cache is recreated empty.
|
|
29
|
-
// Including VERSION in the key is additional defense against stale entries.
|
|
30
38
|
const manifestModuleCache = new Map<string, Map<string, EntryData>>();
|
|
31
39
|
|
|
32
40
|
/**
|
|
@@ -34,8 +42,8 @@ const manifestModuleCache = new Map<string, Map<string, EntryData>>();
|
|
|
34
42
|
* Handles lazy imports, unwrapping, and validation
|
|
35
43
|
*
|
|
36
44
|
* Results are cached at module level after first execution. Subsequent calls
|
|
37
|
-
* for the same (routeKey, isSSR) within the same isolate
|
|
38
|
-
* without re-executing the DSL handler.
|
|
45
|
+
* for the same (routerId, mountIndex, routeKey, isSSR) within the same isolate
|
|
46
|
+
* return cached data without re-executing the DSL handler.
|
|
39
47
|
*/
|
|
40
48
|
/**
|
|
41
49
|
* Clear the module-level manifest cache.
|
|
@@ -65,9 +73,11 @@ export async function loadManifest(
|
|
|
65
73
|
|
|
66
74
|
const mountIndex = entry.mountIndex;
|
|
67
75
|
|
|
68
|
-
// Check module-level cache (persists across requests within same isolate)
|
|
76
|
+
// Check module-level cache (persists across requests within same isolate).
|
|
69
77
|
// Include routerId so multi-router setups (host routing) don't share cached
|
|
70
78
|
// EntryData across routers with overlapping mountIndex + routeKey combinations.
|
|
79
|
+
// routeKey is in the key because loadManifest() builds a manifest pruned to
|
|
80
|
+
// forRoute=routeKey (see path-helper.ts) — see the cache comment above.
|
|
71
81
|
const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
|
|
72
82
|
const cached = manifestModuleCache.get(cacheKey);
|
|
73
83
|
if (cached) {
|
|
@@ -126,28 +136,37 @@ export async function loadManifest(
|
|
|
126
136
|
// were created during pattern extraction. This prevents shortCode
|
|
127
137
|
// collisions between lazy and non-lazy entries under the same parent
|
|
128
138
|
// (e.g., ArticlesLayout and BlogLayout both under NavLayout).
|
|
129
|
-
if (lazyContext
|
|
130
|
-
const
|
|
131
|
-
for (const [key, value] of Object.entries(captured)) {
|
|
139
|
+
if (lazyContext?.counters) {
|
|
140
|
+
for (const [key, value] of Object.entries(lazyContext.counters)) {
|
|
132
141
|
Store.counters[key] = Math.max(Store.counters[key] ?? 0, value);
|
|
133
142
|
}
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
// Propagate cache profiles for DSL-time cache("profileName") resolution.
|
|
137
146
|
// Non-lazy entries carry profiles directly; lazy entries carry them
|
|
138
|
-
// in the captured lazyContext from include() time.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
// in the captured lazyContext from include() time. Always write
|
|
148
|
+
// (including clearing to undefined) so a prior lazy build's profile
|
|
149
|
+
// map cannot leak into a later non-lazy build on the same ALS-backed
|
|
150
|
+
// Store — which would otherwise let cache("name") resolve a profile
|
|
151
|
+
// from an unrelated entry.
|
|
152
|
+
Store.cacheProfiles = entry.cacheProfiles ?? lazyContext?.cacheProfiles;
|
|
144
153
|
|
|
145
154
|
// Propagate rootScoped from lazyContext so that routes inside
|
|
146
155
|
// nested { name: "sub" } under { name: "" } keep inherited root scope
|
|
147
|
-
// when the manifest is rebuilt on each request.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
// when the manifest is rebuilt on each request. Always write
|
|
157
|
+
// (including clearing to undefined, which makes getRootScoped()
|
|
158
|
+
// return its true default) so a prior lazy build's scope cannot leak
|
|
159
|
+
// into a later non-lazy build on the same ALS-backed Store — which
|
|
160
|
+
// would otherwise mis-register plain routes as non-root-scoped and
|
|
161
|
+
// break dot-local reverse resolution.
|
|
162
|
+
Store.rootScoped = lazyContext?.rootScoped;
|
|
163
|
+
|
|
164
|
+
// Propagate includeScope from lazyContext so that direct-descendant
|
|
165
|
+
// shortCodes of this include use the correct scoped counter namespace
|
|
166
|
+
// on every manifest rebuild. Always write (including clearing to
|
|
167
|
+
// undefined) so a prior lazy build's scope cannot leak into a later
|
|
168
|
+
// non-lazy build on the same ALS-backed Store.
|
|
169
|
+
Store.includeScope = lazyContext?.includeScope;
|
|
151
170
|
|
|
152
171
|
const handlerExecStart = performance.now();
|
|
153
172
|
const useItems = await getContext().runWithStore(
|
|
@@ -167,7 +186,10 @@ export async function loadManifest(
|
|
|
167
186
|
if (entry.lazy && entry.lazyPatterns) {
|
|
168
187
|
const lazyPatterns = entry.lazyPatterns as UrlPatterns<any>;
|
|
169
188
|
const includePrefix = (entry as any)._lazyPrefix || "";
|
|
170
|
-
|
|
189
|
+
// Slash-collapsing join so a trailing-slash parent prefix does not
|
|
190
|
+
// bake a double slash into the registered route patterns (must match
|
|
191
|
+
// the same join in evaluateLazyEntry / the build-time runWithPrefixes).
|
|
192
|
+
const fullPrefix = joinPrefix(lazyContext?.urlPrefix, includePrefix);
|
|
171
193
|
|
|
172
194
|
if (fullPrefix || lazyContext?.namePrefix) {
|
|
173
195
|
return runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () =>
|
package/src/router/match-api.ts
CHANGED
|
@@ -22,10 +22,10 @@ import { collectRouteMiddleware } from "./middleware.js";
|
|
|
22
22
|
import { traverseBack } from "./pattern-matching.js";
|
|
23
23
|
import { DefaultErrorFallback } from "../default-error-boundary.js";
|
|
24
24
|
import {
|
|
25
|
-
EntryData,
|
|
26
|
-
LoaderEntry,
|
|
25
|
+
type EntryData,
|
|
26
|
+
type LoaderEntry,
|
|
27
27
|
getContext,
|
|
28
|
-
InterceptSelectorContext,
|
|
28
|
+
type InterceptSelectorContext,
|
|
29
29
|
} from "../server/context";
|
|
30
30
|
import type { ErrorBoundaryHandler, ErrorInfo, MatchResult } from "../types";
|
|
31
31
|
import type { ReactNode } from "react";
|
|
@@ -550,6 +550,7 @@ export async function matchError<TEnv>(
|
|
|
550
550
|
segments: [errorSegment],
|
|
551
551
|
matched: matchedIds,
|
|
552
552
|
diff: [errorSegment.id],
|
|
553
|
+
resolvedIds: [errorSegment.id],
|
|
553
554
|
params: matched.params,
|
|
554
555
|
};
|
|
555
556
|
}
|
|
@@ -33,10 +33,13 @@ import type { ErrorBoundaryHandler, NotFoundBoundaryHandler } from "../types";
|
|
|
33
33
|
import type { MiddlewareFn } from "./middleware.js";
|
|
34
34
|
import {
|
|
35
35
|
type TelemetrySink,
|
|
36
|
+
type CacheSegmentSignal,
|
|
36
37
|
safeEmit,
|
|
37
38
|
resolveSink,
|
|
38
39
|
getRequestId,
|
|
40
|
+
buildCacheSignalSegments,
|
|
39
41
|
} from "./telemetry.js";
|
|
42
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
40
43
|
|
|
41
44
|
export interface MatchHandlerDeps<TEnv = any> {
|
|
42
45
|
buildRouterContext: () => RouterContext<TEnv>;
|
|
@@ -51,6 +54,12 @@ export interface MatchHandlerDeps<TEnv = any> {
|
|
|
51
54
|
isAction: boolean,
|
|
52
55
|
) => { intercept: InterceptEntry; entry: EntryData } | null;
|
|
53
56
|
telemetry?: TelemetrySink;
|
|
57
|
+
/**
|
|
58
|
+
* DEVELOPMENT/TEST ONLY gate for the X-Rango-Cache debug header. When true,
|
|
59
|
+
* match/matchPartial stash a coarse route-level cache signal on the request
|
|
60
|
+
* context for the response-finalization path to emit. Default off.
|
|
61
|
+
*/
|
|
62
|
+
cacheSignalEnabled?: boolean;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
export interface MatchHandlers<TEnv = any> {
|
|
@@ -113,6 +122,25 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
113
122
|
} = deps;
|
|
114
123
|
const hasTelemetry = !!deps.telemetry;
|
|
115
124
|
const telemetry = resolveSink(deps.telemetry);
|
|
125
|
+
const cacheSignalEnabled = !!deps.cacheSignalEnabled;
|
|
126
|
+
// Compute the coarse cache signal when EITHER telemetry needs it (for the
|
|
127
|
+
// cache.decision event) OR the debug header gate is on. When neither is set,
|
|
128
|
+
// this is never called — zero extra work on the hot path.
|
|
129
|
+
const buildSignal = (
|
|
130
|
+
routeKey: string,
|
|
131
|
+
state: {
|
|
132
|
+
cacheHit: boolean;
|
|
133
|
+
cacheSource?: "runtime" | "prerender";
|
|
134
|
+
shouldRevalidate?: boolean;
|
|
135
|
+
},
|
|
136
|
+
): CacheSegmentSignal[] => buildCacheSignalSegments(routeKey, state);
|
|
137
|
+
// Stash the signal on the request context for the response path to emit as
|
|
138
|
+
// the X-Rango-Cache header. Only when the debug gate is on.
|
|
139
|
+
const recordSignalIfEnabled = (segments: CacheSegmentSignal[]): void => {
|
|
140
|
+
if (!cacheSignalEnabled) return;
|
|
141
|
+
const reqCtx = _getRequestContext();
|
|
142
|
+
if (reqCtx) reqCtx._cacheSignal = segments;
|
|
143
|
+
};
|
|
116
144
|
|
|
117
145
|
async function createMatchContextForFull(
|
|
118
146
|
request: Request,
|
|
@@ -196,6 +224,7 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
196
224
|
segments: [],
|
|
197
225
|
matched: [],
|
|
198
226
|
diff: [],
|
|
227
|
+
resolvedIds: [],
|
|
199
228
|
params: {},
|
|
200
229
|
redirect: result.redirectUrl,
|
|
201
230
|
};
|
|
@@ -207,17 +236,24 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
207
236
|
const state = createPipelineState();
|
|
208
237
|
const pipeline = createMatchPartialPipeline(ctx, state);
|
|
209
238
|
const matchResult = await collectMatchResult(pipeline, ctx, state);
|
|
239
|
+
if (hasTelemetry || cacheSignalEnabled) {
|
|
240
|
+
const signalSegments = buildSignal(ctx.routeKey, state);
|
|
241
|
+
recordSignalIfEnabled(signalSegments);
|
|
242
|
+
if (hasTelemetry) {
|
|
243
|
+
safeEmit(telemetry, {
|
|
244
|
+
type: "cache.decision",
|
|
245
|
+
timestamp: performance.now(),
|
|
246
|
+
requestId,
|
|
247
|
+
pathname,
|
|
248
|
+
routeKey: ctx.routeKey,
|
|
249
|
+
hit: state.cacheHit,
|
|
250
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
251
|
+
source: state.cacheSource,
|
|
252
|
+
segments: signalSegments,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
210
256
|
if (hasTelemetry) {
|
|
211
|
-
safeEmit(telemetry, {
|
|
212
|
-
type: "cache.decision",
|
|
213
|
-
timestamp: performance.now(),
|
|
214
|
-
requestId,
|
|
215
|
-
pathname,
|
|
216
|
-
routeKey: ctx.routeKey,
|
|
217
|
-
hit: state.cacheHit,
|
|
218
|
-
shouldRevalidate: !!state.shouldRevalidate,
|
|
219
|
-
source: state.cacheSource,
|
|
220
|
-
});
|
|
221
257
|
safeEmit(telemetry, {
|
|
222
258
|
type: "request.end",
|
|
223
259
|
timestamp: performance.now(),
|
|
@@ -362,17 +398,24 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
362
398
|
state,
|
|
363
399
|
);
|
|
364
400
|
flushRevalidationTrace();
|
|
401
|
+
if (hasTelemetry || cacheSignalEnabled) {
|
|
402
|
+
const signalSegments = buildSignal(ctx.routeKey, state);
|
|
403
|
+
recordSignalIfEnabled(signalSegments);
|
|
404
|
+
if (hasTelemetry) {
|
|
405
|
+
safeEmit(telemetry, {
|
|
406
|
+
type: "cache.decision",
|
|
407
|
+
timestamp: performance.now(),
|
|
408
|
+
requestId: partialRequestId,
|
|
409
|
+
pathname,
|
|
410
|
+
routeKey: ctx.routeKey,
|
|
411
|
+
hit: state.cacheHit,
|
|
412
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
413
|
+
source: state.cacheSource,
|
|
414
|
+
segments: signalSegments,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
365
418
|
if (hasTelemetry) {
|
|
366
|
-
safeEmit(telemetry, {
|
|
367
|
-
type: "cache.decision",
|
|
368
|
-
timestamp: performance.now(),
|
|
369
|
-
requestId: partialRequestId,
|
|
370
|
-
pathname,
|
|
371
|
-
routeKey: ctx.routeKey,
|
|
372
|
-
hit: state.cacheHit,
|
|
373
|
-
shouldRevalidate: !!state.shouldRevalidate,
|
|
374
|
-
source: state.cacheSource,
|
|
375
|
-
});
|
|
376
419
|
safeEmit(telemetry, {
|
|
377
420
|
type: "request.end",
|
|
378
421
|
timestamp: performance.now(),
|
|
@@ -282,6 +282,38 @@ async function* yieldFromStore<TEnv>(
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Look up a prerendered (build-time cached) entry for the current route and, on
|
|
287
|
+
* a hit, yield its segments. Returns true when an entry was served (the caller
|
|
288
|
+
* should stop the pipeline) and false on a miss. Intercept navigations consult
|
|
289
|
+
* only the intercept-specific entry (`paramHash + "/i"`); a miss there falls
|
|
290
|
+
* through to the normal pipeline so intercept-resolution can run. Callers must
|
|
291
|
+
* guard on `prerenderStoreInstance` after `ensurePrerenderDeps()`.
|
|
292
|
+
*/
|
|
293
|
+
async function* tryPrerenderLookup<TEnv>(
|
|
294
|
+
ctx: MatchContext<TEnv>,
|
|
295
|
+
state: MatchPipelineState,
|
|
296
|
+
pipelineStart: number,
|
|
297
|
+
handleStoreRef?: HandleStore,
|
|
298
|
+
): AsyncGenerator<ResolvedSegment, boolean> {
|
|
299
|
+
const paramHash = _hashParams!(ctx.matched.params);
|
|
300
|
+
const isPassthroughPrerenderRoute = ctx.entries.some(
|
|
301
|
+
(entry) => entry.type === "route" && entry.isPassthrough === true,
|
|
302
|
+
);
|
|
303
|
+
const lookupHash = ctx.isIntercept ? paramHash + "/i" : paramHash;
|
|
304
|
+
const entry = await prerenderStoreInstance!.get(
|
|
305
|
+
ctx.matched.routeKey,
|
|
306
|
+
lookupHash,
|
|
307
|
+
{
|
|
308
|
+
pathname: ctx.pathname,
|
|
309
|
+
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
310
|
+
},
|
|
311
|
+
);
|
|
312
|
+
if (!entry) return false;
|
|
313
|
+
yield* yieldFromStore(entry, ctx, state, pipelineStart, handleStoreRef);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
285
317
|
/**
|
|
286
318
|
* Async generator middleware type
|
|
287
319
|
*/
|
|
@@ -334,54 +366,13 @@ export function withCacheLookup<TEnv>(
|
|
|
334
366
|
if (!ctx.isAction && !isHmr && ctx.matched.pr) {
|
|
335
367
|
await ensurePrerenderDeps();
|
|
336
368
|
if (prerenderStoreInstance) {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
369
|
+
const served = yield* tryPrerenderLookup(
|
|
370
|
+
ctx,
|
|
371
|
+
state,
|
|
372
|
+
pipelineStart,
|
|
373
|
+
handleStoreRef,
|
|
340
374
|
);
|
|
341
|
-
|
|
342
|
-
if (ctx.isIntercept) {
|
|
343
|
-
// Intercept navigation: try intercept-specific prerender entry
|
|
344
|
-
const entry = await prerenderStoreInstance.get(
|
|
345
|
-
ctx.matched.routeKey,
|
|
346
|
-
paramHash + "/i",
|
|
347
|
-
{
|
|
348
|
-
pathname: ctx.pathname,
|
|
349
|
-
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
350
|
-
},
|
|
351
|
-
);
|
|
352
|
-
if (entry) {
|
|
353
|
-
yield* yieldFromStore(
|
|
354
|
-
entry,
|
|
355
|
-
ctx,
|
|
356
|
-
state,
|
|
357
|
-
pipelineStart,
|
|
358
|
-
handleStoreRef,
|
|
359
|
-
);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
// No intercept prerender -- fall through to normal pipeline
|
|
363
|
-
// (skip non-intercept prerender to let intercept-resolution run)
|
|
364
|
-
} else {
|
|
365
|
-
// Normal navigation: existing behavior
|
|
366
|
-
const entry = await prerenderStoreInstance.get(
|
|
367
|
-
ctx.matched.routeKey,
|
|
368
|
-
paramHash,
|
|
369
|
-
{
|
|
370
|
-
pathname: ctx.pathname,
|
|
371
|
-
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
372
|
-
},
|
|
373
|
-
);
|
|
374
|
-
if (entry) {
|
|
375
|
-
yield* yieldFromStore(
|
|
376
|
-
entry,
|
|
377
|
-
ctx,
|
|
378
|
-
state,
|
|
379
|
-
pipelineStart,
|
|
380
|
-
handleStoreRef,
|
|
381
|
-
);
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
375
|
+
if (served) return;
|
|
385
376
|
}
|
|
386
377
|
}
|
|
387
378
|
|
|
@@ -404,51 +395,13 @@ export function withCacheLookup<TEnv>(
|
|
|
404
395
|
if (hasStatic) {
|
|
405
396
|
await ensurePrerenderDeps();
|
|
406
397
|
if (prerenderStoreInstance) {
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
398
|
+
const served = yield* tryPrerenderLookup(
|
|
399
|
+
ctx,
|
|
400
|
+
state,
|
|
401
|
+
pipelineStart,
|
|
402
|
+
handleStoreRef,
|
|
410
403
|
);
|
|
411
|
-
|
|
412
|
-
if (ctx.isIntercept) {
|
|
413
|
-
const entry = await prerenderStoreInstance.get(
|
|
414
|
-
ctx.matched.routeKey,
|
|
415
|
-
paramHash + "/i",
|
|
416
|
-
{
|
|
417
|
-
pathname: ctx.pathname,
|
|
418
|
-
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
419
|
-
},
|
|
420
|
-
);
|
|
421
|
-
if (entry) {
|
|
422
|
-
yield* yieldFromStore(
|
|
423
|
-
entry,
|
|
424
|
-
ctx,
|
|
425
|
-
state,
|
|
426
|
-
pipelineStart,
|
|
427
|
-
handleStoreRef,
|
|
428
|
-
);
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
// No intercept prerender -- fall through to normal pipeline
|
|
432
|
-
} else {
|
|
433
|
-
const entry = await prerenderStoreInstance.get(
|
|
434
|
-
ctx.matched.routeKey,
|
|
435
|
-
paramHash,
|
|
436
|
-
{
|
|
437
|
-
pathname: ctx.pathname,
|
|
438
|
-
isPassthroughRoute: isPassthroughPrerenderRoute,
|
|
439
|
-
},
|
|
440
|
-
);
|
|
441
|
-
if (entry) {
|
|
442
|
-
yield* yieldFromStore(
|
|
443
|
-
entry,
|
|
444
|
-
ctx,
|
|
445
|
-
state,
|
|
446
|
-
pipelineStart,
|
|
447
|
-
handleStoreRef,
|
|
448
|
-
);
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
404
|
+
if (served) return;
|
|
452
405
|
}
|
|
453
406
|
}
|
|
454
407
|
}
|
|
@@ -169,10 +169,11 @@ export function withCacheStore<TEnv>(
|
|
|
169
169
|
// skip (client already had them). Segments where the handler intentionally
|
|
170
170
|
// returned null are not revalidation skips — re-rendering them will still
|
|
171
171
|
// produce null, so proactive caching would be wasted work.
|
|
172
|
-
const clientIdSet = new Set(ctx.clientSegmentIds);
|
|
173
172
|
const hasNullComponents = allSegmentsToCache.some(
|
|
174
173
|
(s) =>
|
|
175
|
-
s.component === null &&
|
|
174
|
+
s.component === null &&
|
|
175
|
+
s.type !== "loader" &&
|
|
176
|
+
ctx.clientSegmentSet.has(s.id),
|
|
176
177
|
);
|
|
177
178
|
|
|
178
179
|
const requestCtx = getRequestContext();
|
|
@@ -138,34 +138,38 @@ export async function collectSegments(
|
|
|
138
138
|
function deduplicateLoaderSegments(
|
|
139
139
|
segments: ResolvedSegment[],
|
|
140
140
|
logPrefix: string,
|
|
141
|
-
): ResolvedSegment[] {
|
|
142
|
-
//
|
|
143
|
-
// and
|
|
141
|
+
): { segments: ResolvedSegment[]; removedIds: Set<string> } {
|
|
142
|
+
// Single pass: original (non-inherited) loaderIds, all loaderIds grouped by
|
|
143
|
+
// namespace, and namespaces of segments that declare loading().
|
|
144
144
|
const originalLoaders = new Set<string>();
|
|
145
|
-
const
|
|
145
|
+
const loaderIdsByNamespace = new Map<string, string[]>();
|
|
146
|
+
const namespacesWithLoading = new Set<string>();
|
|
146
147
|
for (const s of segments) {
|
|
147
|
-
if (s.type === "loader" && s.loaderId
|
|
148
|
-
originalLoaders.add(s.loaderId);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
if (s.type === "loader" && s.loaderId) {
|
|
149
|
+
if (!s._inherited) originalLoaders.add(s.loaderId);
|
|
150
|
+
const ids = loaderIdsByNamespace.get(s.namespace);
|
|
151
|
+
if (ids) ids.push(s.loaderId);
|
|
152
|
+
else loaderIdsByNamespace.set(s.namespace, [s.loaderId]);
|
|
153
|
+
} else if (
|
|
154
|
+
s.type !== "loader" &&
|
|
155
|
+
s.loading !== undefined &&
|
|
156
|
+
s.loading !== false
|
|
157
|
+
) {
|
|
158
|
+
namespacesWithLoading.add(s.namespace);
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
|
-
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
loadersWithLoading.add(l.loaderId);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
161
|
+
|
|
162
|
+
// An inherited loader is needed when it shares a namespace with a
|
|
163
|
+
// loading-bearing segment (its data sits behind that LoaderBoundary).
|
|
164
|
+
const loadersWithLoading = new Set<string>();
|
|
165
|
+
for (const ns of namespacesWithLoading) {
|
|
166
|
+
for (const id of loaderIdsByNamespace.get(ns) ?? []) {
|
|
167
|
+
loadersWithLoading.add(id);
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
const result: ResolvedSegment[] = [];
|
|
168
|
-
|
|
172
|
+
const removedIds = new Set<string>();
|
|
169
173
|
|
|
170
174
|
for (const s of segments) {
|
|
171
175
|
if (
|
|
@@ -175,17 +179,20 @@ function deduplicateLoaderSegments(
|
|
|
175
179
|
originalLoaders.has(s.loaderId) &&
|
|
176
180
|
!loadersWithLoading.has(s.loaderId)
|
|
177
181
|
) {
|
|
178
|
-
|
|
182
|
+
removedIds.add(s.id);
|
|
179
183
|
continue;
|
|
180
184
|
}
|
|
181
185
|
result.push(s);
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
if (
|
|
185
|
-
debugLog(
|
|
188
|
+
if (removedIds.size > 0) {
|
|
189
|
+
debugLog(
|
|
190
|
+
logPrefix,
|
|
191
|
+
`deduped ${removedIds.size} inherited loader segment(s)`,
|
|
192
|
+
);
|
|
186
193
|
}
|
|
187
194
|
|
|
188
|
-
return result;
|
|
195
|
+
return { segments: result, removedIds };
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
/**
|
|
@@ -244,7 +251,7 @@ export function buildMatchResult<TEnv>(
|
|
|
244
251
|
);
|
|
245
252
|
}
|
|
246
253
|
|
|
247
|
-
const dedupedSegments = deduplicateLoaderSegments(
|
|
254
|
+
const { segments: dedupedSegments, removedIds } = deduplicateLoaderSegments(
|
|
248
255
|
segmentsToRender,
|
|
249
256
|
logPrefix,
|
|
250
257
|
);
|
|
@@ -262,18 +269,32 @@ export function buildMatchResult<TEnv>(
|
|
|
262
269
|
|
|
263
270
|
// Remove deduped loader IDs from matched so the client doesn't treat
|
|
264
271
|
// them as missing segments and trigger a fallback refetch.
|
|
265
|
-
const removedIds = new Set(
|
|
266
|
-
segmentsToRender
|
|
267
|
-
.filter((s) => !dedupedSegments.includes(s))
|
|
268
|
-
.map((s) => s.id),
|
|
269
|
-
);
|
|
270
272
|
const matchedIds =
|
|
271
273
|
removedIds.size > 0 ? allIds.filter((id) => !removedIds.has(id)) : allIds;
|
|
272
274
|
|
|
275
|
+
// resolvedIds: every segment whose handler actually ran this request.
|
|
276
|
+
// For full-match every segment is fresh; for partial-match we filter by
|
|
277
|
+
// the internal `_handlerRan` flag set in revalidation.ts. Drives the
|
|
278
|
+
// client's handle-bucket cleanup — a slot that re-resolved and pushed
|
|
279
|
+
// nothing must have its previous handle data cleared, but `diff` won't
|
|
280
|
+
// carry it because the segment payload skips null-component cached
|
|
281
|
+
// segments to save bytes.
|
|
282
|
+
const resolvedIds = ctx.isFullMatch
|
|
283
|
+
? allSegments.map((s) => s.id)
|
|
284
|
+
: allSegments.filter((s) => s._handlerRan).map((s) => s.id);
|
|
285
|
+
|
|
286
|
+
// Strip internal-only fields from the segments going on the wire.
|
|
287
|
+
const cleanedSegments = dedupedSegments.map((s) => {
|
|
288
|
+
if (s._handlerRan === undefined) return s;
|
|
289
|
+
const { _handlerRan: _drop, ...rest } = s;
|
|
290
|
+
return rest as ResolvedSegment;
|
|
291
|
+
});
|
|
292
|
+
|
|
273
293
|
return {
|
|
274
|
-
segments:
|
|
294
|
+
segments: cleanedSegments,
|
|
275
295
|
matched: matchedIds,
|
|
276
|
-
diff:
|
|
296
|
+
diff: cleanedSegments.map((s) => s.id),
|
|
297
|
+
resolvedIds,
|
|
277
298
|
params: ctx.matched.params,
|
|
278
299
|
routeName: ctx.routeKey,
|
|
279
300
|
slots: Object.keys(state.slots).length > 0 ? state.slots : undefined,
|
package/src/router/metrics.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
15
15
|
import type { Theme } from "../theme/types.js";
|
|
16
16
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
17
|
+
import type { RequestScope } from "../types/request-scope.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Get variable function type
|
|
@@ -52,33 +53,15 @@ export interface CookieOptions {
|
|
|
52
53
|
* Context passed to middleware
|
|
53
54
|
*
|
|
54
55
|
* @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
|
|
55
|
-
* @template TParams - URL params type (typed for route middleware,
|
|
56
|
+
* @template TParams - URL params type (typed for route middleware,
|
|
57
|
+
* `Record<string, string | undefined>` for global middleware — absent
|
|
58
|
+
* optional segments are omitted from the params record at runtime, so
|
|
59
|
+
* the index signature must include `undefined`)
|
|
56
60
|
*/
|
|
57
61
|
export interface MiddlewareContext<
|
|
58
62
|
TEnv = any,
|
|
59
|
-
TParams = Record<string, string>,
|
|
60
|
-
> {
|
|
61
|
-
/** Original request */
|
|
62
|
-
request: Request;
|
|
63
|
-
|
|
64
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
65
|
-
url: URL;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* The original request URL with all parameters intact, including
|
|
69
|
-
* internal `_rsc*` transport params.
|
|
70
|
-
*/
|
|
71
|
-
originalUrl: URL;
|
|
72
|
-
|
|
73
|
-
/** URL pathname */
|
|
74
|
-
pathname: string;
|
|
75
|
-
|
|
76
|
-
/** URL search params */
|
|
77
|
-
searchParams: URLSearchParams;
|
|
78
|
-
|
|
79
|
-
/** Platform bindings (Cloudflare, etc.) */
|
|
80
|
-
env: TEnv;
|
|
81
|
-
|
|
63
|
+
TParams = Record<string, string | undefined>,
|
|
64
|
+
> extends RequestScope<TEnv> {
|
|
82
65
|
/** URL params extracted from route/middleware pattern */
|
|
83
66
|
params: TParams;
|
|
84
67
|
|
|
@@ -157,7 +140,7 @@ export interface MiddlewareContext<
|
|
|
157
140
|
* @template TEnv - Environment type - defaults to any for internal flexibility
|
|
158
141
|
* @template TParams - URL params type (typed for route middleware)
|
|
159
142
|
*
|
|
160
|
-
* When using middleware with global augmentation (
|
|
143
|
+
* When using middleware with global augmentation (Rango.Env), explicitly
|
|
161
144
|
* annotate your middleware functions, or the types will be inferred from context:
|
|
162
145
|
*
|
|
163
146
|
* @example
|
|
@@ -169,7 +152,10 @@ export interface MiddlewareContext<
|
|
|
169
152
|
* router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
|
|
170
153
|
* ```
|
|
171
154
|
*/
|
|
172
|
-
export type MiddlewareFn<
|
|
155
|
+
export type MiddlewareFn<
|
|
156
|
+
TEnv = any,
|
|
157
|
+
TParams = Record<string, string | undefined>,
|
|
158
|
+
> = (
|
|
173
159
|
ctx: MiddlewareContext<TEnv, TParams>,
|
|
174
160
|
next: () => Promise<Response>,
|
|
175
161
|
) => Response | void | Promise<Response | void>;
|
|
@@ -216,5 +202,8 @@ export interface MiddlewareCollectableEntry {
|
|
|
216
202
|
*/
|
|
217
203
|
export interface CollectedMiddleware {
|
|
218
204
|
handler: MiddlewareFn<any, any>;
|
|
205
|
+
// Internal shape only. The user-facing `MiddlewareContext.params` is
|
|
206
|
+
// typed `Record<string, string | undefined>` to reflect that absent
|
|
207
|
+
// optional segments are omitted from the params record at runtime.
|
|
219
208
|
params: Record<string, string>;
|
|
220
209
|
}
|