@rangojs/router 0.0.0-experimental.5 → 0.0.0-experimental.50
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 +884 -4
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +4567 -769
- package/package.json +77 -58
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +85 -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 +89 -30
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +204 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +318 -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/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 +282 -557
- package/src/browser/navigation-client.ts +157 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +303 -310
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +144 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +193 -73
- package/src/browser/react/NavigationProvider.tsx +160 -13
- package/src/browser/react/context.ts +6 -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 +24 -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 +32 -79
- 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 +63 -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 +188 -55
- package/src/browser/scroll-restoration.ts +117 -44
- package/src/browser/segment-reconciler.ts +221 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +118 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -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 +411 -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 +479 -0
- package/src/build/route-types/scan-filter.ts +78 -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 +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +106 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +15 -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 +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +153 -19
- package/src/index.ts +211 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -147
- 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 +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +959 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -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 +59 -8
- package/src/router/content-negotiation.ts +116 -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 +374 -81
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +154 -35
- package/src/router/match-api.ts +620 -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 +440 -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 +27 -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 +222 -0
- package/src/router/middleware.ts +327 -369
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +677 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1296 -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 +77 -3
- package/src/router.ts +665 -4182
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +172 -21
- package/src/server/context.ts +266 -58
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +439 -73
- package/src/server.ts +35 -128
- package/src/ssr/index.tsx +101 -31
- package/src/static-handler.ts +114 -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 +773 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -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 +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -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 +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -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 +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -782
- package/src/vite/plugin-types.ts +48 -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 +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -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 +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -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} +27 -16
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -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 +189 -0
- package/src/vite/utils/shared-utils.ts +169 -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/warmup/connection-warmup.tsx +0 -94
- /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,84 @@ 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?: { passthrough?: boolean };
|
|
195
|
+
};
|
|
196
|
+
/** Set when handler is a Static definition (build-time only) */
|
|
197
|
+
isStaticPrerender?: true;
|
|
198
|
+
/** Static handler $$id for build-time store lookup */
|
|
199
|
+
staticHandlerId?: string;
|
|
200
|
+
/** Response type for non-RSC routes (json, text, image, any) */
|
|
201
|
+
responseType?: string;
|
|
158
202
|
} & EntryPropCommon &
|
|
159
203
|
EntryPropDatas &
|
|
160
204
|
EntryPropSegments)
|
|
161
205
|
| ({
|
|
162
206
|
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>;
|
|
207
|
+
handler: ReactNode | Handler<any, any, any>;
|
|
171
208
|
loading?: ReactNode | false;
|
|
209
|
+
transition?: TransitionConfig;
|
|
210
|
+
/** Set when handler is a Static definition (build-time only) */
|
|
211
|
+
isStaticPrerender?: true;
|
|
212
|
+
/** Static handler $$id for build-time store lookup */
|
|
213
|
+
staticHandlerId?: string;
|
|
172
214
|
} & EntryPropCommon &
|
|
173
215
|
EntryPropDatas &
|
|
174
216
|
EntryPropSegments)
|
|
217
|
+
| ParallelEntryData
|
|
175
218
|
| ({
|
|
176
219
|
type: "cache";
|
|
177
220
|
/** Cache entries create cache boundaries and render like layouts (with Outlet) */
|
|
178
|
-
handler: ReactNode | Handler<any, any>;
|
|
221
|
+
handler: ReactNode | Handler<any, any, any>;
|
|
179
222
|
loading?: ReactNode | false;
|
|
223
|
+
transition?: TransitionConfig;
|
|
180
224
|
} & EntryPropCommon &
|
|
181
225
|
EntryPropDatas &
|
|
182
226
|
EntryPropSegments);
|
|
@@ -211,17 +255,34 @@ interface HelperContext {
|
|
|
211
255
|
patternsByPrefix?: Map<string, Map<string, string>>;
|
|
212
256
|
/** Trailing slash config per route name */
|
|
213
257
|
trailingSlash?: Map<string, "never" | "always" | "ignore">;
|
|
258
|
+
/** Search param schemas per route name */
|
|
259
|
+
searchSchemas?: Map<string, Record<string, string>>;
|
|
214
260
|
/** URL prefix from include() - applied to all path() patterns */
|
|
215
261
|
urlPrefix?: string;
|
|
216
262
|
/** Name prefix from include() - applied to all named routes */
|
|
217
263
|
namePrefix?: string;
|
|
264
|
+
/** True when this scope is at root level (no named include boundary above).
|
|
265
|
+
* Routes at root scope allow dot-local reverse to fall back to bare names. */
|
|
266
|
+
rootScoped?: boolean;
|
|
218
267
|
/** Run helper for cleaner middleware code */
|
|
219
268
|
run?: <T>(fn: () => T | Promise<T>) => T | Promise<T>;
|
|
220
269
|
/** Tracked includes for build-time manifest generation */
|
|
221
270
|
trackedIncludes?: TrackedInclude[];
|
|
271
|
+
/** Cache profiles for DSL-time cache("profileName") resolution */
|
|
272
|
+
cacheProfiles?: Record<
|
|
273
|
+
string,
|
|
274
|
+
import("../cache/profile-registry.js").CacheProfile
|
|
275
|
+
>;
|
|
222
276
|
}
|
|
223
|
-
|
|
224
|
-
|
|
277
|
+
// Use a global symbol key so the AsyncLocalStorage instance survives HMR
|
|
278
|
+
// module re-evaluation. Without this, Vite's RSC module runner may create
|
|
279
|
+
// a new instance when context.ts is re-evaluated, while other modules still
|
|
280
|
+
// hold references to the old instance — causing getStore() to return
|
|
281
|
+
// undefined even inside a run() callback.
|
|
282
|
+
const RSC_CONTEXT_KEY = Symbol.for("rangojs-router:rsc-context");
|
|
283
|
+
export const RSCRouterContext: AsyncLocalStorage<HelperContext> = ((
|
|
284
|
+
globalThis as any
|
|
285
|
+
)[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage<HelperContext>());
|
|
225
286
|
|
|
226
287
|
export const getContext = (): {
|
|
227
288
|
context: AsyncLocalStorage<HelperContext>;
|
|
@@ -229,21 +290,21 @@ export const getContext = (): {
|
|
|
229
290
|
getParent: () => EntryData | null;
|
|
230
291
|
getOrCreateStore: (forRoute?: string) => HelperContext;
|
|
231
292
|
getNextIndex: (
|
|
232
|
-
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
|
|
293
|
+
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
233
294
|
) => string;
|
|
234
295
|
getShortCode: (
|
|
235
|
-
type: "layout" | "parallel" | "route" | "loader" | "cache"
|
|
296
|
+
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
236
297
|
) => string;
|
|
237
298
|
run: <T>(
|
|
238
299
|
namespace: string,
|
|
239
300
|
parent: EntryData | null,
|
|
240
|
-
callback: (...args: any[]) => T
|
|
301
|
+
callback: (...args: any[]) => T,
|
|
241
302
|
) => T;
|
|
242
303
|
runWithStore: <T>(
|
|
243
304
|
store: HelperContext,
|
|
244
305
|
namespace: string,
|
|
245
306
|
parent: EntryData | null,
|
|
246
|
-
callback: (...args: any[]) => T
|
|
307
|
+
callback: (...args: any[]) => T,
|
|
247
308
|
) => T;
|
|
248
309
|
} => {
|
|
249
310
|
const context = RSCRouterContext;
|
|
@@ -262,6 +323,7 @@ export const getContext = (): {
|
|
|
262
323
|
patterns: new Map<string, string>(),
|
|
263
324
|
patternsByPrefix: new Map<string, Map<string, string>>(),
|
|
264
325
|
trailingSlash: new Map<string, "never" | "always" | "ignore">(),
|
|
326
|
+
searchSchemas: new Map<string, Record<string, string>>(),
|
|
265
327
|
} satisfies HelperContext;
|
|
266
328
|
}
|
|
267
329
|
return store;
|
|
@@ -270,7 +332,7 @@ export const getContext = (): {
|
|
|
270
332
|
const store = context.getStore();
|
|
271
333
|
if (!store) {
|
|
272
334
|
throw new Error(
|
|
273
|
-
"RSC Router context store is not available. Make sure to run within RSC Router context."
|
|
335
|
+
"RSC Router context store is not available. Make sure to run within RSC Router context.",
|
|
274
336
|
);
|
|
275
337
|
}
|
|
276
338
|
return store;
|
|
@@ -284,7 +346,7 @@ export const getContext = (): {
|
|
|
284
346
|
return store.parent;
|
|
285
347
|
},
|
|
286
348
|
getNextIndex: (
|
|
287
|
-
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
|
|
349
|
+
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
288
350
|
) => {
|
|
289
351
|
const store = context.getStore();
|
|
290
352
|
invariant(store, "No context RSCRouterContext available");
|
|
@@ -293,17 +355,31 @@ export const getContext = (): {
|
|
|
293
355
|
store.counters[type] = index + 1;
|
|
294
356
|
return `$${type}.${index}`;
|
|
295
357
|
},
|
|
296
|
-
getShortCode: (
|
|
358
|
+
getShortCode: (
|
|
359
|
+
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
360
|
+
) => {
|
|
297
361
|
const store = context.getStore();
|
|
298
362
|
invariant(store, "No context RSCRouterContext available");
|
|
299
363
|
|
|
300
364
|
const parent = store.parent;
|
|
301
|
-
const prefix =
|
|
302
|
-
|
|
365
|
+
const prefix =
|
|
366
|
+
type === "layout"
|
|
367
|
+
? "L"
|
|
368
|
+
: type === "parallel"
|
|
369
|
+
? "P"
|
|
370
|
+
: type === "loader"
|
|
371
|
+
? "D"
|
|
372
|
+
: type === "cache"
|
|
373
|
+
? "C"
|
|
374
|
+
: "R";
|
|
375
|
+
const mountPrefix =
|
|
376
|
+
store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
|
|
303
377
|
|
|
304
378
|
if (!parent) {
|
|
305
379
|
// Root entry: prefix with mount index and use mount-scoped counter
|
|
306
|
-
const counterKey = mountPrefix
|
|
380
|
+
const counterKey = mountPrefix
|
|
381
|
+
? `${mountPrefix}_root_${type}`
|
|
382
|
+
: `root_${type}`;
|
|
307
383
|
store.counters[counterKey] ??= 0;
|
|
308
384
|
const index = store.counters[counterKey];
|
|
309
385
|
store.counters[counterKey] = index + 1;
|
|
@@ -321,7 +397,7 @@ export const getContext = (): {
|
|
|
321
397
|
store: HelperContext,
|
|
322
398
|
namespace: string,
|
|
323
399
|
parent: EntryData | null,
|
|
324
|
-
callback: (...args: any[]) => T
|
|
400
|
+
callback: (...args: any[]) => T,
|
|
325
401
|
): T => {
|
|
326
402
|
return context.run(
|
|
327
403
|
{
|
|
@@ -335,24 +411,32 @@ export const getContext = (): {
|
|
|
335
411
|
isSSR: store.isSSR,
|
|
336
412
|
patterns: store.patterns,
|
|
337
413
|
trailingSlash: store.trailingSlash,
|
|
414
|
+
searchSchemas: store.searchSchemas,
|
|
338
415
|
urlPrefix: store.urlPrefix,
|
|
339
416
|
namePrefix: store.namePrefix,
|
|
417
|
+
rootScoped: store.rootScoped,
|
|
340
418
|
trackedIncludes: store.trackedIncludes,
|
|
419
|
+
cacheProfiles: store.cacheProfiles,
|
|
341
420
|
},
|
|
342
|
-
callback
|
|
421
|
+
callback,
|
|
343
422
|
);
|
|
344
423
|
},
|
|
345
424
|
run: <T>(
|
|
346
425
|
namespace: string,
|
|
347
426
|
parent: EntryData | null,
|
|
348
|
-
callback: (...args: any[]) => T
|
|
427
|
+
callback: (...args: any[]) => T,
|
|
349
428
|
) => {
|
|
350
429
|
const store = context.getStore();
|
|
351
430
|
// Preserve parent counters to ensure globally unique shortCodes
|
|
352
431
|
const counters = store?.counters || {};
|
|
353
432
|
const manifest = store ? store.manifest : new Map<string, EntryData>();
|
|
354
433
|
const patterns = store?.patterns || new Map<string, string>();
|
|
355
|
-
const
|
|
434
|
+
const patternsByPrefix = store?.patternsByPrefix;
|
|
435
|
+
const trailingSlash =
|
|
436
|
+
store?.trailingSlash ||
|
|
437
|
+
new Map<string, "never" | "always" | "ignore">();
|
|
438
|
+
const searchSchemas =
|
|
439
|
+
store?.searchSchemas || new Map<string, Record<string, string>>();
|
|
356
440
|
return context.run(
|
|
357
441
|
{
|
|
358
442
|
manifest,
|
|
@@ -364,12 +448,16 @@ export const getContext = (): {
|
|
|
364
448
|
metrics: store?.metrics,
|
|
365
449
|
isSSR: store?.isSSR,
|
|
366
450
|
patterns,
|
|
451
|
+
patternsByPrefix,
|
|
367
452
|
trailingSlash,
|
|
453
|
+
searchSchemas,
|
|
368
454
|
urlPrefix: store?.urlPrefix,
|
|
369
455
|
namePrefix: store?.namePrefix,
|
|
456
|
+
rootScoped: store?.rootScoped,
|
|
370
457
|
trackedIncludes: store?.trackedIncludes,
|
|
458
|
+
cacheProfiles: store?.cacheProfiles,
|
|
371
459
|
},
|
|
372
|
-
callback
|
|
460
|
+
callback,
|
|
373
461
|
);
|
|
374
462
|
},
|
|
375
463
|
};
|
|
@@ -382,30 +470,61 @@ export const getContext = (): {
|
|
|
382
470
|
export function runWithPrefixes<T>(
|
|
383
471
|
urlPrefix: string,
|
|
384
472
|
namePrefix: string | undefined,
|
|
385
|
-
callback: () => T
|
|
473
|
+
callback: () => T,
|
|
386
474
|
): T {
|
|
387
475
|
const store = RSCRouterContext.getStore();
|
|
388
476
|
if (!store) {
|
|
389
477
|
throw new Error("runWithPrefixes must be called within router context");
|
|
390
478
|
}
|
|
391
479
|
|
|
392
|
-
// Combine prefixes if there are existing ones
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
480
|
+
// Combine prefixes if there are existing ones, avoiding double slashes
|
|
481
|
+
let combinedUrlPrefix: string;
|
|
482
|
+
if (store.urlPrefix) {
|
|
483
|
+
if (store.urlPrefix.endsWith("/") && urlPrefix.startsWith("/")) {
|
|
484
|
+
combinedUrlPrefix = store.urlPrefix + urlPrefix.slice(1);
|
|
485
|
+
} else {
|
|
486
|
+
combinedUrlPrefix = store.urlPrefix + urlPrefix;
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
combinedUrlPrefix = urlPrefix;
|
|
490
|
+
}
|
|
491
|
+
const combinedNamePrefix =
|
|
492
|
+
namePrefix !== undefined
|
|
493
|
+
? namePrefix === ""
|
|
494
|
+
? store.namePrefix
|
|
495
|
+
: store.namePrefix
|
|
496
|
+
? `${store.namePrefix}.${namePrefix}`
|
|
497
|
+
: namePrefix
|
|
498
|
+
: store.namePrefix;
|
|
499
|
+
|
|
500
|
+
// Track root scope for dot-local reverse resolution.
|
|
501
|
+
//
|
|
502
|
+
// The flag answers: "can this route reach bare names at root scope?"
|
|
503
|
+
// It propagates through the include chain:
|
|
504
|
+
//
|
|
505
|
+
// { name: "" } — transparent: inherit parent, default true
|
|
506
|
+
// { name: "foo" } — inherit parent if already set, else create boundary (false)
|
|
507
|
+
// no name — inherit parent unchanged
|
|
508
|
+
//
|
|
509
|
+
// This means { name: "" } + nested { name: "sub" } keeps rootScoped=true
|
|
510
|
+
// (the outer transparent include establishes root access, and the inner
|
|
511
|
+
// named include inherits it). But a direct { name: "sub" } at root gets
|
|
512
|
+
// rootScoped=false (no prior root-access grant, so it creates a boundary).
|
|
513
|
+
const combinedRootScoped =
|
|
514
|
+
namePrefix === ""
|
|
515
|
+
? (store.rootScoped ?? true)
|
|
516
|
+
: namePrefix !== undefined
|
|
517
|
+
? (store.rootScoped ?? false)
|
|
518
|
+
: store.rootScoped;
|
|
401
519
|
|
|
402
520
|
return RSCRouterContext.run(
|
|
403
521
|
{
|
|
404
522
|
...store,
|
|
405
523
|
urlPrefix: combinedUrlPrefix,
|
|
406
524
|
namePrefix: combinedNamePrefix,
|
|
525
|
+
rootScoped: combinedRootScoped,
|
|
407
526
|
},
|
|
408
|
-
callback
|
|
527
|
+
callback,
|
|
409
528
|
);
|
|
410
529
|
}
|
|
411
530
|
|
|
@@ -425,9 +544,92 @@ export function getNamePrefix(): string | undefined {
|
|
|
425
544
|
return store?.namePrefix;
|
|
426
545
|
}
|
|
427
546
|
|
|
547
|
+
/**
|
|
548
|
+
* Get whether the current scope is at root level (no named include boundary above).
|
|
549
|
+
* Returns true at root or inside { name: "" } includes, false inside named includes.
|
|
550
|
+
*/
|
|
551
|
+
export function getRootScoped(): boolean {
|
|
552
|
+
const store = RSCRouterContext.getStore();
|
|
553
|
+
return store?.rootScoped ?? true;
|
|
554
|
+
}
|
|
555
|
+
|
|
428
556
|
// Export HelperContext type for use in other modules
|
|
429
557
|
export type { HelperContext };
|
|
430
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Return an isolated copy of a lazy include's captured parent entry.
|
|
561
|
+
*
|
|
562
|
+
* DSL helpers (loader(), middleware(), etc.) mutate ctx.parent in place.
|
|
563
|
+
* Multiple include() scopes capture the *same* syntheticMapRoot as their
|
|
564
|
+
* parent, so without isolation one include's loaders/middleware leak into
|
|
565
|
+
* every other route that shares that root.
|
|
566
|
+
*
|
|
567
|
+
* The clone is shallow: only the mutable arrays are copied so each
|
|
568
|
+
* include pushes to its own list. The rest of the entry (id, shortCode,
|
|
569
|
+
* parent pointer, handler) stays shared, which is correct and cheap.
|
|
570
|
+
*/
|
|
571
|
+
export function getIsolatedLazyParent(
|
|
572
|
+
captured: EntryData | null | undefined,
|
|
573
|
+
): EntryData | null {
|
|
574
|
+
if (!captured) return null;
|
|
575
|
+
return {
|
|
576
|
+
...captured,
|
|
577
|
+
loader: [...captured.loader],
|
|
578
|
+
middleware: [...captured.middleware],
|
|
579
|
+
revalidate: [...captured.revalidate],
|
|
580
|
+
errorBoundary: [...captured.errorBoundary],
|
|
581
|
+
notFoundBoundary: [...captured.notFoundBoundary],
|
|
582
|
+
layout: [...captured.layout],
|
|
583
|
+
parallel: { ...captured.parallel },
|
|
584
|
+
intercept: [...captured.intercept],
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export function getParallelEntries(
|
|
589
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
590
|
+
): ParallelEntryData[] {
|
|
591
|
+
if (!parallels) return [];
|
|
592
|
+
if (Array.isArray(parallels)) {
|
|
593
|
+
return parallels.filter(
|
|
594
|
+
(entry): entry is ParallelEntryData => entry.type === "parallel",
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
return Object.values(parallels).filter(
|
|
598
|
+
(entry): entry is ParallelEntryData => !!entry,
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export function getParallelSlotEntries(
|
|
603
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
604
|
+
): Array<{ slot: `@${string}`; entry: ParallelEntryData }> {
|
|
605
|
+
if (!parallels) return [];
|
|
606
|
+
|
|
607
|
+
if (Array.isArray(parallels)) {
|
|
608
|
+
return getParallelEntries(parallels).flatMap((entry) =>
|
|
609
|
+
(Object.keys(entry.handler) as `@${string}`[]).map((slot) => ({
|
|
610
|
+
slot,
|
|
611
|
+
entry,
|
|
612
|
+
})),
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return Object.entries(parallels)
|
|
617
|
+
.filter(([, entry]) => !!entry)
|
|
618
|
+
.map(([slot, entry]) => ({
|
|
619
|
+
slot: slot as `@${string}`,
|
|
620
|
+
entry: entry!,
|
|
621
|
+
}));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export function getParallelSlotCount(
|
|
625
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
626
|
+
): number {
|
|
627
|
+
if (!parallels) return 0;
|
|
628
|
+
return Array.isArray(parallels)
|
|
629
|
+
? parallels.filter((entry) => entry?.type === "parallel").length
|
|
630
|
+
: Object.keys(parallels).length;
|
|
631
|
+
}
|
|
632
|
+
|
|
431
633
|
// ============================================================================
|
|
432
634
|
// Performance Metrics Helpers
|
|
433
635
|
// ============================================================================
|
|
@@ -443,7 +645,7 @@ export type { HelperContext };
|
|
|
443
645
|
* done(); // Records duration
|
|
444
646
|
* ```
|
|
445
647
|
*/
|
|
446
|
-
export function track(label: string): () => void {
|
|
648
|
+
export function track(label: string, depth?: number): () => void {
|
|
447
649
|
const store = RSCRouterContext.getStore();
|
|
448
650
|
|
|
449
651
|
// No-op if context unavailable or metrics not enabled
|
|
@@ -454,7 +656,13 @@ export function track(label: string): () => void {
|
|
|
454
656
|
const startTime = performance.now() - store.metrics.requestStart;
|
|
455
657
|
|
|
456
658
|
return () => {
|
|
457
|
-
const duration =
|
|
458
|
-
|
|
659
|
+
const duration =
|
|
660
|
+
performance.now() - store.metrics!.requestStart - startTime;
|
|
661
|
+
store.metrics!.metrics.push({
|
|
662
|
+
label,
|
|
663
|
+
duration,
|
|
664
|
+
startTime,
|
|
665
|
+
...(depth != null ? { depth } : {}),
|
|
666
|
+
});
|
|
459
667
|
};
|
|
460
668
|
}
|