@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/host/router.ts
CHANGED
|
@@ -32,27 +32,16 @@ import {
|
|
|
32
32
|
InvalidHandlerError,
|
|
33
33
|
} from "./errors.js";
|
|
34
34
|
|
|
35
|
-
/**
|
|
36
|
-
* Registry entry for a host router instance.
|
|
37
|
-
* Stores references to the live routes array and fallback, so the discovery
|
|
38
|
-
* plugin can iterate handlers registered after createHostRouter() returns.
|
|
39
|
-
*/
|
|
40
35
|
export interface HostRouterRegistryEntry {
|
|
41
36
|
routes: RouteEntry[];
|
|
42
37
|
fallback: RouteEntry | null;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
/**
|
|
46
|
-
* Global registry for host routers (parallel to RouterRegistry for RSC routers).
|
|
47
|
-
* Populated by createHostRouter() so the build-time discovery plugin can find
|
|
48
|
-
* host routers and resolve their lazy handlers to trigger sub-app createRouter() calls.
|
|
49
|
-
*/
|
|
50
40
|
export const HostRouterRegistry: Map<string, HostRouterRegistryEntry> =
|
|
51
41
|
new Map();
|
|
52
42
|
|
|
53
43
|
let hostRouterAutoId = 0;
|
|
54
44
|
|
|
55
|
-
/** Whether a value is thenable (a Promise or Promise-like). */
|
|
56
45
|
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
57
46
|
return (
|
|
58
47
|
value !== null &&
|
|
@@ -61,12 +50,6 @@ function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
|
61
50
|
);
|
|
62
51
|
}
|
|
63
52
|
|
|
64
|
-
/**
|
|
65
|
-
* Whether a resolved value looks like a module namespace from a lazy import -
|
|
66
|
-
* an object with a `default` export that is a function (a Handler) or a host
|
|
67
|
-
* router (an object with `match`). Used to detect a `.map(() => import(...))`
|
|
68
|
-
* misuse: an inline handler should return a Response, not a module.
|
|
69
|
-
*/
|
|
70
53
|
function looksLikeLazyModule(value: unknown): boolean {
|
|
71
54
|
if (value === null || typeof value !== "object" || !("default" in value)) {
|
|
72
55
|
return false;
|
|
@@ -80,9 +63,6 @@ function looksLikeLazyModule(value: unknown): boolean {
|
|
|
80
63
|
);
|
|
81
64
|
}
|
|
82
65
|
|
|
83
|
-
/**
|
|
84
|
-
* Create a host router
|
|
85
|
-
*/
|
|
86
66
|
export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
87
67
|
const routes: RouteEntry[] = [];
|
|
88
68
|
const globalMiddleware: Middleware[] = [];
|
|
@@ -96,9 +76,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
96
76
|
}
|
|
97
77
|
}
|
|
98
78
|
|
|
99
|
-
/**
|
|
100
|
-
* Create a route builder for chaining
|
|
101
|
-
*/
|
|
102
79
|
function createRouteBuilder(
|
|
103
80
|
patterns: string[],
|
|
104
81
|
isFallback = false,
|
|
@@ -147,9 +124,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
147
124
|
};
|
|
148
125
|
}
|
|
149
126
|
|
|
150
|
-
/**
|
|
151
|
-
* Find matching route for hostname and path
|
|
152
|
-
*/
|
|
153
127
|
function findMatchingRoute(
|
|
154
128
|
hostname: string,
|
|
155
129
|
pathname: string,
|
|
@@ -168,9 +142,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
168
142
|
return null;
|
|
169
143
|
}
|
|
170
144
|
|
|
171
|
-
/**
|
|
172
|
-
* Execute middleware chain
|
|
173
|
-
*/
|
|
174
145
|
async function executeMiddleware(
|
|
175
146
|
middleware: Middleware[],
|
|
176
147
|
request: Request,
|
|
@@ -189,8 +160,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
189
160
|
return finalHandler();
|
|
190
161
|
}
|
|
191
162
|
|
|
192
|
-
// Guard against double next() calls — a second call would
|
|
193
|
-
// re-enter the downstream chain and run handlers/side-effects twice.
|
|
194
163
|
let nextCalled = false;
|
|
195
164
|
const guardedNext = (): Promise<Response> => {
|
|
196
165
|
if (nextCalled) {
|
|
@@ -208,15 +177,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
208
177
|
return next();
|
|
209
178
|
}
|
|
210
179
|
|
|
211
|
-
/**
|
|
212
|
-
* Execute a route entry, branching on its declared kind:
|
|
213
|
-
* - "lazy": await the loader, then delegate to the default export
|
|
214
|
-
* (a nested HostRouter via `.match`, or a request Handler directly).
|
|
215
|
-
* - "handler": call the inline handler with the request. A `.map()` handler
|
|
216
|
-
* that resolves to a module namespace (`{ default }`) is almost certainly
|
|
217
|
-
* a misused lazy import, so it is rejected with a clear message rather
|
|
218
|
-
* than silently returning a module object as the response.
|
|
219
|
-
*/
|
|
220
180
|
async function executeHandler(
|
|
221
181
|
entry: RouteEntry,
|
|
222
182
|
request: Request,
|
|
@@ -236,8 +196,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
236
196
|
|
|
237
197
|
const result = (handler as Handler)(request, input);
|
|
238
198
|
|
|
239
|
-
// Inline handlers may be async; await to obtain the Response and to run the
|
|
240
|
-
// misuse guard below.
|
|
241
199
|
if (isThenable(result)) {
|
|
242
200
|
const awaited = await result;
|
|
243
201
|
if (looksLikeLazyModule(awaited)) {
|
|
@@ -251,10 +209,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
251
209
|
return result;
|
|
252
210
|
}
|
|
253
211
|
|
|
254
|
-
/**
|
|
255
|
-
* Resolve a `.lazy()` mount: invoke the zero-arg loader, then dispatch to the
|
|
256
|
-
* module's default export.
|
|
257
|
-
*/
|
|
258
212
|
async function executeLazyMount(
|
|
259
213
|
loader: LazyHandler,
|
|
260
214
|
request: Request,
|
|
@@ -266,7 +220,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
266
220
|
const defaultExport = (module as { default: Handler | HostRouter })
|
|
267
221
|
.default;
|
|
268
222
|
|
|
269
|
-
// Default export is a nested host router
|
|
270
223
|
if (
|
|
271
224
|
typeof defaultExport === "object" &&
|
|
272
225
|
defaultExport !== null &&
|
|
@@ -275,7 +228,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
275
228
|
return (defaultExport as HostRouter).match(request, input);
|
|
276
229
|
}
|
|
277
230
|
|
|
278
|
-
// Otherwise treat the default export as a request handler
|
|
279
231
|
return (defaultExport as Handler)(request, input);
|
|
280
232
|
}
|
|
281
233
|
|
|
@@ -288,14 +240,10 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
288
240
|
});
|
|
289
241
|
}
|
|
290
242
|
|
|
291
|
-
/**
|
|
292
|
-
* Router instance
|
|
293
|
-
*/
|
|
294
243
|
const router: HostRouter = {
|
|
295
244
|
host(patterns: HostPattern): HostRouteBuilder {
|
|
296
245
|
const patternsArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
297
246
|
|
|
298
|
-
// Validate and normalize patterns
|
|
299
247
|
const normalized = patternsArray.map((p) => {
|
|
300
248
|
validatePattern(p);
|
|
301
249
|
return normalizePattern(p);
|
|
@@ -314,9 +262,8 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
314
262
|
return createRouteBuilder([], true);
|
|
315
263
|
},
|
|
316
264
|
|
|
317
|
-
test(hostname: string): HostMatchResult | null {
|
|
265
|
+
test(hostname: string, pathname = "/"): HostMatchResult | null {
|
|
318
266
|
const parts = hostname.split(".");
|
|
319
|
-
const pathname = "/";
|
|
320
267
|
|
|
321
268
|
for (const route of routes) {
|
|
322
269
|
for (const pattern of route.patterns) {
|
|
@@ -342,14 +289,11 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
342
289
|
let effectiveHostname: string;
|
|
343
290
|
|
|
344
291
|
try {
|
|
345
|
-
// Handle cookie override (may throw HostRouterError)
|
|
346
292
|
effectiveHostname = handleCookieOverride(request, hostOverride, input);
|
|
347
293
|
} catch (error) {
|
|
348
|
-
// If it's a HostRouterError from cookie override
|
|
349
294
|
if (error instanceof HostRouterError) {
|
|
350
295
|
log(`Cookie override error: ${error.message}`);
|
|
351
296
|
|
|
352
|
-
// If fallback exists, use it
|
|
353
297
|
if (fallbackRoute) {
|
|
354
298
|
const fallbackInput = { ...input, error };
|
|
355
299
|
const allMiddleware = [
|
|
@@ -365,7 +309,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
365
309
|
);
|
|
366
310
|
}
|
|
367
311
|
|
|
368
|
-
// Otherwise return error response with cookie deletion
|
|
369
312
|
if (hostOverride) {
|
|
370
313
|
return createCookieErrorResponse(
|
|
371
314
|
hostOverride.cookieName,
|
|
@@ -374,7 +317,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
374
317
|
}
|
|
375
318
|
}
|
|
376
319
|
|
|
377
|
-
// Re-throw non-HostRouterErrors
|
|
378
320
|
throw error;
|
|
379
321
|
}
|
|
380
322
|
|
|
@@ -384,7 +326,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
384
326
|
log(`Cookie override: ${effectiveHostname}`);
|
|
385
327
|
}
|
|
386
328
|
|
|
387
|
-
// Find matching route
|
|
388
329
|
const matchedRoute = findMatchingRoute(effectiveHostname, pathname);
|
|
389
330
|
|
|
390
331
|
if (!matchedRoute) {
|
|
@@ -397,19 +338,14 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
397
338
|
});
|
|
398
339
|
}
|
|
399
340
|
|
|
400
|
-
// Combine global and route-specific middleware
|
|
401
341
|
const allMiddleware = [...globalMiddleware, ...matchedRoute.middleware];
|
|
402
342
|
|
|
403
|
-
// Execute middleware chain and handler
|
|
404
343
|
return executeMiddleware(allMiddleware, request, input, () =>
|
|
405
344
|
executeHandler(matchedRoute, request, input),
|
|
406
345
|
);
|
|
407
346
|
},
|
|
408
347
|
};
|
|
409
348
|
|
|
410
|
-
// Register in the global HostRouterRegistry for build-time discovery.
|
|
411
|
-
// The routes array and fallbackRoute ref are live - they reflect routes
|
|
412
|
-
// added via .host().map()/.lazy() after this point.
|
|
413
349
|
const registryId = `host-router-${hostRouterAutoId++}`;
|
|
414
350
|
HostRouterRegistry.set(registryId, {
|
|
415
351
|
get routes() {
|
package/src/host/testing.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Helper functions for testing host routing.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { matchPattern } from "./pattern-matcher.js";
|
|
7
|
+
import { matchPattern, parseRequest } from "./pattern-matcher.js";
|
|
8
8
|
|
|
9
9
|
export interface CreateTestRequestOptions {
|
|
10
10
|
host: string;
|
|
@@ -14,18 +14,6 @@ export interface CreateTestRequestOptions {
|
|
|
14
14
|
headers?: Record<string, string>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* Create a test request with specific host and cookies
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const request = createTestRequest({
|
|
23
|
-
* host: 'admin.example.com',
|
|
24
|
-
* path: '/dashboard',
|
|
25
|
-
* cookies: { 'x-requested-host': 'api.example.com' }
|
|
26
|
-
* });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
17
|
export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
30
18
|
const {
|
|
31
19
|
host,
|
|
@@ -38,7 +26,6 @@ export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
|
38
26
|
const url = `http://${host}${path}`;
|
|
39
27
|
const requestHeaders = new Headers(headers);
|
|
40
28
|
|
|
41
|
-
// Add cookies if provided
|
|
42
29
|
if (Object.keys(cookies).length > 0) {
|
|
43
30
|
const cookieString = Object.entries(cookies)
|
|
44
31
|
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
@@ -52,28 +39,54 @@ export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
|
52
39
|
});
|
|
53
40
|
}
|
|
54
41
|
|
|
42
|
+
function matchPatterns(
|
|
43
|
+
pattern: string | string[],
|
|
44
|
+
hostname: string,
|
|
45
|
+
pathname: string,
|
|
46
|
+
parts: string[],
|
|
47
|
+
): boolean {
|
|
48
|
+
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
49
|
+
return patterns.some((p) => matchPattern(p, hostname, pathname, parts));
|
|
50
|
+
}
|
|
51
|
+
|
|
55
52
|
/**
|
|
56
|
-
* Test if a pattern matches a hostname
|
|
53
|
+
* Test if a pattern matches a hostname (and, for path-based patterns, a pathname).
|
|
54
|
+
*
|
|
55
|
+
* `pathname` defaults to `"/"`, so a host-only pattern works with two args. Pass
|
|
56
|
+
* the third arg to test a path-based pattern (`**.workers.dev/admin`,
|
|
57
|
+
* `localhost/shop`) — without it those patterns can never match.
|
|
57
58
|
*
|
|
58
59
|
* @example
|
|
59
60
|
* ```ts
|
|
60
|
-
* expect(testPattern(
|
|
61
|
-
* expect(testPattern([
|
|
61
|
+
* expect(testPattern("admin.*", "admin.example.com")).toBe(true);
|
|
62
|
+
* expect(testPattern(["*", "www.*"], "example.com")).toBe(true);
|
|
63
|
+
* expect(testPattern("**.workers.dev/admin", "foo.workers.dev", "/admin")).toBe(true);
|
|
62
64
|
* ```
|
|
63
65
|
*/
|
|
64
66
|
export function testPattern(
|
|
65
67
|
pattern: string | string[],
|
|
66
68
|
hostname: string,
|
|
69
|
+
pathname: string = "/",
|
|
67
70
|
): boolean {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const pathname = "/";
|
|
71
|
-
|
|
72
|
-
for (const p of patterns) {
|
|
73
|
-
if (matchPattern(p, hostname, pathname, parts)) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
71
|
+
return matchPatterns(pattern, hostname, pathname, hostname.split("."));
|
|
72
|
+
}
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Test if a pattern matches a `Request` — the hostname AND pathname are taken
|
|
76
|
+
* from the request URL (via the same `parseRequest` the host router uses), so a
|
|
77
|
+
* path-based pattern is tested against a real request without splitting the URL
|
|
78
|
+
* by hand.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const req = new Request("https://foo.workers.dev/admin");
|
|
83
|
+
* expect(matchesHost("**.workers.dev/admin", req)).toBe(true);
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export function matchesHost(
|
|
87
|
+
pattern: string | string[],
|
|
88
|
+
request: Request,
|
|
89
|
+
): boolean {
|
|
90
|
+
const { hostname, pathname, parts } = parseRequest(request);
|
|
91
|
+
return matchPatterns(pattern, hostname, pathname, parts);
|
|
79
92
|
}
|
package/src/host/types.ts
CHANGED
|
@@ -110,9 +110,13 @@ export interface HostRouter {
|
|
|
110
110
|
fallback(): HostRouteBuilder;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Test which handler would match a hostname
|
|
113
|
+
* Test which handler would match a hostname (and optional pathname).
|
|
114
|
+
*
|
|
115
|
+
* `pathname` defaults to `"/"`. Pass it to probe path-prefixed patterns
|
|
116
|
+
* such as `host(["example.com/admin"])`, which only match when the request
|
|
117
|
+
* path is under the prefix.
|
|
114
118
|
*/
|
|
115
|
-
test(hostname: string): HostMatchResult | null;
|
|
119
|
+
test(hostname: string, pathname?: string): HostMatchResult | null;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
/**
|
package/src/href-client.ts
CHANGED
|
@@ -298,13 +298,9 @@ declare global {
|
|
|
298
298
|
*/
|
|
299
299
|
export function href<T extends ValidPaths>(path: T, mount?: string): string {
|
|
300
300
|
if (mount && mount !== "/") {
|
|
301
|
-
// Strip trailing slash from mount to avoid double-slash when joining
|
|
302
301
|
const normalizedMount = mount.endsWith("/") ? mount.slice(0, -1) : mount;
|
|
303
302
|
return normalizedMount + path;
|
|
304
303
|
}
|
|
305
|
-
// ValidPaths is built from template literals so T does extend string at
|
|
306
|
-
// runtime, but the inference can fail past a certain route-union complexity
|
|
307
|
-
// and TypeScript reports T as not assignable to string.
|
|
308
304
|
return path as string;
|
|
309
305
|
}
|
|
310
306
|
|
package/src/index.rsc.ts
CHANGED
|
@@ -75,6 +75,13 @@ export type {
|
|
|
75
75
|
ResolveStreamingContext,
|
|
76
76
|
} from "./router.js";
|
|
77
77
|
|
|
78
|
+
// Origin-check callback types (referenced by the RangoOptions.originCheck JSDoc)
|
|
79
|
+
export type {
|
|
80
|
+
OriginCheckConfig,
|
|
81
|
+
OriginCheckContext,
|
|
82
|
+
OriginCheckPhase,
|
|
83
|
+
} from "./rsc/origin-guard.js";
|
|
84
|
+
|
|
78
85
|
// Server-side createLoader and redirect
|
|
79
86
|
export {
|
|
80
87
|
createLoader,
|
|
@@ -124,10 +131,15 @@ export {
|
|
|
124
131
|
type BuildContext,
|
|
125
132
|
type StaticBuildContext,
|
|
126
133
|
type GetParamsContext,
|
|
134
|
+
type PrerenderPassthroughResult,
|
|
127
135
|
} from "./prerender.js";
|
|
128
136
|
|
|
129
137
|
// Static handler API
|
|
130
|
-
export {
|
|
138
|
+
export {
|
|
139
|
+
Static,
|
|
140
|
+
type StaticHandlerDefinition,
|
|
141
|
+
type StaticHandlerOptions,
|
|
142
|
+
} from "./static-handler.js";
|
|
131
143
|
|
|
132
144
|
// Django-style URL patterns (RSC/server context)
|
|
133
145
|
export {
|
|
@@ -184,6 +196,8 @@ export const getRequestContext: <
|
|
|
184
196
|
export {
|
|
185
197
|
cookies,
|
|
186
198
|
headers,
|
|
199
|
+
invalidateClientCache,
|
|
200
|
+
keepClientCache,
|
|
187
201
|
type CookieStore,
|
|
188
202
|
type Cookie,
|
|
189
203
|
type ReadonlyHeaders,
|
|
@@ -200,7 +214,13 @@ export { updateTag, revalidateTag } from "./cache/tag-invalidation.js";
|
|
|
200
214
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
201
215
|
|
|
202
216
|
// Middleware context types
|
|
203
|
-
export type {
|
|
217
|
+
export type {
|
|
218
|
+
MiddlewareContext,
|
|
219
|
+
CookieOptions,
|
|
220
|
+
// The function type of a middleware. Public so the documented "extract the
|
|
221
|
+
// middleware and unit-test it with runMiddleware" pattern has a nameable type.
|
|
222
|
+
MiddlewareFn,
|
|
223
|
+
} from "./router/middleware.js";
|
|
204
224
|
|
|
205
225
|
// Reverse type utilities for type-safe URL generation (Django-style URL reversal)
|
|
206
226
|
export type {
|
|
@@ -235,7 +255,26 @@ export {
|
|
|
235
255
|
export { createConsoleSink } from "./router/telemetry.js";
|
|
236
256
|
export { createOTelSink } from "./router/telemetry-otel.js";
|
|
237
257
|
export type { OTelTracer, OTelSpan } from "./router/telemetry-otel.js";
|
|
238
|
-
|
|
258
|
+
// The full TelemetryEvent union PLUS its member types, so a consumer writing a
|
|
259
|
+
// TelemetrySink can annotate a per-`type` handler (or construct an event literal
|
|
260
|
+
// in a test) instead of only narrowing the opaque union.
|
|
261
|
+
export type {
|
|
262
|
+
TelemetrySink,
|
|
263
|
+
TelemetryEvent,
|
|
264
|
+
RequestStartEvent,
|
|
265
|
+
RequestEndEvent,
|
|
266
|
+
RequestErrorEvent,
|
|
267
|
+
LoaderStartEvent,
|
|
268
|
+
LoaderEndEvent,
|
|
269
|
+
LoaderErrorEvent,
|
|
270
|
+
HandlerErrorEvent,
|
|
271
|
+
CacheSegmentStatus,
|
|
272
|
+
CacheSegmentSignal,
|
|
273
|
+
CacheDecisionEvent,
|
|
274
|
+
RevalidationDecisionEvent,
|
|
275
|
+
RequestTimeoutEvent,
|
|
276
|
+
OriginCheckRejectedEvent,
|
|
277
|
+
} from "./router/telemetry.js";
|
|
239
278
|
|
|
240
279
|
// Timeout types and error class
|
|
241
280
|
export { RouterTimeoutError } from "./router/timeout.js";
|
package/src/index.ts
CHANGED
|
@@ -217,6 +217,17 @@ export function headers(): never {
|
|
|
217
217
|
throw serverOnlyStubError("headers");
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Client implementation of `invalidateClientCache()`. Unlike the server-only
|
|
222
|
+
* stubs above this is a REAL function under the `default` condition (it marks
|
|
223
|
+
* the client's caches stale); the `react-server` condition (index.rsc.ts)
|
|
224
|
+
* selects the server implementation that writes a rotated `Set-Cookie`.
|
|
225
|
+
*/
|
|
226
|
+
export {
|
|
227
|
+
invalidateClientCache,
|
|
228
|
+
keepClientCache,
|
|
229
|
+
} from "./browser/invalidate-client-cache.js";
|
|
230
|
+
|
|
220
231
|
/**
|
|
221
232
|
* Error-throwing stub for server-only `createReverse` function.
|
|
222
233
|
*/
|
|
@@ -327,7 +338,26 @@ export {
|
|
|
327
338
|
// who need the values in non-RSC contexts can import from
|
|
328
339
|
// `@rangojs/router/server`.
|
|
329
340
|
export type { OTelTracer, OTelSpan } from "./router/telemetry-otel.js";
|
|
330
|
-
|
|
341
|
+
// The full TelemetryEvent union PLUS its member types, so a consumer writing a
|
|
342
|
+
// TelemetrySink can annotate a per-`type` handler (or construct an event literal
|
|
343
|
+
// in a test) instead of only narrowing the opaque union.
|
|
344
|
+
export type {
|
|
345
|
+
TelemetrySink,
|
|
346
|
+
TelemetryEvent,
|
|
347
|
+
RequestStartEvent,
|
|
348
|
+
RequestEndEvent,
|
|
349
|
+
RequestErrorEvent,
|
|
350
|
+
LoaderStartEvent,
|
|
351
|
+
LoaderEndEvent,
|
|
352
|
+
LoaderErrorEvent,
|
|
353
|
+
HandlerErrorEvent,
|
|
354
|
+
CacheSegmentStatus,
|
|
355
|
+
CacheSegmentSignal,
|
|
356
|
+
CacheDecisionEvent,
|
|
357
|
+
RevalidationDecisionEvent,
|
|
358
|
+
RequestTimeoutEvent,
|
|
359
|
+
OriginCheckRejectedEvent,
|
|
360
|
+
} from "./router/telemetry.js";
|
|
331
361
|
|
|
332
362
|
// Timeout types and error class
|
|
333
363
|
export { RouterTimeoutError } from "./router/timeout.js";
|
package/src/internal-debug.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// in all runtimes including Cloudflare Workers where process.env is unavailable.
|
|
4
|
-
// Falls back to process.env for non-Vite contexts (tests, direct Node usage).
|
|
1
|
+
// Vite define for compile-time injection; falls back to process.env (tests, Node).
|
|
2
|
+
// Works in all runtimes including Cloudflare Workers where process.env is unavailable.
|
|
5
3
|
export const INTERNAL_RANGO_DEBUG: boolean =
|
|
6
4
|
typeof __RANGO_DEBUG__ !== "undefined"
|
|
7
5
|
? __RANGO_DEBUG__
|
package/src/loader.rsc.ts
CHANGED
|
@@ -22,9 +22,13 @@ import {
|
|
|
22
22
|
getFetchableLoader,
|
|
23
23
|
} from "./server/fetchable-loader-store.js";
|
|
24
24
|
import { missingInjectedIdError } from "./missing-id-error.js";
|
|
25
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
25
26
|
|
|
26
27
|
export { getFetchableLoader };
|
|
27
28
|
|
|
29
|
+
// Runtime-fallback counter for bare unit tests (no Vite plugin); process-stable.
|
|
30
|
+
let runtimeLoaderIdCounter = 0;
|
|
31
|
+
|
|
28
32
|
// Overload 1: With function only (not fetchable)
|
|
29
33
|
export function createLoader<T>(
|
|
30
34
|
fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
@@ -49,12 +53,21 @@ export function createLoader<T>(
|
|
|
49
53
|
// Hidden parameter injected by Vite exposeInternalIds plugin
|
|
50
54
|
__injectedId?: string,
|
|
51
55
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>> {
|
|
52
|
-
|
|
53
|
-
// For fetchable loaders, __injectedId is also passed as a parameter
|
|
54
|
-
const loaderId = __injectedId || "";
|
|
56
|
+
let loaderId = __injectedId || "";
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
// Under test runner, fall back to synthetic id (recovers fn from registry by $$id).
|
|
59
|
+
// Otherwise (dev or prod), missing id means unsupported shape — fail loud.
|
|
60
|
+
if (!loaderId) {
|
|
61
|
+
if (isUnderTestRunner()) {
|
|
62
|
+
loaderId = `__rango_runtime_loader_${runtimeLoaderIdCounter++}`;
|
|
63
|
+
} else if (process.env.NODE_ENV !== "production") {
|
|
64
|
+
throw missingInjectedIdError("Loader", "createLoader");
|
|
65
|
+
} else {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"[rango] Loader is missing $$id — the build plugin did not inject one. " +
|
|
68
|
+
"Export it as `export const X = createLoader(...)`.",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
// If not fetchable, store fn in registry (for SSR ctx.use() resolution)
|
|
@@ -69,12 +82,9 @@ export function createLoader<T>(
|
|
|
69
82
|
};
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
// Fetchable loader - store fn in registry and return a serializable object
|
|
85
|
+
// Fetchable loader - store fn in registry and return a serializable object.
|
|
73
86
|
const middleware: MiddlewareFn[] =
|
|
74
87
|
fetchable === true ? [] : fetchable?.middleware || [];
|
|
75
|
-
|
|
76
|
-
// Register the function in the internal registry by $$id (server-side only)
|
|
77
|
-
// The loader fetch handler looks it up by $$id when load() is called from the client.
|
|
78
88
|
if (fn && loaderId) {
|
|
79
89
|
registerFetchableLoader(loaderId, fn, middleware, true);
|
|
80
90
|
}
|
package/src/loader.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
LoaderFn,
|
|
20
20
|
} from "./types.js";
|
|
21
21
|
import { missingInjectedIdError } from "./missing-id-error.js";
|
|
22
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
22
23
|
|
|
23
24
|
// Overload 1: With function only (not fetchable)
|
|
24
25
|
export function createLoader<T>(
|
|
@@ -37,8 +38,7 @@ export function createLoader<T>(
|
|
|
37
38
|
options: FetchableLoaderOptions,
|
|
38
39
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
|
|
39
40
|
|
|
40
|
-
// Implementation - client stub
|
|
41
|
-
// The $$id parameter is injected by Vite plugin, not user-provided
|
|
41
|
+
// Implementation - client stub ($$id injected by Vite plugin, not user-provided)
|
|
42
42
|
export function createLoader<T>(
|
|
43
43
|
_fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
44
44
|
_fetchable?: true | FetchableLoaderOptions,
|
|
@@ -46,8 +46,16 @@ export function createLoader<T>(
|
|
|
46
46
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>> {
|
|
47
47
|
const loaderId = __injectedId || "";
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// Under test runner, no id needed (loaderId stays ""; loader.rsc.ts provides fallback).
|
|
50
|
+
// Otherwise, missing id means unsupported shape — fail loud to avoid wrong key.
|
|
51
|
+
if (!loaderId && !isUnderTestRunner()) {
|
|
52
|
+
if (process.env.NODE_ENV !== "production") {
|
|
53
|
+
throw missingInjectedIdError("Loader", "createLoader");
|
|
54
|
+
}
|
|
55
|
+
throw new Error(
|
|
56
|
+
"[rango] Loader is missing $$id — the build plugin did not inject one. " +
|
|
57
|
+
"Export it as `export const X = createLoader(...)`.",
|
|
58
|
+
);
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
return {
|
|
@@ -9,12 +9,7 @@ interface NetworkErrorThrowerProps {
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Client component that throws a NetworkError during render.
|
|
12
|
-
*
|
|
13
|
-
* during navigation or server actions.
|
|
14
|
-
*
|
|
15
|
-
* This must be a separate component because:
|
|
16
|
-
* 1. Errors must be thrown during React's render phase to be caught by error boundaries
|
|
17
|
-
* 2. The error occurs in async code (fetch), so we need to propagate it to React's render
|
|
12
|
+
* Errors thrown during render are caught by error boundaries; async errors are not.
|
|
18
13
|
*/
|
|
19
14
|
export function NetworkErrorThrower({
|
|
20
15
|
error,
|
package/src/outlet-provider.tsx
CHANGED
|
@@ -5,11 +5,7 @@ import { OutletContext, type OutletContextValue } from "./outlet-context.js";
|
|
|
5
5
|
import type { ResolvedSegment } from "./types.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Stores a reference to parent context so useLoader can walk up the chain
|
|
11
|
-
* to find loader data from parent layouts. If this segment defines a loading
|
|
12
|
-
* component, Outlet will wrap content with Suspense using that as fallback.
|
|
8
|
+
* Outlet content provider — stores parent context for useLoader chain walking.
|
|
13
9
|
*/
|
|
14
10
|
export function OutletProvider({
|
|
15
11
|
content,
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Deterministic param hashing for prerender storage keys.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* runtime (worker) to look up pre-rendered data. Both environments
|
|
6
|
-
* must produce identical hashes for the same params.
|
|
7
|
-
*
|
|
8
|
-
* Uses a simple DJB2-based hash that works in all JS environments
|
|
9
|
-
* (Node.js, Cloudflare Workers, browsers) without crypto imports.
|
|
3
|
+
* Used at build time and runtime; both must produce identical hashes.
|
|
4
|
+
* DJB2-based; works in all JS environments without crypto imports.
|
|
10
5
|
*/
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
* Compute a deterministic hash string from route params.
|
|
14
|
-
* For static routes (no params), returns "_".
|
|
15
|
-
*/
|
|
7
|
+
// For static routes (no params), returns "_".
|
|
16
8
|
export function hashParams(params: Record<string, string>): string {
|
|
17
9
|
const entries = Object.entries(params);
|
|
18
10
|
if (entries.length === 0) return "_";
|
|
@@ -27,6 +19,13 @@ export function hashParams(params: Record<string, string>): string {
|
|
|
27
19
|
/**
|
|
28
20
|
* DJB2 hash returning an 8-char hex string.
|
|
29
21
|
* Deterministic across all JS runtimes.
|
|
22
|
+
*
|
|
23
|
+
* 32-bit output: per-route collision probability hits ~50% near ~77k distinct
|
|
24
|
+
* param sets (birthday bound). The production store keys solely on
|
|
25
|
+
* routeName/paramHash and does not verify the canonical param string, so a
|
|
26
|
+
* collision serves the surviving entry for both param sets. Benign for typical
|
|
27
|
+
* catalogs; revisit (wider hash or stored-param verification) before
|
|
28
|
+
* pre-rendering hundreds of thousands of pages per route.
|
|
30
29
|
*/
|
|
31
30
|
function djb2Hex(str: string): string {
|
|
32
31
|
let hash = 5381;
|