@choksheak/react-utils 0.4.1 → 0.4.2
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/NoSSR.cjs +1 -1
- package/NoSSR.d.ts +7 -3
- package/NoSSR.min.cjs +1 -1
- package/NoSSR.min.mjs +1 -1
- package/NoSSR.mjs +1 -1
- package/{chunk-33UMQLKY.mjs → chunk-4235I4VO.mjs} +1 -10
- package/{chunk-GPV6JIQK.mjs → chunk-4J7TN5JN.mjs} +1 -1
- package/chunk-BBCGQMWI.min.cjs +2 -0
- package/chunk-BBCGQMWI.min.cjs.map +1 -0
- package/{chunk-5DO7KC3L.cjs → chunk-CYTO5EZE.cjs} +2 -11
- package/chunk-GKNOICX3.cjs +561 -0
- package/chunk-HRE7YPUJ.min.mjs +2 -0
- package/chunk-IS3BZTWE.cjs +35 -0
- package/chunk-JOHZV3FD.min.cjs +2 -0
- package/{chunk-F5L32IUC.min.cjs.map → chunk-JOHZV3FD.min.cjs.map} +1 -1
- package/chunk-KGN4HQPP.min.cjs +2 -0
- package/chunk-KGN4HQPP.min.cjs.map +1 -0
- package/chunk-NL4FU2MJ.min.mjs +2 -0
- package/chunk-NL4FU2MJ.min.mjs.map +1 -0
- package/chunk-R7POPVJR.mjs +35 -0
- package/chunk-TI3FMEYP.min.mjs +2 -0
- package/{chunk-RM4ZGGZ2.min.mjs.map → chunk-TI3FMEYP.min.mjs.map} +1 -1
- package/chunk-U7RD7B3N.min.mjs +2 -0
- package/chunk-U7RD7B3N.min.mjs.map +1 -0
- package/{chunk-WVLPJPWG.cjs → chunk-WXLUEDBO.cjs} +3 -3
- package/chunk-X6EKF4N7.mjs +561 -0
- package/chunk-ZCDXPV3A.min.cjs +2 -0
- package/chunk-ZCDXPV3A.min.cjs.map +1 -0
- package/dist-O6NNX4Y3.min.mjs +2 -0
- package/dist-O6NNX4Y3.min.mjs.map +1 -0
- package/dist-SEGTJ72R.min.cjs +2 -0
- package/dist-SEGTJ72R.min.cjs.map +1 -0
- package/dist-UKP7SPWZ.cjs +545 -0
- package/dist-YXYQDOH5.mjs +545 -0
- package/index.cjs +43 -0
- package/index.d.cts +8 -0
- package/index.d.ts +8 -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/index.mjs +43 -0
- package/magic-string.es-5QZEPJXJ.min.mjs +14 -0
- package/magic-string.es-5QZEPJXJ.min.mjs.map +1 -0
- package/magic-string.es-EREYSDLL.mjs +1308 -0
- package/magic-string.es-FXUUD55Y.min.cjs +14 -0
- package/magic-string.es-FXUUD55Y.min.cjs.map +1 -0
- package/magic-string.es-HC5PDAU3.cjs +1308 -0
- package/package.json +71 -168
- package/sharedQuery.cjs +8 -555
- package/{sharedQuery.d.mts → sharedQuery.d.cts} +2 -2
- package/sharedQuery.d.ts +23 -17
- package/sharedQuery.min.cjs +1 -1
- package/sharedQuery.min.cjs.map +1 -1
- package/sharedQuery.min.mjs +1 -1
- package/sharedQuery.min.mjs.map +1 -1
- package/sharedQuery.mjs +9 -556
- package/sharedState.cjs +1 -1
- package/{sharedState.d.mts → sharedState.d.cts} +1 -1
- package/sharedState.d.ts +17 -11
- package/sharedState.min.cjs +1 -1
- package/sharedState.min.mjs +1 -1
- package/sharedState.mjs +1 -1
- package/storage.cjs +1 -1
- package/storage.d.ts +13 -9
- package/storage.min.cjs +1 -1
- package/storage.min.mjs +1 -1
- package/storage.mjs +1 -1
- package/stringify.cjs +3 -5
- package/stringify.d.ts +3 -1
- package/stringify.min.cjs +1 -1
- package/stringify.min.cjs.map +1 -1
- package/stringify.min.mjs +1 -1
- package/stringify.mjs +2 -4
- package/useDeepMemo.cjs +3 -3
- package/useDeepMemo.d.ts +3 -1
- package/useDeepMemo.min.cjs +1 -1
- package/useDeepMemo.min.mjs +1 -1
- package/useDeepMemo.mjs +2 -2
- package/chunk-74BS2FAO.cjs +0 -12
- package/chunk-F5L32IUC.min.cjs +0 -2
- package/chunk-KE3RXVMS.min.cjs +0 -2
- package/chunk-KE3RXVMS.min.cjs.map +0 -1
- package/chunk-MWDH7YON.min.mjs +0 -2
- package/chunk-RM4ZGGZ2.min.mjs +0 -2
- package/chunk-TEZPF6BG.min.cjs +0 -2
- package/chunk-TEZPF6BG.min.cjs.map +0 -1
- package/chunk-V7L3UICC.min.mjs +0 -2
- package/chunk-V7L3UICC.min.mjs.map +0 -1
- package/chunk-X6BYQHVC.mjs +0 -12
- /package/{NoSSR.d.mts → NoSSR.d.cts} +0 -0
- /package/{chunk-MWDH7YON.min.mjs.map → chunk-HRE7YPUJ.min.mjs.map} +0 -0
- /package/{storage.d.mts → storage.d.cts} +0 -0
- /package/{stringify.d.mts → stringify.d.cts} +0 -0
- /package/{useDeepMemo.d.mts → useDeepMemo.d.cts} +0 -0
package/sharedQuery.cjs
CHANGED
|
@@ -1,564 +1,17 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var _chunk3X25NN6Xcjs = require('./chunk-3X25NN6X.cjs');
|
|
5
|
-
require('./chunk-5PDGU37E.cjs');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
var _chunk5DO7KC3Lcjs = require('./chunk-5DO7KC3L.cjs');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
var _chunkWVLPJPWGcjs = require('./chunk-WVLPJPWG.cjs');
|
|
13
|
-
require('./chunk-74BS2FAO.cjs');
|
|
14
4
|
|
|
15
|
-
// src/sharedQuery.ts
|
|
16
|
-
var _http = require('@choksheak/ts-utils/http');
|
|
17
|
-
var _objectsizeof = require('object-sizeof'); var _objectsizeof2 = _interopRequireDefault(_objectsizeof);
|
|
18
5
|
|
|
6
|
+
var _chunkGKNOICX3cjs = require('./chunk-GKNOICX3.cjs');
|
|
7
|
+
require('./chunk-3X25NN6X.cjs');
|
|
8
|
+
require('./chunk-5PDGU37E.cjs');
|
|
9
|
+
require('./chunk-CYTO5EZE.cjs');
|
|
10
|
+
require('./chunk-WXLUEDBO.cjs');
|
|
11
|
+
require('./chunk-IS3BZTWE.cjs');
|
|
19
12
|
|
|
20
13
|
|
|
21
14
|
|
|
22
15
|
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
var _xxhashjs = require('xxhashjs'); var _xxhashjs2 = _interopRequireDefault(_xxhashjs);
|
|
26
|
-
_chunk5DO7KC3Lcjs.init_stringify.call(void 0, );
|
|
27
|
-
var SharedQueryConfig = {
|
|
28
|
-
/** Refetch on every page load. */
|
|
29
|
-
staleMs: 0,
|
|
30
|
-
/** Keep in cache for a long time. */
|
|
31
|
-
expiryMs: 30 * 864e5,
|
|
32
|
-
/** Always refetch automatically if the data is stale. */
|
|
33
|
-
refetchOnStale: true,
|
|
34
|
-
/** No default persistence configured. */
|
|
35
|
-
persistTo: void 0,
|
|
36
|
-
/** 100 is not too large, but please tweak accordingly. */
|
|
37
|
-
maxSize: 100,
|
|
38
|
-
/** 100kb limit before discarding old records. */
|
|
39
|
-
maxBytes: 1e5,
|
|
40
|
-
/** Default to log to console. */
|
|
41
|
-
log: (...args) => console.log("[sharedQuery]", ...args)
|
|
42
|
-
};
|
|
43
|
-
function configureSharedQuery(config) {
|
|
44
|
-
Object.assign(SharedQueryConfig, config);
|
|
45
|
-
}
|
|
46
|
-
var seenQueryNames = /* @__PURE__ */ new Set();
|
|
47
|
-
function sharedQuery(options) {
|
|
48
|
-
const url = options.url || "";
|
|
49
|
-
const queryName = options.queryName || url;
|
|
50
|
-
if (!queryName) {
|
|
51
|
-
throw new Error(`Either queryName or url must be specified.`);
|
|
52
|
-
}
|
|
53
|
-
if (seenQueryNames.has(queryName)) {
|
|
54
|
-
throw new Error(`Duplicate shared query name "${queryName}"`);
|
|
55
|
-
}
|
|
56
|
-
seenQueryNames.add(queryName);
|
|
57
|
-
const inflightQueries = /* @__PURE__ */ new Map();
|
|
58
|
-
const mountedKeys = /* @__PURE__ */ new Map();
|
|
59
|
-
let queryFn;
|
|
60
|
-
if (options.queryFn) {
|
|
61
|
-
queryFn = options.queryFn;
|
|
62
|
-
} else if (url) {
|
|
63
|
-
const fetcher = _http.http.use(_http.retries.call(void 0, ));
|
|
64
|
-
queryFn = async (...args) => {
|
|
65
|
-
let params = "";
|
|
66
|
-
const firstArg = _optionalChain([args, 'optionalAccess', _ => _[0]]);
|
|
67
|
-
if (firstArg) {
|
|
68
|
-
if (typeof firstArg === "string") {
|
|
69
|
-
params = firstArg;
|
|
70
|
-
} else if (typeof firstArg === "object") {
|
|
71
|
-
const entries = [];
|
|
72
|
-
for (const [k, v] of Object.entries(firstArg)) {
|
|
73
|
-
if (v === void 0) continue;
|
|
74
|
-
if (Array.isArray(v)) {
|
|
75
|
-
for (const item of v) {
|
|
76
|
-
if (item === void 0) continue;
|
|
77
|
-
entries.push([k, String(item)]);
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
entries.push([k, String(v)]);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
params = new URLSearchParams(entries).toString();
|
|
84
|
-
}
|
|
85
|
-
if (params) {
|
|
86
|
-
if (url.includes("?")) {
|
|
87
|
-
params = "&" + params;
|
|
88
|
-
} else {
|
|
89
|
-
params = "?" + params;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const response = await fetcher.get(url + params);
|
|
94
|
-
return await response.json();
|
|
95
|
-
};
|
|
96
|
-
} else {
|
|
97
|
-
throw new Error(`Either queryFn or url must be specified`);
|
|
98
|
-
}
|
|
99
|
-
const refetchFn = options.refetchFn;
|
|
100
|
-
const staleMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _2 => _2.staleMs]), () => ( SharedQueryConfig.staleMs));
|
|
101
|
-
const expiryMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _3 => _3.expiryMs]), () => ( SharedQueryConfig.expiryMs));
|
|
102
|
-
const refetchOnStale = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _4 => _4.refetchOnStale]), () => ( SharedQueryConfig.refetchOnStale));
|
|
103
|
-
const store = options.store;
|
|
104
|
-
if (store) {
|
|
105
|
-
if ("persistTo" in store && store.isValid) {
|
|
106
|
-
const isValid = store.isValid;
|
|
107
|
-
options = {
|
|
108
|
-
...options,
|
|
109
|
-
store: {
|
|
110
|
-
...store,
|
|
111
|
-
isValid: (u) => {
|
|
112
|
-
if (!u || typeof u !== "object") {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
for (const v of Object.values(u)) {
|
|
116
|
-
if (!v || typeof v !== "object" || !("data" in v) || !("dataUpdatedMs" in v) || !("lastUpdatedMs" in v) || typeof v.dataUpdatedMs !== "number" || typeof v.lastUpdatedMs !== "number" || !isValid(v.data)) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
const persistTo = _nullishCoalesce(options.persistTo, () => ( SharedQueryConfig.persistTo));
|
|
127
|
-
if (persistTo) {
|
|
128
|
-
options = {
|
|
129
|
-
...options,
|
|
130
|
-
store: {
|
|
131
|
-
persistTo,
|
|
132
|
-
key: queryName,
|
|
133
|
-
expiryMs
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const maxSize = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _5 => _5.maxSize]), () => ( SharedQueryConfig.maxSize));
|
|
139
|
-
const maxBytes = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _6 => _6.maxBytes]), () => ( SharedQueryConfig.maxBytes));
|
|
140
|
-
const keepLastNonEmptyData = Boolean(_optionalChain([options, 'optionalAccess', _7 => _7.keepLastNonEmptyData]));
|
|
141
|
-
const log = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _8 => _8.log]), () => ( SharedQueryConfig.log));
|
|
142
|
-
const queryState = _chunk3X25NN6Xcjs.sharedState.call(void 0,
|
|
143
|
-
{},
|
|
144
|
-
// defaultValue
|
|
145
|
-
options
|
|
146
|
-
);
|
|
147
|
-
function isStale(dataUpdatedMs) {
|
|
148
|
-
if (staleMs === 0) return true;
|
|
149
|
-
const ageMs = Date.now() - dataUpdatedMs;
|
|
150
|
-
return ageMs > staleMs;
|
|
151
|
-
}
|
|
152
|
-
function isExpired(dataUpdatedMs) {
|
|
153
|
-
if (expiryMs === 0) return false;
|
|
154
|
-
const ageMs = Date.now() - dataUpdatedMs;
|
|
155
|
-
return ageMs > expiryMs;
|
|
156
|
-
}
|
|
157
|
-
function enforceSizeLimit() {
|
|
158
|
-
if (!maxSize && !maxBytes) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const oldState = queryState.getSnapshot();
|
|
162
|
-
const oldSize = Object.keys(oldState).length;
|
|
163
|
-
if (oldSize <= 1) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const oldByteSize = _objectsizeof2.default.call(void 0, oldState);
|
|
167
|
-
let numToCut = maxSize ? oldSize - maxSize : 0;
|
|
168
|
-
let bytesToCut = maxBytes ? oldByteSize - maxBytes : 0;
|
|
169
|
-
if (numToCut <= 0 && bytesToCut <= 0) {
|
|
170
|
-
log(
|
|
171
|
-
`No need to trim data for ${queryName}: size=${oldSize} (limit=${maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${maxBytes.toLocaleString()})`
|
|
172
|
-
);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
log(
|
|
176
|
-
`Need to trim ${queryName}: numToCut=${numToCut}, bytesToCut=${bytesToCut}`
|
|
177
|
-
);
|
|
178
|
-
const newState = { ...oldState };
|
|
179
|
-
let needUpdate = false;
|
|
180
|
-
const deleteKeyIfNotMounted = (key, expired) => {
|
|
181
|
-
if (mountedKeys.has(key)) {
|
|
182
|
-
log(`Cannot clean up ${key} as it is mounted`);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const byteSize = _objectsizeof2.default.call(void 0, key) + _objectsizeof2.default.call(void 0, newState[key]);
|
|
186
|
-
log(
|
|
187
|
-
`Cleaning up unmounted${expired ? ", expired" : ""} ${key} (byteSize=${byteSize.toLocaleString()})`
|
|
188
|
-
);
|
|
189
|
-
numToCut--;
|
|
190
|
-
bytesToCut -= byteSize;
|
|
191
|
-
delete newState[key];
|
|
192
|
-
needUpdate = true;
|
|
193
|
-
};
|
|
194
|
-
let entriesByTimeAscending = Object.entries(newState).sort(
|
|
195
|
-
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
196
|
-
);
|
|
197
|
-
for (const [key] of entriesByTimeAscending) {
|
|
198
|
-
if (!isExpired(newState[key].lastUpdatedMs)) {
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
deleteKeyIfNotMounted(key, true);
|
|
202
|
-
}
|
|
203
|
-
if (numToCut > 0 || bytesToCut > 0) {
|
|
204
|
-
if (needUpdate) {
|
|
205
|
-
entriesByTimeAscending = Object.entries(newState).sort(
|
|
206
|
-
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
for (const [key] of entriesByTimeAscending) {
|
|
210
|
-
deleteKeyIfNotMounted(key, false);
|
|
211
|
-
if (numToCut <= 0 && bytesToCut <= 0) {
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (!needUpdate) {
|
|
217
|
-
log(
|
|
218
|
-
`No trimmable entries found for ${queryName}: size=${oldSize} (limit=${maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${maxBytes.toLocaleString()})`
|
|
219
|
-
);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
queryState.setValue(newState);
|
|
223
|
-
log(
|
|
224
|
-
`Trimmed data for ${queryName}: size=[${oldSize} -> ${Object.keys(newState).length}] (limit=${maxSize}), byteSize=[${oldByteSize.toLocaleString()} -> ${_objectsizeof2.default.call(void 0, newState).toLocaleString()}] (limit=${maxBytes.toLocaleString()})`
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
function setData(queryKey, data, dataUpdatedMs = Date.now()) {
|
|
228
|
-
const entry = {
|
|
229
|
-
lastUpdatedMs: Date.now(),
|
|
230
|
-
loading: false,
|
|
231
|
-
data,
|
|
232
|
-
dataUpdatedMs
|
|
233
|
-
};
|
|
234
|
-
queryState.setValue((prev) => ({ ...prev, [queryKey]: entry }));
|
|
235
|
-
enforceSizeLimit();
|
|
236
|
-
}
|
|
237
|
-
async function startFetching(queryKey, queryType, args) {
|
|
238
|
-
log(`Start fetching ${queryKey}, queryType=${queryType}`);
|
|
239
|
-
try {
|
|
240
|
-
const data = await (queryType === "refetch" && refetchFn ? refetchFn(...args) : queryFn(...args));
|
|
241
|
-
log(`Successfully fetched ${queryKey}`);
|
|
242
|
-
setData(queryKey, data, Date.now());
|
|
243
|
-
return data;
|
|
244
|
-
} catch (e) {
|
|
245
|
-
log(`Failed to fetch ${queryKey} for queryType=${queryType}: ${e}`);
|
|
246
|
-
throw e;
|
|
247
|
-
} finally {
|
|
248
|
-
inflightQueries.delete(queryKey);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function dedupedFetch(queryKey, queryType, ...args) {
|
|
252
|
-
const inflightQuery = inflightQueries.get(queryKey);
|
|
253
|
-
if (inflightQuery) {
|
|
254
|
-
log(
|
|
255
|
-
`Deduplicating inflight fetch for ${queryKey}, queryType=${queryType}`
|
|
256
|
-
);
|
|
257
|
-
return inflightQuery.promise;
|
|
258
|
-
}
|
|
259
|
-
const promise = startFetching(queryKey, queryType, args);
|
|
260
|
-
const abortController = new AbortController();
|
|
261
|
-
inflightQueries.set(queryKey, { promise, abortController });
|
|
262
|
-
return promise;
|
|
263
|
-
}
|
|
264
|
-
const obj = {
|
|
265
|
-
queryName,
|
|
266
|
-
queryFn,
|
|
267
|
-
refetchFn,
|
|
268
|
-
expiryMs,
|
|
269
|
-
staleMs,
|
|
270
|
-
refetchOnStale,
|
|
271
|
-
maxSize,
|
|
272
|
-
maxBytes,
|
|
273
|
-
keepLastNonEmptyData,
|
|
274
|
-
log,
|
|
275
|
-
queryState,
|
|
276
|
-
/** Returns a key to identify requests based on the given args. */
|
|
277
|
-
getQueryKey(args) {
|
|
278
|
-
let s = _chunk5DO7KC3Lcjs.stringifyDeterministicForKeys.call(void 0, args);
|
|
279
|
-
if (s.length > 30) {
|
|
280
|
-
s = _xxhashjs2.default.h32(s, 39406).toString(16).slice(2);
|
|
281
|
-
}
|
|
282
|
-
return queryName + ":" + s;
|
|
283
|
-
},
|
|
284
|
-
/**
|
|
285
|
-
* Get the AbortController to abort the inflight query (if any).
|
|
286
|
-
*
|
|
287
|
-
* Note that once you get the controller, you can also get the signal easily
|
|
288
|
-
* using `controller.signal`. So if you need both the controller and the
|
|
289
|
-
* signal, just use this function.
|
|
290
|
-
*/
|
|
291
|
-
getAbortController(args) {
|
|
292
|
-
const queryKey = obj.getQueryKey(args);
|
|
293
|
-
return obj.getAbortControllerByKey(queryKey);
|
|
294
|
-
},
|
|
295
|
-
getAbortControllerByKey(queryKey) {
|
|
296
|
-
return _nullishCoalesce(_optionalChain([inflightQueries, 'access', _9 => _9.get, 'call', _10 => _10(queryKey), 'optionalAccess', _11 => _11.abortController]), () => ( null));
|
|
297
|
-
},
|
|
298
|
-
/**
|
|
299
|
-
* Get the AbortSignal to check for query abortions.
|
|
300
|
-
*
|
|
301
|
-
* Example using fetch:
|
|
302
|
-
* ```
|
|
303
|
-
* const getUserQuery = sharedQuery({
|
|
304
|
-
* queryName: "getUser",
|
|
305
|
-
* queryFn: (userId: string) => {
|
|
306
|
-
* // Get the signal for this current execution.
|
|
307
|
-
* const signal = getUserQuery.getAbortSignal([userId]);
|
|
308
|
-
*
|
|
309
|
-
* // Pass the signal to fetch so that it can be aborted.
|
|
310
|
-
* const response = await fetch(`/users/${userId}`, { signal });
|
|
311
|
-
*
|
|
312
|
-
* // Check for errors.
|
|
313
|
-
* if (!response.ok) {
|
|
314
|
-
* throw new Error(response.statusText);
|
|
315
|
-
* }
|
|
316
|
-
*
|
|
317
|
-
* // Return the data.
|
|
318
|
-
* return await response.json();
|
|
319
|
-
* },
|
|
320
|
-
* });
|
|
321
|
-
* ```
|
|
322
|
-
*
|
|
323
|
-
* Example using `http.ts`:
|
|
324
|
-
* ```
|
|
325
|
-
* const getUserQuery = sharedQuery({
|
|
326
|
-
* queryName: "getUser",
|
|
327
|
-
* queryFn: (userId: string) => {
|
|
328
|
-
* // Get the signal for this current execution.
|
|
329
|
-
* const signal = getUserQuery.getAbortSignal([userId]);
|
|
330
|
-
*
|
|
331
|
-
* // Pass the signal to fetch so that it can be aborted.
|
|
332
|
-
* const response = await http.get(`/users/${userId}`, { signal });
|
|
333
|
-
* return await response.json();
|
|
334
|
-
* },
|
|
335
|
-
* });
|
|
336
|
-
* ```
|
|
337
|
-
*/
|
|
338
|
-
getAbortSignal(args) {
|
|
339
|
-
const queryKey = obj.getQueryKey(args);
|
|
340
|
-
return obj.getAbortSignalByKey(queryKey);
|
|
341
|
-
},
|
|
342
|
-
getAbortSignalByKey(queryKey) {
|
|
343
|
-
return _nullishCoalesce(_optionalChain([obj, 'access', _12 => _12.getAbortControllerByKey, 'call', _13 => _13(queryKey), 'optionalAccess', _14 => _14.signal]), () => ( null));
|
|
344
|
-
},
|
|
345
|
-
/** For cached data, we need to return the correct timestamps as well. */
|
|
346
|
-
async getCachedOrFetch(...args) {
|
|
347
|
-
const startMs = Date.now();
|
|
348
|
-
await queryState.readyPromise;
|
|
349
|
-
const waitedMs = Date.now() - startMs;
|
|
350
|
-
const queryKey = obj.getQueryKey(args);
|
|
351
|
-
const cached = _optionalChain([queryState, 'access', _15 => _15.getSnapshot, 'call', _16 => _16(), 'optionalAccess', _17 => _17[queryKey]]);
|
|
352
|
-
if (waitedMs) {
|
|
353
|
-
log(`${queryKey}: Waited for ${waitedMs}ms before data is ready`);
|
|
354
|
-
}
|
|
355
|
-
if (_optionalChain([cached, 'optionalAccess', _18 => _18.data]) && !isExpired(_nullishCoalesce(cached.dataUpdatedMs, () => ( 0)))) {
|
|
356
|
-
if (!isStale(_nullishCoalesce(cached.dataUpdatedMs, () => ( 0)))) {
|
|
357
|
-
log(`Return fresh data ${queryKey} from cache without fetching`);
|
|
358
|
-
return cached;
|
|
359
|
-
}
|
|
360
|
-
if (refetchOnStale) {
|
|
361
|
-
log(`refetchOnStale for ${queryKey}`);
|
|
362
|
-
void dedupedFetch(queryKey, "query", ...args);
|
|
363
|
-
}
|
|
364
|
-
log(`Returning stale data for ${queryKey}`);
|
|
365
|
-
return cached;
|
|
366
|
-
}
|
|
367
|
-
const data = await dedupedFetch(queryKey, "query", ...args);
|
|
368
|
-
const now = Date.now();
|
|
369
|
-
return {
|
|
370
|
-
// Don't keep old error.
|
|
371
|
-
lastUpdatedMs: now,
|
|
372
|
-
loading: false,
|
|
373
|
-
data,
|
|
374
|
-
dataUpdatedMs: now
|
|
375
|
-
};
|
|
376
|
-
},
|
|
377
|
-
/** Get the entire stored entry for a query key. */
|
|
378
|
-
getQueryValue(queryKey) {
|
|
379
|
-
return _optionalChain([queryState, 'access', _19 => _19.getSnapshot, 'call', _20 => _20(), 'optionalAccess', _21 => _21[queryKey]]);
|
|
380
|
-
},
|
|
381
|
-
/** Get the current stored data for a query key. */
|
|
382
|
-
getData(queryKey) {
|
|
383
|
-
const stateValue = _optionalChain([queryState, 'access', _22 => _22.getSnapshot, 'call', _23 => _23(), 'optionalAccess', _24 => _24[queryKey]]);
|
|
384
|
-
if (_optionalChain([stateValue, 'optionalAccess', _25 => _25.lastUpdatedMs])) {
|
|
385
|
-
if (isExpired(stateValue.lastUpdatedMs)) {
|
|
386
|
-
obj.deleteData(queryKey);
|
|
387
|
-
return void 0;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
return _optionalChain([stateValue, 'optionalAccess', _26 => _26.data]);
|
|
391
|
-
},
|
|
392
|
-
/** Set the data directly if the user obtained it from somewhere else. */
|
|
393
|
-
setData,
|
|
394
|
-
/**
|
|
395
|
-
* Do a new fetch even when the data is already cached, but don't fetch if
|
|
396
|
-
* another fetch is already inflight.
|
|
397
|
-
*/
|
|
398
|
-
async updateFromSource(...args) {
|
|
399
|
-
const key = obj.getQueryKey(args);
|
|
400
|
-
return await dedupedFetch(key, "query", ...args);
|
|
401
|
-
},
|
|
402
|
-
/**
|
|
403
|
-
* Perform an explicit refetch. If `options.refetchFn` was not specified,
|
|
404
|
-
* then this would be identical to calling `obj.updateFromSource()`.
|
|
405
|
-
*/
|
|
406
|
-
async refetch(...args) {
|
|
407
|
-
const key = obj.getQueryKey(args);
|
|
408
|
-
return await dedupedFetch(key, "refetch", ...args);
|
|
409
|
-
},
|
|
410
|
-
/** Delete the cached data for one set of arguments. */
|
|
411
|
-
deleteData(queryKey) {
|
|
412
|
-
const record = queryState.getSnapshot();
|
|
413
|
-
const clone = { ...record };
|
|
414
|
-
delete clone[queryKey];
|
|
415
|
-
queryState.setValue(clone);
|
|
416
|
-
const inflight = inflightQueries.get(queryKey);
|
|
417
|
-
if (inflight) {
|
|
418
|
-
inflightQueries.delete(queryKey);
|
|
419
|
-
inflight.abortController.abort("Deleted");
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
/** Delete all currently cached data & all inflight promises. */
|
|
423
|
-
clear() {
|
|
424
|
-
queryState.delete();
|
|
425
|
-
for (const { abortController } of Array.from(inflightQueries.values())) {
|
|
426
|
-
abortController.abort("Cleared");
|
|
427
|
-
}
|
|
428
|
-
inflightQueries.clear();
|
|
429
|
-
},
|
|
430
|
-
/** Keep track of mounted keys. */
|
|
431
|
-
mount(queryKey) {
|
|
432
|
-
mountedKeys.set(queryKey, (_nullishCoalesce(mountedKeys.get(queryKey), () => ( 0))) + 1);
|
|
433
|
-
},
|
|
434
|
-
/** Clean up mounted keys. */
|
|
435
|
-
unmount(queryKey) {
|
|
436
|
-
const count = mountedKeys.get(queryKey);
|
|
437
|
-
if (count === 1) {
|
|
438
|
-
mountedKeys.delete(queryKey);
|
|
439
|
-
} else {
|
|
440
|
-
mountedKeys.set(queryKey, (_nullishCoalesce(count, () => ( 0))) - 1);
|
|
441
|
-
}
|
|
442
|
-
},
|
|
443
|
-
/**
|
|
444
|
-
* Discard the oldest entries if the size exceeds the limit. The last
|
|
445
|
-
* remaining record cannot be deleted no matter what the limit is.
|
|
446
|
-
*/
|
|
447
|
-
enforceSizeLimit
|
|
448
|
-
};
|
|
449
|
-
return obj;
|
|
450
|
-
}
|
|
451
|
-
var DEFAULT_QUERY_STATE_ENTRY = {
|
|
452
|
-
lastUpdatedMs: 0,
|
|
453
|
-
loading: true
|
|
454
|
-
};
|
|
455
|
-
function useSharedQuery(query, ...args) {
|
|
456
|
-
const stableArgs = _chunkWVLPJPWGcjs.useDeepMemo.call(void 0, args);
|
|
457
|
-
const [queryState, setQueryState] = _chunk3X25NN6Xcjs.useSharedState.call(void 0, query.queryState);
|
|
458
|
-
const isMounted = _react.useRef.call(void 0, true);
|
|
459
|
-
const queryKey = _react.useMemo.call(void 0,
|
|
460
|
-
() => query.getQueryKey(stableArgs),
|
|
461
|
-
[query, stableArgs]
|
|
462
|
-
);
|
|
463
|
-
const queryStateValue = _nullishCoalesce(queryState[queryKey], () => ( DEFAULT_QUERY_STATE_ENTRY));
|
|
464
|
-
const setQueryStateValue = _react.useCallback.call(void 0,
|
|
465
|
-
(next) => {
|
|
466
|
-
setQueryState((prev) => {
|
|
467
|
-
const clone = { ...prev };
|
|
468
|
-
clone[queryKey] = typeof next === "function" ? next(_nullishCoalesce(clone[queryKey], () => ( DEFAULT_QUERY_STATE_ENTRY))) : next;
|
|
469
|
-
return clone;
|
|
470
|
-
});
|
|
471
|
-
},
|
|
472
|
-
[queryKey, setQueryState]
|
|
473
|
-
);
|
|
474
|
-
const [lastNonEmptyData, setLastNonEmptyData] = _react.useState.call(void 0, );
|
|
475
|
-
_react.useEffect.call(void 0, () => {
|
|
476
|
-
if (query.keepLastNonEmptyData && queryStateValue.data !== void 0) {
|
|
477
|
-
setLastNonEmptyData(queryStateValue.data);
|
|
478
|
-
}
|
|
479
|
-
}, [query.keepLastNonEmptyData, queryStateValue.data]);
|
|
480
|
-
const execute = _react.useCallback.call(void 0,
|
|
481
|
-
async (isRefetch) => {
|
|
482
|
-
query.log(
|
|
483
|
-
`Begin executing shared query ${queryKey}, isRefetch=${isRefetch}`
|
|
484
|
-
);
|
|
485
|
-
if (!isMounted.current) return;
|
|
486
|
-
setQueryStateValue((prev) => ({
|
|
487
|
-
// Keep old data and error.
|
|
488
|
-
...prev,
|
|
489
|
-
lastUpdatedMs: Date.now(),
|
|
490
|
-
loading: true
|
|
491
|
-
}));
|
|
492
|
-
try {
|
|
493
|
-
let state;
|
|
494
|
-
if (isRefetch) {
|
|
495
|
-
const data = await query.refetch(...stableArgs);
|
|
496
|
-
const now = Date.now();
|
|
497
|
-
state = {
|
|
498
|
-
// Don't keep old error.
|
|
499
|
-
lastUpdatedMs: now,
|
|
500
|
-
loading: false,
|
|
501
|
-
data,
|
|
502
|
-
dataUpdatedMs: now
|
|
503
|
-
};
|
|
504
|
-
} else {
|
|
505
|
-
state = await query.getCachedOrFetch(...stableArgs);
|
|
506
|
-
}
|
|
507
|
-
if (!isMounted.current) return;
|
|
508
|
-
setQueryStateValue({ ...state, loading: false });
|
|
509
|
-
} catch (e) {
|
|
510
|
-
if (!isMounted.current) return;
|
|
511
|
-
const now = Date.now();
|
|
512
|
-
setQueryStateValue((prev) => ({
|
|
513
|
-
// Keep old data.
|
|
514
|
-
...prev,
|
|
515
|
-
lastUpdatedMs: now,
|
|
516
|
-
loading: false,
|
|
517
|
-
error: e,
|
|
518
|
-
errorUpdatedMs: now
|
|
519
|
-
}));
|
|
520
|
-
}
|
|
521
|
-
},
|
|
522
|
-
[query, queryKey, setQueryStateValue, stableArgs]
|
|
523
|
-
);
|
|
524
|
-
_react.useEffect.call(void 0, () => {
|
|
525
|
-
isMounted.current = true;
|
|
526
|
-
query.mount(queryKey);
|
|
527
|
-
void execute(false);
|
|
528
|
-
return () => {
|
|
529
|
-
isMounted.current = false;
|
|
530
|
-
query.unmount(queryKey);
|
|
531
|
-
};
|
|
532
|
-
}, [execute, query, queryKey]);
|
|
533
|
-
return _react.useMemo.call(void 0, () => {
|
|
534
|
-
return {
|
|
535
|
-
...queryStateValue,
|
|
536
|
-
// Fill with lastNonEmptyData when data is undefined.
|
|
537
|
-
data: queryStateValue.data !== void 0 ? queryStateValue.data : lastNonEmptyData,
|
|
538
|
-
latestData: queryStateValue.data,
|
|
539
|
-
// This works only if the user-given queryFn supports abort. If not,
|
|
540
|
-
// this function doesn't do anything, since we don't have any means to
|
|
541
|
-
// abort the running queryFn.
|
|
542
|
-
abortCurrentQuery: (reason) => {
|
|
543
|
-
const controller = query.getAbortControllerByKey(queryKey);
|
|
544
|
-
_optionalChain([controller, 'optionalAccess', _27 => _27.abort, 'call', _28 => _28(reason)]);
|
|
545
|
-
return Boolean(controller);
|
|
546
|
-
},
|
|
547
|
-
refetch: () => {
|
|
548
|
-
return execute(true);
|
|
549
|
-
},
|
|
550
|
-
setData: (data, dataUpdatedMs) => {
|
|
551
|
-
query.setData(queryKey, data, dataUpdatedMs);
|
|
552
|
-
},
|
|
553
|
-
deleteData: () => {
|
|
554
|
-
query.deleteData(queryKey);
|
|
555
|
-
}
|
|
556
|
-
};
|
|
557
|
-
}, [execute, lastNonEmptyData, query, queryKey, queryStateValue]);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
exports.SharedQueryConfig = SharedQueryConfig; exports.configureSharedQuery = configureSharedQuery; exports.sharedQuery = sharedQuery; exports.useSharedQuery = useSharedQuery;
|
|
17
|
+
exports.SharedQueryConfig = _chunkGKNOICX3cjs.SharedQueryConfig; exports.configureSharedQuery = _chunkGKNOICX3cjs.configureSharedQuery; exports.sharedQuery = _chunkGKNOICX3cjs.sharedQuery; exports.useSharedQuery = _chunkGKNOICX3cjs.useSharedQuery;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SetStateAction } from 'react';
|
|
2
|
-
import { SharedStateOptions } from './sharedState.
|
|
3
|
-
import { PersistTo } from './storage.
|
|
2
|
+
import { SharedStateOptions } from './sharedState.cjs';
|
|
3
|
+
import { PersistTo } from './storage.cjs';
|
|
4
4
|
import '@choksheak/ts-utils/duration';
|
|
5
5
|
import '@choksheak/ts-utils/storageAdapter';
|
|
6
6
|
|
package/sharedQuery.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { SetStateAction } from 'react';
|
|
2
|
+
import { SharedStateOptions } from './sharedState.js';
|
|
3
|
+
import { PersistTo } from './storage.js';
|
|
4
|
+
import '@choksheak/ts-utils/duration';
|
|
5
|
+
import '@choksheak/ts-utils/storageAdapter';
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* @packageDocumentation
|
|
3
9
|
*
|
|
@@ -82,13 +88,11 @@
|
|
|
82
88
|
* };
|
|
83
89
|
* ```
|
|
84
90
|
*/
|
|
85
|
-
|
|
86
|
-
import { SharedStateOptions } from "./sharedState";
|
|
87
|
-
import { PersistTo } from "./storage";
|
|
91
|
+
|
|
88
92
|
/** Type of the queryFn used to fetch the data from source. */
|
|
89
|
-
|
|
93
|
+
type QueryFn<TArgs extends unknown[], TData> = (...args: TArgs) => Promise<TData> | TData;
|
|
90
94
|
/** An entry for a persisted state corresponding to one query key. */
|
|
91
|
-
|
|
95
|
+
type QueryStateValue<TData> = Readonly<{
|
|
92
96
|
/**
|
|
93
97
|
* When this record was last updated. Used for LRU determination. If you want
|
|
94
98
|
* the data age, please use `dataUpdatedMs` instead.
|
|
@@ -106,7 +110,7 @@ export type QueryStateValue<TData> = Readonly<{
|
|
|
106
110
|
errorUpdatedMs?: number;
|
|
107
111
|
}>;
|
|
108
112
|
/** Return type for useSharedQuery(). */
|
|
109
|
-
|
|
113
|
+
type UseQueryResult<TData> = QueryStateValue<TData> & Readonly<{
|
|
110
114
|
/**
|
|
111
115
|
* Usually same as `data`, but if you specify `keepLastNonEmptyData: true`,
|
|
112
116
|
* then this will contain the latest data (undefined when args just changed)
|
|
@@ -132,8 +136,8 @@ export type UseQueryResult<TData> = QueryStateValue<TData> & Readonly<{
|
|
|
132
136
|
* The full query state is a record of all the fetch states mapped by
|
|
133
137
|
* query key to the state entry.
|
|
134
138
|
*/
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
type SharedQueryState<TData> = Record<string, QueryStateValue<TData>>;
|
|
140
|
+
type QueryTargetWithFn<TArgs extends unknown[], TData> = {
|
|
137
141
|
/**
|
|
138
142
|
* The name could have been auto-generated, but we let the user give us a
|
|
139
143
|
* human-readable name that can be identified quickly in the logs.
|
|
@@ -144,7 +148,7 @@ export type QueryTargetWithFn<TArgs extends unknown[], TData> = {
|
|
|
144
148
|
/** Function to fetch data. */
|
|
145
149
|
queryFn: QueryFn<TArgs, TData>;
|
|
146
150
|
};
|
|
147
|
-
|
|
151
|
+
type QueryTargetWithUrl = {
|
|
148
152
|
/**
|
|
149
153
|
* The queryName can optionally be specified, else we will fallback to use
|
|
150
154
|
* the url as the queryName.
|
|
@@ -173,9 +177,9 @@ export type QueryTargetWithUrl = {
|
|
|
173
177
|
url: string;
|
|
174
178
|
};
|
|
175
179
|
/** Specifies how to fetch the data. */
|
|
176
|
-
|
|
180
|
+
type QueryTarget<TArgs extends unknown[], TData> = QueryTargetWithFn<TArgs, TData> | QueryTargetWithUrl;
|
|
177
181
|
/** Options to configure a shared query. */
|
|
178
|
-
|
|
182
|
+
type SharedQueryOptions<TArgs extends unknown[], TData> = QueryTarget<TArgs, TData> & {
|
|
179
183
|
/**
|
|
180
184
|
* Function to re-fetch data. Usually this would just be the `queryFn`, but
|
|
181
185
|
* if you need a different behavior for refetching, then specify this.
|
|
@@ -221,7 +225,7 @@ export type SharedQueryOptions<TArgs extends unknown[], TData> = QueryTarget<TAr
|
|
|
221
225
|
log?: (...args: unknown[]) => void;
|
|
222
226
|
} & SharedStateOptions<SharedQueryState<TData>>;
|
|
223
227
|
/** Users can override these values globally. */
|
|
224
|
-
|
|
228
|
+
declare const SharedQueryConfig: {
|
|
225
229
|
/** Refetch on every page load. */
|
|
226
230
|
staleMs: number;
|
|
227
231
|
/** Keep in cache for a long time. */
|
|
@@ -237,9 +241,9 @@ export declare const SharedQueryConfig: {
|
|
|
237
241
|
/** Default to log to console. */
|
|
238
242
|
log: (...args: unknown[]) => void;
|
|
239
243
|
};
|
|
240
|
-
|
|
244
|
+
type SharedQueryConfig = typeof SharedQueryConfig;
|
|
241
245
|
/** Convenience function to update global defaults. */
|
|
242
|
-
|
|
246
|
+
declare function configureSharedQuery(config: Partial<SharedQueryConfig>): void;
|
|
243
247
|
/**
|
|
244
248
|
* Create a reusable shared query object that can be used to auto de-duplicate
|
|
245
249
|
* and cache all data fetches, with auto-expiration and staleness checks.
|
|
@@ -258,7 +262,7 @@ export declare function configureSharedQuery(config: Partial<SharedQueryConfig>)
|
|
|
258
262
|
* });
|
|
259
263
|
* ```
|
|
260
264
|
*/
|
|
261
|
-
|
|
265
|
+
declare function sharedQuery<TArgs extends unknown[], TData>(options: SharedQueryOptions<TArgs, TData>): {
|
|
262
266
|
readonly queryName: string;
|
|
263
267
|
readonly queryFn: QueryFn<TArgs, TData>;
|
|
264
268
|
readonly refetchFn: QueryFn<TArgs, TData> | undefined;
|
|
@@ -368,7 +372,7 @@ export declare function sharedQuery<TArgs extends unknown[], TData>(options: Sha
|
|
|
368
372
|
*/
|
|
369
373
|
readonly enforceSizeLimit: () => void;
|
|
370
374
|
};
|
|
371
|
-
|
|
375
|
+
type SharedQuery<TArgs extends unknown[], TData> = ReturnType<typeof sharedQuery<TArgs, TData>>;
|
|
372
376
|
/**
|
|
373
377
|
* React hook to make use of a shared query inside any React component. The
|
|
374
378
|
* args do not need to be specified if no args are needed, i.e. this would work
|
|
@@ -381,4 +385,6 @@ export type SharedQuery<TArgs extends unknown[], TData> = ReturnType<typeof shar
|
|
|
381
385
|
* const users = useSharedQuery(usersQuery, "userid123");
|
|
382
386
|
* ```
|
|
383
387
|
*/
|
|
384
|
-
|
|
388
|
+
declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, ...args: TArgs): UseQueryResult<TData>;
|
|
389
|
+
|
|
390
|
+
export { type QueryFn, type QueryStateValue, type QueryTarget, type QueryTargetWithFn, type QueryTargetWithUrl, type SharedQuery, SharedQueryConfig, type SharedQueryOptions, type SharedQueryState, type UseQueryResult, configureSharedQuery, sharedQuery, useSharedQuery };
|