@rangojs/router 0.0.0-experimental.b02a2fec → 0.0.0-experimental.b30bbf02
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 +112 -17
- package/dist/vite/index.js +1338 -462
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +7 -5
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +33 -20
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +90 -16
- package/skills/loader/SKILL.md +70 -3
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/parallel/SKILL.md +66 -0
- package/skills/rango/SKILL.md +25 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +24 -0
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +3 -1
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +44 -4
- package/src/browser/navigation-bridge.ts +71 -5
- package/src/browser/navigation-client.ts +64 -13
- package/src/browser/navigation-store.ts +25 -1
- package/src/browser/partial-update.ts +34 -3
- package/src/browser/prefetch/cache.ts +129 -21
- package/src/browser/prefetch/fetch.ts +148 -16
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- package/src/browser/react/NavigationProvider.tsx +70 -18
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +11 -1
- package/src/browser/react/use-router.ts +8 -1
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/rsc-router.tsx +34 -6
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/types.ts +19 -0
- package/src/build/route-trie.ts +50 -24
- package/src/cache/cf/cf-cache-store.ts +5 -7
- package/src/client.tsx +82 -174
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +40 -9
- package/src/outlet-context.ts +1 -1
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +7 -3
- package/src/route-definition/dsl-helpers.ts +175 -23
- package/src/route-definition/helpers-types.ts +63 -14
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-types.ts +7 -0
- package/src/router/handler-context.ts +24 -4
- package/src/router/lazy-includes.ts +6 -6
- package/src/router/loader-resolution.ts +3 -0
- package/src/router/manifest.ts +22 -13
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +1 -0
- package/src/router/match-result.ts +21 -2
- package/src/router/middleware-types.ts +2 -22
- package/src/router/middleware.ts +54 -7
- package/src/router/pattern-matching.ts +87 -17
- package/src/router/revalidation.ts +15 -1
- package/src/router/segment-resolution/fresh.ts +8 -0
- package/src/router/segment-resolution/revalidation.ts +128 -100
- package/src/router/trie-matching.ts +18 -13
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +1 -2
- package/src/rsc/handler.ts +8 -4
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +10 -0
- package/src/rsc/server-action.ts +4 -0
- package/src/rsc/types.ts +6 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +11 -61
- package/src/server/context.ts +26 -3
- package/src/server/request-context.ts +10 -42
- package/src/ssr/index.tsx +5 -1
- package/src/types/handler-context.ts +12 -39
- package/src/types/loader-types.ts +5 -6
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +17 -1
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +30 -4
- package/src/urls/response-types.ts +2 -10
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +31 -3
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +48 -1
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/plugins/cjs-to-esm.ts +5 -0
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +16 -4
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +52 -28
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +516 -486
- package/src/vite/plugins/performance-tracks.ts +17 -9
- package/src/vite/plugins/use-cache-transform.ts +56 -43
- package/src/vite/plugins/version-injector.ts +37 -11
- package/src/vite/rango.ts +49 -14
- package/src/vite/router-discovery.ts +558 -53
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +20 -6
|
@@ -37,6 +37,8 @@ import { track, type MetricsStore } from "./context.js";
|
|
|
37
37
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
38
38
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
39
39
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
40
|
+
import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
|
|
41
|
+
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
40
42
|
import { THEME_COOKIE } from "../theme/constants.js";
|
|
41
43
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
42
44
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
@@ -58,22 +60,7 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
58
60
|
export interface RequestContext<
|
|
59
61
|
TEnv = DefaultEnv,
|
|
60
62
|
TParams = Record<string, string>,
|
|
61
|
-
> {
|
|
62
|
-
/** Platform bindings (Cloudflare env, etc.) */
|
|
63
|
-
env: TEnv;
|
|
64
|
-
/** Original HTTP request */
|
|
65
|
-
request: Request;
|
|
66
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
67
|
-
url: URL;
|
|
68
|
-
/**
|
|
69
|
-
* The original request URL with all parameters intact, including
|
|
70
|
-
* internal `_rsc*` transport params.
|
|
71
|
-
*/
|
|
72
|
-
originalUrl: URL;
|
|
73
|
-
/** URL pathname */
|
|
74
|
-
pathname: string;
|
|
75
|
-
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
76
|
-
searchParams: URLSearchParams;
|
|
63
|
+
> extends RequestScope<TEnv> {
|
|
77
64
|
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
78
65
|
_variables: Record<string, any>;
|
|
79
66
|
/** Get a variable set by middleware */
|
|
@@ -159,20 +146,6 @@ export interface RequestContext<
|
|
|
159
146
|
import("../cache/profile-registry.js").CacheProfile
|
|
160
147
|
>;
|
|
161
148
|
|
|
162
|
-
/**
|
|
163
|
-
* Schedule work to run after the response is sent.
|
|
164
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
165
|
-
* On Node.js, runs as fire-and-forget.
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* ```typescript
|
|
169
|
-
* ctx.waitUntil(async () => {
|
|
170
|
-
* await cacheStore.set(key, data, ttl);
|
|
171
|
-
* });
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
175
|
-
|
|
176
149
|
/**
|
|
177
150
|
* Register a callback to run when the response is created.
|
|
178
151
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -498,13 +471,7 @@ export function requireRequestContext<
|
|
|
498
471
|
return getRequestContext<TEnv>();
|
|
499
472
|
}
|
|
500
473
|
|
|
501
|
-
|
|
502
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
503
|
-
*/
|
|
504
|
-
export interface ExecutionContext {
|
|
505
|
-
waitUntil(promise: Promise<any>): void;
|
|
506
|
-
passThroughOnException(): void;
|
|
507
|
-
}
|
|
474
|
+
export type { ExecutionContext };
|
|
508
475
|
|
|
509
476
|
/**
|
|
510
477
|
* Options for creating a request context
|
|
@@ -768,16 +735,14 @@ export function createRequestContext<TEnv>(
|
|
|
768
735
|
|
|
769
736
|
waitUntil(fn: () => Promise<void>): void {
|
|
770
737
|
if (executionContext?.waitUntil) {
|
|
771
|
-
// Cloudflare Workers: use native waitUntil
|
|
772
738
|
executionContext.waitUntil(fn());
|
|
773
739
|
} else {
|
|
774
|
-
|
|
775
|
-
fn().catch((err) =>
|
|
776
|
-
console.error("[waitUntil] Background task failed:", err),
|
|
777
|
-
);
|
|
740
|
+
fireAndForgetWaitUntil(fn);
|
|
778
741
|
}
|
|
779
742
|
},
|
|
780
743
|
|
|
744
|
+
executionContext,
|
|
745
|
+
|
|
781
746
|
_onResponseCallbacks: [],
|
|
782
747
|
|
|
783
748
|
onResponse(callback: (response: Response) => Response): void {
|
|
@@ -1043,7 +1008,10 @@ export function createUseFunction<TEnv>(
|
|
|
1043
1008
|
search: (ctx as any).search ?? {},
|
|
1044
1009
|
pathname: ctx.pathname,
|
|
1045
1010
|
url: ctx.url,
|
|
1011
|
+
originalUrl: ctx.originalUrl,
|
|
1046
1012
|
env: ctx.env as any,
|
|
1013
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1014
|
+
executionContext: ctx.executionContext,
|
|
1047
1015
|
get: ctx.get as any,
|
|
1048
1016
|
use: (<TDep, TDepParams = any>(
|
|
1049
1017
|
dep: LoaderDefinition<TDep, TDepParams>,
|
package/src/ssr/index.tsx
CHANGED
|
@@ -162,9 +162,13 @@ function createSsrEventController(opts: {
|
|
|
162
162
|
}): EventController {
|
|
163
163
|
const location = new URL(opts.pathname, "http://localhost");
|
|
164
164
|
let params = opts.params ?? {};
|
|
165
|
+
const rawMatched = opts.matched ?? [];
|
|
165
166
|
const handleState = {
|
|
166
167
|
data: opts.handleData ?? {},
|
|
167
|
-
segmentOrder: filterSegmentOrder(
|
|
168
|
+
segmentOrder: filterSegmentOrder(rawMatched),
|
|
169
|
+
routeSegmentIds: rawMatched.filter(
|
|
170
|
+
(id) => !id.includes(".@") && !/D\d+\./.test(id),
|
|
171
|
+
),
|
|
168
172
|
};
|
|
169
173
|
const state: DerivedNavigationState = {
|
|
170
174
|
state: "idle",
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
} from "./route-config.js";
|
|
21
21
|
import type { LoaderDefinition } from "./loader-types.js";
|
|
22
22
|
import type { UseItems, HandlerUseItem } from "../route-types.js";
|
|
23
|
+
import type { RequestScope } from "./request-scope.js";
|
|
23
24
|
|
|
24
25
|
// Re-export MiddlewareFn for internal/advanced use
|
|
25
26
|
export type { MiddlewareFn } from "../router/middleware.js";
|
|
@@ -195,7 +196,7 @@ export type HandlerContext<
|
|
|
195
196
|
TEnv = DefaultEnv,
|
|
196
197
|
TSearch extends SearchSchema = {},
|
|
197
198
|
TRouteMap = never,
|
|
198
|
-
> = {
|
|
199
|
+
> = RequestScope<TEnv> & {
|
|
199
200
|
/**
|
|
200
201
|
* Route parameters extracted from the URL pattern.
|
|
201
202
|
* Type-safe when using Handler<"/path/:param"> or Handler<{ param: string }>.
|
|
@@ -215,44 +216,11 @@ export type HandlerContext<
|
|
|
215
216
|
* changing build semantics (e.g., skip expensive operations in dev).
|
|
216
217
|
*/
|
|
217
218
|
dev: boolean;
|
|
218
|
-
/**
|
|
219
|
-
* The original incoming Request object (transport URL intact).
|
|
220
|
-
* Use `ctx.url` / `ctx.searchParams` for application logic — those have
|
|
221
|
-
* internal `_rsc*` params stripped. `ctx.request` preserves the raw URL
|
|
222
|
-
* for cases where you need original headers, method, or body.
|
|
223
|
-
*/
|
|
224
|
-
request: Request;
|
|
225
|
-
/**
|
|
226
|
-
* Query parameters from the URL (system params like `_rsc*` are filtered).
|
|
227
|
-
* Always a standard URLSearchParams instance.
|
|
228
|
-
*/
|
|
229
|
-
searchParams: URLSearchParams;
|
|
230
219
|
/**
|
|
231
220
|
* Typed search parameters parsed from URL query string via the route's
|
|
232
221
|
* search schema. Empty object when no schema is defined.
|
|
233
222
|
*/
|
|
234
223
|
search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
|
|
235
|
-
/**
|
|
236
|
-
* The pathname portion of the request URL.
|
|
237
|
-
*/
|
|
238
|
-
pathname: string;
|
|
239
|
-
/**
|
|
240
|
-
* The full URL object (with internal `_rsc*` params stripped).
|
|
241
|
-
* Use this for application logic — routing, link generation, display.
|
|
242
|
-
*/
|
|
243
|
-
url: URL;
|
|
244
|
-
/**
|
|
245
|
-
* The original request URL with all parameters intact, including
|
|
246
|
-
* internal `_rsc*` transport params. Use `ctx.url` for application
|
|
247
|
-
* logic — this is only needed for advanced cases like debugging
|
|
248
|
-
* or custom cache keying.
|
|
249
|
-
*/
|
|
250
|
-
originalUrl: URL;
|
|
251
|
-
/**
|
|
252
|
-
* Platform bindings (DB, KV, secrets, etc.).
|
|
253
|
-
* Access resources like `ctx.env.DB`, `ctx.env.KV`.
|
|
254
|
-
*/
|
|
255
|
-
env: TEnv;
|
|
256
224
|
/**
|
|
257
225
|
* Type-safe getter for middleware variables.
|
|
258
226
|
* Preferred way to read middleware-injected variables.
|
|
@@ -503,13 +471,16 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
503
471
|
* **Return Types:**
|
|
504
472
|
* - `boolean` - Hard decision: immediately returns this value (short-circuits)
|
|
505
473
|
* - `{ defaultShouldRevalidate: boolean }` - Soft decision: updates suggestion for next revalidator
|
|
474
|
+
* - `void` / `null` / `undefined` - Defer to the current suggestion (no opinion); the
|
|
475
|
+
* loop continues to the next revalidator without changing the running default
|
|
506
476
|
*
|
|
507
477
|
* **Execution Flow:**
|
|
508
478
|
* 1. Start with built-in `defaultShouldRevalidate` (true if params changed)
|
|
509
479
|
* 2. Execute global revalidators first, then route-specific
|
|
510
480
|
* 3. Hard decision (boolean): stop immediately and use that value
|
|
511
481
|
* 4. Soft decision (object): update suggestion and continue to next revalidator
|
|
512
|
-
* 5.
|
|
482
|
+
* 5. Defer (`void` / `null` / `undefined`): leave suggestion unchanged and continue
|
|
483
|
+
* 6. If no hard decision was returned: use the final running suggestion
|
|
513
484
|
*
|
|
514
485
|
* @param args.currentParams - Previous route params (generic by default, can be narrowed)
|
|
515
486
|
* @param args.currentUrl - Previous URL
|
|
@@ -521,7 +492,8 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
521
492
|
* @param args.formData - Form data from action (future support)
|
|
522
493
|
* @param args.formMethod - HTTP method from action (future support)
|
|
523
494
|
*
|
|
524
|
-
* @returns Hard decision (boolean)
|
|
495
|
+
* @returns Hard decision (boolean), soft suggestion (object), or defer
|
|
496
|
+
* (`void` / `null` / `undefined`) to keep the running suggestion as-is.
|
|
525
497
|
*
|
|
526
498
|
* @example
|
|
527
499
|
* ```typescript
|
|
@@ -546,8 +518,9 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
546
518
|
* a segment (layout, route, parallel slot, or loader) should be re-rendered.
|
|
547
519
|
*
|
|
548
520
|
* Return `true` to re-render, `false` to skip (keep client's current version),
|
|
549
|
-
*
|
|
550
|
-
* downstream
|
|
521
|
+
* `{ defaultShouldRevalidate: boolean }` to update the running suggestion for
|
|
522
|
+
* downstream revalidators, or nothing (`void` / `null` / `undefined`) to defer
|
|
523
|
+
* to the current suggestion without changing it.
|
|
551
524
|
*
|
|
552
525
|
* @example
|
|
553
526
|
* ```ts
|
|
@@ -647,7 +620,7 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
|
647
620
|
* action that may have mutated backend state.
|
|
648
621
|
*/
|
|
649
622
|
stale?: boolean;
|
|
650
|
-
}) => boolean | { defaultShouldRevalidate: boolean };
|
|
623
|
+
}) => boolean | { defaultShouldRevalidate: boolean } | null | void;
|
|
651
624
|
|
|
652
625
|
// MiddlewareFn is imported from "../router/middleware.js" and re-exported
|
|
653
626
|
|
|
@@ -3,11 +3,13 @@ import type { Handle } from "../handle.js";
|
|
|
3
3
|
import type { MiddlewareFn } from "../router/middleware.js";
|
|
4
4
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
5
5
|
import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
|
|
6
|
+
import type { UseItems, LoaderUseItem } from "../route-types.js";
|
|
6
7
|
import type {
|
|
7
8
|
DefaultEnv,
|
|
8
9
|
DefaultReverseRouteMap,
|
|
9
10
|
DefaultVars,
|
|
10
11
|
} from "./global-namespace.js";
|
|
12
|
+
import type { RequestScope } from "./request-scope.js";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Context passed to loader functions during execution
|
|
@@ -39,7 +41,7 @@ export type LoaderContext<
|
|
|
39
41
|
TEnv = DefaultEnv,
|
|
40
42
|
TBody = unknown,
|
|
41
43
|
TSearch extends SearchSchema = {},
|
|
42
|
-
> = {
|
|
44
|
+
> = RequestScope<TEnv> & {
|
|
43
45
|
params: TParams;
|
|
44
46
|
/**
|
|
45
47
|
* Route params extracted from the URL pattern match (server-side only).
|
|
@@ -48,12 +50,7 @@ export type LoaderContext<
|
|
|
48
50
|
* resource scoping.
|
|
49
51
|
*/
|
|
50
52
|
routeParams: Record<string, string>;
|
|
51
|
-
request: Request;
|
|
52
|
-
searchParams: URLSearchParams;
|
|
53
53
|
search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
|
|
54
|
-
pathname: string;
|
|
55
|
-
url: URL;
|
|
56
|
-
env: TEnv;
|
|
57
54
|
get: {
|
|
58
55
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
59
56
|
} & (<K extends keyof DefaultVars>(key: K) => DefaultVars[K]);
|
|
@@ -207,4 +204,6 @@ export type LoaderDefinition<
|
|
|
207
204
|
__brand: "loader";
|
|
208
205
|
$$id: string; // Injected by Vite plugin (exposeInternalIds) - unique identifier
|
|
209
206
|
fn?: LoaderFn<T, TParams, any>; // Optional - server-side only, stored in registry for RSC
|
|
207
|
+
/** Composable default DSL items merged when the loader is mounted. */
|
|
208
|
+
use?: () => UseItems<LoaderUseItem>;
|
|
210
209
|
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequestScope: the fields every user-facing context shares.
|
|
3
|
+
*
|
|
4
|
+
* A handler, middleware, loader, response handler, and the ALS-bound
|
|
5
|
+
* RequestContext are all different phases of the same request, and they
|
|
6
|
+
* all carry the same set of request-scoped capabilities: the raw Request,
|
|
7
|
+
* the parsed URL pair (`url` is cleaned of internal `_rsc*` params,
|
|
8
|
+
* `originalUrl` retains them), pathname/searchParams, platform bindings
|
|
9
|
+
* (`env`), and two escape hatches for work that outlives the response
|
|
10
|
+
* (`waitUntil`) or needs the raw Cloudflare runtime object
|
|
11
|
+
* (`executionContext`).
|
|
12
|
+
*
|
|
13
|
+
* Each public context type intersects `RequestScope<TEnv>` with its own
|
|
14
|
+
* phase-specific fields (e.g. `params`/`reverse` on HandlerContext,
|
|
15
|
+
* `headers`/`header()` on MiddlewareContext). That keeps platform surface
|
|
16
|
+
* in one place and lets the next runtime escape hatch we need land in
|
|
17
|
+
* one file instead of four.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { DefaultEnv } from "./global-namespace.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Minimal subset of Cloudflare Workers' ExecutionContext that the router
|
|
24
|
+
* uses. Defined locally so the package does not depend on
|
|
25
|
+
* `@cloudflare/workers-types`. Consumers that want the full type can cast.
|
|
26
|
+
*
|
|
27
|
+
* On non-Cloudflare runtimes (Node, dev server, tests), this is undefined
|
|
28
|
+
* — portable apps should prefer `ctx.waitUntil(...)`, which degrades
|
|
29
|
+
* gracefully. `ctx.executionContext` is the escape hatch for libraries
|
|
30
|
+
* (MCP, Durable Object routing, etc.) that type their arguments as the
|
|
31
|
+
* raw ExecutionContext.
|
|
32
|
+
*/
|
|
33
|
+
export interface ExecutionContext {
|
|
34
|
+
waitUntil(promise: Promise<any>): void;
|
|
35
|
+
passThroughOnException(): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fallback `waitUntil` body used when no Cloudflare `ExecutionContext`
|
|
40
|
+
* is available (Node, dev, tests). Runs the work fire-and-forget and
|
|
41
|
+
* logs errors so they don't silently swallow.
|
|
42
|
+
*
|
|
43
|
+
* Exported so every `waitUntil` call site degrades identically instead
|
|
44
|
+
* of inventing its own fallback policy.
|
|
45
|
+
*/
|
|
46
|
+
export function fireAndForgetWaitUntil(fn: () => Promise<void>): void {
|
|
47
|
+
fn().catch((err) =>
|
|
48
|
+
console.error("[waitUntil] Background task failed:", err),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Fields present on every user-facing request context.
|
|
54
|
+
*
|
|
55
|
+
* @template TEnv - Platform bindings type (Cloudflare env, etc.).
|
|
56
|
+
*/
|
|
57
|
+
export interface RequestScope<TEnv = DefaultEnv> {
|
|
58
|
+
/**
|
|
59
|
+
* The original incoming Request object (transport URL intact).
|
|
60
|
+
* Use `url` / `searchParams` for application logic — those have
|
|
61
|
+
* internal `_rsc*` params stripped. `request` preserves the raw URL
|
|
62
|
+
* when you need original headers, method, or body.
|
|
63
|
+
*/
|
|
64
|
+
request: Request;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The request URL with internal `_rsc*` transport params stripped.
|
|
68
|
+
* Use this for routing, link generation, and display.
|
|
69
|
+
*/
|
|
70
|
+
url: URL;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The original request URL with all parameters intact, including
|
|
74
|
+
* internal `_rsc*` transport params. Use `url` for application logic
|
|
75
|
+
* — this is only needed for advanced cases like debugging or custom
|
|
76
|
+
* cache keying.
|
|
77
|
+
*/
|
|
78
|
+
originalUrl: URL;
|
|
79
|
+
|
|
80
|
+
/** URL pathname (same as `url.pathname`). */
|
|
81
|
+
pathname: string;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Query parameters from the URL (system params like `_rsc*` are
|
|
85
|
+
* filtered). Always a standard `URLSearchParams` instance.
|
|
86
|
+
*/
|
|
87
|
+
searchParams: URLSearchParams;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Platform bindings (DB, KV, secrets, etc.). On Cloudflare Workers
|
|
91
|
+
* these are the `env` object passed to the Worker's `fetch()` handler.
|
|
92
|
+
*/
|
|
93
|
+
env: TEnv;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Schedule work to run after the response is sent.
|
|
97
|
+
* On Cloudflare Workers, delegates to `executionContext.waitUntil()`.
|
|
98
|
+
* On Node / dev / tests, runs as fire-and-forget with error logging.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* ctx.waitUntil(async () => {
|
|
103
|
+
* await cacheStore.set(key, data, ttl);
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
waitUntil(fn: () => Promise<void>): void;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Raw Cloudflare Workers `ExecutionContext`, when running on a
|
|
111
|
+
* Cloudflare-compatible runtime. Undefined elsewhere.
|
|
112
|
+
*
|
|
113
|
+
* Escape hatch for libraries that type their arguments as
|
|
114
|
+
* `ExecutionContext` (MCP `fetch`, `routeAgentRequest`, etc.).
|
|
115
|
+
* For the common "do work after the response" case, prefer
|
|
116
|
+
* `ctx.waitUntil(...)` — it is platform-neutral.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* path.any("/mcp", (ctx) =>
|
|
121
|
+
* emailMcp.fetch(ctx.request, ctx.env, ctx.executionContext!),
|
|
122
|
+
* );
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
executionContext?: ExecutionContext;
|
|
126
|
+
}
|
package/src/types/route-entry.ts
CHANGED
|
@@ -8,10 +8,21 @@ export interface LazyIncludeContext {
|
|
|
8
8
|
urlPrefix: string;
|
|
9
9
|
namePrefix: string | undefined;
|
|
10
10
|
parent: unknown; // EntryData - avoid circular import
|
|
11
|
+
/** Counter snapshot from pattern extraction for consistent shortCode indices */
|
|
12
|
+
counters?: Record<string, number>;
|
|
11
13
|
cacheProfiles?: Record<
|
|
12
14
|
string,
|
|
13
15
|
import("../cache/profile-registry.js").CacheProfile
|
|
14
16
|
>;
|
|
17
|
+
/** Root scope flag for dot-local reverse resolution */
|
|
18
|
+
rootScoped?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Positional include scope token composed from the parent scope plus this
|
|
21
|
+
* include's sibling index (`${parentScope}I${idx}`). Applied to direct-
|
|
22
|
+
* descendant shortCodes during lazy evaluation so routes inside the
|
|
23
|
+
* include cannot collide with siblings declared outside it.
|
|
24
|
+
*/
|
|
25
|
+
includeScope?: string;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
/**
|
package/src/types/segments.ts
CHANGED
|
@@ -56,13 +56,20 @@ export interface ResolvedSegment {
|
|
|
56
56
|
// Intercept loader fields (for streaming loader data in parallel segments)
|
|
57
57
|
loaderDataPromise?: Promise<any[]> | any[]; // Loader data promise or resolved array
|
|
58
58
|
loaderIds?: string[]; // IDs ($$id) of loaders for this segment
|
|
59
|
-
parallelLoaderSources?: any[]; // Internal: preserves stable aggregate promise across renders
|
|
60
59
|
// Error-specific fields
|
|
61
60
|
error?: ErrorInfo; // For error segments: the error information
|
|
62
61
|
// NotFound-specific fields
|
|
63
62
|
notFoundInfo?: NotFoundInfo; // For notFound segments: the not found information
|
|
64
63
|
// Mount path from include() scope, used for MountContext.Provider wrapping
|
|
65
64
|
mountPath?: string;
|
|
65
|
+
/**
|
|
66
|
+
* @internal Server-side marker: true when the segment's handler actually ran
|
|
67
|
+
* this request (not skipped via the revalidate cache path). Used by
|
|
68
|
+
* match-result.ts to populate `MatchResult.resolvedIds` for client-side
|
|
69
|
+
* handle-bucket cleanup. Stripped from the wire payload before serialization
|
|
70
|
+
* — never reaches the client.
|
|
71
|
+
*/
|
|
72
|
+
_handlerRan?: boolean;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
/**
|
|
@@ -117,6 +124,15 @@ export interface MatchResult {
|
|
|
117
124
|
segments: ResolvedSegment[];
|
|
118
125
|
matched: string[];
|
|
119
126
|
diff: string[];
|
|
127
|
+
/**
|
|
128
|
+
* Every segment id whose handler actually ran on the server this request,
|
|
129
|
+
* including ones with `component === null` that get filtered out of
|
|
130
|
+
* `segments`/`diff` to avoid wasted bytes. Drives the client's handle-
|
|
131
|
+
* cleanup pass — a slot that re-resolves and pushes nothing must clear
|
|
132
|
+
* its previous handle bucket, but `diff` doesn't carry it because the
|
|
133
|
+
* segment payload doesn't either. A superset of `diff`.
|
|
134
|
+
*/
|
|
135
|
+
resolvedIds: string[];
|
|
120
136
|
/**
|
|
121
137
|
* Merged route params from all matched segments
|
|
122
138
|
* Available for use by the handler after route matching
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
runWithPrefixes,
|
|
5
5
|
getUrlPrefix,
|
|
6
6
|
getNamePrefix,
|
|
7
|
-
getRootScoped,
|
|
8
7
|
} from "../server/context";
|
|
9
8
|
import {
|
|
10
9
|
INTERNAL_INCLUDE_SCOPE_PREFIX,
|
|
@@ -149,22 +148,32 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
|
|
|
149
148
|
});
|
|
150
149
|
}
|
|
151
150
|
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// and
|
|
161
|
-
|
|
151
|
+
// Allocate an include-scope token for this include() call. The token is
|
|
152
|
+
// appended to the parent's shortCode prefix whenever the include's
|
|
153
|
+
// direct-descendant shortCodes are generated (see getShortCode in
|
|
154
|
+
// context.ts), partitioning the parent's counter namespace so routes
|
|
155
|
+
// inside an include cannot collide with siblings declared outside it.
|
|
156
|
+
//
|
|
157
|
+
// Scopes compose: a nested include inside an outer include with scope
|
|
158
|
+
// "I0" allocates against the `${parent.shortCode}I0_include` counter
|
|
159
|
+
// and produces scope "I0I0", "I0I1", etc.
|
|
160
|
+
const parentScope = ctx.includeScope ?? "";
|
|
161
|
+
let includeScope = parentScope;
|
|
162
162
|
if (capturedParent?.shortCode) {
|
|
163
|
-
const
|
|
164
|
-
ctx.counters[
|
|
165
|
-
ctx.counters[
|
|
163
|
+
const includeCounterKey = `${capturedParent.shortCode}${parentScope}_include`;
|
|
164
|
+
ctx.counters[includeCounterKey] ??= 0;
|
|
165
|
+
const includeIdx = ctx.counters[includeCounterKey];
|
|
166
|
+
ctx.counters[includeCounterKey] = includeIdx + 1;
|
|
167
|
+
includeScope = `${parentScope}I${includeIdx}`;
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
// Snapshot parent's counters AFTER allocating the include scope so lazy
|
|
171
|
+
// manifest generation starts with the same counter state this include
|
|
172
|
+
// observed — its descendants still get fresh per-scope counters because
|
|
173
|
+
// they key off `${parent.shortCode}${includeScope}_*` (not shared with
|
|
174
|
+
// siblings outside the include).
|
|
175
|
+
const capturedCounters = { ...ctx.counters };
|
|
176
|
+
|
|
168
177
|
// Compute rootScoped at capture time, mirroring the logic in runWithPrefixes.
|
|
169
178
|
// This ensures lazy evaluation restores the correct scope state.
|
|
170
179
|
const parentRootScoped = ctx.rootScoped;
|
|
@@ -191,6 +200,7 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
|
|
|
191
200
|
counters: capturedCounters,
|
|
192
201
|
cacheProfiles: ctx.cacheProfiles,
|
|
193
202
|
rootScoped: capturedRootScoped,
|
|
203
|
+
includeScope,
|
|
194
204
|
},
|
|
195
205
|
} as IncludeItem;
|
|
196
206
|
};
|
|
@@ -233,12 +233,27 @@ export type PathHelpers<TEnv> = {
|
|
|
233
233
|
include: IncludeFn<TEnv>;
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
* Define parallel routes that render simultaneously in named slots
|
|
236
|
+
* Define parallel routes that render simultaneously in named slots.
|
|
237
|
+
*
|
|
238
|
+
* A slot value can be a Handler / ReactNode / StaticHandlerDefinition
|
|
239
|
+
* (legacy form, broadcast use applies to every slot) or a slot descriptor
|
|
240
|
+
* `{ handler, use? }` whose `use` is scoped to that slot only. Per-slot
|
|
241
|
+
* merge order is `handler.use` → shared `use` → slot-local `use`, with
|
|
242
|
+
* narrowest scope winning for last-write-wins items like `loading()`.
|
|
237
243
|
*/
|
|
238
244
|
parallel: <
|
|
239
245
|
TSlots extends Record<
|
|
240
246
|
`@${string}`,
|
|
241
|
-
Handler<any, any, TEnv>
|
|
247
|
+
| Handler<any, any, TEnv>
|
|
248
|
+
| ReactNode
|
|
249
|
+
| StaticHandlerDefinition
|
|
250
|
+
| {
|
|
251
|
+
handler:
|
|
252
|
+
| Handler<any, any, TEnv>
|
|
253
|
+
| ReactNode
|
|
254
|
+
| StaticHandlerDefinition;
|
|
255
|
+
use?: () => ParallelUseItem[];
|
|
256
|
+
}
|
|
242
257
|
>,
|
|
243
258
|
>(
|
|
244
259
|
slots: TSlots,
|
|
@@ -264,9 +279,20 @@ export type PathHelpers<TEnv> = {
|
|
|
264
279
|
) => InterceptItem;
|
|
265
280
|
|
|
266
281
|
/**
|
|
267
|
-
* Attach middleware to the current route/layout
|
|
282
|
+
* Attach middleware to the current route/layout, or wrap child segments
|
|
268
283
|
*/
|
|
269
|
-
middleware:
|
|
284
|
+
middleware: {
|
|
285
|
+
(fn: MiddlewareFn<TEnv>): MiddlewareItem;
|
|
286
|
+
(
|
|
287
|
+
fn: MiddlewareFn<TEnv>,
|
|
288
|
+
children: () => UseItems<LayoutUseItem>,
|
|
289
|
+
): MiddlewareItem;
|
|
290
|
+
(fns: MiddlewareFn<TEnv>[]): MiddlewareItem;
|
|
291
|
+
(
|
|
292
|
+
fns: MiddlewareFn<TEnv>[],
|
|
293
|
+
children: () => UseItems<LayoutUseItem>,
|
|
294
|
+
): MiddlewareItem;
|
|
295
|
+
};
|
|
270
296
|
|
|
271
297
|
/**
|
|
272
298
|
* Control when a segment should revalidate during navigation
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
DefaultVars,
|
|
6
6
|
} from "../types/global-namespace.js";
|
|
7
7
|
import type { UseItems, ResponseRouteUseItem } from "../route-types.js";
|
|
8
|
+
import type { RequestScope } from "../types/request-scope.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Reverse function for response handler contexts.
|
|
@@ -93,19 +94,10 @@ export type TextResponseHandler<
|
|
|
93
94
|
export interface ResponseHandlerContext<
|
|
94
95
|
TParams = Record<string, string>,
|
|
95
96
|
TEnv = any,
|
|
96
|
-
> {
|
|
97
|
-
request: Request;
|
|
97
|
+
> extends RequestScope<TEnv> {
|
|
98
98
|
params: TParams;
|
|
99
99
|
/** @internal Phantom property for params type invariance. Prevents mounting handlers on wrong routes. */
|
|
100
100
|
readonly _paramCheck?: (params: TParams) => TParams;
|
|
101
|
-
/** Platform bindings (DB, KV, secrets, etc.). */
|
|
102
|
-
env: TEnv;
|
|
103
|
-
/** Query parameters from the URL (system params like `_rsc*` are filtered). */
|
|
104
|
-
searchParams: URLSearchParams;
|
|
105
|
-
/** The full URL object (with system params filtered). */
|
|
106
|
-
url: URL;
|
|
107
|
-
/** The pathname portion of the request URL. */
|
|
108
|
-
pathname: string;
|
|
109
101
|
reverse: ResponseReverseFunction;
|
|
110
102
|
/** Read a variable set by middleware via ctx.set(key, value) or ctx.set(ContextVar, value). */
|
|
111
103
|
get: {
|