@rangojs/router 0.0.0-experimental.7 → 0.0.0-experimental.70
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/AGENTS.md +9 -0
- package/README.md +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +4951 -930
- package/package.json +70 -60
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +173 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +204 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +257 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +296 -558
- package/src/browser/navigation-client.ts +179 -69
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +328 -313
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +150 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +230 -74
- package/src/browser/react/NavigationProvider.tsx +87 -11
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -126
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +76 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +221 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +105 -179
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +223 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +351 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +982 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +434 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +70 -8
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +435 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +154 -35
- package/src/router/match-api.ts +555 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +459 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +55 -33
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +743 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1373 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +391 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +356 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +165 -17
- package/src/server/context.ts +315 -58
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +346 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1129
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +918 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +207 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
3
|
+
|
|
4
|
+
// -- Revalidation trace types --
|
|
5
|
+
|
|
6
|
+
export interface RevalidationTraceEntry {
|
|
7
|
+
segmentId: string;
|
|
8
|
+
segmentType: string;
|
|
9
|
+
belongsToRoute: boolean;
|
|
10
|
+
source:
|
|
11
|
+
| "segment-resolution"
|
|
12
|
+
| "cache-hit"
|
|
13
|
+
| "loader"
|
|
14
|
+
| "parallel"
|
|
15
|
+
| "orphan-layout"
|
|
16
|
+
| "route-handler"
|
|
17
|
+
| "layout-handler"
|
|
18
|
+
| "intercept-loader";
|
|
19
|
+
defaultShouldRevalidate: boolean;
|
|
20
|
+
finalShouldRevalidate: boolean;
|
|
21
|
+
reason: string;
|
|
22
|
+
customRevalidators?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RevalidationTraceMeta {
|
|
26
|
+
method: string;
|
|
27
|
+
prevUrl: string;
|
|
28
|
+
nextUrl: string;
|
|
29
|
+
routeKey: string;
|
|
30
|
+
isAction: boolean;
|
|
31
|
+
stale?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RevalidationTrace {
|
|
35
|
+
meta: RevalidationTraceMeta;
|
|
36
|
+
entries: RevalidationTraceEntry[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// -- Log context --
|
|
40
|
+
|
|
41
|
+
interface RouterLogContext {
|
|
42
|
+
requestId: string;
|
|
43
|
+
transactionId: string;
|
|
44
|
+
depth: number;
|
|
45
|
+
revalidationTrace?: RevalidationTrace;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface RouterLogOptions {
|
|
49
|
+
request: Request;
|
|
50
|
+
transaction: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface LogDetails {
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const routerLogContext = new AsyncLocalStorage<RouterLogContext>();
|
|
58
|
+
const requestIds = new WeakMap<Request, string>();
|
|
59
|
+
|
|
60
|
+
let requestCounter = 0;
|
|
61
|
+
let transactionCounter = 0;
|
|
62
|
+
|
|
63
|
+
function nextId(prefix: string, counter: number): string {
|
|
64
|
+
return `${prefix}${counter.toString(36)}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getHeaderRequestId(request: Request): string | null {
|
|
68
|
+
const candidate =
|
|
69
|
+
request.headers.get("x-rsc-router-request-id") ??
|
|
70
|
+
request.headers.get("x-request-id") ??
|
|
71
|
+
request.headers.get("cf-ray");
|
|
72
|
+
if (!candidate) return null;
|
|
73
|
+
const trimmed = candidate.trim();
|
|
74
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getOrCreateRequestId(request: Request): string {
|
|
78
|
+
const existing = requestIds.get(request);
|
|
79
|
+
if (existing) return existing;
|
|
80
|
+
|
|
81
|
+
const fromHeaders = getHeaderRequestId(request);
|
|
82
|
+
if (fromHeaders) {
|
|
83
|
+
requestIds.set(request, fromHeaders);
|
|
84
|
+
return fromHeaders;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
requestCounter += 1;
|
|
88
|
+
const generated = nextId("req-", requestCounter);
|
|
89
|
+
requestIds.set(request, generated);
|
|
90
|
+
return generated;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function runWithRouterLogContext<T>(
|
|
94
|
+
options: RouterLogOptions,
|
|
95
|
+
fn: () => T,
|
|
96
|
+
): T {
|
|
97
|
+
if (!INTERNAL_RANGO_DEBUG) {
|
|
98
|
+
return fn();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const requestId = getOrCreateRequestId(options.request);
|
|
102
|
+
transactionCounter += 1;
|
|
103
|
+
const transactionId = `${options.transaction}-${nextId("tx-", transactionCounter)}`;
|
|
104
|
+
|
|
105
|
+
return routerLogContext.run(
|
|
106
|
+
{
|
|
107
|
+
requestId,
|
|
108
|
+
transactionId,
|
|
109
|
+
depth: 0,
|
|
110
|
+
},
|
|
111
|
+
fn,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function withRouterLogScope<T>(
|
|
116
|
+
label: string,
|
|
117
|
+
fn: () => Promise<T>,
|
|
118
|
+
): Promise<T>;
|
|
119
|
+
export function withRouterLogScope<T>(label: string, fn: () => T): T;
|
|
120
|
+
export function withRouterLogScope<T>(
|
|
121
|
+
label: string,
|
|
122
|
+
fn: () => Promise<T> | T,
|
|
123
|
+
): Promise<T> | T {
|
|
124
|
+
const ctx = routerLogContext.getStore();
|
|
125
|
+
if (!INTERNAL_RANGO_DEBUG || !ctx) {
|
|
126
|
+
return fn();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
debugLog(label, "start");
|
|
130
|
+
|
|
131
|
+
return routerLogContext.run({ ...ctx, depth: ctx.depth + 1 }, () => {
|
|
132
|
+
try {
|
|
133
|
+
const result = fn();
|
|
134
|
+
if (result && typeof (result as Promise<T>).then === "function") {
|
|
135
|
+
return (result as Promise<T>).then(
|
|
136
|
+
(value) => {
|
|
137
|
+
debugLog(label, "end");
|
|
138
|
+
return value;
|
|
139
|
+
},
|
|
140
|
+
(error) => {
|
|
141
|
+
debugLog(label, "error", { error: String(error) });
|
|
142
|
+
throw error;
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
debugLog(label, "end");
|
|
147
|
+
return result;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
debugLog(label, "error", { error: String(error) });
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function isRouterDebugEnabled(): boolean {
|
|
156
|
+
return INTERNAL_RANGO_DEBUG && !!routerLogContext.getStore();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function formatPrefix(scope: string): string {
|
|
160
|
+
const ctx = routerLogContext.getStore();
|
|
161
|
+
if (!ctx) return `[Router][${scope}]`;
|
|
162
|
+
const indent = " ".repeat(ctx.depth);
|
|
163
|
+
return `[Router][req:${ctx.requestId}][tx:${ctx.transactionId}] ${indent}[${scope}]`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function debugLog(
|
|
167
|
+
scope: string,
|
|
168
|
+
message: string,
|
|
169
|
+
details?: LogDetails,
|
|
170
|
+
): void {
|
|
171
|
+
if (!isRouterDebugEnabled()) return;
|
|
172
|
+
|
|
173
|
+
const prefix = formatPrefix(scope);
|
|
174
|
+
if (details) {
|
|
175
|
+
console.log(`${prefix} ${message}`, details);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log(`${prefix} ${message}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function debugWarn(
|
|
183
|
+
scope: string,
|
|
184
|
+
message: string,
|
|
185
|
+
details?: LogDetails,
|
|
186
|
+
): void {
|
|
187
|
+
if (!isRouterDebugEnabled()) return;
|
|
188
|
+
|
|
189
|
+
const prefix = formatPrefix(scope);
|
|
190
|
+
if (details) {
|
|
191
|
+
console.warn(`${prefix} ${message}`, details);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.warn(`${prefix} ${message}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// -- Revalidation trace helpers --
|
|
199
|
+
|
|
200
|
+
export function isTraceActive(): boolean {
|
|
201
|
+
if (!INTERNAL_RANGO_DEBUG) return false;
|
|
202
|
+
const ctx = routerLogContext.getStore();
|
|
203
|
+
return !!ctx?.revalidationTrace;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function startRevalidationTrace(meta: RevalidationTraceMeta): void {
|
|
207
|
+
const ctx = routerLogContext.getStore();
|
|
208
|
+
if (!ctx || !INTERNAL_RANGO_DEBUG) return;
|
|
209
|
+
ctx.revalidationTrace = { meta, entries: [] };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function pushRevalidationTraceEntry(
|
|
213
|
+
entry: RevalidationTraceEntry,
|
|
214
|
+
): void {
|
|
215
|
+
const ctx = routerLogContext.getStore();
|
|
216
|
+
if (!ctx?.revalidationTrace) return;
|
|
217
|
+
ctx.revalidationTrace.entries.push(entry);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function flushRevalidationTrace(): RevalidationTrace | null {
|
|
221
|
+
const ctx = routerLogContext.getStore();
|
|
222
|
+
if (!ctx?.revalidationTrace) return null;
|
|
223
|
+
const trace = ctx.revalidationTrace;
|
|
224
|
+
ctx.revalidationTrace = undefined;
|
|
225
|
+
|
|
226
|
+
if (trace.entries.length === 0) return trace;
|
|
227
|
+
|
|
228
|
+
const revalidated = trace.entries.filter((e) => e.finalShouldRevalidate);
|
|
229
|
+
const skipped = trace.entries.filter((e) => !e.finalShouldRevalidate);
|
|
230
|
+
|
|
231
|
+
debugLog("revalidation-trace", "flush", {
|
|
232
|
+
method: trace.meta.method,
|
|
233
|
+
routeKey: trace.meta.routeKey,
|
|
234
|
+
isAction: trace.meta.isAction,
|
|
235
|
+
stale: trace.meta.stale,
|
|
236
|
+
prevUrl: trace.meta.prevUrl,
|
|
237
|
+
nextUrl: trace.meta.nextUrl,
|
|
238
|
+
total: trace.entries.length,
|
|
239
|
+
revalidated: revalidated.length,
|
|
240
|
+
skipped: skipped.length,
|
|
241
|
+
entries: trace.entries.map((e) => ({
|
|
242
|
+
segmentId: e.segmentId,
|
|
243
|
+
type: e.segmentType,
|
|
244
|
+
source: e.source,
|
|
245
|
+
revalidate: e.finalShouldRevalidate,
|
|
246
|
+
reason: e.reason,
|
|
247
|
+
})),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return trace;
|
|
251
|
+
}
|
package/src/router/manifest.ts
CHANGED
|
@@ -6,27 +6,86 @@
|
|
|
6
6
|
|
|
7
7
|
import { invariant, RouteNotFoundError } from "../errors";
|
|
8
8
|
import { createRouteHelpers } from "../route-definition";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getContext,
|
|
11
|
+
runWithPrefixes,
|
|
12
|
+
getIsolatedLazyParent,
|
|
13
|
+
type EntryData,
|
|
14
|
+
type MetricsStore,
|
|
15
|
+
} from "../server/context";
|
|
10
16
|
import MapRootLayout from "../server/root-layout";
|
|
11
17
|
import type { RouteEntry } from "../types";
|
|
12
18
|
import type { UrlPatterns } from "../urls";
|
|
19
|
+
import { VERSION } from "@rangojs/router:version";
|
|
20
|
+
|
|
21
|
+
// Module-level manifest cache: avoids re-executing DSL handler on every request.
|
|
22
|
+
// Handler execution is deterministic (components, loaders, middleware are module-level
|
|
23
|
+
// stable references), so the resulting EntryData tree can be safely cached and reused
|
|
24
|
+
// across requests within the same isolate.
|
|
25
|
+
//
|
|
26
|
+
// Cache is keyed by (VERSION, mountIndex, routeKey, isSSR). VERSION comes from the
|
|
27
|
+
// @rangojs/router:version virtual module which Vite invalidates on RSC module HMR.
|
|
28
|
+
// 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
|
+
const manifestModuleCache = new Map<string, Map<string, EntryData>>();
|
|
13
31
|
|
|
14
32
|
/**
|
|
15
33
|
* Load manifest from route entry with AsyncLocalStorage context
|
|
16
34
|
* Handles lazy imports, unwrapping, and validation
|
|
17
35
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
36
|
+
* Results are cached at module level after first execution. Subsequent calls
|
|
37
|
+
* for the same (routeKey, isSSR) within the same isolate return cached data
|
|
38
|
+
* without re-executing the DSL handler.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Clear the module-level manifest cache.
|
|
42
|
+
* Called on HMR to ensure stale handler references are discarded.
|
|
21
43
|
*/
|
|
44
|
+
export function clearManifestCache(): void {
|
|
45
|
+
manifestModuleCache.clear();
|
|
46
|
+
}
|
|
47
|
+
|
|
22
48
|
export async function loadManifest(
|
|
23
49
|
entry: RouteEntry<any>,
|
|
24
50
|
routeKey: string,
|
|
25
51
|
path: string,
|
|
26
52
|
metricsStore?: MetricsStore,
|
|
27
|
-
isSSR?: boolean
|
|
53
|
+
isSSR?: boolean,
|
|
28
54
|
): Promise<EntryData> {
|
|
55
|
+
// Helper to push a metric entry
|
|
56
|
+
const pushMetric = metricsStore
|
|
57
|
+
? (label: string, start: number) => {
|
|
58
|
+
metricsStore.metrics.push({
|
|
59
|
+
label,
|
|
60
|
+
duration: performance.now() - start,
|
|
61
|
+
startTime: start - metricsStore.requestStart,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
: undefined;
|
|
65
|
+
|
|
29
66
|
const mountIndex = entry.mountIndex;
|
|
67
|
+
|
|
68
|
+
// Check module-level cache (persists across requests within same isolate)
|
|
69
|
+
// Include routerId so multi-router setups (host routing) don't share cached
|
|
70
|
+
// EntryData across routers with overlapping mountIndex + routeKey combinations.
|
|
71
|
+
const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
|
|
72
|
+
const cached = manifestModuleCache.get(cacheKey);
|
|
73
|
+
if (cached) {
|
|
74
|
+
const cacheStart = performance.now();
|
|
75
|
+
// Set up Store for downstream consumers (segment resolution reads Store.manifest)
|
|
76
|
+
const Store = getContext().getOrCreateStore(routeKey);
|
|
77
|
+
Store.mountIndex = mountIndex;
|
|
78
|
+
Store.isSSR = isSSR;
|
|
79
|
+
if (metricsStore) Store.metrics = metricsStore;
|
|
80
|
+
// Restore cached manifest into Store
|
|
81
|
+
for (const [k, v] of cached) {
|
|
82
|
+
Store.manifest.set(k, v);
|
|
83
|
+
}
|
|
84
|
+
pushMetric?.("manifest:cache-hit", cacheStart);
|
|
85
|
+
return cached.get(routeKey)!;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const storeSetupStart = performance.now();
|
|
30
89
|
const Store = getContext().getOrCreateStore(routeKey);
|
|
31
90
|
|
|
32
91
|
// Set mount index in store for unique shortCode prefixes
|
|
@@ -40,20 +99,57 @@ export async function loadManifest(
|
|
|
40
99
|
Store.metrics = metricsStore;
|
|
41
100
|
}
|
|
42
101
|
|
|
102
|
+
pushMetric?.("manifest:store-setup", storeSetupStart);
|
|
103
|
+
|
|
43
104
|
// Clear manifest before rebuilding to prevent stale entry mutations
|
|
105
|
+
const clearStart = performance.now();
|
|
44
106
|
Store.manifest.clear();
|
|
107
|
+
pushMetric?.("manifest:clear", clearStart);
|
|
45
108
|
|
|
46
109
|
try {
|
|
47
110
|
// Include mountIndex in namespace to ensure unique cache keys per mount
|
|
48
|
-
const namespaceWithMount =
|
|
49
|
-
? `#router.M${mountIndex}`
|
|
50
|
-
: "#router";
|
|
111
|
+
const namespaceWithMount =
|
|
112
|
+
mountIndex !== undefined ? `#router.M${mountIndex}` : "#router";
|
|
51
113
|
|
|
52
114
|
// For lazy entries, use the captured parent from include() context
|
|
53
115
|
// This ensures routes are registered under the correct layout hierarchy
|
|
54
|
-
const lazyContext =
|
|
55
|
-
|
|
116
|
+
const lazyContext =
|
|
117
|
+
entry.lazy && entry.lazyPatterns ? entry.lazyContext : null;
|
|
118
|
+
const parentForContext = lazyContext
|
|
119
|
+
? getIsolatedLazyParent(
|
|
120
|
+
(lazyContext.parent as EntryData | null) ?? Store.parent,
|
|
121
|
+
)
|
|
122
|
+
: Store.parent;
|
|
123
|
+
|
|
124
|
+
// For lazy entries, merge captured counters from include() so the
|
|
125
|
+
// handler's entries get shortCode indices after sibling entries that
|
|
126
|
+
// were created during pattern extraction. This prevents shortCode
|
|
127
|
+
// collisions between lazy and non-lazy entries under the same parent
|
|
128
|
+
// (e.g., ArticlesLayout and BlogLayout both under NavLayout).
|
|
129
|
+
if (lazyContext && (lazyContext as any).counters) {
|
|
130
|
+
const captured = (lazyContext as any).counters as Record<string, number>;
|
|
131
|
+
for (const [key, value] of Object.entries(captured)) {
|
|
132
|
+
Store.counters[key] = Math.max(Store.counters[key] ?? 0, value);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
56
135
|
|
|
136
|
+
// Propagate cache profiles for DSL-time cache("profileName") resolution.
|
|
137
|
+
// Non-lazy entries carry profiles directly; lazy entries carry them
|
|
138
|
+
// in the captured lazyContext from include() time.
|
|
139
|
+
const entryProfiles =
|
|
140
|
+
entry.cacheProfiles ?? (lazyContext as any)?.cacheProfiles;
|
|
141
|
+
if (entryProfiles) {
|
|
142
|
+
Store.cacheProfiles = entryProfiles;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Propagate rootScoped from lazyContext so that routes inside
|
|
146
|
+
// nested { name: "sub" } under { name: "" } keep inherited root scope
|
|
147
|
+
// when the manifest is rebuilt on each request.
|
|
148
|
+
if (lazyContext && (lazyContext as any).rootScoped !== undefined) {
|
|
149
|
+
Store.rootScoped = (lazyContext as any).rootScoped;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const handlerExecStart = performance.now();
|
|
57
153
|
const useItems = await getContext().runWithStore(
|
|
58
154
|
Store,
|
|
59
155
|
Store.namespace || namespaceWithMount,
|
|
@@ -62,25 +158,23 @@ export async function loadManifest(
|
|
|
62
158
|
// Create helpers for lazy-loaded handlers that need them
|
|
63
159
|
const helpers = createRouteHelpers();
|
|
64
160
|
|
|
65
|
-
// For lazy entries, use lazyPatterns.handler() with proper prefixes
|
|
161
|
+
// For lazy entries, use lazyPatterns.handler() with proper prefixes.
|
|
162
|
+
// Do NOT wrap in MapRootLayout here: the captured parent chain from
|
|
163
|
+
// pattern extraction already includes the synthetic MapRootLayout
|
|
164
|
+
// parent, so adding another would create an extra level that does
|
|
165
|
+
// not exist in the non-lazy (root handler) path and would produce
|
|
166
|
+
// mismatched shortCodes.
|
|
66
167
|
if (entry.lazy && entry.lazyPatterns) {
|
|
67
168
|
const lazyPatterns = entry.lazyPatterns as UrlPatterns<any>;
|
|
68
169
|
const includePrefix = (entry as any)._lazyPrefix || "";
|
|
69
170
|
const fullPrefix = (lazyContext?.urlPrefix || "") + includePrefix;
|
|
70
171
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
() => lazyPatterns.handler()
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
return lazyPatterns.handler();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
return [wrappedItems].flat(3);
|
|
172
|
+
if (fullPrefix || lazyContext?.namePrefix) {
|
|
173
|
+
return runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () =>
|
|
174
|
+
lazyPatterns.handler(),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return lazyPatterns.handler();
|
|
84
178
|
}
|
|
85
179
|
|
|
86
180
|
// Wrap handler execution in root layout so routes get correct parent
|
|
@@ -106,35 +200,60 @@ export async function loadManifest(
|
|
|
106
200
|
"default" in load
|
|
107
201
|
) {
|
|
108
202
|
// Promise<{ default: () => Array }> - e.g., dynamic import
|
|
109
|
-
|
|
203
|
+
if (typeof load.default !== "function") {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`[@rangojs/router] Unsupported async handler: { default } must be a function, ` +
|
|
206
|
+
`got ${typeof load.default}. Use () => import('./urls') for lazy loading.`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
110
209
|
return (load.default as (h?: any) => any)(helpers);
|
|
111
210
|
}
|
|
112
211
|
if (typeof load === "function") {
|
|
113
212
|
// Promise<() => Array>
|
|
114
213
|
return (load as (h?: any) => any)(helpers);
|
|
115
214
|
}
|
|
116
|
-
//
|
|
117
|
-
|
|
215
|
+
// Reject unsupported async handler results. Supported shapes are:
|
|
216
|
+
// Promise<{ default: fn }> — dynamic import
|
|
217
|
+
// Promise<fn> — lazy function
|
|
218
|
+
// Direct Promise<Array> is not supported; use a function wrapper.
|
|
219
|
+
throw new Error(
|
|
220
|
+
`[@rangojs/router] Unsupported async handler result (${typeof load}). ` +
|
|
221
|
+
`Lazy route handlers must resolve to a function or { default: fn }, ` +
|
|
222
|
+
`not a direct array. Wrap your handler: () => import('./urls') or ` +
|
|
223
|
+
`() => Promise.resolve((h) => [...])`,
|
|
224
|
+
);
|
|
118
225
|
}
|
|
119
226
|
|
|
120
227
|
// Inline handler - routes were registered with correct parent inside layout
|
|
121
228
|
return [wrappedItems].flat(3);
|
|
122
|
-
}
|
|
229
|
+
},
|
|
123
230
|
);
|
|
231
|
+
pushMetric?.("manifest:handler-exec", handlerExecStart);
|
|
124
232
|
|
|
233
|
+
const validationStart = performance.now();
|
|
125
234
|
invariant(
|
|
126
235
|
useItems && useItems.length > 0,
|
|
127
|
-
"Did not receive any handler from router.map()"
|
|
128
|
-
);
|
|
129
|
-
invariant(
|
|
130
|
-
useItems.some((item: { type: string }) => item.type === "layout"),
|
|
131
|
-
"Top-level handler must be a layout"
|
|
236
|
+
"Did not receive any handler from router.map()",
|
|
132
237
|
);
|
|
238
|
+
// For non-lazy entries the root handler is wrapped in MapRootLayout,
|
|
239
|
+
// so the result always contains a layout item. Lazy entries run the
|
|
240
|
+
// included patterns handler directly (no MapRootLayout wrapper) so
|
|
241
|
+
// we skip this check -- the layout is in the captured parent chain.
|
|
242
|
+
if (!lazyContext) {
|
|
243
|
+
invariant(
|
|
244
|
+
useItems.some((item: { type: string }) => item.type === "layout"),
|
|
245
|
+
"Top-level handler must be a layout",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
133
248
|
|
|
134
249
|
invariant(
|
|
135
250
|
Store.manifest.has(routeKey),
|
|
136
|
-
`Route must be registered for ${routeKey}
|
|
251
|
+
`Route must be registered for ${routeKey}`,
|
|
137
252
|
);
|
|
253
|
+
pushMetric?.("manifest:validation", validationStart);
|
|
254
|
+
|
|
255
|
+
// Cache manifest for future requests in this isolate
|
|
256
|
+
manifestModuleCache.set(cacheKey, new Map(Store.manifest));
|
|
138
257
|
|
|
139
258
|
return Store.manifest.get(routeKey)!;
|
|
140
259
|
} catch (e) {
|
|
@@ -148,7 +267,7 @@ export async function loadManifest(
|
|
|
148
267
|
routeKey,
|
|
149
268
|
},
|
|
150
269
|
},
|
|
151
|
-
}
|
|
270
|
+
},
|
|
152
271
|
);
|
|
153
272
|
}
|
|
154
273
|
}
|