clear-react-router 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -6
- package/dist/components/Router.d.ts +1 -1
- package/dist/config/routerConfig.d.ts +1 -0
- package/dist/index.js +60 -21
- package/dist/types.d.ts +12 -4
- package/dist/utils/revalidateCache.d.ts +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,17 +22,17 @@ It provides first-class support for:
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
24
|
- **Nested Routes** - Organize your UI with nested layouts and routes
|
|
25
|
-
- **Data Loading** - Built-in loaders with caching
|
|
25
|
+
- **Data Loading** - Built-in loaders with TTL-based caching (`staleTime`)
|
|
26
26
|
- **Navigation Blocking** - Prevent accidental navigation with `useBlocker`
|
|
27
27
|
- **Smooth Animations** - Page transitions with fade effect (customizable duration)
|
|
28
28
|
- **Static Layout** — Keep navbar, footer, and other elements outside the router to avoid unnecessary re-renders
|
|
29
29
|
- **Programmatic Redirects** - Redirect from beforeLoad hook
|
|
30
30
|
- **Cache invalidation** - Manual route invalidation
|
|
31
|
+
- **Bounded Cache** - Automatically evicts least recently used entries once `maxCacheSize` is reached, keeping memory usage predictable in long sessions
|
|
31
32
|
- **Prefetching** - Preload data on hover for instant navigation
|
|
32
33
|
- **Lazy Loading** - Code-split your routes with dynamic imports for optimal performance
|
|
33
34
|
- **Scroll Restoration** — Automatically saves and restores scroll position when navigating back to a page (preserves user's scroll position)
|
|
34
35
|
- **Optimistic navigation** — Instantly renders stale cached data while fresh data is loaded in the background.
|
|
35
|
-
- **Flexible API** - Use components or hooks as you prefer
|
|
36
36
|
- **Browser History** - Full support for browser back/forward buttons
|
|
37
37
|
- **Context-aware** - Pass and update context through routes
|
|
38
38
|
|
|
@@ -43,6 +43,7 @@ It provides first-class support for:
|
|
|
43
43
|
| Prop | Type | Default | Description |
|
|
44
44
|
|------|------|---------|-------------|
|
|
45
45
|
| `routes` | `RouteItem[]` | required | Array of route configurations |
|
|
46
|
+
| `maxCacheSize` | `number \| undefined` | 60 for mobile, 150 for desktop | Maximum number of cached loader entries. Once the limit is reached, the least recently used entries are evicted |
|
|
46
47
|
| `isAnimated` | `boolean \| undefined` | `false` | Enable smooth page fade transitions |
|
|
47
48
|
| `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
|
|
48
49
|
| `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
|
|
@@ -79,7 +80,7 @@ Normalizes route configuration. Extracts dynamic params, builds nested paths.
|
|
|
79
80
|
| `path` | `string` | Route path, e.g., `/user/:userId` |
|
|
80
81
|
| `element` | `ReactElement \| () => ReactElement \| LazyComponent` | Component to render |
|
|
81
82
|
| `beforeLoad` | `({ params, context, redirect, setContext }) => Promise<unknown> \| undefined \| void` | Runs before every route navigation. Auth checks and redirects. Can update context via `setContext`. `redirect` is provided by the router |
|
|
82
|
-
| `loader` | `({ params, context, setContext, searchParams }) => Promise<unknown>` | Fetch data using route params, search params, and context. Can update context via `setContext` |
|
|
83
|
+
| `loader` | `({ params, context, setContext, searchParams, signal }) => Promise<unknown>` | Fetch data using route params, search params, abort controller signal and context. Can update context via `setContext` |
|
|
83
84
|
| `afterLoad` | `({ params, context, setContext }) => Promise<void>` | Runs after a successful navigation once the route has finished loading. Analytics, side effects after data is loaded. Can update context via `setContext` |
|
|
84
85
|
| `fallback` | `ReactElement \| () => ReactElement` | Loading fallback (for lazy loading) |
|
|
85
86
|
| `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback for the route's `loader`. Overrides the global `defaultLoaderFallback` set in `Router` |
|
|
@@ -98,6 +99,7 @@ Loader arguments:
|
|
|
98
99
|
context: Record<string, unknown>; // Router context
|
|
99
100
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>; // Updates the router context
|
|
100
101
|
searchParams: Record<string, string>; // URL search parameters
|
|
102
|
+
signal: AbortSignal; // AbortController signal
|
|
101
103
|
}
|
|
102
104
|
```
|
|
103
105
|
|
|
@@ -563,16 +565,24 @@ const UserProfile = () => {
|
|
|
563
565
|
```
|
|
564
566
|
|
|
565
567
|
### Caching behavior:
|
|
568
|
+
|
|
566
569
|
- The loader result is cached and reused when navigating back to the same route (e.g., from /user/123 back to /user/456 it will be a new request because different params, but from /user/456 to /user/456 — cache hit).
|
|
567
|
-
- Use staleTime in route config to control how long cache is considered fresh:
|
|
568
|
-
|
|
570
|
+
- Use `staleTime` in route config to control how long cache is considered fresh:
|
|
571
|
+
|
|
572
|
+
```tsx
|
|
569
573
|
{
|
|
570
574
|
path: '/user/:userId',
|
|
571
575
|
loader: async ({ params }) => fetchUser(params.userId),
|
|
572
576
|
staleTime: 60000, // 1 minute — cache is fresh for 60 seconds
|
|
573
577
|
}
|
|
574
578
|
```
|
|
575
|
-
|
|
579
|
+
|
|
580
|
+
- Stale entries are cleaned up on every navigation, so cache growth stays tied to how often you actually revisit stale data — not to how long the session lasts.
|
|
581
|
+
- On top of that, the cache is bounded by `maxCacheSize` — once the limit is reached, the least recently used entry is evicted to make room for a new one, regardless of whether it's still fresh. This caps memory usage for apps with many high-cardinality dynamic routes (e.g. `/product/:id` across a large catalog). It defaults to a device-aware value (lower on mobile) and can be overridden on the `Router`:
|
|
582
|
+
|
|
583
|
+
```tsx
|
|
584
|
+
<Router routes={routes} maxCacheSize={200} />
|
|
585
|
+
```
|
|
576
586
|
|
|
577
587
|
|
|
578
588
|
### `useInvalidate()`
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types';
|
|
2
|
-
export declare const Router: ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated, spinner, defaultPreserveScroll, showFallbackOnAnimation, defaultPrefetch, defaultHoverPrefetchDelay, errorBoundary: ErrorBoundary, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
2
|
+
export declare const Router: ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated, spinner, defaultPreserveScroll, showFallbackOnAnimation, maxCacheSize, defaultPrefetch, defaultHoverPrefetchDelay, errorBoundary: ErrorBoundary, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ClientRouteItem, RouterProps } from '../types';
|
|
2
2
|
declare class RouterConfig {
|
|
3
3
|
routes: RouterProps['routes'];
|
|
4
|
+
maxCacheSize: number;
|
|
4
5
|
defaultPrefetch: RouterProps['defaultPrefetch'];
|
|
5
6
|
isAnimated: RouterProps['isAnimated'];
|
|
6
7
|
defaultHoverPrefetchDelay: number;
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,7 @@ function _defineProperty(e, r, t) {
|
|
|
86
86
|
var RouterConfig = class {
|
|
87
87
|
constructor() {
|
|
88
88
|
_defineProperty(this, "routes", []);
|
|
89
|
+
_defineProperty(this, "maxCacheSize", 0);
|
|
89
90
|
_defineProperty(this, "defaultPrefetch", "hover");
|
|
90
91
|
_defineProperty(this, "isAnimated", false);
|
|
91
92
|
_defineProperty(this, "defaultHoverPrefetchDelay", 150);
|
|
@@ -220,12 +221,18 @@ var findRoute = (pathname, includeAll) => {
|
|
|
220
221
|
};
|
|
221
222
|
//#endregion
|
|
222
223
|
//#region runtime/navigate.ts
|
|
223
|
-
var navigationSeq = 0;
|
|
224
|
-
var interval = 0;
|
|
225
224
|
var createNavigate = (routerState, revalidateCache) => {
|
|
225
|
+
let navigationSeq = 0;
|
|
226
|
+
let interval = 0;
|
|
227
|
+
let abortController = null;
|
|
226
228
|
const { loaderStateRef, scrollMapState, pendingState, contextState, loaderMap, routeItemDataState } = routerState;
|
|
227
229
|
const commitNavigation = createCommitNavigation(createCommitState(routerState), routeItemDataState);
|
|
228
230
|
const isCacheItemFresh = createIsCacheItemFresh(loaderMap);
|
|
231
|
+
const createSignal = () => {
|
|
232
|
+
abortController?.abort();
|
|
233
|
+
abortController = new AbortController();
|
|
234
|
+
return abortController.signal;
|
|
235
|
+
};
|
|
229
236
|
const getContext = () => ({
|
|
230
237
|
context: contextState.getState(),
|
|
231
238
|
setContext: contextState.setState
|
|
@@ -290,20 +297,25 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
290
297
|
};
|
|
291
298
|
const polling = (routeItem, location) => {
|
|
292
299
|
if (!routeItem?.pollingInterval) return;
|
|
300
|
+
const signal = createSignal();
|
|
293
301
|
interval = window.setInterval(() => revalidateCache({
|
|
294
302
|
routeItem,
|
|
295
303
|
pathname: location.pathname,
|
|
296
|
-
search: location.search
|
|
304
|
+
search: location.search,
|
|
305
|
+
signal
|
|
297
306
|
}), routeItem.pollingInterval);
|
|
298
307
|
};
|
|
299
|
-
const loader = async (routeItem, location) => {
|
|
308
|
+
const loader = async (routeItem, location, seq) => {
|
|
300
309
|
if (!routeItem?.loader) return;
|
|
301
310
|
window.clearInterval(interval);
|
|
311
|
+
const signal = createSignal();
|
|
302
312
|
await revalidateCache({
|
|
303
313
|
routeItem,
|
|
304
314
|
pathname: location.pathname,
|
|
305
|
-
search: location.search
|
|
315
|
+
search: location.search,
|
|
316
|
+
signal
|
|
306
317
|
});
|
|
318
|
+
if (seq !== navigationSeq) return;
|
|
307
319
|
polling(routeItem, location);
|
|
308
320
|
};
|
|
309
321
|
const afterLoad = async (routeItem, params) => {
|
|
@@ -324,7 +336,7 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
324
336
|
await beforeLoad(nextItem, params);
|
|
325
337
|
if (seq !== navigationSeq) return;
|
|
326
338
|
prepareNavigation(nextItem, nextLocation);
|
|
327
|
-
await loader(nextItem, nextLocation);
|
|
339
|
+
await loader(nextItem, nextLocation, seq);
|
|
328
340
|
if (seq !== navigationSeq) return;
|
|
329
341
|
commitNavigation(nextLocation, nextItem);
|
|
330
342
|
await afterLoad(nextItem, params);
|
|
@@ -333,7 +345,7 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
333
345
|
};
|
|
334
346
|
//#endregion
|
|
335
347
|
//#region runtime/invalidate.ts
|
|
336
|
-
var redirect =
|
|
348
|
+
var redirect = Promise.resolve;
|
|
337
349
|
var createInvalidate = ({ routeItemDataState, loaderStateRef, loaderMap, currentLoaderState, contextState }, revalidateCache) => {
|
|
338
350
|
const invalidatePath = async (routeItem, pathname, options) => {
|
|
339
351
|
const routePathname = routeItemDataState.getState().location.pathname;
|
|
@@ -400,7 +412,6 @@ var createPrefetch = (revalidateCache) => async (pathname) => {
|
|
|
400
412
|
};
|
|
401
413
|
//#endregion
|
|
402
414
|
//#region utils/revalidateCache.ts
|
|
403
|
-
var loadingPromises = /* @__PURE__ */ new Map();
|
|
404
415
|
var isObjectRetry = (arg) => typeof arg === "object";
|
|
405
416
|
var createRetry = (arg) => {
|
|
406
417
|
if (arg === void 0) return null;
|
|
@@ -420,27 +431,44 @@ var getRetry = (routeItem) => {
|
|
|
420
431
|
};
|
|
421
432
|
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
422
433
|
var createRevalidateCache = (routerState) => {
|
|
423
|
-
const { loaderStateRef, contextState, loaderMap } = routerState;
|
|
424
|
-
const
|
|
425
|
-
const
|
|
434
|
+
const { loaderStateRef, contextState, loaderMap, loadingPromises } = routerState;
|
|
435
|
+
const removeStaleItems = () => {
|
|
436
|
+
const deletedItems = [...loaderMap.entries()].filter(([, item]) => {
|
|
426
437
|
const staleTime = item.staleTime ?? routerConfig.defaultStaleTime;
|
|
427
438
|
return staleTime && staleTime + item.timestamp < Date.now();
|
|
428
439
|
});
|
|
429
|
-
if (
|
|
440
|
+
if (deletedItems.length) deletedItems.forEach((item) => loaderMap.delete(item[0]));
|
|
441
|
+
};
|
|
442
|
+
const evict = () => {
|
|
443
|
+
if (loaderMap.size <= routerConfig.maxCacheSize) return;
|
|
444
|
+
const oldestKey = loaderMap.keys().next().value;
|
|
445
|
+
if (oldestKey) loaderMap.delete(oldestKey);
|
|
446
|
+
};
|
|
447
|
+
const moveItemToLastPosition = (path) => {
|
|
448
|
+
const item = loaderMap.get(path);
|
|
449
|
+
if (item) {
|
|
450
|
+
loaderMap.delete(path);
|
|
451
|
+
loaderMap.set(path, item);
|
|
452
|
+
}
|
|
453
|
+
return item;
|
|
430
454
|
};
|
|
431
|
-
const revalidateCache = async ({ routeItem, pathname, search = "" }, retried = 0) => {
|
|
455
|
+
const revalidateCache = async ({ routeItem, pathname, search = "", signal }, retried = 0) => {
|
|
432
456
|
if (!routeItem?.loader) return;
|
|
433
457
|
const isCacheItemFresh = createIsCacheItemFresh(loaderMap);
|
|
434
|
-
|
|
458
|
+
removeStaleItems();
|
|
435
459
|
const path = `${pathname}${search}`;
|
|
436
|
-
if (loadingPromises.has(path))
|
|
460
|
+
if (loadingPromises.has(path)) {
|
|
461
|
+
moveItemToLastPosition(path);
|
|
462
|
+
return loadingPromises.get(path);
|
|
463
|
+
}
|
|
437
464
|
if (isCacheItemFresh(path)) {
|
|
438
|
-
const item =
|
|
465
|
+
const item = moveItemToLastPosition(path);
|
|
439
466
|
if (item?.state) loaderStateRef.set(item.state);
|
|
440
467
|
return;
|
|
441
468
|
}
|
|
442
469
|
const promise = (async () => {
|
|
443
470
|
if (!routeItem?.loader) return;
|
|
471
|
+
const effectiveSignal = signal ?? new AbortController().signal;
|
|
444
472
|
try {
|
|
445
473
|
const context = contextState.getState();
|
|
446
474
|
const setContext = contextState.setState;
|
|
@@ -450,7 +478,8 @@ var createRevalidateCache = (routerState) => {
|
|
|
450
478
|
params,
|
|
451
479
|
context,
|
|
452
480
|
setContext,
|
|
453
|
-
searchParams
|
|
481
|
+
searchParams,
|
|
482
|
+
signal: effectiveSignal
|
|
454
483
|
});
|
|
455
484
|
loaderStateRef.set((prev) => ({
|
|
456
485
|
...prev,
|
|
@@ -462,11 +491,16 @@ var createRevalidateCache = (routerState) => {
|
|
|
462
491
|
timestamp: Date.now(),
|
|
463
492
|
staleTime: routeItem.staleTime
|
|
464
493
|
});
|
|
494
|
+
evict();
|
|
465
495
|
return {
|
|
466
496
|
data: result,
|
|
467
497
|
error: null
|
|
468
498
|
};
|
|
469
499
|
} catch (error) {
|
|
500
|
+
if (effectiveSignal.aborted) return {
|
|
501
|
+
data: null,
|
|
502
|
+
error: null
|
|
503
|
+
};
|
|
470
504
|
const retry = getRetry(routeItem);
|
|
471
505
|
if (retry && retry.count > retried) {
|
|
472
506
|
loadingPromises.delete(path);
|
|
@@ -474,7 +508,8 @@ var createRevalidateCache = (routerState) => {
|
|
|
474
508
|
await revalidateCache({
|
|
475
509
|
routeItem,
|
|
476
510
|
pathname,
|
|
477
|
-
search
|
|
511
|
+
search,
|
|
512
|
+
signal
|
|
478
513
|
}, retried + 1);
|
|
479
514
|
return {
|
|
480
515
|
data: null,
|
|
@@ -531,7 +566,8 @@ var createRouterInstance = () => {
|
|
|
531
566
|
to: ""
|
|
532
567
|
}),
|
|
533
568
|
loaderStateRef: new Cell(emptyLoaderState),
|
|
534
|
-
loaderMap: /* @__PURE__ */ new Map()
|
|
569
|
+
loaderMap: /* @__PURE__ */ new Map(),
|
|
570
|
+
loadingPromises: /* @__PURE__ */ new Map()
|
|
535
571
|
};
|
|
536
572
|
const revalidateCache = createRevalidateCache(routerState);
|
|
537
573
|
const invalidate = createInvalidate(routerState, revalidateCache);
|
|
@@ -719,7 +755,9 @@ var renderElement = (Component) => {
|
|
|
719
755
|
//#region components/Router.tsx
|
|
720
756
|
var EmptyBoundary = ({ children }) => children;
|
|
721
757
|
var IS_MOBILE = isMobile();
|
|
722
|
-
var
|
|
758
|
+
var MOBILE_CACHE_SIZE = 60;
|
|
759
|
+
var DESKTOP_CACHE_SIZE = 150;
|
|
760
|
+
var Router = ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated = false, spinner = true, defaultPreserveScroll = true, showFallbackOnAnimation = false, maxCacheSize = IS_MOBILE ? MOBILE_CACHE_SIZE : DESKTOP_CACHE_SIZE, defaultPrefetch = IS_MOBILE ? "viewport" : "hover", defaultHoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary }) => {
|
|
723
761
|
const { useRouteItemData, usePendingState } = router.hooks;
|
|
724
762
|
const [routeItemData] = useRouteItemData();
|
|
725
763
|
const [pendingState] = usePendingState();
|
|
@@ -735,7 +773,8 @@ var Router = ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration,
|
|
|
735
773
|
defaultAfterLoad,
|
|
736
774
|
defaultRetry,
|
|
737
775
|
defaultStaleTime,
|
|
738
|
-
defaultPreserveScroll
|
|
776
|
+
defaultPreserveScroll,
|
|
777
|
+
maxCacheSize
|
|
739
778
|
});
|
|
740
779
|
useApplyCustomAnimation(animationDuration);
|
|
741
780
|
useSetInitialContext(initialContext);
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type ClientRouteItem = {
|
|
|
23
23
|
context: Record<string, unknown>;
|
|
24
24
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
25
25
|
searchParams: Record<string, string>;
|
|
26
|
+
signal: AbortSignal;
|
|
26
27
|
}): Promise<unknown>;
|
|
27
28
|
loaderFallback?: RenderElement;
|
|
28
29
|
errorElement?: RenderElement;
|
|
@@ -64,6 +65,7 @@ export type RevalidateCacheArgs = {
|
|
|
64
65
|
pathname: string;
|
|
65
66
|
routeItem?: RouteItem;
|
|
66
67
|
search?: string;
|
|
68
|
+
signal?: AbortSignal;
|
|
67
69
|
};
|
|
68
70
|
export type LoaderState<T = unknown> = {
|
|
69
71
|
data: T;
|
|
@@ -92,6 +94,7 @@ export type RouterProps = {
|
|
|
92
94
|
showFallbackOnAnimation?: boolean;
|
|
93
95
|
defaultPrefetch?: 'hover' | 'render' | 'viewport' | 'none';
|
|
94
96
|
defaultHoverPrefetchDelay?: number;
|
|
97
|
+
maxCacheSize?: number;
|
|
95
98
|
errorBoundary?: ComponentType<{
|
|
96
99
|
children: ReactNode;
|
|
97
100
|
}>;
|
|
@@ -104,6 +107,13 @@ export type LoaderStateItem = {
|
|
|
104
107
|
timestamp: number;
|
|
105
108
|
staleTime: number | undefined;
|
|
106
109
|
};
|
|
110
|
+
export type LoadingPromise = Promise<{
|
|
111
|
+
data: unknown;
|
|
112
|
+
error: null;
|
|
113
|
+
} | {
|
|
114
|
+
data: null;
|
|
115
|
+
error: unknown;
|
|
116
|
+
} | undefined>;
|
|
107
117
|
export type RouterState = {
|
|
108
118
|
routeItemDataState: Store<RouteItemData>;
|
|
109
119
|
pendingState: Store<RouteItemData | undefined>;
|
|
@@ -116,6 +126,7 @@ export type RouterState = {
|
|
|
116
126
|
}>;
|
|
117
127
|
loaderStateRef: Cell<LoaderState>;
|
|
118
128
|
loaderMap: Map<string, LoaderStateItem>;
|
|
129
|
+
loadingPromises: Map<string, LoadingPromise>;
|
|
119
130
|
};
|
|
120
131
|
export type RouterType = {
|
|
121
132
|
state: Omit<RouterState, 'timestampMap'>;
|
|
@@ -147,10 +158,7 @@ export type InvalidateOptions = {
|
|
|
147
158
|
withChildren?: boolean;
|
|
148
159
|
withBeforeLoad?: boolean;
|
|
149
160
|
};
|
|
150
|
-
export type RevalidateCache = (args: RevalidateCacheArgs) =>
|
|
151
|
-
data: unknown;
|
|
152
|
-
error: unknown;
|
|
153
|
-
}>;
|
|
161
|
+
export type RevalidateCache = (args: RevalidateCacheArgs) => LoadingPromise;
|
|
154
162
|
export type Options = Partial<{
|
|
155
163
|
onSuccess: (args: unknown) => void;
|
|
156
164
|
onError: (args: unknown) => void;
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { RevalidateCacheArgs, RouterState } from '../types';
|
|
2
|
-
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname, search }: RevalidateCacheArgs, retried?: number) => Promise<
|
|
2
|
+
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname, search, signal }: RevalidateCacheArgs, retried?: number) => Promise<{
|
|
3
|
+
data: unknown;
|
|
4
|
+
error: null;
|
|
5
|
+
} | {
|
|
6
|
+
data: null;
|
|
7
|
+
error: unknown;
|
|
8
|
+
} | undefined>;
|