@choksheak/react-utils 0.2.3 → 0.2.5
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/fetcher.min.cjs +2 -0
- package/fetcher.min.cjs.map +1 -0
- package/fetcher.min.mjs +2 -0
- package/fetcher.min.mjs.map +1 -0
- package/index.min.cjs +2 -0
- package/index.min.cjs.map +1 -0
- package/index.min.mjs +2 -0
- package/index.min.mjs.map +1 -0
- package/package.json +38 -5
- package/{sharedQuery.js → sharedQuery.cjs} +202 -79
- package/sharedQuery.d.mts +73 -9
- package/sharedQuery.d.ts +72 -8
- package/sharedQuery.min.cjs +2 -0
- package/sharedQuery.min.cjs.map +1 -0
- package/sharedQuery.min.mjs +2 -0
- package/sharedQuery.min.mjs.map +1 -0
- package/sharedQuery.mjs +200 -77
- package/sharedState.min.cjs +2 -0
- package/sharedState.min.cjs.map +1 -0
- package/sharedState.min.mjs +2 -0
- package/sharedState.min.mjs.map +1 -0
- package/utils/storage.min.cjs +2 -0
- package/utils/storage.min.cjs.map +1 -0
- package/utils/storage.min.mjs +2 -0
- package/utils/storage.min.mjs.map +1 -0
- package/utils/stringify.min.cjs +2 -0
- package/utils/stringify.min.cjs.map +1 -0
- package/utils/stringify.min.mjs +2 -0
- package/utils/stringify.min.mjs.map +1 -0
- package/utils/useDeepMemo.cjs +51 -0
- package/utils/useDeepMemo.d.mts +4 -0
- package/utils/useDeepMemo.d.ts +2 -0
- package/utils/useDeepMemo.min.cjs +2 -0
- package/utils/useDeepMemo.min.cjs.map +1 -0
- package/utils/useDeepMemo.min.mjs +2 -0
- package/utils/useDeepMemo.min.mjs.map +1 -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/src/fetcher.ts +0 -14
- package/src/index.ts +0 -4
- package/src/sharedQuery.ts +0 -507
- package/src/sharedState.ts +0 -232
- package/src/utils/storage.ts +0 -197
- package/src/utils/stringify.ts +0 -49
- /package/{fetcher.js → fetcher.cjs} +0 -0
- /package/{index.js → index.cjs} +0 -0
- /package/{sharedState.js → sharedState.cjs} +0 -0
- /package/utils/{storage.js → storage.cjs} +0 -0
- /package/utils/{stringify.js → stringify.cjs} +0 -0
package/README.md
CHANGED
package/fetcher.min.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var f=Object.create;var m=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var d=Object.getPrototypeOf,n=Object.prototype.hasOwnProperty;var w=(r,e)=>{for(var t in e)m(r,t,{get:e[t],enumerable:!0})},p=(r,e,t,c)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of a(e))!n.call(r,o)&&o!==t&&m(r,o,{get:()=>e[o],enumerable:!(c=h(e,o))||c.enumerable});return r};var x=(r,e,t)=>(t=r!=null?f(d(r)):{},p(e||!r||!r.__esModule?m(t,"default",{value:r,enumerable:!0}):t,r)),y=r=>p(m({},"__esModule",{value:!0}),r);var u={};w(u,{fetcher:()=>l,fetcherWithRetries:()=>k});module.exports=y(u);var i=x(require("wretch")),s=require("wretch/middlewares"),l=(0,i.default)(),k=(0,i.default)().middlewares([(0,s.retry)({maxAttempts:3,delayTimer:1e3,retryOnNetworkError:!0})]);0&&(module.exports={fetcher,fetcherWithRetries});
|
|
2
|
+
//# sourceMappingURL=fetcher.min.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fetcher.ts"],"sourcesContent":["import wretch from \"wretch\";\nimport { retry } from \"wretch/middlewares\";\n\n/** Name this as \"fetcher\" to make it easier to understand. */\nexport const fetcher = wretch();\n\n/** Fetcher with default short retries. */\nexport const fetcherWithRetries = wretch().middlewares([\n retry({\n maxAttempts: 3,\n delayTimer: 1000,\n retryOnNetworkError: true,\n }),\n]);\n"],"mappings":"0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,uBAAAC,IAAA,eAAAC,EAAAJ,GAAA,IAAAK,EAAmB,qBACnBC,EAAsB,8BAGTJ,KAAU,EAAAK,SAAO,EAGjBJ,KAAqB,EAAAI,SAAO,EAAE,YAAY,IACrD,SAAM,CACJ,YAAa,EACb,WAAY,IACZ,oBAAqB,EACvB,CAAC,CACH,CAAC","names":["fetcher_exports","__export","fetcher","fetcherWithRetries","__toCommonJS","import_wretch","import_middlewares","wretch"]}
|
package/fetcher.min.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fetcher.ts"],"sourcesContent":["import wretch from \"wretch\";\nimport { retry } from \"wretch/middlewares\";\n\n/** Name this as \"fetcher\" to make it easier to understand. */\nexport const fetcher = wretch();\n\n/** Fetcher with default short retries. */\nexport const fetcherWithRetries = wretch().middlewares([\n retry({\n maxAttempts: 3,\n delayTimer: 1000,\n retryOnNetworkError: true,\n }),\n]);\n"],"mappings":"AAAA,OAAOA,MAAY,SACnB,OAAS,SAAAC,MAAa,qBAGf,IAAMC,EAAUF,EAAO,EAGjBG,EAAqBH,EAAO,EAAE,YAAY,CACrDC,EAAM,CACJ,YAAa,EACb,WAAY,IACZ,oBAAqB,EACvB,CAAC,CACH,CAAC","names":["wretch","retry","fetcher","fetcherWithRetries"]}
|
package/index.min.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Unused file. To support full tree-shaking, please import from the individual\n// files directly.\n\nconsole.log(`Hello from Chok's React Utils`);\n"],"mappings":"aAGA,QAAQ,IAAI,+BAA+B","names":[]}
|
package/index.min.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Unused file. To support full tree-shaking, please import from the individual\n// files directly.\n\nconsole.log(`Hello from Chok's React Utils`);\n"],"mappings":"8DAAA,IAAAA,EAAAC,EAAA,KAGA,QAAQ,IAAI,+BAA+B","names":["require_index","__commonJSMin"]}
|
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.5",
|
|
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",
|
|
@@ -63,7 +63,9 @@
|
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@eslint/compat": "^1.4.0",
|
|
65
65
|
"@eslint/js": "^9.38.0",
|
|
66
|
+
"@swc/core": "^1.14.0",
|
|
66
67
|
"@types/jest": "^30.0.0",
|
|
68
|
+
"@types/lodash": "^4.17.20",
|
|
67
69
|
"@types/react": "^19.2.2",
|
|
68
70
|
"@types/react-dom": "^19.2.2",
|
|
69
71
|
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
|
@@ -79,6 +81,7 @@
|
|
|
79
81
|
"globals": "^16.4.0",
|
|
80
82
|
"jiti": "^2.6.1",
|
|
81
83
|
"lint-staged": "^16.2.6",
|
|
84
|
+
"lodash": "^4.17.21",
|
|
82
85
|
"object-sizeof": "^2.6.5",
|
|
83
86
|
"prettier": "^3.6.2",
|
|
84
87
|
"react": "^19.2.0",
|
|
@@ -89,28 +92,58 @@
|
|
|
89
92
|
"typedoc": "^0.28.14",
|
|
90
93
|
"typescript": "^5.9.3",
|
|
91
94
|
"typescript-eslint": "^8.46.2",
|
|
95
|
+
"use-deep-compare": "^1.3.0",
|
|
92
96
|
"vitest": "^4.0.3",
|
|
93
97
|
"wretch": "^3.0.2"
|
|
94
98
|
},
|
|
95
99
|
"peerDependencies": {
|
|
96
100
|
"@choksheak/ts-utils": ">=0.2.9",
|
|
101
|
+
"lodash": ">=4.17.21",
|
|
97
102
|
"object-sizeof": ">=2.6.5",
|
|
98
103
|
"react": ">=16",
|
|
104
|
+
"use-deep-compare": ">=1.3.0",
|
|
99
105
|
"wretch": ">=3"
|
|
100
106
|
},
|
|
101
107
|
"exports": {
|
|
102
108
|
"./package.json": "./package.json",
|
|
103
109
|
"./fetcher": {
|
|
104
110
|
"types": "./fetcher.d.ts",
|
|
105
|
-
"import":
|
|
111
|
+
"import": {
|
|
112
|
+
"production": "./fetcher.min.mjs",
|
|
113
|
+
"development": "./fetcher.mjs",
|
|
114
|
+
"default": "./fetcher.mjs"
|
|
115
|
+
},
|
|
116
|
+
"require": {
|
|
117
|
+
"production": "./fetcher.min.cjs",
|
|
118
|
+
"development": "./fetcher.cjs",
|
|
119
|
+
"default": "./fetcher.cjs"
|
|
120
|
+
}
|
|
106
121
|
},
|
|
107
122
|
"./sharedQuery": {
|
|
108
123
|
"types": "./sharedQuery.d.ts",
|
|
109
|
-
"import":
|
|
124
|
+
"import": {
|
|
125
|
+
"production": "./sharedQuery.min.mjs",
|
|
126
|
+
"development": "./sharedQuery.mjs",
|
|
127
|
+
"default": "./sharedQuery.mjs"
|
|
128
|
+
},
|
|
129
|
+
"require": {
|
|
130
|
+
"production": "./sharedQuery.min.cjs",
|
|
131
|
+
"development": "./sharedQuery.cjs",
|
|
132
|
+
"default": "./sharedQuery.cjs"
|
|
133
|
+
}
|
|
110
134
|
},
|
|
111
135
|
"./sharedState": {
|
|
112
136
|
"types": "./sharedState.d.ts",
|
|
113
|
-
"import":
|
|
137
|
+
"import": {
|
|
138
|
+
"production": "./sharedState.min.mjs",
|
|
139
|
+
"development": "./sharedState.mjs",
|
|
140
|
+
"default": "./sharedState.mjs"
|
|
141
|
+
},
|
|
142
|
+
"require": {
|
|
143
|
+
"production": "./sharedState.min.cjs",
|
|
144
|
+
"development": "./sharedState.cjs",
|
|
145
|
+
"default": "./sharedState.cjs"
|
|
146
|
+
}
|
|
114
147
|
}
|
|
115
148
|
},
|
|
116
149
|
"typesVersions": {
|
|
@@ -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,56 @@ 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(args) {
|
|
387
|
+
const queryKey = this.getQueryKey(args);
|
|
388
|
+
return this.getAbortControllerByKey(queryKey);
|
|
389
|
+
}
|
|
390
|
+
getAbortControllerByKey(queryKey) {
|
|
391
|
+
return this.inflightQueries.get(queryKey)?.abortController ?? null;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Get the AbortSignal to check for query abortions.
|
|
395
|
+
*
|
|
396
|
+
* Example:
|
|
397
|
+
* ```
|
|
398
|
+
* const getUserQuery = sharedQuery({
|
|
399
|
+
* queryName: "getUser",
|
|
400
|
+
* queryFn: (userId: string) => {
|
|
401
|
+
* // Get the signal for this current execution.
|
|
402
|
+
* const signal = getUserQuery.getAbortSignal([userId]);
|
|
403
|
+
*
|
|
404
|
+
* // Pass the signal to fetch so that it can be aborted.
|
|
405
|
+
* const response = await fetch(`/users/${userId}`, { signal });
|
|
406
|
+
*
|
|
407
|
+
* // Check for errors.
|
|
408
|
+
* if (!response.ok) {
|
|
409
|
+
* throw new Error(response.statusText);
|
|
410
|
+
* }
|
|
411
|
+
*
|
|
412
|
+
* // Return the data.
|
|
413
|
+
* return await response.json();
|
|
414
|
+
* },
|
|
415
|
+
* });
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
getAbortSignal(args) {
|
|
419
|
+
const queryKey = this.getQueryKey(args);
|
|
420
|
+
return this.getAbortSignalByKey(queryKey);
|
|
421
|
+
}
|
|
422
|
+
getAbortSignalByKey(queryKey) {
|
|
423
|
+
return this.getAbortControllerByKey(queryKey)?.signal ?? null;
|
|
424
|
+
}
|
|
352
425
|
isStale(dataUpdatedMs) {
|
|
353
426
|
if (this.staleMs === 0) return true;
|
|
354
427
|
const ageMs = Date.now() - dataUpdatedMs;
|
|
@@ -364,46 +437,53 @@ var SharedQuery = class {
|
|
|
364
437
|
const cached = this.queryState.getSnapshot()?.[queryKey];
|
|
365
438
|
if (cached?.data && !this.isExpired(cached.dataUpdatedMs ?? 0)) {
|
|
366
439
|
if (!this.isStale(cached.dataUpdatedMs ?? 0)) {
|
|
367
|
-
|
|
368
|
-
`Return fresh data ${queryKey} from cache without fetching`
|
|
369
|
-
);
|
|
440
|
+
this.log(`Return fresh data ${queryKey} from cache without fetching`);
|
|
370
441
|
return cached.data;
|
|
371
442
|
}
|
|
372
443
|
if (this.refetchOnStale) {
|
|
373
|
-
|
|
444
|
+
this.log(`refetchOnStale for ${queryKey}`);
|
|
374
445
|
void this.dedupedFetch(queryKey, ...args);
|
|
375
446
|
}
|
|
376
|
-
|
|
447
|
+
this.log(`Returning stale data for ${queryKey}`);
|
|
377
448
|
return cached.data;
|
|
378
449
|
}
|
|
379
450
|
return await this.dedupedFetch(queryKey, ...args);
|
|
380
451
|
}
|
|
381
452
|
dedupedFetch(queryKey, ...args) {
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
return
|
|
453
|
+
const inflightQuery = this.inflightQueries.get(queryKey);
|
|
454
|
+
if (inflightQuery) {
|
|
455
|
+
this.log(`Deduplicating inflight fetch for ${queryKey}`);
|
|
456
|
+
return inflightQuery.promise;
|
|
386
457
|
}
|
|
387
458
|
const promise = this.startFetching(queryKey, args);
|
|
388
|
-
|
|
459
|
+
const abortController = new AbortController();
|
|
460
|
+
this.inflightQueries.set(queryKey, { promise, abortController });
|
|
389
461
|
return promise;
|
|
390
462
|
}
|
|
391
463
|
async startFetching(queryKey, args) {
|
|
392
|
-
|
|
464
|
+
this.log(`Start fetching ${queryKey}`);
|
|
393
465
|
try {
|
|
394
466
|
const data = await this.queryFn(...args);
|
|
395
|
-
|
|
467
|
+
this.log(`Successfully fetched ${queryKey}`);
|
|
396
468
|
this.setData(queryKey, data, Date.now());
|
|
397
469
|
return data;
|
|
398
470
|
} catch (e) {
|
|
399
|
-
|
|
471
|
+
this.log(`Failed to fetch ${queryKey}: ${e}`);
|
|
400
472
|
throw e;
|
|
401
473
|
} finally {
|
|
402
|
-
this.
|
|
474
|
+
this.inflightQueries.delete(queryKey);
|
|
403
475
|
}
|
|
404
476
|
}
|
|
477
|
+
/** Get the entire stored entry for a query key. */
|
|
478
|
+
getQueryValue(queryKey) {
|
|
479
|
+
return this.queryState.getSnapshot()?.[queryKey];
|
|
480
|
+
}
|
|
481
|
+
/** Get the current stored data for a query key. */
|
|
482
|
+
getData(queryKey) {
|
|
483
|
+
return this.queryState.getSnapshot()?.[queryKey]?.data;
|
|
484
|
+
}
|
|
405
485
|
/** Set the data directly if the user obtained it from somewhere else. */
|
|
406
|
-
|
|
486
|
+
setData(queryKey, data, dataUpdatedMs = Date.now()) {
|
|
407
487
|
const entry = {
|
|
408
488
|
lastUpdatedMs: Date.now(),
|
|
409
489
|
loading: false,
|
|
@@ -413,24 +493,35 @@ var SharedQuery = class {
|
|
|
413
493
|
this.queryState.setValue((prev) => ({ ...prev, [queryKey]: entry }));
|
|
414
494
|
this.enforceSizeLimit();
|
|
415
495
|
}
|
|
416
|
-
/**
|
|
496
|
+
/**
|
|
497
|
+
* Do a new fetch even when the data is already cached, but don't fetch if
|
|
498
|
+
* another fetch is already inflight.
|
|
499
|
+
*/
|
|
417
500
|
async updateFromSource(...args) {
|
|
418
501
|
const key = this.getQueryKey(args);
|
|
419
502
|
return await this.dedupedFetch(key, ...args);
|
|
420
503
|
}
|
|
421
504
|
/** Delete the cached data for one set of arguments. */
|
|
422
|
-
deleteData(
|
|
423
|
-
const queryKey = this.getQueryKey(args);
|
|
505
|
+
deleteData(queryKey) {
|
|
424
506
|
const record = this.queryState.getSnapshot();
|
|
425
507
|
const clone = { ...record };
|
|
426
508
|
delete clone[queryKey];
|
|
427
509
|
this.queryState.setValue(clone);
|
|
428
|
-
this.
|
|
510
|
+
const inflight = this.inflightQueries.get(queryKey);
|
|
511
|
+
if (inflight) {
|
|
512
|
+
this.inflightQueries.delete(queryKey);
|
|
513
|
+
inflight.abortController.abort("Deleted");
|
|
514
|
+
}
|
|
429
515
|
}
|
|
430
516
|
/** Delete all currently cached data & all inflight promises. */
|
|
431
517
|
clear() {
|
|
432
518
|
this.queryState.delete();
|
|
433
|
-
|
|
519
|
+
for (const { abortController } of Array.from(
|
|
520
|
+
this.inflightQueries.values()
|
|
521
|
+
)) {
|
|
522
|
+
abortController.abort("Cleared");
|
|
523
|
+
}
|
|
524
|
+
this.inflightQueries.clear();
|
|
434
525
|
}
|
|
435
526
|
/**
|
|
436
527
|
* Discard the oldest entries if the size exceeds the limit. The last
|
|
@@ -451,7 +542,7 @@ var SharedQuery = class {
|
|
|
451
542
|
if (this.maxSize) {
|
|
452
543
|
const numToCut = oldSize - this.maxSize;
|
|
453
544
|
if (numToCut > 0) {
|
|
454
|
-
|
|
545
|
+
this.log(
|
|
455
546
|
`enforceSizeLimit for ${this.queryName} needed due to size ${oldSize} > ${this.maxSize}`
|
|
456
547
|
);
|
|
457
548
|
newState = { ...newState };
|
|
@@ -470,7 +561,7 @@ var SharedQuery = class {
|
|
|
470
561
|
const currentByteSize = needUpdate ? (0, import_object_sizeof.default)(newState) : oldByteSize;
|
|
471
562
|
let bytesToCut = currentByteSize - this.maxBytes;
|
|
472
563
|
if (bytesToCut > 0) {
|
|
473
|
-
|
|
564
|
+
this.log(
|
|
474
565
|
`enforceSizeLimit for ${this.queryName} needed due to byte size ${currentByteSize.toLocaleString()} > ${this.maxBytes.toLocaleString()}`
|
|
475
566
|
);
|
|
476
567
|
if (!needUpdate) {
|
|
@@ -495,12 +586,12 @@ var SharedQuery = class {
|
|
|
495
586
|
}
|
|
496
587
|
if (needUpdate) {
|
|
497
588
|
this.queryState.setValue(newState);
|
|
498
|
-
|
|
589
|
+
this.log(
|
|
499
590
|
`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
591
|
);
|
|
501
592
|
return;
|
|
502
593
|
}
|
|
503
|
-
|
|
594
|
+
this.log(
|
|
504
595
|
`No need to trim data for ${this.queryName}: size=${oldSize} (limit=${this.maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`
|
|
505
596
|
);
|
|
506
597
|
}
|
|
@@ -518,77 +609,109 @@ var DEFAULT_QUERY_STATE_ENTRY = {
|
|
|
518
609
|
loading: true
|
|
519
610
|
};
|
|
520
611
|
function useSharedQuery(query, args = []) {
|
|
612
|
+
const stableArgs = useDeepMemo(args);
|
|
521
613
|
const [queryState, setQueryState] = useSharedState(query.queryState);
|
|
522
|
-
const isMounted = (0,
|
|
523
|
-
const queryKey =
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
614
|
+
const isMounted = (0, import_react3.useRef)(true);
|
|
615
|
+
const queryKey = (0, import_react3.useMemo)(
|
|
616
|
+
() => query.getQueryKey(stableArgs),
|
|
617
|
+
[query, stableArgs]
|
|
618
|
+
);
|
|
619
|
+
const queryStateValue = queryState[queryKey];
|
|
620
|
+
const setQueryStateValue = (0, import_react3.useCallback)(
|
|
621
|
+
(arg) => {
|
|
622
|
+
setQueryState((prev) => {
|
|
623
|
+
const clone = { ...prev };
|
|
624
|
+
clone[queryKey] = typeof arg === "function" ? arg(clone[queryKey]) : arg;
|
|
625
|
+
return clone;
|
|
626
|
+
});
|
|
627
|
+
},
|
|
628
|
+
[queryKey, setQueryState]
|
|
629
|
+
);
|
|
630
|
+
const execute = (0, import_react3.useCallback)(
|
|
631
|
+
async (forceRefresh) => {
|
|
632
|
+
query.log(`Begin executing shared query ${queryKey}`);
|
|
633
|
+
setQueryStateValue((prev) => ({
|
|
634
|
+
// Keep old data and error.
|
|
635
|
+
...prev ?? {},
|
|
530
636
|
lastUpdatedMs: Date.now(),
|
|
531
637
|
loading: true
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const data = await query.getCachedOrFetch(...args);
|
|
537
|
-
if (isMounted.current) {
|
|
538
|
-
setQueryState((prev) => {
|
|
539
|
-
const clone = { ...prev };
|
|
638
|
+
}));
|
|
639
|
+
try {
|
|
640
|
+
const data = await (forceRefresh ? query.updateFromSource(...stableArgs) : query.getCachedOrFetch(...stableArgs));
|
|
641
|
+
if (isMounted.current) {
|
|
540
642
|
const now = Date.now();
|
|
541
|
-
|
|
643
|
+
setQueryStateValue({
|
|
542
644
|
// Don't keep old error.
|
|
543
645
|
lastUpdatedMs: now,
|
|
544
646
|
loading: false,
|
|
545
647
|
data,
|
|
546
648
|
dataUpdatedMs: now
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
} catch (e) {
|
|
552
|
-
const error = e instanceof Error ? e : new Error(`An unknown error occurred during fetch: ${e}`);
|
|
553
|
-
if (isMounted.current) {
|
|
554
|
-
setQueryState((prev) => {
|
|
555
|
-
const clone = { ...prev };
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
} catch (e) {
|
|
652
|
+
if (isMounted.current) {
|
|
556
653
|
const now = Date.now();
|
|
557
|
-
|
|
654
|
+
setQueryStateValue((prev) => ({
|
|
558
655
|
// Keep old data.
|
|
559
|
-
...
|
|
656
|
+
...prev ?? {},
|
|
560
657
|
lastUpdatedMs: now,
|
|
561
658
|
loading: false,
|
|
562
|
-
error,
|
|
659
|
+
error: e,
|
|
563
660
|
errorUpdatedMs: now
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
});
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
567
663
|
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
664
|
+
},
|
|
665
|
+
[query, queryKey, setQueryStateValue, stableArgs]
|
|
666
|
+
);
|
|
667
|
+
(0, import_react3.useEffect)(() => {
|
|
571
668
|
isMounted.current = true;
|
|
572
|
-
void execute();
|
|
669
|
+
void execute(false);
|
|
573
670
|
return () => {
|
|
574
671
|
isMounted.current = false;
|
|
575
672
|
};
|
|
576
673
|
}, [execute]);
|
|
577
|
-
return (0,
|
|
578
|
-
const state =
|
|
674
|
+
return (0, import_react3.useMemo)(() => {
|
|
675
|
+
const state = queryStateValue ?? DEFAULT_QUERY_STATE_ENTRY;
|
|
579
676
|
return {
|
|
580
677
|
...state,
|
|
678
|
+
// This works only if the user-given queryFn supports abort. If not,
|
|
679
|
+
// this function doesn't do anything, since we don't have any means to
|
|
680
|
+
// abort the running queryFn.
|
|
681
|
+
abortCurrentQuery: (reason) => {
|
|
682
|
+
const controller = query.getAbortControllerByKey(queryKey);
|
|
683
|
+
controller?.abort(reason);
|
|
684
|
+
return Boolean(controller);
|
|
685
|
+
},
|
|
581
686
|
refetch: () => {
|
|
582
|
-
|
|
687
|
+
void execute(true);
|
|
583
688
|
},
|
|
584
689
|
setData: (data, dataUpdatedMs) => {
|
|
585
|
-
|
|
690
|
+
const now = Date.now();
|
|
691
|
+
setQueryStateValue((prev) => ({
|
|
692
|
+
...prev ?? {},
|
|
693
|
+
data,
|
|
694
|
+
dataUpdatedMs: dataUpdatedMs ?? now,
|
|
695
|
+
lastUpdatedMs: now,
|
|
696
|
+
loading: false
|
|
697
|
+
}));
|
|
586
698
|
},
|
|
587
699
|
deleteData: () => {
|
|
588
|
-
|
|
700
|
+
setQueryState((prev) => {
|
|
701
|
+
const clone = { ...prev };
|
|
702
|
+
delete clone[queryKey];
|
|
703
|
+
return clone;
|
|
704
|
+
});
|
|
589
705
|
}
|
|
590
706
|
};
|
|
591
|
-
}, [
|
|
707
|
+
}, [
|
|
708
|
+
execute,
|
|
709
|
+
query,
|
|
710
|
+
queryKey,
|
|
711
|
+
queryStateValue,
|
|
712
|
+
setQueryState,
|
|
713
|
+
setQueryStateValue
|
|
714
|
+
]);
|
|
592
715
|
}
|
|
593
716
|
// Annotate the CommonJS export names for ESM import in node:
|
|
594
717
|
0 && (module.exports = {
|