@rangojs/router 0.22.1 → 0.24.0
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/bin/rango.js +10 -4
- package/dist/testing/vitest.js +31 -1
- package/dist/types/browser/defer.d.ts +48 -0
- package/dist/types/browser/document-revealed.d.ts +17 -0
- package/dist/types/browser/event-controller.d.ts +7 -1
- package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
- package/dist/types/browser/navigation-transaction.d.ts +21 -7
- package/dist/types/browser/partial-update.d.ts +39 -8
- package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
- package/dist/types/browser/react/context.d.ts +3 -2
- package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
- package/dist/types/browser/response-adapter.d.ts +7 -0
- package/dist/types/browser/types.d.ts +24 -10
- package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/router-processing.d.ts +1 -1
- package/dist/types/build/route-types/scan-filter.d.ts +22 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
- package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
- package/dist/types/cache/document-cache-policy.d.ts +24 -0
- package/dist/types/cache/document-cache.d.ts +2 -10
- package/dist/types/client-urls/client-urls.d.ts +1 -1
- package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
- package/dist/types/client-urls/navigation.d.ts +0 -37
- package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
- package/dist/types/client-urls/server-projection.d.ts +29 -1
- package/dist/types/client-urls/types.d.ts +34 -69
- package/dist/types/client.d.ts +0 -1
- package/dist/types/context-var.d.ts +12 -0
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/handles/deferred-resolution.d.ts +2 -1
- package/dist/types/handles/pushed-by.d.ts +31 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.rsc.d.ts +2 -0
- package/dist/types/loader-store.d.ts +8 -0
- package/dist/types/prefetch-markers.d.ts +11 -0
- package/dist/types/response-utils.d.ts +7 -0
- package/dist/types/reverse.d.ts +8 -8
- package/dist/types/route-definition/helpers-types.d.ts +27 -0
- package/dist/types/route-definition/rewrite.d.ts +56 -0
- package/dist/types/route-map-builder.d.ts +7 -2
- package/dist/types/router/basename.d.ts +7 -0
- package/dist/types/router/handler-context.d.ts +1 -5
- package/dist/types/router/internal-params.d.ts +8 -0
- package/dist/types/router/is-action.d.ts +2 -1
- package/dist/types/router/middleware-types.d.ts +15 -0
- package/dist/types/router/revalidation.d.ts +1 -2
- package/dist/types/router/route-trie-builder.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
- package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
- package/dist/types/rsc/full-payload.d.ts +11 -0
- package/dist/types/rsc/handler.d.ts +33 -0
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/loader-fetch.d.ts +16 -0
- package/dist/types/rsc/rewrite-passes.d.ts +74 -0
- package/dist/types/rsc/ssr-setup.d.ts +3 -1
- package/dist/types/server/context.d.ts +21 -1
- package/dist/types/server/handle-store.d.ts +9 -1
- package/dist/types/server/request-context.d.ts +52 -2
- package/dist/types/server/rewrite-trail.d.ts +49 -0
- package/dist/types/server/slot-accessors.d.ts +29 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +4 -4
- package/dist/types/testing/internal/action-id.d.ts +10 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
- package/dist/types/testing/internal/test-action.d.ts +8 -0
- package/dist/types/testing/render-route.d.ts +8 -0
- package/dist/types/testing/run-loader.d.ts +2 -1
- package/dist/types/testing/run-middleware.d.ts +20 -0
- package/dist/types/testing/run-revalidate.d.ts +74 -0
- package/dist/types/testing/run-transition-when.d.ts +2 -3
- package/dist/types/testing/serve-shell-request.d.ts +40 -1
- package/dist/types/testing/vitest.d.ts +11 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/boundaries.d.ts +9 -0
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +30 -0
- package/dist/types/types/request-scope.d.ts +7 -0
- package/dist/types/types/segments.d.ts +21 -4
- package/dist/types/urls/path-helper-types.d.ts +25 -4
- package/dist/types/urls/type-extraction.d.ts +6 -2
- package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
- package/dist/types/vite/discovery/state.d.ts +14 -4
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
- package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
- package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
- package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
- package/dist/types/vite/router-discovery.d.ts +10 -0
- package/dist/vite/index.js +974 -379
- package/package.json +1 -1
- package/skills/caching/SKILL.md +54 -5
- package/skills/catalog.json +3 -3
- package/skills/client-urls/SKILL.md +197 -24
- package/skills/cloudflare/SKILL.md +14 -9
- package/skills/hooks/SKILL.md +16 -3
- package/skills/hooks/data.md +3 -1
- package/skills/links/SKILL.md +24 -0
- package/skills/loader/SKILL.md +199 -2
- package/skills/middleware/SKILL.md +183 -2
- package/skills/migrate-nextjs/SKILL.md +2 -2
- package/skills/migrate-react-router/SKILL.md +10 -7
- package/skills/migrate-react-router/data-and-actions.md +1 -1
- package/skills/ppr/SKILL.md +22 -0
- package/skills/prerender/SKILL.md +22 -8
- package/skills/rango/SKILL.md +13 -7
- package/skills/route/SKILL.md +139 -0
- package/skills/server-actions/SKILL.md +27 -0
- package/skills/testing/SKILL.md +3 -2
- package/skills/testing/client-components.md +2 -1
- package/skills/testing/middleware.md +31 -10
- package/skills/testing/predicates.md +67 -32
- package/skills/testing/response-routes.md +1 -0
- package/skills/testing/serve-shell-request.md +107 -19
- package/skills/testing/setup.md +2 -2
- package/skills/typesafety/route-types.md +2 -2
- package/skills/use-cache/SKILL.md +4 -2
- package/skills/view-transitions/SKILL.md +26 -0
- package/src/__augment-tests__/augmented.check.ts +20 -0
- package/src/browser/defer.ts +82 -0
- package/src/browser/document-revealed.ts +52 -0
- package/src/browser/event-controller.ts +46 -32
- package/src/browser/merge-segment-loaders.ts +31 -18
- package/src/browser/navigation-bridge.ts +82 -16
- package/src/browser/navigation-client.ts +48 -36
- package/src/browser/navigation-store.ts +13 -0
- package/src/browser/navigation-transaction.ts +25 -8
- package/src/browser/partial-update.ts +554 -146
- package/src/browser/prefetch/fetch.ts +2 -1
- package/src/browser/react/Link.tsx +7 -9
- package/src/browser/react/NavigationProvider.tsx +88 -49
- package/src/browser/react/context.ts +3 -2
- package/src/browser/react/filter-segment-order.ts +13 -2
- package/src/browser/react/use-router.ts +2 -4
- package/src/browser/response-adapter.ts +20 -0
- package/src/browser/rsc-router.tsx +17 -45
- package/src/browser/server-action-bridge.ts +25 -63
- package/src/browser/types.ts +24 -10
- package/src/browser/validate-redirect-origin.ts +16 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/router-processing.ts +3 -6
- package/src/build/route-types/scan-filter.ts +45 -11
- package/src/cache/cf/cf-cache-store.ts +59 -21
- package/src/cache/cf/cf-cache-types.ts +24 -2
- package/src/cache/document-cache-policy.ts +131 -0
- package/src/cache/document-cache.ts +21 -107
- package/src/client-urls/client-urls.ts +22 -6
- package/src/client-urls/hoisted-revalidate.ts +25 -0
- package/src/client-urls/navigation.ts +0 -96
- package/src/client-urls/revalidate-registry.ts +30 -0
- package/src/client-urls/server-projection.ts +151 -65
- package/src/client-urls/types.ts +39 -71
- package/src/client.tsx +9 -5
- package/src/context-var.ts +32 -2
- package/src/errors.ts +34 -0
- package/src/handles/deferred-resolution.ts +28 -5
- package/src/handles/pushed-by.ts +148 -0
- package/src/index.rsc.ts +8 -0
- package/src/index.ts +7 -0
- package/src/loader-store.ts +18 -7
- package/src/prefetch-markers.ts +13 -0
- package/src/response-utils.ts +19 -0
- package/src/reverse.ts +17 -14
- package/src/route-content-wrapper.tsx +18 -4
- package/src/route-definition/dsl-helpers.ts +27 -4
- package/src/route-definition/helpers-types.ts +27 -1
- package/src/route-definition/redirect.ts +2 -15
- package/src/route-definition/rewrite.ts +146 -0
- package/src/route-map-builder.ts +14 -11
- package/src/router/basename.ts +20 -0
- package/src/router/handler-context.ts +3 -13
- package/src/router/intercept-resolution.ts +35 -16
- package/src/router/internal-params.ts +16 -0
- package/src/router/is-action.ts +2 -1
- package/src/router/loader-resolution.ts +85 -4
- package/src/router/match-api.ts +36 -1
- package/src/router/match-middleware/background-revalidation.ts +16 -4
- package/src/router/match-middleware/cache-lookup.ts +35 -7
- package/src/router/match-middleware/cache-store.ts +15 -1
- package/src/router/match-middleware/intercept-resolution.ts +4 -2
- package/src/router/match-result.ts +14 -2
- package/src/router/middleware-types.ts +16 -0
- package/src/router/middleware.ts +127 -49
- package/src/router/navigation-snapshot.ts +2 -1
- package/src/router/revalidation.ts +1 -1
- package/src/router/route-trie-builder.ts +28 -1
- package/src/router/segment-resolution/fresh.ts +22 -18
- package/src/router/segment-resolution/loader-cache.ts +2 -1
- package/src/router/segment-resolution/loader-mask.ts +5 -3
- package/src/router/segment-resolution/loader-snapshot.ts +18 -4
- package/src/router/segment-resolution/mask-nested.ts +8 -4
- package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
- package/src/router/segment-resolution/revalidation.ts +215 -82
- package/src/rsc/full-payload.ts +37 -5
- package/src/rsc/handler.ts +82 -55
- package/src/rsc/helpers.ts +24 -0
- package/src/rsc/loader-fetch.ts +79 -24
- package/src/rsc/response-cache-serve.ts +18 -2
- package/src/rsc/response-route-handler.ts +4 -7
- package/src/rsc/rewrite-passes.ts +203 -0
- package/src/rsc/rsc-rendering.ts +54 -9
- package/src/rsc/shell-capture.ts +2 -1
- package/src/rsc/ssr-setup.ts +11 -5
- package/src/segment-loader-promise.ts +23 -20
- package/src/segment-system.tsx +60 -26
- package/src/server/context.ts +39 -5
- package/src/server/cookie-parse.ts +36 -12
- package/src/server/cookie-store.ts +7 -3
- package/src/server/handle-store.ts +152 -25
- package/src/server/request-context.ts +175 -64
- package/src/server/request-headers.ts +4 -14
- package/src/server/rewrite-trail.ts +190 -0
- package/src/server/slot-accessors.ts +57 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +118 -16
- package/src/testing/action-form.ts +2 -11
- package/src/testing/dispatch.ts +138 -67
- package/src/testing/e2e/page-helpers.ts +3 -1
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +7 -3
- package/src/testing/internal/action-id.ts +16 -1
- package/src/testing/internal/client-urls-projection.ts +70 -0
- package/src/testing/internal/test-action.ts +25 -0
- package/src/testing/render-route.tsx +16 -3
- package/src/testing/run-loader.ts +4 -1
- package/src/testing/run-middleware.ts +56 -11
- package/src/testing/run-revalidate.ts +142 -0
- package/src/testing/run-transition-when.ts +7 -8
- package/src/testing/serve-shell-request.ts +131 -8
- package/src/testing/vitest.ts +67 -1
- package/src/thenable-status.ts +11 -0
- package/src/types/boundaries.ts +12 -0
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +30 -0
- package/src/types/request-scope.ts +8 -0
- package/src/types/segments.ts +26 -4
- package/src/urls/path-helper-types.ts +27 -5
- package/src/urls/type-extraction.ts +30 -10
- package/src/use-loader.tsx +18 -13
- package/src/vite/discovery/client-urls-projection.ts +20 -21
- package/src/vite/discovery/discover-routers.ts +94 -26
- package/src/vite/discovery/state.ts +28 -4
- package/src/vite/discovery/virtual-module-codegen.ts +22 -17
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
- package/src/vite/plugins/expose-internal-ids.ts +2 -3
- package/src/vite/plugins/hoist/literal.ts +417 -0
- package/src/vite/plugins/hoist/scope.ts +370 -0
- package/src/vite/plugins/hoist/source.ts +84 -0
- package/src/vite/plugins/transition-when-hoist.ts +113 -779
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +116 -53
- package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
- package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
- package/dist/types/testing/run-client-revalidate.d.ts +0 -43
- package/src/client-urls/revalidate-chain.ts +0 -83
- package/src/client-urls/revalidation-protocol.ts +0 -56
- package/src/testing/run-client-revalidate.ts +0 -92
package/src/vite/rango.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "./plugins/expose-internal-ids.js";
|
|
15
15
|
import { useCacheTransform } from "./plugins/use-cache-transform.js";
|
|
16
16
|
import { transitionWhenHoistPlugin } from "./plugins/transition-when-hoist.js";
|
|
17
|
+
import { clientUrlsRevalidateHoistPlugin } from "./plugins/client-urls-revalidate-hoist.js";
|
|
17
18
|
import { clientRefDedup } from "./plugins/client-ref-dedup.js";
|
|
18
19
|
import { stableRscOutput } from "./plugins/stable-rsc-output.js";
|
|
19
20
|
import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
|
|
@@ -592,6 +593,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
592
593
|
|
|
593
594
|
plugins.push(createLoaderDirectiveGuardPlugin());
|
|
594
595
|
plugins.push(transitionWhenHoistPlugin());
|
|
596
|
+
plugins.push(...clientUrlsRevalidateHoistPlugin());
|
|
595
597
|
plugins.push(createLoaderScanStubPlugin());
|
|
596
598
|
plugins.push(exposeActionId());
|
|
597
599
|
plugins.push(
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Plugin, ViteDevServer } from "vite";
|
|
10
|
-
import { createServer as createViteServer } from "vite";
|
|
10
|
+
import { createServer as createViteServer, normalizePath } from "vite";
|
|
11
11
|
import { dirname, join, resolve } from "node:path";
|
|
12
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
13
|
import { createRequire, register } from "node:module";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
findNestedRouterConflict,
|
|
18
18
|
findRouterFiles,
|
|
19
19
|
createScanFilter,
|
|
20
|
+
isInSkippedScanDir,
|
|
20
21
|
} from "../build/generate-route-types.js";
|
|
21
22
|
import { firstCodeMatchIndex } from "../build/route-types/source-scan.js";
|
|
22
23
|
import {
|
|
@@ -50,7 +51,9 @@ import { hashServerRefs } from "./plugins/server-ref-hashing.js";
|
|
|
50
51
|
import { defineEncryptionKeyExpr } from "./encryption-key.js";
|
|
51
52
|
import { extractHandlerExportsFromChunk } from "./utils/bundle-analysis.js";
|
|
52
53
|
import {
|
|
54
|
+
canRefreshRuntimeDiscovery,
|
|
53
55
|
createDiscoveryState,
|
|
56
|
+
recordDiscoveryFailure,
|
|
54
57
|
VIRTUAL_ROUTES_MANIFEST_ID,
|
|
55
58
|
type DiscoveryState,
|
|
56
59
|
type PluginOptions,
|
|
@@ -75,6 +78,7 @@ import {
|
|
|
75
78
|
import { describeDiscoveryFailure } from "./discovery/discovery-errors.js";
|
|
76
79
|
import { findTransitionWhenError } from "../transition-when-ref.js";
|
|
77
80
|
import { transitionWhenHoistPlugin } from "./plugins/transition-when-hoist.js";
|
|
81
|
+
import { clientUrlsRevalidateHoistPlugin } from "./plugins/client-urls-revalidate-hoist.js";
|
|
78
82
|
import {
|
|
79
83
|
createDevPrerenderCache,
|
|
80
84
|
devPrerenderCacheKey,
|
|
@@ -106,6 +110,35 @@ const debugDev = createRangoDebugger(NS.dev);
|
|
|
106
110
|
|
|
107
111
|
export { VIRTUAL_ROUTES_MANIFEST_ID };
|
|
108
112
|
|
|
113
|
+
/** The slice of a Vite environment module graph the route-file watcher reads. */
|
|
114
|
+
interface ModulesByFileGraph {
|
|
115
|
+
getModulesByFile(file: string): ReadonlySet<unknown> | undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Modules a Vite graph holds for `file`; Vite keys its file index by normalized path. */
|
|
119
|
+
function modulesForFile(
|
|
120
|
+
graph: ModulesByFileGraph,
|
|
121
|
+
file: string,
|
|
122
|
+
): ReadonlySet<unknown> | undefined {
|
|
123
|
+
return graph.getModulesByFile(normalizePath(file));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Whether the dev route-file watcher ignores `file`: it sits in a directory
|
|
128
|
+
* every discovery scan skips (isInSkippedScanDir) and the RSC graph has not
|
|
129
|
+
* loaded it. See docs/manifests.md, "A reload no file of the app asked for".
|
|
130
|
+
*/
|
|
131
|
+
export function isFileOutsideApp(
|
|
132
|
+
root: string,
|
|
133
|
+
file: string,
|
|
134
|
+
rscGraph: ModulesByFileGraph | undefined,
|
|
135
|
+
): boolean {
|
|
136
|
+
return (
|
|
137
|
+
isInSkippedScanDir(root, file) &&
|
|
138
|
+
!(rscGraph && modulesForFile(rscGraph, file)?.size)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
109
142
|
// ============================================================================
|
|
110
143
|
// Node ESM Loader Hook Registration
|
|
111
144
|
// ============================================================================
|
|
@@ -299,6 +332,10 @@ export async function createTempRscServer(
|
|
|
299
332
|
// Inline transition({ when }) literals become client references here
|
|
300
333
|
// too, or discovery would see (and reject) the plain function.
|
|
301
334
|
transitionWhenHoistPlugin(),
|
|
335
|
+
// clientUrls() revalidate() predicates move to the server here too:
|
|
336
|
+
// discovery serializes the SSR copy's markers and the RSC realm
|
|
337
|
+
// registers the predicates the materialized stubs call.
|
|
338
|
+
...clientUrlsRevalidateHoistPlugin(),
|
|
302
339
|
// Dev prerender must use dev-mode IDs (path-based) to match the workerd
|
|
303
340
|
// runtime. forceBuild produces hashed IDs for production bundle consistency.
|
|
304
341
|
exposeInternalIds(options.forceBuild ? { forceBuild: true } : undefined),
|
|
@@ -1014,12 +1051,20 @@ export function createRouterDiscoveryPlugin(
|
|
|
1014
1051
|
acquireBuildEnv(s, viteCommand, viteMode),
|
|
1015
1052
|
);
|
|
1016
1053
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
)
|
|
1021
|
-
)
|
|
1022
|
-
|
|
1054
|
+
const temp = await timed(
|
|
1055
|
+
debugDiscovery,
|
|
1056
|
+
"getOrCreateTempServer",
|
|
1057
|
+
() => getOrCreateTempServer(),
|
|
1058
|
+
);
|
|
1059
|
+
tempRscEnv = temp.env;
|
|
1060
|
+
if (!tempRscEnv) {
|
|
1061
|
+
// Recovery mode, as in the catch below. getOrCreateTempServer
|
|
1062
|
+
// already logged why the temp runner failed.
|
|
1063
|
+
recordDiscoveryFailure(
|
|
1064
|
+
s,
|
|
1065
|
+
temp.error ?? "temp runner unavailable for discovery",
|
|
1066
|
+
);
|
|
1067
|
+
} else {
|
|
1023
1068
|
optimizerHashBefore =
|
|
1024
1069
|
tempRscEnv.depsOptimizer?.metadata?.browserHash;
|
|
1025
1070
|
await timed(debugDiscovery, "discoverRouters (cloudflare)", () =>
|
|
@@ -1034,6 +1079,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1034
1079
|
);
|
|
1035
1080
|
}
|
|
1036
1081
|
} catch (err: any) {
|
|
1082
|
+
recordDiscoveryFailure(s, err);
|
|
1037
1083
|
emitDiscoveryFailure(
|
|
1038
1084
|
err,
|
|
1039
1085
|
optimizerHashBefore,
|
|
@@ -1066,20 +1112,17 @@ export function createRouterDiscoveryPlugin(
|
|
|
1066
1112
|
// Discover routers FIRST, then arm the manifest-readiness gate on the
|
|
1067
1113
|
// server module discovery actually read the registry from.
|
|
1068
1114
|
//
|
|
1069
|
-
// We deliberately do NOT pre-import "@rangojs/router/server"
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
// imported
|
|
1073
|
-
// the runner's module graph
|
|
1074
|
-
//
|
|
1075
|
-
//
|
|
1076
|
-
//
|
|
1077
|
-
//
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
// read and write on one copy. The virtual manifest module's own gate
|
|
1081
|
-
// (s.discoveryDone, armed by beginDiscoveryGate) already blocks early
|
|
1082
|
-
// requests during discovery on the Node path, so arming
|
|
1115
|
+
// We deliberately do NOT pre-import "@rangojs/router/server" here to
|
|
1116
|
+
// arm the gate early. During a Vite dependency re-optimization (dev
|
|
1117
|
+
// boot after a lockfile change, or `vite dev --force`) or any runner
|
|
1118
|
+
// full reload, a module imported before the entry can be a stale copy
|
|
1119
|
+
// of the runner's module graph: createRouter() populates RouterRegistry
|
|
1120
|
+
// on the fresh copy and the stale one reads an empty Map, a spurious
|
|
1121
|
+
// "No routers found" (#637). discoverRouters returns the instance it
|
|
1122
|
+
// read the registry from and verified the runner still serves (it
|
|
1123
|
+
// redoes a pass that a reload split). The virtual manifest module's
|
|
1124
|
+
// own gate (s.discoveryDone, armed by beginDiscoveryGate) already
|
|
1125
|
+
// blocks early requests during discovery on the Node path, so arming
|
|
1083
1126
|
// manifestReadyPromise after discovery is sufficient here.
|
|
1084
1127
|
const serverMod = await timed(debugDiscovery, "discoverRouters", () =>
|
|
1085
1128
|
discoverRouters(s, rscEnv, (server.environments as any)?.ssr),
|
|
@@ -1105,6 +1148,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
1105
1148
|
propagateDiscoveryState(rscEnv),
|
|
1106
1149
|
);
|
|
1107
1150
|
} catch (err: any) {
|
|
1151
|
+
// Recovery mode: the next in-scan source change re-runs discovery
|
|
1152
|
+
// (canRefreshRuntimeDiscovery) instead of waiting for a restart.
|
|
1153
|
+
recordDiscoveryFailure(s, err);
|
|
1108
1154
|
emitDiscoveryFailure(
|
|
1109
1155
|
err,
|
|
1110
1156
|
optimizerHashBefore,
|
|
@@ -1630,6 +1676,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
1630
1676
|
(filePath.includes("named-routes.gen.ts") ||
|
|
1631
1677
|
filePath.includes("urls.gen.ts"));
|
|
1632
1678
|
|
|
1679
|
+
// Files no discovery scan reaches are ignored unless the RSC graph
|
|
1680
|
+
// loaded them (docs/manifests.md, "A reload no file of the app asked for").
|
|
1681
|
+
const isOutsideApp = (filePath: string): boolean =>
|
|
1682
|
+
isFileOutsideApp(
|
|
1683
|
+
s.projectRoot,
|
|
1684
|
+
filePath,
|
|
1685
|
+
(server.environments as any)?.rsc?.moduleGraph,
|
|
1686
|
+
);
|
|
1687
|
+
|
|
1633
1688
|
const regenerateGeneratedRouteFiles = () => {
|
|
1634
1689
|
if (s.perRouterManifests.length > 0) {
|
|
1635
1690
|
writeRouteTypesFiles(s);
|
|
@@ -1676,6 +1731,18 @@ export function createRouterDiscoveryPlugin(
|
|
|
1676
1731
|
// only the expensive regeneration is debounced.
|
|
1677
1732
|
let routeChangeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1678
1733
|
|
|
1734
|
+
// Drop the routes-manifest virtual module's cached transform so the
|
|
1735
|
+
// next runner evaluation regenerates it from current state.
|
|
1736
|
+
const invalidateRoutesManifestModule = (rscEnv: any) => {
|
|
1737
|
+
for (const virtualId of [
|
|
1738
|
+
VIRTUAL_ROUTES_MANIFEST_ID,
|
|
1739
|
+
`\0${VIRTUAL_ROUTES_MANIFEST_ID}`,
|
|
1740
|
+
]) {
|
|
1741
|
+
const virtualMod = rscEnv.moduleGraph?.getModuleById?.(virtualId);
|
|
1742
|
+
if (virtualMod) rscEnv.moduleGraph.invalidateModule(virtualMod);
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1679
1746
|
// Re-run runtime discovery so factory-generated routes that the
|
|
1680
1747
|
// static parser cannot see are refreshed after source changes.
|
|
1681
1748
|
// The state-machine concerns (queued/pending/gatePending) are
|
|
@@ -1684,18 +1751,12 @@ export function createRouterDiscoveryPlugin(
|
|
|
1684
1751
|
const refreshRuntimeDiscovery = async () => {
|
|
1685
1752
|
const rscEnv = (server.environments as any)?.rsc;
|
|
1686
1753
|
const hasMainRunner = !!rscEnv?.runner;
|
|
1687
|
-
// Cloudflare HMR has no main RSC runner (workerd is a separate
|
|
1688
|
-
// runtime). When we have a populated runtime manifest from cold
|
|
1689
|
-
// start, we can re-discover via the temp Node runner — the same
|
|
1690
|
-
// mechanism getOrCreateTempServer() uses at startup. Without a
|
|
1691
|
-
// populated manifest there's nothing useful to do, so bail
|
|
1692
|
-
// before involving the gate machine at all.
|
|
1693
|
-
if (!hasMainRunner && s.perRouterManifests.length === 0) return;
|
|
1694
1754
|
let previousRouteShape = routeShapeSignature();
|
|
1695
1755
|
await gate.runRefreshCycle(async () => {
|
|
1696
1756
|
const hmrStart = performance.now();
|
|
1697
1757
|
try {
|
|
1698
1758
|
if (hasMainRunner) {
|
|
1759
|
+
const recoveringColdStart = s.perRouterManifests.length === 0;
|
|
1699
1760
|
await timed(debugDiscovery, "hmr discoverRouters", () =>
|
|
1700
1761
|
discoverRouters(s, rscEnv, (server.environments as any)?.ssr),
|
|
1701
1762
|
);
|
|
@@ -1705,12 +1766,20 @@ export function createRouterDiscoveryPlugin(
|
|
|
1705
1766
|
await timed(debugDiscovery, "hmr propagateDiscoveryState", () =>
|
|
1706
1767
|
propagateDiscoveryState(rscEnv),
|
|
1707
1768
|
);
|
|
1769
|
+
// The manifest module emitted after a failed cold discovery has
|
|
1770
|
+
// no gen-file import, so no gen write ever invalidates it.
|
|
1771
|
+
if (recoveringColdStart) invalidateRoutesManifestModule(rscEnv);
|
|
1708
1772
|
} else {
|
|
1709
|
-
// Cloudflare HMR
|
|
1710
|
-
//
|
|
1711
|
-
//
|
|
1773
|
+
// Cloudflare HMR has no main RSC runner (workerd is a separate
|
|
1774
|
+
// runtime): re-discover on the temp Node runner. Invalidate its
|
|
1775
|
+
// RSC module graph (or close+recreate as a fallback) so the
|
|
1776
|
+
// runner re-reads the freshly edited source. Keeping the same
|
|
1712
1777
|
// Vite instance alive avoids disrupting workerd's transport
|
|
1713
|
-
// during the first post-cold-start module-fetch window.
|
|
1778
|
+
// during the first post-cold-start module-fetch window. After
|
|
1779
|
+
// a cold start whose temp runner failed there is none, and
|
|
1780
|
+
// getOrCreateTempServer creates one. A recovering cycle needs
|
|
1781
|
+
// no routes-manifest invalidation here: the worker reload below
|
|
1782
|
+
// invalidates its whole module graph.
|
|
1714
1783
|
const tempRscEnv = await timed(
|
|
1715
1784
|
debugDiscovery,
|
|
1716
1785
|
"hmr refreshTempRscEnv (cloudflare)",
|
|
@@ -1766,10 +1835,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1766
1835
|
forceCloudflareWorkerReload(rscEnv, expectedEpoch);
|
|
1767
1836
|
}
|
|
1768
1837
|
} catch (err: any) {
|
|
1769
|
-
s
|
|
1770
|
-
message: err?.message ?? String(err),
|
|
1771
|
-
at: Date.now(),
|
|
1772
|
-
};
|
|
1838
|
+
recordDiscoveryFailure(s, err);
|
|
1773
1839
|
// An invalid transition({ when }) is a definition error the
|
|
1774
1840
|
// user must see: terminal error plus the browser's Vite overlay,
|
|
1775
1841
|
// not a warning while the last-good route tree keeps serving.
|
|
@@ -1924,16 +1990,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1924
1990
|
// stale replay after propagateDiscoveryState would clobber
|
|
1925
1991
|
// the refreshed registry as the realm's last write and the
|
|
1926
1992
|
// next router evaluation would materialize the old mount.
|
|
1927
|
-
|
|
1928
|
-
VIRTUAL_ROUTES_MANIFEST_ID,
|
|
1929
|
-
`\0${VIRTUAL_ROUTES_MANIFEST_ID}`,
|
|
1930
|
-
]) {
|
|
1931
|
-
const virtualMod =
|
|
1932
|
-
rscEnv.moduleGraph?.getModuleById?.(virtualId);
|
|
1933
|
-
if (virtualMod) {
|
|
1934
|
-
rscEnv.moduleGraph.invalidateModule(virtualMod);
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1993
|
+
invalidateRoutesManifestModule(rscEnv);
|
|
1937
1994
|
} catch (err: any) {
|
|
1938
1995
|
debugDiscovery?.(
|
|
1939
1996
|
"watcher: clientUrls projection pre-refresh failed: %s",
|
|
@@ -1970,7 +2027,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1970
2027
|
// Async: re-run runtime discovery to refresh factory-generated
|
|
1971
2028
|
// routes that the static parser cannot resolve. Resolves the
|
|
1972
2029
|
// discovery gate when complete.
|
|
1973
|
-
if (s
|
|
2030
|
+
if (canRefreshRuntimeDiscovery(s)) {
|
|
1974
2031
|
// The cloudflare workerd reload fires inside refreshRuntimeDiscovery
|
|
1975
2032
|
// on the successful cycle (see forceCloudflareWorkerReload call
|
|
1976
2033
|
// there) so queued follow-up cycles also trigger it.
|
|
@@ -1987,6 +2044,13 @@ export function createRouterDiscoveryPlugin(
|
|
|
1987
2044
|
};
|
|
1988
2045
|
|
|
1989
2046
|
const handleRouteFileChange = (filePath: string) => {
|
|
2047
|
+
if (isOutsideApp(filePath)) {
|
|
2048
|
+
debugDiscovery?.(
|
|
2049
|
+
"watcher: skip %s (outside the discovery scan)",
|
|
2050
|
+
filePath,
|
|
2051
|
+
);
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
1990
2054
|
if (maybeHandleGeneratedRouteFileMutation(filePath)) return;
|
|
1991
2055
|
if (
|
|
1992
2056
|
!filePath.endsWith(".ts") &&
|
|
@@ -2124,9 +2188,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
2124
2188
|
}
|
|
2125
2189
|
const seen = new Set<any>();
|
|
2126
2190
|
for (const file of routerSourceFiles) {
|
|
2127
|
-
const mods = rscGraph
|
|
2128
|
-
file.replaceAll("\\", "/"),
|
|
2129
|
-
);
|
|
2191
|
+
const mods = modulesForFile(rscGraph, file);
|
|
2130
2192
|
if (!mods) {
|
|
2131
2193
|
debugDiscovery?.(
|
|
2132
2194
|
"watcher: clientUrls invalidation found no rsc modules for %s",
|
|
@@ -2166,8 +2228,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
2166
2228
|
// and resets `discoveryDone` to a fresh pending promise (so
|
|
2167
2229
|
// workerd reloads triggered by the same source change can't
|
|
2168
2230
|
// observe a stale resolved gate from cold-start). Resolved
|
|
2169
|
-
// by the trailing refreshRuntimeDiscovery() cycle
|
|
2170
|
-
|
|
2231
|
+
// by the trailing refreshRuntimeDiscovery() cycle, which runs
|
|
2232
|
+
// under the same predicate.
|
|
2233
|
+
if (canRefreshRuntimeDiscovery(s)) {
|
|
2171
2234
|
gate.noteRouteEvent();
|
|
2172
2235
|
}
|
|
2173
2236
|
scheduleRouteRegeneration();
|
|
@@ -2194,7 +2257,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
2194
2257
|
// Same no-runner guard as change/add: stale perRouterManifests would
|
|
2195
2258
|
// reintroduce reverted content.
|
|
2196
2259
|
server.watcher.on("unlink", (filePath) => {
|
|
2197
|
-
if (!isGeneratedRouteFile(filePath)) return;
|
|
2260
|
+
if (!isGeneratedRouteFile(filePath) || isOutsideApp(filePath)) return;
|
|
2198
2261
|
const hasRunner = !!(server.environments as any)?.rsc?.runner;
|
|
2199
2262
|
if (!hasRunner) return;
|
|
2200
2263
|
regenerateGeneratedRouteFiles();
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The clientUrls revalidate() chain evaluator, shared by the browser
|
|
3
|
-
* collector (navigation.ts) and the public testing primitive
|
|
4
|
-
* (testing/run-client-revalidate.ts) so the two can never drift.
|
|
5
|
-
*
|
|
6
|
-
* Semantics mirror the server's evaluateRevalidation
|
|
7
|
-
* (src/router/revalidation.ts): a boolean verdict is a hard decision and
|
|
8
|
-
* short-circuits the rest of the chain; a `{ defaultShouldRevalidate }`
|
|
9
|
-
* object updates the running suggestion, which later predicates receive as
|
|
10
|
-
* their `defaultShouldRevalidate`; null/undefined defers; a throwing
|
|
11
|
-
* predicate fails open to the current suggestion (logged). One deliberate
|
|
12
|
-
* divergence: the object form is accepted only with a boolean value — the
|
|
13
|
-
* server is laxer, but never re-compares the value, while this decision
|
|
14
|
-
* feeds a strict-equality delta gate and the wire encoding
|
|
15
|
-
* (navigation.ts), where a truthy non-boolean would invert intent.
|
|
16
|
-
*/
|
|
17
|
-
import type { ClientRevalidateArgs, ClientRevalidateFn } from "./types.js";
|
|
18
|
-
/**
|
|
19
|
-
* The locked default the server will apply to the request these decisions
|
|
20
|
-
* ride on. `actionRequest` is about the REQUEST, not the user gesture: only
|
|
21
|
-
* the action POST itself is evaluated server-side with actionContext
|
|
22
|
-
* (default `true`); the follow-up refetch GETs an action can trigger carry
|
|
23
|
-
* no actionContext and get navigation defaults — even though their
|
|
24
|
-
* predicates still see `isAction()` as true.
|
|
25
|
-
*/
|
|
26
|
-
export declare function lockedClientDefault(options: {
|
|
27
|
-
actionRequest: boolean;
|
|
28
|
-
currentParams: Record<string, string>;
|
|
29
|
-
nextParams: Record<string, string>;
|
|
30
|
-
currentUrl: URL;
|
|
31
|
-
nextUrl: URL;
|
|
32
|
-
}): boolean;
|
|
33
|
-
export declare function runClientRevalidateChain(fns: readonly ClientRevalidateFn[], baseArgs: Omit<ClientRevalidateArgs, "defaultShouldRevalidate">, lockedDefault: boolean, label: string): boolean;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wire protocol for CLIENT-RUN per-loader revalidation decisions.
|
|
3
|
-
*
|
|
4
|
-
* clientUrls() revalidate() predicates are declared in a "use client" module,
|
|
5
|
-
* so they cannot cross the projection boundary as functions — instead they
|
|
6
|
-
* EXECUTE in the browser and only their DECISIONS cross: the browser attaches
|
|
7
|
-
* this header to partial-navigation and action requests, and the synthesized
|
|
8
|
-
* per-loader revalidate() on every materialized loader stub reads it back
|
|
9
|
-
* (see materializeRouteItems in server-projection.ts).
|
|
10
|
-
*
|
|
11
|
-
* Trust model: same class as `_rsc_segments` — a decision can only make the
|
|
12
|
-
* CLIENT's own view staler (skip) or fresher (force), and the synthesized
|
|
13
|
-
* predicates exist only on client-urls loader stubs, so the header can never
|
|
14
|
-
* influence server-tree loaders. Requests without the header (no-JS, PE,
|
|
15
|
-
* prefetch, document loads) fall back to the locked server defaults.
|
|
16
|
-
*/
|
|
17
|
-
export declare const CLIENT_REVALIDATION_HEADER = "X-Rango-Client-Reval";
|
|
18
|
-
export interface ClientRevalidationDecisions {
|
|
19
|
-
/** Loader $$ids whose client predicate said false (keep held data). */
|
|
20
|
-
readonly skip: readonly string[];
|
|
21
|
-
/** Loader $$ids whose client predicate said true against a false default. */
|
|
22
|
-
readonly force: readonly string[];
|
|
23
|
-
}
|
|
24
|
-
export declare function encodeClientRevalidationDecisions(decisions: ClientRevalidationDecisions): string | null;
|
|
25
|
-
export declare function decodeClientRevalidationDecisions(headerValue: string | null): ClientRevalidationDecisions | null;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* runClientRevalidate — unit-test clientUrls() revalidate() predicates.
|
|
3
|
-
*
|
|
4
|
-
* Builds the same {@link ClientRevalidateArgs} the browser collector passes
|
|
5
|
-
* and evaluates the predicate(s) through the SAME chain evaluator production
|
|
6
|
-
* uses (client-urls/revalidate-chain.ts) — locked default, boolean
|
|
7
|
-
* short-circuit, soft-verdict threading, and fail-open are the production
|
|
8
|
-
* code paths, not a re-implementation. Pass an array to test a chain.
|
|
9
|
-
*
|
|
10
|
-
* Synchronous: client revalidate functions must be sync.
|
|
11
|
-
*/
|
|
12
|
-
import type { ClientRevalidateFn } from "../client-urls/types.js";
|
|
13
|
-
/**
|
|
14
|
-
* Options for {@link runClientRevalidate}. Defaults model a same-URL
|
|
15
|
-
* navigation with no action (locked default `false`).
|
|
16
|
-
*/
|
|
17
|
-
export interface RunClientRevalidateOptions {
|
|
18
|
-
currentUrl?: string | URL;
|
|
19
|
-
nextUrl?: string | URL;
|
|
20
|
-
currentParams?: Record<string, string>;
|
|
21
|
-
nextParams?: Record<string, string>;
|
|
22
|
-
stale?: boolean;
|
|
23
|
-
/**
|
|
24
|
-
* The triggering action: a single imported reference (id resolved via
|
|
25
|
-
* `$id ?? $$id`; throws if the function carries neither) or a raw actionId
|
|
26
|
-
* string. A namespace/object is rejected — it cannot identify the ONE
|
|
27
|
-
* action that triggered the request. Omit for a plain navigation.
|
|
28
|
-
*/
|
|
29
|
-
action?: ((...args: never[]) => unknown) | string;
|
|
30
|
-
/**
|
|
31
|
-
* Model an action-triggered refetch GET: predicates see `isAction()` as
|
|
32
|
-
* true, but the locked default stays the navigation default, matching how
|
|
33
|
-
* the server evaluates that request (no actionContext). Defaults to
|
|
34
|
-
* treating a provided `action` as the action POST itself.
|
|
35
|
-
*/
|
|
36
|
-
actionRequest?: boolean;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Run one clientUrls `revalidate()` predicate — or a chain, in declaration
|
|
40
|
-
* order — against production-built args. Returns the final boolean decision
|
|
41
|
-
* (locked default if every predicate defers or throws).
|
|
42
|
-
*/
|
|
43
|
-
export declare function runClientRevalidate(fn: ClientRevalidateFn | readonly ClientRevalidateFn[], opts?: RunClientRevalidateOptions): boolean;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The clientUrls revalidate() chain evaluator, shared by the browser
|
|
3
|
-
* collector (navigation.ts) and the public testing primitive
|
|
4
|
-
* (testing/run-client-revalidate.ts) so the two can never drift.
|
|
5
|
-
*
|
|
6
|
-
* Semantics mirror the server's evaluateRevalidation
|
|
7
|
-
* (src/router/revalidation.ts): a boolean verdict is a hard decision and
|
|
8
|
-
* short-circuits the rest of the chain; a `{ defaultShouldRevalidate }`
|
|
9
|
-
* object updates the running suggestion, which later predicates receive as
|
|
10
|
-
* their `defaultShouldRevalidate`; null/undefined defers; a throwing
|
|
11
|
-
* predicate fails open to the current suggestion (logged). One deliberate
|
|
12
|
-
* divergence: the object form is accepted only with a boolean value — the
|
|
13
|
-
* server is laxer, but never re-compares the value, while this decision
|
|
14
|
-
* feeds a strict-equality delta gate and the wire encoding
|
|
15
|
-
* (navigation.ts), where a truthy non-boolean would invert intent.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { paramsEqual } from "../router/params-util.js";
|
|
19
|
-
import type { ClientRevalidateArgs, ClientRevalidateFn } from "./types.js";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The locked default the server will apply to the request these decisions
|
|
23
|
-
* ride on. `actionRequest` is about the REQUEST, not the user gesture: only
|
|
24
|
-
* the action POST itself is evaluated server-side with actionContext
|
|
25
|
-
* (default `true`); the follow-up refetch GETs an action can trigger carry
|
|
26
|
-
* no actionContext and get navigation defaults — even though their
|
|
27
|
-
* predicates still see `isAction()` as true.
|
|
28
|
-
*/
|
|
29
|
-
export function lockedClientDefault(options: {
|
|
30
|
-
actionRequest: boolean;
|
|
31
|
-
currentParams: Record<string, string>;
|
|
32
|
-
nextParams: Record<string, string>;
|
|
33
|
-
currentUrl: URL;
|
|
34
|
-
nextUrl: URL;
|
|
35
|
-
}): boolean {
|
|
36
|
-
if (options.actionRequest) return true;
|
|
37
|
-
return (
|
|
38
|
-
!paramsEqual(options.currentParams, options.nextParams) ||
|
|
39
|
-
options.currentUrl.search !== options.nextUrl.search
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function runClientRevalidateChain(
|
|
44
|
-
fns: readonly ClientRevalidateFn[],
|
|
45
|
-
baseArgs: Omit<ClientRevalidateArgs, "defaultShouldRevalidate">,
|
|
46
|
-
lockedDefault: boolean,
|
|
47
|
-
label: string,
|
|
48
|
-
): boolean {
|
|
49
|
-
let suggestion = lockedDefault;
|
|
50
|
-
for (const fn of fns) {
|
|
51
|
-
let verdict: ReturnType<ClientRevalidateFn>;
|
|
52
|
-
try {
|
|
53
|
-
verdict = fn({ ...baseArgs, defaultShouldRevalidate: suggestion });
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error(
|
|
56
|
-
`[@rangojs/router] clientUrls revalidate() threw for ${label}; using default decision:`,
|
|
57
|
-
error,
|
|
58
|
-
);
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (
|
|
62
|
-
process.env.NODE_ENV !== "production" &&
|
|
63
|
-
verdict != null &&
|
|
64
|
-
typeof (verdict as { then?: unknown }).then === "function"
|
|
65
|
-
) {
|
|
66
|
-
console.warn(
|
|
67
|
-
`[rango] clientUrls revalidate() for ${label} returned a Promise; ` +
|
|
68
|
-
`predicates must be synchronous (return a boolean, ` +
|
|
69
|
-
`{ defaultShouldRevalidate }, or null/undefined). The async result ` +
|
|
70
|
-
`was IGNORED and the default (${suggestion}) was kept.`,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
if (typeof verdict === "boolean") return verdict;
|
|
74
|
-
if (
|
|
75
|
-
verdict &&
|
|
76
|
-
typeof verdict === "object" &&
|
|
77
|
-
typeof verdict.defaultShouldRevalidate === "boolean"
|
|
78
|
-
) {
|
|
79
|
-
suggestion = verdict.defaultShouldRevalidate;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return suggestion;
|
|
83
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wire protocol for CLIENT-RUN per-loader revalidation decisions.
|
|
3
|
-
*
|
|
4
|
-
* clientUrls() revalidate() predicates are declared in a "use client" module,
|
|
5
|
-
* so they cannot cross the projection boundary as functions — instead they
|
|
6
|
-
* EXECUTE in the browser and only their DECISIONS cross: the browser attaches
|
|
7
|
-
* this header to partial-navigation and action requests, and the synthesized
|
|
8
|
-
* per-loader revalidate() on every materialized loader stub reads it back
|
|
9
|
-
* (see materializeRouteItems in server-projection.ts).
|
|
10
|
-
*
|
|
11
|
-
* Trust model: same class as `_rsc_segments` — a decision can only make the
|
|
12
|
-
* CLIENT's own view staler (skip) or fresher (force), and the synthesized
|
|
13
|
-
* predicates exist only on client-urls loader stubs, so the header can never
|
|
14
|
-
* influence server-tree loaders. Requests without the header (no-JS, PE,
|
|
15
|
-
* prefetch, document loads) fall back to the locked server defaults.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export const CLIENT_REVALIDATION_HEADER = "X-Rango-Client-Reval";
|
|
19
|
-
|
|
20
|
-
export interface ClientRevalidationDecisions {
|
|
21
|
-
/** Loader $$ids whose client predicate said false (keep held data). */
|
|
22
|
-
readonly skip: readonly string[];
|
|
23
|
-
/** Loader $$ids whose client predicate said true against a false default. */
|
|
24
|
-
readonly force: readonly string[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function encodeClientRevalidationDecisions(
|
|
28
|
-
decisions: ClientRevalidationDecisions,
|
|
29
|
-
): string | null {
|
|
30
|
-
if (decisions.skip.length === 0 && decisions.force.length === 0) return null;
|
|
31
|
-
// encodeURIComponent keeps the header value ISO-8859-1-safe regardless of
|
|
32
|
-
// what characters loader module ids contain.
|
|
33
|
-
return encodeURIComponent(
|
|
34
|
-
JSON.stringify({ skip: decisions.skip, force: decisions.force }),
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function decodeClientRevalidationDecisions(
|
|
39
|
-
headerValue: string | null,
|
|
40
|
-
): ClientRevalidationDecisions | null {
|
|
41
|
-
if (!headerValue) return null;
|
|
42
|
-
try {
|
|
43
|
-
const parsed = JSON.parse(decodeURIComponent(headerValue)) as {
|
|
44
|
-
skip?: unknown;
|
|
45
|
-
force?: unknown;
|
|
46
|
-
};
|
|
47
|
-
const toIds = (value: unknown): readonly string[] =>
|
|
48
|
-
Array.isArray(value)
|
|
49
|
-
? value.filter((entry): entry is string => typeof entry === "string")
|
|
50
|
-
: [];
|
|
51
|
-
return { skip: toIds(parsed.skip), force: toIds(parsed.force) };
|
|
52
|
-
} catch {
|
|
53
|
-
// A malformed header is treated as absent: locked defaults apply.
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|