@choksheak/react-utils 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/sharedQuery.cjs +94 -34
- package/sharedQuery.d.mts +12 -1
- package/sharedQuery.d.ts +12 -1
- 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 +94 -34
- package/sharedState.cjs +67 -21
- package/sharedState.d.mts +28 -0
- package/sharedState.d.ts +28 -0
- package/sharedState.min.cjs +1 -1
- package/sharedState.min.cjs.map +1 -1
- package/sharedState.min.mjs +1 -1
- package/sharedState.min.mjs.map +1 -1
- package/sharedState.mjs +67 -21
- package/storage.cjs +30 -10
- package/storage.d.mts +7 -0
- package/storage.d.ts +7 -0
- package/storage.min.cjs +1 -1
- package/storage.min.cjs.map +1 -1
- package/storage.min.mjs +1 -1
- package/storage.min.mjs.map +1 -1
- package/storage.mjs +30 -10
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@choksheak/react-utils",
|
|
3
3
|
"license": "The Unlicense",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.3",
|
|
5
5
|
"description": "Miscellaneous React+TypeScript utilities with support for full tree-shaking.",
|
|
6
6
|
"private": false,
|
|
7
7
|
"files": [
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"wretch": "^3.0.2"
|
|
96
96
|
},
|
|
97
97
|
"peerDependencies": {
|
|
98
|
-
"@choksheak/ts-utils": ">=0.3.
|
|
98
|
+
"@choksheak/ts-utils": ">=0.3.2",
|
|
99
99
|
"lodash": ">=4.17.21",
|
|
100
100
|
"object-sizeof": ">=2.6.5",
|
|
101
101
|
"react": ">=16",
|
package/sharedQuery.cjs
CHANGED
|
@@ -48,6 +48,7 @@ var import_react = require("react");
|
|
|
48
48
|
// src/storage.ts
|
|
49
49
|
var import_kvStore = require("@choksheak/ts-utils/kvStore");
|
|
50
50
|
var import_localStore = require("@choksheak/ts-utils/localStore");
|
|
51
|
+
var IS_NOT_BROWSER = typeof window === "undefined";
|
|
51
52
|
function getStorageAdapter(options, defaultStoreExpiryMs) {
|
|
52
53
|
const { store } = options ?? {};
|
|
53
54
|
if (!store) {
|
|
@@ -55,6 +56,7 @@ function getStorageAdapter(options, defaultStoreExpiryMs) {
|
|
|
55
56
|
}
|
|
56
57
|
if ("persistTo" in store && typeof store.persistTo === "string" && "key" in store && typeof store.key === "string") {
|
|
57
58
|
const expiryMs = store.expiryMs || defaultStoreExpiryMs;
|
|
59
|
+
const isValid = store.isValid;
|
|
58
60
|
switch (store.persistTo) {
|
|
59
61
|
case "localStorage": {
|
|
60
62
|
const adapter = new import_localStore.LocalStore(store.key, {
|
|
@@ -62,40 +64,58 @@ function getStorageAdapter(options, defaultStoreExpiryMs) {
|
|
|
62
64
|
});
|
|
63
65
|
return {
|
|
64
66
|
set: (key, value) => {
|
|
65
|
-
if (
|
|
67
|
+
if (IS_NOT_BROWSER) return;
|
|
66
68
|
adapter.set(key, value);
|
|
67
69
|
},
|
|
68
70
|
get: (key) => {
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
+
if (IS_NOT_BROWSER) return void 0;
|
|
72
|
+
const value = adapter.get(key);
|
|
73
|
+
if (isValid && !isValid(value)) {
|
|
74
|
+
console.warn(
|
|
75
|
+
`localStorage adapter: Auto-discard invalid value for ${key}: ${JSON.stringify(value)}`
|
|
76
|
+
);
|
|
77
|
+
adapter.delete(key);
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
71
81
|
},
|
|
72
82
|
delete: (key) => {
|
|
73
|
-
if (
|
|
83
|
+
if (IS_NOT_BROWSER) return;
|
|
74
84
|
adapter.delete(key);
|
|
75
85
|
},
|
|
76
86
|
clear: () => {
|
|
77
|
-
if (
|
|
87
|
+
if (IS_NOT_BROWSER) return;
|
|
78
88
|
adapter.clear();
|
|
79
89
|
}
|
|
80
90
|
};
|
|
81
91
|
}
|
|
82
92
|
case "indexedDb": {
|
|
83
93
|
const keyPrefix = store.key + ":";
|
|
94
|
+
const isValid2 = store.isValid;
|
|
84
95
|
return {
|
|
85
96
|
set: async (key, value) => {
|
|
86
|
-
if (
|
|
97
|
+
if (IS_NOT_BROWSER) return;
|
|
87
98
|
await import_kvStore.kvStore.set(keyPrefix + key, value, expiryMs);
|
|
88
99
|
},
|
|
89
100
|
get: async (key) => {
|
|
90
|
-
if (
|
|
91
|
-
|
|
101
|
+
if (IS_NOT_BROWSER) return void 0;
|
|
102
|
+
const fullKey = keyPrefix + key;
|
|
103
|
+
const value = await import_kvStore.kvStore.get(fullKey);
|
|
104
|
+
if (isValid2 && !isValid2(value)) {
|
|
105
|
+
console.warn(
|
|
106
|
+
`indexedDb adapter: Auto-discard invalid value for ${key}: ${JSON.stringify(value)}`
|
|
107
|
+
);
|
|
108
|
+
await import_kvStore.kvStore.delete(fullKey);
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
92
112
|
},
|
|
93
113
|
delete: async (key) => {
|
|
94
|
-
if (
|
|
114
|
+
if (IS_NOT_BROWSER) return;
|
|
95
115
|
await import_kvStore.kvStore.delete(keyPrefix + key);
|
|
96
116
|
},
|
|
97
117
|
clear: async () => {
|
|
98
|
-
if (
|
|
118
|
+
if (IS_NOT_BROWSER) return;
|
|
99
119
|
await import_kvStore.kvStore.clear();
|
|
100
120
|
}
|
|
101
121
|
};
|
|
@@ -193,39 +213,65 @@ var SharedState = class {
|
|
|
193
213
|
setValueBounded;
|
|
194
214
|
initStarted = false;
|
|
195
215
|
initDone = false;
|
|
196
|
-
|
|
216
|
+
/** `subscribe` function required by useSyncExternalStore. */
|
|
197
217
|
subscribe(subscriber) {
|
|
198
218
|
return pubSubStore.subscribe(this.pubSubKey, () => subscriber());
|
|
199
219
|
}
|
|
200
|
-
|
|
201
|
-
|
|
220
|
+
/** Returns true if there is a backing store. */
|
|
221
|
+
hasStore() {
|
|
222
|
+
return Boolean(this.storageAdapter);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Set a value directly into the store. Does nothing if there is no
|
|
226
|
+
* backing store.
|
|
227
|
+
*
|
|
228
|
+
* Warning: If you want to set the value properly, you should use
|
|
229
|
+
* `setValue()` instead which also updates the in-memory copy.
|
|
230
|
+
*/
|
|
231
|
+
async setInStore(value) {
|
|
232
|
+
return await this.storageAdapter?.set(STORAGE_KEY, value);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get a value directly from the store. Returns undefined if there is no
|
|
236
|
+
* backing store.
|
|
237
|
+
*/
|
|
238
|
+
async getFromStore() {
|
|
239
|
+
return await this.storageAdapter?.get(STORAGE_KEY);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get the value of the shared state.
|
|
243
|
+
* `getSnapshot` function — must return same ref if value unchanged.
|
|
244
|
+
* Note that this would return the default value if data init from store is
|
|
245
|
+
* not completed yet.
|
|
246
|
+
*/
|
|
202
247
|
getSnapshot() {
|
|
203
248
|
return pubSubStore.get(this.pubSubKey);
|
|
204
249
|
}
|
|
205
|
-
|
|
250
|
+
/** Set the value of the shared state. */
|
|
206
251
|
setValue(next) {
|
|
207
252
|
if (typeof next === "function") {
|
|
208
253
|
const prev = pubSubStore.get(this.pubSubKey);
|
|
209
254
|
next = next(prev);
|
|
210
255
|
}
|
|
211
256
|
pubSubStore.set(this.pubSubKey, next);
|
|
212
|
-
void this.
|
|
257
|
+
void this.setInStore(next);
|
|
213
258
|
}
|
|
214
|
-
|
|
215
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Remove the value of the shared state. Actually this just sets the value
|
|
261
|
+
* back to the given default value.
|
|
262
|
+
*/
|
|
216
263
|
delete() {
|
|
217
264
|
pubSubStore.set(this.pubSubKey, this.defaultValue);
|
|
218
265
|
void this.storageAdapter?.set(STORAGE_KEY, this.defaultValue);
|
|
219
266
|
}
|
|
220
|
-
|
|
267
|
+
/** Initial default handling. */
|
|
221
268
|
async initDefaultValueOnce() {
|
|
222
269
|
if (this.initDone || this.initStarted) return;
|
|
223
270
|
this.initStarted = true;
|
|
224
|
-
const
|
|
225
|
-
const value =
|
|
271
|
+
const stored = await this.getFromStore();
|
|
272
|
+
const value = stored !== void 0 ? stored : this.defaultValue;
|
|
226
273
|
pubSubStore.set(this.pubSubKey, value);
|
|
227
274
|
this.initDone = true;
|
|
228
|
-
pubSubStore.set(this.pubSubKey, value);
|
|
229
275
|
}
|
|
230
276
|
};
|
|
231
277
|
function sharedState(defaultValue, options) {
|
|
@@ -318,6 +364,7 @@ var SharedQuery = class {
|
|
|
318
364
|
mountedKeys = /* @__PURE__ */ new Map();
|
|
319
365
|
queryName;
|
|
320
366
|
queryFn;
|
|
367
|
+
refetchFn;
|
|
321
368
|
expiryMs;
|
|
322
369
|
staleMs;
|
|
323
370
|
refetchOnStale;
|
|
@@ -328,6 +375,7 @@ var SharedQuery = class {
|
|
|
328
375
|
constructor(options) {
|
|
329
376
|
this.queryName = options.queryName;
|
|
330
377
|
this.queryFn = options.queryFn;
|
|
378
|
+
this.refetchFn = options.refetchFn;
|
|
331
379
|
this.staleMs = options?.staleMs ?? SharedQueryConfig.staleMs;
|
|
332
380
|
this.expiryMs = options?.expiryMs ?? SharedQueryConfig.expiryMs;
|
|
333
381
|
this.refetchOnStale = options?.refetchOnStale ?? SharedQueryConfig.refetchOnStale;
|
|
@@ -421,33 +469,35 @@ var SharedQuery = class {
|
|
|
421
469
|
}
|
|
422
470
|
if (this.refetchOnStale) {
|
|
423
471
|
this.log(`refetchOnStale for ${queryKey}`);
|
|
424
|
-
void this.dedupedFetch(queryKey, ...args);
|
|
472
|
+
void this.dedupedFetch(queryKey, "query", ...args);
|
|
425
473
|
}
|
|
426
474
|
this.log(`Returning stale data for ${queryKey}`);
|
|
427
475
|
return cached.data;
|
|
428
476
|
}
|
|
429
|
-
return await this.dedupedFetch(queryKey, ...args);
|
|
477
|
+
return await this.dedupedFetch(queryKey, "query", ...args);
|
|
430
478
|
}
|
|
431
|
-
dedupedFetch(queryKey, ...args) {
|
|
479
|
+
dedupedFetch(queryKey, queryType, ...args) {
|
|
432
480
|
const inflightQuery = this.inflightQueries.get(queryKey);
|
|
433
481
|
if (inflightQuery) {
|
|
434
|
-
this.log(
|
|
482
|
+
this.log(
|
|
483
|
+
`Deduplicating inflight fetch for ${queryKey}, queryType=${queryType}`
|
|
484
|
+
);
|
|
435
485
|
return inflightQuery.promise;
|
|
436
486
|
}
|
|
437
|
-
const promise = this.startFetching(queryKey, args);
|
|
487
|
+
const promise = this.startFetching(queryKey, queryType, args);
|
|
438
488
|
const abortController = new AbortController();
|
|
439
489
|
this.inflightQueries.set(queryKey, { promise, abortController });
|
|
440
490
|
return promise;
|
|
441
491
|
}
|
|
442
|
-
async startFetching(queryKey, args) {
|
|
443
|
-
this.log(`Start fetching ${queryKey}`);
|
|
492
|
+
async startFetching(queryKey, queryType, args) {
|
|
493
|
+
this.log(`Start fetching ${queryKey}, queryType=${queryType}`);
|
|
444
494
|
try {
|
|
445
|
-
const data = await this.queryFn(...args);
|
|
495
|
+
const data = await (queryType === "refetch" && this.refetchFn ? this.refetchFn(...args) : this.queryFn(...args));
|
|
446
496
|
this.log(`Successfully fetched ${queryKey}`);
|
|
447
497
|
this.setData(queryKey, data, Date.now());
|
|
448
498
|
return data;
|
|
449
499
|
} catch (e) {
|
|
450
|
-
this.log(`Failed to fetch ${queryKey}: ${e}`);
|
|
500
|
+
this.log(`Failed to fetch ${queryKey} for queryType=${queryType}: ${e}`);
|
|
451
501
|
throw e;
|
|
452
502
|
} finally {
|
|
453
503
|
this.inflightQueries.delete(queryKey);
|
|
@@ -485,7 +535,15 @@ var SharedQuery = class {
|
|
|
485
535
|
*/
|
|
486
536
|
async updateFromSource(...args) {
|
|
487
537
|
const key = this.getQueryKey(args);
|
|
488
|
-
return await this.dedupedFetch(key, ...args);
|
|
538
|
+
return await this.dedupedFetch(key, "query", ...args);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Perform an explicit refetch. If `options.refetchFn` was not specified,
|
|
542
|
+
* then this would be identical to calling `this.updateFromSource()`.
|
|
543
|
+
*/
|
|
544
|
+
async refetch(...args) {
|
|
545
|
+
const key = this.getQueryKey(args);
|
|
546
|
+
return await this.dedupedFetch(key, "refetch", ...args);
|
|
489
547
|
}
|
|
490
548
|
/** Delete the cached data for one set of arguments. */
|
|
491
549
|
deleteData(queryKey) {
|
|
@@ -635,8 +693,10 @@ function useSharedQuery(query, ...args) {
|
|
|
635
693
|
}
|
|
636
694
|
}, [query.keepLastNonEmptyData, queryStateValue.data]);
|
|
637
695
|
const execute = (0, import_react3.useCallback)(
|
|
638
|
-
async (
|
|
639
|
-
query.log(
|
|
696
|
+
async (isRefetch) => {
|
|
697
|
+
query.log(
|
|
698
|
+
`Begin executing shared query ${queryKey}, isRefetch=${isRefetch}`
|
|
699
|
+
);
|
|
640
700
|
setQueryStateValue((prev) => ({
|
|
641
701
|
// Keep old data and error.
|
|
642
702
|
...prev,
|
|
@@ -644,7 +704,7 @@ function useSharedQuery(query, ...args) {
|
|
|
644
704
|
loading: true
|
|
645
705
|
}));
|
|
646
706
|
try {
|
|
647
|
-
const data = await (
|
|
707
|
+
const data = await (isRefetch ? query.refetch(...stableArgs) : query.getCachedOrFetch(...stableArgs));
|
|
648
708
|
if (isMounted.current) {
|
|
649
709
|
const now = Date.now();
|
|
650
710
|
setQueryStateValue({
|
package/sharedQuery.d.mts
CHANGED
|
@@ -147,6 +147,11 @@ type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
|
147
147
|
* Function to fetch data.
|
|
148
148
|
*/
|
|
149
149
|
queryFn: QueryFn<TArgs, TData>;
|
|
150
|
+
/**
|
|
151
|
+
* Function to re-fetch data. Usually this would just be the `queryFn`, but
|
|
152
|
+
* if you need a different behavior for refetching, then specify this.
|
|
153
|
+
*/
|
|
154
|
+
refetchFn?: QueryFn<TArgs, TData>;
|
|
150
155
|
/**
|
|
151
156
|
* ms before data considered stale; 0 means always stale.
|
|
152
157
|
*/
|
|
@@ -217,7 +222,8 @@ declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
217
222
|
*/
|
|
218
223
|
private readonly mountedKeys;
|
|
219
224
|
readonly queryName: string;
|
|
220
|
-
|
|
225
|
+
readonly queryFn: QueryFn<TArgs, TData>;
|
|
226
|
+
readonly refetchFn: QueryFn<TArgs, TData> | undefined;
|
|
221
227
|
readonly expiryMs: number;
|
|
222
228
|
readonly staleMs: number;
|
|
223
229
|
readonly refetchOnStale: boolean;
|
|
@@ -280,6 +286,11 @@ declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
280
286
|
* another fetch is already inflight.
|
|
281
287
|
*/
|
|
282
288
|
updateFromSource(...args: TArgs): Promise<TData>;
|
|
289
|
+
/**
|
|
290
|
+
* Perform an explicit refetch. If `options.refetchFn` was not specified,
|
|
291
|
+
* then this would be identical to calling `this.updateFromSource()`.
|
|
292
|
+
*/
|
|
293
|
+
refetch(...args: TArgs): Promise<TData>;
|
|
283
294
|
/** Delete the cached data for one set of arguments. */
|
|
284
295
|
deleteData(queryKey: string): void;
|
|
285
296
|
/** Delete all currently cached data & all inflight promises. */
|
package/sharedQuery.d.ts
CHANGED
|
@@ -143,6 +143,11 @@ export type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
|
143
143
|
* Function to fetch data.
|
|
144
144
|
*/
|
|
145
145
|
queryFn: QueryFn<TArgs, TData>;
|
|
146
|
+
/**
|
|
147
|
+
* Function to re-fetch data. Usually this would just be the `queryFn`, but
|
|
148
|
+
* if you need a different behavior for refetching, then specify this.
|
|
149
|
+
*/
|
|
150
|
+
refetchFn?: QueryFn<TArgs, TData>;
|
|
146
151
|
/**
|
|
147
152
|
* ms before data considered stale; 0 means always stale.
|
|
148
153
|
*/
|
|
@@ -213,7 +218,8 @@ export declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
213
218
|
*/
|
|
214
219
|
private readonly mountedKeys;
|
|
215
220
|
readonly queryName: string;
|
|
216
|
-
|
|
221
|
+
readonly queryFn: QueryFn<TArgs, TData>;
|
|
222
|
+
readonly refetchFn: QueryFn<TArgs, TData> | undefined;
|
|
217
223
|
readonly expiryMs: number;
|
|
218
224
|
readonly staleMs: number;
|
|
219
225
|
readonly refetchOnStale: boolean;
|
|
@@ -276,6 +282,11 @@ export declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
276
282
|
* another fetch is already inflight.
|
|
277
283
|
*/
|
|
278
284
|
updateFromSource(...args: TArgs): Promise<TData>;
|
|
285
|
+
/**
|
|
286
|
+
* Perform an explicit refetch. If `options.refetchFn` was not specified,
|
|
287
|
+
* then this would be identical to calling `this.updateFromSource()`.
|
|
288
|
+
*/
|
|
289
|
+
refetch(...args: TArgs): Promise<TData>;
|
|
279
290
|
/** Delete the cached data for one set of arguments. */
|
|
280
291
|
deleteData(queryKey: string): void;
|
|
281
292
|
/** Delete all currently cached data & all inflight promises. */
|
package/sharedQuery.min.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var R=Object.create;var T=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var J=(r,e)=>{for(var t in e)T(r,t,{get:e[t],enumerable:!0})},v=(r,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of _(e))!Y.call(r,i)&&i!==t&&T(r,i,{get:()=>e[i],enumerable:!(a=q(e,i))||a.enumerable});return r};var B=(r,e,t)=>(t=r!=null?R(j(r)):{},v(e||!r||!r.__esModule?T(t,"default",{value:r,enumerable:!0}):t,r)),G=r=>v(T({},"__esModule",{value:!0}),r);var Z={};J(Z,{SharedQuery:()=>w,SharedQueryConfig:()=>g,configureSharedQuery:()=>H,sharedQuery:()=>W,useSharedQuery:()=>X});module.exports=G(Z);var P=require("@choksheak/ts-utils/timeConstants"),m=B(require("object-sizeof")),l=require("react");var $=require("@choksheak/ts-utils/timeConstants"),z=require("react");var b=require("@choksheak/ts-utils/kvStore"),O=require("@choksheak/ts-utils/localStore");function K(r,e){let{store:t}=r??{};if(!t)return null;if("persistTo"in t&&typeof t.persistTo=="string"&&"key"in t&&typeof t.key=="string"){let a=t.expiryMs||e;switch(t.persistTo){case"localStorage":{let i=new O.LocalStore(t.key,{defaultExpiryMs:a});return{set:(s,n)=>{typeof window>"u"||i.set(s,n)},get:s=>{if(!(typeof window>"u"))return i.get(s)},delete:s=>{typeof window>"u"||i.delete(s)},clear:()=>{typeof window>"u"||i.clear()}}}case"indexedDb":{let i=t.key+":";return{set:async(s,n)=>{typeof window>"u"||await b.kvStore.set(i+s,n,a)},get:async s=>{if(!(typeof window>"u"))return await b.kvStore.get(i+s)},delete:async s=>{typeof window>"u"||await b.kvStore.delete(i+s)},clear:async()=>{typeof window>"u"||await b.kvStore.clear()}}}default:throw t.persistTo,new Error(`Unknown store.adapter ${t.persistTo}`)}}return t}var C={storeExpiryMs:30*$.MS_PER_DAY,lazyLoad:!1};var M=class{dataByKey=new Map;subscribersByKey=new Map;get(e){return this.dataByKey.get(e)}set(e,t){let a=this.dataByKey.get(e);a!==t&&(this.dataByKey.set(e,t),this.notify(e,t,a))}delete(e){this.dataByKey.delete(e)}setNoNotify(e,t){this.dataByKey.set(e,t)}subscribe(e,t){let a=this.subscribersByKey.get(e);return a||(a=new Set,this.subscribersByKey.set(e,a)),a.add(t),()=>{a.delete(t),a.size===0&&this.subscribersByKey.delete(e)}}notify(e,t,a){let i=this.subscribersByKey.get(e);if(!i)return;let s=Array.from(i.values());for(let n of s)try{n(t,a)}catch(p){console.error("Error invoking subscriber:",p)}}},f=new M,A="state",I=0,Q=class{constructor(e,t){this.defaultValue=e;this.pubSubKey=String(I++),this.storageAdapter=K(t,C.storeExpiryMs),this.setValueBounded=s=>{this.setValue(s)},f.setNoNotify(this.pubSubKey,e);let a=t?.lazyLoad;(a!==void 0?a:C.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;initStarted=!1;initDone=!1;subscribe(e){return f.subscribe(this.pubSubKey,()=>e())}getSnapshot(){return f.get(this.pubSubKey)}setValue(e){if(typeof e=="function"){let t=f.get(this.pubSubKey);e=e(t)}f.set(this.pubSubKey,e),this.storageAdapter?.set(A,e)}delete(){f.set(this.pubSubKey,this.defaultValue),this.storageAdapter?.set(A,this.defaultValue)}async initDefaultValueOnce(){if(this.initDone||this.initStarted)return;this.initStarted=!0;let e=await this.storageAdapter?.get(A),t=e!==void 0?e:this.defaultValue;f.set(this.pubSubKey,t),this.initDone=!0,f.set(this.pubSubKey,t)}};function N(r,e){return new Q(r,e)}function k(r){return r.initDefaultValueOnce(),[(0,z.useSyncExternalStore)(t=>r.subscribe(t),()=>r.getSnapshot(),()=>r.getSnapshot()),r.setValueBounded]}function E(r){let e=new Set;return JSON.stringify(r,(t,a)=>{if(typeof a=="function")throw new Error("Functions should not be passed into query parameters");if(!a||typeof a!="object"||Array.isArray(a))return a;if(e.has(a))return"[Circular]";if(e.add(a),a!==r)try{JSON.stringify(a)}catch{return"[Unserializable]"}return Object.keys(a).sort().reduce((i,s)=>(i[s]=a[s],i),{})})}var U=B(require("lodash/isEqual")),D=require("react");function V(r){let[e,t]=(0,D.useState)(r);return(0,D.useMemo)(()=>e===r||(0,U.default)(e,r)?e:(t(r),r),[e,r])}var g={staleMs:0,expiryMs:30*P.MS_PER_DAY,refetchOnStale:!0,persistTo:void 0,maxSize:100,maxBytes:1e5,log:(...r)=>console.log("[sharedQuery]",...r)};function H(r){Object.assign(g,r)}var w=class{inflightQueries=new Map;queryState;mountedKeys=new Map;queryName;queryFn;expiryMs;staleMs;refetchOnStale;maxSize;maxBytes;keepLastNonEmptyData;log;constructor(e){if(this.queryName=e.queryName,this.queryFn=e.queryFn,this.staleMs=e?.staleMs??g.staleMs,this.expiryMs=e?.expiryMs??g.expiryMs,this.refetchOnStale=e?.refetchOnStale??g.refetchOnStale,!e.store){let t=e.persistTo??g.persistTo;t&&(e.store={persistTo:t,key:e.queryName,expiryMs:this.expiryMs})}this.maxSize=e?.maxSize??g.maxSize,this.maxBytes=e?.maxBytes??g.maxBytes,this.keepLastNonEmptyData=!!e?.keepLastNonEmptyData,this.log=e?.log??g.log,this.queryState=N({},e)}getQueryKey(e){return this.queryName+":"+E(e)}getAbortController(e){let t=this.getQueryKey(e);return this.getAbortControllerByKey(t)}getAbortControllerByKey(e){return this.inflightQueries.get(e)?.abortController??null}getAbortSignal(e){let t=this.getQueryKey(e);return this.getAbortSignalByKey(t)}getAbortSignalByKey(e){return this.getAbortControllerByKey(e)?.signal??null}isStale(e){return this.staleMs===0?!0:Date.now()-e>this.staleMs}isExpired(e){return this.expiryMs===0?!1:Date.now()-e>this.expiryMs}async getCachedOrFetch(...e){let t=this.getQueryKey(e),a=this.queryState.getSnapshot()?.[t];return a?.data&&!this.isExpired(a.dataUpdatedMs??0)?this.isStale(a.dataUpdatedMs??0)?(this.refetchOnStale&&(this.log(`refetchOnStale for ${t}`),this.dedupedFetch(t,...e)),this.log(`Returning stale data for ${t}`),a.data):(this.log(`Return fresh data ${t} from cache without fetching`),a.data):await this.dedupedFetch(t,...e)}dedupedFetch(e,...t){let a=this.inflightQueries.get(e);if(a)return this.log(`Deduplicating inflight fetch for ${e}`),a.promise;let i=this.startFetching(e,t),s=new AbortController;return this.inflightQueries.set(e,{promise:i,abortController:s}),i}async startFetching(e,t){this.log(`Start fetching ${e}`);try{let a=await this.queryFn(...t);return this.log(`Successfully fetched ${e}`),this.setData(e,a,Date.now()),a}catch(a){throw this.log(`Failed to fetch ${e}: ${a}`),a}finally{this.inflightQueries.delete(e)}}getQueryValue(e){return this.queryState.getSnapshot()?.[e]}getData(e){let t=this.queryState.getSnapshot()?.[e];if(t?.lastUpdatedMs&&this.isExpired(t.lastUpdatedMs)){this.deleteData(e);return}return t?.data}setData(e,t,a=Date.now()){let i={lastUpdatedMs:Date.now(),loading:!1,data:t,dataUpdatedMs:a};this.queryState.setValue(s=>({...s,[e]:i})),this.enforceSizeLimit()}async updateFromSource(...e){let t=this.getQueryKey(e);return await this.dedupedFetch(t,...e)}deleteData(e){let a={...this.queryState.getSnapshot()};delete a[e],this.queryState.setValue(a);let i=this.inflightQueries.get(e);i&&(this.inflightQueries.delete(e),i.abortController.abort("Deleted"))}clear(){this.queryState.delete();for(let{abortController:e}of Array.from(this.inflightQueries.values()))e.abort("Cleared");this.inflightQueries.clear()}mount(e){this.mountedKeys.set(e,(this.mountedKeys.get(e)??0)+1)}unmount(e){let t=this.mountedKeys.get(e);t===1?this.mountedKeys.delete(e):this.mountedKeys.set(e,(t??0)-1)}enforceSizeLimit(){if(!this.maxSize&&!this.maxBytes)return;let e=this.queryState.getSnapshot(),t=Object.keys(e).length;if(t<=1)return;let a=(0,m.default)(e),i=this.maxSize?t-this.maxSize:0,s=this.maxBytes?a-this.maxBytes:0;if(i<=0&&s<=0){this.log(`No need to trim data for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.log(`Need to trim ${this.queryName}: numToCut=${i}, bytesToCut=${s}`);let n={...e},p=!1,h=(o,y)=>{if(this.mountedKeys.has(o)){this.log(`Cannot clean up ${o} as it is mounted`);return}let d=(0,m.default)(o)+(0,m.default)(n[o]);this.log(`Cleaning up unmounted${y?", expired":""} ${o} (byteSize=${d.toLocaleString()})`),i--,s-=d,delete n[o],p=!0},S=Object.entries(n).sort((o,y)=>o[1].lastUpdatedMs-y[1].lastUpdatedMs);for(let[o]of S){if(!this.isExpired(n[o].lastUpdatedMs))break;h(o,!0)}if(i>0||s>0){p&&(S=Object.entries(n).sort((o,y)=>o[1].lastUpdatedMs-y[1].lastUpdatedMs));for(let[o]of S)if(h(o,!1),i<=0&&s<=0)break}if(!p){this.log(`No trimmable entries found for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.queryState.setValue(n),this.log(`Trimmed data for ${this.queryName}: size=[${t} -> ${Object.keys(n).length}] (limit=${this.maxSize}), byteSize=[${a.toLocaleString()} -> ${(0,m.default)(n).toLocaleString()}] (limit=${this.maxBytes.toLocaleString()})`)}},L=new Set;function W(r){if(L.has(r.queryName))throw new Error(`Duplicate shared query "${r.queryName}"`);return L.add(r.queryName),new w(r)}var F={lastUpdatedMs:0,loading:!0};function X(r,...e){let t=V(e),[a,i]=k(r.queryState),s=(0,l.useRef)(!0),n=(0,l.useMemo)(()=>r.getQueryKey(t),[r,t]),p=a[n]??F,h=(0,l.useCallback)(d=>{i(u=>{let c={...u};return c[n]=typeof d=="function"?d(c[n]??F):d,c})},[n,i]),[S,o]=(0,l.useState)();(0,l.useEffect)(()=>{r.keepLastNonEmptyData&&p.data!==void 0&&o(p.data)},[r.keepLastNonEmptyData,p.data]);let y=(0,l.useCallback)(async d=>{r.log(`Begin executing shared query ${n}`),h(u=>({...u,lastUpdatedMs:Date.now(),loading:!0}));try{let u=await(d?r.updateFromSource(...t):r.getCachedOrFetch(...t));if(s.current){let c=Date.now();h({lastUpdatedMs:c,loading:!1,data:u,dataUpdatedMs:c})}}catch(u){if(s.current){let c=Date.now();h(x=>({...x,lastUpdatedMs:c,loading:!1,error:u,errorUpdatedMs:c}))}}},[r,n,h,t]);return(0,l.useEffect)(()=>(s.current=!0,r.mount(n),y(!1),()=>{s.current=!1,r.unmount(n)}),[y,r,n]),(0,l.useMemo)(()=>({...p,lastNonEmptyData:S,abortCurrentQuery:d=>{let u=r.getAbortControllerByKey(n);return u?.abort(d),!!u},refetch:()=>{y(!0)},setData:(d,u)=>{let c=Date.now();h(x=>({...x,data:d,dataUpdatedMs:u??c,lastUpdatedMs:c,loading:!1}))},deleteData:()=>{i(d=>{let u={...d};return delete u[n],u})}}),[y,S,r,n,p,i,h])}0&&(module.exports={SharedQuery,SharedQueryConfig,configureSharedQuery,sharedQuery,useSharedQuery});
|
|
1
|
+
"use strict";var q=Object.create;var D=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var Y=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var J=(r,e)=>{for(var t in e)D(r,t,{get:e[t],enumerable:!0})},$=(r,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of j(e))!I.call(r,i)&&i!==t&&D(r,i,{get:()=>e[i],enumerable:!(a=_(e,i))||a.enumerable});return r};var O=(r,e,t)=>(t=r!=null?q(Y(r)):{},$(e||!r||!r.__esModule?D(t,"default",{value:r,enumerable:!0}):t,r)),G=r=>$(D({},"__esModule",{value:!0}),r);var ee={};J(ee,{SharedQuery:()=>w,SharedQueryConfig:()=>f,configureSharedQuery:()=>H,sharedQuery:()=>X,useSharedQuery:()=>Z});module.exports=G(ee);var R=require("@choksheak/ts-utils/timeConstants"),m=O(require("object-sizeof")),y=require("react");var C=require("@choksheak/ts-utils/timeConstants"),z=require("react");var T=require("@choksheak/ts-utils/kvStore"),B=require("@choksheak/ts-utils/localStore"),S=typeof window>"u";function K(r,e){let{store:t}=r??{};if(!t)return null;if("persistTo"in t&&typeof t.persistTo=="string"&&"key"in t&&typeof t.key=="string"){let a=t.expiryMs||e,i=t.isValid;switch(t.persistTo){case"localStorage":{let n=new B.LocalStore(t.key,{defaultExpiryMs:a});return{set:(s,o)=>{S||n.set(s,o)},get:s=>{if(S)return;let o=n.get(s);if(i&&!i(o)){console.warn(`localStorage adapter: Auto-discard invalid value for ${s}: ${JSON.stringify(o)}`),n.delete(s);return}return o},delete:s=>{S||n.delete(s)},clear:()=>{S||n.clear()}}}case"indexedDb":{let n=t.key+":",s=t.isValid;return{set:async(o,c)=>{S||await T.kvStore.set(n+o,c,a)},get:async o=>{if(S)return;let c=n+o,h=await T.kvStore.get(c);if(s&&!s(h)){console.warn(`indexedDb adapter: Auto-discard invalid value for ${o}: ${JSON.stringify(h)}`),await T.kvStore.delete(c);return}return h},delete:async o=>{S||await T.kvStore.delete(n+o)},clear:async()=>{S||await T.kvStore.clear()}}}default:throw t.persistTo,new Error(`Unknown store.adapter ${t.persistTo}`)}}return t}var N={storeExpiryMs:30*C.MS_PER_DAY,lazyLoad:!1};var Q=class{dataByKey=new Map;subscribersByKey=new Map;get(e){return this.dataByKey.get(e)}set(e,t){let a=this.dataByKey.get(e);a!==t&&(this.dataByKey.set(e,t),this.notify(e,t,a))}delete(e){this.dataByKey.delete(e)}setNoNotify(e,t){this.dataByKey.set(e,t)}subscribe(e,t){let a=this.subscribersByKey.get(e);return a||(a=new Set,this.subscribersByKey.set(e,a)),a.add(t),()=>{a.delete(t),a.size===0&&this.subscribersByKey.delete(e)}}notify(e,t,a){let i=this.subscribersByKey.get(e);if(!i)return;let n=Array.from(i.values());for(let s of n)try{s(t,a)}catch(o){console.error("Error invoking subscriber:",o)}}},b=new Q,M="state",W=0,v=class{constructor(e,t){this.defaultValue=e;this.pubSubKey=String(W++),this.storageAdapter=K(t,N.storeExpiryMs),this.setValueBounded=n=>{this.setValue(n)},b.setNoNotify(this.pubSubKey,e);let a=t?.lazyLoad;(a!==void 0?a:N.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;initStarted=!1;initDone=!1;subscribe(e){return b.subscribe(this.pubSubKey,()=>e())}hasStore(){return!!this.storageAdapter}async setInStore(e){return await this.storageAdapter?.set(M,e)}async getFromStore(){return await this.storageAdapter?.get(M)}getSnapshot(){return b.get(this.pubSubKey)}setValue(e){if(typeof e=="function"){let t=b.get(this.pubSubKey);e=e(t)}b.set(this.pubSubKey,e),this.setInStore(e)}delete(){b.set(this.pubSubKey,this.defaultValue),this.storageAdapter?.set(M,this.defaultValue)}async initDefaultValueOnce(){if(this.initDone||this.initStarted)return;this.initStarted=!0;let e=await this.getFromStore(),t=e!==void 0?e:this.defaultValue;b.set(this.pubSubKey,t),this.initDone=!0}};function F(r,e){return new v(r,e)}function V(r){return r.initDefaultValueOnce(),[(0,z.useSyncExternalStore)(t=>r.subscribe(t),()=>r.getSnapshot(),()=>r.getSnapshot()),r.setValueBounded]}function k(r){let e=new Set;return JSON.stringify(r,(t,a)=>{if(typeof a=="function")throw new Error("Functions should not be passed into query parameters");if(!a||typeof a!="object"||Array.isArray(a))return a;if(e.has(a))return"[Circular]";if(e.add(a),a!==r)try{JSON.stringify(a)}catch{return"[Unserializable]"}return Object.keys(a).sort().reduce((i,n)=>(i[n]=a[n],i),{})})}var E=O(require("lodash/isEqual")),x=require("react");function U(r){let[e,t]=(0,x.useState)(r);return(0,x.useMemo)(()=>e===r||(0,E.default)(e,r)?e:(t(r),r),[e,r])}var f={staleMs:0,expiryMs:30*R.MS_PER_DAY,refetchOnStale:!0,persistTo:void 0,maxSize:100,maxBytes:1e5,log:(...r)=>console.log("[sharedQuery]",...r)};function H(r){Object.assign(f,r)}var w=class{inflightQueries=new Map;queryState;mountedKeys=new Map;queryName;queryFn;refetchFn;expiryMs;staleMs;refetchOnStale;maxSize;maxBytes;keepLastNonEmptyData;log;constructor(e){if(this.queryName=e.queryName,this.queryFn=e.queryFn,this.refetchFn=e.refetchFn,this.staleMs=e?.staleMs??f.staleMs,this.expiryMs=e?.expiryMs??f.expiryMs,this.refetchOnStale=e?.refetchOnStale??f.refetchOnStale,!e.store){let t=e.persistTo??f.persistTo;t&&(e.store={persistTo:t,key:e.queryName,expiryMs:this.expiryMs})}this.maxSize=e?.maxSize??f.maxSize,this.maxBytes=e?.maxBytes??f.maxBytes,this.keepLastNonEmptyData=!!e?.keepLastNonEmptyData,this.log=e?.log??f.log,this.queryState=F({},e)}getQueryKey(e){return this.queryName+":"+k(e)}getAbortController(e){let t=this.getQueryKey(e);return this.getAbortControllerByKey(t)}getAbortControllerByKey(e){return this.inflightQueries.get(e)?.abortController??null}getAbortSignal(e){let t=this.getQueryKey(e);return this.getAbortSignalByKey(t)}getAbortSignalByKey(e){return this.getAbortControllerByKey(e)?.signal??null}isStale(e){return this.staleMs===0?!0:Date.now()-e>this.staleMs}isExpired(e){return this.expiryMs===0?!1:Date.now()-e>this.expiryMs}async getCachedOrFetch(...e){let t=this.getQueryKey(e),a=this.queryState.getSnapshot()?.[t];return a?.data&&!this.isExpired(a.dataUpdatedMs??0)?this.isStale(a.dataUpdatedMs??0)?(this.refetchOnStale&&(this.log(`refetchOnStale for ${t}`),this.dedupedFetch(t,"query",...e)),this.log(`Returning stale data for ${t}`),a.data):(this.log(`Return fresh data ${t} from cache without fetching`),a.data):await this.dedupedFetch(t,"query",...e)}dedupedFetch(e,t,...a){let i=this.inflightQueries.get(e);if(i)return this.log(`Deduplicating inflight fetch for ${e}, queryType=${t}`),i.promise;let n=this.startFetching(e,t,a),s=new AbortController;return this.inflightQueries.set(e,{promise:n,abortController:s}),n}async startFetching(e,t,a){this.log(`Start fetching ${e}, queryType=${t}`);try{let i=await(t==="refetch"&&this.refetchFn?this.refetchFn(...a):this.queryFn(...a));return this.log(`Successfully fetched ${e}`),this.setData(e,i,Date.now()),i}catch(i){throw this.log(`Failed to fetch ${e} for queryType=${t}: ${i}`),i}finally{this.inflightQueries.delete(e)}}getQueryValue(e){return this.queryState.getSnapshot()?.[e]}getData(e){let t=this.queryState.getSnapshot()?.[e];if(t?.lastUpdatedMs&&this.isExpired(t.lastUpdatedMs)){this.deleteData(e);return}return t?.data}setData(e,t,a=Date.now()){let i={lastUpdatedMs:Date.now(),loading:!1,data:t,dataUpdatedMs:a};this.queryState.setValue(n=>({...n,[e]:i})),this.enforceSizeLimit()}async updateFromSource(...e){let t=this.getQueryKey(e);return await this.dedupedFetch(t,"query",...e)}async refetch(...e){let t=this.getQueryKey(e);return await this.dedupedFetch(t,"refetch",...e)}deleteData(e){let a={...this.queryState.getSnapshot()};delete a[e],this.queryState.setValue(a);let i=this.inflightQueries.get(e);i&&(this.inflightQueries.delete(e),i.abortController.abort("Deleted"))}clear(){this.queryState.delete();for(let{abortController:e}of Array.from(this.inflightQueries.values()))e.abort("Cleared");this.inflightQueries.clear()}mount(e){this.mountedKeys.set(e,(this.mountedKeys.get(e)??0)+1)}unmount(e){let t=this.mountedKeys.get(e);t===1?this.mountedKeys.delete(e):this.mountedKeys.set(e,(t??0)-1)}enforceSizeLimit(){if(!this.maxSize&&!this.maxBytes)return;let e=this.queryState.getSnapshot(),t=Object.keys(e).length;if(t<=1)return;let a=(0,m.default)(e),i=this.maxSize?t-this.maxSize:0,n=this.maxBytes?a-this.maxBytes:0;if(i<=0&&n<=0){this.log(`No need to trim data for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.log(`Need to trim ${this.queryName}: numToCut=${i}, bytesToCut=${n}`);let s={...e},o=!1,c=(u,g)=>{if(this.mountedKeys.has(u)){this.log(`Cannot clean up ${u} as it is mounted`);return}let l=(0,m.default)(u)+(0,m.default)(s[u]);this.log(`Cleaning up unmounted${g?", expired":""} ${u} (byteSize=${l.toLocaleString()})`),i--,n-=l,delete s[u],o=!0},h=Object.entries(s).sort((u,g)=>u[1].lastUpdatedMs-g[1].lastUpdatedMs);for(let[u]of h){if(!this.isExpired(s[u].lastUpdatedMs))break;c(u,!0)}if(i>0||n>0){o&&(h=Object.entries(s).sort((u,g)=>u[1].lastUpdatedMs-g[1].lastUpdatedMs));for(let[u]of h)if(c(u,!1),i<=0&&n<=0)break}if(!o){this.log(`No trimmable entries found for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.queryState.setValue(s),this.log(`Trimmed data for ${this.queryName}: size=[${t} -> ${Object.keys(s).length}] (limit=${this.maxSize}), byteSize=[${a.toLocaleString()} -> ${(0,m.default)(s).toLocaleString()}] (limit=${this.maxBytes.toLocaleString()})`)}},L=new Set;function X(r){if(L.has(r.queryName))throw new Error(`Duplicate shared query "${r.queryName}"`);return L.add(r.queryName),new w(r)}var P={lastUpdatedMs:0,loading:!0};function Z(r,...e){let t=U(e),[a,i]=V(r.queryState),n=(0,y.useRef)(!0),s=(0,y.useMemo)(()=>r.getQueryKey(t),[r,t]),o=a[s]??P,c=(0,y.useCallback)(l=>{i(d=>{let p={...d};return p[s]=typeof l=="function"?l(p[s]??P):l,p})},[s,i]),[h,u]=(0,y.useState)();(0,y.useEffect)(()=>{r.keepLastNonEmptyData&&o.data!==void 0&&u(o.data)},[r.keepLastNonEmptyData,o.data]);let g=(0,y.useCallback)(async l=>{r.log(`Begin executing shared query ${s}, isRefetch=${l}`),c(d=>({...d,lastUpdatedMs:Date.now(),loading:!0}));try{let d=await(l?r.refetch(...t):r.getCachedOrFetch(...t));if(n.current){let p=Date.now();c({lastUpdatedMs:p,loading:!1,data:d,dataUpdatedMs:p})}}catch(d){if(n.current){let p=Date.now();c(A=>({...A,lastUpdatedMs:p,loading:!1,error:d,errorUpdatedMs:p}))}}},[r,s,c,t]);return(0,y.useEffect)(()=>(n.current=!0,r.mount(s),g(!1),()=>{n.current=!1,r.unmount(s)}),[g,r,s]),(0,y.useMemo)(()=>({...o,lastNonEmptyData:h,abortCurrentQuery:l=>{let d=r.getAbortControllerByKey(s);return d?.abort(l),!!d},refetch:()=>{g(!0)},setData:(l,d)=>{let p=Date.now();c(A=>({...A,data:l,dataUpdatedMs:d??p,lastUpdatedMs:p,loading:!1}))},deleteData:()=>{i(l=>{let d={...l};return delete d[s],d})}}),[g,h,r,s,o,i,c])}0&&(module.exports={SharedQuery,SharedQueryConfig,configureSharedQuery,sharedQuery,useSharedQuery});
|
|
2
2
|
//# sourceMappingURL=sharedQuery.min.cjs.map
|