@choksheak/react-utils 0.5.0 → 0.5.1
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/chunk-25NYDKKO.min.js +2 -0
- package/chunk-25NYDKKO.min.js.map +1 -0
- package/chunk-2WE3FDDW.min.cjs +2 -0
- package/{chunk-SQX7XDQJ.min.cjs.map → chunk-2WE3FDDW.min.cjs.map} +1 -1
- package/{chunk-UBAHJNMF.js → chunk-5O4HBYCM.js} +214 -284
- package/{chunk-ALTY6BU7.cjs → chunk-5WECMKSV.cjs} +219 -289
- package/chunk-AX6NADU2.min.cjs +2 -0
- package/chunk-AX6NADU2.min.cjs.map +1 -0
- package/chunk-JHM37XTC.min.js +2 -0
- package/chunk-JHM37XTC.min.js.map +1 -0
- package/{chunk-KXFV42DP.cjs → chunk-YMQDZV47.cjs} +30 -10
- package/{chunk-H4OYYPJV.js → chunk-ZEVXDXJD.js} +30 -10
- package/index.cjs +3 -3
- package/index.d.cts +1 -1
- package/index.d.ts +1 -1
- package/index.js +2 -2
- package/index.min.cjs +1 -1
- package/index.min.js +1 -1
- package/package.json +12 -12
- package/sharedQuery.cjs +3 -3
- package/sharedQuery.d.cts +16 -79
- package/sharedQuery.d.ts +16 -79
- package/sharedQuery.js +2 -2
- package/sharedQuery.min.cjs +1 -1
- package/sharedQuery.min.js +1 -1
- package/sharedState.cjs +2 -2
- package/sharedState.d.cts +2 -2
- package/sharedState.d.ts +2 -2
- package/sharedState.js +1 -1
- package/sharedState.min.cjs +1 -1
- package/sharedState.min.js +1 -1
- package/chunk-GV7KUTU2.min.js +0 -2
- package/chunk-GV7KUTU2.min.js.map +0 -1
- package/chunk-IKHSARNO.min.js +0 -2
- package/chunk-IKHSARNO.min.js.map +0 -1
- package/chunk-OCJG7MUG.min.cjs +0 -2
- package/chunk-OCJG7MUG.min.cjs.map +0 -1
- package/chunk-SQX7XDQJ.min.cjs +0 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sharedState,
|
|
3
3
|
useSharedState
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZEVXDXJD.js";
|
|
5
5
|
import {
|
|
6
6
|
stringifyDeterministicForKeys
|
|
7
7
|
} from "./chunk-7XC6EVMX.js";
|
|
@@ -11,15 +11,8 @@ import {
|
|
|
11
11
|
|
|
12
12
|
// src/sharedQuery.ts
|
|
13
13
|
import { http, retries } from "@choksheak/ts-utils/http";
|
|
14
|
-
import { nonNil } from "@choksheak/ts-utils/nonNil";
|
|
15
14
|
import getByteSize from "object-sizeof";
|
|
16
|
-
import {
|
|
17
|
-
useCallback,
|
|
18
|
-
useEffect,
|
|
19
|
-
useMemo,
|
|
20
|
-
useRef,
|
|
21
|
-
useState
|
|
22
|
-
} from "react";
|
|
15
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
23
16
|
import XXH from "xxhashjs";
|
|
24
17
|
var hasWindow = typeof window !== "undefined";
|
|
25
18
|
var SharedQueryConfig = {
|
|
@@ -27,8 +20,6 @@ var SharedQueryConfig = {
|
|
|
27
20
|
staleMs: 0,
|
|
28
21
|
/** Keep in cache for a long time. */
|
|
29
22
|
expiryMs: 30 * 864e5,
|
|
30
|
-
/** Always refetch automatically if the data is stale. */
|
|
31
|
-
refetchOnStale: true,
|
|
32
23
|
/** No default persistence configured. */
|
|
33
24
|
persistTo: void 0,
|
|
34
25
|
/** 100 is not too large, but please tweak accordingly. */
|
|
@@ -114,7 +105,6 @@ function sharedQuery2(options) {
|
|
|
114
105
|
const refetchFn = options.refetchFn;
|
|
115
106
|
const staleMs = options?.staleMs ?? SharedQueryConfig.staleMs;
|
|
116
107
|
const expiryMs = options?.expiryMs ?? SharedQueryConfig.expiryMs;
|
|
117
|
-
const refetchOnStale = options?.refetchOnStale ?? SharedQueryConfig.refetchOnStale;
|
|
118
108
|
const store = options.store;
|
|
119
109
|
if (store) {
|
|
120
110
|
if ("persistTo" in store && store.isValid) {
|
|
@@ -160,6 +150,27 @@ function sharedQuery2(options) {
|
|
|
160
150
|
// defaultValue
|
|
161
151
|
options
|
|
162
152
|
);
|
|
153
|
+
function getQueryKey(args) {
|
|
154
|
+
let s = stringifyDeterministicForKeys(args);
|
|
155
|
+
if (s.length > 30) {
|
|
156
|
+
s = XXH.h32(s, 39406).toString(16).slice(2);
|
|
157
|
+
}
|
|
158
|
+
return queryName + ":" + s;
|
|
159
|
+
}
|
|
160
|
+
function getAbortControllerByKey(queryKey) {
|
|
161
|
+
return inflightQueries.get(queryKey)?.abortController ?? null;
|
|
162
|
+
}
|
|
163
|
+
function getAbortController(args) {
|
|
164
|
+
const queryKey = getQueryKey(args);
|
|
165
|
+
return getAbortControllerByKey(queryKey);
|
|
166
|
+
}
|
|
167
|
+
function getAbortSignal(args) {
|
|
168
|
+
const queryKey = getQueryKey(args);
|
|
169
|
+
return getAbortSignalByKey(queryKey);
|
|
170
|
+
}
|
|
171
|
+
function getAbortSignalByKey(queryKey) {
|
|
172
|
+
return getAbortControllerByKey(queryKey)?.signal ?? null;
|
|
173
|
+
}
|
|
163
174
|
function isStale(dataUpdatedMs) {
|
|
164
175
|
if (staleMs === 0) return true;
|
|
165
176
|
const ageMs = Date.now() - dataUpdatedMs;
|
|
@@ -196,7 +207,7 @@ function sharedQuery2(options) {
|
|
|
196
207
|
const deleteKeyIfNotMounted = (key, expired) => {
|
|
197
208
|
if (mountedKeys.has(key)) {
|
|
198
209
|
log(`Cannot clean up ${key} as it is mounted`);
|
|
199
|
-
return;
|
|
210
|
+
return false;
|
|
200
211
|
}
|
|
201
212
|
const byteSize = getByteSize(key) + getByteSize(newState[key]);
|
|
202
213
|
log(
|
|
@@ -206,23 +217,31 @@ function sharedQuery2(options) {
|
|
|
206
217
|
bytesToCut -= byteSize;
|
|
207
218
|
delete newState[key];
|
|
208
219
|
needUpdate = true;
|
|
220
|
+
return true;
|
|
209
221
|
};
|
|
210
|
-
|
|
211
|
-
(
|
|
222
|
+
const entriesByLastUpdatedMsAsc = Object.fromEntries(
|
|
223
|
+
Object.entries(newState).sort(
|
|
224
|
+
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
225
|
+
)
|
|
212
226
|
);
|
|
213
|
-
for (const
|
|
214
|
-
|
|
227
|
+
for (const key of Object.keys(entriesByLastUpdatedMsAsc)) {
|
|
228
|
+
const state = newState[key];
|
|
229
|
+
if (!state?.lastUpdatedMs || state?.loading) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (state?.lastUpdatedMs && !isExpired(state.lastUpdatedMs)) {
|
|
215
233
|
break;
|
|
216
234
|
}
|
|
217
|
-
deleteKeyIfNotMounted(key, true)
|
|
235
|
+
if (deleteKeyIfNotMounted(key, true)) {
|
|
236
|
+
delete entriesByLastUpdatedMsAsc[key];
|
|
237
|
+
}
|
|
218
238
|
}
|
|
219
239
|
if (numToCut > 0 || bytesToCut > 0) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
for (const [key] of entriesByTimeAscending) {
|
|
240
|
+
for (const key of Object.keys(entriesByLastUpdatedMsAsc)) {
|
|
241
|
+
const state = newState[key];
|
|
242
|
+
if (!state?.lastUpdatedMs || state?.loading) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
226
245
|
deleteKeyIfNotMounted(key, false);
|
|
227
246
|
if (numToCut <= 0 && bytesToCut <= 0) {
|
|
228
247
|
break;
|
|
@@ -240,15 +259,120 @@ function sharedQuery2(options) {
|
|
|
240
259
|
`Trimmed data for ${queryName}: size=[${oldSize} -> ${Object.keys(newState).length}] (limit=${maxSize}), byteSize=[${oldByteSize.toLocaleString()} -> ${getByteSize(newState).toLocaleString()}] (limit=${maxBytes.toLocaleString()})`
|
|
241
260
|
);
|
|
242
261
|
}
|
|
262
|
+
function getQueryValue(queryKey) {
|
|
263
|
+
return queryState.getSnapshot()?.[queryKey];
|
|
264
|
+
}
|
|
265
|
+
function setQueryValue(queryKey, next) {
|
|
266
|
+
queryState.setValue((prev) => {
|
|
267
|
+
const value = typeof next === "function" ? next(prev[queryKey] ?? DEFAULT_QUERY_STATE_ENTRY) : next;
|
|
268
|
+
return { ...prev, [queryKey]: value };
|
|
269
|
+
});
|
|
270
|
+
enforceSizeLimit();
|
|
271
|
+
}
|
|
272
|
+
function getData(queryKey) {
|
|
273
|
+
const stateValue = getQueryValue(queryKey);
|
|
274
|
+
if (stateValue?.dataUpdatedMs) {
|
|
275
|
+
if (isExpired(stateValue.dataUpdatedMs)) {
|
|
276
|
+
deleteData(queryKey);
|
|
277
|
+
return void 0;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return stateValue?.data;
|
|
281
|
+
}
|
|
243
282
|
function setData(queryKey, data, dataUpdatedMs = Date.now()) {
|
|
244
|
-
|
|
283
|
+
setQueryValue(queryKey, {
|
|
245
284
|
lastUpdatedMs: Date.now(),
|
|
246
285
|
loading: false,
|
|
247
286
|
data,
|
|
248
287
|
dataUpdatedMs
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function deleteData(queryKey) {
|
|
291
|
+
const record = queryState.getSnapshot();
|
|
292
|
+
const clone = { ...record };
|
|
293
|
+
delete clone[queryKey];
|
|
294
|
+
queryState.setValue(clone);
|
|
295
|
+
const inflight = inflightQueries.get(queryKey);
|
|
296
|
+
if (inflight) {
|
|
297
|
+
inflightQueries.delete(queryKey);
|
|
298
|
+
inflight.abortController.abort("Deleted");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function wrapLoading(queryKey, fetchData) {
|
|
302
|
+
setQueryValue(queryKey, DEFAULT_QUERY_STATE_ENTRY);
|
|
303
|
+
try {
|
|
304
|
+
const data = await fetchData();
|
|
305
|
+
if (getQueryValue(queryKey)?.loading) {
|
|
306
|
+
const now = Date.now();
|
|
307
|
+
setQueryValue(queryKey, {
|
|
308
|
+
data,
|
|
309
|
+
loading: false,
|
|
310
|
+
lastUpdatedMs: now,
|
|
311
|
+
dataUpdatedMs: now
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return data;
|
|
315
|
+
} catch (e) {
|
|
316
|
+
if (getQueryValue(queryKey)?.loading) {
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
setQueryValue(queryKey, {
|
|
319
|
+
loading: false,
|
|
320
|
+
lastUpdatedMs: now,
|
|
321
|
+
error: e,
|
|
322
|
+
errorUpdatedMs: now
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
throw e;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function waitForStateInit(queryKey) {
|
|
329
|
+
const startMs = Date.now();
|
|
330
|
+
await queryState.readyPromise;
|
|
331
|
+
const waitedMs = Date.now() - startMs;
|
|
332
|
+
if (waitedMs) {
|
|
333
|
+
log(`${queryKey}: Waited for ${waitedMs}ms before data is ready`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async function getCachedOrFetch(queryKey, ...args) {
|
|
337
|
+
await waitForStateInit(queryKey);
|
|
338
|
+
const cached = getQueryValue(queryKey);
|
|
339
|
+
if (cached?.data !== void 0 && cached.dataUpdatedMs && !isExpired(cached.dataUpdatedMs)) {
|
|
340
|
+
if (!isStale(cached.dataUpdatedMs)) {
|
|
341
|
+
log(`Return fresh data ${queryKey} from cache without fetching`);
|
|
342
|
+
return cached.data;
|
|
343
|
+
}
|
|
344
|
+
void dedupedFetch(queryKey, "query", ...args);
|
|
345
|
+
log(`Returning stale data for ${queryKey}`);
|
|
346
|
+
return cached.data;
|
|
347
|
+
}
|
|
348
|
+
return await dedupedFetch(queryKey, "query", ...args);
|
|
349
|
+
}
|
|
350
|
+
async function getCachedStateOrFetch(queryKey, ...args) {
|
|
351
|
+
await waitForStateInit(queryKey);
|
|
352
|
+
const cached = getQueryValue(queryKey);
|
|
353
|
+
if (cached?.data !== void 0 && cached.dataUpdatedMs && !isExpired(cached.dataUpdatedMs)) {
|
|
354
|
+
if (!isStale(cached.dataUpdatedMs)) {
|
|
355
|
+
log(`Return fresh data ${queryKey} from cache without fetching`);
|
|
356
|
+
return cached;
|
|
357
|
+
}
|
|
358
|
+
void dedupedFetch(queryKey, "query", ...args);
|
|
359
|
+
log(`Returning stale data for ${queryKey}`);
|
|
360
|
+
return cached;
|
|
361
|
+
}
|
|
362
|
+
if (cached?.error !== void 0 && cached.errorUpdatedMs && !isExpired(cached.errorUpdatedMs)) {
|
|
363
|
+
if (!isStale(cached.errorUpdatedMs)) {
|
|
364
|
+
log(`Return fresh error ${queryKey} from cache without fetching`);
|
|
365
|
+
return cached;
|
|
366
|
+
}
|
|
367
|
+
void dedupedFetch(queryKey, "query", ...args);
|
|
368
|
+
log(`Returning stale error for ${queryKey}`);
|
|
369
|
+
return cached;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
await dedupedFetch(queryKey, "query", ...args);
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
return getQueryValue(queryKey) ?? DEFAULT_QUERY_STATE_ENTRY;
|
|
252
376
|
}
|
|
253
377
|
async function startFetching(queryKey, queryType, args) {
|
|
254
378
|
log(`Start fetching ${queryKey}, queryType=${queryType}`);
|
|
@@ -272,197 +396,69 @@ function sharedQuery2(options) {
|
|
|
272
396
|
);
|
|
273
397
|
return inflightQuery.promise;
|
|
274
398
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
399
|
+
return wrapLoading(queryKey, () => {
|
|
400
|
+
const promise = startFetching(queryKey, queryType, args);
|
|
401
|
+
const abortController = new AbortController();
|
|
402
|
+
inflightQueries.set(queryKey, { promise, abortController });
|
|
403
|
+
return promise;
|
|
404
|
+
});
|
|
279
405
|
}
|
|
280
|
-
|
|
406
|
+
async function fetch(queryKey, ...args) {
|
|
407
|
+
return await dedupedFetch(queryKey, "query", ...args);
|
|
408
|
+
}
|
|
409
|
+
async function refetch(queryKey, ...args) {
|
|
410
|
+
return await dedupedFetch(queryKey, "refetch", ...args);
|
|
411
|
+
}
|
|
412
|
+
function clear() {
|
|
413
|
+
queryState.delete();
|
|
414
|
+
for (const { abortController } of Array.from(inflightQueries.values())) {
|
|
415
|
+
abortController.abort("Cleared");
|
|
416
|
+
}
|
|
417
|
+
inflightQueries.clear();
|
|
418
|
+
}
|
|
419
|
+
function mount(queryKey) {
|
|
420
|
+
mountedKeys.set(queryKey, (mountedKeys.get(queryKey) ?? 0) + 1);
|
|
421
|
+
}
|
|
422
|
+
function unmount(queryKey) {
|
|
423
|
+
const count = mountedKeys.get(queryKey);
|
|
424
|
+
if (count === 1) {
|
|
425
|
+
mountedKeys.delete(queryKey);
|
|
426
|
+
} else {
|
|
427
|
+
mountedKeys.set(queryKey, (count ?? 0) - 1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
281
431
|
queryName,
|
|
282
432
|
queryFn,
|
|
283
433
|
refetchFn,
|
|
284
434
|
expiryMs,
|
|
285
435
|
staleMs,
|
|
286
|
-
refetchOnStale,
|
|
287
436
|
maxSize,
|
|
288
437
|
maxBytes,
|
|
289
438
|
keepLastNonEmptyData,
|
|
290
439
|
log,
|
|
291
440
|
queryState,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
* Note that once you get the controller, you can also get the signal easily
|
|
304
|
-
* using `controller.signal`. So if you need both the controller and the
|
|
305
|
-
* signal, just use this function.
|
|
306
|
-
*/
|
|
307
|
-
getAbortController(args) {
|
|
308
|
-
const queryKey = obj.getQueryKey(args);
|
|
309
|
-
return obj.getAbortControllerByKey(queryKey);
|
|
310
|
-
},
|
|
311
|
-
getAbortControllerByKey(queryKey) {
|
|
312
|
-
return inflightQueries.get(queryKey)?.abortController ?? null;
|
|
313
|
-
},
|
|
314
|
-
/**
|
|
315
|
-
* Get the AbortSignal to check for query abortions.
|
|
316
|
-
*
|
|
317
|
-
* Example using fetch:
|
|
318
|
-
* ```
|
|
319
|
-
* const getUserQuery = sharedQuery({
|
|
320
|
-
* queryName: "getUser",
|
|
321
|
-
* queryFn: (userId: string) => {
|
|
322
|
-
* // Get the signal for this current execution.
|
|
323
|
-
* const signal = getUserQuery.getAbortSignal([userId]);
|
|
324
|
-
*
|
|
325
|
-
* // Pass the signal to fetch so that it can be aborted.
|
|
326
|
-
* const response = await fetch(`/users/${userId}`, { signal });
|
|
327
|
-
*
|
|
328
|
-
* // Check for errors.
|
|
329
|
-
* if (!response.ok) {
|
|
330
|
-
* throw new Error(response.statusText);
|
|
331
|
-
* }
|
|
332
|
-
*
|
|
333
|
-
* // Return the data.
|
|
334
|
-
* return await response.json();
|
|
335
|
-
* },
|
|
336
|
-
* });
|
|
337
|
-
* ```
|
|
338
|
-
*
|
|
339
|
-
* Example using `http.ts`:
|
|
340
|
-
* ```
|
|
341
|
-
* const getUserQuery = sharedQuery({
|
|
342
|
-
* queryName: "getUser",
|
|
343
|
-
* queryFn: (userId: string) => {
|
|
344
|
-
* // Get the signal for this current execution.
|
|
345
|
-
* const signal = getUserQuery.getAbortSignal([userId]);
|
|
346
|
-
*
|
|
347
|
-
* // Pass the signal to fetch so that it can be aborted.
|
|
348
|
-
* const response = await http.get(`/users/${userId}`, { signal });
|
|
349
|
-
* return await response.json();
|
|
350
|
-
* },
|
|
351
|
-
* });
|
|
352
|
-
* ```
|
|
353
|
-
*/
|
|
354
|
-
getAbortSignal(args) {
|
|
355
|
-
const queryKey = obj.getQueryKey(args);
|
|
356
|
-
return obj.getAbortSignalByKey(queryKey);
|
|
357
|
-
},
|
|
358
|
-
getAbortSignalByKey(queryKey) {
|
|
359
|
-
return obj.getAbortControllerByKey(queryKey)?.signal ?? null;
|
|
360
|
-
},
|
|
361
|
-
/** For cached data, we need to return the correct timestamps as well. */
|
|
362
|
-
async getCachedOrFetch(...args) {
|
|
363
|
-
const startMs = Date.now();
|
|
364
|
-
await queryState.readyPromise;
|
|
365
|
-
const waitedMs = Date.now() - startMs;
|
|
366
|
-
const queryKey = obj.getQueryKey(args);
|
|
367
|
-
const cached = queryState.getSnapshot()?.[queryKey];
|
|
368
|
-
if (waitedMs) {
|
|
369
|
-
log(`${queryKey}: Waited for ${waitedMs}ms before data is ready`);
|
|
370
|
-
}
|
|
371
|
-
if (cached?.data && !isExpired(cached.dataUpdatedMs ?? 0)) {
|
|
372
|
-
if (!isStale(cached.dataUpdatedMs ?? 0)) {
|
|
373
|
-
log(`Return fresh data ${queryKey} from cache without fetching`);
|
|
374
|
-
return cached;
|
|
375
|
-
}
|
|
376
|
-
if (refetchOnStale) {
|
|
377
|
-
log(`refetchOnStale for ${queryKey}`);
|
|
378
|
-
void dedupedFetch(queryKey, "query", ...args);
|
|
379
|
-
}
|
|
380
|
-
log(`Returning stale data for ${queryKey}`);
|
|
381
|
-
return cached;
|
|
382
|
-
}
|
|
383
|
-
const data = await dedupedFetch(queryKey, "query", ...args);
|
|
384
|
-
const now = Date.now();
|
|
385
|
-
return {
|
|
386
|
-
// Don't keep old error.
|
|
387
|
-
lastUpdatedMs: now,
|
|
388
|
-
loading: false,
|
|
389
|
-
data,
|
|
390
|
-
dataUpdatedMs: now
|
|
391
|
-
};
|
|
392
|
-
},
|
|
393
|
-
/** Get the entire stored entry for a query key. */
|
|
394
|
-
getQueryValue(queryKey) {
|
|
395
|
-
return queryState.getSnapshot()?.[queryKey];
|
|
396
|
-
},
|
|
397
|
-
/** Get the current stored data for a query key. */
|
|
398
|
-
getData(queryKey) {
|
|
399
|
-
const stateValue = queryState.getSnapshot()?.[queryKey];
|
|
400
|
-
if (stateValue?.lastUpdatedMs) {
|
|
401
|
-
if (isExpired(stateValue.lastUpdatedMs)) {
|
|
402
|
-
obj.deleteData(queryKey);
|
|
403
|
-
return void 0;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return stateValue?.data;
|
|
407
|
-
},
|
|
408
|
-
/** Set the data directly if the user obtained it from somewhere else. */
|
|
441
|
+
getQueryKey,
|
|
442
|
+
getAbortController,
|
|
443
|
+
getAbortControllerByKey,
|
|
444
|
+
getAbortSignal,
|
|
445
|
+
getAbortSignalByKey,
|
|
446
|
+
isStale,
|
|
447
|
+
isExpired,
|
|
448
|
+
enforceSizeLimit,
|
|
449
|
+
getQueryValue,
|
|
450
|
+
setQueryValue,
|
|
451
|
+
getData,
|
|
409
452
|
setData,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Perform an explicit refetch. If `options.refetchFn` was not specified,
|
|
420
|
-
* then this would be identical to calling `obj.updateFromSource()`.
|
|
421
|
-
*/
|
|
422
|
-
async refetch(...args) {
|
|
423
|
-
const key = obj.getQueryKey(args);
|
|
424
|
-
return await dedupedFetch(key, "refetch", ...args);
|
|
425
|
-
},
|
|
426
|
-
/** Delete the cached data for one set of arguments. */
|
|
427
|
-
deleteData(queryKey) {
|
|
428
|
-
const record = queryState.getSnapshot();
|
|
429
|
-
const clone = { ...record };
|
|
430
|
-
delete clone[queryKey];
|
|
431
|
-
queryState.setValue(clone);
|
|
432
|
-
const inflight = inflightQueries.get(queryKey);
|
|
433
|
-
if (inflight) {
|
|
434
|
-
inflightQueries.delete(queryKey);
|
|
435
|
-
inflight.abortController.abort("Deleted");
|
|
436
|
-
}
|
|
437
|
-
},
|
|
438
|
-
/** Delete all currently cached data & all inflight promises. */
|
|
439
|
-
clear() {
|
|
440
|
-
queryState.delete();
|
|
441
|
-
for (const { abortController } of Array.from(inflightQueries.values())) {
|
|
442
|
-
abortController.abort("Cleared");
|
|
443
|
-
}
|
|
444
|
-
inflightQueries.clear();
|
|
445
|
-
},
|
|
446
|
-
/** Keep track of mounted keys. */
|
|
447
|
-
mount(queryKey) {
|
|
448
|
-
mountedKeys.set(queryKey, (mountedKeys.get(queryKey) ?? 0) + 1);
|
|
449
|
-
},
|
|
450
|
-
/** Clean up mounted keys. */
|
|
451
|
-
unmount(queryKey) {
|
|
452
|
-
const count = mountedKeys.get(queryKey);
|
|
453
|
-
if (count === 1) {
|
|
454
|
-
mountedKeys.delete(queryKey);
|
|
455
|
-
} else {
|
|
456
|
-
mountedKeys.set(queryKey, (count ?? 0) - 1);
|
|
457
|
-
}
|
|
458
|
-
},
|
|
459
|
-
/**
|
|
460
|
-
* Discard the oldest entries if the size exceeds the limit. The last
|
|
461
|
-
* remaining record cannot be deleted no matter what the limit is.
|
|
462
|
-
*/
|
|
463
|
-
enforceSizeLimit
|
|
453
|
+
deleteData,
|
|
454
|
+
getCachedOrFetch,
|
|
455
|
+
getCachedStateOrFetch,
|
|
456
|
+
fetch,
|
|
457
|
+
refetch,
|
|
458
|
+
clear,
|
|
459
|
+
mount,
|
|
460
|
+
unmount
|
|
464
461
|
};
|
|
465
|
-
return obj;
|
|
466
462
|
}
|
|
467
463
|
function sharedQuery(options) {
|
|
468
464
|
return sharedQuery1(options);
|
|
@@ -473,7 +469,7 @@ var DEFAULT_QUERY_STATE_ENTRY = {
|
|
|
473
469
|
};
|
|
474
470
|
function useSharedQuery(query, ...args) {
|
|
475
471
|
const stableArgs = useDeepMemo(args);
|
|
476
|
-
const [queryState
|
|
472
|
+
const [queryState] = useSharedState(query.queryState);
|
|
477
473
|
const isMounted = useRef(true);
|
|
478
474
|
const initQueryKeyDone = useRef("");
|
|
479
475
|
const queryKey = useMemo(
|
|
@@ -481,75 +477,12 @@ function useSharedQuery(query, ...args) {
|
|
|
481
477
|
[query, stableArgs]
|
|
482
478
|
);
|
|
483
479
|
const queryStateValue = queryState[queryKey] ?? DEFAULT_QUERY_STATE_ENTRY;
|
|
484
|
-
const setQueryStateValue = useCallback(
|
|
485
|
-
(next) => {
|
|
486
|
-
setQueryState((prev) => {
|
|
487
|
-
const clone = { ...prev };
|
|
488
|
-
clone[queryKey] = typeof next === "function" ? next(clone[queryKey] ?? DEFAULT_QUERY_STATE_ENTRY) : next;
|
|
489
|
-
return clone;
|
|
490
|
-
});
|
|
491
|
-
},
|
|
492
|
-
[queryKey, setQueryState]
|
|
493
|
-
);
|
|
494
480
|
const [lastNonEmptyData, setLastNonEmptyData] = useState();
|
|
495
481
|
useEffect(() => {
|
|
496
482
|
if (query.keepLastNonEmptyData && queryStateValue.data !== void 0) {
|
|
497
483
|
setLastNonEmptyData(queryStateValue.data);
|
|
498
484
|
}
|
|
499
485
|
}, [query.keepLastNonEmptyData, queryStateValue.data]);
|
|
500
|
-
const executeQuery = useCallback(
|
|
501
|
-
async (isRefetch) => {
|
|
502
|
-
query.log(
|
|
503
|
-
`Begin executing shared query ${queryKey}, isRefetch=${isRefetch}`
|
|
504
|
-
);
|
|
505
|
-
if (!isMounted.current) return;
|
|
506
|
-
setQueryStateValue((prev) => ({
|
|
507
|
-
// Keep old data and error.
|
|
508
|
-
...prev,
|
|
509
|
-
lastUpdatedMs: Date.now(),
|
|
510
|
-
loading: true
|
|
511
|
-
}));
|
|
512
|
-
try {
|
|
513
|
-
let state;
|
|
514
|
-
if (isRefetch) {
|
|
515
|
-
const data = await query.refetch(...stableArgs);
|
|
516
|
-
const now = Date.now();
|
|
517
|
-
state = {
|
|
518
|
-
// Don't keep old error.
|
|
519
|
-
lastUpdatedMs: now,
|
|
520
|
-
loading: false,
|
|
521
|
-
data,
|
|
522
|
-
dataUpdatedMs: now
|
|
523
|
-
};
|
|
524
|
-
} else {
|
|
525
|
-
state = await query.getCachedOrFetch(...stableArgs);
|
|
526
|
-
}
|
|
527
|
-
if (!isMounted.current) return;
|
|
528
|
-
setQueryStateValue({ ...state, loading: false });
|
|
529
|
-
return state.data;
|
|
530
|
-
} catch (e) {
|
|
531
|
-
if (!isMounted.current) return;
|
|
532
|
-
const now = Date.now();
|
|
533
|
-
setQueryStateValue((prev) => ({
|
|
534
|
-
// Keep old data.
|
|
535
|
-
...prev,
|
|
536
|
-
lastUpdatedMs: now,
|
|
537
|
-
loading: false,
|
|
538
|
-
error: e,
|
|
539
|
-
errorUpdatedMs: now
|
|
540
|
-
}));
|
|
541
|
-
}
|
|
542
|
-
},
|
|
543
|
-
[query, queryKey, setQueryStateValue, stableArgs]
|
|
544
|
-
);
|
|
545
|
-
useEffect(() => {
|
|
546
|
-
if (initQueryKeyDone.current === queryKey) return;
|
|
547
|
-
initQueryKeyDone.current = queryKey;
|
|
548
|
-
if (queryStateValue.data !== void 0) {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
void executeQuery(false);
|
|
552
|
-
}, [executeQuery, queryKey, queryStateValue.data]);
|
|
553
486
|
useEffect(() => {
|
|
554
487
|
isMounted.current = true;
|
|
555
488
|
query.mount(queryKey);
|
|
@@ -558,6 +491,11 @@ function useSharedQuery(query, ...args) {
|
|
|
558
491
|
query.unmount(queryKey);
|
|
559
492
|
};
|
|
560
493
|
}, [query, queryKey]);
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
if (initQueryKeyDone.current === queryKey) return;
|
|
496
|
+
initQueryKeyDone.current = queryKey;
|
|
497
|
+
void query.getCachedStateOrFetch(queryKey, ...stableArgs);
|
|
498
|
+
}, [query, queryKey, stableArgs]);
|
|
561
499
|
return useMemo(() => {
|
|
562
500
|
return {
|
|
563
501
|
...queryStateValue,
|
|
@@ -573,14 +511,13 @@ function useSharedQuery(query, ...args) {
|
|
|
573
511
|
return Boolean(controller);
|
|
574
512
|
},
|
|
575
513
|
getCachedOrFetch: async () => {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
);
|
|
514
|
+
return await query.getCachedOrFetch(queryKey, ...stableArgs);
|
|
515
|
+
},
|
|
516
|
+
getCachedStateOrFetch: async () => {
|
|
517
|
+
return await query.getCachedStateOrFetch(queryKey, ...stableArgs);
|
|
581
518
|
},
|
|
582
519
|
refetch: () => {
|
|
583
|
-
return
|
|
520
|
+
return query.refetch(queryKey, ...stableArgs);
|
|
584
521
|
},
|
|
585
522
|
setData: (data, dataUpdatedMs) => {
|
|
586
523
|
query.setData(queryKey, data, dataUpdatedMs);
|
|
@@ -589,14 +526,7 @@ function useSharedQuery(query, ...args) {
|
|
|
589
526
|
query.deleteData(queryKey);
|
|
590
527
|
}
|
|
591
528
|
};
|
|
592
|
-
}, [
|
|
593
|
-
executeQuery,
|
|
594
|
-
lastNonEmptyData,
|
|
595
|
-
query,
|
|
596
|
-
queryKey,
|
|
597
|
-
queryStateValue,
|
|
598
|
-
stableArgs
|
|
599
|
-
]);
|
|
529
|
+
}, [lastNonEmptyData, query, queryKey, queryStateValue, stableArgs]);
|
|
600
530
|
}
|
|
601
531
|
|
|
602
532
|
export {
|