@rangojs/router 0.22.1 → 0.23.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 +8 -3
- 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/navigation-transaction.d.ts +21 -7
- package/dist/types/browser/partial-update.d.ts +34 -5
- package/dist/types/browser/react/NavigationProvider.d.ts +13 -3
- 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 +19 -9
- 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 +16 -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/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/errors.d.ts +5 -0
- package/dist/types/handles/deferred-resolution.d.ts +2 -1
- package/dist/types/handles/pushed-by.d.ts +23 -0
- package/dist/types/loader-store.d.ts +8 -0
- package/dist/types/prefetch-markers.d.ts +11 -0
- package/dist/types/route-definition/helpers-types.d.ts +27 -0
- package/dist/types/router/is-action.d.ts +2 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +241 -0
- 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 +14 -1
- 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 +3 -3
- 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/run-loader.d.ts +2 -1
- 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 +38 -0
- package/dist/types/testing/vitest.d.ts +11 -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/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 +11 -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 +842 -308
- package/package.json +1 -1
- package/skills/caching/SKILL.md +42 -5
- package/skills/catalog.json +2 -2
- package/skills/client-urls/SKILL.md +113 -24
- package/skills/cloudflare/SKILL.md +14 -9
- package/skills/hooks/SKILL.md +11 -3
- package/skills/links/SKILL.md +24 -0
- package/skills/loader/SKILL.md +192 -1
- 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 +13 -0
- package/skills/prerender/SKILL.md +22 -8
- package/skills/rango/SKILL.md +12 -6
- package/skills/route/SKILL.md +139 -0
- package/skills/testing/SKILL.md +3 -2
- package/skills/testing/client-components.md +1 -1
- package/skills/testing/predicates.md +67 -32
- package/skills/testing/serve-shell-request.md +88 -18
- package/skills/testing/setup.md +2 -2
- package/skills/typesafety/route-types.md +2 -2
- package/skills/view-transitions/SKILL.md +26 -0
- package/src/browser/defer.ts +82 -0
- package/src/browser/document-revealed.ts +52 -0
- package/src/browser/event-controller.ts +43 -32
- package/src/browser/navigation-bridge.ts +64 -14
- 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 +528 -135
- package/src/browser/prefetch/fetch.ts +2 -1
- package/src/browser/react/NavigationProvider.tsx +47 -28
- package/src/browser/react/filter-segment-order.ts +13 -2
- package/src/browser/response-adapter.ts +20 -0
- package/src/browser/rsc-router.tsx +17 -45
- package/src/browser/server-action-bridge.ts +19 -63
- package/src/browser/types.ts +19 -9
- 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 +37 -10
- 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.ts +22 -1
- 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/errors.ts +15 -0
- package/src/handles/deferred-resolution.ts +28 -5
- package/src/handles/pushed-by.ts +122 -0
- package/src/loader-store.ts +18 -7
- package/src/prefetch-markers.ts +13 -0
- 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/router/intercept-resolution.ts +6 -4
- package/src/router/is-action.ts +2 -1
- package/src/router/loader-resolution.ts +83 -4
- package/src/router/match-api.ts +36 -1
- package/src/router/match-middleware/background-revalidation.ts +11 -1
- package/src/router/match-middleware/cache-lookup.ts +29 -6
- package/src/router/match-middleware/cache-store.ts +17 -1
- package/src/router/match-result.ts +14 -2
- package/src/router/navigation-snapshot.ts +2 -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/prefetch-deferral.ts +543 -0
- package/src/router/segment-resolution/revalidation.ts +169 -60
- package/src/rsc/rsc-rendering.ts +50 -7
- package/src/rsc/shell-capture.ts +2 -1
- 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/handle-store.ts +104 -8
- package/src/server/request-context.ts +21 -1
- package/src/server.ts +4 -0
- package/src/testing/action-form.ts +2 -11
- package/src/testing/e2e/page-helpers.ts +3 -1
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +6 -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/run-loader.ts +2 -1
- package/src/testing/run-revalidate.ts +142 -0
- package/src/testing/run-transition-when.ts +7 -8
- package/src/testing/serve-shell-request.ts +116 -6
- package/src/testing/vitest.ts +67 -1
- 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/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 +20 -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/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 +93 -32
- 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,6 +51,7 @@ 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,
|
|
54
56
|
VIRTUAL_ROUTES_MANIFEST_ID,
|
|
55
57
|
type DiscoveryState,
|
|
@@ -75,6 +77,7 @@ import {
|
|
|
75
77
|
import { describeDiscoveryFailure } from "./discovery/discovery-errors.js";
|
|
76
78
|
import { findTransitionWhenError } from "../transition-when-ref.js";
|
|
77
79
|
import { transitionWhenHoistPlugin } from "./plugins/transition-when-hoist.js";
|
|
80
|
+
import { clientUrlsRevalidateHoistPlugin } from "./plugins/client-urls-revalidate-hoist.js";
|
|
78
81
|
import {
|
|
79
82
|
createDevPrerenderCache,
|
|
80
83
|
devPrerenderCacheKey,
|
|
@@ -106,6 +109,35 @@ const debugDev = createRangoDebugger(NS.dev);
|
|
|
106
109
|
|
|
107
110
|
export { VIRTUAL_ROUTES_MANIFEST_ID };
|
|
108
111
|
|
|
112
|
+
/** The slice of a Vite environment module graph the route-file watcher reads. */
|
|
113
|
+
interface ModulesByFileGraph {
|
|
114
|
+
getModulesByFile(file: string): ReadonlySet<unknown> | undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Modules a Vite graph holds for `file`; Vite keys its file index by normalized path. */
|
|
118
|
+
function modulesForFile(
|
|
119
|
+
graph: ModulesByFileGraph,
|
|
120
|
+
file: string,
|
|
121
|
+
): ReadonlySet<unknown> | undefined {
|
|
122
|
+
return graph.getModulesByFile(normalizePath(file));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Whether the dev route-file watcher ignores `file`: it sits in a directory
|
|
127
|
+
* every discovery scan skips (isInSkippedScanDir) and the RSC graph has not
|
|
128
|
+
* loaded it. See docs/manifests.md, "A reload no file of the app asked for".
|
|
129
|
+
*/
|
|
130
|
+
export function isFileOutsideApp(
|
|
131
|
+
root: string,
|
|
132
|
+
file: string,
|
|
133
|
+
rscGraph: ModulesByFileGraph | undefined,
|
|
134
|
+
): boolean {
|
|
135
|
+
return (
|
|
136
|
+
isInSkippedScanDir(root, file) &&
|
|
137
|
+
!(rscGraph && modulesForFile(rscGraph, file)?.size)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
109
141
|
// ============================================================================
|
|
110
142
|
// Node ESM Loader Hook Registration
|
|
111
143
|
// ============================================================================
|
|
@@ -299,6 +331,10 @@ export async function createTempRscServer(
|
|
|
299
331
|
// Inline transition({ when }) literals become client references here
|
|
300
332
|
// too, or discovery would see (and reject) the plain function.
|
|
301
333
|
transitionWhenHoistPlugin(),
|
|
334
|
+
// clientUrls() revalidate() predicates move to the server here too:
|
|
335
|
+
// discovery serializes the SSR copy's markers and the RSC realm
|
|
336
|
+
// registers the predicates the materialized stubs call.
|
|
337
|
+
...clientUrlsRevalidateHoistPlugin(),
|
|
302
338
|
// Dev prerender must use dev-mode IDs (path-based) to match the workerd
|
|
303
339
|
// runtime. forceBuild produces hashed IDs for production bundle consistency.
|
|
304
340
|
exposeInternalIds(options.forceBuild ? { forceBuild: true } : undefined),
|
|
@@ -1066,20 +1102,17 @@ export function createRouterDiscoveryPlugin(
|
|
|
1066
1102
|
// Discover routers FIRST, then arm the manifest-readiness gate on the
|
|
1067
1103
|
// server module discovery actually read the registry from.
|
|
1068
1104
|
//
|
|
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
|
|
1105
|
+
// We deliberately do NOT pre-import "@rangojs/router/server" here to
|
|
1106
|
+
// arm the gate early. During a Vite dependency re-optimization (dev
|
|
1107
|
+
// boot after a lockfile change, or `vite dev --force`) or any runner
|
|
1108
|
+
// full reload, a module imported before the entry can be a stale copy
|
|
1109
|
+
// of the runner's module graph: createRouter() populates RouterRegistry
|
|
1110
|
+
// on the fresh copy and the stale one reads an empty Map, a spurious
|
|
1111
|
+
// "No routers found" (#637). discoverRouters returns the instance it
|
|
1112
|
+
// read the registry from and verified the runner still serves (it
|
|
1113
|
+
// redoes a pass that a reload split). The virtual manifest module's
|
|
1114
|
+
// own gate (s.discoveryDone, armed by beginDiscoveryGate) already
|
|
1115
|
+
// blocks early requests during discovery on the Node path, so arming
|
|
1083
1116
|
// manifestReadyPromise after discovery is sufficient here.
|
|
1084
1117
|
const serverMod = await timed(debugDiscovery, "discoverRouters", () =>
|
|
1085
1118
|
discoverRouters(s, rscEnv, (server.environments as any)?.ssr),
|
|
@@ -1105,6 +1138,12 @@ export function createRouterDiscoveryPlugin(
|
|
|
1105
1138
|
propagateDiscoveryState(rscEnv),
|
|
1106
1139
|
);
|
|
1107
1140
|
} catch (err: any) {
|
|
1141
|
+
// Recovery mode: the next in-scan source change re-runs discovery
|
|
1142
|
+
// (canRefreshRuntimeDiscovery) instead of waiting for a restart.
|
|
1143
|
+
s.lastDiscoveryError = {
|
|
1144
|
+
message: err?.message ?? String(err),
|
|
1145
|
+
at: Date.now(),
|
|
1146
|
+
};
|
|
1108
1147
|
emitDiscoveryFailure(
|
|
1109
1148
|
err,
|
|
1110
1149
|
optimizerHashBefore,
|
|
@@ -1630,6 +1669,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
1630
1669
|
(filePath.includes("named-routes.gen.ts") ||
|
|
1631
1670
|
filePath.includes("urls.gen.ts"));
|
|
1632
1671
|
|
|
1672
|
+
// Files no discovery scan reaches are ignored unless the RSC graph
|
|
1673
|
+
// loaded them (docs/manifests.md, "A reload no file of the app asked for").
|
|
1674
|
+
const isOutsideApp = (filePath: string): boolean =>
|
|
1675
|
+
isFileOutsideApp(
|
|
1676
|
+
s.projectRoot,
|
|
1677
|
+
filePath,
|
|
1678
|
+
(server.environments as any)?.rsc?.moduleGraph,
|
|
1679
|
+
);
|
|
1680
|
+
|
|
1633
1681
|
const regenerateGeneratedRouteFiles = () => {
|
|
1634
1682
|
if (s.perRouterManifests.length > 0) {
|
|
1635
1683
|
writeRouteTypesFiles(s);
|
|
@@ -1676,6 +1724,18 @@ export function createRouterDiscoveryPlugin(
|
|
|
1676
1724
|
// only the expensive regeneration is debounced.
|
|
1677
1725
|
let routeChangeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1678
1726
|
|
|
1727
|
+
// Drop the routes-manifest virtual module's cached transform so the
|
|
1728
|
+
// next runner evaluation regenerates it from current state.
|
|
1729
|
+
const invalidateRoutesManifestModule = (rscEnv: any) => {
|
|
1730
|
+
for (const virtualId of [
|
|
1731
|
+
VIRTUAL_ROUTES_MANIFEST_ID,
|
|
1732
|
+
`\0${VIRTUAL_ROUTES_MANIFEST_ID}`,
|
|
1733
|
+
]) {
|
|
1734
|
+
const virtualMod = rscEnv.moduleGraph?.getModuleById?.(virtualId);
|
|
1735
|
+
if (virtualMod) rscEnv.moduleGraph.invalidateModule(virtualMod);
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1679
1739
|
// Re-run runtime discovery so factory-generated routes that the
|
|
1680
1740
|
// static parser cannot see are refreshed after source changes.
|
|
1681
1741
|
// The state-machine concerns (queued/pending/gatePending) are
|
|
@@ -1696,6 +1756,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
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,6 +1766,9 @@ 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
1773
|
// Cloudflare HMR: invalidate the temp server's RSC module
|
|
1710
1774
|
// graph (or close+recreate as a fallback) so the runner
|
|
@@ -1924,16 +1988,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1924
1988
|
// stale replay after propagateDiscoveryState would clobber
|
|
1925
1989
|
// the refreshed registry as the realm's last write and the
|
|
1926
1990
|
// 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
|
-
}
|
|
1991
|
+
invalidateRoutesManifestModule(rscEnv);
|
|
1937
1992
|
} catch (err: any) {
|
|
1938
1993
|
debugDiscovery?.(
|
|
1939
1994
|
"watcher: clientUrls projection pre-refresh failed: %s",
|
|
@@ -1970,7 +2025,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1970
2025
|
// Async: re-run runtime discovery to refresh factory-generated
|
|
1971
2026
|
// routes that the static parser cannot resolve. Resolves the
|
|
1972
2027
|
// discovery gate when complete.
|
|
1973
|
-
if (s
|
|
2028
|
+
if (canRefreshRuntimeDiscovery(s, !!rscEnv?.runner)) {
|
|
1974
2029
|
// The cloudflare workerd reload fires inside refreshRuntimeDiscovery
|
|
1975
2030
|
// on the successful cycle (see forceCloudflareWorkerReload call
|
|
1976
2031
|
// there) so queued follow-up cycles also trigger it.
|
|
@@ -1987,6 +2042,13 @@ export function createRouterDiscoveryPlugin(
|
|
|
1987
2042
|
};
|
|
1988
2043
|
|
|
1989
2044
|
const handleRouteFileChange = (filePath: string) => {
|
|
2045
|
+
if (isOutsideApp(filePath)) {
|
|
2046
|
+
debugDiscovery?.(
|
|
2047
|
+
"watcher: skip %s (outside the discovery scan)",
|
|
2048
|
+
filePath,
|
|
2049
|
+
);
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
1990
2052
|
if (maybeHandleGeneratedRouteFileMutation(filePath)) return;
|
|
1991
2053
|
if (
|
|
1992
2054
|
!filePath.endsWith(".ts") &&
|
|
@@ -2124,9 +2186,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
2124
2186
|
}
|
|
2125
2187
|
const seen = new Set<any>();
|
|
2126
2188
|
for (const file of routerSourceFiles) {
|
|
2127
|
-
const mods = rscGraph
|
|
2128
|
-
file.replaceAll("\\", "/"),
|
|
2129
|
-
);
|
|
2189
|
+
const mods = modulesForFile(rscGraph, file);
|
|
2130
2190
|
if (!mods) {
|
|
2131
2191
|
debugDiscovery?.(
|
|
2132
2192
|
"watcher: clientUrls invalidation found no rsc modules for %s",
|
|
@@ -2166,8 +2226,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
2166
2226
|
// and resets `discoveryDone` to a fresh pending promise (so
|
|
2167
2227
|
// workerd reloads triggered by the same source change can't
|
|
2168
2228
|
// observe a stale resolved gate from cold-start). Resolved
|
|
2169
|
-
// by the trailing refreshRuntimeDiscovery() cycle
|
|
2170
|
-
|
|
2229
|
+
// by the trailing refreshRuntimeDiscovery() cycle, which runs
|
|
2230
|
+
// under the same predicate.
|
|
2231
|
+
if (canRefreshRuntimeDiscovery(s, !!mainRscEnv?.runner)) {
|
|
2171
2232
|
gate.noteRouteEvent();
|
|
2172
2233
|
}
|
|
2173
2234
|
scheduleRouteRegeneration();
|
|
@@ -2194,7 +2255,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
2194
2255
|
// Same no-runner guard as change/add: stale perRouterManifests would
|
|
2195
2256
|
// reintroduce reverted content.
|
|
2196
2257
|
server.watcher.on("unlink", (filePath) => {
|
|
2197
|
-
if (!isGeneratedRouteFile(filePath)) return;
|
|
2258
|
+
if (!isGeneratedRouteFile(filePath) || isOutsideApp(filePath)) return;
|
|
2198
2259
|
const hasRunner = !!(server.environments as any)?.rsc?.runner;
|
|
2199
2260
|
if (!hasRunner) return;
|
|
2200
2261
|
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
|
-
}
|
|
@@ -1,92 +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
|
-
|
|
13
|
-
import { makeIsAction } from "../router/is-action.js";
|
|
14
|
-
import { resolveTestActionId } from "./internal/action-id.js";
|
|
15
|
-
import {
|
|
16
|
-
lockedClientDefault,
|
|
17
|
-
runClientRevalidateChain,
|
|
18
|
-
} from "../client-urls/revalidate-chain.js";
|
|
19
|
-
import { toURL } from "./to-url.js";
|
|
20
|
-
import type {
|
|
21
|
-
ClientRevalidateArgs,
|
|
22
|
-
ClientRevalidateFn,
|
|
23
|
-
} from "../client-urls/types.js";
|
|
24
|
-
|
|
25
|
-
const DEFAULT_URL = "http://localhost/";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Options for {@link runClientRevalidate}. Defaults model a same-URL
|
|
29
|
-
* navigation with no action (locked default `false`).
|
|
30
|
-
*/
|
|
31
|
-
export interface RunClientRevalidateOptions {
|
|
32
|
-
currentUrl?: string | URL;
|
|
33
|
-
nextUrl?: string | URL;
|
|
34
|
-
currentParams?: Record<string, string>;
|
|
35
|
-
nextParams?: Record<string, string>;
|
|
36
|
-
stale?: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* The triggering action: a single imported reference (id resolved via
|
|
39
|
-
* `$id ?? $$id`; throws if the function carries neither) or a raw actionId
|
|
40
|
-
* string. A namespace/object is rejected — it cannot identify the ONE
|
|
41
|
-
* action that triggered the request. Omit for a plain navigation.
|
|
42
|
-
*/
|
|
43
|
-
action?: ((...args: never[]) => unknown) | string;
|
|
44
|
-
/**
|
|
45
|
-
* Model an action-triggered refetch GET: predicates see `isAction()` as
|
|
46
|
-
* true, but the locked default stays the navigation default, matching how
|
|
47
|
-
* the server evaluates that request (no actionContext). Defaults to
|
|
48
|
-
* treating a provided `action` as the action POST itself.
|
|
49
|
-
*/
|
|
50
|
-
actionRequest?: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Run one clientUrls `revalidate()` predicate — or a chain, in declaration
|
|
55
|
-
* order — against production-built args. Returns the final boolean decision
|
|
56
|
-
* (locked default if every predicate defers or throws).
|
|
57
|
-
*/
|
|
58
|
-
export function runClientRevalidate(
|
|
59
|
-
fn: ClientRevalidateFn | readonly ClientRevalidateFn[],
|
|
60
|
-
opts: RunClientRevalidateOptions = {},
|
|
61
|
-
): boolean {
|
|
62
|
-
const currentUrl = toURL(opts.currentUrl, new URL(DEFAULT_URL));
|
|
63
|
-
const nextUrl = toURL(opts.nextUrl, currentUrl);
|
|
64
|
-
const currentParams = opts.currentParams ?? {};
|
|
65
|
-
const nextParams = opts.nextParams ?? currentParams;
|
|
66
|
-
const inAction = opts.action !== undefined;
|
|
67
|
-
const actionId = resolveTestActionId(opts.action, "runClientRevalidate");
|
|
68
|
-
const defaultShouldRevalidate = lockedClientDefault({
|
|
69
|
-
actionRequest: opts.actionRequest ?? inAction,
|
|
70
|
-
currentParams,
|
|
71
|
-
nextParams,
|
|
72
|
-
currentUrl,
|
|
73
|
-
nextUrl,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const baseArgs: Omit<ClientRevalidateArgs, "defaultShouldRevalidate"> = {
|
|
77
|
-
currentUrl,
|
|
78
|
-
nextUrl,
|
|
79
|
-
currentParams,
|
|
80
|
-
nextParams,
|
|
81
|
-
stale: opts.stale ?? false,
|
|
82
|
-
isAction: makeIsAction(actionId, inAction),
|
|
83
|
-
...(actionId !== undefined ? { actionId } : {}),
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return runClientRevalidateChain(
|
|
87
|
-
Array.isArray(fn) ? fn : [fn],
|
|
88
|
-
baseArgs,
|
|
89
|
-
defaultShouldRevalidate,
|
|
90
|
-
"runClientRevalidate predicate",
|
|
91
|
-
);
|
|
92
|
-
}
|