@rangojs/router 0.0.0-experimental.1b930379 → 0.0.0-experimental.1fa245e2
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 +76 -18
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +558 -319
- package/package.json +16 -15
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +45 -4
- package/skills/links/SKILL.md +3 -1
- package/skills/loader/SKILL.md +53 -43
- package/skills/middleware/SKILL.md +2 -0
- package/skills/parallel/SKILL.md +126 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/route/SKILL.md +31 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/typesafety/SKILL.md +10 -0
- package/src/__internal.ts +1 -1
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/navigation-bridge.ts +19 -13
- package/src/browser/navigation-client.ts +115 -58
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +80 -15
- package/src/browser/prefetch/cache.ts +57 -5
- package/src/browser/prefetch/fetch.ts +38 -23
- package/src/browser/prefetch/queue.ts +92 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/react/Link.tsx +53 -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 +6 -1
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +36 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- 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.tsx +2 -56
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +3 -1
- package/src/index.ts +8 -0
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +22 -1
- package/src/route-definition/dsl-helpers.ts +73 -25
- package/src/route-definition/helpers-types.ts +10 -6
- 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 +79 -23
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +4 -1
- package/src/router/loader-resolution.ts +122 -10
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +9 -3
- package/src/router/match-api.ts +124 -189
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +88 -16
- 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 +22 -6
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +6 -8
- package/src/router/middleware.ts +4 -6
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/prerender-match.ts +110 -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 +183 -20
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +412 -297
- 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 +460 -368
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/rsc-rendering.ts +5 -0
- package/src/rsc/server-action.ts +2 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +8 -1
- package/src/segment-system.tsx +140 -4
- package/src/server/context.ts +140 -14
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +144 -18
- 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 +137 -33
- package/src/types/loader-types.ts +36 -9
- package/src/types/route-entry.ts +8 -1
- package/src/types/segments.ts +2 -0
- package/src/urls/path-helper-types.ts +9 -2
- 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 +73 -4
- 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 +14 -1
- 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/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 +153 -42
- package/src/vite/utils/banner.ts +3 -3
- package/src/vite/utils/prerender-utils.ts +18 -0
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
ResolvedRouteMap,
|
|
20
20
|
} from "./route-config.js";
|
|
21
21
|
import type { LoaderDefinition } from "./loader-types.js";
|
|
22
|
+
import type { UseItems, HandlerUseItem } from "../route-types.js";
|
|
22
23
|
|
|
23
24
|
// Re-export MiddlewareFn for internal/advanced use
|
|
24
25
|
export type { MiddlewareFn } from "../router/middleware.js";
|
|
@@ -135,7 +136,7 @@ export type Handler<
|
|
|
135
136
|
| Record<string, any> = {},
|
|
136
137
|
TRouteMap extends {} = DefaultHandlerRouteMap,
|
|
137
138
|
TEnv = DefaultEnv,
|
|
138
|
-
> = (
|
|
139
|
+
> = ((
|
|
139
140
|
ctx: HandlerContext<
|
|
140
141
|
T extends `.${infer Local}`
|
|
141
142
|
? Local extends keyof TRouteMap
|
|
@@ -160,7 +161,10 @@ export type Handler<
|
|
|
160
161
|
: ExtractSearchFromEntry<DefaultHandlerRouteMap, T>,
|
|
161
162
|
TRouteMap extends DefaultHandlerRouteMap ? never : TRouteMap
|
|
162
163
|
>,
|
|
163
|
-
) => ReactNode | Promise<ReactNode> | Response | Promise<Response
|
|
164
|
+
) => ReactNode | Promise<ReactNode> | Response | Promise<Response>) & {
|
|
165
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
166
|
+
use?: () => UseItems<HandlerUseItem>;
|
|
167
|
+
};
|
|
164
168
|
|
|
165
169
|
/**
|
|
166
170
|
* Context passed to handlers (Hono-inspired type-safe context)
|
|
@@ -170,7 +174,7 @@ export type Handler<
|
|
|
170
174
|
* - Cleaned route URL (`url`, `searchParams`, `pathname` — no `_rsc*` params)
|
|
171
175
|
* - Original request (`request` — raw transport URL, headers, method, body)
|
|
172
176
|
* - Platform bindings (env.DB, env.KV, env.SECRETS)
|
|
173
|
-
* - Middleware variables (
|
|
177
|
+
* - Middleware variables (`get("user")`, `get("permissions")`)
|
|
174
178
|
* - Getter/setter for variables (get('user'), set('user', ...))
|
|
175
179
|
*
|
|
176
180
|
* @example
|
|
@@ -178,8 +182,7 @@ export type Handler<
|
|
|
178
182
|
* const handler = (ctx: HandlerContext<{ slug: string }, AppEnv>) => {
|
|
179
183
|
* ctx.params.slug // Route param (string)
|
|
180
184
|
* ctx.env.DB // Binding (D1Database)
|
|
181
|
-
* ctx.
|
|
182
|
-
* ctx.get('user') // Alternative getter
|
|
185
|
+
* ctx.get('user') // Variable (User | undefined)
|
|
183
186
|
* ctx.set('user', {...}) // Setter
|
|
184
187
|
* ctx.url // Clean URL (no _rsc* params)
|
|
185
188
|
* ctx.searchParams // Clean params (no _rsc* params)
|
|
@@ -206,6 +209,12 @@ export type HandlerContext<
|
|
|
206
209
|
* Live request rendering, including passthrough fallback, uses `false`.
|
|
207
210
|
*/
|
|
208
211
|
build: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* True when running in Vite dev mode, false during production build or
|
|
214
|
+
* live request rendering. Use this to branch on runtime mode without
|
|
215
|
+
* changing build semantics (e.g., skip expensive operations in dev).
|
|
216
|
+
*/
|
|
217
|
+
dev: boolean;
|
|
209
218
|
/**
|
|
210
219
|
* The original incoming Request object (transport URL intact).
|
|
211
220
|
* Use `ctx.url` / `ctx.searchParams` for application logic — those have
|
|
@@ -244,14 +253,9 @@ export type HandlerContext<
|
|
|
244
253
|
* Access resources like `ctx.env.DB`, `ctx.env.KV`.
|
|
245
254
|
*/
|
|
246
255
|
env: TEnv;
|
|
247
|
-
/**
|
|
248
|
-
* Middleware-injected variables.
|
|
249
|
-
* Access values like `ctx.var.user`, `ctx.var.permissions`.
|
|
250
|
-
*/
|
|
251
|
-
var: DefaultVars;
|
|
252
256
|
/**
|
|
253
257
|
* Type-safe getter for middleware variables.
|
|
254
|
-
*
|
|
258
|
+
* Preferred way to read middleware-injected variables.
|
|
255
259
|
*
|
|
256
260
|
* @example
|
|
257
261
|
* ```typescript
|
|
@@ -272,8 +276,16 @@ export type HandlerContext<
|
|
|
272
276
|
* ```
|
|
273
277
|
*/
|
|
274
278
|
set: {
|
|
275
|
-
<T>(
|
|
276
|
-
|
|
279
|
+
<T>(
|
|
280
|
+
contextVar: ContextVar<T>,
|
|
281
|
+
value: T,
|
|
282
|
+
options?: { cache?: boolean },
|
|
283
|
+
): void;
|
|
284
|
+
} & (<K extends keyof DefaultVars>(
|
|
285
|
+
key: K,
|
|
286
|
+
value: DefaultVars[K],
|
|
287
|
+
options?: { cache?: boolean },
|
|
288
|
+
) => void);
|
|
277
289
|
/**
|
|
278
290
|
* Response headers. Headers set here are merged into the final response.
|
|
279
291
|
*
|
|
@@ -289,8 +301,15 @@ export type HandlerContext<
|
|
|
289
301
|
/**
|
|
290
302
|
* Access loader data or push handle data.
|
|
291
303
|
*
|
|
304
|
+
* Available in route handlers, layout handlers, middleware, server actions,
|
|
305
|
+
* and server components rendered within the request context.
|
|
306
|
+
*
|
|
292
307
|
* For loaders: Returns a promise that resolves to the loader data.
|
|
293
308
|
* Loaders are executed in parallel and memoized per request.
|
|
309
|
+
* Prefer DSL `loader()` + client `useLoader()` over `ctx.use(Loader)` —
|
|
310
|
+
* DSL loaders are always fresh and cache-safe. Use `ctx.use(Loader)` only
|
|
311
|
+
* when you need loader data in the handler itself (e.g., to set context
|
|
312
|
+
* variables or make routing decisions).
|
|
294
313
|
*
|
|
295
314
|
* For handles: Returns a push function to add data for this segment.
|
|
296
315
|
* Handle data accumulates across all matched route segments.
|
|
@@ -298,10 +317,11 @@ export type HandlerContext<
|
|
|
298
317
|
*
|
|
299
318
|
* @example
|
|
300
319
|
* ```typescript
|
|
301
|
-
* // Loader
|
|
302
|
-
* route("
|
|
303
|
-
* const
|
|
304
|
-
*
|
|
320
|
+
* // Loader escape hatch — use when handler needs the data directly
|
|
321
|
+
* route("product", async (ctx) => {
|
|
322
|
+
* const { product } = await ctx.use(ProductLoader);
|
|
323
|
+
* ctx.set(Product, product); // make available to children
|
|
324
|
+
* return <ProductPage />;
|
|
305
325
|
* });
|
|
306
326
|
*
|
|
307
327
|
* // Handle usage - direct value
|
|
@@ -432,6 +452,8 @@ export type InternalHandlerContext<
|
|
|
432
452
|
> = HandlerContext<TParams, TEnv, TSearch> & {
|
|
433
453
|
/** @internal Stub response for collecting headers/cookies. */
|
|
434
454
|
res: Response;
|
|
455
|
+
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
456
|
+
_variables: Record<string, any>;
|
|
435
457
|
/** Prerender-only control flow helper, attached when the runtime context supports it. */
|
|
436
458
|
passthrough?: () => unknown;
|
|
437
459
|
/** Current segment ID for handle data attribution. */
|
|
@@ -519,30 +541,112 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
519
541
|
* })
|
|
520
542
|
* ```
|
|
521
543
|
*/
|
|
544
|
+
/**
|
|
545
|
+
* Revalidation function called during client-side navigation to decide whether
|
|
546
|
+
* a segment (layout, route, parallel slot, or loader) should be re-rendered.
|
|
547
|
+
*
|
|
548
|
+
* Return `true` to re-render, `false` to skip (keep client's current version),
|
|
549
|
+
* or `{ defaultShouldRevalidate: boolean }` to override the default for
|
|
550
|
+
* downstream segments.
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* ```ts
|
|
554
|
+
* // Re-render only when a cart action happened or browser signals staleness
|
|
555
|
+
* revalidate(({ actionId, stale }) =>
|
|
556
|
+
* actionId?.includes("cart") || stale || false
|
|
557
|
+
* )
|
|
558
|
+
*
|
|
559
|
+
* // Always re-render when params change (default behavior made explicit)
|
|
560
|
+
* revalidate(({ defaultShouldRevalidate }) => defaultShouldRevalidate)
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
522
563
|
export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
564
|
+
/** Route params from the page being navigated away from. */
|
|
523
565
|
currentParams: TParams;
|
|
566
|
+
/** Full URL of the page being navigated away from. */
|
|
524
567
|
currentUrl: URL;
|
|
568
|
+
/** Route params for the navigation target. */
|
|
525
569
|
nextParams: TParams;
|
|
570
|
+
/** Full URL of the navigation target. */
|
|
526
571
|
nextUrl: URL;
|
|
572
|
+
/**
|
|
573
|
+
* The router's default revalidation decision for this segment.
|
|
574
|
+
* `true` when params changed or the segment is new to the client.
|
|
575
|
+
* Return this when you want default behavior plus your own conditions.
|
|
576
|
+
*/
|
|
527
577
|
defaultShouldRevalidate: boolean;
|
|
578
|
+
/** Full handler context — access to `ctx.use()`, `ctx.env`, `ctx.params`, etc. */
|
|
528
579
|
context: HandlerContext<TParams, TEnv>;
|
|
529
|
-
|
|
580
|
+
|
|
581
|
+
// ── Segment metadata (which segment is being evaluated) ──────────────
|
|
582
|
+
|
|
583
|
+
/** The type of segment being revalidated. */
|
|
530
584
|
segmentType: "layout" | "route" | "parallel";
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
585
|
+
/** Layout name (e.g., `"root"`, `"shop"`, `"auth"`). Only set for layout segments. */
|
|
586
|
+
layoutName?: string;
|
|
587
|
+
/** Slot name (e.g., `"@sidebar"`, `"@modal"`). Only set for parallel segments. */
|
|
588
|
+
slotName?: string;
|
|
589
|
+
|
|
590
|
+
// ── Action context (populated when revalidation is triggered by a server action) ──
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Identifier of the server action that triggered revalidation.
|
|
594
|
+
* `undefined` during normal navigation (no action involved).
|
|
595
|
+
*
|
|
596
|
+
* Format: `"src/<path>#<exportName>"` — the file path is the source path
|
|
597
|
+
* relative to the project root, followed by `#` and the exported function name.
|
|
598
|
+
*
|
|
599
|
+
* This is stable and can be used for path-based matching to revalidate
|
|
600
|
+
* when any action in a module or directory fires:
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* ```ts
|
|
604
|
+
* // Match a specific action
|
|
605
|
+
* revalidate(({ actionId }) => actionId === "src/actions/cart.ts#addToCart")
|
|
606
|
+
*
|
|
607
|
+
* // Match any action in the cart module
|
|
608
|
+
* revalidate(({ actionId }) => actionId?.includes("cart") ?? false)
|
|
609
|
+
*
|
|
610
|
+
* // Match any action under src/apps/store/actions/
|
|
611
|
+
* revalidate(({ actionId }) => actionId?.startsWith("src/apps/store/actions/") ?? false)
|
|
612
|
+
* ```
|
|
613
|
+
*/
|
|
614
|
+
actionId?: string;
|
|
615
|
+
/** URL where the action was executed (the page the user was on when they triggered the action). */
|
|
616
|
+
actionUrl?: URL;
|
|
617
|
+
/** Return value from the action execution. Can be used to conditionally revalidate based on the action's outcome. */
|
|
618
|
+
actionResult?: any;
|
|
619
|
+
/** FormData from the action request body. Only set for form-based actions (not inline `"use server"` actions). */
|
|
620
|
+
formData?: FormData;
|
|
621
|
+
/** HTTP method: `"GET"` for navigation, `"POST"` for server actions. */
|
|
622
|
+
method?: string;
|
|
623
|
+
|
|
624
|
+
// ── Route identity ───────────────────────────────────────────────────
|
|
625
|
+
|
|
626
|
+
/** Route name of the navigation target. Alias for `toRouteName`. */
|
|
627
|
+
routeName?: DefaultRouteName;
|
|
628
|
+
/**
|
|
629
|
+
* Route name being navigated away from.
|
|
630
|
+
* `undefined` for unnamed internal routes (those without a `name` option).
|
|
631
|
+
*/
|
|
632
|
+
fromRouteName?: DefaultRouteName;
|
|
633
|
+
/**
|
|
634
|
+
* Route name being navigated to.
|
|
635
|
+
* `undefined` for unnamed internal routes (those without a `name` option).
|
|
636
|
+
*/
|
|
637
|
+
toRouteName?: DefaultRouteName;
|
|
638
|
+
|
|
639
|
+
// ── Staleness signal ─────────────────────────────────────────────────
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* `true` when the browser signals that data may be stale — typically because
|
|
643
|
+
* a server action was executed in this or another tab (`_rsc_stale` header).
|
|
644
|
+
*
|
|
645
|
+
* This is NOT segment cache staleness (loaders are never segment-cached).
|
|
646
|
+
* Use this to decide whether loader data should be re-fetched after an
|
|
647
|
+
* action that may have mutated backend state.
|
|
648
|
+
*/
|
|
649
|
+
stale?: boolean;
|
|
546
650
|
}) => boolean | { defaultShouldRevalidate: boolean };
|
|
547
651
|
|
|
548
652
|
// MiddlewareFn is imported from "../router/middleware.js" and re-exported
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContextVar } from "../context-var.js";
|
|
2
|
+
import type { Handle } from "../handle.js";
|
|
2
3
|
import type { MiddlewareFn } from "../router/middleware.js";
|
|
3
4
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
4
5
|
import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
|
|
@@ -53,16 +54,42 @@ export type LoaderContext<
|
|
|
53
54
|
pathname: string;
|
|
54
55
|
url: URL;
|
|
55
56
|
env: TEnv;
|
|
56
|
-
var: DefaultVars;
|
|
57
57
|
get: {
|
|
58
58
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
59
59
|
} & (<K extends keyof DefaultVars>(key: K) => DefaultVars[K]);
|
|
60
60
|
/**
|
|
61
|
-
* Access another loader's data
|
|
61
|
+
* Access another loader's data, or read handle data after rendered().
|
|
62
|
+
*
|
|
63
|
+
* For loaders: returns a promise (loaders run in parallel).
|
|
64
|
+
* For handles: returns collected data (only after `await ctx.rendered()`).
|
|
62
65
|
*/
|
|
63
|
-
use:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
use: {
|
|
67
|
+
<T, TLoaderParams = any>(
|
|
68
|
+
loader: LoaderDefinition<T, TLoaderParams>,
|
|
69
|
+
): Promise<T>;
|
|
70
|
+
<TData, TAccumulated = TData[]>(
|
|
71
|
+
handle: Handle<TData, TAccumulated>,
|
|
72
|
+
): TAccumulated;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* **Experimental.** Wait for all non-loader segments to settle.
|
|
76
|
+
*
|
|
77
|
+
* After the returned promise resolves, handle data is available via
|
|
78
|
+
* `ctx.use(handle)`. Only supported in DSL loaders on non-streaming
|
|
79
|
+
* trees (no `loading()`). Throws if called from a handler-invoked
|
|
80
|
+
* loader or when the tree uses streaming.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const PricesLoader = createLoader(async (ctx) => {
|
|
85
|
+
* "use server";
|
|
86
|
+
* await ctx.rendered();
|
|
87
|
+
* const products = ctx.use(Products); // reads handle data
|
|
88
|
+
* return pricing.getLive(products.map(p => p.id));
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
rendered: () => Promise<void>;
|
|
66
93
|
/**
|
|
67
94
|
* HTTP method (GET, POST, PUT, PATCH, DELETE)
|
|
68
95
|
* Available when loader is called via load({ method: "POST", ... })
|
|
@@ -166,11 +193,11 @@ export type LoadOptions =
|
|
|
166
193
|
* return await db.products.findBySlug(slug);
|
|
167
194
|
* });
|
|
168
195
|
*
|
|
169
|
-
* //
|
|
170
|
-
* const
|
|
196
|
+
* // Client usage (preferred — cache-safe, always fresh)
|
|
197
|
+
* const { data } = useLoader(CartLoader);
|
|
171
198
|
*
|
|
172
|
-
* //
|
|
173
|
-
* const cart =
|
|
199
|
+
* // Server escape hatch (handler needs data directly)
|
|
200
|
+
* const cart = await ctx.use(CartLoader);
|
|
174
201
|
* ```
|
|
175
202
|
*/
|
|
176
203
|
export type LoaderDefinition<
|
package/src/types/route-entry.ts
CHANGED
|
@@ -55,6 +55,13 @@ export interface RouteEntry<TEnv = any> {
|
|
|
55
55
|
| Promise<() => Array<AllUseItems>>;
|
|
56
56
|
mountIndex: number;
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Router ID that owns this entry. Used to namespace the manifest cache
|
|
60
|
+
* so multi-router setups (host routing) don't share cached EntryData
|
|
61
|
+
* across routers with overlapping mountIndex + routeKey combinations.
|
|
62
|
+
*/
|
|
63
|
+
routerId?: string;
|
|
64
|
+
|
|
58
65
|
/**
|
|
59
66
|
* Route keys in this entry that have pre-render handlers.
|
|
60
67
|
* Used by the non-trie match path to set the `pr` flag.
|
|
@@ -62,7 +69,7 @@ export interface RouteEntry<TEnv = any> {
|
|
|
62
69
|
prerenderRouteKeys?: Set<string>;
|
|
63
70
|
|
|
64
71
|
/**
|
|
65
|
-
* Route keys in this entry that
|
|
72
|
+
* Route keys in this entry that are wrapped with `Passthrough()`.
|
|
66
73
|
* Used by the non-trie match path to set the `pt` flag.
|
|
67
74
|
*/
|
|
68
75
|
passthroughRouteKeys?: Set<string>;
|
package/src/types/segments.ts
CHANGED
|
@@ -51,9 +51,11 @@ export interface ResolvedSegment {
|
|
|
51
51
|
// Loader-specific fields
|
|
52
52
|
loaderId?: string; // For loaders: the loader $$id identifier
|
|
53
53
|
loaderData?: any; // For loaders: the resolved data from loader execution
|
|
54
|
+
parallelLoading?: ReactNode; // For parallel-owned loaders: the parallel's loading fallback
|
|
54
55
|
// Intercept loader fields (for streaming loader data in parallel segments)
|
|
55
56
|
loaderDataPromise?: Promise<any[]> | any[]; // Loader data promise or resolved array
|
|
56
57
|
loaderIds?: string[]; // IDs ($$id) of loaders for this segment
|
|
58
|
+
parallelLoaderSources?: any[]; // Internal: preserves stable aggregate promise across renders
|
|
57
59
|
// Error-specific fields
|
|
58
60
|
error?: ErrorInfo; // For error segments: the error information
|
|
59
61
|
// NotFound-specific fields
|
|
@@ -37,7 +37,10 @@ import type {
|
|
|
37
37
|
UseItems,
|
|
38
38
|
} from "../route-types.js";
|
|
39
39
|
import type { SearchSchema } from "../search-params.js";
|
|
40
|
-
import type {
|
|
40
|
+
import type {
|
|
41
|
+
PrerenderHandlerDefinition,
|
|
42
|
+
PassthroughHandlerDefinition,
|
|
43
|
+
} from "../prerender.js";
|
|
41
44
|
import type { StaticHandlerDefinition } from "../static-handler.js";
|
|
42
45
|
import type { InterceptWhenFn } from "../server/context";
|
|
43
46
|
import type {
|
|
@@ -70,6 +73,7 @@ export type PathFn<TEnv> = <
|
|
|
70
73
|
ctx: HandlerContext<TParams, TEnv, TSearch>,
|
|
71
74
|
) => ReactNode | Promise<ReactNode> | Response | Promise<Response>)
|
|
72
75
|
| PrerenderHandlerDefinition<TParams>
|
|
76
|
+
| PassthroughHandlerDefinition<TParams, TEnv>
|
|
73
77
|
| StaticHandlerDefinition<TParams>,
|
|
74
78
|
optionsOrUse?: PathOptions<TName, TSearch> | (() => UseItems<RouteUseItem>),
|
|
75
79
|
use?: () => UseItems<RouteUseItem>,
|
|
@@ -280,7 +284,10 @@ export type PathHelpers<TEnv> = {
|
|
|
280
284
|
/**
|
|
281
285
|
* Attach a loading component to the current route/layout
|
|
282
286
|
*/
|
|
283
|
-
loading: (
|
|
287
|
+
loading: (
|
|
288
|
+
component: ReactNode | (() => ReactNode),
|
|
289
|
+
options?: { ssr?: boolean },
|
|
290
|
+
) => LoadingItem;
|
|
284
291
|
|
|
285
292
|
/**
|
|
286
293
|
* Attach an error boundary to catch errors in this segment
|
package/src/urls/path-helper.ts
CHANGED
|
@@ -12,10 +12,11 @@ import {
|
|
|
12
12
|
getNamePrefix,
|
|
13
13
|
getRootScoped,
|
|
14
14
|
} from "../server/context";
|
|
15
|
-
import { invariant } from "../errors";
|
|
15
|
+
import { invariant, DataNotFoundError } from "../errors";
|
|
16
16
|
import { validateUserRouteName } from "../route-name.js";
|
|
17
17
|
import {
|
|
18
18
|
isPrerenderHandler,
|
|
19
|
+
isPassthroughHandler,
|
|
19
20
|
type PrerenderHandlerDefinition,
|
|
20
21
|
} from "../prerender.js";
|
|
21
22
|
import {
|
|
@@ -34,6 +35,10 @@ import type {
|
|
|
34
35
|
JsonResponsePathFn,
|
|
35
36
|
TextResponsePathFn,
|
|
36
37
|
} from "./path-helper-types.js";
|
|
38
|
+
import {
|
|
39
|
+
resolveHandlerUse,
|
|
40
|
+
mergeHandlerUse,
|
|
41
|
+
} from "../route-definition/resolve-handler-use.js";
|
|
37
42
|
|
|
38
43
|
/**
|
|
39
44
|
* Check if a value is a valid use item
|
|
@@ -142,6 +147,12 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
|
|
|
142
147
|
use = maybeUse;
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
// Merge handler.use() defaults with explicit use()
|
|
151
|
+
// Response routes (path.json, path.text, etc.) only allow middleware + cache
|
|
152
|
+
const handlerUseFn = resolveHandlerUse(handler);
|
|
153
|
+
const mountSite = resolveResponseType(options) ? "response" : "path";
|
|
154
|
+
const mergedUse = mergeHandlerUse(handlerUseFn, use, mountSite);
|
|
155
|
+
|
|
145
156
|
// Get prefixes from context (set by include())
|
|
146
157
|
const urlPrefix = getUrlPrefix();
|
|
147
158
|
const namePrefix = getNamePrefix();
|
|
@@ -176,14 +187,31 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
|
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
// Ensure handler is always a function (wrap ReactNode or extract from prerender/static def)
|
|
190
|
+
// For prerender stubs (production builds where handler code is evicted),
|
|
191
|
+
// handler.handler is undefined — provide a notFound fallback so requests
|
|
192
|
+
// for non-prerendered params get 404 instead of "handler is not a function".
|
|
179
193
|
const wrappedHandler: Handler<any, any, TEnv> =
|
|
180
194
|
typeof handler === "function"
|
|
181
195
|
? (handler as Handler<any, any, TEnv>)
|
|
182
|
-
:
|
|
183
|
-
?
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
196
|
+
: isPassthroughHandler(handler)
|
|
197
|
+
? typeof handler.prerenderDef.handler === "function"
|
|
198
|
+
? (handler.prerenderDef.handler as Handler<any, any, TEnv>)
|
|
199
|
+
: () => {
|
|
200
|
+
throw new DataNotFoundError(
|
|
201
|
+
"No prerender data found for this route",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
: isPrerenderHandler(handler)
|
|
205
|
+
? typeof handler.handler === "function"
|
|
206
|
+
? (handler.handler as Handler<any, any, TEnv>)
|
|
207
|
+
: () => {
|
|
208
|
+
throw new DataNotFoundError(
|
|
209
|
+
"No prerender data found for this route",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
: isStaticHandler(handler)
|
|
213
|
+
? (handler.handler as Handler<any, any, TEnv>)
|
|
214
|
+
: () => handler;
|
|
187
215
|
|
|
188
216
|
const entry = {
|
|
189
217
|
id: namespace,
|
|
@@ -199,16 +227,23 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
|
|
|
199
227
|
errorBoundary: [],
|
|
200
228
|
notFoundBoundary: [],
|
|
201
229
|
layout: [],
|
|
202
|
-
parallel:
|
|
230
|
+
parallel: {},
|
|
203
231
|
intercept: [],
|
|
204
232
|
loader: [],
|
|
205
233
|
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
206
|
-
...(
|
|
234
|
+
...(isPassthroughHandler(handler)
|
|
207
235
|
? {
|
|
208
236
|
isPrerender: true as const,
|
|
209
|
-
prerenderDef: handler as PrerenderHandlerDefinition,
|
|
237
|
+
prerenderDef: handler.prerenderDef as PrerenderHandlerDefinition,
|
|
238
|
+
isPassthrough: true as const,
|
|
239
|
+
liveHandler: handler.liveHandler as Handler<any, any, TEnv>,
|
|
210
240
|
}
|
|
211
|
-
:
|
|
241
|
+
: isPrerenderHandler(handler)
|
|
242
|
+
? {
|
|
243
|
+
isPrerender: true as const,
|
|
244
|
+
prerenderDef: handler as PrerenderHandlerDefinition,
|
|
245
|
+
}
|
|
246
|
+
: {}),
|
|
212
247
|
...(isStaticHandler(handler)
|
|
213
248
|
? {
|
|
214
249
|
isStaticPrerender: true as const,
|
|
@@ -264,9 +299,9 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
|
|
|
264
299
|
registerSearchSchema(routeName, options.search);
|
|
265
300
|
}
|
|
266
301
|
|
|
267
|
-
// Run use callback if
|
|
268
|
-
if (
|
|
269
|
-
const result = store.run(namespace, entry,
|
|
302
|
+
// Run merged use callback (handler.use defaults + explicit use) if present
|
|
303
|
+
if (mergedUse) {
|
|
304
|
+
const result = store.run(namespace, entry, mergedUse)?.flat(3);
|
|
270
305
|
invariant(
|
|
271
306
|
Array.isArray(result) && result.every((item) => isValidUseItem(item)),
|
|
272
307
|
`path() use() callback must return an array of use items [${namespace}]`,
|
|
@@ -7,6 +7,18 @@ import type {
|
|
|
7
7
|
} from "../route-types.js";
|
|
8
8
|
import type { SearchSchema } from "../search-params.js";
|
|
9
9
|
import { RESPONSE_TYPE } from "./response-types.js";
|
|
10
|
+
import type { DefaultEnv } from "../types.js";
|
|
11
|
+
import type { PathHelpers } from "./path-helper-types.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builder function accepted by urls() and as a shorthand for routes()/urls option.
|
|
15
|
+
* When passed directly to routes() or createRouter({ urls }), it is wrapped in urls() automatically.
|
|
16
|
+
*/
|
|
17
|
+
export type UrlBuilder<
|
|
18
|
+
TEnv = DefaultEnv,
|
|
19
|
+
TItems extends readonly (AllUseItems | readonly AllUseItems[])[] =
|
|
20
|
+
readonly AllUseItems[],
|
|
21
|
+
> = (helpers: PathHelpers<TEnv>) => TItems;
|
|
10
22
|
|
|
11
23
|
/**
|
|
12
24
|
* Sentinel type for unnamed routes.
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
DefaultReverseRouteMap,
|
|
5
5
|
DefaultVars,
|
|
6
6
|
} from "../types/global-namespace.js";
|
|
7
|
+
import type { UseItems, ResponseRouteUseItem } from "../route-types.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Reverse function for response handler contexts.
|
|
@@ -38,9 +39,12 @@ export const RESPONSE_TYPE: unique symbol = Symbol.for(
|
|
|
38
39
|
* Handler that must return Response (not ReactNode).
|
|
39
40
|
* Used by path.image(), path.stream(), path.any() (binary/streaming data).
|
|
40
41
|
*/
|
|
41
|
-
export type ResponseHandler<TParams = Record<string, string>, TEnv = any> = (
|
|
42
|
+
export type ResponseHandler<TParams = Record<string, string>, TEnv = any> = ((
|
|
42
43
|
ctx: ResponseHandlerContext<TParams, TEnv>,
|
|
43
|
-
) => Response | Promise<Response
|
|
44
|
+
) => Response | Promise<Response>) & {
|
|
45
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
46
|
+
use?: () => UseItems<ResponseRouteUseItem>;
|
|
47
|
+
};
|
|
44
48
|
|
|
45
49
|
/**
|
|
46
50
|
* JSON-serializable value type for auto-wrap support.
|
|
@@ -60,9 +64,12 @@ export type JsonValue =
|
|
|
60
64
|
export type JsonResponseHandler<
|
|
61
65
|
TParams = Record<string, string>,
|
|
62
66
|
TEnv = any,
|
|
63
|
-
> = (
|
|
67
|
+
> = ((
|
|
64
68
|
ctx: ResponseHandlerContext<TParams, TEnv>,
|
|
65
|
-
) => JsonValue | Response | Promise<JsonValue | Response
|
|
69
|
+
) => JsonValue | Response | Promise<JsonValue | Response>) & {
|
|
70
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
71
|
+
use?: () => UseItems<ResponseRouteUseItem>;
|
|
72
|
+
};
|
|
66
73
|
|
|
67
74
|
/**
|
|
68
75
|
* Handler for text-based response routes (text, html, xml).
|
|
@@ -71,9 +78,12 @@ export type JsonResponseHandler<
|
|
|
71
78
|
export type TextResponseHandler<
|
|
72
79
|
TParams = Record<string, string>,
|
|
73
80
|
TEnv = any,
|
|
74
|
-
> = (
|
|
81
|
+
> = ((
|
|
75
82
|
ctx: ResponseHandlerContext<TParams, TEnv>,
|
|
76
|
-
) => string | Response | Promise<string | Response
|
|
83
|
+
) => string | Response | Promise<string | Response>) & {
|
|
84
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
85
|
+
use?: () => UseItems<ResponseRouteUseItem>;
|
|
86
|
+
};
|
|
77
87
|
|
|
78
88
|
/**
|
|
79
89
|
* Lighter handler context for response routes.
|