@rangojs/router 0.18.0 → 0.19.1
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/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +2 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +37 -0
- package/dist/types/cache/cache-key-utils.d.ts +44 -13
- package/dist/types/cache/cache-policy.d.ts +1 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +117 -35
- package/dist/types/cache/cache-tag.d.ts +48 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
- package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +40 -8
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-snapshot.d.ts +88 -141
- package/dist/types/cache/tag-invalidation.d.ts +38 -14
- package/dist/types/cache/types.d.ts +68 -61
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
- package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +14 -7
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
- package/dist/types/rsc/shell-capture.d.ts +78 -81
- package/dist/types/rsc/shell-serve.d.ts +19 -48
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +126 -61
- package/dist/types/server/cookie-store.d.ts +30 -18
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +66 -45
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +7 -6
- package/dist/types/testing/shell-status.d.ts +34 -22
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +3 -4
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/vite/index.js +962 -97
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +76 -49
- package/skills/caching/SKILL.md +167 -69
- package/skills/client-urls/SKILL.md +1 -1
- package/skills/comparison/references/framework-comparison.md +8 -5
- package/skills/document-cache/SKILL.md +24 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +98 -6
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +52 -21
- package/skills/middleware/SKILL.md +18 -0
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/observability/SKILL.md +6 -5
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +129 -77
- package/skills/prerender/SKILL.md +3 -3
- package/skills/rango/SKILL.md +25 -6
- package/skills/route/SKILL.md +2 -0
- package/skills/testing/SKILL.md +27 -25
- package/skills/testing/cache-prerender.md +9 -9
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +3 -2
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +14 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +8 -4
- package/skills/typesafety/env-and-bindings.md +8 -0
- package/skills/use-cache/SKILL.md +67 -19
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +52 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +4 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +54 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +202 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +209 -13
- package/src/cache/cf/cf-cache-store.ts +152 -76
- package/src/cache/cf/cf-shell-frame.ts +2 -2
- package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +52 -9
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-snapshot.ts +130 -348
- package/src/cache/tag-invalidation.ts +57 -51
- package/src/cache/types.ts +71 -67
- package/src/cache/vercel/vercel-cache-store.ts +61 -58
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +155 -146
- package/src/route-definition/dsl-helpers.ts +2 -0
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +10 -7
- package/src/router/intercept-resolution.ts +11 -1
- package/src/router/loader-resolution.ts +42 -5
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +62 -34
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +9 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +35 -19
- package/src/router/match-middleware/cache-store.ts +15 -9
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +0 -4
- package/src/router/segment-resolution/loader-cache.ts +152 -26
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +15 -78
- package/src/router/segment-resolution/mask-nested.ts +47 -22
- package/src/router/segment-resolution/revalidation.ts +0 -3
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +30 -10
- package/src/rsc/handler.ts +12 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +7 -30
- package/src/rsc/response-cache-serve.ts +68 -80
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +206 -252
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +22 -20
- package/src/rsc/shell-capture-constants.ts +136 -20
- package/src/rsc/shell-capture.ts +352 -589
- package/src/rsc/shell-serve.ts +73 -109
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +2 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +263 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +134 -87
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +10 -7
- package/src/testing/shell-status.ts +60 -56
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +3 -4
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +0 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +75 -5
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -261,24 +261,27 @@ function buildRequest(url: URL, options: ServeShellRequestOptions): Request {
|
|
|
261
261
|
/**
|
|
262
262
|
* Reset the per-isolate PPR state requests leave behind, so the next test
|
|
263
263
|
* starts as a fresh worker would: the capture's stampede guard, its backoff
|
|
264
|
-
* (a refused capture backs its URL off for later tests too), the
|
|
265
|
-
*
|
|
266
|
-
* and CFCacheStore's isolate memos (shells,
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
264
|
+
* (a refused capture backs its URL off for later tests too), the shell path's
|
|
265
|
+
* once-per-key warnings, the build-shell manifest memo, the "use cache"
|
|
266
|
+
* in-flight leaders and warnings, and CFCacheStore's isolate memos (shells,
|
|
267
|
+
* tag markers, tag hints) and warnings, which every CFCacheStore in the
|
|
268
|
+
* process shares by namespace and URL. Call it in `beforeEach`, never while
|
|
269
|
+
* a request is in flight. VercelCacheStore's memos live on the `cache`
|
|
270
|
+
* handle it is given: a new handle starts empty.
|
|
270
271
|
*/
|
|
271
272
|
export async function resetShellTestState(): Promise<void> {
|
|
272
|
-
const [capture, serve, buildShells, cf] = await Promise.all([
|
|
273
|
+
const [capture, serve, buildShells, cf, cacheRuntime] = await Promise.all([
|
|
273
274
|
import("../rsc/shell-capture.js"),
|
|
274
275
|
import("../rsc/shell-serve.js"),
|
|
275
276
|
import("../rsc/shell-build-manifest.js"),
|
|
276
277
|
import("../cache/cf/cf-cache-store.js"),
|
|
278
|
+
import("../cache/cache-runtime.js"),
|
|
277
279
|
]);
|
|
278
280
|
capture.resetShellCaptureStateForTests();
|
|
279
281
|
serve.resetShellServeStateForTests();
|
|
280
282
|
buildShells.resetBuildShellManifestForTests();
|
|
281
283
|
cf.resetCFShellMemoForTests();
|
|
284
|
+
cacheRuntime.resetCacheRuntimeForTests();
|
|
282
285
|
}
|
|
283
286
|
|
|
284
287
|
/** Settle background tasks, including ones scheduled while settling. */
|
|
@@ -32,48 +32,28 @@
|
|
|
32
32
|
* (Playwright — same pure helpers, no Vite virtuals).
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
+
import { composeCacheKeys } from "../cache/cache-key-utils.js";
|
|
35
36
|
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
PPR_REPLAY_BYPASS_REASONS,
|
|
38
|
+
PPR_REPLAY_STATUS_HEADER,
|
|
39
|
+
SHELL_STATUS_HEADER,
|
|
40
|
+
buildShellKey,
|
|
41
|
+
partitionShellKey,
|
|
42
|
+
type PprReplayBypassReason,
|
|
43
|
+
} from "../rsc/shell-capture-constants.js";
|
|
40
44
|
import {
|
|
41
45
|
compileSearchParamsFilter,
|
|
42
46
|
type CacheSearchParams,
|
|
43
47
|
} from "../cache/search-params-filter.js";
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
export const PPR_REPLAY_STATUS_HEADER: string = "x-rango-ppr-replay";
|
|
49
|
+
// The production header names and bypass reasons, from the React-free leaf
|
|
50
|
+
// the serve path reads them from.
|
|
51
|
+
export { PPR_REPLAY_STATUS_HEADER, SHELL_STATUS_HEADER };
|
|
52
|
+
export type { PprReplayBypassReason };
|
|
50
53
|
|
|
51
54
|
/** Values the serve path writes on `x-rango-shell`. */
|
|
52
55
|
export type ShellStatus = "HIT" | "MISS";
|
|
53
56
|
|
|
54
|
-
const PPR_REPLAY_BYPASS_REASONS = [
|
|
55
|
-
"method",
|
|
56
|
-
"dynamic",
|
|
57
|
-
"nonce",
|
|
58
|
-
"store-unavailable",
|
|
59
|
-
"passive-read-unsupported",
|
|
60
|
-
"no-navigation-context",
|
|
61
|
-
"prerender-store",
|
|
62
|
-
"intercept",
|
|
63
|
-
"cache-disabled",
|
|
64
|
-
"read-error",
|
|
65
|
-
"no-entry",
|
|
66
|
-
"invalid-version",
|
|
67
|
-
"corrupt-entry",
|
|
68
|
-
"no-segment-snapshot",
|
|
69
|
-
"snapshot-miss",
|
|
70
|
-
"explicit-cache-hit",
|
|
71
|
-
"stale-build-entry",
|
|
72
|
-
] as const;
|
|
73
|
-
|
|
74
|
-
/** Bounded reasons a PPR partial request can fall open to ordinary matching. */
|
|
75
|
-
export type PprReplayBypassReason = (typeof PPR_REPLAY_BYPASS_REASONS)[number];
|
|
76
|
-
|
|
77
57
|
/** Parsed `x-rango-ppr-replay` value. */
|
|
78
58
|
export type PprReplayStatus =
|
|
79
59
|
| { outcome: "HIT"; freshness: "fresh" | "stale" }
|
|
@@ -83,44 +63,68 @@ export type PprReplayStatus =
|
|
|
83
63
|
export type ShellStatusTarget = Response | { headers: Headers };
|
|
84
64
|
|
|
85
65
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
66
|
+
* What partitions a ppr route's shell, for {@link shellCacheKey}: the
|
|
67
|
+
* request's results of the functions that partition the route's cache()
|
|
68
|
+
* record.
|
|
69
|
+
*/
|
|
70
|
+
export interface ShellCachePartition {
|
|
71
|
+
/** The route's cache() `key()` results, outermost first. */
|
|
72
|
+
keys?: readonly string[];
|
|
73
|
+
/**
|
|
74
|
+
* Store `keyGenerator` results, one per store, outermost first: an
|
|
75
|
+
* enclosing cache() on another store's, then the store's of the route's
|
|
76
|
+
* own cache() when it sets no `key()` (with no route cache(), the app
|
|
77
|
+
* store's). When every one returns the default key they partition
|
|
78
|
+
* nothing: leave them out. Otherwise each keeps its position, and one
|
|
79
|
+
* that returns the default key is `""`.
|
|
80
|
+
*/
|
|
81
|
+
generated?: readonly string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Shell store key for a document URL: the production `buildShellKey` (host +
|
|
86
|
+
* pathname + sorted search + `:shell`) and `partitionShellKey`, both from the
|
|
87
|
+
* React-free leaf `rsc/shell-capture-constants.ts`, so the testing barrel never
|
|
88
|
+
* imports the shell-serve module (it pulls React).
|
|
92
89
|
*
|
|
93
90
|
* Accepts a `URL` or an absolute/relative request URL string (relative strings
|
|
94
91
|
* resolve against `http://localhost`). Pass the router's `cache.searchParams`
|
|
95
92
|
* config as `searchParams` when the app under test sets one — the production
|
|
96
93
|
* key applies it, so the expected key must too. When the route's record is
|
|
97
|
-
* partitioned by the request
|
|
98
|
-
* `
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* `cache()` scopes
|
|
102
|
-
*
|
|
103
|
-
*
|
|
94
|
+
* partitioned by the request, its shell is too: pass what partitions it as
|
|
95
|
+
* `partition`, composed as production composes it (`composeCacheKeys`,
|
|
96
|
+
* which namespaces each `key()` result):
|
|
97
|
+
* - its `cache({ key })` result, as a string;
|
|
98
|
+
* - nested keyed `cache()` scopes' `key()` results, as an array, outermost
|
|
99
|
+
* first;
|
|
100
|
+
* - store `keyGenerator` results, with or without `key()` results, as
|
|
101
|
+
* `{ keys, generated }` (see {@link ShellCachePartition}).
|
|
104
102
|
*/
|
|
105
103
|
export function shellCacheKey(
|
|
106
104
|
url: URL | string,
|
|
107
105
|
searchParams?: CacheSearchParams,
|
|
108
|
-
partition?: string | readonly string[],
|
|
106
|
+
partition?: string | readonly string[] | ShellCachePartition,
|
|
109
107
|
): string {
|
|
110
108
|
const resolved =
|
|
111
109
|
typeof url === "string" ? new URL(url, "http://localhost") : url;
|
|
112
|
-
const
|
|
113
|
-
resolved.searchParams,
|
|
114
|
-
compileSearchParamsFilter(searchParams),
|
|
115
|
-
);
|
|
116
|
-
const searchSuffix = sorted ? `?${sorted}` : "";
|
|
117
|
-
const key = `${resolved.host}${resolved.pathname}${searchSuffix}:shell`;
|
|
110
|
+
const key = buildShellKey(resolved, compileSearchParamsFilter(searchParams));
|
|
118
111
|
if (partition === undefined) return key;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
const { keys = [], generated = [] }: ShellCachePartition =
|
|
113
|
+
typeof partition === "string"
|
|
114
|
+
? { keys: [partition] }
|
|
115
|
+
: isPartitionList(partition)
|
|
116
|
+
? { keys: partition }
|
|
117
|
+
: partition;
|
|
118
|
+
// Nothing partitions: the key as when `partition` is omitted.
|
|
119
|
+
return keys.length === 0 && generated.length === 0
|
|
122
120
|
? key
|
|
123
|
-
: partitionShellKey(key, composeCacheKeys(
|
|
121
|
+
: partitionShellKey(key, composeCacheKeys(keys, generated));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isPartitionList(
|
|
125
|
+
partition: readonly string[] | ShellCachePartition,
|
|
126
|
+
): partition is readonly string[] {
|
|
127
|
+
return Array.isArray(partition);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
function getHeaders(target: ShellStatusTarget): Headers {
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transition({ when }) references: what a route definition may carry as a
|
|
3
|
+
* `when`, how definitions are validated, and how the browser turns the carried
|
|
4
|
+
* value back into a callable predicate.
|
|
5
|
+
*
|
|
6
|
+
* `when` is a BROWSER predicate. The server never calls it; it only carries it
|
|
7
|
+
* to the browser inside the segment's transition config, as one of:
|
|
8
|
+
*
|
|
9
|
+
* - a React client reference: a function exported from a `"use client"`
|
|
10
|
+
* module and imported into a server `urls()` file, or an inline literal the
|
|
11
|
+
* build hoisted into one (vite/plugins/transition-when-hoist.ts). Flight
|
|
12
|
+
* serializes it as a module reference and the browser resolves it to the
|
|
13
|
+
* real function;
|
|
14
|
+
* - a clientUrls() when reference, synthesized by the projection materializer
|
|
15
|
+
* (client-urls/server-projection.ts): the group's definition client
|
|
16
|
+
* reference plus the route id. The browser resolves it to the `when` the
|
|
17
|
+
* route declared in its `"use client"` module.
|
|
18
|
+
*
|
|
19
|
+
* A plain function (a server function) cannot cross Flight, and a
|
|
20
|
+
* non-function is never callable. Both are LOUD errors: route discovery
|
|
21
|
+
* (generateManifestFull `validateTransitionWhen`) fails dev startup, the
|
|
22
|
+
* build and HMR re-discovery, and attachTransitionWhen (rsc/) throws the same
|
|
23
|
+
* error at render time for a route discovery did not see.
|
|
24
|
+
*
|
|
25
|
+
* Telling a client reference apart from a server function needs the
|
|
26
|
+
* react-server module graph, where a `"use client"` import IS a client
|
|
27
|
+
* reference. Outside it (a node/DOM unit-test project that aliases the router
|
|
28
|
+
* entry) the import is the plain function, so strict validation is enabled
|
|
29
|
+
* only by the Vite plugin: discovery passes the option, and the generated
|
|
30
|
+
* routes-manifest module calls enableTransitionWhenValidation() in the app's
|
|
31
|
+
* RSC realm (vite/discovery/virtual-module-codegen.ts).
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { isAutoGeneratedRouteName } from "./route-name.js";
|
|
35
|
+
import type { TransitionWhenFn } from "./types/segments.js";
|
|
36
|
+
|
|
37
|
+
const CLIENT_REFERENCE = Symbol.for("react.client.reference");
|
|
38
|
+
|
|
39
|
+
/** String key, so the marker survives Flight (symbol keys are not serialized). */
|
|
40
|
+
const CLIENT_URLS_WHEN_KEY = "__rangoClientUrlsWhen";
|
|
41
|
+
|
|
42
|
+
interface ClientUrlsWhenRef {
|
|
43
|
+
readonly __rangoClientUrlsWhen: 1;
|
|
44
|
+
/** The clientUrls() definition: a client reference on the server, the definition object in the browser. */
|
|
45
|
+
readonly definition: unknown;
|
|
46
|
+
readonly routeId: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isClientReference(value: unknown): boolean {
|
|
50
|
+
if (typeof value !== "function" && (typeof value !== "object" || !value)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return (value as { $$typeof?: unknown }).$$typeof === CLIENT_REFERENCE;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isClientUrlsWhenRef(value: unknown): value is ClientUrlsWhenRef {
|
|
57
|
+
return (
|
|
58
|
+
typeof value === "object" &&
|
|
59
|
+
value !== null &&
|
|
60
|
+
(value as Record<string, unknown>)[CLIENT_URLS_WHEN_KEY] === 1
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createClientUrlsWhenRef(
|
|
65
|
+
definition: unknown,
|
|
66
|
+
routeId: string,
|
|
67
|
+
): TransitionWhenFn {
|
|
68
|
+
const ref: ClientUrlsWhenRef = {
|
|
69
|
+
__rangoClientUrlsWhen: 1,
|
|
70
|
+
definition,
|
|
71
|
+
routeId,
|
|
72
|
+
};
|
|
73
|
+
return ref as unknown as TransitionWhenFn;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let strictValidation = false;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Turn on server-function rejection for this realm. Called by the Vite
|
|
80
|
+
* plugin's generated routes-manifest module, never by tests: see the module
|
|
81
|
+
* comment for why it cannot be on by default.
|
|
82
|
+
*/
|
|
83
|
+
export function enableTransitionWhenValidation(): void {
|
|
84
|
+
strictValidation = true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isTransitionWhenValidationEnabled(): boolean {
|
|
88
|
+
return strictValidation;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @internal Reset for unit tests. */
|
|
92
|
+
export function resetTransitionWhenValidation(): void {
|
|
93
|
+
strictValidation = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Thrown for an invalid transition({ when }); recognized by the dev HMR overlay. */
|
|
97
|
+
export class TransitionWhenError extends Error {
|
|
98
|
+
override readonly name: string = "TransitionWhenError";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The TransitionWhenError in an error's cause chain, matched by name: discovery
|
|
103
|
+
* throws it from the module runner's copy of this module (another class
|
|
104
|
+
* object), and include resolution wraps it (build/generate-manifest.ts).
|
|
105
|
+
*/
|
|
106
|
+
export function findTransitionWhenError(error: unknown): Error | undefined {
|
|
107
|
+
const seen = new Set<unknown>();
|
|
108
|
+
let current: unknown = error;
|
|
109
|
+
while (
|
|
110
|
+
typeof current === "object" &&
|
|
111
|
+
current !== null &&
|
|
112
|
+
!seen.has(current)
|
|
113
|
+
) {
|
|
114
|
+
seen.add(current);
|
|
115
|
+
if ((current as Error).name === "TransitionWhenError") {
|
|
116
|
+
return current as Error;
|
|
117
|
+
}
|
|
118
|
+
current = (current as { cause?: unknown }).cause;
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Where an invalid `when` was declared, for the error message. */
|
|
124
|
+
export interface TransitionWhenSite {
|
|
125
|
+
/** The route the entry belongs to (the matched or enumerated route). */
|
|
126
|
+
readonly routeName?: string;
|
|
127
|
+
readonly pattern?: string;
|
|
128
|
+
/** The declaring entry's type when it is not the route itself. */
|
|
129
|
+
readonly entryType?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function describeSite(site: TransitionWhenSite): string {
|
|
133
|
+
const named =
|
|
134
|
+
site.routeName && !isAutoGeneratedRouteName(site.routeName)
|
|
135
|
+
? `route "${site.routeName}"${site.pattern ? ` (${site.pattern})` : ""}`
|
|
136
|
+
: site.pattern
|
|
137
|
+
? `route ${site.pattern}`
|
|
138
|
+
: "a route";
|
|
139
|
+
if (!site.entryType || site.entryType === "route") return named;
|
|
140
|
+
return `a ${site.entryType} of ${named}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function describeValue(value: unknown): string {
|
|
144
|
+
if (value === null) return "null";
|
|
145
|
+
if (Array.isArray(value)) return "an array";
|
|
146
|
+
return typeof value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The problem with a `when` value, or null when it is valid. Non-functions
|
|
151
|
+
* are always invalid; a plain function is invalid only under strict
|
|
152
|
+
* validation (see the module comment).
|
|
153
|
+
*/
|
|
154
|
+
export function transitionWhenProblem(
|
|
155
|
+
when: unknown,
|
|
156
|
+
strict: boolean,
|
|
157
|
+
): "not-a-function" | "server-function" | null {
|
|
158
|
+
if (when === undefined || isClientUrlsWhenRef(when)) return null;
|
|
159
|
+
if (typeof when !== "function") return "not-a-function";
|
|
160
|
+
if (strict && !isClientReference(when)) return "server-function";
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function createTransitionWhenError(
|
|
165
|
+
when: unknown,
|
|
166
|
+
problem: "not-a-function" | "server-function",
|
|
167
|
+
site: TransitionWhenSite,
|
|
168
|
+
): TransitionWhenError {
|
|
169
|
+
const where = `transition({ when }) on ${describeSite(site)}`;
|
|
170
|
+
const head =
|
|
171
|
+
problem === "not-a-function"
|
|
172
|
+
? `${where} must be a function, got ${describeValue(when)}.`
|
|
173
|
+
: `${where} is not a client function.`;
|
|
174
|
+
return new TransitionWhenError(
|
|
175
|
+
`[rango] ${head} ` +
|
|
176
|
+
"`when` runs in the browser: write it inline in the transition() call " +
|
|
177
|
+
"(transition({ when: (ctx) => ... }), where transition is the urls() helper " +
|
|
178
|
+
'or imported from "@rangojs/router"; the build hoists that literal into a ' +
|
|
179
|
+
'client module) or export it from a "use client" module and import it ' +
|
|
180
|
+
"into your urls() file. It receives { kind, from, to, isAction, action }: " +
|
|
181
|
+
"URLs, params, route names, history state and action data; get(), env " +
|
|
182
|
+
'and handler state do not exist there. See skills/view-transitions "Conditional transitions".',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Definition-time shape check (the DSL's transition()). Runs in every
|
|
188
|
+
* environment, so it rejects only what is invalid everywhere: a non-function.
|
|
189
|
+
*/
|
|
190
|
+
export function assertTransitionWhenShape(when: unknown): void {
|
|
191
|
+
if (transitionWhenProblem(when, false) === "not-a-function") {
|
|
192
|
+
throw new TypeError(
|
|
193
|
+
createTransitionWhenError(when, "not-a-function", {}).message,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Browser: turn a transition config's `when` into the predicate to call.
|
|
200
|
+
* Undefined when there is none (or a clientUrls route no longer declares it).
|
|
201
|
+
*/
|
|
202
|
+
export function resolveTransitionWhen(
|
|
203
|
+
when: unknown,
|
|
204
|
+
): TransitionWhenFn | undefined {
|
|
205
|
+
if (typeof when === "function") return when as TransitionWhenFn;
|
|
206
|
+
if (!isClientUrlsWhenRef(when)) return undefined;
|
|
207
|
+
const definition = when.definition as
|
|
208
|
+
| {
|
|
209
|
+
routes?: ReadonlyArray<{
|
|
210
|
+
id: string;
|
|
211
|
+
transition?: { when?: unknown };
|
|
212
|
+
}>;
|
|
213
|
+
}
|
|
214
|
+
| undefined;
|
|
215
|
+
const record = definition?.routes?.find(
|
|
216
|
+
(candidate) => candidate.id === when.routeId,
|
|
217
|
+
);
|
|
218
|
+
const fn = record?.transition?.when;
|
|
219
|
+
return typeof fn === "function" ? (fn as TransitionWhenFn) : undefined;
|
|
220
|
+
}
|
package/src/types/cache-types.ts
CHANGED
|
@@ -72,7 +72,13 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
72
72
|
/**
|
|
73
73
|
* Override the cache store for this boundary.
|
|
74
74
|
* When specified, this boundary and its children use this store
|
|
75
|
-
* instead of the app-level store from handler config.
|
|
75
|
+
* instead of the app-level store from handler config. The records of a
|
|
76
|
+
* boundary with a store here are partitioned by the `keyGenerator` of each
|
|
77
|
+
* enclosing cache() without a `key` whose store differs, the app-level
|
|
78
|
+
* store included: under `cache(() => [...])` on an app store with a
|
|
79
|
+
* `keyGenerator`, `cache({ store })` records are split by its result too.
|
|
80
|
+
* Likewise, without a `key` of its own, this store's `keyGenerator`
|
|
81
|
+
* partitions a nested cache() on another store.
|
|
76
82
|
*
|
|
77
83
|
* Useful for:
|
|
78
84
|
* - Different backends per route section (memory vs KV vs Redis)
|
|
@@ -108,6 +114,8 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
108
114
|
/**
|
|
109
115
|
* Conditional cache read function.
|
|
110
116
|
* Return false to skip cache for this request (always fetch fresh).
|
|
117
|
+
* It gates every cache() nested under this one too: a nested boundary
|
|
118
|
+
* caches only when every enclosing condition allows it.
|
|
111
119
|
*
|
|
112
120
|
* Has access to full RequestContext including env, request, params, cookies, etc.
|
|
113
121
|
* Runs after global and route middleware on RSC routes; on response routes,
|
|
@@ -130,7 +138,10 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
130
138
|
|
|
131
139
|
/**
|
|
132
140
|
* Custom cache key function - FULL OVERRIDE.
|
|
133
|
-
* Bypasses default key generation AND store's keyGenerator.
|
|
141
|
+
* Bypasses default key generation AND store's keyGenerator. The result is
|
|
142
|
+
* stored namespaced (`key:` plus its URI encoding), so no value it returns
|
|
143
|
+
* names another route's record; a nested cache() keys its records within
|
|
144
|
+
* this partition.
|
|
134
145
|
*
|
|
135
146
|
* Has access to full RequestContext including env, request, params, cookies, etc.
|
|
136
147
|
* Runs after global and route middleware on RSC routes; on response routes,
|
|
@@ -154,7 +165,8 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
154
165
|
|
|
155
166
|
/**
|
|
156
167
|
* Tags for cache invalidation.
|
|
157
|
-
* Can be a static array or a function that returns tags.
|
|
168
|
+
* Can be a static array or a function that returns tags. They tag the
|
|
169
|
+
* records of every cache() nested under this one too.
|
|
158
170
|
*
|
|
159
171
|
* The built-in `MemorySegmentCacheStore`, `CFCacheStore`, and
|
|
160
172
|
* `VercelCacheStore` index by tag.
|