@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/bin/rango.js +3 -4
- package/dist/vite/index.js +315 -68
- package/package.json +19 -18
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/hooks/SKILL.md +2 -2
- package/skills/route/SKILL.md +6 -0
- package/skills/server-actions/SKILL.md +25 -1
- package/skills/testing/SKILL.md +17 -17
- package/skills/testing/cache-prerender.md +29 -3
- package/skills/testing/flight.md +13 -10
- package/skills/testing/render-handler.md +3 -0
- package/skills/testing/server-tree.md +1 -1
- package/skills/testing/setup.md +1 -1
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +10 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/navigation-store-handle.ts +3 -4
- package/src/browser/navigation-store.ts +0 -39
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +23 -84
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +2 -23
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -45
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +2 -1
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +10 -3
- package/src/browser/server-action-bridge.ts +51 -3
- package/src/browser/types.ts +23 -5
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/index.ts +8 -9
- package/src/build/route-trie.ts +46 -11
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +48 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +72 -45
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +10 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +0 -52
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/types.ts +0 -98
- package/src/client.rsc.tsx +4 -22
- package/src/client.tsx +19 -32
- package/src/context-var.ts +12 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +2 -12
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +0 -16
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +27 -2
- package/src/index.ts +7 -0
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +4 -15
- package/src/loader.ts +3 -9
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +23 -30
- package/src/prerender.ts +34 -0
- package/src/redirect-origin.ts +100 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-map-builder.ts +0 -16
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -31
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +25 -23
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +0 -43
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +96 -179
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +77 -60
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +5 -56
- package/src/router/prerender-match.ts +56 -51
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +14 -62
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +10 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +35 -23
- package/src/router/segment-resolution/revalidation.ts +188 -283
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +0 -22
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +66 -45
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +8 -11
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +20 -4
- package/src/rsc/helpers.ts +71 -3
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/origin-guard.ts +9 -15
- package/src/rsc/progressive-enhancement.ts +10 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-route-handler.ts +23 -18
- package/src/rsc/rsc-rendering.ts +2 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +34 -29
- package/src/rsc/types.ts +6 -3
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +29 -92
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +2 -27
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +1 -24
- package/src/testing/dispatch.ts +43 -6
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/flight-matchers.ts +0 -13
- package/src/testing/flight-normalize.ts +3 -30
- package/src/testing/flight.ts +46 -48
- package/src/testing/generated-routes.ts +1 -41
- package/src/testing/index.ts +1 -21
- package/src/testing/internal/context.ts +3 -45
- package/src/testing/internal/seed-vars.ts +0 -26
- package/src/testing/render-handler.ts +31 -61
- package/src/testing/render-route.tsx +75 -103
- package/src/testing/run-loader.ts +0 -96
- package/src/testing/run-middleware.ts +0 -26
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/error-types.ts +25 -89
- package/src/types/global-namespace.ts +4 -14
- package/src/types/handler-context.ts +28 -9
- package/src/types/index.ts +0 -10
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +28 -18
- package/src/vite/discovery/prerender-collection.ts +2 -4
- package/src/vite/discovery/state.ts +5 -0
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +35 -9
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +2 -108
- package/src/vite/router-discovery.ts +9 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -28,8 +28,6 @@ import type { CacheProfile } from "../../cache/profile-registry.js";
|
|
|
28
28
|
|
|
29
29
|
const DEFAULT_ORIGIN = "http://localhost/";
|
|
30
30
|
|
|
31
|
-
// VarsInit + seedVariables live in ./seed-vars.js (react-server-safe) so the
|
|
32
|
-
// Flight tier can seed vars too; re-exported here for existing importers.
|
|
33
31
|
export type { VarsInit, StateCookieSeed };
|
|
34
32
|
export { seedVariables };
|
|
35
33
|
|
|
@@ -39,10 +37,9 @@ export function toRequest(
|
|
|
39
37
|
init?: RequestInit,
|
|
40
38
|
): Request {
|
|
41
39
|
if (request instanceof Request) return request;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return new Request(DEFAULT_ORIGIN, init);
|
|
40
|
+
return typeof request === "string"
|
|
41
|
+
? new Request(new URL(request, DEFAULT_ORIGIN), init)
|
|
42
|
+
: new Request(DEFAULT_ORIGIN, init);
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
export interface CreateTestContextOptions<TEnv> {
|
|
@@ -146,10 +143,6 @@ export function createTestRequestContext<TEnv>(
|
|
|
146
143
|
const request = toRequest(opts.request, opts.requestInit);
|
|
147
144
|
const url = new URL(request.url);
|
|
148
145
|
const variables = seedVariables(opts.variables ?? {}, opts.vars);
|
|
149
|
-
// Always seed a resolved name so invalidateClientCache() rotates (and emits
|
|
150
|
-
// the Set-Cookie) like production instead of no-opping; opts.stateCookie
|
|
151
|
-
// customizes the name/version. Surfaced on the result so a consumer asserts
|
|
152
|
-
// the rotation against the same name without recomputing it.
|
|
153
146
|
const stateCookieName = resolveSeededStateCookieName(opts.stateCookie);
|
|
154
147
|
const ctx = createRequestContext<TEnv>({
|
|
155
148
|
env: (opts.env ?? {}) as TEnv,
|
|
@@ -174,10 +167,6 @@ export function createTestRequestContext<TEnv>(
|
|
|
174
167
|
opts.params ?? {},
|
|
175
168
|
) as RequestContext<TEnv>["reverse"];
|
|
176
169
|
}
|
|
177
|
-
// ctx.reverse is assigned the routeMap-scoped reverse (string-accepting) above;
|
|
178
|
-
// expose it through the relaxed type so a test reverses a local route name
|
|
179
|
-
// without casting. The runtime value matches; RequestContext's reverse is
|
|
180
|
-
// declared against the narrower global route-name union, hence the cast.
|
|
181
170
|
return {
|
|
182
171
|
ctx: ctx as unknown as TestRequestContextObject<TEnv>,
|
|
183
172
|
request,
|
|
@@ -246,13 +235,6 @@ export interface RunInRequestContextResult<T> {
|
|
|
246
235
|
stateCookieName: string;
|
|
247
236
|
}
|
|
248
237
|
|
|
249
|
-
/**
|
|
250
|
-
* Snapshot the observable effects a run left on `ctx` (cookies + location
|
|
251
|
-
* state). Reads the fields directly off the ctx object, so it works both inside
|
|
252
|
-
* and outside the AsyncLocalStorage scope (no `getRequestContext()`). Headers are
|
|
253
|
-
* snapshotted separately from the final {@link Response} (via
|
|
254
|
-
* {@link headersToObject}) so a thrown redirect's `Location` is included.
|
|
255
|
-
*/
|
|
256
238
|
export function snapshotRunEffects<TEnv>(ctx: RequestContext<TEnv>): {
|
|
257
239
|
cookies: Record<string, string>;
|
|
258
240
|
locationState: Record<string, unknown>;
|
|
@@ -263,12 +245,6 @@ export function snapshotRunEffects<TEnv>(ctx: RequestContext<TEnv>): {
|
|
|
263
245
|
};
|
|
264
246
|
}
|
|
265
247
|
|
|
266
|
-
/**
|
|
267
|
-
* The response headers as a plain `{ name: value }` object, EXCLUDING
|
|
268
|
-
* `set-cookie` (surfaced parsed on `cookies`). Names are lowercased (HTTP header
|
|
269
|
-
* names are case-insensitive). Read from the final response so a thrown
|
|
270
|
-
* redirect's `Location` and any `ctx.header(...)` both appear.
|
|
271
|
-
*/
|
|
272
248
|
export function headersToObject(headers: Headers): Record<string, string> {
|
|
273
249
|
const out: Record<string, string> = {};
|
|
274
250
|
headers.forEach((value, name) => {
|
|
@@ -278,14 +254,6 @@ export function headersToObject(headers: Headers): Record<string, string> {
|
|
|
278
254
|
return out;
|
|
279
255
|
}
|
|
280
256
|
|
|
281
|
-
/**
|
|
282
|
-
* Build the observable response from what the run accumulated on `ctx.res`. When
|
|
283
|
-
* `fn` threw a `Response` (a `redirect()`/`notFound()`), that Response IS the
|
|
284
|
-
* response — merge the accumulated Set-Cookie/other headers into it (the
|
|
285
|
-
* framework does this when it catches the thrown Response in production), with
|
|
286
|
-
* its status/Location preserved. Otherwise snapshot the stub (status + headers).
|
|
287
|
-
* The `Response`/`Headers` constructors copy, so the result is immutable.
|
|
288
|
-
*/
|
|
289
257
|
export function buildRunResponse<TEnv>(
|
|
290
258
|
ctx: RequestContext<TEnv>,
|
|
291
259
|
thrown: unknown,
|
|
@@ -305,14 +273,6 @@ export function buildRunResponse<TEnv>(
|
|
|
305
273
|
return new Response(null, { status: stub.status, headers: stub.headers });
|
|
306
274
|
}
|
|
307
275
|
|
|
308
|
-
/**
|
|
309
|
-
* Snapshot the shared run-result envelope fields (everything except the primary
|
|
310
|
-
* value) from what a run left on `ctx`: the captured `thrown`, the merged
|
|
311
|
-
* `response`, the effective `cookies`/`headers` views, the `locationState`, and
|
|
312
|
-
* the resolved `stateCookieName`. Shared by `runInRequestContext` and
|
|
313
|
-
* `runLoaderResult` so the snapshot sequence lives once; each spreads it next to
|
|
314
|
-
* its own primary field (`result` / `data`).
|
|
315
|
-
*/
|
|
316
276
|
export function buildRunSnapshot<TEnv>(
|
|
317
277
|
ctx: RequestContext<TEnv>,
|
|
318
278
|
thrown: unknown,
|
|
@@ -382,8 +342,6 @@ export async function runInRequestContext<T, TEnv = unknown>(
|
|
|
382
342
|
try {
|
|
383
343
|
result = (await runWithRequestContext(ctx, () => fn(ctx))) as T;
|
|
384
344
|
} catch (error) {
|
|
385
|
-
// Capture (do NOT re-throw): a redirect/notFound action throws its Response
|
|
386
|
-
// on the SUCCESS path, and its cookie/flash output must stay observable.
|
|
387
345
|
thrown = error;
|
|
388
346
|
}
|
|
389
347
|
return { result, ...buildRunSnapshot(ctx, thrown, stateCookieName) };
|
|
@@ -9,14 +9,6 @@
|
|
|
9
9
|
import { contextSet, type ContextVar } from "../../context-var.js";
|
|
10
10
|
import { resolveStateCookieName } from "../../router/state-cookie-name.js";
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Seed for the rango state cookie a handler/action/loader rotates when it calls
|
|
14
|
-
* `invalidateClientCache()`. Production always resolves a name at router init
|
|
15
|
-
* (so rotation always fires); the test stub did not, so the call silently
|
|
16
|
-
* no-opped. Supplying this (or accepting the defaults) closes that gap. Lives in
|
|
17
|
-
* the react-server-safe seed module so both the node tier (createTestRequestContext)
|
|
18
|
-
* and the Flight tier (renderHandler) share one shape and one default.
|
|
19
|
-
*/
|
|
20
12
|
export interface StateCookieSeed {
|
|
21
13
|
/**
|
|
22
14
|
* Cookie-name prefix, sanitized then composed with `routerId` exactly like
|
|
@@ -36,37 +28,19 @@ export interface StateCookieSeed {
|
|
|
36
28
|
version?: string;
|
|
37
29
|
}
|
|
38
30
|
|
|
39
|
-
/**
|
|
40
|
-
* Resolve the state cookie name a seed maps to, mirroring `createRouter`'s
|
|
41
|
-
* `resolveStateCookieName` so a test asserts the SAME name production writes.
|
|
42
|
-
* The default routerId `"router_0"` matches a single default router.
|
|
43
|
-
*/
|
|
44
31
|
export function resolveSeededStateCookieName(seed?: StateCookieSeed): string {
|
|
45
32
|
return resolveStateCookieName(seed?.prefix, seed?.routerId ?? "router_0");
|
|
46
33
|
}
|
|
47
34
|
|
|
48
|
-
/**
|
|
49
|
-
* Initializer for seeded context variables (as a prior middleware would have
|
|
50
|
-
* set, or a server component would read during render). Either a plain object
|
|
51
|
-
* keyed by var name (the common, best-inferring form: `{ user: u }`) or a list
|
|
52
|
-
* of `[key, value]` tuples where the key may be a `createVar()` handle or a
|
|
53
|
-
* string (`[[userVar, u], ["flag", true]]`).
|
|
54
|
-
*/
|
|
55
35
|
export type VarsInit =
|
|
56
36
|
| Record<string, unknown>
|
|
57
37
|
| ReadonlyArray<readonly [ContextVar<unknown> | string, unknown]>;
|
|
58
38
|
|
|
59
|
-
/**
|
|
60
|
-
* Preload variables as if set by upstream middleware (or visible to a rendered
|
|
61
|
-
* server tree). Accepts entries keyed by either a ContextVar (from createVar) or
|
|
62
|
-
* a string, matching ctx.set().
|
|
63
|
-
*/
|
|
64
39
|
export function seedVariables(
|
|
65
40
|
variables: Record<string, unknown>,
|
|
66
41
|
vars?: VarsInit,
|
|
67
42
|
): Record<string, unknown> {
|
|
68
43
|
if (!vars) return variables;
|
|
69
|
-
// Array/iterable form -> use the tuples as-is; plain object -> its entries.
|
|
70
44
|
const entries: Iterable<readonly [ContextVar<unknown> | string, unknown]> =
|
|
71
45
|
Symbol.iterator in (vars as object)
|
|
72
46
|
? (vars as ReadonlyArray<
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { createHandlerContext } from "../router/handler-context.js";
|
|
32
32
|
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
33
33
|
import { isHandle, type Handle } from "../handle.js";
|
|
34
|
+
import { withDefer } from "../defer.js";
|
|
34
35
|
import type { HandlerContext } from "../types/handler-context.js";
|
|
35
36
|
import type { LoaderDefinition } from "../types.js";
|
|
36
37
|
import {
|
|
@@ -41,7 +42,13 @@ import {
|
|
|
41
42
|
} from "./internal/seed-vars.js";
|
|
42
43
|
|
|
43
44
|
export type { StateCookieSeed } from "./internal/seed-vars.js";
|
|
44
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
assertNoLegacyUrlOption,
|
|
47
|
+
serializeNodeToFlight,
|
|
48
|
+
isServerOnlyStubError,
|
|
49
|
+
} from "./flight.js";
|
|
50
|
+
import type { SegmentCacheStore } from "../cache/types.js";
|
|
51
|
+
import type { CacheProfile } from "../cache/profile-registry.js";
|
|
45
52
|
import {
|
|
46
53
|
deserializeFlight,
|
|
47
54
|
makeClientManifest,
|
|
@@ -94,6 +101,19 @@ export interface RenderHandlerOptions<TEnv = any> {
|
|
|
94
101
|
* `result.stateCookieName`.
|
|
95
102
|
*/
|
|
96
103
|
stateCookie?: StateCookieSeed;
|
|
104
|
+
/**
|
|
105
|
+
* Segment cache store backing a `"use cache"` function the handler invokes
|
|
106
|
+
* (e.g. `new MemorySegmentCacheStore()`). Without it, `registerCachedFunction`
|
|
107
|
+
* takes the uncached bypass and the cached path is NOT exercised (the runtime
|
|
108
|
+
* emits a one-time warning under the test runner). Pair with `cacheProfiles`
|
|
109
|
+
* so the profile the directive names resolves.
|
|
110
|
+
*/
|
|
111
|
+
cacheStore?: SegmentCacheStore;
|
|
112
|
+
/**
|
|
113
|
+
* Cache profiles in the `createRouter({ cacheProfiles })` shape, required for
|
|
114
|
+
* `"use cache: profileName"` resolution once a `cacheStore` is wired.
|
|
115
|
+
*/
|
|
116
|
+
cacheProfiles?: Record<string, CacheProfile>;
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
/** Result of {@link renderHandler}. */
|
|
@@ -134,11 +154,6 @@ export interface RenderHandlerResult {
|
|
|
134
154
|
*/
|
|
135
155
|
class RenderHandlerSetupError extends Error {}
|
|
136
156
|
|
|
137
|
-
// Local copy (not imported from internal/context.ts): that module is the NODE
|
|
138
|
-
// tier and is deliberately NOT react-server-safe (the reason seed-vars.ts was
|
|
139
|
-
// split out), and render-handler ships from the react-server ./testing/flight
|
|
140
|
-
// entry. A 6-line pure projection is cheaper to duplicate than to route a
|
|
141
|
-
// shared util across that boundary.
|
|
142
157
|
function headersToObject(headers: Headers): Record<string, string> {
|
|
143
158
|
const out: Record<string, string> = {};
|
|
144
159
|
headers.forEach((value, name) => {
|
|
@@ -147,23 +162,6 @@ function headersToObject(headers: Headers): Record<string, string> {
|
|
|
147
162
|
return out;
|
|
148
163
|
}
|
|
149
164
|
|
|
150
|
-
/**
|
|
151
|
-
* Detect the server-only-API stub throw: when a handler/component imports
|
|
152
|
-
* getRequestContext()/cookies()/etc. from the BARE `@rangojs/router` specifier
|
|
153
|
-
* (the out-of-react-server stub in index.ts) instead of the react-server build.
|
|
154
|
-
* In an rsc test this happens when the vitest.rsc.config.ts `resolve.alias` does
|
|
155
|
-
* not map the bare specifier to `index.rsc.ts` (the `rangoTestAliases` preset).
|
|
156
|
-
* The dual-substring match keeps a legitimate handler throw from being
|
|
157
|
-
* reclassified as a setup error.
|
|
158
|
-
*/
|
|
159
|
-
function isServerOnlyStubError(error: unknown): boolean {
|
|
160
|
-
return (
|
|
161
|
-
error instanceof Error &&
|
|
162
|
-
error.message.includes("is only available from") &&
|
|
163
|
-
error.message.includes("react-server")
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
165
|
function toRequest(
|
|
168
166
|
request: Request | string | undefined,
|
|
169
167
|
headers?: HeadersInit,
|
|
@@ -175,20 +173,6 @@ function toRequest(
|
|
|
175
173
|
return new Request(DEFAULT_URL, { headers });
|
|
176
174
|
}
|
|
177
175
|
|
|
178
|
-
/**
|
|
179
|
-
* Build the result `response` from the request-context stub and, when present,
|
|
180
|
-
* the Response the handler returned or threw (`source`). The stub cookies and
|
|
181
|
-
* headers are merged in (Set-Cookie appended to preserve duplicates, other stub
|
|
182
|
-
* headers filled in without clobbering the source), mirroring dispatch.ts's
|
|
183
|
-
* rewrap.
|
|
184
|
-
*
|
|
185
|
-
* The source's BODY is carried over (not dropped): a response route returns a
|
|
186
|
-
* `new Response(JSON.stringify(...))`, so callers reach for
|
|
187
|
-
* `await result.response.text()`/`.json()`. Pre-fix this rewrapped to
|
|
188
|
-
* `new Response(null, ...)` and the body was lost irrecoverably. A body is a
|
|
189
|
-
* single-use stream; `source` is not read again here or by renderHandler, so
|
|
190
|
-
* handing its body to the new Response is safe.
|
|
191
|
-
*/
|
|
192
176
|
function buildResponse(reqCtx: RequestContext<any>, source: unknown): Response {
|
|
193
177
|
const stub = reqCtx.res;
|
|
194
178
|
if (source instanceof Response) {
|
|
@@ -231,9 +215,6 @@ export async function renderHandler<TEnv = any>(
|
|
|
231
215
|
if (opts.clientComponents) registerClientComponents(opts.clientComponents);
|
|
232
216
|
const request = toRequest(opts.request, opts.headers);
|
|
233
217
|
const url = new URL(request.url);
|
|
234
|
-
// Seed a resolved name so a handler calling invalidateClientCache() rotates
|
|
235
|
-
// (emits the Set-Cookie) like production; opts.stateCookie customizes it. Also
|
|
236
|
-
// surfaced on the result so a consumer asserts the rotation without recomputing.
|
|
237
218
|
const stateCookieName = resolveSeededStateCookieName(opts.stateCookie);
|
|
238
219
|
const reqCtx = createRequestContext<TEnv>({
|
|
239
220
|
env: (opts.env ?? {}) as TEnv,
|
|
@@ -242,6 +223,8 @@ export async function renderHandler<TEnv = any>(
|
|
|
242
223
|
variables: seedVariables({}, opts.vars),
|
|
243
224
|
stateCookieName,
|
|
244
225
|
version: opts.stateCookie?.version,
|
|
226
|
+
cacheStore: opts.cacheStore,
|
|
227
|
+
cacheProfiles: opts.cacheProfiles,
|
|
245
228
|
});
|
|
246
229
|
|
|
247
230
|
const loaderSeeds = new Map<unknown, unknown>(opts.loaders ?? []);
|
|
@@ -253,7 +236,11 @@ export async function renderHandler<TEnv = any>(
|
|
|
253
236
|
let didThrow = false;
|
|
254
237
|
|
|
255
238
|
await runWithRequestContext(reqCtx as RequestContext<TEnv>, async () => {
|
|
256
|
-
|
|
239
|
+
// Scope the request-context reverse to opts.routeMap too (not just the
|
|
240
|
+
// handler context built below), so a nested server component reading
|
|
241
|
+
// getRequestContext().reverse() resolves against the same map as the
|
|
242
|
+
// handler's ctx.reverse -- matching renderToFlightString/renderServerTree.
|
|
243
|
+
setRequestContextParams(opts.params ?? {}, opts.routeName, opts.routeMap);
|
|
257
244
|
const hctx = createHandlerContext<TEnv>(
|
|
258
245
|
opts.params ?? {},
|
|
259
246
|
reqCtx.request,
|
|
@@ -264,17 +251,11 @@ export async function renderHandler<TEnv = any>(
|
|
|
264
251
|
opts.routeMap ?? {},
|
|
265
252
|
opts.routeName,
|
|
266
253
|
);
|
|
267
|
-
// Seed ctx.use: a handle returns a push fn that RECORDS (so ctx.use(Meta)
|
|
268
|
-
// doesn't crash and pushes are assertable); a loader returns its seeded data
|
|
269
|
-
// (no real loader run).
|
|
270
254
|
(hctx as { use: unknown }).use = (item: unknown) => {
|
|
271
255
|
if (isHandle(item)) {
|
|
272
256
|
const handle = item as Handle<any, any>;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// (ctx.use(Meta)(() => fetchMeta())) is CALLED and its result is
|
|
276
|
-
// recorded, not the function itself. An async callback records the
|
|
277
|
-
// promise it returns, same as production (which does not await it).
|
|
257
|
+
// withDefer attaches .defer() so the harness mirrors production's push.
|
|
258
|
+
return withDefer((dataOrFn: unknown) => {
|
|
278
259
|
const value =
|
|
279
260
|
typeof dataOrFn === "function"
|
|
280
261
|
? (dataOrFn as () => unknown)()
|
|
@@ -282,7 +263,7 @@ export async function renderHandler<TEnv = any>(
|
|
|
282
263
|
const pushed = handlePushes.get(handle) ?? [];
|
|
283
264
|
pushed.push(value);
|
|
284
265
|
handlePushes.set(handle, pushed);
|
|
285
|
-
};
|
|
266
|
+
});
|
|
286
267
|
}
|
|
287
268
|
if (loaderSeeds.has(item)) return loaderSeeds.get(item);
|
|
288
269
|
throw new RenderHandlerSetupError(
|
|
@@ -294,8 +275,6 @@ export async function renderHandler<TEnv = any>(
|
|
|
294
275
|
|
|
295
276
|
try {
|
|
296
277
|
out = await handler(hctx as HandlerContext<any, TEnv>);
|
|
297
|
-
// Serialize the RSC in THIS context, so nested async server components see
|
|
298
|
-
// getRequestContext()/cookies()/vars while they render.
|
|
299
278
|
if (out !== undefined && !(out instanceof Response)) {
|
|
300
279
|
flight = await serializeNodeToFlight(
|
|
301
280
|
out as ReactNode,
|
|
@@ -304,13 +283,7 @@ export async function renderHandler<TEnv = any>(
|
|
|
304
283
|
);
|
|
305
284
|
}
|
|
306
285
|
} catch (error) {
|
|
307
|
-
// A harness misconfiguration (unseeded loader) is the consumer's mistake —
|
|
308
|
-
// surface it as a rejection, not as a captured handler throw.
|
|
309
286
|
if (error instanceof RenderHandlerSetupError) throw error;
|
|
310
|
-
// Same for the server-only-API stub throw: the handler read
|
|
311
|
-
// getRequestContext()/cookies() but the bare `@rangojs/router` resolved to
|
|
312
|
-
// the throwing stub. Rethrow LOUDLY with the fix, instead of silently
|
|
313
|
-
// capturing it (which surfaces as an opaque tree:undefined + bare throw).
|
|
314
287
|
if (isServerOnlyStubError(error)) {
|
|
315
288
|
throw new RenderHandlerSetupError(
|
|
316
289
|
`renderHandler: the handler called a server-only API (getRequestContext/cookies/...) ` +
|
|
@@ -320,8 +293,6 @@ export async function renderHandler<TEnv = any>(
|
|
|
320
293
|
`Original: ${(error as Error).message}`,
|
|
321
294
|
);
|
|
322
295
|
}
|
|
323
|
-
// Otherwise captured, NOT re-thrown: a handler's success path is often
|
|
324
|
-
// `throw redirect(...)`; its cookies/flash must stay observable.
|
|
325
296
|
didThrow = true;
|
|
326
297
|
thrown = error;
|
|
327
298
|
}
|
|
@@ -342,7 +313,6 @@ export async function renderHandler<TEnv = any>(
|
|
|
342
313
|
}
|
|
343
314
|
)._locationState ?? [],
|
|
344
315
|
);
|
|
345
|
-
// Deserialize outside the context (the client deserializer needs no ctx).
|
|
346
316
|
const tree =
|
|
347
317
|
flight !== undefined ? await deserializeFlight(flight) : undefined;
|
|
348
318
|
|