@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430
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 +5 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4474 -867
- package/package.json +60 -51
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +50 -21
- 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 +78 -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 +87 -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 +285 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +258 -308
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +185 -73
- package/src/browser/react/NavigationProvider.tsx +51 -11
- 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 +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 +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 +107 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -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 +109 -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 +469 -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 +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -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 +17 -7
- 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 +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 +153 -19
- package/src/index.ts +211 -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 +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 +934 -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 +211 -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 +158 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +148 -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 +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- 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 +36 -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 +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -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 +289 -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 +692 -4257
- 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 +235 -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 +25 -13
- package/src/server/context.ts +182 -51
- 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 +430 -70
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +100 -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 +687 -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 +102 -0
- package/src/types/segments.ts +148 -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 +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -1133
- package/src/vite/plugin-types.ts +131 -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 -51
- 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 +254 -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 +510 -0
- package/src/vite/router-discovery.ts +785 -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/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,17 +143,18 @@ 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
|
|
|
144
160
|
export type EntryPropSegments = {
|
|
@@ -151,32 +167,57 @@ export type EntryPropSegments = {
|
|
|
151
167
|
export type EntryData =
|
|
152
168
|
| ({
|
|
153
169
|
type: "route";
|
|
154
|
-
handler: Handler<any, any>;
|
|
170
|
+
handler: Handler<any, any, any>;
|
|
155
171
|
loading?: ReactNode | false;
|
|
172
|
+
transition?: TransitionConfig;
|
|
156
173
|
/** URL pattern for this route (used by path() in urls()) */
|
|
157
174
|
pattern?: string;
|
|
175
|
+
/** Set when handler is a Prerender definition */
|
|
176
|
+
isPrerender?: true;
|
|
177
|
+
/** Original PrerenderHandlerDefinition (for build-time getParams access) */
|
|
178
|
+
prerenderDef?: {
|
|
179
|
+
getParams?: (ctx: any) => Promise<any[]> | any[];
|
|
180
|
+
options?: { passthrough?: boolean };
|
|
181
|
+
};
|
|
182
|
+
/** Set when handler is a Static definition (build-time only) */
|
|
183
|
+
isStaticPrerender?: true;
|
|
184
|
+
/** Static handler $$id for build-time store lookup */
|
|
185
|
+
staticHandlerId?: string;
|
|
186
|
+
/** Response type for non-RSC routes (json, text, image, any) */
|
|
187
|
+
responseType?: string;
|
|
158
188
|
} & EntryPropCommon &
|
|
159
189
|
EntryPropDatas &
|
|
160
190
|
EntryPropSegments)
|
|
161
191
|
| ({
|
|
162
192
|
type: "layout";
|
|
163
|
-
handler: ReactNode | Handler<any, any>;
|
|
193
|
+
handler: ReactNode | Handler<any, any, any>;
|
|
164
194
|
loading?: ReactNode | false;
|
|
195
|
+
transition?: TransitionConfig;
|
|
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;
|
|
165
200
|
} & EntryPropCommon &
|
|
166
201
|
EntryPropDatas &
|
|
167
202
|
EntryPropSegments)
|
|
168
203
|
| ({
|
|
169
204
|
type: "parallel";
|
|
170
|
-
handler: Record<`@${string}`, Handler<any, any> | ReactNode>;
|
|
205
|
+
handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
|
|
171
206
|
loading?: ReactNode | false;
|
|
207
|
+
transition?: TransitionConfig;
|
|
208
|
+
/** Set when any parallel slot is a Static definition */
|
|
209
|
+
isStaticPrerender?: true;
|
|
210
|
+
/** Per-slot static handler $$ids for build-time store lookup */
|
|
211
|
+
staticHandlerIds?: Record<string, string>;
|
|
172
212
|
} & EntryPropCommon &
|
|
173
213
|
EntryPropDatas &
|
|
174
214
|
EntryPropSegments)
|
|
175
215
|
| ({
|
|
176
216
|
type: "cache";
|
|
177
217
|
/** Cache entries create cache boundaries and render like layouts (with Outlet) */
|
|
178
|
-
handler: ReactNode | Handler<any, any>;
|
|
218
|
+
handler: ReactNode | Handler<any, any, any>;
|
|
179
219
|
loading?: ReactNode | false;
|
|
220
|
+
transition?: TransitionConfig;
|
|
180
221
|
} & EntryPropCommon &
|
|
181
222
|
EntryPropDatas &
|
|
182
223
|
EntryPropSegments);
|
|
@@ -211,17 +252,34 @@ interface HelperContext {
|
|
|
211
252
|
patternsByPrefix?: Map<string, Map<string, string>>;
|
|
212
253
|
/** Trailing slash config per route name */
|
|
213
254
|
trailingSlash?: Map<string, "never" | "always" | "ignore">;
|
|
255
|
+
/** Search param schemas per route name */
|
|
256
|
+
searchSchemas?: Map<string, Record<string, string>>;
|
|
214
257
|
/** URL prefix from include() - applied to all path() patterns */
|
|
215
258
|
urlPrefix?: string;
|
|
216
259
|
/** Name prefix from include() - applied to all named routes */
|
|
217
260
|
namePrefix?: string;
|
|
261
|
+
/** True when this scope is at root level (no named include boundary above).
|
|
262
|
+
* Routes at root scope allow dot-local reverse to fall back to bare names. */
|
|
263
|
+
rootScoped?: boolean;
|
|
218
264
|
/** Run helper for cleaner middleware code */
|
|
219
265
|
run?: <T>(fn: () => T | Promise<T>) => T | Promise<T>;
|
|
220
266
|
/** Tracked includes for build-time manifest generation */
|
|
221
267
|
trackedIncludes?: TrackedInclude[];
|
|
268
|
+
/** Cache profiles for DSL-time cache("profileName") resolution */
|
|
269
|
+
cacheProfiles?: Record<
|
|
270
|
+
string,
|
|
271
|
+
import("../cache/profile-registry.js").CacheProfile
|
|
272
|
+
>;
|
|
222
273
|
}
|
|
223
|
-
|
|
224
|
-
|
|
274
|
+
// Use a global symbol key so the AsyncLocalStorage instance survives HMR
|
|
275
|
+
// module re-evaluation. Without this, Vite's RSC module runner may create
|
|
276
|
+
// a new instance when context.ts is re-evaluated, while other modules still
|
|
277
|
+
// hold references to the old instance — causing getStore() to return
|
|
278
|
+
// undefined even inside a run() callback.
|
|
279
|
+
const RSC_CONTEXT_KEY = Symbol.for("rangojs-router:rsc-context");
|
|
280
|
+
export const RSCRouterContext: AsyncLocalStorage<HelperContext> = ((
|
|
281
|
+
globalThis as any
|
|
282
|
+
)[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage<HelperContext>());
|
|
225
283
|
|
|
226
284
|
export const getContext = (): {
|
|
227
285
|
context: AsyncLocalStorage<HelperContext>;
|
|
@@ -229,21 +287,21 @@ export const getContext = (): {
|
|
|
229
287
|
getParent: () => EntryData | null;
|
|
230
288
|
getOrCreateStore: (forRoute?: string) => HelperContext;
|
|
231
289
|
getNextIndex: (
|
|
232
|
-
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
|
|
290
|
+
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
233
291
|
) => string;
|
|
234
292
|
getShortCode: (
|
|
235
|
-
type: "layout" | "parallel" | "route" | "loader" | "cache"
|
|
293
|
+
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
236
294
|
) => string;
|
|
237
295
|
run: <T>(
|
|
238
296
|
namespace: string,
|
|
239
297
|
parent: EntryData | null,
|
|
240
|
-
callback: (...args: any[]) => T
|
|
298
|
+
callback: (...args: any[]) => T,
|
|
241
299
|
) => T;
|
|
242
300
|
runWithStore: <T>(
|
|
243
301
|
store: HelperContext,
|
|
244
302
|
namespace: string,
|
|
245
303
|
parent: EntryData | null,
|
|
246
|
-
callback: (...args: any[]) => T
|
|
304
|
+
callback: (...args: any[]) => T,
|
|
247
305
|
) => T;
|
|
248
306
|
} => {
|
|
249
307
|
const context = RSCRouterContext;
|
|
@@ -262,6 +320,7 @@ export const getContext = (): {
|
|
|
262
320
|
patterns: new Map<string, string>(),
|
|
263
321
|
patternsByPrefix: new Map<string, Map<string, string>>(),
|
|
264
322
|
trailingSlash: new Map<string, "never" | "always" | "ignore">(),
|
|
323
|
+
searchSchemas: new Map<string, Record<string, string>>(),
|
|
265
324
|
} satisfies HelperContext;
|
|
266
325
|
}
|
|
267
326
|
return store;
|
|
@@ -270,7 +329,7 @@ export const getContext = (): {
|
|
|
270
329
|
const store = context.getStore();
|
|
271
330
|
if (!store) {
|
|
272
331
|
throw new Error(
|
|
273
|
-
"RSC Router context store is not available. Make sure to run within RSC Router context."
|
|
332
|
+
"RSC Router context store is not available. Make sure to run within RSC Router context.",
|
|
274
333
|
);
|
|
275
334
|
}
|
|
276
335
|
return store;
|
|
@@ -284,7 +343,7 @@ export const getContext = (): {
|
|
|
284
343
|
return store.parent;
|
|
285
344
|
},
|
|
286
345
|
getNextIndex: (
|
|
287
|
-
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
|
|
346
|
+
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
288
347
|
) => {
|
|
289
348
|
const store = context.getStore();
|
|
290
349
|
invariant(store, "No context RSCRouterContext available");
|
|
@@ -293,17 +352,31 @@ export const getContext = (): {
|
|
|
293
352
|
store.counters[type] = index + 1;
|
|
294
353
|
return `$${type}.${index}`;
|
|
295
354
|
},
|
|
296
|
-
getShortCode: (
|
|
355
|
+
getShortCode: (
|
|
356
|
+
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
357
|
+
) => {
|
|
297
358
|
const store = context.getStore();
|
|
298
359
|
invariant(store, "No context RSCRouterContext available");
|
|
299
360
|
|
|
300
361
|
const parent = store.parent;
|
|
301
|
-
const prefix =
|
|
302
|
-
|
|
362
|
+
const prefix =
|
|
363
|
+
type === "layout"
|
|
364
|
+
? "L"
|
|
365
|
+
: type === "parallel"
|
|
366
|
+
? "P"
|
|
367
|
+
: type === "loader"
|
|
368
|
+
? "D"
|
|
369
|
+
: type === "cache"
|
|
370
|
+
? "C"
|
|
371
|
+
: "R";
|
|
372
|
+
const mountPrefix =
|
|
373
|
+
store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
|
|
303
374
|
|
|
304
375
|
if (!parent) {
|
|
305
376
|
// Root entry: prefix with mount index and use mount-scoped counter
|
|
306
|
-
const counterKey = mountPrefix
|
|
377
|
+
const counterKey = mountPrefix
|
|
378
|
+
? `${mountPrefix}_root_${type}`
|
|
379
|
+
: `root_${type}`;
|
|
307
380
|
store.counters[counterKey] ??= 0;
|
|
308
381
|
const index = store.counters[counterKey];
|
|
309
382
|
store.counters[counterKey] = index + 1;
|
|
@@ -321,7 +394,7 @@ export const getContext = (): {
|
|
|
321
394
|
store: HelperContext,
|
|
322
395
|
namespace: string,
|
|
323
396
|
parent: EntryData | null,
|
|
324
|
-
callback: (...args: any[]) => T
|
|
397
|
+
callback: (...args: any[]) => T,
|
|
325
398
|
): T => {
|
|
326
399
|
return context.run(
|
|
327
400
|
{
|
|
@@ -335,24 +408,32 @@ export const getContext = (): {
|
|
|
335
408
|
isSSR: store.isSSR,
|
|
336
409
|
patterns: store.patterns,
|
|
337
410
|
trailingSlash: store.trailingSlash,
|
|
411
|
+
searchSchemas: store.searchSchemas,
|
|
338
412
|
urlPrefix: store.urlPrefix,
|
|
339
413
|
namePrefix: store.namePrefix,
|
|
414
|
+
rootScoped: store.rootScoped,
|
|
340
415
|
trackedIncludes: store.trackedIncludes,
|
|
416
|
+
cacheProfiles: store.cacheProfiles,
|
|
341
417
|
},
|
|
342
|
-
callback
|
|
418
|
+
callback,
|
|
343
419
|
);
|
|
344
420
|
},
|
|
345
421
|
run: <T>(
|
|
346
422
|
namespace: string,
|
|
347
423
|
parent: EntryData | null,
|
|
348
|
-
callback: (...args: any[]) => T
|
|
424
|
+
callback: (...args: any[]) => T,
|
|
349
425
|
) => {
|
|
350
426
|
const store = context.getStore();
|
|
351
427
|
// Preserve parent counters to ensure globally unique shortCodes
|
|
352
428
|
const counters = store?.counters || {};
|
|
353
429
|
const manifest = store ? store.manifest : new Map<string, EntryData>();
|
|
354
430
|
const patterns = store?.patterns || new Map<string, string>();
|
|
355
|
-
const
|
|
431
|
+
const patternsByPrefix = store?.patternsByPrefix;
|
|
432
|
+
const trailingSlash =
|
|
433
|
+
store?.trailingSlash ||
|
|
434
|
+
new Map<string, "never" | "always" | "ignore">();
|
|
435
|
+
const searchSchemas =
|
|
436
|
+
store?.searchSchemas || new Map<string, Record<string, string>>();
|
|
356
437
|
return context.run(
|
|
357
438
|
{
|
|
358
439
|
manifest,
|
|
@@ -364,12 +445,16 @@ export const getContext = (): {
|
|
|
364
445
|
metrics: store?.metrics,
|
|
365
446
|
isSSR: store?.isSSR,
|
|
366
447
|
patterns,
|
|
448
|
+
patternsByPrefix,
|
|
367
449
|
trailingSlash,
|
|
450
|
+
searchSchemas,
|
|
368
451
|
urlPrefix: store?.urlPrefix,
|
|
369
452
|
namePrefix: store?.namePrefix,
|
|
453
|
+
rootScoped: store?.rootScoped,
|
|
370
454
|
trackedIncludes: store?.trackedIncludes,
|
|
455
|
+
cacheProfiles: store?.cacheProfiles,
|
|
371
456
|
},
|
|
372
|
-
callback
|
|
457
|
+
callback,
|
|
373
458
|
);
|
|
374
459
|
},
|
|
375
460
|
};
|
|
@@ -382,30 +467,61 @@ export const getContext = (): {
|
|
|
382
467
|
export function runWithPrefixes<T>(
|
|
383
468
|
urlPrefix: string,
|
|
384
469
|
namePrefix: string | undefined,
|
|
385
|
-
callback: () => T
|
|
470
|
+
callback: () => T,
|
|
386
471
|
): T {
|
|
387
472
|
const store = RSCRouterContext.getStore();
|
|
388
473
|
if (!store) {
|
|
389
474
|
throw new Error("runWithPrefixes must be called within router context");
|
|
390
475
|
}
|
|
391
476
|
|
|
392
|
-
// Combine prefixes if there are existing ones
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
477
|
+
// Combine prefixes if there are existing ones, avoiding double slashes
|
|
478
|
+
let combinedUrlPrefix: string;
|
|
479
|
+
if (store.urlPrefix) {
|
|
480
|
+
if (store.urlPrefix.endsWith("/") && urlPrefix.startsWith("/")) {
|
|
481
|
+
combinedUrlPrefix = store.urlPrefix + urlPrefix.slice(1);
|
|
482
|
+
} else {
|
|
483
|
+
combinedUrlPrefix = store.urlPrefix + urlPrefix;
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
combinedUrlPrefix = urlPrefix;
|
|
487
|
+
}
|
|
488
|
+
const combinedNamePrefix =
|
|
489
|
+
namePrefix !== undefined
|
|
490
|
+
? namePrefix === ""
|
|
491
|
+
? store.namePrefix
|
|
492
|
+
: store.namePrefix
|
|
493
|
+
? `${store.namePrefix}.${namePrefix}`
|
|
494
|
+
: namePrefix
|
|
495
|
+
: store.namePrefix;
|
|
496
|
+
|
|
497
|
+
// Track root scope for dot-local reverse resolution.
|
|
498
|
+
//
|
|
499
|
+
// The flag answers: "can this route reach bare names at root scope?"
|
|
500
|
+
// It propagates through the include chain:
|
|
501
|
+
//
|
|
502
|
+
// { name: "" } — transparent: inherit parent, default true
|
|
503
|
+
// { name: "foo" } — inherit parent if already set, else create boundary (false)
|
|
504
|
+
// no name — inherit parent unchanged
|
|
505
|
+
//
|
|
506
|
+
// This means { name: "" } + nested { name: "sub" } keeps rootScoped=true
|
|
507
|
+
// (the outer transparent include establishes root access, and the inner
|
|
508
|
+
// named include inherits it). But a direct { name: "sub" } at root gets
|
|
509
|
+
// rootScoped=false (no prior root-access grant, so it creates a boundary).
|
|
510
|
+
const combinedRootScoped =
|
|
511
|
+
namePrefix === ""
|
|
512
|
+
? (store.rootScoped ?? true)
|
|
513
|
+
: namePrefix !== undefined
|
|
514
|
+
? (store.rootScoped ?? false)
|
|
515
|
+
: store.rootScoped;
|
|
401
516
|
|
|
402
517
|
return RSCRouterContext.run(
|
|
403
518
|
{
|
|
404
519
|
...store,
|
|
405
520
|
urlPrefix: combinedUrlPrefix,
|
|
406
521
|
namePrefix: combinedNamePrefix,
|
|
522
|
+
rootScoped: combinedRootScoped,
|
|
407
523
|
},
|
|
408
|
-
callback
|
|
524
|
+
callback,
|
|
409
525
|
);
|
|
410
526
|
}
|
|
411
527
|
|
|
@@ -425,6 +541,15 @@ export function getNamePrefix(): string | undefined {
|
|
|
425
541
|
return store?.namePrefix;
|
|
426
542
|
}
|
|
427
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Get whether the current scope is at root level (no named include boundary above).
|
|
546
|
+
* Returns true at root or inside { name: "" } includes, false inside named includes.
|
|
547
|
+
*/
|
|
548
|
+
export function getRootScoped(): boolean {
|
|
549
|
+
const store = RSCRouterContext.getStore();
|
|
550
|
+
return store?.rootScoped ?? true;
|
|
551
|
+
}
|
|
552
|
+
|
|
428
553
|
// Export HelperContext type for use in other modules
|
|
429
554
|
export type { HelperContext };
|
|
430
555
|
|
|
@@ -443,7 +568,7 @@ export type { HelperContext };
|
|
|
443
568
|
* done(); // Records duration
|
|
444
569
|
* ```
|
|
445
570
|
*/
|
|
446
|
-
export function track(label: string): () => void {
|
|
571
|
+
export function track(label: string, depth?: number): () => void {
|
|
447
572
|
const store = RSCRouterContext.getStore();
|
|
448
573
|
|
|
449
574
|
// No-op if context unavailable or metrics not enabled
|
|
@@ -454,7 +579,13 @@ export function track(label: string): () => void {
|
|
|
454
579
|
const startTime = performance.now() - store.metrics.requestStart;
|
|
455
580
|
|
|
456
581
|
return () => {
|
|
457
|
-
const duration =
|
|
458
|
-
|
|
582
|
+
const duration =
|
|
583
|
+
performance.now() - store.metrics!.requestStart - startTime;
|
|
584
|
+
store.metrics!.metrics.push({
|
|
585
|
+
label,
|
|
586
|
+
duration,
|
|
587
|
+
startTime,
|
|
588
|
+
...(depth != null ? { depth } : {}),
|
|
589
|
+
});
|
|
459
590
|
};
|
|
460
591
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie Store — Next.js-style cookie facade backed by the response-derived model.
|
|
3
|
+
*
|
|
4
|
+
* `cookies()` returns a CookieStore scoped to the current request.
|
|
5
|
+
* Reads merge the original Cookie header with Set-Cookie mutations
|
|
6
|
+
* already queued on the response stub (last-write-wins).
|
|
7
|
+
* Writes append Set-Cookie to the response stub.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CookieOptions } from "../router/middleware-types.js";
|
|
11
|
+
import { getRequestContext } from "./request-context.js";
|
|
12
|
+
import { INSIDE_CACHE_EXEC } from "../cache/taint.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A single cookie entry returned by get() and getAll().
|
|
16
|
+
*/
|
|
17
|
+
export interface Cookie {
|
|
18
|
+
name: string;
|
|
19
|
+
value: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Request-scoped cookie store.
|
|
24
|
+
*
|
|
25
|
+
* Reads see the effective merged view (original request + same-request mutations).
|
|
26
|
+
* Writes append Set-Cookie headers to the shared response stub.
|
|
27
|
+
*/
|
|
28
|
+
export interface CookieStore {
|
|
29
|
+
/** Get a single cookie by name. Returns undefined if not set or deleted. */
|
|
30
|
+
get(name: string): Cookie | undefined;
|
|
31
|
+
|
|
32
|
+
/** Get all effective cookies, or all cookies with a given name. */
|
|
33
|
+
getAll(name?: string): Cookie[];
|
|
34
|
+
|
|
35
|
+
/** Check whether a cookie exists in the effective view. */
|
|
36
|
+
has(name: string): boolean;
|
|
37
|
+
|
|
38
|
+
/** Set a cookie (appends Set-Cookie to the response stub). */
|
|
39
|
+
set(name: string, value: string, options?: CookieOptions): void;
|
|
40
|
+
|
|
41
|
+
/** Delete a cookie (appends Set-Cookie with maxAge=0 to the response stub). */
|
|
42
|
+
delete(name: string, options?: Pick<CookieOptions, "domain" | "path">): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the request-scoped cookie store.
|
|
47
|
+
*
|
|
48
|
+
* Must be called inside a request context (middleware, handler, loader, action).
|
|
49
|
+
* Throws if called outside request scope.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* import { cookies } from "@rangojs/router";
|
|
54
|
+
*
|
|
55
|
+
* // In a handler, loader, or action:
|
|
56
|
+
* const session = cookies().get("session")?.value;
|
|
57
|
+
* cookies().set("session", "new-token", { httpOnly: true });
|
|
58
|
+
* cookies().delete("session");
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function cookies(): CookieStore {
|
|
62
|
+
const ctx = getRequestContext();
|
|
63
|
+
assertNotInsideCacheContext(ctx, "cookies");
|
|
64
|
+
return createCookieStore(ctx);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read-only view of HTTP headers.
|
|
69
|
+
* Exposes only the read methods of the Headers API.
|
|
70
|
+
*/
|
|
71
|
+
export interface ReadonlyHeaders {
|
|
72
|
+
get(name: string): string | null;
|
|
73
|
+
has(name: string): boolean;
|
|
74
|
+
entries(): HeadersIterator<[string, string]>;
|
|
75
|
+
keys(): HeadersIterator<string>;
|
|
76
|
+
values(): HeadersIterator<string>;
|
|
77
|
+
forEach(
|
|
78
|
+
callback: (value: string, name: string, parent: ReadonlyHeaders) => void,
|
|
79
|
+
): void;
|
|
80
|
+
[Symbol.iterator](): HeadersIterator<[string, string]>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Minimal iterator interface (avoids pulling IterableIterator from lib.dom)
|
|
84
|
+
type HeadersIterator<T> = IterableIterator<T>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Throw if called inside a "use cache" function.
|
|
88
|
+
* Reading request-scoped data (cookies, headers) inside a cached function
|
|
89
|
+
* produces results that vary per request but the cache key does not include
|
|
90
|
+
* those values, leading to one user's data being served to another.
|
|
91
|
+
*/
|
|
92
|
+
function assertNotInsideCacheContext(ctx: unknown, fnName: string): void {
|
|
93
|
+
if (
|
|
94
|
+
ctx !== null &&
|
|
95
|
+
ctx !== undefined &&
|
|
96
|
+
typeof ctx === "object" &&
|
|
97
|
+
(INSIDE_CACHE_EXEC as symbol) in (ctx as Record<symbol, unknown>)
|
|
98
|
+
) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`${fnName}() cannot be called inside a "use cache" function. ` +
|
|
101
|
+
`Request-scoped data (cookies, headers) varies per request but is not ` +
|
|
102
|
+
`reflected in the cache key, so cached results would be served to the ` +
|
|
103
|
+
`wrong users. Extract the value before the cached function and pass it ` +
|
|
104
|
+
`as an argument:\n\n` +
|
|
105
|
+
` const locale = cookies().get("locale")?.value ?? "en";\n` +
|
|
106
|
+
` const data = await getCachedData(locale); // locale is now in the cache key`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const HEADERS_MUTATION_METHODS = new Set(["set", "append", "delete"]);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the original request headers (read-only).
|
|
115
|
+
*
|
|
116
|
+
* Must be called inside a request context.
|
|
117
|
+
* Returns a read-only view of the incoming request's headers.
|
|
118
|
+
* Mutation methods (set, append, delete) throw at runtime.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* import { headers } from "@rangojs/router";
|
|
123
|
+
*
|
|
124
|
+
* const auth = headers().get("authorization");
|
|
125
|
+
* const contentType = headers().get("content-type");
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export function headers(): ReadonlyHeaders {
|
|
129
|
+
const ctx = getRequestContext();
|
|
130
|
+
assertNotInsideCacheContext(ctx, "headers");
|
|
131
|
+
return new Proxy(ctx.request.headers, {
|
|
132
|
+
get(target, prop, receiver) {
|
|
133
|
+
if (typeof prop === "string" && HEADERS_MUTATION_METHODS.has(prop)) {
|
|
134
|
+
return () => {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`headers().${prop}() is not allowed. headers() returns a read-only view of request headers. ` +
|
|
137
|
+
`Use ctx.header() to set response headers.`,
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const value = Reflect.get(target, prop, receiver);
|
|
142
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
143
|
+
},
|
|
144
|
+
}) as unknown as ReadonlyHeaders;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Create a CookieStore backed by a RequestContext.
|
|
149
|
+
* @internal Shared between cookies() shorthand and context methods.
|
|
150
|
+
*/
|
|
151
|
+
function createCookieStore(ctx: {
|
|
152
|
+
cookie(name: string): string | undefined;
|
|
153
|
+
cookies(): Record<string, string>;
|
|
154
|
+
setCookie(name: string, value: string, options?: CookieOptions): void;
|
|
155
|
+
deleteCookie(
|
|
156
|
+
name: string,
|
|
157
|
+
options?: Pick<CookieOptions, "domain" | "path">,
|
|
158
|
+
): void;
|
|
159
|
+
}): CookieStore {
|
|
160
|
+
return {
|
|
161
|
+
get(name: string): Cookie | undefined {
|
|
162
|
+
const value = ctx.cookie(name);
|
|
163
|
+
return value !== undefined ? { name, value } : undefined;
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
getAll(name?: string): Cookie[] {
|
|
167
|
+
const all = ctx.cookies();
|
|
168
|
+
if (name !== undefined) {
|
|
169
|
+
const value = all[name];
|
|
170
|
+
return value !== undefined ? [{ name, value }] : [];
|
|
171
|
+
}
|
|
172
|
+
return Object.entries(all).map(([n, v]) => ({ name: n, value: v }));
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
has(name: string): boolean {
|
|
176
|
+
return ctx.cookie(name) !== undefined;
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
set(name: string, value: string, options?: CookieOptions): void {
|
|
180
|
+
ctx.setCookie(name, value, options);
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
delete(
|
|
184
|
+
name: string,
|
|
185
|
+
options?: Pick<CookieOptions, "domain" | "path">,
|
|
186
|
+
): void {
|
|
187
|
+
ctx.deleteCookie(name, options);
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetchable loader store - internal registry for fetchable loader functions.
|
|
3
|
+
*
|
|
4
|
+
* Extracted into its own module to avoid circular dependencies between
|
|
5
|
+
* loader.rsc.ts and request-context.ts. This module has no imports from
|
|
6
|
+
* either, so both can safely import from here.
|
|
7
|
+
*
|
|
8
|
+
* Populated by createLoader() in loader.rsc.ts.
|
|
9
|
+
* Read by request-context.ts (for ctx.use()) and loader-registry.ts (for GET-based fetching).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { LoaderFn } from "../types.js";
|
|
13
|
+
import type { MiddlewareFn } from "../router/middleware.js";
|
|
14
|
+
|
|
15
|
+
export interface LoaderRegistryEntry {
|
|
16
|
+
fn: LoaderFn<any, any, any>;
|
|
17
|
+
middleware: MiddlewareFn[];
|
|
18
|
+
/** Whether this loader is fetchable via the _rsc_loader endpoint. */
|
|
19
|
+
fetchable: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const fetchableLoaderRegistry = new Map<string, LoaderRegistryEntry>();
|
|
23
|
+
|
|
24
|
+
export function registerFetchableLoader(
|
|
25
|
+
id: string,
|
|
26
|
+
fn: LoaderFn<any, any, any>,
|
|
27
|
+
middleware: MiddlewareFn[],
|
|
28
|
+
fetchable: boolean,
|
|
29
|
+
): void {
|
|
30
|
+
fetchableLoaderRegistry.set(id, { fn, middleware, fetchable });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getFetchableLoader(
|
|
34
|
+
id: string,
|
|
35
|
+
): LoaderRegistryEntry | undefined {
|
|
36
|
+
return fetchableLoaderRegistry.get(id);
|
|
37
|
+
}
|