@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.81
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 +5091 -941
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +61 -52
- 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/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +340 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -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 +317 -560
- package/src/browser/navigation-client.ts +206 -68
- 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 +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -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 +253 -74
- package/src/browser/react/NavigationProvider.tsx +91 -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 +44 -65
- package/src/browser/react/use-params.ts +75 -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 +243 -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 +291 -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 +135 -301
- 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 +251 -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 +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -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 +77 -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 +438 -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 +163 -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 +460 -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 +135 -35
- 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 +748 -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 +1379 -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 +393 -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 +358 -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-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- 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 +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -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 -1133
- 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/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/{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 +977 -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 +221 -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
package/src/server/context.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
PartialCacheOptions,
|
|
5
|
+
ErrorBoundaryHandler,
|
|
6
|
+
Handler,
|
|
7
|
+
LoaderDefinition,
|
|
8
|
+
MiddlewareFn,
|
|
9
|
+
NotFoundBoundaryHandler,
|
|
10
|
+
ShouldRevalidateFn,
|
|
11
|
+
TransitionConfig,
|
|
12
|
+
} from "../types";
|
|
4
13
|
import { invariant } from "../errors";
|
|
14
|
+
import type { DefaultRouteName } from "../types/global-namespace.js";
|
|
5
15
|
|
|
6
16
|
// ============================================================================
|
|
7
17
|
// Performance Metrics Types
|
|
@@ -13,9 +23,10 @@ import { invariant } from "../errors";
|
|
|
13
23
|
* @internal This type is an implementation detail and may change without notice.
|
|
14
24
|
*/
|
|
15
25
|
export interface PerformanceMetric {
|
|
16
|
-
label: string;
|
|
17
|
-
duration: number;
|
|
18
|
-
startTime: number;
|
|
26
|
+
label: string; // e.g., "route-matching", "loader:UserLoader"
|
|
27
|
+
duration: number; // milliseconds
|
|
28
|
+
startTime: number; // relative to request start
|
|
29
|
+
depth?: number; // nesting level for hierarchical display (0 = top-level)
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
/**
|
|
@@ -105,12 +116,14 @@ export type InterceptSegmentsState = {
|
|
|
105
116
|
* @internal This type is an implementation detail and may change without notice.
|
|
106
117
|
*/
|
|
107
118
|
export type InterceptSelectorContext<TEnv = any> = {
|
|
108
|
-
from: URL;
|
|
109
|
-
to: URL;
|
|
110
|
-
params: Record<string, string>;
|
|
111
|
-
request: Request;
|
|
112
|
-
env: TEnv;
|
|
113
|
-
segments: InterceptSegmentsState;
|
|
119
|
+
from: URL; // Source URL (where user is coming from)
|
|
120
|
+
to: URL; // Destination URL (where user is navigating to)
|
|
121
|
+
params: Record<string, string>; // Matched route params
|
|
122
|
+
request: Request; // The HTTP request object
|
|
123
|
+
env: TEnv; // Platform bindings (Cloudflare env, etc.)
|
|
124
|
+
segments: InterceptSegmentsState; // Client's current segments (where navigating FROM)
|
|
125
|
+
fromRouteName?: DefaultRouteName; // Named route being navigated away from (undefined for unnamed routes)
|
|
126
|
+
toRouteName?: DefaultRouteName; // Named route being navigated to (undefined for unnamed routes)
|
|
114
127
|
};
|
|
115
128
|
|
|
116
129
|
/**
|
|
@@ -119,7 +132,9 @@ export type InterceptSelectorContext<TEnv = any> = {
|
|
|
119
132
|
*
|
|
120
133
|
* @internal This type is an implementation detail and may change without notice.
|
|
121
134
|
*/
|
|
122
|
-
export type InterceptWhenFn<TEnv = any> = (
|
|
135
|
+
export type InterceptWhenFn<TEnv = any> = (
|
|
136
|
+
ctx: InterceptSelectorContext<TEnv>,
|
|
137
|
+
) => boolean;
|
|
123
138
|
|
|
124
139
|
/**
|
|
125
140
|
* Intercept entry stored in EntryData
|
|
@@ -128,55 +143,88 @@ export type InterceptWhenFn<TEnv = any> = (ctx: InterceptSelectorContext<TEnv>)
|
|
|
128
143
|
* @internal This type is an implementation detail and may change without notice.
|
|
129
144
|
*/
|
|
130
145
|
export type InterceptEntry = {
|
|
131
|
-
slotName: `@${string}`;
|
|
132
|
-
routeName: string;
|
|
133
|
-
handler: ReactNode | Handler<any, any>;
|
|
146
|
+
slotName: `@${string}`; // e.g., "@modal"
|
|
147
|
+
routeName: string; // e.g., "card"
|
|
148
|
+
handler: ReactNode | Handler<any, any, any>;
|
|
134
149
|
middleware: MiddlewareFn<any, any>[];
|
|
135
150
|
revalidate: ShouldRevalidateFn<any, any>[];
|
|
136
151
|
errorBoundary: (ReactNode | ErrorBoundaryHandler)[];
|
|
137
152
|
notFoundBoundary: (ReactNode | NotFoundBoundaryHandler)[];
|
|
138
153
|
loader: LoaderEntry[];
|
|
139
154
|
loading?: ReactNode | false;
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
transition?: TransitionConfig;
|
|
156
|
+
layout?: ReactNode | Handler<any, any, any>; // Wrapper layout with <Outlet /> for content
|
|
157
|
+
when: InterceptWhenFn[]; // Selector conditions - all must return true to intercept
|
|
142
158
|
};
|
|
143
159
|
|
|
160
|
+
export interface ParallelEntryData
|
|
161
|
+
extends EntryPropCommon, EntryPropDatas, EntryPropSegments {
|
|
162
|
+
type: "parallel";
|
|
163
|
+
handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
|
|
164
|
+
loading?: ReactNode | false;
|
|
165
|
+
transition?: TransitionConfig;
|
|
166
|
+
/** Set when any parallel slot is a Static definition */
|
|
167
|
+
isStaticPrerender?: true;
|
|
168
|
+
/** Per-slot static handler $$ids for build-time store lookup */
|
|
169
|
+
staticHandlerIds?: Record<string, string>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type ParallelEntries = Partial<Record<`@${string}`, ParallelEntryData>>;
|
|
173
|
+
|
|
144
174
|
export type EntryPropSegments = {
|
|
145
175
|
loader: LoaderEntry[];
|
|
146
176
|
layout: EntryData[];
|
|
147
|
-
parallel:
|
|
177
|
+
parallel: ParallelEntries; // slot -> parallel entry (same entry may back multiple slots)
|
|
148
178
|
intercept: InterceptEntry[]; // intercept definitions for soft navigation
|
|
149
179
|
};
|
|
150
180
|
|
|
151
181
|
export type EntryData =
|
|
152
182
|
| ({
|
|
153
183
|
type: "route";
|
|
154
|
-
handler: Handler<any, any>;
|
|
184
|
+
handler: Handler<any, any, any>;
|
|
155
185
|
loading?: ReactNode | false;
|
|
186
|
+
transition?: TransitionConfig;
|
|
156
187
|
/** URL pattern for this route (used by path() in urls()) */
|
|
157
188
|
pattern?: string;
|
|
189
|
+
/** Set when handler is a Prerender definition */
|
|
190
|
+
isPrerender?: true;
|
|
191
|
+
/** Original PrerenderHandlerDefinition (for build-time getParams access) */
|
|
192
|
+
prerenderDef?: {
|
|
193
|
+
getParams?: (ctx: any) => Promise<any[]> | any[];
|
|
194
|
+
options?: { concurrency?: number };
|
|
195
|
+
};
|
|
196
|
+
/** Set when route is wrapped with Passthrough() — has a separate live handler */
|
|
197
|
+
isPassthrough?: true;
|
|
198
|
+
/** Live handler for runtime fallback (only set on Passthrough routes) */
|
|
199
|
+
liveHandler?: Handler<any, any, any>;
|
|
200
|
+
/** Set when handler is a Static definition (build-time only) */
|
|
201
|
+
isStaticPrerender?: true;
|
|
202
|
+
/** Static handler $$id for build-time store lookup */
|
|
203
|
+
staticHandlerId?: string;
|
|
204
|
+
/** Response type for non-RSC routes (json, text, image, any) */
|
|
205
|
+
responseType?: string;
|
|
158
206
|
} & EntryPropCommon &
|
|
159
207
|
EntryPropDatas &
|
|
160
208
|
EntryPropSegments)
|
|
161
209
|
| ({
|
|
162
210
|
type: "layout";
|
|
163
|
-
handler: ReactNode | Handler<any, any>;
|
|
164
|
-
loading?: ReactNode | false;
|
|
165
|
-
} & EntryPropCommon &
|
|
166
|
-
EntryPropDatas &
|
|
167
|
-
EntryPropSegments)
|
|
168
|
-
| ({
|
|
169
|
-
type: "parallel";
|
|
170
|
-
handler: Record<`@${string}`, Handler<any, any> | ReactNode>;
|
|
211
|
+
handler: ReactNode | Handler<any, any, any>;
|
|
171
212
|
loading?: ReactNode | false;
|
|
213
|
+
transition?: TransitionConfig;
|
|
214
|
+
/** Set when handler is a Static definition (build-time only) */
|
|
215
|
+
isStaticPrerender?: true;
|
|
216
|
+
/** Static handler $$id for build-time store lookup */
|
|
217
|
+
staticHandlerId?: string;
|
|
172
218
|
} & EntryPropCommon &
|
|
173
219
|
EntryPropDatas &
|
|
174
220
|
EntryPropSegments)
|
|
221
|
+
| ParallelEntryData
|
|
175
222
|
| ({
|
|
176
223
|
type: "cache";
|
|
177
224
|
/** Cache entries create cache boundaries and render like layouts (with Outlet) */
|
|
178
|
-
handler: ReactNode | Handler<any, any>;
|
|
225
|
+
handler: ReactNode | Handler<any, any, any>;
|
|
179
226
|
loading?: ReactNode | false;
|
|
227
|
+
transition?: TransitionConfig;
|
|
180
228
|
} & EntryPropCommon &
|
|
181
229
|
EntryPropDatas &
|
|
182
230
|
EntryPropSegments);
|
|
@@ -211,17 +259,53 @@ interface HelperContext {
|
|
|
211
259
|
patternsByPrefix?: Map<string, Map<string, string>>;
|
|
212
260
|
/** Trailing slash config per route name */
|
|
213
261
|
trailingSlash?: Map<string, "never" | "always" | "ignore">;
|
|
262
|
+
/** Search param schemas per route name */
|
|
263
|
+
searchSchemas?: Map<string, Record<string, string>>;
|
|
214
264
|
/** URL prefix from include() - applied to all path() patterns */
|
|
215
265
|
urlPrefix?: string;
|
|
216
266
|
/** Name prefix from include() - applied to all named routes */
|
|
217
267
|
namePrefix?: string;
|
|
268
|
+
/** True when this scope is at root level (no named include boundary above).
|
|
269
|
+
* Routes at root scope allow dot-local reverse to fall back to bare names. */
|
|
270
|
+
rootScoped?: boolean;
|
|
218
271
|
/** Run helper for cleaner middleware code */
|
|
219
272
|
run?: <T>(fn: () => T | Promise<T>) => T | Promise<T>;
|
|
220
273
|
/** Tracked includes for build-time manifest generation */
|
|
221
274
|
trackedIncludes?: TrackedInclude[];
|
|
275
|
+
/** Cache profiles for DSL-time cache("profileName") resolution */
|
|
276
|
+
cacheProfiles?: Record<
|
|
277
|
+
string,
|
|
278
|
+
import("../cache/profile-registry.js").CacheProfile
|
|
279
|
+
>;
|
|
280
|
+
/** True when resolving handlers inside a cache() DSL boundary.
|
|
281
|
+
* Read by ctx.get() to guard non-cacheable variable reads. */
|
|
282
|
+
insideCacheScope?: boolean;
|
|
283
|
+
/**
|
|
284
|
+
* Include scope string applied to direct-descendant shortCodes.
|
|
285
|
+
*
|
|
286
|
+
* Each `include(...)` call allocates a sibling-positional token like `I0`,
|
|
287
|
+
* `I1` from its parent's include counter and stores the composed scope
|
|
288
|
+
* (`${parentScope}I${idx}`) in its lazyContext. When the include's handler
|
|
289
|
+
* evaluates lazily, the store's `includeScope` is set from that context so
|
|
290
|
+
* every direct-descendant shortCode is generated as
|
|
291
|
+
* `${parent.shortCode}${includeScope}${prefix}${index}` — preventing
|
|
292
|
+
* collisions with siblings declared outside the include.
|
|
293
|
+
*
|
|
294
|
+
* The scope is NOT propagated through `store.run(...)`, so layouts /
|
|
295
|
+
* parallels / caches inside the include absorb the scope into their own
|
|
296
|
+
* shortCodes and their children start fresh.
|
|
297
|
+
*/
|
|
298
|
+
includeScope?: string;
|
|
222
299
|
}
|
|
223
|
-
|
|
224
|
-
|
|
300
|
+
// Use a global symbol key so the AsyncLocalStorage instance survives HMR
|
|
301
|
+
// module re-evaluation. Without this, Vite's RSC module runner may create
|
|
302
|
+
// a new instance when context.ts is re-evaluated, while other modules still
|
|
303
|
+
// hold references to the old instance — causing getStore() to return
|
|
304
|
+
// undefined even inside a run() callback.
|
|
305
|
+
const RSC_CONTEXT_KEY = Symbol.for("rangojs-router:rsc-context");
|
|
306
|
+
export const RSCRouterContext: AsyncLocalStorage<HelperContext> = ((
|
|
307
|
+
globalThis as any
|
|
308
|
+
)[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage<HelperContext>());
|
|
225
309
|
|
|
226
310
|
export const getContext = (): {
|
|
227
311
|
context: AsyncLocalStorage<HelperContext>;
|
|
@@ -229,21 +313,21 @@ export const getContext = (): {
|
|
|
229
313
|
getParent: () => EntryData | null;
|
|
230
314
|
getOrCreateStore: (forRoute?: string) => HelperContext;
|
|
231
315
|
getNextIndex: (
|
|
232
|
-
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
|
|
316
|
+
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
233
317
|
) => string;
|
|
234
318
|
getShortCode: (
|
|
235
|
-
type: "layout" | "parallel" | "route" | "loader" | "cache"
|
|
319
|
+
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
236
320
|
) => string;
|
|
237
321
|
run: <T>(
|
|
238
322
|
namespace: string,
|
|
239
323
|
parent: EntryData | null,
|
|
240
|
-
callback: (...args: any[]) => T
|
|
324
|
+
callback: (...args: any[]) => T,
|
|
241
325
|
) => T;
|
|
242
326
|
runWithStore: <T>(
|
|
243
327
|
store: HelperContext,
|
|
244
328
|
namespace: string,
|
|
245
329
|
parent: EntryData | null,
|
|
246
|
-
callback: (...args: any[]) => T
|
|
330
|
+
callback: (...args: any[]) => T,
|
|
247
331
|
) => T;
|
|
248
332
|
} => {
|
|
249
333
|
const context = RSCRouterContext;
|
|
@@ -262,6 +346,7 @@ export const getContext = (): {
|
|
|
262
346
|
patterns: new Map<string, string>(),
|
|
263
347
|
patternsByPrefix: new Map<string, Map<string, string>>(),
|
|
264
348
|
trailingSlash: new Map<string, "never" | "always" | "ignore">(),
|
|
349
|
+
searchSchemas: new Map<string, Record<string, string>>(),
|
|
265
350
|
} satisfies HelperContext;
|
|
266
351
|
}
|
|
267
352
|
return store;
|
|
@@ -270,7 +355,7 @@ export const getContext = (): {
|
|
|
270
355
|
const store = context.getStore();
|
|
271
356
|
if (!store) {
|
|
272
357
|
throw new Error(
|
|
273
|
-
"RSC Router context store is not available. Make sure to run within RSC Router context."
|
|
358
|
+
"RSC Router context store is not available. Make sure to run within RSC Router context.",
|
|
274
359
|
);
|
|
275
360
|
}
|
|
276
361
|
return store;
|
|
@@ -284,7 +369,7 @@ export const getContext = (): {
|
|
|
284
369
|
return store.parent;
|
|
285
370
|
},
|
|
286
371
|
getNextIndex: (
|
|
287
|
-
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
|
|
372
|
+
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
288
373
|
) => {
|
|
289
374
|
const store = context.getStore();
|
|
290
375
|
invariant(store, "No context RSCRouterContext available");
|
|
@@ -293,35 +378,55 @@ export const getContext = (): {
|
|
|
293
378
|
store.counters[type] = index + 1;
|
|
294
379
|
return `$${type}.${index}`;
|
|
295
380
|
},
|
|
296
|
-
getShortCode: (
|
|
381
|
+
getShortCode: (
|
|
382
|
+
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
383
|
+
) => {
|
|
297
384
|
const store = context.getStore();
|
|
298
385
|
invariant(store, "No context RSCRouterContext available");
|
|
299
386
|
|
|
300
387
|
const parent = store.parent;
|
|
301
|
-
const prefix =
|
|
302
|
-
|
|
388
|
+
const prefix =
|
|
389
|
+
type === "layout"
|
|
390
|
+
? "L"
|
|
391
|
+
: type === "parallel"
|
|
392
|
+
? "P"
|
|
393
|
+
: type === "loader"
|
|
394
|
+
? "D"
|
|
395
|
+
: type === "cache"
|
|
396
|
+
? "C"
|
|
397
|
+
: "R";
|
|
398
|
+
const mountPrefix =
|
|
399
|
+
store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
|
|
400
|
+
|
|
401
|
+
const includeScope = store.includeScope ?? "";
|
|
303
402
|
|
|
304
403
|
if (!parent) {
|
|
305
404
|
// Root entry: prefix with mount index and use mount-scoped counter
|
|
306
|
-
const counterKey = mountPrefix
|
|
405
|
+
const counterKey = mountPrefix
|
|
406
|
+
? `${mountPrefix}_root_${type}`
|
|
407
|
+
: `root_${type}`;
|
|
307
408
|
store.counters[counterKey] ??= 0;
|
|
308
409
|
const index = store.counters[counterKey];
|
|
309
410
|
store.counters[counterKey] = index + 1;
|
|
310
411
|
return `${mountPrefix}${prefix}${index}`;
|
|
311
412
|
} else {
|
|
312
|
-
// Child entry: use parent-scoped counter
|
|
313
|
-
|
|
413
|
+
// Child entry: use parent-scoped counter with includeScope appended.
|
|
414
|
+
// When we're evaluating a lazy include's direct children, includeScope
|
|
415
|
+
// is a per-include token like "I0" / "I1I0" that partitions the
|
|
416
|
+
// parent's counter namespace so routes inside one include cannot
|
|
417
|
+
// collide with siblings declared outside it.
|
|
418
|
+
const counterKey = `${parent.shortCode}${includeScope}_${type}`;
|
|
314
419
|
store.counters[counterKey] ??= 0;
|
|
315
420
|
const index = store.counters[counterKey];
|
|
316
421
|
store.counters[counterKey] = index + 1;
|
|
317
|
-
return `${parent.shortCode}${prefix}${index}`;
|
|
422
|
+
return `${parent.shortCode}${includeScope}${prefix}${index}`;
|
|
318
423
|
}
|
|
319
424
|
},
|
|
320
425
|
runWithStore: <T>(
|
|
321
426
|
store: HelperContext,
|
|
322
427
|
namespace: string,
|
|
323
428
|
parent: EntryData | null,
|
|
324
|
-
callback: (...args: any[]) => T
|
|
429
|
+
callback: (...args: any[]) => T,
|
|
325
430
|
): T => {
|
|
326
431
|
return context.run(
|
|
327
432
|
{
|
|
@@ -335,24 +440,33 @@ export const getContext = (): {
|
|
|
335
440
|
isSSR: store.isSSR,
|
|
336
441
|
patterns: store.patterns,
|
|
337
442
|
trailingSlash: store.trailingSlash,
|
|
443
|
+
searchSchemas: store.searchSchemas,
|
|
338
444
|
urlPrefix: store.urlPrefix,
|
|
339
445
|
namePrefix: store.namePrefix,
|
|
446
|
+
rootScoped: store.rootScoped,
|
|
340
447
|
trackedIncludes: store.trackedIncludes,
|
|
448
|
+
cacheProfiles: store.cacheProfiles,
|
|
449
|
+
includeScope: store.includeScope,
|
|
341
450
|
},
|
|
342
|
-
callback
|
|
451
|
+
callback,
|
|
343
452
|
);
|
|
344
453
|
},
|
|
345
454
|
run: <T>(
|
|
346
455
|
namespace: string,
|
|
347
456
|
parent: EntryData | null,
|
|
348
|
-
callback: (...args: any[]) => T
|
|
457
|
+
callback: (...args: any[]) => T,
|
|
349
458
|
) => {
|
|
350
459
|
const store = context.getStore();
|
|
351
460
|
// Preserve parent counters to ensure globally unique shortCodes
|
|
352
461
|
const counters = store?.counters || {};
|
|
353
462
|
const manifest = store ? store.manifest : new Map<string, EntryData>();
|
|
354
463
|
const patterns = store?.patterns || new Map<string, string>();
|
|
355
|
-
const
|
|
464
|
+
const patternsByPrefix = store?.patternsByPrefix;
|
|
465
|
+
const trailingSlash =
|
|
466
|
+
store?.trailingSlash ||
|
|
467
|
+
new Map<string, "never" | "always" | "ignore">();
|
|
468
|
+
const searchSchemas =
|
|
469
|
+
store?.searchSchemas || new Map<string, Record<string, string>>();
|
|
356
470
|
return context.run(
|
|
357
471
|
{
|
|
358
472
|
manifest,
|
|
@@ -364,12 +478,16 @@ export const getContext = (): {
|
|
|
364
478
|
metrics: store?.metrics,
|
|
365
479
|
isSSR: store?.isSSR,
|
|
366
480
|
patterns,
|
|
481
|
+
patternsByPrefix,
|
|
367
482
|
trailingSlash,
|
|
483
|
+
searchSchemas,
|
|
368
484
|
urlPrefix: store?.urlPrefix,
|
|
369
485
|
namePrefix: store?.namePrefix,
|
|
486
|
+
rootScoped: store?.rootScoped,
|
|
370
487
|
trackedIncludes: store?.trackedIncludes,
|
|
488
|
+
cacheProfiles: store?.cacheProfiles,
|
|
371
489
|
},
|
|
372
|
-
callback
|
|
490
|
+
callback,
|
|
373
491
|
);
|
|
374
492
|
},
|
|
375
493
|
};
|
|
@@ -382,30 +500,61 @@ export const getContext = (): {
|
|
|
382
500
|
export function runWithPrefixes<T>(
|
|
383
501
|
urlPrefix: string,
|
|
384
502
|
namePrefix: string | undefined,
|
|
385
|
-
callback: () => T
|
|
503
|
+
callback: () => T,
|
|
386
504
|
): T {
|
|
387
505
|
const store = RSCRouterContext.getStore();
|
|
388
506
|
if (!store) {
|
|
389
507
|
throw new Error("runWithPrefixes must be called within router context");
|
|
390
508
|
}
|
|
391
509
|
|
|
392
|
-
// Combine prefixes if there are existing ones
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
510
|
+
// Combine prefixes if there are existing ones, avoiding double slashes
|
|
511
|
+
let combinedUrlPrefix: string;
|
|
512
|
+
if (store.urlPrefix) {
|
|
513
|
+
if (store.urlPrefix.endsWith("/") && urlPrefix.startsWith("/")) {
|
|
514
|
+
combinedUrlPrefix = store.urlPrefix + urlPrefix.slice(1);
|
|
515
|
+
} else {
|
|
516
|
+
combinedUrlPrefix = store.urlPrefix + urlPrefix;
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
combinedUrlPrefix = urlPrefix;
|
|
520
|
+
}
|
|
521
|
+
const combinedNamePrefix =
|
|
522
|
+
namePrefix !== undefined
|
|
523
|
+
? namePrefix === ""
|
|
524
|
+
? store.namePrefix
|
|
525
|
+
: store.namePrefix
|
|
526
|
+
? `${store.namePrefix}.${namePrefix}`
|
|
527
|
+
: namePrefix
|
|
528
|
+
: store.namePrefix;
|
|
529
|
+
|
|
530
|
+
// Track root scope for dot-local reverse resolution.
|
|
531
|
+
//
|
|
532
|
+
// The flag answers: "can this route reach bare names at root scope?"
|
|
533
|
+
// It propagates through the include chain:
|
|
534
|
+
//
|
|
535
|
+
// { name: "" } — transparent: inherit parent, default true
|
|
536
|
+
// { name: "foo" } — inherit parent if already set, else create boundary (false)
|
|
537
|
+
// no name — inherit parent unchanged
|
|
538
|
+
//
|
|
539
|
+
// This means { name: "" } + nested { name: "sub" } keeps rootScoped=true
|
|
540
|
+
// (the outer transparent include establishes root access, and the inner
|
|
541
|
+
// named include inherits it). But a direct { name: "sub" } at root gets
|
|
542
|
+
// rootScoped=false (no prior root-access grant, so it creates a boundary).
|
|
543
|
+
const combinedRootScoped =
|
|
544
|
+
namePrefix === ""
|
|
545
|
+
? (store.rootScoped ?? true)
|
|
546
|
+
: namePrefix !== undefined
|
|
547
|
+
? (store.rootScoped ?? false)
|
|
548
|
+
: store.rootScoped;
|
|
401
549
|
|
|
402
550
|
return RSCRouterContext.run(
|
|
403
551
|
{
|
|
404
552
|
...store,
|
|
405
553
|
urlPrefix: combinedUrlPrefix,
|
|
406
554
|
namePrefix: combinedNamePrefix,
|
|
555
|
+
rootScoped: combinedRootScoped,
|
|
407
556
|
},
|
|
408
|
-
callback
|
|
557
|
+
callback,
|
|
409
558
|
);
|
|
410
559
|
}
|
|
411
560
|
|
|
@@ -425,9 +574,92 @@ export function getNamePrefix(): string | undefined {
|
|
|
425
574
|
return store?.namePrefix;
|
|
426
575
|
}
|
|
427
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Get whether the current scope is at root level (no named include boundary above).
|
|
579
|
+
* Returns true at root or inside { name: "" } includes, false inside named includes.
|
|
580
|
+
*/
|
|
581
|
+
export function getRootScoped(): boolean {
|
|
582
|
+
const store = RSCRouterContext.getStore();
|
|
583
|
+
return store?.rootScoped ?? true;
|
|
584
|
+
}
|
|
585
|
+
|
|
428
586
|
// Export HelperContext type for use in other modules
|
|
429
587
|
export type { HelperContext };
|
|
430
588
|
|
|
589
|
+
/**
|
|
590
|
+
* Return an isolated copy of a lazy include's captured parent entry.
|
|
591
|
+
*
|
|
592
|
+
* DSL helpers (loader(), middleware(), etc.) mutate ctx.parent in place.
|
|
593
|
+
* Multiple include() scopes capture the *same* syntheticMapRoot as their
|
|
594
|
+
* parent, so without isolation one include's loaders/middleware leak into
|
|
595
|
+
* every other route that shares that root.
|
|
596
|
+
*
|
|
597
|
+
* The clone is shallow: only the mutable arrays are copied so each
|
|
598
|
+
* include pushes to its own list. The rest of the entry (id, shortCode,
|
|
599
|
+
* parent pointer, handler) stays shared, which is correct and cheap.
|
|
600
|
+
*/
|
|
601
|
+
export function getIsolatedLazyParent(
|
|
602
|
+
captured: EntryData | null | undefined,
|
|
603
|
+
): EntryData | null {
|
|
604
|
+
if (!captured) return null;
|
|
605
|
+
return {
|
|
606
|
+
...captured,
|
|
607
|
+
loader: [...captured.loader],
|
|
608
|
+
middleware: [...captured.middleware],
|
|
609
|
+
revalidate: [...captured.revalidate],
|
|
610
|
+
errorBoundary: [...captured.errorBoundary],
|
|
611
|
+
notFoundBoundary: [...captured.notFoundBoundary],
|
|
612
|
+
layout: [...captured.layout],
|
|
613
|
+
parallel: { ...captured.parallel },
|
|
614
|
+
intercept: [...captured.intercept],
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export function getParallelEntries(
|
|
619
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
620
|
+
): ParallelEntryData[] {
|
|
621
|
+
if (!parallels) return [];
|
|
622
|
+
if (Array.isArray(parallels)) {
|
|
623
|
+
return parallels.filter(
|
|
624
|
+
(entry): entry is ParallelEntryData => entry.type === "parallel",
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
return Object.values(parallels).filter(
|
|
628
|
+
(entry): entry is ParallelEntryData => !!entry,
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export function getParallelSlotEntries(
|
|
633
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
634
|
+
): Array<{ slot: `@${string}`; entry: ParallelEntryData }> {
|
|
635
|
+
if (!parallels) return [];
|
|
636
|
+
|
|
637
|
+
if (Array.isArray(parallels)) {
|
|
638
|
+
return getParallelEntries(parallels).flatMap((entry) =>
|
|
639
|
+
(Object.keys(entry.handler) as `@${string}`[]).map((slot) => ({
|
|
640
|
+
slot,
|
|
641
|
+
entry,
|
|
642
|
+
})),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return Object.entries(parallels)
|
|
647
|
+
.filter(([, entry]) => !!entry)
|
|
648
|
+
.map(([slot, entry]) => ({
|
|
649
|
+
slot: slot as `@${string}`,
|
|
650
|
+
entry: entry!,
|
|
651
|
+
}));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function getParallelSlotCount(
|
|
655
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
656
|
+
): number {
|
|
657
|
+
if (!parallels) return 0;
|
|
658
|
+
return Array.isArray(parallels)
|
|
659
|
+
? parallels.filter((entry) => entry?.type === "parallel").length
|
|
660
|
+
: Object.keys(parallels).length;
|
|
661
|
+
}
|
|
662
|
+
|
|
431
663
|
// ============================================================================
|
|
432
664
|
// Performance Metrics Helpers
|
|
433
665
|
// ============================================================================
|
|
@@ -443,7 +675,7 @@ export type { HelperContext };
|
|
|
443
675
|
* done(); // Records duration
|
|
444
676
|
* ```
|
|
445
677
|
*/
|
|
446
|
-
export function track(label: string): () => void {
|
|
678
|
+
export function track(label: string, depth?: number): () => void {
|
|
447
679
|
const store = RSCRouterContext.getStore();
|
|
448
680
|
|
|
449
681
|
// No-op if context unavailable or metrics not enabled
|
|
@@ -454,7 +686,55 @@ export function track(label: string): () => void {
|
|
|
454
686
|
const startTime = performance.now() - store.metrics.requestStart;
|
|
455
687
|
|
|
456
688
|
return () => {
|
|
457
|
-
const duration =
|
|
458
|
-
|
|
689
|
+
const duration =
|
|
690
|
+
performance.now() - store.metrics!.requestStart - startTime;
|
|
691
|
+
store.metrics!.metrics.push({
|
|
692
|
+
label,
|
|
693
|
+
duration,
|
|
694
|
+
startTime,
|
|
695
|
+
...(depth != null ? { depth } : {}),
|
|
696
|
+
});
|
|
459
697
|
};
|
|
460
698
|
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Separate ALS for tracking loader execution scope.
|
|
702
|
+
* Uses a dedicated ALS (not RSCRouterContext) to avoid issues with
|
|
703
|
+
* nested RSCRouterContext.run() calls in Vite's module runner.
|
|
704
|
+
*/
|
|
705
|
+
const LOADER_SCOPE_KEY = Symbol.for("rangojs-router:loader-scope");
|
|
706
|
+
const loaderScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
707
|
+
globalThis as any
|
|
708
|
+
)[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage<{ active: true }>());
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Check if the current execution is inside a cache() DSL boundary.
|
|
712
|
+
* Returns false inside loader execution — loaders are always fresh
|
|
713
|
+
* (never cached), so non-cacheable reads are safe.
|
|
714
|
+
*/
|
|
715
|
+
export function isInsideCacheScope(): boolean {
|
|
716
|
+
if (RSCRouterContext.getStore()?.insideCacheScope !== true) return false;
|
|
717
|
+
// Loaders are always fresh — even inside a cache() boundary, the loader
|
|
718
|
+
// function re-executes on every request. Skip the guard when running
|
|
719
|
+
// inside a loader.
|
|
720
|
+
if (loaderScopeALS.getStore()?.active) return false;
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Check if the current execution is inside a DSL loader scope
|
|
726
|
+
* (wrapped by runInsideLoaderScope). Used by rendered() barrier
|
|
727
|
+
* to distinguish DSL loaders from handler-invoked loaders.
|
|
728
|
+
*/
|
|
729
|
+
export function isInsideLoaderScope(): boolean {
|
|
730
|
+
return loaderScopeALS.getStore()?.active === true;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Run `fn` inside a loader scope. While active, cache-scope guards
|
|
735
|
+
* are bypassed because loaders are always fresh (never cached) and
|
|
736
|
+
* their side effects (setCookie, header, etc.) are safe.
|
|
737
|
+
*/
|
|
738
|
+
export function runInsideLoaderScope<T>(fn: () => T): T {
|
|
739
|
+
return loaderScopeALS.run({ active: true }, fn);
|
|
740
|
+
}
|