@rangojs/router 0.0.0-experimental.29 → 0.0.0-experimental.2a0dea97
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 +4 -0
- package/README.md +78 -19
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +853 -435
- package/package.json +17 -16
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +45 -4
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +22 -4
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +3 -1
- package/skills/loader/SKILL.md +71 -21
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +764 -0
- package/skills/parallel/SKILL.md +185 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/rango/SKILL.md +24 -22
- package/skills/route/SKILL.md +56 -2
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/typesafety/SKILL.md +33 -21
- package/src/__internal.ts +92 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +125 -16
- package/src/browser/navigation-client.ts +142 -57
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +94 -17
- package/src/browser/prefetch/cache.ts +82 -12
- package/src/browser/prefetch/fetch.ts +98 -27
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +92 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/react/Link.tsx +88 -9
- package/src/browser/react/NavigationProvider.tsx +40 -4
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-router.ts +21 -8
- package/src/browser/rsc-router.tsx +134 -59
- package/src/browser/scroll-restoration.ts +41 -42
- package/src/browser/segment-reconciler.ts +72 -10
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +55 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +50 -24
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +223 -74
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +48 -7
- package/src/cache/cf/cf-cache-store.ts +453 -11
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +2 -0
- package/src/client.tsx +6 -66
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/index.rsc.ts +6 -36
- package/src/index.ts +50 -43
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +25 -1
- package/src/route-definition/dsl-helpers.ts +224 -37
- package/src/route-definition/helpers-types.ts +67 -19
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +11 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +111 -25
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +4 -1
- package/src/router/loader-resolution.ts +156 -21
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +9 -3
- package/src/router/match-api.ts +125 -190
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +94 -17
- package/src/router/match-middleware/cache-store.ts +53 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +104 -10
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +16 -22
- package/src/router/middleware.ts +24 -30
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/prerender-match.ts +114 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +198 -20
- package/src/router/segment-resolution/helpers.ts +30 -25
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +438 -300
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/types.ts +1 -0
- package/src/router.ts +59 -6
- package/src/rsc/handler.ts +472 -372
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +14 -2
- package/src/rsc/rsc-rendering.ts +12 -1
- package/src/rsc/server-action.ts +8 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +9 -1
- package/src/segment-content-promise.ts +33 -0
- package/src/segment-system.tsx +164 -23
- package/src/server/context.ts +140 -14
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +204 -28
- package/src/ssr/index.tsx +4 -0
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +149 -49
- package/src/types/loader-types.ts +36 -9
- package/src/types/route-entry.ts +8 -1
- package/src/types/segments.ts +6 -0
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +48 -13
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +16 -6
- package/src/use-loader.tsx +77 -5
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +5 -1
- package/src/vite/discovery/prerender-collection.ts +128 -74
- package/src/vite/discovery/state.ts +13 -6
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +51 -79
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/expose-id-utils.ts +12 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-internal-ids.ts +257 -40
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +163 -211
- package/src/vite/router-discovery.ts +178 -45
- package/src/vite/utils/banner.ts +3 -3
- package/src/vite/utils/prerender-utils.ts +37 -5
- package/src/vite/utils/shared-utils.ts +3 -2
package/src/index.ts
CHANGED
|
@@ -10,9 +10,6 @@
|
|
|
10
10
|
* import from "@rangojs/router/client"
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
// Universal rendering utilities (work on both server and client)
|
|
14
|
-
export { renderSegments } from "./segment-system.js";
|
|
15
|
-
|
|
16
13
|
// Error classes (can be used on both server and client)
|
|
17
14
|
export {
|
|
18
15
|
RouteNotFoundError,
|
|
@@ -22,9 +19,6 @@ export {
|
|
|
22
19
|
HandlerError,
|
|
23
20
|
BuildError,
|
|
24
21
|
InvalidHandlerError,
|
|
25
|
-
NetworkError,
|
|
26
|
-
isNetworkError,
|
|
27
|
-
sanitizeError,
|
|
28
22
|
RouterError,
|
|
29
23
|
Skip,
|
|
30
24
|
isSkip,
|
|
@@ -41,7 +35,6 @@ export type {
|
|
|
41
35
|
TrailingSlashMode,
|
|
42
36
|
// Handler types
|
|
43
37
|
Handler, // Supports params object, path pattern, or route name
|
|
44
|
-
ScopedRouteMap, // Scoped view of GeneratedRouteMap for Handler<"localName", ScopedRouteMap<"prefix">>
|
|
45
38
|
HandlerContext,
|
|
46
39
|
ExtractParams,
|
|
47
40
|
GenericParams,
|
|
@@ -95,6 +88,7 @@ export type {
|
|
|
95
88
|
LayoutUseItem,
|
|
96
89
|
AllUseItems,
|
|
97
90
|
UseItems,
|
|
91
|
+
HandlerUseItem,
|
|
98
92
|
} from "./route-types.js";
|
|
99
93
|
|
|
100
94
|
// Response route types (usable in both server and client contexts)
|
|
@@ -153,17 +147,52 @@ export { createVar, type ContextVar } from "./context-var.js";
|
|
|
153
147
|
export { nonce } from "./rsc/nonce.js";
|
|
154
148
|
|
|
155
149
|
/**
|
|
156
|
-
*
|
|
150
|
+
* SSR/client stub for server-only `Prerender` function.
|
|
151
|
+
*
|
|
152
|
+
* Returns a lightweight stub object instead of throwing so that the
|
|
153
|
+
* production SSR build can safely bundle the RSC entry chunk — the SSR
|
|
154
|
+
* bundler resolves `@rangojs/router` to this (SSR) entry, so Prerender
|
|
155
|
+
* calls in RSC code must not crash at module-evaluation time.
|
|
157
156
|
*/
|
|
158
|
-
export function Prerender(
|
|
159
|
-
|
|
157
|
+
export function Prerender(
|
|
158
|
+
_handler?: any,
|
|
159
|
+
_optionsOrId?: any,
|
|
160
|
+
__injectedId?: string,
|
|
161
|
+
): any {
|
|
162
|
+
const id =
|
|
163
|
+
typeof _optionsOrId === "string" ? _optionsOrId : __injectedId || "";
|
|
164
|
+
return { __brand: "prerenderHandler" as const, $$id: id };
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
/**
|
|
163
|
-
*
|
|
168
|
+
* SSR/client stub for server-only `Passthrough` function.
|
|
164
169
|
*/
|
|
165
|
-
export function
|
|
166
|
-
|
|
170
|
+
export function Passthrough(
|
|
171
|
+
_handler?: any,
|
|
172
|
+
_optionsOrId?: any,
|
|
173
|
+
__injectedId?: string,
|
|
174
|
+
): any {
|
|
175
|
+
const id =
|
|
176
|
+
typeof _optionsOrId === "string" ? _optionsOrId : __injectedId || "";
|
|
177
|
+
return { __brand: "passthroughHandler" as const, $$id: id };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* SSR/client stub for server-only `Static` function.
|
|
182
|
+
*
|
|
183
|
+
* Returns a lightweight stub object instead of throwing so that the
|
|
184
|
+
* production SSR build can safely bundle the RSC entry chunk — the SSR
|
|
185
|
+
* bundler resolves `@rangojs/router` to this (SSR) entry, so Static
|
|
186
|
+
* calls in RSC code must not crash at module-evaluation time.
|
|
187
|
+
*/
|
|
188
|
+
export function Static(
|
|
189
|
+
_handler?: any,
|
|
190
|
+
_optionsOrId?: any,
|
|
191
|
+
__injectedId?: string,
|
|
192
|
+
): any {
|
|
193
|
+
const id =
|
|
194
|
+
typeof _optionsOrId === "string" ? _optionsOrId : __injectedId || "";
|
|
195
|
+
return { __brand: "staticHandler" as const, $$id: id };
|
|
167
196
|
}
|
|
168
197
|
|
|
169
198
|
/**
|
|
@@ -194,20 +223,6 @@ export function createReverse(): never {
|
|
|
194
223
|
throw serverOnlyStubError("createReverse");
|
|
195
224
|
}
|
|
196
225
|
|
|
197
|
-
/**
|
|
198
|
-
* Error-throwing stub for server-only `enableMatchDebug` function.
|
|
199
|
-
*/
|
|
200
|
-
export function enableMatchDebug(): never {
|
|
201
|
-
throw serverOnlyStubError("enableMatchDebug");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Error-throwing stub for server-only `getMatchDebugStats` function.
|
|
206
|
-
*/
|
|
207
|
-
export function getMatchDebugStats(): never {
|
|
208
|
-
throw serverOnlyStubError("getMatchDebugStats");
|
|
209
|
-
}
|
|
210
|
-
|
|
211
226
|
// Error-throwing stubs for server-only route helpers
|
|
212
227
|
export function layout(): never {
|
|
213
228
|
throw serverOnlyStubError("layout");
|
|
@@ -256,17 +271,22 @@ export type {
|
|
|
256
271
|
ReadonlyHeaders,
|
|
257
272
|
} from "./server/cookie-store.js";
|
|
258
273
|
|
|
274
|
+
// Built-in handles (universal — work on both server and client)
|
|
275
|
+
export { Meta } from "./handles/meta.js";
|
|
276
|
+
export { Breadcrumbs } from "./handles/breadcrumbs.js";
|
|
277
|
+
|
|
259
278
|
// Meta types
|
|
260
279
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
261
280
|
|
|
281
|
+
// Breadcrumb types
|
|
282
|
+
export type { BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
283
|
+
|
|
262
284
|
// Reverse type utilities for type-safe URL generation (Django-style URL reversal)
|
|
263
285
|
export type {
|
|
264
286
|
ScopedReverseFunction,
|
|
265
287
|
ReverseFunction,
|
|
266
288
|
ExtractLocalRoutes,
|
|
267
289
|
ParamsFor,
|
|
268
|
-
SanitizePrefix,
|
|
269
|
-
MergeRoutes,
|
|
270
290
|
} from "./reverse.js";
|
|
271
291
|
// scopedReverse() helper for handlers to get locally-typed reverse
|
|
272
292
|
export { scopedReverse } from "./reverse.js";
|
|
@@ -286,20 +306,7 @@ export type { PathResponse } from "./href-client.js";
|
|
|
286
306
|
export { createConsoleSink } from "./router/telemetry.js";
|
|
287
307
|
export { createOTelSink } from "./router/telemetry-otel.js";
|
|
288
308
|
export type { OTelTracer, OTelSpan } from "./router/telemetry-otel.js";
|
|
289
|
-
export type {
|
|
290
|
-
TelemetrySink,
|
|
291
|
-
TelemetryEvent,
|
|
292
|
-
RequestStartEvent,
|
|
293
|
-
RequestEndEvent,
|
|
294
|
-
RequestErrorEvent,
|
|
295
|
-
RequestTimeoutEvent,
|
|
296
|
-
LoaderStartEvent,
|
|
297
|
-
LoaderEndEvent,
|
|
298
|
-
LoaderErrorEvent,
|
|
299
|
-
HandlerErrorEvent,
|
|
300
|
-
CacheDecisionEvent,
|
|
301
|
-
RevalidationDecisionEvent,
|
|
302
|
-
} from "./router/telemetry.js";
|
|
309
|
+
export type { TelemetrySink, TelemetryEvent } from "./router/telemetry.js";
|
|
303
310
|
|
|
304
311
|
// Timeout types and error class
|
|
305
312
|
export { RouterTimeoutError } from "./router/timeout.js";
|
package/src/prerender/store.ts
CHANGED
|
@@ -121,10 +121,11 @@ export function createPrerenderStore(): PrerenderStore | null {
|
|
|
121
121
|
if (!mod) return null;
|
|
122
122
|
const specifier = mod.default[key];
|
|
123
123
|
if (!specifier) return null;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
// Let asset load errors propagate — a missing/corrupted artifact
|
|
125
|
+
// for a key that exists in the manifest is a build/deploy error
|
|
126
|
+
// and should surface as a 500, not be silently swallowed as null
|
|
127
|
+
// (which the handler stub would misreport as a 404).
|
|
128
|
+
return mod.loadPrerenderAsset(specifier).then((asset) => asset.default);
|
|
128
129
|
});
|
|
129
130
|
cache.set(key, promise);
|
|
130
131
|
return promise;
|
package/src/prerender.ts
CHANGED
|
@@ -36,6 +36,7 @@ import type { Handle } from "./handle.js";
|
|
|
36
36
|
import type { ContextVar } from "./context-var.js";
|
|
37
37
|
import type { ReverseFunction } from "./reverse.js";
|
|
38
38
|
import type { DefaultReverseRouteMap } from "./types/global-namespace.js";
|
|
39
|
+
import type { UseItems, HandlerUseItem } from "./route-types.js";
|
|
39
40
|
import { isCachedFunction } from "./cache/taint.js";
|
|
40
41
|
|
|
41
42
|
// -- Named route resolution types -------------------------------------------
|
|
@@ -105,13 +106,6 @@ type ResolvePrerenderParams<
|
|
|
105
106
|
// -- Types ------------------------------------------------------------------
|
|
106
107
|
|
|
107
108
|
export interface PrerenderOptions {
|
|
108
|
-
/**
|
|
109
|
-
* Keep handler in server bundle for live fallback (default: false).
|
|
110
|
-
* false: handler replaced with stub, source-only APIs excluded from bundle.
|
|
111
|
-
* true: handler stays in bundle, unknown params render live at request time.
|
|
112
|
-
*/
|
|
113
|
-
passthrough?: boolean;
|
|
114
|
-
|
|
115
109
|
/**
|
|
116
110
|
* Maximum number of param sets to render in parallel (default: 1).
|
|
117
111
|
* Only applies to dynamic Prerender handlers with getParams().
|
|
@@ -131,8 +125,8 @@ export interface PrerenderOptions {
|
|
|
131
125
|
|
|
132
126
|
/**
|
|
133
127
|
* Context passed to Prerender() handlers at build time.
|
|
134
|
-
* Has a synthetic URL from getParams, params, and
|
|
135
|
-
* No request,
|
|
128
|
+
* Has a synthetic URL from getParams, params, pathname, and optionally env.
|
|
129
|
+
* No request, headers, cookies.
|
|
136
130
|
*/
|
|
137
131
|
export interface BuildContext<TParams> {
|
|
138
132
|
/** Params extracted from the route pattern (populated from getParams). */
|
|
@@ -141,6 +135,23 @@ export interface BuildContext<TParams> {
|
|
|
141
135
|
/** True during build-time pre-rendering, false during passthrough live render. */
|
|
142
136
|
build: true;
|
|
143
137
|
|
|
138
|
+
/**
|
|
139
|
+
* True when running in Vite dev mode (on-demand prerender), false during
|
|
140
|
+
* production `vite build`. Use this to branch on runtime mode without
|
|
141
|
+
* changing build semantics.
|
|
142
|
+
*/
|
|
143
|
+
dev: boolean;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Build-time environment bindings (KV, D1, etc.) supplied by the Vite plugin.
|
|
147
|
+
* Only available when `buildEnv` is configured in rango() options.
|
|
148
|
+
* Throws with a clear error if not configured.
|
|
149
|
+
*
|
|
150
|
+
* This is NOT the live request env — it is shared across all prerender
|
|
151
|
+
* invocations for the build.
|
|
152
|
+
*/
|
|
153
|
+
env: DefaultEnv;
|
|
154
|
+
|
|
144
155
|
/** Read a variable set by getParams or a parent handler. */
|
|
145
156
|
get: {
|
|
146
157
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -173,8 +184,8 @@ export interface BuildContext<TParams> {
|
|
|
173
184
|
|
|
174
185
|
/**
|
|
175
186
|
* Signal that this param set should not produce a local prerender artifact.
|
|
176
|
-
* At runtime the handler runs
|
|
177
|
-
* with `
|
|
187
|
+
* At runtime the live handler runs instead. Only valid on routes wrapped
|
|
188
|
+
* with `Passthrough()`.
|
|
178
189
|
*/
|
|
179
190
|
passthrough: () => PrerenderPassthroughResult;
|
|
180
191
|
}
|
|
@@ -187,6 +198,17 @@ export interface StaticBuildContext {
|
|
|
187
198
|
/** Always true for Static handlers at build time. */
|
|
188
199
|
build: true;
|
|
189
200
|
|
|
201
|
+
/**
|
|
202
|
+
* True when running in Vite dev mode, false during production build.
|
|
203
|
+
*/
|
|
204
|
+
dev: boolean;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Build-time environment bindings supplied by the Vite plugin.
|
|
208
|
+
* Only available when `buildEnv` is configured in rango() options.
|
|
209
|
+
*/
|
|
210
|
+
env: DefaultEnv;
|
|
211
|
+
|
|
190
212
|
/** Read a variable (available for type consistency with BuildContext). */
|
|
191
213
|
get: {
|
|
192
214
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -214,6 +236,17 @@ export interface GetParamsContext {
|
|
|
214
236
|
/** Always true during build-time getParams execution. */
|
|
215
237
|
build: true;
|
|
216
238
|
|
|
239
|
+
/**
|
|
240
|
+
* True when running in Vite dev mode, false during production build.
|
|
241
|
+
*/
|
|
242
|
+
dev: boolean;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Build-time environment bindings supplied by the Vite plugin.
|
|
246
|
+
* Only available when `buildEnv` is configured in rango() options.
|
|
247
|
+
*/
|
|
248
|
+
env: DefaultEnv;
|
|
249
|
+
|
|
217
250
|
/** Set a variable that will be available to each handler invocation via ctx.get(). */
|
|
218
251
|
set: {
|
|
219
252
|
<T>(contextVar: ContextVar<T>, value: T): void;
|
|
@@ -224,23 +257,6 @@ export interface GetParamsContext {
|
|
|
224
257
|
reverse: BuildReverseFunction;
|
|
225
258
|
}
|
|
226
259
|
|
|
227
|
-
/**
|
|
228
|
-
* Context type for passthrough Prerender handlers.
|
|
229
|
-
*
|
|
230
|
-
* When `passthrough: true`, the handler runs both at build time and at request
|
|
231
|
-
* time. The context is a full `HandlerContext` with `build: boolean`:
|
|
232
|
-
* - `ctx.build === true`: build-time, env/request/res throw at runtime
|
|
233
|
-
* - `ctx.build === false`: live request, full context available
|
|
234
|
-
*
|
|
235
|
-
* For `passthrough: false` (default), handlers receive `BuildContext` only.
|
|
236
|
-
*/
|
|
237
|
-
export type PrerenderPassthroughContext<
|
|
238
|
-
TParams = {},
|
|
239
|
-
TEnv = DefaultEnv,
|
|
240
|
-
> = HandlerContext<TParams, TEnv> & {
|
|
241
|
-
passthrough: () => PrerenderPassthroughResult;
|
|
242
|
-
};
|
|
243
|
-
|
|
244
260
|
export interface PrerenderHandlerDefinition<
|
|
245
261
|
TParams extends Record<string, any> = any,
|
|
246
262
|
> {
|
|
@@ -253,6 +269,8 @@ export interface PrerenderHandlerDefinition<
|
|
|
253
269
|
getParams?: (ctx: GetParamsContext) => Promise<TParams[]> | TParams[];
|
|
254
270
|
/** Pre-render options. */
|
|
255
271
|
options?: PrerenderOptions;
|
|
272
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
273
|
+
use?: () => UseItems<HandlerUseItem>;
|
|
256
274
|
}
|
|
257
275
|
|
|
258
276
|
// -- Overloads --------------------------------------------------------------
|
|
@@ -263,7 +281,7 @@ export interface PrerenderHandlerDefinition<
|
|
|
263
281
|
// Explicit params work as before:
|
|
264
282
|
// Prerender<{ slug: string }> → params = { slug: string }
|
|
265
283
|
|
|
266
|
-
// Overload 1: Static handler
|
|
284
|
+
// Overload 1: Static handler (build-time only)
|
|
267
285
|
export function Prerender<
|
|
268
286
|
T extends
|
|
269
287
|
| keyof DefaultPrerenderRouteMap
|
|
@@ -273,34 +291,15 @@ export function Prerender<
|
|
|
273
291
|
>(
|
|
274
292
|
handler: (
|
|
275
293
|
ctx: BuildContext<ResolvePrerenderParams<T, TRouteMap>>,
|
|
276
|
-
) => ReactNode | Promise<ReactNode>,
|
|
277
|
-
options?: PrerenderOptions & { passthrough?: false },
|
|
278
|
-
__injectedId?: string,
|
|
279
|
-
): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
|
|
280
|
-
|
|
281
|
-
// Overload 2: Static handler, passthrough (build + live — full HandlerContext)
|
|
282
|
-
export function Prerender<
|
|
283
|
-
T extends
|
|
284
|
-
| keyof DefaultPrerenderRouteMap
|
|
285
|
-
| `.${keyof TRouteMap & string}`
|
|
286
|
-
| Record<string, any> = {},
|
|
287
|
-
TRouteMap extends {} = DefaultPrerenderRouteMap,
|
|
288
|
-
TEnv = DefaultEnv,
|
|
289
|
-
>(
|
|
290
|
-
handler: (
|
|
291
|
-
ctx: PrerenderPassthroughContext<
|
|
292
|
-
ResolvePrerenderParams<T, TRouteMap>,
|
|
293
|
-
TEnv
|
|
294
|
-
>,
|
|
295
294
|
) =>
|
|
296
295
|
| ReactNode
|
|
297
296
|
| PrerenderPassthroughResult
|
|
298
297
|
| Promise<ReactNode | PrerenderPassthroughResult>,
|
|
299
|
-
options
|
|
298
|
+
options?: PrerenderOptions,
|
|
300
299
|
__injectedId?: string,
|
|
301
300
|
): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
|
|
302
301
|
|
|
303
|
-
// Overload
|
|
302
|
+
// Overload 2: Dynamic handler (build-time only)
|
|
304
303
|
export function Prerender<
|
|
305
304
|
T extends
|
|
306
305
|
| keyof DefaultPrerenderRouteMap
|
|
@@ -315,35 +314,11 @@ export function Prerender<
|
|
|
315
314
|
| ResolvePrerenderParams<T, TRouteMap>[],
|
|
316
315
|
handler: (
|
|
317
316
|
ctx: BuildContext<ResolvePrerenderParams<T, TRouteMap>>,
|
|
318
|
-
) => ReactNode | Promise<ReactNode>,
|
|
319
|
-
options?: PrerenderOptions & { passthrough?: false },
|
|
320
|
-
__injectedId?: string,
|
|
321
|
-
): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
|
|
322
|
-
|
|
323
|
-
// Overload 4: Dynamic handler, passthrough (build + live — full HandlerContext)
|
|
324
|
-
export function Prerender<
|
|
325
|
-
T extends
|
|
326
|
-
| keyof DefaultPrerenderRouteMap
|
|
327
|
-
| `.${keyof TRouteMap & string}`
|
|
328
|
-
| Record<string, any>,
|
|
329
|
-
TRouteMap extends {} = DefaultPrerenderRouteMap,
|
|
330
|
-
TEnv = DefaultEnv,
|
|
331
|
-
>(
|
|
332
|
-
getParams: (
|
|
333
|
-
ctx: GetParamsContext,
|
|
334
|
-
) =>
|
|
335
|
-
| Promise<ResolvePrerenderParams<T, TRouteMap>[]>
|
|
336
|
-
| ResolvePrerenderParams<T, TRouteMap>[],
|
|
337
|
-
handler: (
|
|
338
|
-
ctx: PrerenderPassthroughContext<
|
|
339
|
-
ResolvePrerenderParams<T, TRouteMap>,
|
|
340
|
-
TEnv
|
|
341
|
-
>,
|
|
342
317
|
) =>
|
|
343
318
|
| ReactNode
|
|
344
319
|
| PrerenderPassthroughResult
|
|
345
320
|
| Promise<ReactNode | PrerenderPassthroughResult>,
|
|
346
|
-
options
|
|
321
|
+
options?: PrerenderOptions,
|
|
347
322
|
__injectedId?: string,
|
|
348
323
|
): PrerenderHandlerDefinition<ResolvePrerenderParams<T, TRouteMap>>;
|
|
349
324
|
|
|
@@ -422,7 +397,7 @@ export function Prerender<TParams extends Record<string, any>>(
|
|
|
422
397
|
/**
|
|
423
398
|
* Sentinel returned by `ctx.passthrough()` to signal that a specific param set
|
|
424
399
|
* should not produce a local prerender artifact. The build skips writing the
|
|
425
|
-
* entry; at runtime the
|
|
400
|
+
* entry; at runtime the Passthrough live handler runs instead.
|
|
426
401
|
*/
|
|
427
402
|
export const PRERENDER_PASSTHROUGH: Readonly<{
|
|
428
403
|
__brand: "prerenderPassthrough";
|
|
@@ -446,7 +421,7 @@ export function isPrerenderPassthrough(
|
|
|
446
421
|
);
|
|
447
422
|
}
|
|
448
423
|
|
|
449
|
-
// -- Type
|
|
424
|
+
// -- Type guards ------------------------------------------------------------
|
|
450
425
|
|
|
451
426
|
/**
|
|
452
427
|
* Type guard to check if a value is a PrerenderHandlerDefinition.
|
|
@@ -461,3 +436,89 @@ export function isPrerenderHandler(
|
|
|
461
436
|
(value as { __brand: unknown }).__brand === "prerenderHandler"
|
|
462
437
|
);
|
|
463
438
|
}
|
|
439
|
+
|
|
440
|
+
// -- Passthrough wrapper ----------------------------------------------------
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* A prerender route with a live fallback handler for unknown params at runtime.
|
|
444
|
+
*
|
|
445
|
+
* Wraps a `Prerender(...)` definition with a separate handler that runs at
|
|
446
|
+
* request time for params not covered by `getParams()`.
|
|
447
|
+
*
|
|
448
|
+
* - Build time: `prerenderDef` provides getParams + build handler.
|
|
449
|
+
* - Runtime: `liveHandler` runs for unknown params with full HandlerContext.
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```ts
|
|
453
|
+
* const BlogPrerender = Prerender(
|
|
454
|
+
* async () => [{ slug: "getting-started" }, { slug: "api-reference" }],
|
|
455
|
+
* async (ctx) => <BlogPost slug={ctx.params.slug} />,
|
|
456
|
+
* );
|
|
457
|
+
*
|
|
458
|
+
* // In route definition:
|
|
459
|
+
* path("/blog/:slug", Passthrough(BlogPrerender, async (ctx) => {
|
|
460
|
+
* const post = await ctx.env.DB.get(ctx.params.slug);
|
|
461
|
+
* return <BlogPost slug={ctx.params.slug} post={post} />;
|
|
462
|
+
* }))
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
export interface PassthroughHandlerDefinition<
|
|
466
|
+
TParams extends Record<string, any> = any,
|
|
467
|
+
TEnv = DefaultEnv,
|
|
468
|
+
> {
|
|
469
|
+
readonly __brand: "passthroughHandler";
|
|
470
|
+
/** The underlying prerender definition (build-time rendering). */
|
|
471
|
+
prerenderDef: PrerenderHandlerDefinition<TParams>;
|
|
472
|
+
/** Live handler for runtime fallback on unknown params. */
|
|
473
|
+
liveHandler: (
|
|
474
|
+
ctx: HandlerContext<TParams, TEnv>,
|
|
475
|
+
) => ReactNode | Promise<ReactNode> | Response | Promise<Response>;
|
|
476
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
477
|
+
use?: () => UseItems<HandlerUseItem>;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function Passthrough<
|
|
481
|
+
TParams extends Record<string, any>,
|
|
482
|
+
TEnv = DefaultEnv,
|
|
483
|
+
>(
|
|
484
|
+
prerenderDef: PrerenderHandlerDefinition<TParams>,
|
|
485
|
+
liveHandler: (
|
|
486
|
+
ctx: HandlerContext<TParams, TEnv>,
|
|
487
|
+
) => ReactNode | Promise<ReactNode> | Response | Promise<Response>,
|
|
488
|
+
): PassthroughHandlerDefinition<TParams, TEnv>;
|
|
489
|
+
|
|
490
|
+
// Implementation
|
|
491
|
+
export function Passthrough<
|
|
492
|
+
TParams extends Record<string, any>,
|
|
493
|
+
TEnv = DefaultEnv,
|
|
494
|
+
>(
|
|
495
|
+
prerenderDef: PrerenderHandlerDefinition<TParams>,
|
|
496
|
+
liveHandler: (
|
|
497
|
+
ctx: HandlerContext<TParams, TEnv>,
|
|
498
|
+
) => ReactNode | Promise<ReactNode> | Response | Promise<Response>,
|
|
499
|
+
): PassthroughHandlerDefinition<TParams, TEnv> {
|
|
500
|
+
if (!isPrerenderHandler(prerenderDef)) {
|
|
501
|
+
throw new Error(
|
|
502
|
+
"[rsc-router] Passthrough: first argument must be a Prerender() definition.",
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
__brand: "passthroughHandler" as const,
|
|
507
|
+
prerenderDef,
|
|
508
|
+
liveHandler,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Type guard to check if a value is a PassthroughHandlerDefinition.
|
|
514
|
+
*/
|
|
515
|
+
export function isPassthroughHandler(
|
|
516
|
+
value: unknown,
|
|
517
|
+
): value is PassthroughHandlerDefinition {
|
|
518
|
+
return (
|
|
519
|
+
typeof value === "object" &&
|
|
520
|
+
value !== null &&
|
|
521
|
+
"__brand" in value &&
|
|
522
|
+
(value as { __brand: unknown }).__brand === "passthroughHandler"
|
|
523
|
+
);
|
|
524
|
+
}
|
package/src/reverse.ts
CHANGED
|
@@ -305,8 +305,25 @@ export function createReverse<TRoutes extends Record<string, string>>(
|
|
|
305
305
|
if (params) {
|
|
306
306
|
// Replace :param placeholders with actual values
|
|
307
307
|
// Strip constraint syntax: :param(a|b) -> use "param" as key
|
|
308
|
+
// Optional params (:param?) are omitted when not provided
|
|
309
|
+
let hadOmittedOptional = false;
|
|
308
310
|
result = result.replace(
|
|
309
|
-
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))
|
|
311
|
+
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(\?)/g,
|
|
312
|
+
(_, key, _constraint, optional) => {
|
|
313
|
+
const value = params[key];
|
|
314
|
+
// Empty string is treated as omitted — the trie matcher fills
|
|
315
|
+
// unmatched optional params with "" (not undefined), so reverse
|
|
316
|
+
// must collapse those segments instead of leaving empty slots.
|
|
317
|
+
if (value === undefined || value === "") {
|
|
318
|
+
hadOmittedOptional = true;
|
|
319
|
+
return "";
|
|
320
|
+
}
|
|
321
|
+
return encodeURIComponent(value);
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
// Second pass: required params (no trailing ?)
|
|
325
|
+
result = result.replace(
|
|
326
|
+
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(?!\?)/g,
|
|
310
327
|
(_, key) => {
|
|
311
328
|
const value = params[key];
|
|
312
329
|
if (value === undefined) {
|
|
@@ -315,6 +332,13 @@ export function createReverse<TRoutes extends Record<string, string>>(
|
|
|
315
332
|
return encodeURIComponent(value);
|
|
316
333
|
},
|
|
317
334
|
);
|
|
335
|
+
// Clean up slashes only when an optional param was actually omitted,
|
|
336
|
+
// so intentional trailing-slash patterns like "/blog/" are preserved.
|
|
337
|
+
if (hadOmittedOptional) {
|
|
338
|
+
const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
339
|
+
result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
|
|
340
|
+
if (hadTrailingSlash && !result.endsWith("/")) result += "/";
|
|
341
|
+
}
|
|
318
342
|
}
|
|
319
343
|
|
|
320
344
|
// Append search params as query string
|