@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1fa245e2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{CLAUDE.md → AGENTS.md} +4 -0
- package/README.md +122 -30
- package/dist/bin/rango.js +245 -63
- package/dist/vite/index.js +859 -418
- package/package.json +3 -3
- package/skills/breadcrumbs/SKILL.md +250 -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/hooks/SKILL.md +33 -31
- package/skills/host-router/SKILL.md +218 -0
- package/skills/links/SKILL.md +3 -1
- package/skills/loader/SKILL.md +72 -22
- package/skills/middleware/SKILL.md +2 -0
- package/skills/parallel/SKILL.md +126 -0
- package/skills/prerender/SKILL.md +112 -70
- package/skills/rango/SKILL.md +0 -1
- package/skills/route/SKILL.md +34 -4
- package/skills/router-setup/SKILL.md +95 -5
- package/skills/typesafety/SKILL.md +35 -23
- package/src/__internal.ts +92 -0
- package/src/bin/rango.ts +18 -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 +114 -18
- package/src/browser/navigation-client.ts +126 -44
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +80 -15
- package/src/browser/prefetch/cache.ts +166 -27
- package/src/browser/prefetch/fetch.ts +52 -39
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +92 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/react/Link.tsx +70 -14
- package/src/browser/react/NavigationProvider.tsx +40 -4
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-router.ts +21 -8
- package/src/browser/rsc-router.tsx +143 -59
- package/src/browser/scroll-restoration.ts +41 -42
- package/src/browser/segment-reconciler.ts +6 -1
- package/src/browser/server-action-bridge.ts +454 -436
- package/src/browser/types.ts +60 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/route-trie.ts +19 -3
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +346 -87
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +48 -7
- package/src/cache/cf/cf-cache-store.ts +453 -11
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +2 -1
- package/src/client.tsx +3 -102
- 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 +8 -37
- package/src/index.ts +40 -66
- package/src/prerender/store.ts +57 -15
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +22 -1
- package/src/route-definition/dsl-helpers.ts +73 -25
- package/src/route-definition/helpers-types.ts +10 -6
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +11 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +108 -25
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +4 -1
- package/src/router/loader-resolution.ts +123 -11
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +9 -3
- package/src/router/match-api.ts +125 -190
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +88 -16
- package/src/router/match-middleware/cache-store.ts +53 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +22 -15
- package/src/router/metrics.ts +238 -13
- package/src/router/middleware-types.ts +53 -12
- package/src/router/middleware.ts +172 -85
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +20 -5
- 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 +200 -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 +429 -301
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +20 -2
- package/src/router/types.ts +1 -0
- package/src/router.ts +88 -15
- package/src/rsc/handler.ts +546 -359
- package/src/rsc/index.ts +0 -20
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +25 -8
- package/src/rsc/rsc-rendering.ts +35 -43
- package/src/rsc/server-action.ts +16 -10
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +10 -1
- package/src/search-params.ts +16 -13
- package/src/segment-system.tsx +140 -4
- package/src/server/context.ts +148 -16
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +182 -34
- 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 +149 -49
- package/src/types/loader-types.ts +36 -9
- package/src/types/route-config.ts +17 -8
- package/src/types/route-entry.ts +8 -1
- package/src/types/segments.ts +2 -5
- package/src/urls/path-helper-types.ts +9 -2
- package/src/urls/path-helper.ts +48 -13
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +16 -6
- package/src/use-loader.tsx +73 -4
- package/src/vite/discovery/bundle-postprocess.ts +61 -89
- package/src/vite/discovery/discover-routers.ts +23 -5
- package/src/vite/discovery/prerender-collection.ts +48 -15
- 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/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +174 -211
- package/src/vite/router-discovery.ts +169 -42
- package/src/vite/utils/banner.ts +3 -3
- package/src/vite/utils/prerender-utils.ts +78 -0
- 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
|
@@ -15,21 +15,31 @@ import type { CookieOptions } from "../router/middleware.js";
|
|
|
15
15
|
import type { LoaderDefinition, LoaderContext } from "../types.js";
|
|
16
16
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
17
17
|
import type {
|
|
18
|
+
DefaultEnv,
|
|
18
19
|
DefaultReverseRouteMap,
|
|
19
20
|
DefaultRouteName,
|
|
20
21
|
} from "../types/global-namespace.js";
|
|
21
22
|
import type { Handle } from "../handle.js";
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
type ContextVar,
|
|
25
|
+
contextGet,
|
|
26
|
+
contextSet,
|
|
27
|
+
isNonCacheable,
|
|
28
|
+
} from "../context-var.js";
|
|
23
29
|
import { createHandleStore, type HandleStore } from "./handle-store.js";
|
|
24
30
|
import { isHandle } from "../handle.js";
|
|
25
|
-
import { track } from "./context.js";
|
|
31
|
+
import { track, type MetricsStore } from "./context.js";
|
|
26
32
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
27
33
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
28
34
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
29
35
|
import { THEME_COOKIE } from "../theme/constants.js";
|
|
30
36
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
31
37
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
32
|
-
import {
|
|
38
|
+
import { isInsideCacheScope } from "./context.js";
|
|
39
|
+
import {
|
|
40
|
+
createReverseFunction,
|
|
41
|
+
stripInternalParams,
|
|
42
|
+
} from "../router/handler-context.js";
|
|
33
43
|
import { getGlobalRouteMap, isRouteRootScoped } from "../route-map-builder.js";
|
|
34
44
|
import { invariant } from "../errors.js";
|
|
35
45
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
@@ -41,21 +51,26 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
41
51
|
* Use this when you need access to request data outside of route handlers.
|
|
42
52
|
*/
|
|
43
53
|
export interface RequestContext<
|
|
44
|
-
TEnv =
|
|
54
|
+
TEnv = DefaultEnv,
|
|
45
55
|
TParams = Record<string, string>,
|
|
46
56
|
> {
|
|
47
57
|
/** Platform bindings (Cloudflare env, etc.) */
|
|
48
58
|
env: TEnv;
|
|
49
59
|
/** Original HTTP request */
|
|
50
60
|
request: Request;
|
|
51
|
-
/** Parsed URL (
|
|
61
|
+
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
52
62
|
url: URL;
|
|
63
|
+
/**
|
|
64
|
+
* The original request URL with all parameters intact, including
|
|
65
|
+
* internal `_rsc*` transport params.
|
|
66
|
+
*/
|
|
67
|
+
originalUrl: URL;
|
|
53
68
|
/** URL pathname */
|
|
54
69
|
pathname: string;
|
|
55
|
-
/** URL search params (
|
|
70
|
+
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
56
71
|
searchParams: URLSearchParams;
|
|
57
|
-
/**
|
|
58
|
-
|
|
72
|
+
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
73
|
+
_variables: Record<string, any>;
|
|
59
74
|
/** Get a variable set by middleware */
|
|
60
75
|
get: {
|
|
61
76
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -63,20 +78,19 @@ export interface RequestContext<
|
|
|
63
78
|
};
|
|
64
79
|
/** Set a variable (shared with middleware and handlers) */
|
|
65
80
|
set: {
|
|
66
|
-
<T>(
|
|
67
|
-
|
|
81
|
+
<T>(
|
|
82
|
+
contextVar: ContextVar<T>,
|
|
83
|
+
value: T,
|
|
84
|
+
options?: { cache?: boolean },
|
|
85
|
+
): void;
|
|
86
|
+
<K extends string>(key: K, value: any, options?: { cache?: boolean }): void;
|
|
68
87
|
};
|
|
69
88
|
/**
|
|
70
89
|
* Route params (populated after route matching)
|
|
71
90
|
* Initially empty, then set to matched params
|
|
72
91
|
*/
|
|
73
92
|
params: TParams;
|
|
74
|
-
/**
|
|
75
|
-
* Stub response for setting headers/cookies (read-only).
|
|
76
|
-
* Headers set here are merged into the final response.
|
|
77
|
-
* Use header() or setStatus() to mutate response headers/status.
|
|
78
|
-
* Use cookies().set()/cookies().delete() for cookie mutations.
|
|
79
|
-
*/
|
|
93
|
+
/** @internal Stub response for collecting headers/cookies. Use ctx.headers or ctx.header() instead. */
|
|
80
94
|
readonly res: Response;
|
|
81
95
|
|
|
82
96
|
/** @internal Get a cookie value (effective: request + response mutations). Use cookies().get() instead. */
|
|
@@ -94,6 +108,8 @@ export interface RequestContext<
|
|
|
94
108
|
header(name: string, value: string): void;
|
|
95
109
|
/** Set the response status code */
|
|
96
110
|
setStatus(status: number): void;
|
|
111
|
+
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
112
|
+
_setStatus(status: number): void;
|
|
97
113
|
|
|
98
114
|
/**
|
|
99
115
|
* Access loader data or push handle data.
|
|
@@ -255,6 +271,41 @@ export interface RequestContext<
|
|
|
255
271
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
256
272
|
_prevRouteKey?: string;
|
|
257
273
|
|
|
274
|
+
/**
|
|
275
|
+
* @internal Render barrier for experimental `rendered()` API.
|
|
276
|
+
* Resolves when all non-loader segments have settled and handle data
|
|
277
|
+
* is available. Used by DSL loaders that call `ctx.rendered()`.
|
|
278
|
+
*/
|
|
279
|
+
_renderBarrier: Promise<void>;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @internal Resolve the render barrier. Accepts resolved segments, filters
|
|
283
|
+
* out loaders, and captures non-loader segment IDs as the handle ordering.
|
|
284
|
+
* Called after segment resolution (fresh) or handle replay (cache/prerender).
|
|
285
|
+
*/
|
|
286
|
+
_resolveRenderBarrier: (
|
|
287
|
+
segments: Array<{ type: string; id: string }>,
|
|
288
|
+
) => void;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @internal Segment order at barrier resolution time, used by loader
|
|
292
|
+
* ctx.use(handle) to collect handle data in correct order.
|
|
293
|
+
*/
|
|
294
|
+
_renderBarrierSegmentOrder?: string[];
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @internal Set to true when the matched entry tree contains any `loading()`
|
|
298
|
+
* entries (streaming). Used by rendered() to fail fast.
|
|
299
|
+
*/
|
|
300
|
+
_treeHasStreaming?: boolean;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* @internal Loader IDs that have called rendered() and are waiting for the
|
|
304
|
+
* barrier. Used to detect deadlocks when a handler tries to await the same
|
|
305
|
+
* loader via ctx.use(Loader).
|
|
306
|
+
*/
|
|
307
|
+
_renderBarrierWaiters?: Set<string>;
|
|
308
|
+
|
|
258
309
|
/** @internal Per-request error dedup set for onError reporting */
|
|
259
310
|
_reportedErrors: WeakSet<object>;
|
|
260
311
|
|
|
@@ -265,6 +316,21 @@ export interface RequestContext<
|
|
|
265
316
|
* errors without failing the response.
|
|
266
317
|
*/
|
|
267
318
|
_reportBackgroundError?: (error: unknown, category: string) => void;
|
|
319
|
+
|
|
320
|
+
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
321
|
+
_debugPerformance?: boolean;
|
|
322
|
+
|
|
323
|
+
/** @internal Request-scoped performance metrics store */
|
|
324
|
+
_metricsStore?: MetricsStore;
|
|
325
|
+
|
|
326
|
+
/** @internal Router basename for this request (used by redirect()) */
|
|
327
|
+
_basename?: string;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
|
|
331
|
+
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
332
|
+
*/
|
|
333
|
+
_classifiedRoute?: import("../router/route-snapshot.js").RouteSnapshot;
|
|
268
334
|
}
|
|
269
335
|
|
|
270
336
|
/**
|
|
@@ -274,7 +340,7 @@ export interface RequestContext<
|
|
|
274
340
|
* use the full RequestContext interface directly.
|
|
275
341
|
*/
|
|
276
342
|
export type PublicRequestContext<
|
|
277
|
-
TEnv =
|
|
343
|
+
TEnv = DefaultEnv,
|
|
278
344
|
TParams = Record<string, string>,
|
|
279
345
|
> = Omit<
|
|
280
346
|
RequestContext<TEnv, TParams>,
|
|
@@ -291,7 +357,19 @@ export type PublicRequestContext<
|
|
|
291
357
|
| "_routeName"
|
|
292
358
|
| "_prevRouteKey"
|
|
293
359
|
| "_reportedErrors"
|
|
360
|
+
| "_renderBarrier"
|
|
361
|
+
| "_resolveRenderBarrier"
|
|
362
|
+
| "_renderBarrierSegmentOrder"
|
|
363
|
+
| "_treeHasStreaming"
|
|
364
|
+
| "_renderBarrierWaiters"
|
|
294
365
|
| "_reportBackgroundError"
|
|
366
|
+
| "_debugPerformance"
|
|
367
|
+
| "_metricsStore"
|
|
368
|
+
| "_basename"
|
|
369
|
+
| "_setStatus"
|
|
370
|
+
| "_variables"
|
|
371
|
+
| "_classifiedRoute"
|
|
372
|
+
| "res"
|
|
295
373
|
>;
|
|
296
374
|
|
|
297
375
|
// AsyncLocalStorage instance for request context
|
|
@@ -312,7 +390,7 @@ export function runWithRequestContext<TEnv, T>(
|
|
|
312
390
|
* Get the current request context
|
|
313
391
|
* Throws if called outside of a request context
|
|
314
392
|
*/
|
|
315
|
-
export function getRequestContext<TEnv =
|
|
393
|
+
export function getRequestContext<TEnv = DefaultEnv>(): RequestContext<TEnv> {
|
|
316
394
|
const ctx = requestContextStorage.getStore() as
|
|
317
395
|
| RequestContext<TEnv>
|
|
318
396
|
| undefined;
|
|
@@ -329,7 +407,7 @@ export function getRequestContext<TEnv = unknown>(): RequestContext<TEnv> {
|
|
|
329
407
|
* @internal Get the request context without throwing — for internal code that
|
|
330
408
|
* may run outside a request context (cache stores, optional handle lookups, etc.)
|
|
331
409
|
*/
|
|
332
|
-
export function _getRequestContext<TEnv =
|
|
410
|
+
export function _getRequestContext<TEnv = DefaultEnv>():
|
|
333
411
|
| RequestContext<TEnv>
|
|
334
412
|
| undefined {
|
|
335
413
|
return requestContextStorage.getStore() as RequestContext<TEnv> | undefined;
|
|
@@ -394,7 +472,9 @@ export function getLocationState(): LocationStateEntry[] | undefined {
|
|
|
394
472
|
* Get the current request context, throwing if not available
|
|
395
473
|
* @deprecated Use getRequestContext() directly — it now throws if outside context
|
|
396
474
|
*/
|
|
397
|
-
export function requireRequestContext<
|
|
475
|
+
export function requireRequestContext<
|
|
476
|
+
TEnv = DefaultEnv,
|
|
477
|
+
>(): RequestContext<TEnv> {
|
|
398
478
|
return getRequestContext<TEnv>();
|
|
399
479
|
}
|
|
400
480
|
|
|
@@ -488,6 +568,18 @@ export function createRequestContext<TEnv>(
|
|
|
488
568
|
responseCookieCache = null;
|
|
489
569
|
};
|
|
490
570
|
|
|
571
|
+
// Guard: throw if a response-level side effect is called inside a cache() scope.
|
|
572
|
+
// Uses ALS to detect the scope (set during segment resolution).
|
|
573
|
+
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
574
|
+
if (isInsideCacheScope()) {
|
|
575
|
+
throw new Error(
|
|
576
|
+
`ctx.${methodName}() cannot be called inside a cache() boundary. ` +
|
|
577
|
+
`On cache hit the handler is skipped, so this side effect would be lost. ` +
|
|
578
|
+
`Move ctx.${methodName}() to a middleware or layout outside the cache() scope.`,
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
491
583
|
// Effective cookie read: response stub Set-Cookie wins, then original header.
|
|
492
584
|
// The stub IS the source of truth for same-request mutations.
|
|
493
585
|
const effectiveCookie = (name: string): string | undefined => {
|
|
@@ -540,19 +632,31 @@ export function createRequestContext<TEnv>(
|
|
|
540
632
|
invalidateResponseCookieCache();
|
|
541
633
|
};
|
|
542
634
|
|
|
635
|
+
// Strip internal _rsc* params so userland sees a clean URL.
|
|
636
|
+
const cleanUrl = stripInternalParams(url);
|
|
637
|
+
|
|
543
638
|
// Build the context object first (without use), then add use
|
|
544
639
|
const ctx: RequestContext<TEnv> = {
|
|
545
640
|
env,
|
|
546
641
|
request,
|
|
547
|
-
url,
|
|
642
|
+
url: cleanUrl,
|
|
643
|
+
originalUrl: new URL(request.url),
|
|
548
644
|
pathname: url.pathname,
|
|
549
|
-
searchParams:
|
|
550
|
-
|
|
551
|
-
get: ((keyOrVar: any) =>
|
|
552
|
-
|
|
553
|
-
|
|
645
|
+
searchParams: cleanUrl.searchParams,
|
|
646
|
+
_variables: variables,
|
|
647
|
+
get: ((keyOrVar: any) => {
|
|
648
|
+
if (isNonCacheable(variables, keyOrVar) && isInsideCacheScope()) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`ctx.get() for a non-cacheable variable cannot be called inside a cache() boundary. ` +
|
|
651
|
+
`The variable was created with { cache: false } or set with { cache: false }, ` +
|
|
652
|
+
`and its value would be stale on cache hit. Move the read outside the cached scope.`,
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
return contextGet(variables, keyOrVar);
|
|
656
|
+
}) as RequestContext<TEnv>["get"],
|
|
657
|
+
set: ((keyOrVar: any, value: any, options?: any) => {
|
|
554
658
|
assertNotInsideCacheExec(ctx, "set");
|
|
555
|
-
contextSet(variables, keyOrVar, value);
|
|
659
|
+
contextSet(variables, keyOrVar, value, options);
|
|
556
660
|
}) as RequestContext<TEnv>["set"],
|
|
557
661
|
params: {} as Record<string, string>,
|
|
558
662
|
|
|
@@ -590,6 +694,7 @@ export function createRequestContext<TEnv>(
|
|
|
590
694
|
|
|
591
695
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
592
696
|
assertNotInsideCacheExec(ctx, "setCookie");
|
|
697
|
+
assertNotInsideCacheScopeALS("setCookie");
|
|
593
698
|
stubResponse.headers.append(
|
|
594
699
|
"Set-Cookie",
|
|
595
700
|
serializeCookieValue(name, value, options),
|
|
@@ -602,6 +707,7 @@ export function createRequestContext<TEnv>(
|
|
|
602
707
|
options?: Pick<CookieOptions, "domain" | "path">,
|
|
603
708
|
): void {
|
|
604
709
|
assertNotInsideCacheExec(ctx, "deleteCookie");
|
|
710
|
+
assertNotInsideCacheScopeALS("deleteCookie");
|
|
605
711
|
stubResponse.headers.append(
|
|
606
712
|
"Set-Cookie",
|
|
607
713
|
serializeCookieValue(name, "", { ...options, maxAge: 0 }),
|
|
@@ -611,13 +717,20 @@ export function createRequestContext<TEnv>(
|
|
|
611
717
|
|
|
612
718
|
header(name: string, value: string): void {
|
|
613
719
|
assertNotInsideCacheExec(ctx, "header");
|
|
720
|
+
assertNotInsideCacheScopeALS("header");
|
|
614
721
|
stubResponse.headers.set(name, value);
|
|
615
722
|
},
|
|
616
723
|
|
|
617
724
|
setStatus(status: number): void {
|
|
618
725
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
619
|
-
|
|
620
|
-
|
|
726
|
+
assertNotInsideCacheScopeALS("setStatus");
|
|
727
|
+
stubResponse = new Response(null, {
|
|
728
|
+
status,
|
|
729
|
+
headers: stubResponse.headers,
|
|
730
|
+
});
|
|
731
|
+
},
|
|
732
|
+
|
|
733
|
+
_setStatus(status: number): void {
|
|
621
734
|
stubResponse = new Response(null, {
|
|
622
735
|
status,
|
|
623
736
|
headers: stubResponse.headers,
|
|
@@ -649,6 +762,7 @@ export function createRequestContext<TEnv>(
|
|
|
649
762
|
|
|
650
763
|
onResponse(callback: (response: Response) => Response): void {
|
|
651
764
|
assertNotInsideCacheExec(ctx, "onResponse");
|
|
765
|
+
assertNotInsideCacheScopeALS("onResponse");
|
|
652
766
|
this._onResponseCallbacks.push(callback);
|
|
653
767
|
},
|
|
654
768
|
|
|
@@ -674,10 +788,40 @@ export function createRequestContext<TEnv>(
|
|
|
674
788
|
_locationState: undefined,
|
|
675
789
|
|
|
676
790
|
_reportedErrors: new WeakSet<object>(),
|
|
791
|
+
_metricsStore: undefined,
|
|
792
|
+
|
|
793
|
+
// Render barrier: deferred promise resolved after non-loader segments settle.
|
|
794
|
+
_renderBarrier: null as any, // set below
|
|
795
|
+
_resolveRenderBarrier: null as any, // set below
|
|
796
|
+
_renderBarrierSegmentOrder: undefined,
|
|
677
797
|
|
|
678
798
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
679
799
|
};
|
|
680
800
|
|
|
801
|
+
// Create deferred render barrier. Phase 1: non-streaming only, so all handlers
|
|
802
|
+
// complete synchronously during resolveAllSegments. The barrier is a simple
|
|
803
|
+
// deferred promise resolved after segment resolution (or after handle replay
|
|
804
|
+
// on cache/prerender paths). No HandleStore sealing here — that stays in the
|
|
805
|
+
// existing lifecycle (rsc-rendering.ts, cache-scope.ts, etc.).
|
|
806
|
+
let barrierResolved = false;
|
|
807
|
+
let resolveBarrier: () => void;
|
|
808
|
+
ctx._renderBarrier = new Promise<void>((resolve) => {
|
|
809
|
+
resolveBarrier = resolve;
|
|
810
|
+
});
|
|
811
|
+
ctx._resolveRenderBarrier = (
|
|
812
|
+
segments: Array<{ type: string; id: string }>,
|
|
813
|
+
) => {
|
|
814
|
+
if (barrierResolved) return;
|
|
815
|
+
barrierResolved = true;
|
|
816
|
+
ctx._renderBarrierSegmentOrder = segments
|
|
817
|
+
.filter((s) => s.type !== "loader")
|
|
818
|
+
.map((s) => s.id);
|
|
819
|
+
// Clear deadlock detection set — once the barrier resolves, the loaders
|
|
820
|
+
// waiting on it will settle and the deadlock window is closed.
|
|
821
|
+
ctx._renderBarrierWaiters = undefined;
|
|
822
|
+
resolveBarrier();
|
|
823
|
+
};
|
|
824
|
+
|
|
681
825
|
// Now create use() with access to ctx
|
|
682
826
|
ctx.use = createUseFunction({
|
|
683
827
|
handleStore,
|
|
@@ -860,14 +1004,13 @@ export function createUseFunction<TEnv>(
|
|
|
860
1004
|
pathname: ctx.pathname,
|
|
861
1005
|
url: ctx.url,
|
|
862
1006
|
env: ctx.env as any,
|
|
863
|
-
var: ctx.var as any,
|
|
864
1007
|
get: ctx.get as any,
|
|
865
|
-
use: <TDep, TDepParams = any>(
|
|
1008
|
+
use: (<TDep, TDepParams = any>(
|
|
866
1009
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
867
1010
|
): Promise<TDep> => {
|
|
868
1011
|
// Recursive call - will start dep loader if not already started
|
|
869
1012
|
return ctx.use(dep);
|
|
870
|
-
},
|
|
1013
|
+
}) as LoaderContext["use"],
|
|
871
1014
|
method: "GET",
|
|
872
1015
|
body: undefined,
|
|
873
1016
|
reverse: createReverseFunction(
|
|
@@ -876,10 +1019,15 @@ export function createUseFunction<TEnv>(
|
|
|
876
1019
|
ctx.params as Record<string, string>,
|
|
877
1020
|
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
878
1021
|
),
|
|
1022
|
+
rendered: () => {
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
`ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
|
|
1025
|
+
`It cannot be used from request-context loaders or server actions.`,
|
|
1026
|
+
);
|
|
1027
|
+
},
|
|
879
1028
|
};
|
|
880
1029
|
|
|
881
|
-
|
|
882
|
-
const doneLoader = track(`loader:${loader.$$id}`);
|
|
1030
|
+
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
883
1031
|
const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
|
|
884
1032
|
doneLoader();
|
|
885
1033
|
});
|
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
|