@rangojs/router 0.0.0-experimental.18 → 0.0.0-experimental.19
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 +46 -8
- package/dist/bin/rango.js +105 -18
- package/dist/vite/index.js +227 -93
- package/package.json +15 -14
- package/skills/hooks/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +79 -0
- package/skills/layout/SKILL.md +62 -2
- package/skills/loader/SKILL.md +94 -1
- package/skills/middleware/SKILL.md +81 -0
- package/skills/parallel/SKILL.md +57 -2
- package/skills/prerender/SKILL.md +187 -17
- package/skills/route/SKILL.md +42 -1
- package/skills/router-setup/SKILL.md +77 -0
- package/src/__internal.ts +1 -1
- package/src/bin/rango.ts +38 -19
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/event-controller.ts +25 -27
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +1 -1
- package/src/browser/link-interceptor.ts +0 -3
- package/src/browser/merge-segment-loaders.ts +9 -2
- package/src/browser/navigation-bridge.ts +46 -13
- package/src/browser/navigation-client.ts +32 -61
- package/src/browser/navigation-store.ts +1 -31
- package/src/browser/navigation-transaction.ts +46 -207
- package/src/browser/partial-update.ts +102 -150
- package/src/browser/{prefetch-cache.ts → prefetch/cache.ts} +23 -4
- package/src/browser/{prefetch-fetch.ts → prefetch/fetch.ts} +36 -8
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/{prefetch-queue.ts → prefetch/queue.ts} +10 -3
- package/src/browser/react/Link.tsx +28 -23
- package/src/browser/react/NavigationProvider.tsx +9 -1
- package/src/browser/react/index.ts +2 -6
- package/src/browser/react/location-state-shared.ts +1 -1
- package/src/browser/react/location-state.ts +2 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/use-action.ts +9 -1
- package/src/browser/react/use-handle.ts +3 -25
- package/src/browser/react/use-params.ts +2 -4
- package/src/browser/react/use-pathname.ts +2 -3
- package/src/browser/react/use-router.ts +1 -1
- package/src/browser/react/use-search-params.ts +2 -1
- package/src/browser/react/use-segments.ts +7 -60
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +29 -23
- package/src/browser/scroll-restoration.ts +10 -7
- package/src/browser/server-action-bridge.ts +115 -96
- package/src/browser/types.ts +1 -31
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +5 -0
- package/src/build/generate-route-types.ts +2 -0
- 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 +45 -3
- 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 +132 -96
- package/src/cache/cache-scope.ts +71 -73
- package/src/cache/cf/cf-cache-store.ts +9 -4
- package/src/cache/document-cache.ts +72 -47
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/memory-segment-store.ts +18 -7
- package/src/cache/profile-registry.ts +43 -8
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +101 -112
- package/src/cache/taint.ts +26 -0
- package/src/client.tsx +53 -30
- 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/router.ts +14 -1
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +33 -1
- package/src/index.ts +27 -0
- package/src/loader.rsc.ts +12 -4
- package/src/loader.ts +8 -0
- package/src/prerender/store.ts +4 -3
- 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/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 +94 -15
- package/src/router/intercept-resolution.ts +6 -4
- package/src/router/lazy-includes.ts +4 -0
- package/src/router/loader-resolution.ts +1 -0
- package/src/router/logging.ts +100 -3
- package/src/router/manifest.ts +32 -3
- package/src/router/match-api.ts +61 -7
- 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 +69 -4
- package/src/router/match-middleware/cache-store.ts +2 -0
- package/src/router/match-pipelines.ts +8 -43
- package/src/router/middleware-types.ts +7 -0
- package/src/router/middleware.ts +93 -8
- package/src/router/pattern-matching.ts +41 -5
- package/src/router/prerender-match.ts +34 -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 +34 -0
- package/src/router/router-options.ts +200 -0
- package/src/router/segment-resolution/fresh.ts +123 -30
- package/src/router/segment-resolution/helpers.ts +19 -0
- package/src/router/segment-resolution/loader-cache.ts +37 -146
- package/src/router/segment-resolution/revalidation.ts +358 -94
- 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/types.ts +7 -1
- package/src/router.ts +155 -11
- package/src/rsc/handler-context.ts +11 -0
- package/src/rsc/handler.ts +380 -88
- package/src/rsc/helpers.ts +25 -16
- package/src/rsc/loader-fetch.ts +84 -42
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +232 -19
- package/src/rsc/response-route-handler.ts +37 -26
- package/src/rsc/rsc-rendering.ts +12 -5
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +134 -58
- package/src/rsc/types.ts +8 -0
- package/src/search-params.ts +22 -10
- package/src/server/context.ts +53 -5
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +66 -9
- package/src/server/loader-registry.ts +11 -46
- package/src/server/request-context.ts +90 -9
- package/src/ssr/index.tsx +63 -27
- package/src/static-handler.ts +7 -0
- package/src/theme/ThemeProvider.tsx +6 -1
- package/src/theme/index.ts +1 -6
- package/src/theme/theme-context.ts +1 -28
- package/src/theme/theme-script.ts +2 -1
- package/src/types/cache-types.ts +5 -0
- package/src/types/error-types.ts +3 -0
- package/src/types/global-namespace.ts +9 -0
- package/src/types/handler-context.ts +35 -13
- package/src/types/loader-types.ts +7 -0
- package/src/types/route-entry.ts +28 -0
- 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 +27 -2
- package/src/urls/type-extraction.ts +23 -15
- package/src/use-loader.tsx +12 -4
- package/src/vite/discovery/bundle-postprocess.ts +12 -7
- package/src/vite/discovery/discover-routers.ts +30 -18
- package/src/vite/discovery/prerender-collection.ts +24 -27
- package/src/vite/discovery/route-types-writer.ts +7 -7
- package/src/vite/discovery/virtual-module-codegen.ts +5 -2
- package/src/vite/plugins/client-ref-hashing.ts +3 -3
- package/src/vite/plugins/use-cache-transform.ts +91 -3
- package/src/vite/rango.ts +3 -3
- package/src/vite/router-discovery.ts +99 -36
- package/src/vite/utils/prerender-utils.ts +21 -0
- package/src/vite/utils/shared-utils.ts +3 -1
- package/src/browser/request-controller.ts +0 -164
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- /package/src/browser/{prefetch-observer.ts → prefetch/observer.ts} +0 -0
package/src/prerender.ts
CHANGED
|
@@ -34,10 +34,36 @@ import type {
|
|
|
34
34
|
} from "./types.js";
|
|
35
35
|
import type { Handle } from "./handle.js";
|
|
36
36
|
import type { ContextVar } from "./context-var.js";
|
|
37
|
+
import type { ReverseFunction } from "./reverse.js";
|
|
38
|
+
import type { DefaultReverseRouteMap } from "./types/global-namespace.js";
|
|
37
39
|
import { isCachedFunction } from "./cache/taint.js";
|
|
38
40
|
|
|
39
41
|
// -- Named route resolution types -------------------------------------------
|
|
40
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Reverse function for build contexts (BuildContext, StaticBuildContext, GetParamsContext).
|
|
45
|
+
* Global names get full autocomplete and param validation from the generated route map.
|
|
46
|
+
* Local `.name` calls are accepted but not validated (the include() scope is unknown
|
|
47
|
+
* at the type level).
|
|
48
|
+
*/
|
|
49
|
+
type BuildReverseFunction = [DefaultReverseRouteMap] extends [
|
|
50
|
+
Record<string, string>,
|
|
51
|
+
]
|
|
52
|
+
? // No generated route map — permissive fallback
|
|
53
|
+
(
|
|
54
|
+
name: string,
|
|
55
|
+
params?: Record<string, string>,
|
|
56
|
+
search?: Record<string, unknown>,
|
|
57
|
+
) => string
|
|
58
|
+
: // Generated route map available — typed globals + permissive locals
|
|
59
|
+
ReverseFunction<DefaultReverseRouteMap> & {
|
|
60
|
+
(
|
|
61
|
+
name: `.${string}`,
|
|
62
|
+
params?: Record<string, string>,
|
|
63
|
+
search?: Record<string, unknown>,
|
|
64
|
+
): string;
|
|
65
|
+
};
|
|
66
|
+
|
|
41
67
|
/**
|
|
42
68
|
* Default route map for Prerender named route resolution.
|
|
43
69
|
* Uses GeneratedRouteMap (from gen file) to avoid circular dependencies.
|
|
@@ -143,11 +169,14 @@ export interface BuildContext<TParams> {
|
|
|
143
169
|
search: {};
|
|
144
170
|
|
|
145
171
|
/** URL generation by route name. */
|
|
146
|
-
reverse:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
reverse: BuildReverseFunction;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Signal that this param set should not produce a local prerender artifact.
|
|
176
|
+
* At runtime the handler runs live instead. Only valid on routes declared
|
|
177
|
+
* with `{ passthrough: true }`.
|
|
178
|
+
*/
|
|
179
|
+
passthrough: () => PrerenderPassthroughResult;
|
|
151
180
|
}
|
|
152
181
|
|
|
153
182
|
/**
|
|
@@ -174,11 +203,7 @@ export interface StaticBuildContext {
|
|
|
174
203
|
use: <T>(handle: Handle<T>) => (data: T) => void;
|
|
175
204
|
|
|
176
205
|
/** URL generation by route name. */
|
|
177
|
-
reverse:
|
|
178
|
-
name: string,
|
|
179
|
-
params?: Record<string, string>,
|
|
180
|
-
search?: Record<string, unknown>,
|
|
181
|
-
) => string;
|
|
206
|
+
reverse: BuildReverseFunction;
|
|
182
207
|
}
|
|
183
208
|
|
|
184
209
|
/**
|
|
@@ -196,11 +221,7 @@ export interface GetParamsContext {
|
|
|
196
221
|
};
|
|
197
222
|
|
|
198
223
|
/** URL generation by route name. */
|
|
199
|
-
reverse:
|
|
200
|
-
name: string,
|
|
201
|
-
params?: Record<string, string>,
|
|
202
|
-
search?: Record<string, unknown>,
|
|
203
|
-
) => string;
|
|
224
|
+
reverse: BuildReverseFunction;
|
|
204
225
|
}
|
|
205
226
|
|
|
206
227
|
/**
|
|
@@ -216,7 +237,9 @@ export interface GetParamsContext {
|
|
|
216
237
|
export type PrerenderPassthroughContext<
|
|
217
238
|
TParams = {},
|
|
218
239
|
TEnv = DefaultEnv,
|
|
219
|
-
> = HandlerContext<TParams, TEnv
|
|
240
|
+
> = HandlerContext<TParams, TEnv> & {
|
|
241
|
+
passthrough: () => PrerenderPassthroughResult;
|
|
242
|
+
};
|
|
220
243
|
|
|
221
244
|
export interface PrerenderHandlerDefinition<
|
|
222
245
|
TParams extends Record<string, any> = any,
|
|
@@ -269,7 +292,10 @@ export function Prerender<
|
|
|
269
292
|
ResolvePrerenderParams<T, TRouteMap>,
|
|
270
293
|
TEnv
|
|
271
294
|
>,
|
|
272
|
-
) =>
|
|
295
|
+
) =>
|
|
296
|
+
| ReactNode
|
|
297
|
+
| PrerenderPassthroughResult
|
|
298
|
+
| Promise<ReactNode | PrerenderPassthroughResult>,
|
|
273
299
|
options: PrerenderOptions & { passthrough: true },
|
|
274
300
|
__injectedId?: string,
|
|
275
301
|
): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
|
|
@@ -313,7 +339,10 @@ export function Prerender<
|
|
|
313
339
|
ResolvePrerenderParams<T, TRouteMap>,
|
|
314
340
|
TEnv
|
|
315
341
|
>,
|
|
316
|
-
) =>
|
|
342
|
+
) =>
|
|
343
|
+
| ReactNode
|
|
344
|
+
| PrerenderPassthroughResult
|
|
345
|
+
| Promise<ReactNode | PrerenderPassthroughResult>,
|
|
317
346
|
options: PrerenderOptions & { passthrough: true },
|
|
318
347
|
__injectedId?: string,
|
|
319
348
|
): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
|
|
@@ -388,6 +417,35 @@ export function Prerender<TParams extends Record<string, any>>(
|
|
|
388
417
|
};
|
|
389
418
|
}
|
|
390
419
|
|
|
420
|
+
// -- Passthrough sentinel ---------------------------------------------------
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Sentinel returned by `ctx.passthrough()` to signal that a specific param set
|
|
424
|
+
* should not produce a local prerender artifact. The build skips writing the
|
|
425
|
+
* entry; at runtime the handler runs live (requires `{ passthrough: true }`).
|
|
426
|
+
*/
|
|
427
|
+
export const PRERENDER_PASSTHROUGH: Readonly<{
|
|
428
|
+
__brand: "prerenderPassthrough";
|
|
429
|
+
}> = Object.freeze({
|
|
430
|
+
__brand: "prerenderPassthrough" as const,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
export type PrerenderPassthroughResult = typeof PRERENDER_PASSTHROUGH;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Type guard to check if a value is the passthrough sentinel.
|
|
437
|
+
*/
|
|
438
|
+
export function isPrerenderPassthrough(
|
|
439
|
+
value: unknown,
|
|
440
|
+
): value is PrerenderPassthroughResult {
|
|
441
|
+
return (
|
|
442
|
+
typeof value === "object" &&
|
|
443
|
+
value !== null &&
|
|
444
|
+
"__brand" in value &&
|
|
445
|
+
(value as { __brand: unknown }).__brand === "prerenderPassthrough"
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
391
449
|
// -- Type guard -------------------------------------------------------------
|
|
392
450
|
|
|
393
451
|
/**
|
package/src/reverse.ts
CHANGED
|
@@ -304,13 +304,17 @@ export function createReverse<TRoutes extends Record<string, string>>(
|
|
|
304
304
|
let result = pattern;
|
|
305
305
|
if (params) {
|
|
306
306
|
// Replace :param placeholders with actual values
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
// Strip constraint syntax: :param(a|b) -> use "param" as key
|
|
308
|
+
result = result.replace(
|
|
309
|
+
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\??/g,
|
|
310
|
+
(_, key) => {
|
|
311
|
+
const value = params[key];
|
|
312
|
+
if (value === undefined) {
|
|
313
|
+
throw new Error(`Missing param "${key}" for route "${name}"`);
|
|
314
|
+
}
|
|
315
|
+
return encodeURIComponent(value);
|
|
316
|
+
},
|
|
317
|
+
);
|
|
314
318
|
}
|
|
315
319
|
|
|
316
320
|
// Append search params as query string
|
|
@@ -109,6 +109,8 @@ function RootErrorFallback({
|
|
|
109
109
|
error,
|
|
110
110
|
reset,
|
|
111
111
|
}: ClientErrorBoundaryFallbackProps): ReactNode {
|
|
112
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
113
|
+
|
|
112
114
|
return (
|
|
113
115
|
<div
|
|
114
116
|
style={{
|
|
@@ -135,38 +137,40 @@ function RootErrorFallback({
|
|
|
135
137
|
>
|
|
136
138
|
An unexpected error occurred while processing your request.
|
|
137
139
|
</p>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
background: "#fef2f2",
|
|
141
|
-
border: "1px solid #fecaca",
|
|
142
|
-
borderRadius: "0.5rem",
|
|
143
|
-
padding: "1rem",
|
|
144
|
-
marginBottom: "1rem",
|
|
145
|
-
}}
|
|
146
|
-
>
|
|
147
|
-
<p
|
|
140
|
+
{isDev && (
|
|
141
|
+
<div
|
|
148
142
|
style={{
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
143
|
+
background: "#fef2f2",
|
|
144
|
+
border: "1px solid #fecaca",
|
|
145
|
+
borderRadius: "0.5rem",
|
|
146
|
+
padding: "1rem",
|
|
147
|
+
marginBottom: "1rem",
|
|
152
148
|
}}
|
|
153
149
|
>
|
|
154
|
-
|
|
155
|
-
</p>
|
|
156
|
-
{error.stack && (
|
|
157
|
-
<pre
|
|
150
|
+
<p
|
|
158
151
|
style={{
|
|
159
|
-
|
|
160
|
-
color: "#
|
|
161
|
-
|
|
162
|
-
whiteSpace: "pre-wrap",
|
|
163
|
-
wordBreak: "break-word",
|
|
152
|
+
fontWeight: 600,
|
|
153
|
+
color: "#991b1b",
|
|
154
|
+
marginBottom: "0.5rem",
|
|
164
155
|
}}
|
|
165
156
|
>
|
|
166
|
-
{error.
|
|
167
|
-
</
|
|
168
|
-
|
|
169
|
-
|
|
157
|
+
{error.name}: {error.message}
|
|
158
|
+
</p>
|
|
159
|
+
{error.stack && (
|
|
160
|
+
<pre
|
|
161
|
+
style={{
|
|
162
|
+
fontSize: "0.75rem",
|
|
163
|
+
color: "#6b7280",
|
|
164
|
+
overflow: "auto",
|
|
165
|
+
whiteSpace: "pre-wrap",
|
|
166
|
+
wordBreak: "break-word",
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
{error.stack}
|
|
170
|
+
</pre>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
170
174
|
<div style={{ display: "flex", gap: "1rem" }}>
|
|
171
175
|
<button
|
|
172
176
|
type="button"
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "../server/context";
|
|
17
17
|
import { invariant } from "../errors";
|
|
18
18
|
import { isCachedFunction } from "../cache/taint.js";
|
|
19
|
-
import {
|
|
19
|
+
import { RSCRouterContext } from "../server/context";
|
|
20
20
|
import { isStaticHandler } from "../static-handler.js";
|
|
21
21
|
import RootLayout from "../server/root-layout";
|
|
22
22
|
import type {
|
|
@@ -227,7 +227,9 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
227
227
|
children = undefined;
|
|
228
228
|
} else if (typeof optionsOrChildren === "string") {
|
|
229
229
|
// cache('profileName') or cache('profileName', () => [...])
|
|
230
|
-
|
|
230
|
+
// Resolve from context-scoped profiles (set per-router via HelperContext).
|
|
231
|
+
const ctxStore = RSCRouterContext.getStore();
|
|
232
|
+
const profile = ctxStore?.cacheProfiles?.[optionsOrChildren];
|
|
231
233
|
invariant(
|
|
232
234
|
profile,
|
|
233
235
|
`cache("${optionsOrChildren}"): unknown cache profile. ` +
|
|
@@ -245,7 +247,9 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
245
247
|
children = maybeChildren;
|
|
246
248
|
}
|
|
247
249
|
|
|
248
|
-
|
|
250
|
+
// Allocate a single index for this cache() call (used in all paths)
|
|
251
|
+
const cacheIndex = store.getNextIndex("cache");
|
|
252
|
+
const name = `$${cacheIndex}`;
|
|
249
253
|
const cacheConfig = { options };
|
|
250
254
|
|
|
251
255
|
// If no children, create an orphan cache entry (like orphan layouts)
|
|
@@ -262,7 +266,7 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
262
266
|
|
|
263
267
|
// Create orphan cache entry (like orphan layout)
|
|
264
268
|
// Subsequent siblings in the same array will attach to this entry
|
|
265
|
-
const namespace = `${ctx.namespace}.${
|
|
269
|
+
const namespace = `${ctx.namespace}.${cacheIndex}`;
|
|
266
270
|
const cacheUrlPrefix = getUrlPrefix();
|
|
267
271
|
|
|
268
272
|
const entry = {
|
|
@@ -297,8 +301,7 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
297
301
|
}
|
|
298
302
|
|
|
299
303
|
// With children: create a cache entry (like layout with caching semantics)
|
|
300
|
-
const
|
|
301
|
-
const namespace = `${ctx.namespace}.${cacheNextIndex}`;
|
|
304
|
+
const namespace = `${ctx.namespace}.${cacheIndex}`;
|
|
302
305
|
const cacheShortCode = store.getShortCode("cache");
|
|
303
306
|
|
|
304
307
|
const cacheUrlPrefix2 = getUrlPrefix();
|
|
@@ -43,11 +43,16 @@ import {
|
|
|
43
43
|
export function redirect(url: string, status?: number): Response;
|
|
44
44
|
export function redirect(
|
|
45
45
|
url: string,
|
|
46
|
-
options: {
|
|
46
|
+
options: {
|
|
47
|
+
status?: number;
|
|
48
|
+
state?: LocationStateEntry | LocationStateEntry[];
|
|
49
|
+
},
|
|
47
50
|
): Response;
|
|
48
51
|
export function redirect(
|
|
49
52
|
url: string,
|
|
50
|
-
statusOrOptions?:
|
|
53
|
+
statusOrOptions?:
|
|
54
|
+
| number
|
|
55
|
+
| { status?: number; state?: LocationStateEntry | LocationStateEntry[] },
|
|
51
56
|
): Response {
|
|
52
57
|
const status =
|
|
53
58
|
typeof statusOrOptions === "number"
|
|
@@ -62,7 +67,14 @@ export function redirect(
|
|
|
62
67
|
|
|
63
68
|
if (process.env.NODE_ENV !== "production") {
|
|
64
69
|
const reqCtx = getRequestContext();
|
|
65
|
-
|
|
70
|
+
// Warn only on true full-page SSR loads. SPA partial requests and server
|
|
71
|
+
// actions both deliver state through Flight payloads, so suppress for those.
|
|
72
|
+
if (
|
|
73
|
+
reqCtx &&
|
|
74
|
+
!reqCtx.url.searchParams.has("_rsc_partial") &&
|
|
75
|
+
!reqCtx.request.headers.has("rsc-action") &&
|
|
76
|
+
!reqCtx.url.searchParams.has("_rsc_action")
|
|
77
|
+
) {
|
|
66
78
|
console.warn(
|
|
67
79
|
`[Router] redirect() with state during a full-page (SSR) request to "${url}". ` +
|
|
68
80
|
"Location state is only delivered during SPA navigations and will be lost on this request.",
|
package/src/route-map-builder.ts
CHANGED
|
@@ -128,15 +128,23 @@ const perRouterPrecomputedEntriesMap: Map<
|
|
|
128
128
|
> = new Map();
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
* Clear all
|
|
131
|
+
* Clear all cached route data (global and per-router).
|
|
132
132
|
* Called during HMR when route definitions change so the handler rebuilds
|
|
133
133
|
* the trie from the updated router.urlpatterns on the next request.
|
|
134
|
+
*
|
|
135
|
+
* The virtual module calls this before repopulating with fresh data,
|
|
136
|
+
* preventing stale entries from removed routes from accumulating.
|
|
134
137
|
*/
|
|
135
138
|
export function clearAllRouterData(): void {
|
|
139
|
+
globalRouteMap = {};
|
|
140
|
+
cachedManifest = null;
|
|
141
|
+
cachedPrecomputedEntries = null;
|
|
142
|
+
cachedRouteTrie = null;
|
|
143
|
+
rootScopeRoutes.clear();
|
|
144
|
+
globalSearchSchemas.clear();
|
|
136
145
|
perRouterManifestMap.clear();
|
|
137
146
|
perRouterTrieMap.clear();
|
|
138
147
|
perRouterPrecomputedEntriesMap.clear();
|
|
139
|
-
cachedRouteTrie = null;
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
export function setRouterManifest(
|
|
@@ -217,6 +225,34 @@ export function waitForManifestReady(): Promise<void> | null {
|
|
|
217
225
|
return manifestReadyPromise;
|
|
218
226
|
}
|
|
219
227
|
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// Route Scope Registry
|
|
230
|
+
// ============================================================================
|
|
231
|
+
|
|
232
|
+
// Tracks whether each route is at root scope (no named include boundary above).
|
|
233
|
+
// Used by dot-local reverse resolution to decide whether bare-name fallback
|
|
234
|
+
// is allowed after scoped lookups are exhausted.
|
|
235
|
+
const rootScopeRoutes: Map<string, boolean> = new Map();
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Register whether a route is at root scope.
|
|
239
|
+
* Called by path() during route evaluation.
|
|
240
|
+
*/
|
|
241
|
+
export function registerRouteRootScope(
|
|
242
|
+
routeName: string,
|
|
243
|
+
rootScoped: boolean,
|
|
244
|
+
): void {
|
|
245
|
+
rootScopeRoutes.set(routeName, rootScoped);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if a route is at root scope.
|
|
250
|
+
* Returns undefined if the route has not been registered (e.g. in unit tests).
|
|
251
|
+
*/
|
|
252
|
+
export function isRouteRootScoped(routeName: string): boolean | undefined {
|
|
253
|
+
return rootScopeRoutes.get(routeName);
|
|
254
|
+
}
|
|
255
|
+
|
|
220
256
|
// ============================================================================
|
|
221
257
|
// Search Schema Registry
|
|
222
258
|
// ============================================================================
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route name utilities for filtering internal route names.
|
|
3
|
+
*
|
|
4
|
+
* Internal names stay active in the runtime manifest for matching and local
|
|
5
|
+
* reverse() resolution, but they must not leak into public APIs or generated
|
|
6
|
+
* route maps.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const AUTO_GENERATED_ROUTE_PREFIX = "$path_";
|
|
10
|
+
export const INTERNAL_INCLUDE_SCOPE_PREFIX = "$prefix_";
|
|
11
|
+
|
|
12
|
+
const RESERVED_PREFIXES = [
|
|
13
|
+
AUTO_GENERATED_ROUTE_PREFIX,
|
|
14
|
+
INTERNAL_INCLUDE_SCOPE_PREFIX,
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if a route name is internal.
|
|
19
|
+
* Internal names include:
|
|
20
|
+
* - unnamed path() routes like "$path__health" or "docs.$path__health"
|
|
21
|
+
* - hidden include scopes like "$prefix_0.index" or "blog.$prefix_1.post"
|
|
22
|
+
*
|
|
23
|
+
* User-defined names containing "$" (e.g. "docs.$admin") are valid and must
|
|
24
|
+
* be preserved.
|
|
25
|
+
*/
|
|
26
|
+
export function isAutoGeneratedRouteName(name: string): boolean {
|
|
27
|
+
return name.split(".").some((segment) => {
|
|
28
|
+
return (
|
|
29
|
+
segment.startsWith(AUTO_GENERATED_ROUTE_PREFIX) ||
|
|
30
|
+
segment.startsWith(INTERNAL_INCLUDE_SCOPE_PREFIX)
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate that a user-provided route name does not collide with
|
|
37
|
+
* reserved internal prefixes. Checks every dot-separated segment,
|
|
38
|
+
* mirroring the same rule used by isAutoGeneratedRouteName().
|
|
39
|
+
*
|
|
40
|
+
* Throws with a clear message when a reserved prefix is detected.
|
|
41
|
+
*/
|
|
42
|
+
export function validateUserRouteName(name: string): void {
|
|
43
|
+
for (const segment of name.split(".")) {
|
|
44
|
+
for (const prefix of RESERVED_PREFIXES) {
|
|
45
|
+
if (segment.startsWith(prefix)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Route name "${name}" contains segment "${segment}" which uses reserved internal prefix "${prefix}". ` +
|
|
48
|
+
`Choose a different name to avoid collision with auto-generated route names.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/route-types.ts
CHANGED
|
@@ -169,6 +169,13 @@ export type IncludeItem = {
|
|
|
169
169
|
parent: unknown; // EntryData - avoid circular import
|
|
170
170
|
/** Counter snapshot from pattern extraction for consistent shortCode indices */
|
|
171
171
|
counters?: Record<string, number>;
|
|
172
|
+
/** Cache profiles for DSL-time cache("profileName") resolution */
|
|
173
|
+
cacheProfiles?: Record<
|
|
174
|
+
string,
|
|
175
|
+
import("./cache/profile-registry.js").CacheProfile
|
|
176
|
+
>;
|
|
177
|
+
/** Root scope flag for dot-local reverse resolution */
|
|
178
|
+
rootScoped?: boolean;
|
|
172
179
|
};
|
|
173
180
|
[IncludeBrand]: void;
|
|
174
181
|
};
|
|
@@ -52,7 +52,7 @@ export function parseAcceptTypes(accept: string): AcceptEntry[] {
|
|
|
52
52
|
for (let i = 0; i < parts.length; i++) {
|
|
53
53
|
const part = parts[i]!;
|
|
54
54
|
const segments = part.split(";");
|
|
55
|
-
const mime = segments[0]!.trim();
|
|
55
|
+
const mime = segments[0]!.trim().toLowerCase();
|
|
56
56
|
if (!mime) continue;
|
|
57
57
|
let q = 1.0;
|
|
58
58
|
for (let j = 1; j < segments.length; j++) {
|
|
@@ -45,10 +45,23 @@ export async function buildDebugManifest<TEnv = any>(
|
|
|
45
45
|
if (promiseResult !== null) {
|
|
46
46
|
const load = await (promiseResult as Promise<any>);
|
|
47
47
|
if (load && typeof load === "object" && "default" in load) {
|
|
48
|
-
|
|
49
|
-
if (typeof
|
|
50
|
-
|
|
48
|
+
// Promise<{ default: fn }> — e.g. dynamic import
|
|
49
|
+
if (typeof load.default !== "function") {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`[@rangojs/router] Unsupported async handler: { default } must be a function, ` +
|
|
52
|
+
`got ${typeof load.default}. Use () => import('./urls') for lazy loading.`,
|
|
53
|
+
);
|
|
51
54
|
}
|
|
55
|
+
load.default(helpers);
|
|
56
|
+
} else if (typeof load === "function") {
|
|
57
|
+
// Promise<fn>
|
|
58
|
+
load(helpers);
|
|
59
|
+
} else {
|
|
60
|
+
// Reject unsupported async handler results (same policy as manifest.ts)
|
|
61
|
+
throw new Error(
|
|
62
|
+
`[@rangojs/router] Unsupported async handler result (${typeof load}). ` +
|
|
63
|
+
`Lazy route handlers must resolve to a function or { default: fn }.`,
|
|
64
|
+
);
|
|
52
65
|
}
|
|
53
66
|
}
|
|
54
67
|
},
|