@choksheak/react-utils 0.2.3 → 0.2.4
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 +3 -1
- package/package.json +7 -2
- package/sharedQuery.d.mts +38 -7
- package/sharedQuery.d.ts +37 -6
- package/sharedQuery.js +149 -89
- package/sharedQuery.mjs +147 -87
- package/src/sharedQuery.ts +198 -117
- package/src/utils/useDeepMemo.ts +19 -0
- package/utils/useDeepMemo.d.mts +4 -0
- package/utils/useDeepMemo.d.ts +2 -0
- package/utils/useDeepMemo.js +51 -0
- package/utils/useDeepMemo.mjs +16 -0
- package/docs/.nojekyll +0 -1
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -71
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/classes/sharedQuery.SharedQuery.html +0 -22
- package/docs/classes/sharedState.SharedState.html +0 -9
- package/docs/classes/utils_storage.InMemoryStorageAdapter.html +0 -9
- package/docs/classes/utils_storage.IndexedDbAdapter.html +0 -9
- package/docs/classes/utils_storage.LocalStorageAdapter.html +0 -9
- package/docs/functions/sharedQuery.sharedQuery.html +0 -9
- package/docs/functions/sharedQuery.useSharedQuery.html +0 -6
- package/docs/functions/sharedState.sharedState.html +0 -3
- package/docs/functions/sharedState.useSharedState.html +0 -3
- package/docs/functions/sharedState.useSharedStateSetter.html +0 -2
- package/docs/functions/sharedState.useSharedStateValue.html +0 -2
- package/docs/functions/utils_storage.getStorageAdapter.html +0 -1
- package/docs/functions/utils_stringify.stringifyDeterministicForKeys.html +0 -7
- package/docs/hierarchy.html +0 -1
- package/docs/index.html +0 -2
- package/docs/modules/fetcher.html +0 -1
- package/docs/modules/index.html +0 -1
- package/docs/modules/sharedQuery.html +0 -1
- package/docs/modules/sharedState.html +0 -1
- package/docs/modules/utils_storage.html +0 -1
- package/docs/modules/utils_stringify.html +0 -1
- package/docs/modules.html +0 -1
- package/docs/types/sharedQuery.FetchFn.html +0 -1
- package/docs/types/sharedQuery.PersistTo.html +0 -1
- package/docs/types/sharedQuery.QueryStateValue.html +0 -9
- package/docs/types/sharedQuery.SharedQueryOptions.html +0 -1
- package/docs/types/sharedQuery.SharedQueryState.html +0 -3
- package/docs/types/sharedQuery.UseQueryResult.html +0 -1
- package/docs/types/sharedState.SharedStateOptions.html +0 -1
- package/docs/types/utils_storage.StorageAdapter.html +0 -5
- package/docs/types/utils_storage.StorageOptions.html +0 -5
- package/docs/variables/fetcher.fetcher.html +0 -2
- package/docs/variables/fetcher.fetcherWithRetries.html +0 -2
- package/docs/variables/sharedQuery.SharedQueryDefaults.html +0 -2
- package/docs/variables/sharedState.SharedStateDefaults.html +0 -6
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@choksheak/react-utils",
|
|
3
3
|
"license": "The Unlicense",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"description": "Miscellaneous React+TypeScript utilities with support for full tree-shaking.",
|
|
6
6
|
"private": false,
|
|
7
7
|
"main": "index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"lint": "eslint .",
|
|
15
15
|
"fix": "eslint . --fix",
|
|
16
|
-
"tsc": "tsc --noEmit",
|
|
16
|
+
"tsc": "tsc --noEmit --allowSyntheticDefaultImports",
|
|
17
17
|
"ft": "npm run fix && npm run tsc",
|
|
18
18
|
"build": "tsup src/*",
|
|
19
19
|
"test": "vitest run",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"@eslint/compat": "^1.4.0",
|
|
65
65
|
"@eslint/js": "^9.38.0",
|
|
66
66
|
"@types/jest": "^30.0.0",
|
|
67
|
+
"@types/lodash": "^4.17.20",
|
|
67
68
|
"@types/react": "^19.2.2",
|
|
68
69
|
"@types/react-dom": "^19.2.2",
|
|
69
70
|
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"globals": "^16.4.0",
|
|
80
81
|
"jiti": "^2.6.1",
|
|
81
82
|
"lint-staged": "^16.2.6",
|
|
83
|
+
"lodash": "^4.17.21",
|
|
82
84
|
"object-sizeof": "^2.6.5",
|
|
83
85
|
"prettier": "^3.6.2",
|
|
84
86
|
"react": "^19.2.0",
|
|
@@ -89,13 +91,16 @@
|
|
|
89
91
|
"typedoc": "^0.28.14",
|
|
90
92
|
"typescript": "^5.9.3",
|
|
91
93
|
"typescript-eslint": "^8.46.2",
|
|
94
|
+
"use-deep-compare": "^1.3.0",
|
|
92
95
|
"vitest": "^4.0.3",
|
|
93
96
|
"wretch": "^3.0.2"
|
|
94
97
|
},
|
|
95
98
|
"peerDependencies": {
|
|
96
99
|
"@choksheak/ts-utils": ">=0.2.9",
|
|
100
|
+
"lodash": ">=4.17.21",
|
|
97
101
|
"object-sizeof": ">=2.6.5",
|
|
98
102
|
"react": ">=16",
|
|
103
|
+
"use-deep-compare": ">=1.3.0",
|
|
99
104
|
"wretch": ">=3"
|
|
100
105
|
},
|
|
101
106
|
"exports": {
|
package/sharedQuery.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SharedStateOptions, SharedState } from './sharedState.mjs';
|
|
2
2
|
import './utils/storage.mjs';
|
|
3
3
|
|
|
4
|
-
type
|
|
4
|
+
type QueryFn<TArgs extends unknown[], TData> = (...args: TArgs) => Promise<TData> | TData;
|
|
5
5
|
type QueryStateValue<TData> = {
|
|
6
6
|
/**
|
|
7
7
|
* When this record was last updated. Used for LRU determination. If you want
|
|
@@ -11,11 +11,27 @@ type QueryStateValue<TData> = {
|
|
|
11
11
|
loading: boolean;
|
|
12
12
|
data?: TData;
|
|
13
13
|
dataUpdatedMs?: number;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Use unknown type for error because we can't assume what type of variable
|
|
16
|
+
* will be thrown from the query function. We also don't want to add this
|
|
17
|
+
* type to QueryStateValue<TError> as it adds complexity to the type.
|
|
18
|
+
*/
|
|
19
|
+
error?: unknown;
|
|
15
20
|
errorUpdatedMs?: number;
|
|
16
21
|
};
|
|
17
22
|
type UseQueryResult<TData> = QueryStateValue<TData> & {
|
|
18
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Cancels the inflight query (if any). Returns true if canceled.
|
|
25
|
+
*/
|
|
26
|
+
abortCurrentQuery: (reason?: unknown) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Manually refresh the data by doing a new query.
|
|
29
|
+
*/
|
|
30
|
+
refetch: () => void;
|
|
31
|
+
/**
|
|
32
|
+
* If you got the data from somewhere else (e.g. an in-memory data update),
|
|
33
|
+
* just set it directly.
|
|
34
|
+
*/
|
|
19
35
|
setData: (data: TData, dataUpdatedMs?: number) => void;
|
|
20
36
|
deleteData: () => void;
|
|
21
37
|
};
|
|
@@ -27,24 +43,27 @@ type PersistTo = "localStorage" | "indexedDb";
|
|
|
27
43
|
type SharedQueryState<TData> = Record<string, QueryStateValue<TData>>;
|
|
28
44
|
type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
29
45
|
queryName: string;
|
|
30
|
-
queryFn:
|
|
46
|
+
queryFn: QueryFn<TArgs, TData>;
|
|
31
47
|
staleMs?: number;
|
|
32
48
|
expiryMs?: number;
|
|
33
49
|
refetchOnStale?: boolean;
|
|
34
50
|
persistTo?: PersistTo;
|
|
35
51
|
maxSize?: number;
|
|
36
52
|
maxBytes?: number;
|
|
53
|
+
log?: (...args: unknown[]) => void;
|
|
37
54
|
} & SharedStateOptions<SharedQueryState<TData>>;
|
|
38
55
|
/** Users can override these values globally. */
|
|
39
56
|
declare const SharedQueryDefaults: {
|
|
40
57
|
staleMs: number;
|
|
41
58
|
expiryMs: number;
|
|
59
|
+
refetchOnStale: boolean;
|
|
42
60
|
persistTo: PersistTo | undefined;
|
|
43
61
|
maxSize: number;
|
|
44
62
|
maxBytes: number;
|
|
63
|
+
log: (...args: unknown[]) => void;
|
|
45
64
|
};
|
|
46
65
|
declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
47
|
-
private readonly
|
|
66
|
+
private readonly inflightQueries;
|
|
48
67
|
readonly queryState: SharedState<SharedQueryState<TData>>;
|
|
49
68
|
readonly queryName: string;
|
|
50
69
|
private readonly queryFn;
|
|
@@ -53,8 +72,20 @@ declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
53
72
|
readonly refetchOnStale: boolean;
|
|
54
73
|
readonly maxSize: number;
|
|
55
74
|
readonly maxBytes: number;
|
|
75
|
+
readonly log: (...args: unknown[]) => void;
|
|
56
76
|
constructor(options: SharedQueryOptions<TArgs, TData>);
|
|
77
|
+
/** Returns a key to identify requests based on the given args. */
|
|
57
78
|
getQueryKey(args: TArgs): string;
|
|
79
|
+
/**
|
|
80
|
+
* Get the AbortController to abort the inflight query (if any).
|
|
81
|
+
*
|
|
82
|
+
* Note that once you get the controller, you can also get the signal easily
|
|
83
|
+
* using `controller.signal`. So if you need both the controller and the
|
|
84
|
+
* signal, just use this function.
|
|
85
|
+
*/
|
|
86
|
+
getAbortController(queryKey: string): AbortController | null;
|
|
87
|
+
/** Get the AbortSignal to check for query abortions. */
|
|
88
|
+
getAbortSignal(queryKey: string): AbortSignal | null;
|
|
58
89
|
private isStale;
|
|
59
90
|
private isExpired;
|
|
60
91
|
getCachedOrFetch(...args: TArgs): Promise<TData>;
|
|
@@ -65,7 +96,7 @@ declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
65
96
|
/** Do a new fetch even when the data is already cached. */
|
|
66
97
|
updateFromSource(...args: TArgs): Promise<TData>;
|
|
67
98
|
/** Delete the cached data for one set of arguments. */
|
|
68
|
-
deleteData(
|
|
99
|
+
deleteData(queryKey: string): void;
|
|
69
100
|
/** Delete all currently cached data & all inflight promises. */
|
|
70
101
|
clear(): void;
|
|
71
102
|
/**
|
|
@@ -102,4 +133,4 @@ declare function sharedQuery<TArgs extends unknown[], TData>(options: SharedQuer
|
|
|
102
133
|
*/
|
|
103
134
|
declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, args?: TArgs): UseQueryResult<TData>;
|
|
104
135
|
|
|
105
|
-
export { type
|
|
136
|
+
export { type PersistTo, type QueryFn, type QueryStateValue, SharedQuery, SharedQueryDefaults, type SharedQueryOptions, type SharedQueryState, type UseQueryResult, sharedQuery, useSharedQuery };
|
package/sharedQuery.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SharedState, SharedStateOptions } from "./sharedState";
|
|
2
|
-
export type
|
|
2
|
+
export type QueryFn<TArgs extends unknown[], TData> = (...args: TArgs) => Promise<TData> | TData;
|
|
3
3
|
export type QueryStateValue<TData> = {
|
|
4
4
|
/**
|
|
5
5
|
* When this record was last updated. Used for LRU determination. If you want
|
|
@@ -9,11 +9,27 @@ export type QueryStateValue<TData> = {
|
|
|
9
9
|
loading: boolean;
|
|
10
10
|
data?: TData;
|
|
11
11
|
dataUpdatedMs?: number;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Use unknown type for error because we can't assume what type of variable
|
|
14
|
+
* will be thrown from the query function. We also don't want to add this
|
|
15
|
+
* type to QueryStateValue<TError> as it adds complexity to the type.
|
|
16
|
+
*/
|
|
17
|
+
error?: unknown;
|
|
13
18
|
errorUpdatedMs?: number;
|
|
14
19
|
};
|
|
15
20
|
export type UseQueryResult<TData> = QueryStateValue<TData> & {
|
|
16
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Cancels the inflight query (if any). Returns true if canceled.
|
|
23
|
+
*/
|
|
24
|
+
abortCurrentQuery: (reason?: unknown) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Manually refresh the data by doing a new query.
|
|
27
|
+
*/
|
|
28
|
+
refetch: () => void;
|
|
29
|
+
/**
|
|
30
|
+
* If you got the data from somewhere else (e.g. an in-memory data update),
|
|
31
|
+
* just set it directly.
|
|
32
|
+
*/
|
|
17
33
|
setData: (data: TData, dataUpdatedMs?: number) => void;
|
|
18
34
|
deleteData: () => void;
|
|
19
35
|
};
|
|
@@ -25,24 +41,27 @@ export type PersistTo = "localStorage" | "indexedDb";
|
|
|
25
41
|
export type SharedQueryState<TData> = Record<string, QueryStateValue<TData>>;
|
|
26
42
|
export type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
27
43
|
queryName: string;
|
|
28
|
-
queryFn:
|
|
44
|
+
queryFn: QueryFn<TArgs, TData>;
|
|
29
45
|
staleMs?: number;
|
|
30
46
|
expiryMs?: number;
|
|
31
47
|
refetchOnStale?: boolean;
|
|
32
48
|
persistTo?: PersistTo;
|
|
33
49
|
maxSize?: number;
|
|
34
50
|
maxBytes?: number;
|
|
51
|
+
log?: (...args: unknown[]) => void;
|
|
35
52
|
} & SharedStateOptions<SharedQueryState<TData>>;
|
|
36
53
|
/** Users can override these values globally. */
|
|
37
54
|
export declare const SharedQueryDefaults: {
|
|
38
55
|
staleMs: number;
|
|
39
56
|
expiryMs: number;
|
|
57
|
+
refetchOnStale: boolean;
|
|
40
58
|
persistTo: PersistTo | undefined;
|
|
41
59
|
maxSize: number;
|
|
42
60
|
maxBytes: number;
|
|
61
|
+
log: (...args: unknown[]) => void;
|
|
43
62
|
};
|
|
44
63
|
export declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
45
|
-
private readonly
|
|
64
|
+
private readonly inflightQueries;
|
|
46
65
|
readonly queryState: SharedState<SharedQueryState<TData>>;
|
|
47
66
|
readonly queryName: string;
|
|
48
67
|
private readonly queryFn;
|
|
@@ -51,8 +70,20 @@ export declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
51
70
|
readonly refetchOnStale: boolean;
|
|
52
71
|
readonly maxSize: number;
|
|
53
72
|
readonly maxBytes: number;
|
|
73
|
+
readonly log: (...args: unknown[]) => void;
|
|
54
74
|
constructor(options: SharedQueryOptions<TArgs, TData>);
|
|
75
|
+
/** Returns a key to identify requests based on the given args. */
|
|
55
76
|
getQueryKey(args: TArgs): string;
|
|
77
|
+
/**
|
|
78
|
+
* Get the AbortController to abort the inflight query (if any).
|
|
79
|
+
*
|
|
80
|
+
* Note that once you get the controller, you can also get the signal easily
|
|
81
|
+
* using `controller.signal`. So if you need both the controller and the
|
|
82
|
+
* signal, just use this function.
|
|
83
|
+
*/
|
|
84
|
+
getAbortController(queryKey: string): AbortController | null;
|
|
85
|
+
/** Get the AbortSignal to check for query abortions. */
|
|
86
|
+
getAbortSignal(queryKey: string): AbortSignal | null;
|
|
56
87
|
private isStale;
|
|
57
88
|
private isExpired;
|
|
58
89
|
getCachedOrFetch(...args: TArgs): Promise<TData>;
|
|
@@ -63,7 +94,7 @@ export declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
63
94
|
/** Do a new fetch even when the data is already cached. */
|
|
64
95
|
updateFromSource(...args: TArgs): Promise<TData>;
|
|
65
96
|
/** Delete the cached data for one set of arguments. */
|
|
66
|
-
deleteData(
|
|
97
|
+
deleteData(queryKey: string): void;
|
|
67
98
|
/** Delete all currently cached data & all inflight promises. */
|
|
68
99
|
clear(): void;
|
|
69
100
|
/**
|
package/sharedQuery.js
CHANGED
|
@@ -38,7 +38,7 @@ __export(sharedQuery_exports, {
|
|
|
38
38
|
module.exports = __toCommonJS(sharedQuery_exports);
|
|
39
39
|
var import_timeConstants2 = require("@choksheak/ts-utils/timeConstants");
|
|
40
40
|
var import_object_sizeof = __toESM(require("object-sizeof"));
|
|
41
|
-
var
|
|
41
|
+
var import_react3 = require("react");
|
|
42
42
|
|
|
43
43
|
// src/sharedState.ts
|
|
44
44
|
var import_timeConstants = require("@choksheak/ts-utils/timeConstants");
|
|
@@ -303,18 +303,39 @@ function stringifyDeterministicForKeys(params) {
|
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
// src/utils/useDeepMemo.ts
|
|
307
|
+
var import_isEqual = __toESM(require("lodash/isEqual"));
|
|
308
|
+
var import_react2 = require("react");
|
|
309
|
+
function useDeepMemo(value) {
|
|
310
|
+
const [state, setState] = (0, import_react2.useState)(value);
|
|
311
|
+
return (0, import_react2.useMemo)(() => {
|
|
312
|
+
if (state === value || (0, import_isEqual.default)(state, value)) {
|
|
313
|
+
return state;
|
|
314
|
+
}
|
|
315
|
+
setState(value);
|
|
316
|
+
return value;
|
|
317
|
+
}, [state, value]);
|
|
318
|
+
}
|
|
319
|
+
|
|
306
320
|
// src/sharedQuery.ts
|
|
307
321
|
var SharedQueryDefaults = {
|
|
322
|
+
// Refetch on every page load.
|
|
308
323
|
staleMs: 0,
|
|
324
|
+
// Keep in cache for a long time.
|
|
309
325
|
expiryMs: 30 * import_timeConstants2.MS_PER_DAY,
|
|
326
|
+
// Always refetch automatically if the data is stale.
|
|
327
|
+
refetchOnStale: true,
|
|
328
|
+
// No default persistence configured.
|
|
310
329
|
persistTo: void 0,
|
|
330
|
+
// 100 is not too large, but please tweak accordingly.
|
|
311
331
|
maxSize: 100,
|
|
312
|
-
//
|
|
313
|
-
maxBytes: 1e5
|
|
314
|
-
//
|
|
332
|
+
// 100kb limit before discarding old records.
|
|
333
|
+
maxBytes: 1e5,
|
|
334
|
+
// Default to log to console.
|
|
335
|
+
log: (...args) => console.log("[sharedQuery]", ...args)
|
|
315
336
|
};
|
|
316
337
|
var SharedQuery = class {
|
|
317
|
-
|
|
338
|
+
inflightQueries = /* @__PURE__ */ new Map();
|
|
318
339
|
queryState;
|
|
319
340
|
queryName;
|
|
320
341
|
queryFn;
|
|
@@ -323,22 +344,27 @@ var SharedQuery = class {
|
|
|
323
344
|
refetchOnStale;
|
|
324
345
|
maxSize;
|
|
325
346
|
maxBytes;
|
|
347
|
+
log;
|
|
326
348
|
constructor(options) {
|
|
327
349
|
this.queryName = options.queryName;
|
|
328
350
|
this.queryFn = options.queryFn;
|
|
329
|
-
this.
|
|
330
|
-
this.
|
|
331
|
-
this.refetchOnStale =
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
351
|
+
this.staleMs = options?.staleMs ?? SharedQueryDefaults.staleMs;
|
|
352
|
+
this.expiryMs = options?.expiryMs ?? SharedQueryDefaults.expiryMs;
|
|
353
|
+
this.refetchOnStale = options?.refetchOnStale ?? SharedQueryDefaults.refetchOnStale;
|
|
354
|
+
if (!options.localStorageKey && !options.indexedDbKey) {
|
|
355
|
+
const persistTo = options.persistTo ?? SharedQueryDefaults.persistTo;
|
|
356
|
+
if (persistTo === "localStorage") {
|
|
357
|
+
options.localStorageKey = options.queryName;
|
|
358
|
+
} else if (persistTo === "indexedDb") {
|
|
359
|
+
options.indexedDbKey = options.queryName;
|
|
360
|
+
}
|
|
337
361
|
}
|
|
338
362
|
this.maxSize = options?.maxSize ?? SharedQueryDefaults.maxSize;
|
|
339
363
|
this.maxBytes = options?.maxBytes ?? SharedQueryDefaults.maxBytes;
|
|
364
|
+
this.log = options?.log ?? SharedQueryDefaults.log;
|
|
340
365
|
this.queryState = sharedState(
|
|
341
366
|
{},
|
|
367
|
+
// defaultValue
|
|
342
368
|
{
|
|
343
369
|
...options,
|
|
344
370
|
// Fallback to use expiryMs for the storeExpiryMs.
|
|
@@ -346,9 +372,24 @@ var SharedQuery = class {
|
|
|
346
372
|
}
|
|
347
373
|
);
|
|
348
374
|
}
|
|
375
|
+
/** Returns a key to identify requests based on the given args. */
|
|
349
376
|
getQueryKey(args) {
|
|
350
377
|
return this.queryName + ":" + stringifyDeterministicForKeys(args);
|
|
351
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Get the AbortController to abort the inflight query (if any).
|
|
381
|
+
*
|
|
382
|
+
* Note that once you get the controller, you can also get the signal easily
|
|
383
|
+
* using `controller.signal`. So if you need both the controller and the
|
|
384
|
+
* signal, just use this function.
|
|
385
|
+
*/
|
|
386
|
+
getAbortController(queryKey) {
|
|
387
|
+
return this.inflightQueries.get(queryKey)?.abortController ?? null;
|
|
388
|
+
}
|
|
389
|
+
/** Get the AbortSignal to check for query abortions. */
|
|
390
|
+
getAbortSignal(queryKey) {
|
|
391
|
+
return this.getAbortController(queryKey)?.signal ?? null;
|
|
392
|
+
}
|
|
352
393
|
isStale(dataUpdatedMs) {
|
|
353
394
|
if (this.staleMs === 0) return true;
|
|
354
395
|
const ageMs = Date.now() - dataUpdatedMs;
|
|
@@ -364,42 +405,41 @@ var SharedQuery = class {
|
|
|
364
405
|
const cached = this.queryState.getSnapshot()?.[queryKey];
|
|
365
406
|
if (cached?.data && !this.isExpired(cached.dataUpdatedMs ?? 0)) {
|
|
366
407
|
if (!this.isStale(cached.dataUpdatedMs ?? 0)) {
|
|
367
|
-
|
|
368
|
-
`Return fresh data ${queryKey} from cache without fetching`
|
|
369
|
-
);
|
|
408
|
+
this.log(`Return fresh data ${queryKey} from cache without fetching`);
|
|
370
409
|
return cached.data;
|
|
371
410
|
}
|
|
372
411
|
if (this.refetchOnStale) {
|
|
373
|
-
|
|
412
|
+
this.log(`refetchOnStale for ${queryKey}`);
|
|
374
413
|
void this.dedupedFetch(queryKey, ...args);
|
|
375
414
|
}
|
|
376
|
-
|
|
415
|
+
this.log(`Returning stale data for ${queryKey}`);
|
|
377
416
|
return cached.data;
|
|
378
417
|
}
|
|
379
418
|
return await this.dedupedFetch(queryKey, ...args);
|
|
380
419
|
}
|
|
381
420
|
dedupedFetch(queryKey, ...args) {
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
return
|
|
421
|
+
const inflightQuery = this.inflightQueries.get(queryKey);
|
|
422
|
+
if (inflightQuery) {
|
|
423
|
+
this.log(`Deduplicating inflight fetch for ${queryKey}`);
|
|
424
|
+
return inflightQuery.promise;
|
|
386
425
|
}
|
|
387
426
|
const promise = this.startFetching(queryKey, args);
|
|
388
|
-
|
|
427
|
+
const abortController = new AbortController();
|
|
428
|
+
this.inflightQueries.set(queryKey, { promise, abortController });
|
|
389
429
|
return promise;
|
|
390
430
|
}
|
|
391
431
|
async startFetching(queryKey, args) {
|
|
392
|
-
|
|
432
|
+
this.log(`Start fetching ${queryKey}`);
|
|
393
433
|
try {
|
|
394
434
|
const data = await this.queryFn(...args);
|
|
395
|
-
|
|
435
|
+
this.log(`Successfully fetched ${queryKey}`);
|
|
396
436
|
this.setData(queryKey, data, Date.now());
|
|
397
437
|
return data;
|
|
398
438
|
} catch (e) {
|
|
399
|
-
|
|
439
|
+
this.log(`Failed to fetch ${queryKey}: ${e}`);
|
|
400
440
|
throw e;
|
|
401
441
|
} finally {
|
|
402
|
-
this.
|
|
442
|
+
this.inflightQueries.delete(queryKey);
|
|
403
443
|
}
|
|
404
444
|
}
|
|
405
445
|
/** Set the data directly if the user obtained it from somewhere else. */
|
|
@@ -419,18 +459,26 @@ var SharedQuery = class {
|
|
|
419
459
|
return await this.dedupedFetch(key, ...args);
|
|
420
460
|
}
|
|
421
461
|
/** Delete the cached data for one set of arguments. */
|
|
422
|
-
deleteData(
|
|
423
|
-
const queryKey = this.getQueryKey(args);
|
|
462
|
+
deleteData(queryKey) {
|
|
424
463
|
const record = this.queryState.getSnapshot();
|
|
425
464
|
const clone = { ...record };
|
|
426
465
|
delete clone[queryKey];
|
|
427
466
|
this.queryState.setValue(clone);
|
|
428
|
-
this.
|
|
467
|
+
const inflight = this.inflightQueries.get(queryKey);
|
|
468
|
+
if (inflight) {
|
|
469
|
+
this.inflightQueries.delete(queryKey);
|
|
470
|
+
inflight.abortController.abort("Deleted");
|
|
471
|
+
}
|
|
429
472
|
}
|
|
430
473
|
/** Delete all currently cached data & all inflight promises. */
|
|
431
474
|
clear() {
|
|
432
475
|
this.queryState.delete();
|
|
433
|
-
|
|
476
|
+
for (const { abortController } of Array.from(
|
|
477
|
+
this.inflightQueries.values()
|
|
478
|
+
)) {
|
|
479
|
+
abortController.abort("Cleared");
|
|
480
|
+
}
|
|
481
|
+
this.inflightQueries.clear();
|
|
434
482
|
}
|
|
435
483
|
/**
|
|
436
484
|
* Discard the oldest entries if the size exceeds the limit. The last
|
|
@@ -451,7 +499,7 @@ var SharedQuery = class {
|
|
|
451
499
|
if (this.maxSize) {
|
|
452
500
|
const numToCut = oldSize - this.maxSize;
|
|
453
501
|
if (numToCut > 0) {
|
|
454
|
-
|
|
502
|
+
this.log(
|
|
455
503
|
`enforceSizeLimit for ${this.queryName} needed due to size ${oldSize} > ${this.maxSize}`
|
|
456
504
|
);
|
|
457
505
|
newState = { ...newState };
|
|
@@ -470,7 +518,7 @@ var SharedQuery = class {
|
|
|
470
518
|
const currentByteSize = needUpdate ? (0, import_object_sizeof.default)(newState) : oldByteSize;
|
|
471
519
|
let bytesToCut = currentByteSize - this.maxBytes;
|
|
472
520
|
if (bytesToCut > 0) {
|
|
473
|
-
|
|
521
|
+
this.log(
|
|
474
522
|
`enforceSizeLimit for ${this.queryName} needed due to byte size ${currentByteSize.toLocaleString()} > ${this.maxBytes.toLocaleString()}`
|
|
475
523
|
);
|
|
476
524
|
if (!needUpdate) {
|
|
@@ -495,12 +543,12 @@ var SharedQuery = class {
|
|
|
495
543
|
}
|
|
496
544
|
if (needUpdate) {
|
|
497
545
|
this.queryState.setValue(newState);
|
|
498
|
-
|
|
546
|
+
this.log(
|
|
499
547
|
`Trimmed data for ${this.queryName}: size=(${oldSize} -> ${Object.keys(newState).length}) (limit=${this.maxSize}), byteSize=(${oldByteSize.toLocaleString()} -> ${(0, import_object_sizeof.default)(newState).toLocaleString()}) (limit=${this.maxBytes.toLocaleString()})`
|
|
500
548
|
);
|
|
501
549
|
return;
|
|
502
550
|
}
|
|
503
|
-
|
|
551
|
+
this.log(
|
|
504
552
|
`No need to trim data for ${this.queryName}: size=${oldSize} (limit=${this.maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`
|
|
505
553
|
);
|
|
506
554
|
}
|
|
@@ -518,77 +566,89 @@ var DEFAULT_QUERY_STATE_ENTRY = {
|
|
|
518
566
|
loading: true
|
|
519
567
|
};
|
|
520
568
|
function useSharedQuery(query, args = []) {
|
|
569
|
+
const stableArgs = useDeepMemo(args);
|
|
521
570
|
const [queryState, setQueryState] = useSharedState(query.queryState);
|
|
522
|
-
const isMounted = (0,
|
|
523
|
-
const queryKey =
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
571
|
+
const isMounted = (0, import_react3.useRef)(true);
|
|
572
|
+
const queryKey = (0, import_react3.useMemo)(
|
|
573
|
+
() => query.getQueryKey(stableArgs),
|
|
574
|
+
[query, stableArgs]
|
|
575
|
+
);
|
|
576
|
+
const queryStateValue = queryState[queryKey];
|
|
577
|
+
const execute = (0, import_react3.useCallback)(
|
|
578
|
+
async (forceRefresh) => {
|
|
579
|
+
query.log(`Begin executing shared query ${queryKey}`);
|
|
580
|
+
setQueryState((prev) => {
|
|
581
|
+
const clone = { ...prev };
|
|
582
|
+
clone[queryKey] = {
|
|
583
|
+
...clone[queryKey] ?? {},
|
|
584
|
+
lastUpdatedMs: Date.now(),
|
|
585
|
+
loading: true
|
|
586
|
+
};
|
|
587
|
+
return clone;
|
|
588
|
+
});
|
|
589
|
+
try {
|
|
590
|
+
const data = await (forceRefresh ? query.updateFromSource(...stableArgs) : query.getCachedOrFetch(...stableArgs));
|
|
591
|
+
if (isMounted.current) {
|
|
592
|
+
setQueryState((prev) => {
|
|
593
|
+
const clone = { ...prev };
|
|
594
|
+
const now = Date.now();
|
|
595
|
+
clone[queryKey] = {
|
|
596
|
+
// Don't keep old error.
|
|
597
|
+
lastUpdatedMs: now,
|
|
598
|
+
loading: false,
|
|
599
|
+
data,
|
|
600
|
+
dataUpdatedMs: now
|
|
601
|
+
};
|
|
602
|
+
return clone;
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
} catch (e) {
|
|
606
|
+
if (isMounted.current) {
|
|
607
|
+
setQueryState((prev) => {
|
|
608
|
+
const clone = { ...prev };
|
|
609
|
+
const now = Date.now();
|
|
610
|
+
clone[queryKey] = {
|
|
611
|
+
// Keep old data.
|
|
612
|
+
...clone[queryKey] ?? {},
|
|
613
|
+
lastUpdatedMs: now,
|
|
614
|
+
loading: false,
|
|
615
|
+
error: e,
|
|
616
|
+
errorUpdatedMs: now
|
|
617
|
+
};
|
|
618
|
+
return clone;
|
|
619
|
+
});
|
|
620
|
+
}
|
|
567
621
|
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
622
|
+
},
|
|
623
|
+
[query, queryKey, setQueryState, stableArgs]
|
|
624
|
+
);
|
|
625
|
+
(0, import_react3.useEffect)(() => {
|
|
571
626
|
isMounted.current = true;
|
|
572
|
-
void execute();
|
|
627
|
+
void execute(false);
|
|
573
628
|
return () => {
|
|
574
629
|
isMounted.current = false;
|
|
575
630
|
};
|
|
576
631
|
}, [execute]);
|
|
577
|
-
return (0,
|
|
578
|
-
const state =
|
|
632
|
+
return (0, import_react3.useMemo)(() => {
|
|
633
|
+
const state = queryStateValue ?? DEFAULT_QUERY_STATE_ENTRY;
|
|
579
634
|
return {
|
|
580
635
|
...state,
|
|
636
|
+
abortCurrentQuery: (reason) => {
|
|
637
|
+
const controller = query.getAbortController(queryKey);
|
|
638
|
+
controller?.abort(reason);
|
|
639
|
+
return Boolean(controller);
|
|
640
|
+
},
|
|
581
641
|
refetch: () => {
|
|
582
|
-
|
|
642
|
+
void execute(true);
|
|
583
643
|
},
|
|
584
644
|
setData: (data, dataUpdatedMs) => {
|
|
585
645
|
query.setData(queryKey, data, dataUpdatedMs ?? Date.now());
|
|
586
646
|
},
|
|
587
647
|
deleteData: () => {
|
|
588
|
-
query.deleteData(
|
|
648
|
+
query.deleteData(queryKey);
|
|
589
649
|
}
|
|
590
650
|
};
|
|
591
|
-
}, [query, queryKey,
|
|
651
|
+
}, [execute, query, queryKey, queryStateValue]);
|
|
592
652
|
}
|
|
593
653
|
// Annotate the CommonJS export names for ESM import in node:
|
|
594
654
|
0 && (module.exports = {
|