@rangojs/router 0.0.0-experimental.259 → 0.0.0-experimental.26
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/README.md +294 -28
- package/dist/bin/rango.js +355 -47
- package/dist/vite/index.js +1658 -1239
- package/package.json +3 -3
- package/skills/cache-guide/SKILL.md +9 -5
- package/skills/caching/SKILL.md +4 -4
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/hooks/SKILL.md +40 -29
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +79 -0
- package/skills/layout/SKILL.md +62 -2
- package/skills/loader/SKILL.md +229 -15
- package/skills/middleware/SKILL.md +109 -30
- package/skills/parallel/SKILL.md +57 -2
- package/skills/prerender/SKILL.md +189 -19
- package/skills/rango/SKILL.md +1 -2
- package/skills/response-routes/SKILL.md +3 -3
- package/skills/route/SKILL.md +44 -3
- package/skills/router-setup/SKILL.md +80 -3
- package/skills/theme/SKILL.md +5 -4
- package/skills/typesafety/SKILL.md +59 -16
- package/skills/use-cache/SKILL.md +16 -2
- package/src/__internal.ts +1 -1
- package/src/bin/rango.ts +56 -19
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/event-controller.ts +29 -48
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +1 -1
- package/src/browser/link-interceptor.ts +19 -3
- package/src/browser/merge-segment-loaders.ts +9 -2
- package/src/browser/navigation-bridge.ts +66 -443
- package/src/browser/navigation-client.ts +34 -62
- package/src/browser/navigation-store.ts +4 -33
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/partial-update.ts +103 -151
- package/src/browser/prefetch/cache.ts +67 -0
- package/src/browser/prefetch/fetch.ts +137 -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 +154 -44
- package/src/browser/react/NavigationProvider.tsx +32 -0
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +2 -6
- package/src/browser/react/location-state-shared.ts +29 -11
- package/src/browser/react/location-state.ts +6 -4
- 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 +23 -45
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +21 -64
- package/src/browser/react/use-navigation.ts +7 -32
- package/src/browser/react/use-params.ts +5 -34
- package/src/browser/react/use-pathname.ts +2 -3
- package/src/browser/react/use-router.ts +3 -6
- package/src/browser/react/use-search-params.ts +2 -1
- package/src/browser/react/use-segments.ts +75 -114
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +46 -22
- package/src/browser/scroll-restoration.ts +10 -7
- package/src/browser/server-action-bridge.ts +458 -405
- package/src/browser/types.ts +21 -35
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +38 -13
- package/src/build/generate-route-types.ts +4 -0
- package/src/build/index.ts +1 -0
- package/src/build/route-trie.ts +19 -3
- package/src/build/route-types/codegen.ts +13 -4
- package/src/build/route-types/include-resolution.ts +13 -0
- package/src/build/route-types/per-module-writer.ts +15 -3
- package/src/build/route-types/router-processing.ts +170 -18
- package/src/build/runtime-discovery.ts +13 -1
- 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 +136 -123
- package/src/cache/cache-scope.ts +76 -83
- package/src/cache/cf/cf-cache-store.ts +12 -7
- package/src/cache/document-cache.ts +93 -69
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +43 -69
- package/src/cache/profile-registry.ts +43 -8
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +140 -117
- package/src/cache/taint.ts +30 -3
- package/src/cache/types.ts +1 -115
- package/src/client.rsc.tsx +0 -1
- package/src/client.tsx +53 -76
- package/src/errors.ts +6 -1
- package/src/handle.ts +1 -1
- package/src/handles/MetaTags.tsx +5 -2
- package/src/host/cookie-handler.ts +8 -3
- package/src/host/index.ts +0 -3
- package/src/host/router.ts +14 -1
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +53 -10
- package/src/index.ts +73 -43
- package/src/loader.rsc.ts +12 -4
- package/src/loader.ts +8 -0
- package/src/prerender/store.ts +60 -18
- package/src/prerender.ts +76 -18
- package/src/reverse.ts +11 -7
- package/src/root-error-boundary.tsx +30 -26
- package/src/route-definition/dsl-helpers.ts +9 -6
- package/src/route-definition/index.ts +0 -3
- package/src/route-definition/redirect.ts +15 -3
- package/src/route-map-builder.ts +38 -2
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +7 -0
- package/src/router/content-negotiation.ts +1 -1
- package/src/router/debug-manifest.ts +16 -3
- package/src/router/handler-context.ts +96 -17
- package/src/router/intercept-resolution.ts +6 -4
- package/src/router/lazy-includes.ts +4 -0
- package/src/router/loader-resolution.ts +6 -11
- package/src/router/logging.ts +100 -3
- package/src/router/manifest.ts +32 -3
- package/src/router/match-api.ts +62 -54
- package/src/router/match-context.ts +3 -0
- package/src/router/match-handlers.ts +185 -11
- package/src/router/match-middleware/background-revalidation.ts +65 -85
- package/src/router/match-middleware/cache-lookup.ts +78 -10
- package/src/router/match-middleware/cache-store.ts +2 -0
- package/src/router/match-pipelines.ts +8 -43
- package/src/router/match-result.ts +0 -9
- package/src/router/metrics.ts +233 -13
- package/src/router/middleware-types.ts +34 -39
- package/src/router/middleware.ts +290 -130
- package/src/router/pattern-matching.ts +61 -10
- package/src/router/prerender-match.ts +36 -6
- package/src/router/preview-match.ts +7 -1
- package/src/router/revalidation.ts +61 -2
- package/src/router/router-context.ts +15 -0
- package/src/router/router-interfaces.ts +158 -40
- package/src/router/router-options.ts +223 -1
- package/src/router/router-registry.ts +5 -2
- package/src/router/segment-resolution/fresh.ts +165 -242
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +102 -98
- package/src/router/segment-resolution/revalidation.ts +394 -272
- package/src/router/segment-resolution/static-store.ts +2 -2
- package/src/router/segment-resolution.ts +1 -3
- package/src/router/segment-wrappers.ts +3 -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 +20 -2
- package/src/router/types.ts +7 -1
- package/src/router.ts +203 -18
- package/src/rsc/handler-context.ts +13 -2
- package/src/rsc/handler.ts +489 -438
- package/src/rsc/helpers.ts +125 -5
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +84 -42
- package/src/rsc/manifest-init.ts +3 -2
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +245 -19
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +47 -43
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +166 -66
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +20 -2
- package/src/search-params.ts +38 -23
- package/src/server/context.ts +61 -7
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +84 -12
- package/src/server/loader-registry.ts +11 -46
- package/src/server/request-context.ts +275 -49
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +67 -28
- package/src/static-handler.ts +7 -0
- package/src/theme/ThemeProvider.tsx +6 -1
- package/src/theme/index.ts +4 -18
- package/src/theme/theme-context.ts +1 -28
- package/src/theme/theme-script.ts +2 -1
- package/src/types/cache-types.ts +6 -1
- package/src/types/error-types.ts +3 -0
- package/src/types/global-namespace.ts +22 -0
- package/src/types/handler-context.ts +103 -16
- package/src/types/index.ts +1 -1
- package/src/types/loader-types.ts +9 -6
- package/src/types/route-config.ts +17 -26
- package/src/types/route-entry.ts +28 -0
- package/src/types/segments.ts +0 -5
- package/src/urls/include-helper.ts +49 -8
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +30 -12
- package/src/urls/path-helper.ts +17 -2
- package/src/urls/pattern-types.ts +21 -1
- package/src/urls/response-types.ts +29 -7
- package/src/urls/type-extraction.ts +23 -15
- package/src/use-loader.tsx +27 -9
- package/src/vite/discovery/bundle-postprocess.ts +32 -52
- package/src/vite/discovery/discover-routers.ts +52 -26
- package/src/vite/discovery/prerender-collection.ts +58 -41
- package/src/vite/discovery/route-types-writer.ts +7 -7
- package/src/vite/discovery/state.ts +7 -7
- package/src/vite/discovery/virtual-module-codegen.ts +5 -2
- package/src/vite/index.ts +10 -51
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +3 -3
- package/src/vite/plugins/expose-internal-ids.ts +4 -3
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +91 -3
- package/src/vite/plugins/version-plugin.ts +188 -18
- package/src/vite/rango.ts +61 -36
- package/src/vite/router-discovery.ts +173 -100
- package/src/vite/utils/prerender-utils.ts +81 -0
- package/src/vite/utils/shared-utils.ts +19 -9
- package/skills/testing/SKILL.md +0 -226
- package/src/browser/lru-cache.ts +0 -61
- package/src/browser/react/prefetch.ts +0 -27
- 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/route-definition/route-function.ts +0 -119
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- /package/{CLAUDE.md → AGENTS.md} +0 -0
package/src/ssr/index.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { renderSegments } from "../segment-system.js";
|
|
3
|
-
import {
|
|
4
|
-
import { initSegmentsSync } from "../browser/react/use-segments.js";
|
|
5
|
-
import { initThemeConfigSync } from "../theme/theme-context.js";
|
|
3
|
+
import { filterSegmentOrder } from "../browser/react/filter-segment-order.js";
|
|
6
4
|
import { ThemeProvider } from "../theme/ThemeProvider.js";
|
|
5
|
+
import { NonceContext } from "../browser/react/nonce-context.js";
|
|
7
6
|
import { NavigationStoreContext } from "../browser/react/context.js";
|
|
8
7
|
import type { NavigationStoreContextValue } from "../browser/react/context.js";
|
|
9
8
|
import type { HandleData } from "../browser/types.js";
|
|
@@ -34,6 +33,13 @@ interface RenderToReadableStreamOptions {
|
|
|
34
33
|
formState?: unknown;
|
|
35
34
|
}
|
|
36
35
|
|
|
36
|
+
/**
|
|
37
|
+
* ReadableStream with the allReady promise added by react-dom/server.edge.
|
|
38
|
+
*/
|
|
39
|
+
interface ReactDOMReadableStream extends ReadableStream<Uint8Array> {
|
|
40
|
+
allReady: Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
/**
|
|
38
44
|
* Options for the renderHTML function
|
|
39
45
|
*/
|
|
@@ -50,6 +56,14 @@ export interface SSRRenderOptions {
|
|
|
50
56
|
* Nonce for Content Security Policy (CSP)
|
|
51
57
|
*/
|
|
52
58
|
nonce?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* SSR stream mode.
|
|
62
|
+
*
|
|
63
|
+
* - `"stream"` (default) — start flushing HTML immediately.
|
|
64
|
+
* - `"allReady"` — await `stream.allReady` before returning.
|
|
65
|
+
*/
|
|
66
|
+
streamMode?: import("../router/router-options.js").SSRStreamMode;
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
/**
|
|
@@ -69,7 +83,7 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
69
83
|
renderToReadableStream: (
|
|
70
84
|
element: React.ReactNode,
|
|
71
85
|
options?: RenderToReadableStreamOptions,
|
|
72
|
-
) => Promise<
|
|
86
|
+
) => Promise<ReactDOMReadableStream>;
|
|
73
87
|
|
|
74
88
|
/**
|
|
75
89
|
* injectRSCPayload from rsc-html-stream/server
|
|
@@ -117,6 +131,7 @@ interface RscPayload {
|
|
|
117
131
|
params?: Record<string, string>;
|
|
118
132
|
themeConfig?: ResolvedThemeConfig | null;
|
|
119
133
|
initialTheme?: Theme;
|
|
134
|
+
version?: string;
|
|
120
135
|
};
|
|
121
136
|
}
|
|
122
137
|
|
|
@@ -138,8 +153,18 @@ async function consumeAsyncGenerator(
|
|
|
138
153
|
* Create a minimal event controller for SSR.
|
|
139
154
|
* This provides the correct pathname so useNavigation returns the right value during SSR.
|
|
140
155
|
*/
|
|
141
|
-
function createSsrEventController(
|
|
142
|
-
|
|
156
|
+
function createSsrEventController(opts: {
|
|
157
|
+
pathname: string;
|
|
158
|
+
params?: Record<string, string>;
|
|
159
|
+
handleData?: HandleData;
|
|
160
|
+
matched?: string[];
|
|
161
|
+
}): EventController {
|
|
162
|
+
const location = new URL(opts.pathname, "http://localhost");
|
|
163
|
+
let params = opts.params ?? {};
|
|
164
|
+
const handleState = {
|
|
165
|
+
data: opts.handleData ?? {},
|
|
166
|
+
segmentOrder: filterSegmentOrder(opts.matched ?? []),
|
|
167
|
+
};
|
|
143
168
|
const state: DerivedNavigationState = {
|
|
144
169
|
state: "idle",
|
|
145
170
|
isStreaming: false,
|
|
@@ -150,6 +175,7 @@ function createSsrEventController(pathname: string): EventController {
|
|
|
150
175
|
|
|
151
176
|
return {
|
|
152
177
|
getState: () => state,
|
|
178
|
+
getLocation: () => location,
|
|
153
179
|
subscribe: () => () => {},
|
|
154
180
|
getActionState: () => ({
|
|
155
181
|
state: "idle",
|
|
@@ -161,9 +187,11 @@ function createSsrEventController(pathname: string): EventController {
|
|
|
161
187
|
subscribeToAction: () => () => {},
|
|
162
188
|
subscribeToHandles: () => () => {},
|
|
163
189
|
setHandleData: () => {},
|
|
164
|
-
getHandleState: () =>
|
|
165
|
-
setParams: () => {
|
|
166
|
-
|
|
190
|
+
getHandleState: () => handleState,
|
|
191
|
+
setParams: (nextParams) => {
|
|
192
|
+
params = nextParams;
|
|
193
|
+
},
|
|
194
|
+
getParams: () => params,
|
|
167
195
|
setLocation: () => {},
|
|
168
196
|
startNavigation: () => {
|
|
169
197
|
throw new Error("Navigation not supported during SSR");
|
|
@@ -175,6 +203,7 @@ function createSsrEventController(pathname: string): EventController {
|
|
|
175
203
|
abortAllActions: () => {},
|
|
176
204
|
getCurrentNavigation: () => null,
|
|
177
205
|
getInflightActions: () => new Map(),
|
|
206
|
+
hadAnyConcurrentActions: () => false,
|
|
178
207
|
};
|
|
179
208
|
}
|
|
180
209
|
|
|
@@ -216,7 +245,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
216
245
|
rscStream: ReadableStream<Uint8Array>,
|
|
217
246
|
options?: SSRRenderOptions,
|
|
218
247
|
): Promise<ReadableStream<Uint8Array>> {
|
|
219
|
-
const { nonce, formState } = options ?? {};
|
|
248
|
+
const { nonce, formState, streamMode } = options ?? {};
|
|
220
249
|
|
|
221
250
|
try {
|
|
222
251
|
// Tee the stream:
|
|
@@ -231,35 +260,31 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
231
260
|
function SsrRoot() {
|
|
232
261
|
payload ??= createFromReadableStream<RscPayload>(rscStream1);
|
|
233
262
|
const resolved = React.use(payload);
|
|
234
|
-
|
|
235
|
-
// Initialize segments state before children render (for useSegments hook)
|
|
236
|
-
initSegmentsSync(
|
|
237
|
-
resolved.metadata?.matched,
|
|
238
|
-
resolved.metadata?.pathname,
|
|
239
|
-
resolved.metadata?.params,
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
// Initialize theme config for MetaTags to render theme script
|
|
243
263
|
const themeConfig = resolved.metadata?.themeConfig ?? null;
|
|
244
|
-
|
|
264
|
+
const pathname = resolved.metadata?.pathname ?? "/";
|
|
245
265
|
|
|
246
|
-
// Await handles
|
|
266
|
+
// Await handles before creating SSR event controller so hooks can
|
|
267
|
+
// read request-local handle data via NavigationStoreContext.
|
|
247
268
|
// The handles property is an async generator that yields on each push
|
|
248
269
|
// Memoize the promise since async generators can only be iterated once
|
|
270
|
+
let handleData: HandleData = {};
|
|
249
271
|
if (resolved.metadata?.handles) {
|
|
250
272
|
handlesPromise ??= consumeAsyncGenerator(resolved.metadata.handles);
|
|
251
|
-
|
|
252
|
-
initHandleDataSync(handleData, resolved.metadata.matched);
|
|
273
|
+
handleData = React.use(handlesPromise);
|
|
253
274
|
}
|
|
254
275
|
|
|
255
|
-
// Create SSR context with
|
|
276
|
+
// Create SSR context with request-local pathname/params/handles.
|
|
256
277
|
ssrContextValue ??= {
|
|
257
278
|
store: null as any,
|
|
258
|
-
eventController: createSsrEventController(
|
|
259
|
-
|
|
260
|
-
|
|
279
|
+
eventController: createSsrEventController({
|
|
280
|
+
pathname,
|
|
281
|
+
params: resolved.metadata?.params,
|
|
282
|
+
handleData,
|
|
283
|
+
matched: resolved.metadata?.matched,
|
|
284
|
+
}),
|
|
261
285
|
navigate: async () => {},
|
|
262
286
|
refresh: async () => {},
|
|
287
|
+
version: resolved.metadata?.version,
|
|
263
288
|
};
|
|
264
289
|
|
|
265
290
|
// Build content tree from segments.
|
|
@@ -287,9 +312,16 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
287
312
|
);
|
|
288
313
|
}
|
|
289
314
|
|
|
315
|
+
// Wrap with NonceContext so client components (e.g. MetaTags) can
|
|
316
|
+
// apply CSP nonces to inline scripts during SSR. Always present to
|
|
317
|
+
// match the browser-side NavigationProvider tree shape for hydration.
|
|
318
|
+
content = (
|
|
319
|
+
<NonceContext.Provider value={nonce}>{content}</NonceContext.Provider>
|
|
320
|
+
);
|
|
321
|
+
|
|
290
322
|
// Wrap with NavigationStoreContext for useNavigation hook
|
|
291
323
|
return (
|
|
292
|
-
<NavigationStoreContext.Provider value={ssrContextValue}>
|
|
324
|
+
<NavigationStoreContext.Provider value={ssrContextValue!}>
|
|
293
325
|
{content}
|
|
294
326
|
</NavigationStoreContext.Provider>
|
|
295
327
|
);
|
|
@@ -307,6 +339,13 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
307
339
|
nonce,
|
|
308
340
|
});
|
|
309
341
|
|
|
342
|
+
// Wait for all Suspense boundaries to resolve when streamMode is "allReady".
|
|
343
|
+
// This buffers the entire HTML before flushing — used for bots that
|
|
344
|
+
// cannot process streamed HTML.
|
|
345
|
+
if (streamMode === "allReady") {
|
|
346
|
+
await htmlStream.allReady;
|
|
347
|
+
}
|
|
348
|
+
|
|
310
349
|
// Inject RSC payload into HTML as <script nonce="...">__FLIGHT_DATA__</script>
|
|
311
350
|
return htmlStream.pipeThrough(injectRSCPayload(rscStream2, { nonce }));
|
|
312
351
|
} catch (error) {
|
package/src/static-handler.ts
CHANGED
|
@@ -82,6 +82,13 @@ export function Static<TParams extends Record<string, any>>(
|
|
|
82
82
|
id = maybeId ?? "";
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
if (!id) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"[rsc-router] Static: missing $$id. " +
|
|
88
|
+
"Ensure the exposeInternalIds Vite plugin is configured.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
85
92
|
return {
|
|
86
93
|
__brand: "staticHandler" as const,
|
|
87
94
|
$$id: id,
|
|
@@ -50,7 +50,12 @@ function readThemeFromCookie(storageKey: string): string | null {
|
|
|
50
50
|
for (const cookie of cookies) {
|
|
51
51
|
const [name, ...rest] = cookie.trim().split("=");
|
|
52
52
|
if (name === storageKey) {
|
|
53
|
-
|
|
53
|
+
const raw = rest.join("=");
|
|
54
|
+
try {
|
|
55
|
+
return decodeURIComponent(raw);
|
|
56
|
+
} catch {
|
|
57
|
+
return raw;
|
|
58
|
+
}
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
return null;
|
package/src/theme/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme module exports for @rangojs/router/theme
|
|
3
3
|
*
|
|
4
|
-
* This module provides
|
|
4
|
+
* This module provides the public theme API:
|
|
5
5
|
* - useTheme: Hook for accessing theme state in client components
|
|
6
6
|
* - ThemeProvider: Component for manual theme provider setup (typically not needed)
|
|
7
|
+
* - ThemeScript: FOUC-prevention script component for document/head usage
|
|
7
8
|
* - Types for theme configuration
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
@@ -43,20 +44,5 @@ export type {
|
|
|
43
44
|
ThemeContextValue,
|
|
44
45
|
} from "./types.js";
|
|
45
46
|
|
|
46
|
-
// Constants
|
|
47
|
-
export {
|
|
48
|
-
THEME_DEFAULTS,
|
|
49
|
-
THEME_COOKIE,
|
|
50
|
-
resolveThemeConfig,
|
|
51
|
-
} from "./constants.js";
|
|
52
|
-
|
|
53
|
-
// Script generation (for advanced SSR use cases)
|
|
54
|
-
export { generateThemeScript, getNonceAttribute } from "./theme-script.js";
|
|
55
|
-
|
|
56
|
-
// Context (for advanced use cases)
|
|
57
|
-
export {
|
|
58
|
-
ThemeContext,
|
|
59
|
-
useThemeContext,
|
|
60
|
-
initThemeConfigSync,
|
|
61
|
-
getSSRThemeConfig,
|
|
62
|
-
} from "./theme-context.js";
|
|
47
|
+
// Constants
|
|
48
|
+
export { THEME_DEFAULTS, THEME_COOKIE } from "./constants.js";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createContext, useContext, type Context } from "react";
|
|
13
|
-
import type {
|
|
13
|
+
import type { ThemeContextValue } from "./types.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* React context for theme state
|
|
@@ -19,33 +19,6 @@ import type { ResolvedThemeConfig, ThemeContextValue } from "./types.js";
|
|
|
19
19
|
export const ThemeContext: Context<ThemeContextValue | null> =
|
|
20
20
|
createContext<ThemeContextValue | null>(null);
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
* SSR module-level state for theme config.
|
|
24
|
-
* Populated by initThemeConfigSync before React renders.
|
|
25
|
-
* Used by MetaTags during SSR to render the theme script.
|
|
26
|
-
*/
|
|
27
|
-
let ssrThemeConfig: ResolvedThemeConfig | null = null;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Initialize theme config synchronously for SSR.
|
|
31
|
-
* Called before rendering to populate state for MetaTags.
|
|
32
|
-
*
|
|
33
|
-
* @param config - Theme config from router, or null if theme is disabled
|
|
34
|
-
*/
|
|
35
|
-
export function initThemeConfigSync(config: ResolvedThemeConfig | null): void {
|
|
36
|
-
ssrThemeConfig = config;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get theme config for SSR/hydration.
|
|
41
|
-
* Used by MetaTags to render the theme script.
|
|
42
|
-
*
|
|
43
|
-
* @returns Theme config if available, null otherwise
|
|
44
|
-
*/
|
|
45
|
-
export function getSSRThemeConfig(): ResolvedThemeConfig | null {
|
|
46
|
-
return ssrThemeConfig;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
22
|
/**
|
|
50
23
|
* Get theme context (internal use)
|
|
51
24
|
* Returns null if theme is not enabled
|
|
@@ -40,7 +40,8 @@ export function generateThemeScript(config: ResolvedThemeConfig): string {
|
|
|
40
40
|
for (var i = 0; i < cookies.length; i++) {
|
|
41
41
|
var cookie = cookies[i].trim();
|
|
42
42
|
if (cookie.indexOf(storageKey + '=') === 0) {
|
|
43
|
-
return decodeURIComponent(cookie.substring(storageKey.length + 1));
|
|
43
|
+
try { return decodeURIComponent(cookie.substring(storageKey.length + 1)); }
|
|
44
|
+
catch (e) { return cookie.substring(storageKey.length + 1); }
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
// Fall back to localStorage
|
package/src/types/cache-types.ts
CHANGED
|
@@ -134,7 +134,7 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
134
134
|
* key: (ctx) => `${ctx.env.REGION}:product:${ctx.params.id}`
|
|
135
135
|
*
|
|
136
136
|
* // Include cookies
|
|
137
|
-
* key: (ctx) => `${
|
|
137
|
+
* key: (ctx) => `${cookies().get('locale')?.value ?? 'en'}:${ctx.pathname}`
|
|
138
138
|
* ```
|
|
139
139
|
*/
|
|
140
140
|
key?: (
|
|
@@ -145,6 +145,11 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
145
145
|
* Tags for cache invalidation.
|
|
146
146
|
* Can be a static array or a function that returns tags.
|
|
147
147
|
*
|
|
148
|
+
* Note: Tags are passed through to the store but built-in stores
|
|
149
|
+
* (MemorySegmentCacheStore, CFCacheStore) do not yet index or
|
|
150
|
+
* invalidate by tag. Effective tag-based invalidation requires a
|
|
151
|
+
* custom store implementation with secondary indices.
|
|
152
|
+
*
|
|
148
153
|
* @example
|
|
149
154
|
* ```typescript
|
|
150
155
|
* // Static tags
|
package/src/types/error-types.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - "rendering": Invoked during SSR rendering errors (ssr/index.tsx, separate callback)
|
|
11
11
|
* - "action": Invoked when server action execution fails (rsc/handler.ts, router.ts)
|
|
12
12
|
* - "revalidation": Invoked when revalidation fails (router.ts, conditional with action)
|
|
13
|
+
* - "origin": Invoked when cross-origin request validation rejects a request (rsc/handler.ts)
|
|
13
14
|
* - "unknown": Fallback for unclassified errors (not currently invoked)
|
|
14
15
|
*/
|
|
15
16
|
export type ErrorPhase =
|
|
@@ -21,8 +22,10 @@ export type ErrorPhase =
|
|
|
21
22
|
| "rendering" // During RSC/SSR rendering (SSR handler uses separate callback)
|
|
22
23
|
| "action" // During server action execution
|
|
23
24
|
| "revalidation" // During revalidation evaluation
|
|
25
|
+
| "cache" // During "use cache" background operations (stale revalidation, async cache writes)
|
|
24
26
|
| "prerender" // During build-time pre-rendering (Vite closeBundle)
|
|
25
27
|
| "static" // During build-time static handler rendering (Vite closeBundle)
|
|
28
|
+
| "origin" // During cross-origin request validation (CSRF protection)
|
|
26
29
|
| "unknown"; // Fallback for unclassified errors
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -52,6 +52,19 @@ export type GetRegisteredRoutes = keyof RSCRouter.RegisteredRoutes extends never
|
|
|
52
52
|
? Record<string, string>
|
|
53
53
|
: RSCRouter.RegisteredRoutes;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Default route map for reverse() surfaces.
|
|
57
|
+
* Prefers GeneratedRouteMap to avoid router.tsx -> urls.tsx -> types -> router.tsx
|
|
58
|
+
* cycles, but falls back to RegisteredRoutes for manual augmentation and then to
|
|
59
|
+
* a permissive record when no route types are available.
|
|
60
|
+
*/
|
|
61
|
+
export type DefaultReverseRouteMap =
|
|
62
|
+
keyof RSCRouter.GeneratedRouteMap extends never
|
|
63
|
+
? keyof RSCRouter.RegisteredRoutes extends never
|
|
64
|
+
? Record<string, string>
|
|
65
|
+
: RSCRouter.RegisteredRoutes
|
|
66
|
+
: RSCRouter.GeneratedRouteMap;
|
|
67
|
+
|
|
55
68
|
/**
|
|
56
69
|
* Default route map for Handler type.
|
|
57
70
|
* Uses GeneratedRouteMap (from gen file) instead of RegisteredRoutes to avoid
|
|
@@ -76,3 +89,12 @@ export type DefaultEnv = keyof RSCRouter.Env extends never
|
|
|
76
89
|
export type DefaultVars = keyof RSCRouter.Vars extends never
|
|
77
90
|
? Record<string, any>
|
|
78
91
|
: RSCRouter.Vars;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Default route name type for public `routeName` on contexts.
|
|
95
|
+
* When GeneratedRouteMap is augmented, narrows to the known route names.
|
|
96
|
+
* Otherwise falls back to `string` for untyped usage.
|
|
97
|
+
*/
|
|
98
|
+
export type DefaultRouteName = keyof RSCRouter.GeneratedRouteMap extends never
|
|
99
|
+
? string
|
|
100
|
+
: keyof RSCRouter.GeneratedRouteMap & string;
|
|
@@ -9,8 +9,9 @@ import type { LocationStateEntry } from "../browser/react/location-state-shared.
|
|
|
9
9
|
import type {
|
|
10
10
|
DefaultEnv,
|
|
11
11
|
DefaultHandlerRouteMap,
|
|
12
|
+
DefaultReverseRouteMap,
|
|
13
|
+
DefaultRouteName,
|
|
12
14
|
DefaultVars,
|
|
13
|
-
GetRegisteredRoutes,
|
|
14
15
|
} from "./global-namespace.js";
|
|
15
16
|
import type {
|
|
16
17
|
ExtractParams,
|
|
@@ -67,6 +68,65 @@ type ExtractSearchFromEntry<TMap, TKey> = TKey extends keyof TMap
|
|
|
67
68
|
: {}
|
|
68
69
|
: {};
|
|
69
70
|
|
|
71
|
+
type IsEmptyObject<T> = keyof T extends never ? true : false;
|
|
72
|
+
|
|
73
|
+
type AutofillParamsFromEntry<TEntry> = TEntry extends string
|
|
74
|
+
? string extends TEntry
|
|
75
|
+
? Record<string, string>
|
|
76
|
+
: Partial<ExtractParams<TEntry>>
|
|
77
|
+
: TEntry extends { readonly path: infer P extends string }
|
|
78
|
+
? string extends P
|
|
79
|
+
? Record<string, string>
|
|
80
|
+
: Partial<ExtractParams<P>>
|
|
81
|
+
: Record<string, string>;
|
|
82
|
+
|
|
83
|
+
type AutofillSearchFromEntry<TMap, TKey> = TKey extends keyof TMap
|
|
84
|
+
? TMap[TKey] extends { readonly search: infer S extends SearchSchema }
|
|
85
|
+
? ResolveSearchSchema<S>
|
|
86
|
+
: Record<string, unknown>
|
|
87
|
+
: Record<string, unknown>;
|
|
88
|
+
|
|
89
|
+
type AutofillAwareReverseFunction<TLocalRoutes, TGlobalRoutes> =
|
|
90
|
+
ScopedReverseFunction<TLocalRoutes, TGlobalRoutes> & {
|
|
91
|
+
<TName extends keyof TGlobalRoutes & string>(
|
|
92
|
+
name: TName,
|
|
93
|
+
params?: AutofillParamsFromEntry<TGlobalRoutes[TName]>,
|
|
94
|
+
search?: AutofillSearchFromEntry<TGlobalRoutes, TName>,
|
|
95
|
+
): string;
|
|
96
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
97
|
+
name: `.${TName}`,
|
|
98
|
+
params?: AutofillParamsFromEntry<TLocalRoutes[TName]>,
|
|
99
|
+
search?: AutofillSearchFromEntry<TLocalRoutes, TName>,
|
|
100
|
+
): string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
type StrictLocalParamsWithExtras<TEntry> =
|
|
104
|
+
IsEmptyObject<ExtractParamsFromEntry<TEntry, {}>> extends true
|
|
105
|
+
? Record<string, string>
|
|
106
|
+
: ExtractParamsFromEntry<TEntry, {}> & Record<string, string>;
|
|
107
|
+
|
|
108
|
+
// HandlerContext.reverse is the only reverse surface with runtime param autofill
|
|
109
|
+
// from the current matched request. Middleware/loaders/request context do not
|
|
110
|
+
// have the same local-route guarantees, so they keep plain ScopedReverseFunction.
|
|
111
|
+
//
|
|
112
|
+
// When a handler has an explicit local route map, enforce that local route
|
|
113
|
+
// params declared by that map are present while still allowing extra mount
|
|
114
|
+
// params to be passed through. Global names remain autofill-friendly because
|
|
115
|
+
// parent include() params are often unknown at the module definition site.
|
|
116
|
+
type StrictLocalAutofillGlobalReverseFunction<TLocalRoutes, TGlobalRoutes> =
|
|
117
|
+
ScopedReverseFunction<TLocalRoutes, TGlobalRoutes> & {
|
|
118
|
+
<TName extends keyof TGlobalRoutes & string>(
|
|
119
|
+
name: TName,
|
|
120
|
+
params?: AutofillParamsFromEntry<TGlobalRoutes[TName]>,
|
|
121
|
+
search?: AutofillSearchFromEntry<TGlobalRoutes, TName>,
|
|
122
|
+
): string;
|
|
123
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
124
|
+
name: `.${TName}`,
|
|
125
|
+
params: StrictLocalParamsWithExtras<TLocalRoutes[TName]>,
|
|
126
|
+
search?: AutofillSearchFromEntry<TLocalRoutes, TName>,
|
|
127
|
+
): string;
|
|
128
|
+
};
|
|
129
|
+
|
|
70
130
|
export type Handler<
|
|
71
131
|
T extends
|
|
72
132
|
| keyof DefaultHandlerRouteMap
|
|
@@ -107,14 +167,12 @@ export type Handler<
|
|
|
107
167
|
*
|
|
108
168
|
* Provides type-safe access to:
|
|
109
169
|
* - Route params (from URL pattern)
|
|
110
|
-
* -
|
|
170
|
+
* - Cleaned route URL (`url`, `searchParams`, `pathname` — no `_rsc*` params)
|
|
171
|
+
* - Original request (`request` — raw transport URL, headers, method, body)
|
|
111
172
|
* - Platform bindings (env.DB, env.KV, env.SECRETS)
|
|
112
173
|
* - Middleware variables (var.user, var.permissions)
|
|
113
174
|
* - Getter/setter for variables (get('user'), set('user', ...))
|
|
114
175
|
*
|
|
115
|
-
* **Note:** System parameters (query params starting with `_rsc`) are automatically
|
|
116
|
-
* filtered from `url`, `searchParams`, and `request.url` for cleaner access.
|
|
117
|
-
*
|
|
118
176
|
* @example
|
|
119
177
|
* ```typescript
|
|
120
178
|
* const handler = (ctx: HandlerContext<{ slug: string }, AppEnv>) => {
|
|
@@ -125,6 +183,7 @@ export type Handler<
|
|
|
125
183
|
* ctx.set('user', {...}) // Setter
|
|
126
184
|
* ctx.url // Clean URL (no _rsc* params)
|
|
127
185
|
* ctx.searchParams // Clean params (no _rsc* params)
|
|
186
|
+
* ctx.request // Raw transport request (original URL intact)
|
|
128
187
|
* }
|
|
129
188
|
* ```
|
|
130
189
|
*/
|
|
@@ -143,13 +202,15 @@ export type HandlerContext<
|
|
|
143
202
|
readonly _paramCheck?: (params: TParams) => TParams;
|
|
144
203
|
/**
|
|
145
204
|
* True during build-time pre-rendering, false at runtime.
|
|
146
|
-
*
|
|
147
|
-
*
|
|
205
|
+
* Build-time collection and dev on-demand prerender use `true`.
|
|
206
|
+
* Live request rendering, including passthrough fallback, uses `false`.
|
|
148
207
|
*/
|
|
149
208
|
build: boolean;
|
|
150
209
|
/**
|
|
151
|
-
* The incoming Request object.
|
|
152
|
-
*
|
|
210
|
+
* The original incoming Request object (transport URL intact).
|
|
211
|
+
* Use `ctx.url` / `ctx.searchParams` for application logic — those have
|
|
212
|
+
* internal `_rsc*` params stripped. `ctx.request` preserves the raw URL
|
|
213
|
+
* for cases where you need original headers, method, or body.
|
|
153
214
|
*/
|
|
154
215
|
request: Request;
|
|
155
216
|
/**
|
|
@@ -320,12 +381,26 @@ export type HandlerContext<
|
|
|
320
381
|
* @example
|
|
321
382
|
* ```typescript
|
|
322
383
|
* route("product", (ctx) => {
|
|
323
|
-
* ctx.setLocationState(
|
|
384
|
+
* ctx.setLocationState(ServerInfo({ data: "value" }));
|
|
385
|
+
* return <ProductPage />;
|
|
386
|
+
* });
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
390
|
+
/**
|
|
391
|
+
* The matched route name, if the route has an explicit name.
|
|
392
|
+
* Undefined for unnamed routes (those without a `name` option in path()).
|
|
393
|
+
* Includes the namespace prefix from include() (e.g., "blog.post").
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```typescript
|
|
397
|
+
* route("product", (ctx) => {
|
|
398
|
+
* ctx.routeName // "product"
|
|
324
399
|
* return <ProductPage />;
|
|
325
400
|
* });
|
|
326
401
|
* ```
|
|
327
402
|
*/
|
|
328
|
-
|
|
403
|
+
routeName?: DefaultRouteName;
|
|
329
404
|
/**
|
|
330
405
|
* Generate URLs from route names.
|
|
331
406
|
*
|
|
@@ -341,8 +416,14 @@ export type HandlerContext<
|
|
|
341
416
|
* ```
|
|
342
417
|
*/
|
|
343
418
|
reverse: [TRouteMap] extends [never]
|
|
344
|
-
?
|
|
345
|
-
|
|
419
|
+
? AutofillAwareReverseFunction<
|
|
420
|
+
Record<string, string>,
|
|
421
|
+
DefaultReverseRouteMap
|
|
422
|
+
>
|
|
423
|
+
: StrictLocalAutofillGlobalReverseFunction<
|
|
424
|
+
TRouteMap,
|
|
425
|
+
DefaultReverseRouteMap
|
|
426
|
+
>;
|
|
346
427
|
};
|
|
347
428
|
|
|
348
429
|
/**
|
|
@@ -355,12 +436,14 @@ export type InternalHandlerContext<
|
|
|
355
436
|
TEnv = DefaultEnv,
|
|
356
437
|
TSearch extends SearchSchema = {},
|
|
357
438
|
> = HandlerContext<TParams, TEnv, TSearch> & {
|
|
358
|
-
/**
|
|
359
|
-
|
|
439
|
+
/** Prerender-only control flow helper, attached when the runtime context supports it. */
|
|
440
|
+
passthrough?: () => unknown;
|
|
360
441
|
/** Current segment ID for handle data attribution. */
|
|
361
442
|
_currentSegmentId?: string;
|
|
362
443
|
/** Response type tag (json, text, html, etc.) for cache key differentiation. */
|
|
363
444
|
_responseType?: string;
|
|
445
|
+
/** Route name for cache key scoping (prevents cross-route collisions). */
|
|
446
|
+
_routeName?: string;
|
|
364
447
|
};
|
|
365
448
|
|
|
366
449
|
/**
|
|
@@ -457,7 +540,11 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
|
457
540
|
actionResult?: any; // Return value from action execution
|
|
458
541
|
formData?: FormData; // FormData from action request
|
|
459
542
|
method?: string; // Request method: 'GET' for navigation, 'POST' for actions
|
|
460
|
-
routeName?:
|
|
543
|
+
routeName?: DefaultRouteName; // Route name of the navigation target (alias for toRouteName)
|
|
544
|
+
// Named-route identity for both ends of a navigation transition.
|
|
545
|
+
// Undefined for unnamed internal routes (those without a `name` option).
|
|
546
|
+
fromRouteName?: DefaultRouteName; // Route name being navigated away from
|
|
547
|
+
toRouteName?: DefaultRouteName; // Route name being navigated to
|
|
461
548
|
// Stale cache revalidation (SWR pattern):
|
|
462
549
|
stale?: boolean; // True if this is a stale cache revalidation request
|
|
463
550
|
}) => boolean | { defaultShouldRevalidate: boolean };
|
package/src/types/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export type {
|
|
3
3
|
GetRegisteredRoutes,
|
|
4
4
|
DefaultHandlerRouteMap,
|
|
5
|
+
DefaultReverseRouteMap,
|
|
5
6
|
DefaultEnv,
|
|
6
7
|
} from "./global-namespace.js";
|
|
7
8
|
// Ensure the global namespace declaration is evaluated
|
|
@@ -10,7 +11,6 @@ import "./global-namespace.js";
|
|
|
10
11
|
// Route configuration
|
|
11
12
|
export type {
|
|
12
13
|
DocumentProps,
|
|
13
|
-
RouterEnv,
|
|
14
14
|
ExtractParams,
|
|
15
15
|
TrailingSlashMode,
|
|
16
16
|
RouteConfig,
|
|
@@ -4,7 +4,7 @@ import type { ScopedReverseFunction } from "../reverse.js";
|
|
|
4
4
|
import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
|
|
5
5
|
import type {
|
|
6
6
|
DefaultEnv,
|
|
7
|
-
|
|
7
|
+
DefaultReverseRouteMap,
|
|
8
8
|
DefaultVars,
|
|
9
9
|
} from "./global-namespace.js";
|
|
10
10
|
|
|
@@ -40,6 +40,13 @@ export type LoaderContext<
|
|
|
40
40
|
TSearch extends SearchSchema = {},
|
|
41
41
|
> = {
|
|
42
42
|
params: TParams;
|
|
43
|
+
/**
|
|
44
|
+
* Route params extracted from the URL pattern match (server-side only).
|
|
45
|
+
* Unlike `params`, these cannot be overridden by client-provided loader params.
|
|
46
|
+
* Use this when you need trusted, server-matched route params for auth or
|
|
47
|
+
* resource scoping.
|
|
48
|
+
*/
|
|
49
|
+
routeParams: Record<string, string>;
|
|
43
50
|
request: Request;
|
|
44
51
|
searchParams: URLSearchParams;
|
|
45
52
|
search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
|
|
@@ -50,10 +57,6 @@ export type LoaderContext<
|
|
|
50
57
|
get: {
|
|
51
58
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
52
59
|
} & (<K extends keyof DefaultVars>(key: K) => DefaultVars[K]);
|
|
53
|
-
/** Get a cookie value from the request */
|
|
54
|
-
cookie(name: string): string | undefined;
|
|
55
|
-
/** Get all cookies from the request */
|
|
56
|
-
cookies(): Record<string, string>;
|
|
57
60
|
/**
|
|
58
61
|
* Access another loader's data (returns promise since loaders run in parallel)
|
|
59
62
|
*/
|
|
@@ -82,7 +85,7 @@ export type LoaderContext<
|
|
|
82
85
|
*/
|
|
83
86
|
reverse: ScopedReverseFunction<
|
|
84
87
|
Record<string, string>,
|
|
85
|
-
|
|
88
|
+
DefaultReverseRouteMap
|
|
86
89
|
>;
|
|
87
90
|
};
|
|
88
91
|
|