@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.20dbba0c
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 +172 -50
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +1160 -508
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +17 -16
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +49 -8
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +61 -51
- package/skills/host-router/SKILL.md +218 -0
- 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 +107 -24
- 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 +112 -70
- package/skills/rango/SKILL.md +24 -23
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +58 -4
- package/skills/router-setup/SKILL.md +95 -5
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +38 -24
- package/src/__internal.ts +92 -0
- 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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +175 -17
- package/src/browser/navigation-client.ts +177 -44
- 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 +275 -28
- package/src/browser/prefetch/fetch.ts +191 -46
- 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 +98 -14
- 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 +177 -66
- 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 +73 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +67 -25
- 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.rsc.tsx +2 -1
- package/src/client.tsx +85 -276
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/host/index.ts +0 -3
- package/src/index.rsc.ts +9 -36
- package/src/index.ts +79 -70
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +57 -15
- 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 -3
- 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 +129 -26
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +160 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -193
- 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 +103 -18
- package/src/router/metrics.ts +238 -13
- package/src/router/middleware-types.ts +48 -27
- package/src/router/middleware.ts +201 -86
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +77 -11
- package/src/router/prerender-match.ts +114 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +27 -7
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +50 -5
- package/src/router/router-options.ts +50 -19
- package/src/router/segment-resolution/fresh.ts +215 -19
- package/src/router/segment-resolution/helpers.ts +30 -25
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +454 -301
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +30 -6
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +89 -17
- package/src/rsc/handler.ts +563 -364
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +37 -10
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +47 -44
- package/src/rsc/server-action.ts +24 -10
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +11 -1
- package/src/search-params.ts +16 -13
- 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 +174 -19
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +218 -65
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +4 -0
- package/src/static-handler.ts +18 -6
- package/src/theme/index.ts +4 -13
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +140 -72
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +17 -8
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +2 -5
- 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/discovery/bundle-postprocess.ts +61 -89
- package/src/vite/discovery/discover-routers.ts +7 -4
- package/src/vite/discovery/prerender-collection.ts +162 -88
- package/src/vite/discovery/state.ts +17 -13
- package/src/vite/index.ts +8 -3
- 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 +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +190 -217
- package/src/vite/router-discovery.ts +241 -45
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +34 -1
- package/src/vite/utils/prerender-utils.ts +97 -5
- package/src/vite/utils/shared-utils.ts +3 -2
- package/skills/testing/SKILL.md +0 -226
- package/src/route-definition/route-function.ts +0 -119
|
@@ -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
|
-
import { track } from "./context.js";
|
|
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,19 +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 (system params like _rsc* are NOT filtered here) */
|
|
53
|
-
url: URL;
|
|
54
|
-
/** URL pathname */
|
|
55
|
-
pathname: string;
|
|
56
|
-
/** URL search params (system params like _rsc* are NOT filtered here) */
|
|
57
|
-
searchParams: URLSearchParams;
|
|
58
|
-
/** Variables set by middleware (same as ctx.var) */
|
|
59
|
-
var: Record<string, any>;
|
|
63
|
+
> extends RequestScope<TEnv> {
|
|
64
|
+
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
65
|
+
_variables: Record<string, any>;
|
|
60
66
|
/** Get a variable set by middleware */
|
|
61
67
|
get: {
|
|
62
68
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -64,20 +70,19 @@ export interface RequestContext<
|
|
|
64
70
|
};
|
|
65
71
|
/** Set a variable (shared with middleware and handlers) */
|
|
66
72
|
set: {
|
|
67
|
-
<T>(
|
|
68
|
-
|
|
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;
|
|
69
79
|
};
|
|
70
80
|
/**
|
|
71
81
|
* Route params (populated after route matching)
|
|
72
82
|
* Initially empty, then set to matched params
|
|
73
83
|
*/
|
|
74
84
|
params: TParams;
|
|
75
|
-
/**
|
|
76
|
-
* Stub response for setting headers/cookies (read-only).
|
|
77
|
-
* Headers set here are merged into the final response.
|
|
78
|
-
* Use header() or setStatus() to mutate response headers/status.
|
|
79
|
-
* Use cookies().set()/cookies().delete() for cookie mutations.
|
|
80
|
-
*/
|
|
85
|
+
/** @internal Stub response for collecting headers/cookies. Use ctx.headers or ctx.header() instead. */
|
|
81
86
|
readonly res: Response;
|
|
82
87
|
|
|
83
88
|
/** @internal Get a cookie value (effective: request + response mutations). Use cookies().get() instead. */
|
|
@@ -95,6 +100,8 @@ export interface RequestContext<
|
|
|
95
100
|
header(name: string, value: string): void;
|
|
96
101
|
/** Set the response status code */
|
|
97
102
|
setStatus(status: number): void;
|
|
103
|
+
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
104
|
+
_setStatus(status: number): void;
|
|
98
105
|
|
|
99
106
|
/**
|
|
100
107
|
* Access loader data or push handle data.
|
|
@@ -139,20 +146,6 @@ export interface RequestContext<
|
|
|
139
146
|
import("../cache/profile-registry.js").CacheProfile
|
|
140
147
|
>;
|
|
141
148
|
|
|
142
|
-
/**
|
|
143
|
-
* Schedule work to run after the response is sent.
|
|
144
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
145
|
-
* On Node.js, runs as fire-and-forget.
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```typescript
|
|
149
|
-
* ctx.waitUntil(async () => {
|
|
150
|
-
* await cacheStore.set(key, data, ttl);
|
|
151
|
-
* });
|
|
152
|
-
* ```
|
|
153
|
-
*/
|
|
154
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
155
|
-
|
|
156
149
|
/**
|
|
157
150
|
* Register a callback to run when the response is created.
|
|
158
151
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -256,6 +249,54 @@ export interface RequestContext<
|
|
|
256
249
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
257
250
|
_prevRouteKey?: string;
|
|
258
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
|
+
|
|
259
300
|
/** @internal Per-request error dedup set for onError reporting */
|
|
260
301
|
_reportedErrors: WeakSet<object>;
|
|
261
302
|
|
|
@@ -266,6 +307,21 @@ export interface RequestContext<
|
|
|
266
307
|
* errors without failing the response.
|
|
267
308
|
*/
|
|
268
309
|
_reportBackgroundError?: (error: unknown, category: string) => void;
|
|
310
|
+
|
|
311
|
+
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
312
|
+
_debugPerformance?: boolean;
|
|
313
|
+
|
|
314
|
+
/** @internal Request-scoped performance metrics store */
|
|
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;
|
|
269
325
|
}
|
|
270
326
|
|
|
271
327
|
/**
|
|
@@ -292,7 +348,21 @@ export type PublicRequestContext<
|
|
|
292
348
|
| "_routeName"
|
|
293
349
|
| "_prevRouteKey"
|
|
294
350
|
| "_reportedErrors"
|
|
351
|
+
| "_renderBarrier"
|
|
352
|
+
| "_resolveRenderBarrier"
|
|
353
|
+
| "_renderBarrierSegmentOrder"
|
|
354
|
+
| "_treeHasStreaming"
|
|
355
|
+
| "_renderBarrierWaiters"
|
|
356
|
+
| "_handlerLoaderDeps"
|
|
357
|
+
| "_renderBarrierHandleSnapshot"
|
|
295
358
|
| "_reportBackgroundError"
|
|
359
|
+
| "_debugPerformance"
|
|
360
|
+
| "_metricsStore"
|
|
361
|
+
| "_basename"
|
|
362
|
+
| "_setStatus"
|
|
363
|
+
| "_variables"
|
|
364
|
+
| "_classifiedRoute"
|
|
365
|
+
| "res"
|
|
296
366
|
>;
|
|
297
367
|
|
|
298
368
|
// AsyncLocalStorage instance for request context
|
|
@@ -401,13 +471,7 @@ export function requireRequestContext<
|
|
|
401
471
|
return getRequestContext<TEnv>();
|
|
402
472
|
}
|
|
403
473
|
|
|
404
|
-
|
|
405
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
406
|
-
*/
|
|
407
|
-
export interface ExecutionContext {
|
|
408
|
-
waitUntil(promise: Promise<any>): void;
|
|
409
|
-
passThroughOnException(): void;
|
|
410
|
-
}
|
|
474
|
+
export type { ExecutionContext };
|
|
411
475
|
|
|
412
476
|
/**
|
|
413
477
|
* Options for creating a request context
|
|
@@ -491,6 +555,18 @@ export function createRequestContext<TEnv>(
|
|
|
491
555
|
responseCookieCache = null;
|
|
492
556
|
};
|
|
493
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
|
+
|
|
494
570
|
// Effective cookie read: response stub Set-Cookie wins, then original header.
|
|
495
571
|
// The stub IS the source of truth for same-request mutations.
|
|
496
572
|
const effectiveCookie = (name: string): string | undefined => {
|
|
@@ -543,19 +619,31 @@ export function createRequestContext<TEnv>(
|
|
|
543
619
|
invalidateResponseCookieCache();
|
|
544
620
|
};
|
|
545
621
|
|
|
622
|
+
// Strip internal _rsc* params so userland sees a clean URL.
|
|
623
|
+
const cleanUrl = stripInternalParams(url);
|
|
624
|
+
|
|
546
625
|
// Build the context object first (without use), then add use
|
|
547
626
|
const ctx: RequestContext<TEnv> = {
|
|
548
627
|
env,
|
|
549
628
|
request,
|
|
550
|
-
url,
|
|
629
|
+
url: cleanUrl,
|
|
630
|
+
originalUrl: new URL(request.url),
|
|
551
631
|
pathname: url.pathname,
|
|
552
|
-
searchParams:
|
|
553
|
-
|
|
554
|
-
get: ((keyOrVar: any) =>
|
|
555
|
-
|
|
556
|
-
|
|
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) => {
|
|
557
645
|
assertNotInsideCacheExec(ctx, "set");
|
|
558
|
-
contextSet(variables, keyOrVar, value);
|
|
646
|
+
contextSet(variables, keyOrVar, value, options);
|
|
559
647
|
}) as RequestContext<TEnv>["set"],
|
|
560
648
|
params: {} as Record<string, string>,
|
|
561
649
|
|
|
@@ -593,6 +681,7 @@ export function createRequestContext<TEnv>(
|
|
|
593
681
|
|
|
594
682
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
595
683
|
assertNotInsideCacheExec(ctx, "setCookie");
|
|
684
|
+
assertNotInsideCacheScopeALS("setCookie");
|
|
596
685
|
stubResponse.headers.append(
|
|
597
686
|
"Set-Cookie",
|
|
598
687
|
serializeCookieValue(name, value, options),
|
|
@@ -605,6 +694,7 @@ export function createRequestContext<TEnv>(
|
|
|
605
694
|
options?: Pick<CookieOptions, "domain" | "path">,
|
|
606
695
|
): void {
|
|
607
696
|
assertNotInsideCacheExec(ctx, "deleteCookie");
|
|
697
|
+
assertNotInsideCacheScopeALS("deleteCookie");
|
|
608
698
|
stubResponse.headers.append(
|
|
609
699
|
"Set-Cookie",
|
|
610
700
|
serializeCookieValue(name, "", { ...options, maxAge: 0 }),
|
|
@@ -614,13 +704,20 @@ export function createRequestContext<TEnv>(
|
|
|
614
704
|
|
|
615
705
|
header(name: string, value: string): void {
|
|
616
706
|
assertNotInsideCacheExec(ctx, "header");
|
|
707
|
+
assertNotInsideCacheScopeALS("header");
|
|
617
708
|
stubResponse.headers.set(name, value);
|
|
618
709
|
},
|
|
619
710
|
|
|
620
711
|
setStatus(status: number): void {
|
|
621
712
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
622
|
-
|
|
623
|
-
|
|
713
|
+
assertNotInsideCacheScopeALS("setStatus");
|
|
714
|
+
stubResponse = new Response(null, {
|
|
715
|
+
status,
|
|
716
|
+
headers: stubResponse.headers,
|
|
717
|
+
});
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
_setStatus(status: number): void {
|
|
624
721
|
stubResponse = new Response(null, {
|
|
625
722
|
status,
|
|
626
723
|
headers: stubResponse.headers,
|
|
@@ -638,20 +735,19 @@ export function createRequestContext<TEnv>(
|
|
|
638
735
|
|
|
639
736
|
waitUntil(fn: () => Promise<void>): void {
|
|
640
737
|
if (executionContext?.waitUntil) {
|
|
641
|
-
// Cloudflare Workers: use native waitUntil
|
|
642
738
|
executionContext.waitUntil(fn());
|
|
643
739
|
} else {
|
|
644
|
-
|
|
645
|
-
fn().catch((err) =>
|
|
646
|
-
console.error("[waitUntil] Background task failed:", err),
|
|
647
|
-
);
|
|
740
|
+
fireAndForgetWaitUntil(fn);
|
|
648
741
|
}
|
|
649
742
|
},
|
|
650
743
|
|
|
744
|
+
executionContext,
|
|
745
|
+
|
|
651
746
|
_onResponseCallbacks: [],
|
|
652
747
|
|
|
653
748
|
onResponse(callback: (response: Response) => Response): void {
|
|
654
749
|
assertNotInsideCacheExec(ctx, "onResponse");
|
|
750
|
+
assertNotInsideCacheScopeALS("onResponse");
|
|
655
751
|
this._onResponseCallbacks.push(callback);
|
|
656
752
|
},
|
|
657
753
|
|
|
@@ -677,10 +773,60 @@ export function createRequestContext<TEnv>(
|
|
|
677
773
|
_locationState: undefined,
|
|
678
774
|
|
|
679
775
|
_reportedErrors: new WeakSet<object>(),
|
|
776
|
+
_metricsStore: undefined,
|
|
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,
|
|
680
782
|
|
|
681
783
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
682
784
|
};
|
|
683
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
|
+
|
|
684
830
|
// Now create use() with access to ctx
|
|
685
831
|
ctx.use = createUseFunction({
|
|
686
832
|
handleStore,
|
|
@@ -862,15 +1008,17 @@ export function createUseFunction<TEnv>(
|
|
|
862
1008
|
search: (ctx as any).search ?? {},
|
|
863
1009
|
pathname: ctx.pathname,
|
|
864
1010
|
url: ctx.url,
|
|
1011
|
+
originalUrl: ctx.originalUrl,
|
|
865
1012
|
env: ctx.env as any,
|
|
866
|
-
|
|
1013
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1014
|
+
executionContext: ctx.executionContext,
|
|
867
1015
|
get: ctx.get as any,
|
|
868
|
-
use: <TDep, TDepParams = any>(
|
|
1016
|
+
use: (<TDep, TDepParams = any>(
|
|
869
1017
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
870
1018
|
): Promise<TDep> => {
|
|
871
1019
|
// Recursive call - will start dep loader if not already started
|
|
872
1020
|
return ctx.use(dep);
|
|
873
|
-
},
|
|
1021
|
+
}) as LoaderContext["use"],
|
|
874
1022
|
method: "GET",
|
|
875
1023
|
body: undefined,
|
|
876
1024
|
reverse: createReverseFunction(
|
|
@@ -879,10 +1027,15 @@ export function createUseFunction<TEnv>(
|
|
|
879
1027
|
ctx.params as Record<string, string>,
|
|
880
1028
|
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
881
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
|
+
},
|
|
882
1036
|
};
|
|
883
1037
|
|
|
884
|
-
|
|
885
|
-
const doneLoader = track(`loader:${loader.$$id}`);
|
|
1038
|
+
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
886
1039
|
const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
|
|
887
1040
|
doneLoader();
|
|
888
1041
|
});
|
package/src/server.ts
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
// Router registry (used by Vite plugin for build-time discovery)
|
|
12
12
|
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router.js";
|
|
13
13
|
|
|
14
|
+
// Host router registry (used by Vite plugin for host-router lazy discovery)
|
|
15
|
+
export {
|
|
16
|
+
HostRouterRegistry,
|
|
17
|
+
type HostRouterRegistryEntry,
|
|
18
|
+
} from "./host/router.js";
|
|
19
|
+
|
|
14
20
|
// Route map builder (Vite plugin injects these via virtual modules)
|
|
15
21
|
export {
|
|
16
22
|
registerRouteMap,
|
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/theme/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme module exports for @rangojs/router/theme
|
|
3
3
|
*
|
|
4
|
-
* This module provides
|
|
4
|
+
* This module provides the public theme API:
|
|
5
5
|
* - useTheme: Hook for accessing theme state in client components
|
|
6
6
|
* - ThemeProvider: Component for manual theme provider setup (typically not needed)
|
|
7
|
+
* - ThemeScript: FOUC-prevention script component for document/head usage
|
|
7
8
|
* - Types for theme configuration
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
@@ -43,15 +44,5 @@ export type {
|
|
|
43
44
|
ThemeContextValue,
|
|
44
45
|
} from "./types.js";
|
|
45
46
|
|
|
46
|
-
// Constants
|
|
47
|
-
export {
|
|
48
|
-
THEME_DEFAULTS,
|
|
49
|
-
THEME_COOKIE,
|
|
50
|
-
resolveThemeConfig,
|
|
51
|
-
} from "./constants.js";
|
|
52
|
-
|
|
53
|
-
// Script generation (for advanced SSR use cases)
|
|
54
|
-
export { generateThemeScript, getNonceAttribute } from "./theme-script.js";
|
|
55
|
-
|
|
56
|
-
// Context (for advanced use cases)
|
|
57
|
-
export { ThemeContext, useThemeContext } from "./theme-context.js";
|
|
47
|
+
// Constants
|
|
48
|
+
export { THEME_DEFAULTS, THEME_COOKIE } from "./constants.js";
|
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
|