@rangojs/router 0.0.0-experimental.8a4d0430 → 0.0.0-experimental.8bcfea43
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 +126 -38
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +1171 -461
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +19 -16
- package/skills/breadcrumbs/SKILL.md +3 -1
- 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 +28 -20
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +91 -17
- package/skills/loader/SKILL.md +88 -45
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +185 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/rango/SKILL.md +24 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +55 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +13 -1
- package/src/__internal.ts +1 -1
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/navigation-bridge.ts +90 -16
- package/src/browser/navigation-client.ts +167 -59
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +113 -17
- package/src/browser/prefetch/cache.ts +184 -16
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +89 -14
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- 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 +29 -9
- package/src/browser/rsc-router.tsx +168 -65
- package/src/browser/scroll-restoration.ts +41 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +49 -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 +455 -15
- 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 +84 -230
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +6 -1
- package/src/index.ts +49 -6
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +27 -2
- package/src/route-definition/dsl-helpers.ts +240 -40
- 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 +155 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +101 -25
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +159 -21
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +127 -192
- 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 +8 -30
- package/src/router/middleware.ts +36 -10
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +60 -9
- 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 +198 -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 +438 -300
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +10 -4
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +60 -8
- package/src/rsc/handler.ts +478 -374
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +16 -2
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +19 -1
- package/src/rsc/server-action.ts +10 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +9 -1
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +109 -23
- package/src/server/context.ts +166 -17
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +194 -60
- 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 -65
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +2 -0
- package/src/urls/include-helper.ts +24 -14
- 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 +18 -16
- package/src/use-loader.tsx +77 -5
- package/src/vite/debug.ts +55 -0
- 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/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 +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 +86 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +204 -217
- package/src/vite/router-discovery.ts +335 -64
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +37 -5
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -20,17 +20,33 @@ import type {
|
|
|
20
20
|
DefaultRouteName,
|
|
21
21
|
} from "../types/global-namespace.js";
|
|
22
22
|
import type { Handle } from "../handle.js";
|
|
23
|
-
import {
|
|
24
|
-
|
|
23
|
+
import {
|
|
24
|
+
type ContextVar,
|
|
25
|
+
contextGet,
|
|
26
|
+
contextSet,
|
|
27
|
+
isNonCacheable,
|
|
28
|
+
} from "../context-var.js";
|
|
29
|
+
import {
|
|
30
|
+
createHandleStore,
|
|
31
|
+
buildHandleSnapshot,
|
|
32
|
+
type HandleStore,
|
|
33
|
+
type HandleData,
|
|
34
|
+
} from "./handle-store.js";
|
|
25
35
|
import { isHandle } from "../handle.js";
|
|
26
36
|
import { track, type MetricsStore } from "./context.js";
|
|
27
37
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
28
38
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
29
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";
|
|
30
42
|
import { THEME_COOKIE } from "../theme/constants.js";
|
|
31
43
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
32
44
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
33
|
-
import {
|
|
45
|
+
import { isInsideCacheScope } from "./context.js";
|
|
46
|
+
import {
|
|
47
|
+
createReverseFunction,
|
|
48
|
+
stripInternalParams,
|
|
49
|
+
} from "../router/handler-context.js";
|
|
34
50
|
import { getGlobalRouteMap, isRouteRootScoped } from "../route-map-builder.js";
|
|
35
51
|
import { invariant } from "../errors.js";
|
|
36
52
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
@@ -44,24 +60,9 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
44
60
|
export interface RequestContext<
|
|
45
61
|
TEnv = DefaultEnv,
|
|
46
62
|
TParams = Record<string, string>,
|
|
47
|
-
> {
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
/** Original HTTP request */
|
|
51
|
-
request: Request;
|
|
52
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
53
|
-
url: URL;
|
|
54
|
-
/**
|
|
55
|
-
* The original request URL with all parameters intact, including
|
|
56
|
-
* internal `_rsc*` transport params.
|
|
57
|
-
*/
|
|
58
|
-
originalUrl: URL;
|
|
59
|
-
/** URL pathname */
|
|
60
|
-
pathname: string;
|
|
61
|
-
/** URL search params (system params like _rsc* are NOT filtered here) */
|
|
62
|
-
searchParams: URLSearchParams;
|
|
63
|
-
/** Variables set by middleware (same as ctx.var) */
|
|
64
|
-
var: Record<string, any>;
|
|
63
|
+
> extends RequestScope<TEnv> {
|
|
64
|
+
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
65
|
+
_variables: Record<string, any>;
|
|
65
66
|
/** Get a variable set by middleware */
|
|
66
67
|
get: {
|
|
67
68
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -69,8 +70,12 @@ export interface RequestContext<
|
|
|
69
70
|
};
|
|
70
71
|
/** Set a variable (shared with middleware and handlers) */
|
|
71
72
|
set: {
|
|
72
|
-
<T>(
|
|
73
|
-
|
|
73
|
+
<T>(
|
|
74
|
+
contextVar: ContextVar<T>,
|
|
75
|
+
value: T,
|
|
76
|
+
options?: { cache?: boolean },
|
|
77
|
+
): void;
|
|
78
|
+
<K extends string>(key: K, value: any, options?: { cache?: boolean }): void;
|
|
74
79
|
};
|
|
75
80
|
/**
|
|
76
81
|
* Route params (populated after route matching)
|
|
@@ -141,20 +146,6 @@ export interface RequestContext<
|
|
|
141
146
|
import("../cache/profile-registry.js").CacheProfile
|
|
142
147
|
>;
|
|
143
148
|
|
|
144
|
-
/**
|
|
145
|
-
* Schedule work to run after the response is sent.
|
|
146
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
147
|
-
* On Node.js, runs as fire-and-forget.
|
|
148
|
-
*
|
|
149
|
-
* @example
|
|
150
|
-
* ```typescript
|
|
151
|
-
* ctx.waitUntil(async () => {
|
|
152
|
-
* await cacheStore.set(key, data, ttl);
|
|
153
|
-
* });
|
|
154
|
-
* ```
|
|
155
|
-
*/
|
|
156
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
157
|
-
|
|
158
149
|
/**
|
|
159
150
|
* Register a callback to run when the response is created.
|
|
160
151
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -258,6 +249,54 @@ export interface RequestContext<
|
|
|
258
249
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
259
250
|
_prevRouteKey?: string;
|
|
260
251
|
|
|
252
|
+
/**
|
|
253
|
+
* @internal Render barrier for experimental `rendered()` API.
|
|
254
|
+
* Resolves when all non-loader segments have settled and handle data
|
|
255
|
+
* is available. Used by DSL loaders that call `ctx.rendered()`.
|
|
256
|
+
*/
|
|
257
|
+
_renderBarrier: Promise<void>;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @internal Resolve the render barrier. Accepts resolved segments, filters
|
|
261
|
+
* out loaders, and captures non-loader segment IDs as the handle ordering.
|
|
262
|
+
* Called after segment resolution (fresh) or handle replay (cache/prerender).
|
|
263
|
+
*/
|
|
264
|
+
_resolveRenderBarrier: (
|
|
265
|
+
segments: Array<{ type: string; id: string }>,
|
|
266
|
+
) => void;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @internal Segment order at barrier resolution time, used by loader
|
|
270
|
+
* ctx.use(handle) to collect handle data in correct order.
|
|
271
|
+
*/
|
|
272
|
+
_renderBarrierSegmentOrder?: string[];
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @internal Set to true when the matched entry tree contains any `loading()`
|
|
276
|
+
* entries (streaming). Used by rendered() to fail fast.
|
|
277
|
+
*/
|
|
278
|
+
_treeHasStreaming?: boolean;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* @internal Loader IDs that have called rendered() and are waiting for the
|
|
282
|
+
* barrier. Used to detect deadlocks when a handler tries to await the same
|
|
283
|
+
* loader via ctx.use(Loader).
|
|
284
|
+
*/
|
|
285
|
+
_renderBarrierWaiters?: Set<string>;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @internal Loader IDs that handlers have started awaiting via ctx.use().
|
|
289
|
+
* Used for bidirectional deadlock detection: if a loader later calls
|
|
290
|
+
* rendered() and a handler already awaits it, we can detect the deadlock.
|
|
291
|
+
*/
|
|
292
|
+
_handlerLoaderDeps?: Set<string>;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @internal Cached HandleData snapshot built at barrier resolution time.
|
|
296
|
+
* Avoids rebuilding the snapshot on every loader ctx.use(handle) call.
|
|
297
|
+
*/
|
|
298
|
+
_renderBarrierHandleSnapshot?: HandleData;
|
|
299
|
+
|
|
261
300
|
/** @internal Per-request error dedup set for onError reporting */
|
|
262
301
|
_reportedErrors: WeakSet<object>;
|
|
263
302
|
|
|
@@ -274,6 +313,15 @@ export interface RequestContext<
|
|
|
274
313
|
|
|
275
314
|
/** @internal Request-scoped performance metrics store */
|
|
276
315
|
_metricsStore?: MetricsStore;
|
|
316
|
+
|
|
317
|
+
/** @internal Router basename for this request (used by redirect()) */
|
|
318
|
+
_basename?: string;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
|
|
322
|
+
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
323
|
+
*/
|
|
324
|
+
_classifiedRoute?: import("../router/route-snapshot.js").RouteSnapshot;
|
|
277
325
|
}
|
|
278
326
|
|
|
279
327
|
/**
|
|
@@ -300,10 +348,20 @@ export type PublicRequestContext<
|
|
|
300
348
|
| "_routeName"
|
|
301
349
|
| "_prevRouteKey"
|
|
302
350
|
| "_reportedErrors"
|
|
351
|
+
| "_renderBarrier"
|
|
352
|
+
| "_resolveRenderBarrier"
|
|
353
|
+
| "_renderBarrierSegmentOrder"
|
|
354
|
+
| "_treeHasStreaming"
|
|
355
|
+
| "_renderBarrierWaiters"
|
|
356
|
+
| "_handlerLoaderDeps"
|
|
357
|
+
| "_renderBarrierHandleSnapshot"
|
|
303
358
|
| "_reportBackgroundError"
|
|
304
359
|
| "_debugPerformance"
|
|
305
360
|
| "_metricsStore"
|
|
361
|
+
| "_basename"
|
|
306
362
|
| "_setStatus"
|
|
363
|
+
| "_variables"
|
|
364
|
+
| "_classifiedRoute"
|
|
307
365
|
| "res"
|
|
308
366
|
>;
|
|
309
367
|
|
|
@@ -413,13 +471,7 @@ export function requireRequestContext<
|
|
|
413
471
|
return getRequestContext<TEnv>();
|
|
414
472
|
}
|
|
415
473
|
|
|
416
|
-
|
|
417
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
418
|
-
*/
|
|
419
|
-
export interface ExecutionContext {
|
|
420
|
-
waitUntil(promise: Promise<any>): void;
|
|
421
|
-
passThroughOnException(): void;
|
|
422
|
-
}
|
|
474
|
+
export type { ExecutionContext };
|
|
423
475
|
|
|
424
476
|
/**
|
|
425
477
|
* Options for creating a request context
|
|
@@ -503,6 +555,18 @@ export function createRequestContext<TEnv>(
|
|
|
503
555
|
responseCookieCache = null;
|
|
504
556
|
};
|
|
505
557
|
|
|
558
|
+
// Guard: throw if a response-level side effect is called inside a cache() scope.
|
|
559
|
+
// Uses ALS to detect the scope (set during segment resolution).
|
|
560
|
+
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
561
|
+
if (isInsideCacheScope()) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
`ctx.${methodName}() cannot be called inside a cache() boundary. ` +
|
|
564
|
+
`On cache hit the handler is skipped, so this side effect would be lost. ` +
|
|
565
|
+
`Move ctx.${methodName}() to a middleware or layout outside the cache() scope.`,
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
506
570
|
// Effective cookie read: response stub Set-Cookie wins, then original header.
|
|
507
571
|
// The stub IS the source of truth for same-request mutations.
|
|
508
572
|
const effectiveCookie = (name: string): string | undefined => {
|
|
@@ -555,20 +619,31 @@ export function createRequestContext<TEnv>(
|
|
|
555
619
|
invalidateResponseCookieCache();
|
|
556
620
|
};
|
|
557
621
|
|
|
622
|
+
// Strip internal _rsc* params so userland sees a clean URL.
|
|
623
|
+
const cleanUrl = stripInternalParams(url);
|
|
624
|
+
|
|
558
625
|
// Build the context object first (without use), then add use
|
|
559
626
|
const ctx: RequestContext<TEnv> = {
|
|
560
627
|
env,
|
|
561
628
|
request,
|
|
562
|
-
url,
|
|
629
|
+
url: cleanUrl,
|
|
563
630
|
originalUrl: new URL(request.url),
|
|
564
631
|
pathname: url.pathname,
|
|
565
|
-
searchParams:
|
|
566
|
-
|
|
567
|
-
get: ((keyOrVar: any) =>
|
|
568
|
-
|
|
569
|
-
|
|
632
|
+
searchParams: cleanUrl.searchParams,
|
|
633
|
+
_variables: variables,
|
|
634
|
+
get: ((keyOrVar: any) => {
|
|
635
|
+
if (isNonCacheable(variables, keyOrVar) && isInsideCacheScope()) {
|
|
636
|
+
throw new Error(
|
|
637
|
+
`ctx.get() for a non-cacheable variable cannot be called inside a cache() boundary. ` +
|
|
638
|
+
`The variable was created with { cache: false } or set with { cache: false }, ` +
|
|
639
|
+
`and its value would be stale on cache hit. Move the read outside the cached scope.`,
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
return contextGet(variables, keyOrVar);
|
|
643
|
+
}) as RequestContext<TEnv>["get"],
|
|
644
|
+
set: ((keyOrVar: any, value: any, options?: any) => {
|
|
570
645
|
assertNotInsideCacheExec(ctx, "set");
|
|
571
|
-
contextSet(variables, keyOrVar, value);
|
|
646
|
+
contextSet(variables, keyOrVar, value, options);
|
|
572
647
|
}) as RequestContext<TEnv>["set"],
|
|
573
648
|
params: {} as Record<string, string>,
|
|
574
649
|
|
|
@@ -606,6 +681,7 @@ export function createRequestContext<TEnv>(
|
|
|
606
681
|
|
|
607
682
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
608
683
|
assertNotInsideCacheExec(ctx, "setCookie");
|
|
684
|
+
assertNotInsideCacheScopeALS("setCookie");
|
|
609
685
|
stubResponse.headers.append(
|
|
610
686
|
"Set-Cookie",
|
|
611
687
|
serializeCookieValue(name, value, options),
|
|
@@ -618,6 +694,7 @@ export function createRequestContext<TEnv>(
|
|
|
618
694
|
options?: Pick<CookieOptions, "domain" | "path">,
|
|
619
695
|
): void {
|
|
620
696
|
assertNotInsideCacheExec(ctx, "deleteCookie");
|
|
697
|
+
assertNotInsideCacheScopeALS("deleteCookie");
|
|
621
698
|
stubResponse.headers.append(
|
|
622
699
|
"Set-Cookie",
|
|
623
700
|
serializeCookieValue(name, "", { ...options, maxAge: 0 }),
|
|
@@ -627,11 +704,13 @@ export function createRequestContext<TEnv>(
|
|
|
627
704
|
|
|
628
705
|
header(name: string, value: string): void {
|
|
629
706
|
assertNotInsideCacheExec(ctx, "header");
|
|
707
|
+
assertNotInsideCacheScopeALS("header");
|
|
630
708
|
stubResponse.headers.set(name, value);
|
|
631
709
|
},
|
|
632
710
|
|
|
633
711
|
setStatus(status: number): void {
|
|
634
712
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
713
|
+
assertNotInsideCacheScopeALS("setStatus");
|
|
635
714
|
stubResponse = new Response(null, {
|
|
636
715
|
status,
|
|
637
716
|
headers: stubResponse.headers,
|
|
@@ -656,20 +735,19 @@ export function createRequestContext<TEnv>(
|
|
|
656
735
|
|
|
657
736
|
waitUntil(fn: () => Promise<void>): void {
|
|
658
737
|
if (executionContext?.waitUntil) {
|
|
659
|
-
// Cloudflare Workers: use native waitUntil
|
|
660
738
|
executionContext.waitUntil(fn());
|
|
661
739
|
} else {
|
|
662
|
-
|
|
663
|
-
fn().catch((err) =>
|
|
664
|
-
console.error("[waitUntil] Background task failed:", err),
|
|
665
|
-
);
|
|
740
|
+
fireAndForgetWaitUntil(fn);
|
|
666
741
|
}
|
|
667
742
|
},
|
|
668
743
|
|
|
744
|
+
executionContext,
|
|
745
|
+
|
|
669
746
|
_onResponseCallbacks: [],
|
|
670
747
|
|
|
671
748
|
onResponse(callback: (response: Response) => Response): void {
|
|
672
749
|
assertNotInsideCacheExec(ctx, "onResponse");
|
|
750
|
+
assertNotInsideCacheScopeALS("onResponse");
|
|
673
751
|
this._onResponseCallbacks.push(callback);
|
|
674
752
|
},
|
|
675
753
|
|
|
@@ -697,9 +775,58 @@ export function createRequestContext<TEnv>(
|
|
|
697
775
|
_reportedErrors: new WeakSet<object>(),
|
|
698
776
|
_metricsStore: undefined,
|
|
699
777
|
|
|
778
|
+
// Render barrier: deferred promise resolved after non-loader segments settle.
|
|
779
|
+
_renderBarrier: null as any, // set below
|
|
780
|
+
_resolveRenderBarrier: null as any, // set below
|
|
781
|
+
_renderBarrierSegmentOrder: undefined,
|
|
782
|
+
|
|
700
783
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
701
784
|
};
|
|
702
785
|
|
|
786
|
+
// Lazy render barrier: only allocate the Promise when a loader actually
|
|
787
|
+
// calls rendered(). Requests that don't use rendered() pay zero cost.
|
|
788
|
+
let barrierResolved = false;
|
|
789
|
+
let resolveBarrier: (() => void) | undefined;
|
|
790
|
+
ctx._renderBarrier = null as any; // lazy — created on first access
|
|
791
|
+
ctx._resolveRenderBarrier = (
|
|
792
|
+
segments: Array<{ type: string; id: string }>,
|
|
793
|
+
) => {
|
|
794
|
+
if (barrierResolved) return;
|
|
795
|
+
barrierResolved = true;
|
|
796
|
+
const segOrder = segments
|
|
797
|
+
.filter((s) => s.type !== "loader")
|
|
798
|
+
.map((s) => s.id);
|
|
799
|
+
ctx._renderBarrierSegmentOrder = segOrder;
|
|
800
|
+
// Build and cache handle snapshot so loader ctx.use(handle) calls
|
|
801
|
+
// don't rebuild it on every invocation.
|
|
802
|
+
ctx._renderBarrierHandleSnapshot = buildHandleSnapshot(
|
|
803
|
+
handleStore,
|
|
804
|
+
segOrder,
|
|
805
|
+
);
|
|
806
|
+
ctx._renderBarrierWaiters = undefined;
|
|
807
|
+
ctx._handlerLoaderDeps = undefined;
|
|
808
|
+
if (resolveBarrier) resolveBarrier();
|
|
809
|
+
};
|
|
810
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
811
|
+
get() {
|
|
812
|
+
// Barrier already resolved (cache/prerender hit) or first lazy access.
|
|
813
|
+
// Either way, replace the getter with a concrete value to avoid
|
|
814
|
+
// repeated Promise.resolve() allocations on subsequent reads.
|
|
815
|
+
const p = barrierResolved
|
|
816
|
+
? Promise.resolve()
|
|
817
|
+
: new Promise<void>((resolve) => {
|
|
818
|
+
resolveBarrier = resolve;
|
|
819
|
+
});
|
|
820
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
821
|
+
value: p,
|
|
822
|
+
writable: false,
|
|
823
|
+
configurable: false,
|
|
824
|
+
});
|
|
825
|
+
return p;
|
|
826
|
+
},
|
|
827
|
+
configurable: true,
|
|
828
|
+
});
|
|
829
|
+
|
|
703
830
|
// Now create use() with access to ctx
|
|
704
831
|
ctx.use = createUseFunction({
|
|
705
832
|
handleStore,
|
|
@@ -881,15 +1008,17 @@ export function createUseFunction<TEnv>(
|
|
|
881
1008
|
search: (ctx as any).search ?? {},
|
|
882
1009
|
pathname: ctx.pathname,
|
|
883
1010
|
url: ctx.url,
|
|
1011
|
+
originalUrl: ctx.originalUrl,
|
|
884
1012
|
env: ctx.env as any,
|
|
885
|
-
|
|
1013
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1014
|
+
executionContext: ctx.executionContext,
|
|
886
1015
|
get: ctx.get as any,
|
|
887
|
-
use: <TDep, TDepParams = any>(
|
|
1016
|
+
use: (<TDep, TDepParams = any>(
|
|
888
1017
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
889
1018
|
): Promise<TDep> => {
|
|
890
1019
|
// Recursive call - will start dep loader if not already started
|
|
891
1020
|
return ctx.use(dep);
|
|
892
|
-
},
|
|
1021
|
+
}) as LoaderContext["use"],
|
|
893
1022
|
method: "GET",
|
|
894
1023
|
body: undefined,
|
|
895
1024
|
reverse: createReverseFunction(
|
|
@@ -898,9 +1027,14 @@ export function createUseFunction<TEnv>(
|
|
|
898
1027
|
ctx.params as Record<string, string>,
|
|
899
1028
|
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
900
1029
|
),
|
|
1030
|
+
rendered: () => {
|
|
1031
|
+
throw new Error(
|
|
1032
|
+
`ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
|
|
1033
|
+
`It cannot be used from request-context loaders or server actions.`,
|
|
1034
|
+
);
|
|
1035
|
+
},
|
|
901
1036
|
};
|
|
902
1037
|
|
|
903
|
-
// Start loader execution with tracking
|
|
904
1038
|
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
905
1039
|
const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
|
|
906
1040
|
doneLoader();
|
package/src/ssr/index.tsx
CHANGED
|
@@ -129,6 +129,7 @@ interface RscPayload {
|
|
|
129
129
|
matched?: string[];
|
|
130
130
|
pathname?: string;
|
|
131
131
|
params?: Record<string, string>;
|
|
132
|
+
basename?: string;
|
|
132
133
|
themeConfig?: ResolvedThemeConfig | null;
|
|
133
134
|
initialTheme?: Theme;
|
|
134
135
|
version?: string;
|
|
@@ -168,6 +169,7 @@ function createSsrEventController(opts: {
|
|
|
168
169
|
const state: DerivedNavigationState = {
|
|
169
170
|
state: "idle",
|
|
170
171
|
isStreaming: false,
|
|
172
|
+
isNavigating: false,
|
|
171
173
|
location,
|
|
172
174
|
pendingUrl: null,
|
|
173
175
|
inflightActions: [],
|
|
@@ -260,6 +262,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
260
262
|
function SsrRoot() {
|
|
261
263
|
payload ??= createFromReadableStream<RscPayload>(rscStream1);
|
|
262
264
|
const resolved = React.use(payload);
|
|
265
|
+
|
|
263
266
|
const themeConfig = resolved.metadata?.themeConfig ?? null;
|
|
264
267
|
const pathname = resolved.metadata?.pathname ?? "/";
|
|
265
268
|
|
|
@@ -285,6 +288,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
285
288
|
navigate: async () => {},
|
|
286
289
|
refresh: async () => {},
|
|
287
290
|
version: resolved.metadata?.version,
|
|
291
|
+
basename: resolved.metadata?.basename,
|
|
288
292
|
};
|
|
289
293
|
|
|
290
294
|
// Build content tree from segments.
|
package/src/static-handler.ts
CHANGED
|
@@ -32,11 +32,21 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import type { ReactNode } from "react";
|
|
34
34
|
import type { Handler } from "./types.js";
|
|
35
|
-
import type {
|
|
35
|
+
import type { StaticBuildContext } from "./prerender.js";
|
|
36
|
+
import type { UseItems, HandlerUseItem } from "./route-types.js";
|
|
36
37
|
import { isCachedFunction } from "./cache/taint.js";
|
|
37
38
|
|
|
38
39
|
// -- Types ------------------------------------------------------------------
|
|
39
40
|
|
|
41
|
+
export interface StaticHandlerOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Keep handler in server bundle for live fallback (default: false).
|
|
44
|
+
* false: handler replaced with stub, source-only APIs excluded from bundle.
|
|
45
|
+
* true: handler stays in bundle, renders live at request time.
|
|
46
|
+
*/
|
|
47
|
+
passthrough?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
export interface StaticHandlerDefinition<
|
|
41
51
|
TParams extends Record<string, any> = any,
|
|
42
52
|
> {
|
|
@@ -46,14 +56,16 @@ export interface StaticHandlerDefinition<
|
|
|
46
56
|
/** In dev mode, the actual handler function that layout/path/parallel can call. */
|
|
47
57
|
handler: Handler<TParams>;
|
|
48
58
|
/** Static handler options (passthrough support). */
|
|
49
|
-
options?:
|
|
59
|
+
options?: StaticHandlerOptions;
|
|
60
|
+
/** Composable default DSL items merged when the handler is mounted. */
|
|
61
|
+
use?: () => UseItems<HandlerUseItem>;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
// -- Function ---------------------------------------------------------------
|
|
53
65
|
|
|
54
66
|
export function Static<TParams extends Record<string, any> = {}>(
|
|
55
67
|
handler: (ctx: StaticBuildContext) => ReactNode | Promise<ReactNode>,
|
|
56
|
-
options?:
|
|
68
|
+
options?: StaticHandlerOptions,
|
|
57
69
|
__injectedId?: string,
|
|
58
70
|
): StaticHandlerDefinition<TParams>;
|
|
59
71
|
|
|
@@ -61,7 +73,7 @@ export function Static<TParams extends Record<string, any> = {}>(
|
|
|
61
73
|
|
|
62
74
|
export function Static<TParams extends Record<string, any>>(
|
|
63
75
|
handler: Function,
|
|
64
|
-
optionsOrId?:
|
|
76
|
+
optionsOrId?: StaticHandlerOptions | string,
|
|
65
77
|
maybeId?: string,
|
|
66
78
|
): StaticHandlerDefinition<TParams> {
|
|
67
79
|
if (isCachedFunction(handler)) {
|
|
@@ -72,13 +84,13 @@ export function Static<TParams extends Record<string, any>>(
|
|
|
72
84
|
);
|
|
73
85
|
}
|
|
74
86
|
|
|
75
|
-
let options:
|
|
87
|
+
let options: StaticHandlerOptions | undefined;
|
|
76
88
|
let id: string;
|
|
77
89
|
|
|
78
90
|
if (typeof optionsOrId === "string") {
|
|
79
91
|
id = optionsOrId;
|
|
80
92
|
} else {
|
|
81
|
-
options = optionsOrId as
|
|
93
|
+
options = optionsOrId as StaticHandlerOptions | undefined;
|
|
82
94
|
id = maybeId ?? "";
|
|
83
95
|
}
|
|
84
96
|
|
package/src/types/cache-types.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* during cache key generation (before middleware runs).
|
|
6
6
|
*
|
|
7
7
|
* Note: While the full RequestContext is passed, middleware-set variables
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* read via `ctx.get()` may not be populated yet since cache lookup happens
|
|
9
|
+
* before middleware execution.
|
|
10
10
|
*/
|
|
11
11
|
export type { RequestContext as CacheContext } from "../server/request-context.js";
|
|
12
12
|
|
|
@@ -101,7 +101,7 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
101
101
|
* Return false to skip cache for this request (always fetch fresh).
|
|
102
102
|
*
|
|
103
103
|
* Has access to full RequestContext including env, request, params, cookies, etc.
|
|
104
|
-
* Note: Middleware-set variables
|
|
104
|
+
* Note: Middleware-set variables read via `ctx.get()` may not be populated yet.
|
|
105
105
|
*
|
|
106
106
|
* @example
|
|
107
107
|
* ```typescript
|
|
@@ -123,7 +123,7 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
123
123
|
* Bypasses default key generation AND store's keyGenerator.
|
|
124
124
|
*
|
|
125
125
|
* Has access to full RequestContext including env, request, params, cookies, etc.
|
|
126
|
-
* Note: Middleware-set variables
|
|
126
|
+
* Note: Middleware-set variables read via `ctx.get()` may not be populated yet.
|
|
127
127
|
*
|
|
128
128
|
* @example
|
|
129
129
|
* ```typescript
|