@choksheak/react-utils 0.2.7 → 0.2.8
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 +1 -1
- package/sharedQuery.cjs +60 -71
- package/sharedQuery.d.mts +58 -4
- package/sharedQuery.d.ts +57 -4
- 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 +67 -72
- package/sharedState.cjs +3 -1
- package/sharedState.d.mts +5 -4
- package/sharedState.d.ts +5 -4
- 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 +3 -1
package/package.json
CHANGED
package/sharedQuery.cjs
CHANGED
|
@@ -214,7 +214,9 @@ var SharedState = class {
|
|
|
214
214
|
options,
|
|
215
215
|
SharedStateDefaults.storeExpiryMs
|
|
216
216
|
);
|
|
217
|
-
this.setValueBounded = (
|
|
217
|
+
this.setValueBounded = (next) => {
|
|
218
|
+
this.setValue(next);
|
|
219
|
+
};
|
|
218
220
|
pubSubStore.setNoNotify(this.pubSubKey, defaultValue);
|
|
219
221
|
const lazyLoad = options?.lazyLoad;
|
|
220
222
|
const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateDefaults.lazyLoad;
|
|
@@ -319,19 +321,19 @@ function useDeepMemo(value) {
|
|
|
319
321
|
|
|
320
322
|
// src/sharedQuery.ts
|
|
321
323
|
var SharedQueryDefaults = {
|
|
322
|
-
|
|
324
|
+
/** Refetch on every page load. */
|
|
323
325
|
staleMs: 0,
|
|
324
|
-
|
|
326
|
+
/** Keep in cache for a long time. */
|
|
325
327
|
expiryMs: 30 * import_timeConstants2.MS_PER_DAY,
|
|
326
|
-
|
|
328
|
+
/** Always refetch automatically if the data is stale. */
|
|
327
329
|
refetchOnStale: true,
|
|
328
|
-
|
|
330
|
+
/** No default persistence configured. */
|
|
329
331
|
persistTo: void 0,
|
|
330
|
-
|
|
332
|
+
/** 100 is not too large, but please tweak accordingly. */
|
|
331
333
|
maxSize: 100,
|
|
332
|
-
|
|
334
|
+
/** 100kb limit before discarding old records. */
|
|
333
335
|
maxBytes: 1e5,
|
|
334
|
-
|
|
336
|
+
/** Default to log to console. */
|
|
335
337
|
log: (...args) => console.log("[sharedQuery]", ...args)
|
|
336
338
|
};
|
|
337
339
|
var SharedQuery = class {
|
|
@@ -350,6 +352,7 @@ var SharedQuery = class {
|
|
|
350
352
|
refetchOnStale;
|
|
351
353
|
maxSize;
|
|
352
354
|
maxBytes;
|
|
355
|
+
keepLastNonEmptyData;
|
|
353
356
|
log;
|
|
354
357
|
constructor(options) {
|
|
355
358
|
this.queryName = options.queryName;
|
|
@@ -367,6 +370,7 @@ var SharedQuery = class {
|
|
|
367
370
|
}
|
|
368
371
|
this.maxSize = options?.maxSize ?? SharedQueryDefaults.maxSize;
|
|
369
372
|
this.maxBytes = options?.maxBytes ?? SharedQueryDefaults.maxBytes;
|
|
373
|
+
this.keepLastNonEmptyData = Boolean(options?.keepLastNonEmptyData);
|
|
370
374
|
this.log = options?.log ?? SharedQueryDefaults.log;
|
|
371
375
|
this.queryState = sharedState(
|
|
372
376
|
{},
|
|
@@ -556,70 +560,48 @@ var SharedQuery = class {
|
|
|
556
560
|
return;
|
|
557
561
|
}
|
|
558
562
|
const oldByteSize = (0, import_object_sizeof.default)(oldState);
|
|
559
|
-
let
|
|
563
|
+
let numToCut = this.maxSize ? oldSize - this.maxSize : 0;
|
|
564
|
+
let bytesToCut = this.maxBytes ? oldByteSize - this.maxBytes : 0;
|
|
565
|
+
if (numToCut <= 0 && bytesToCut <= 0) {
|
|
566
|
+
this.log(
|
|
567
|
+
`No need to trim data for ${this.queryName}: size=${oldSize} (limit=${this.maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`
|
|
568
|
+
);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
this.log(
|
|
572
|
+
`Need to trim ${this.queryName}: numToCut=${numToCut}, bytesToCut=${bytesToCut}`
|
|
573
|
+
);
|
|
574
|
+
const newState = { ...oldState };
|
|
560
575
|
let needUpdate = false;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
);
|
|
567
|
-
|
|
568
|
-
needUpdate = true;
|
|
569
|
-
const entriesByTimeAscending = Object.entries(newState).sort(
|
|
570
|
-
(entry1, entry2) => (
|
|
571
|
-
// Sort descending.
|
|
572
|
-
entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
573
|
-
)
|
|
574
|
-
);
|
|
575
|
-
for (let i = 0; i < entriesByTimeAscending.length && numToCut > 0; i++) {
|
|
576
|
-
const key = entriesByTimeAscending[i][0];
|
|
577
|
-
if (this.mountedKeys.has(key)) {
|
|
578
|
-
this.log(`Cannot clean up ${key} as it is mounted`);
|
|
579
|
-
} else {
|
|
580
|
-
this.log(`Cleaning up unmounted ${key}`);
|
|
581
|
-
delete newState[key];
|
|
582
|
-
numToCut--;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
576
|
+
const entriesByTimeAscending = Object.entries(newState).sort(
|
|
577
|
+
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
578
|
+
);
|
|
579
|
+
for (const [key] of entriesByTimeAscending) {
|
|
580
|
+
if (this.mountedKeys.has(key)) {
|
|
581
|
+
this.log(`Cannot clean up ${key} as it is mounted`);
|
|
582
|
+
continue;
|
|
585
583
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
needUpdate = true;
|
|
597
|
-
}
|
|
598
|
-
const entriesByTimeAscending = Object.entries(newState).sort(
|
|
599
|
-
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
600
|
-
);
|
|
601
|
-
for (let i = 0; i < entriesByTimeAscending.length && bytesToCut > 0; i++) {
|
|
602
|
-
const key = entriesByTimeAscending[i][0];
|
|
603
|
-
if (this.mountedKeys.has(key)) {
|
|
604
|
-
this.log(`Cannot clean up ${key} as it is mounted`);
|
|
605
|
-
} else {
|
|
606
|
-
this.log(`Cleaning up unmounted ${key}`);
|
|
607
|
-
const thisSize = (0, import_object_sizeof.default)(key) + (0, import_object_sizeof.default)(newState[key]);
|
|
608
|
-
delete newState[key];
|
|
609
|
-
bytesToCut -= thisSize;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
584
|
+
const byteSize = (0, import_object_sizeof.default)(key) + (0, import_object_sizeof.default)(newState[key]);
|
|
585
|
+
this.log(
|
|
586
|
+
`Cleaning up unmounted ${key} (byteSize=${byteSize.toLocaleString()})`
|
|
587
|
+
);
|
|
588
|
+
numToCut--;
|
|
589
|
+
bytesToCut -= byteSize;
|
|
590
|
+
delete newState[key];
|
|
591
|
+
needUpdate = true;
|
|
592
|
+
if (numToCut <= 0 && bytesToCut <= 0) {
|
|
593
|
+
break;
|
|
612
594
|
}
|
|
613
595
|
}
|
|
614
|
-
if (needUpdate) {
|
|
615
|
-
this.queryState.setValue(newState);
|
|
596
|
+
if (!needUpdate) {
|
|
616
597
|
this.log(
|
|
617
|
-
`
|
|
598
|
+
`No trimmable entries found for ${this.queryName}: size=${oldSize} (limit=${this.maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`
|
|
618
599
|
);
|
|
619
600
|
return;
|
|
620
601
|
}
|
|
602
|
+
this.queryState.setValue(newState);
|
|
621
603
|
this.log(
|
|
622
|
-
`
|
|
604
|
+
`Trimmed data for ${this.queryName}: size=[${oldSize} -> ${Object.keys(newState).length}] (limit=${this.maxSize}), byteSize=[${oldByteSize.toLocaleString()} -> ${(0, import_object_sizeof.default)(newState).toLocaleString()}] (limit=${this.maxBytes.toLocaleString()})`
|
|
623
605
|
);
|
|
624
606
|
}
|
|
625
607
|
};
|
|
@@ -643,23 +625,29 @@ function useSharedQuery(query, args = []) {
|
|
|
643
625
|
() => query.getQueryKey(stableArgs),
|
|
644
626
|
[query, stableArgs]
|
|
645
627
|
);
|
|
646
|
-
const queryStateValue = queryState[queryKey];
|
|
628
|
+
const queryStateValue = queryState[queryKey] ?? DEFAULT_QUERY_STATE_ENTRY;
|
|
647
629
|
const setQueryStateValue = (0, import_react3.useCallback)(
|
|
648
|
-
(
|
|
630
|
+
(next) => {
|
|
649
631
|
setQueryState((prev) => {
|
|
650
632
|
const clone = { ...prev };
|
|
651
|
-
clone[queryKey] = typeof
|
|
633
|
+
clone[queryKey] = typeof next === "function" ? next(clone[queryKey] ?? DEFAULT_QUERY_STATE_ENTRY) : next;
|
|
652
634
|
return clone;
|
|
653
635
|
});
|
|
654
636
|
},
|
|
655
637
|
[queryKey, setQueryState]
|
|
656
638
|
);
|
|
639
|
+
const [lastNonEmptyData, setLastNonEmptyData] = (0, import_react3.useState)();
|
|
640
|
+
(0, import_react3.useEffect)(() => {
|
|
641
|
+
if (query.keepLastNonEmptyData && queryStateValue.data !== void 0) {
|
|
642
|
+
setLastNonEmptyData(queryStateValue.data);
|
|
643
|
+
}
|
|
644
|
+
}, [query.keepLastNonEmptyData, queryStateValue.data]);
|
|
657
645
|
const execute = (0, import_react3.useCallback)(
|
|
658
646
|
async (forceRefresh) => {
|
|
659
647
|
query.log(`Begin executing shared query ${queryKey}`);
|
|
660
648
|
setQueryStateValue((prev) => ({
|
|
661
649
|
// Keep old data and error.
|
|
662
|
-
...prev
|
|
650
|
+
...prev,
|
|
663
651
|
lastUpdatedMs: Date.now(),
|
|
664
652
|
loading: true
|
|
665
653
|
}));
|
|
@@ -680,7 +668,7 @@ function useSharedQuery(query, args = []) {
|
|
|
680
668
|
const now = Date.now();
|
|
681
669
|
setQueryStateValue((prev) => ({
|
|
682
670
|
// Keep old data.
|
|
683
|
-
...prev
|
|
671
|
+
...prev,
|
|
684
672
|
lastUpdatedMs: now,
|
|
685
673
|
loading: false,
|
|
686
674
|
error: e,
|
|
@@ -701,9 +689,9 @@ function useSharedQuery(query, args = []) {
|
|
|
701
689
|
};
|
|
702
690
|
}, [execute, query, queryKey]);
|
|
703
691
|
return (0, import_react3.useMemo)(() => {
|
|
704
|
-
const state = queryStateValue ?? DEFAULT_QUERY_STATE_ENTRY;
|
|
705
692
|
return {
|
|
706
|
-
...
|
|
693
|
+
...queryStateValue,
|
|
694
|
+
lastNonEmptyData,
|
|
707
695
|
// This works only if the user-given queryFn supports abort. If not,
|
|
708
696
|
// this function doesn't do anything, since we don't have any means to
|
|
709
697
|
// abort the running queryFn.
|
|
@@ -718,7 +706,7 @@ function useSharedQuery(query, args = []) {
|
|
|
718
706
|
setData: (data, dataUpdatedMs) => {
|
|
719
707
|
const now = Date.now();
|
|
720
708
|
setQueryStateValue((prev) => ({
|
|
721
|
-
...prev
|
|
709
|
+
...prev,
|
|
722
710
|
data,
|
|
723
711
|
dataUpdatedMs: dataUpdatedMs ?? now,
|
|
724
712
|
lastUpdatedMs: now,
|
|
@@ -735,6 +723,7 @@ function useSharedQuery(query, args = []) {
|
|
|
735
723
|
};
|
|
736
724
|
}, [
|
|
737
725
|
execute,
|
|
726
|
+
lastNonEmptyData,
|
|
738
727
|
query,
|
|
739
728
|
queryKey,
|
|
740
729
|
queryStateValue,
|
package/sharedQuery.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SharedStateOptions, SharedState } from './sharedState.mjs';
|
|
2
|
+
import 'react';
|
|
2
3
|
import './utils/storage.mjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -88,7 +89,7 @@ import './utils/storage.mjs';
|
|
|
88
89
|
/** Type of the queryFn used to fetch the data from source. */
|
|
89
90
|
type QueryFn<TArgs extends unknown[], TData> = (...args: TArgs) => Promise<TData> | TData;
|
|
90
91
|
/** An entry for a persisted state corresponding to one query key. */
|
|
91
|
-
type QueryStateValue<TData> = {
|
|
92
|
+
type QueryStateValue<TData> = Readonly<{
|
|
92
93
|
/**
|
|
93
94
|
* When this record was last updated. Used for LRU determination. If you want
|
|
94
95
|
* the data age, please use `dataUpdatedMs` instead.
|
|
@@ -104,9 +105,14 @@ type QueryStateValue<TData> = {
|
|
|
104
105
|
*/
|
|
105
106
|
error?: unknown;
|
|
106
107
|
errorUpdatedMs?: number;
|
|
107
|
-
}
|
|
108
|
+
}>;
|
|
108
109
|
/** Return type for useSharedQuery(). */
|
|
109
|
-
type UseQueryResult<TData> = QueryStateValue<TData> & {
|
|
110
|
+
type UseQueryResult<TData> = QueryStateValue<TData> & Readonly<{
|
|
111
|
+
/**
|
|
112
|
+
* Return the last non-empty data if you want to display the last loaded data
|
|
113
|
+
* while waiting for the new data to load.
|
|
114
|
+
*/
|
|
115
|
+
lastNonEmptyData: TData | undefined;
|
|
110
116
|
/**
|
|
111
117
|
* Cancels the inflight query (if any). Returns true if canceled.
|
|
112
118
|
*/
|
|
@@ -121,7 +127,7 @@ type UseQueryResult<TData> = QueryStateValue<TData> & {
|
|
|
121
127
|
*/
|
|
122
128
|
setData: (data: TData, dataUpdatedMs?: number) => void;
|
|
123
129
|
deleteData: () => void;
|
|
124
|
-
}
|
|
130
|
+
}>;
|
|
125
131
|
/** List of all available persistence options. */
|
|
126
132
|
type PersistTo = "localStorage" | "indexedDb";
|
|
127
133
|
/**
|
|
@@ -131,24 +137,71 @@ type PersistTo = "localStorage" | "indexedDb";
|
|
|
131
137
|
type SharedQueryState<TData> = Record<string, QueryStateValue<TData>>;
|
|
132
138
|
/** Options to configure a shared query. */
|
|
133
139
|
type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
140
|
+
/**
|
|
141
|
+
* The name could have been auto-generated, but we let the user give us a
|
|
142
|
+
* human-readable name that can be identified quickly in the logs.
|
|
143
|
+
* Note that the queryKey is a string formed by the queryName plus the query
|
|
144
|
+
* arguments.
|
|
145
|
+
*/
|
|
134
146
|
queryName: string;
|
|
147
|
+
/**
|
|
148
|
+
* Function to fetch data.
|
|
149
|
+
*/
|
|
135
150
|
queryFn: QueryFn<TArgs, TData>;
|
|
151
|
+
/**
|
|
152
|
+
* ms before data considered stale; 0 means always stale.
|
|
153
|
+
*/
|
|
136
154
|
staleMs?: number;
|
|
155
|
+
/**
|
|
156
|
+
* ms before data is removed from cache; 0 means never expire.
|
|
157
|
+
*/
|
|
137
158
|
expiryMs?: number;
|
|
159
|
+
/**
|
|
160
|
+
* Trigger background re-fetch if stale.
|
|
161
|
+
*/
|
|
138
162
|
refetchOnStale?: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Shortcut to set the localStorageKey or indexedDbKey automatically from
|
|
165
|
+
* queryName. This is important because it avoids requiring the user to
|
|
166
|
+
* specify the same queryName twice everytime.
|
|
167
|
+
*/
|
|
139
168
|
persistTo?: PersistTo;
|
|
169
|
+
/**
|
|
170
|
+
* Max number of entries to keep. The oldest entries will be discarded.
|
|
171
|
+
* The last record cannot be discarded. 0 means no limit.
|
|
172
|
+
*/
|
|
140
173
|
maxSize?: number;
|
|
174
|
+
/**
|
|
175
|
+
* Max number of bytes to keep. The oldest entries will be discarded.
|
|
176
|
+
* The last record cannot be discarded. 0 means no limit.
|
|
177
|
+
*/
|
|
141
178
|
maxBytes?: number;
|
|
179
|
+
/**
|
|
180
|
+
* Keep a copy of the last non-empty data so that when you change the
|
|
181
|
+
* query arguments, you can still display the old data while waiting for the
|
|
182
|
+
* new data to load.
|
|
183
|
+
*/
|
|
184
|
+
keepLastNonEmptyData?: boolean;
|
|
185
|
+
/**
|
|
186
|
+
* Customize the logger.
|
|
187
|
+
*/
|
|
142
188
|
log?: (...args: unknown[]) => void;
|
|
143
189
|
} & SharedStateOptions<SharedQueryState<TData>>;
|
|
144
190
|
/** Users can override these values globally. */
|
|
145
191
|
declare const SharedQueryDefaults: {
|
|
192
|
+
/** Refetch on every page load. */
|
|
146
193
|
staleMs: number;
|
|
194
|
+
/** Keep in cache for a long time. */
|
|
147
195
|
expiryMs: number;
|
|
196
|
+
/** Always refetch automatically if the data is stale. */
|
|
148
197
|
refetchOnStale: boolean;
|
|
198
|
+
/** No default persistence configured. */
|
|
149
199
|
persistTo: PersistTo | undefined;
|
|
200
|
+
/** 100 is not too large, but please tweak accordingly. */
|
|
150
201
|
maxSize: number;
|
|
202
|
+
/** 100kb limit before discarding old records. */
|
|
151
203
|
maxBytes: number;
|
|
204
|
+
/** Default to log to console. */
|
|
152
205
|
log: (...args: unknown[]) => void;
|
|
153
206
|
};
|
|
154
207
|
/** Please use sharedQuery() instead. */
|
|
@@ -168,6 +221,7 @@ declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
168
221
|
readonly refetchOnStale: boolean;
|
|
169
222
|
readonly maxSize: number;
|
|
170
223
|
readonly maxBytes: number;
|
|
224
|
+
readonly keepLastNonEmptyData: boolean;
|
|
171
225
|
readonly log: (...args: unknown[]) => void;
|
|
172
226
|
constructor(options: SharedQueryOptions<TArgs, TData>);
|
|
173
227
|
/** Returns a key to identify requests based on the given args. */
|
package/sharedQuery.d.ts
CHANGED
|
@@ -85,7 +85,7 @@ import { SharedState, SharedStateOptions } from "./sharedState";
|
|
|
85
85
|
/** Type of the queryFn used to fetch the data from source. */
|
|
86
86
|
export type QueryFn<TArgs extends unknown[], TData> = (...args: TArgs) => Promise<TData> | TData;
|
|
87
87
|
/** An entry for a persisted state corresponding to one query key. */
|
|
88
|
-
export type QueryStateValue<TData> = {
|
|
88
|
+
export type QueryStateValue<TData> = Readonly<{
|
|
89
89
|
/**
|
|
90
90
|
* When this record was last updated. Used for LRU determination. If you want
|
|
91
91
|
* the data age, please use `dataUpdatedMs` instead.
|
|
@@ -101,9 +101,14 @@ export type QueryStateValue<TData> = {
|
|
|
101
101
|
*/
|
|
102
102
|
error?: unknown;
|
|
103
103
|
errorUpdatedMs?: number;
|
|
104
|
-
}
|
|
104
|
+
}>;
|
|
105
105
|
/** Return type for useSharedQuery(). */
|
|
106
|
-
export type UseQueryResult<TData> = QueryStateValue<TData> & {
|
|
106
|
+
export type UseQueryResult<TData> = QueryStateValue<TData> & Readonly<{
|
|
107
|
+
/**
|
|
108
|
+
* Return the last non-empty data if you want to display the last loaded data
|
|
109
|
+
* while waiting for the new data to load.
|
|
110
|
+
*/
|
|
111
|
+
lastNonEmptyData: TData | undefined;
|
|
107
112
|
/**
|
|
108
113
|
* Cancels the inflight query (if any). Returns true if canceled.
|
|
109
114
|
*/
|
|
@@ -118,7 +123,7 @@ export type UseQueryResult<TData> = QueryStateValue<TData> & {
|
|
|
118
123
|
*/
|
|
119
124
|
setData: (data: TData, dataUpdatedMs?: number) => void;
|
|
120
125
|
deleteData: () => void;
|
|
121
|
-
}
|
|
126
|
+
}>;
|
|
122
127
|
/** List of all available persistence options. */
|
|
123
128
|
export type PersistTo = "localStorage" | "indexedDb";
|
|
124
129
|
/**
|
|
@@ -128,24 +133,71 @@ export type PersistTo = "localStorage" | "indexedDb";
|
|
|
128
133
|
export type SharedQueryState<TData> = Record<string, QueryStateValue<TData>>;
|
|
129
134
|
/** Options to configure a shared query. */
|
|
130
135
|
export type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
136
|
+
/**
|
|
137
|
+
* The name could have been auto-generated, but we let the user give us a
|
|
138
|
+
* human-readable name that can be identified quickly in the logs.
|
|
139
|
+
* Note that the queryKey is a string formed by the queryName plus the query
|
|
140
|
+
* arguments.
|
|
141
|
+
*/
|
|
131
142
|
queryName: string;
|
|
143
|
+
/**
|
|
144
|
+
* Function to fetch data.
|
|
145
|
+
*/
|
|
132
146
|
queryFn: QueryFn<TArgs, TData>;
|
|
147
|
+
/**
|
|
148
|
+
* ms before data considered stale; 0 means always stale.
|
|
149
|
+
*/
|
|
133
150
|
staleMs?: number;
|
|
151
|
+
/**
|
|
152
|
+
* ms before data is removed from cache; 0 means never expire.
|
|
153
|
+
*/
|
|
134
154
|
expiryMs?: number;
|
|
155
|
+
/**
|
|
156
|
+
* Trigger background re-fetch if stale.
|
|
157
|
+
*/
|
|
135
158
|
refetchOnStale?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Shortcut to set the localStorageKey or indexedDbKey automatically from
|
|
161
|
+
* queryName. This is important because it avoids requiring the user to
|
|
162
|
+
* specify the same queryName twice everytime.
|
|
163
|
+
*/
|
|
136
164
|
persistTo?: PersistTo;
|
|
165
|
+
/**
|
|
166
|
+
* Max number of entries to keep. The oldest entries will be discarded.
|
|
167
|
+
* The last record cannot be discarded. 0 means no limit.
|
|
168
|
+
*/
|
|
137
169
|
maxSize?: number;
|
|
170
|
+
/**
|
|
171
|
+
* Max number of bytes to keep. The oldest entries will be discarded.
|
|
172
|
+
* The last record cannot be discarded. 0 means no limit.
|
|
173
|
+
*/
|
|
138
174
|
maxBytes?: number;
|
|
175
|
+
/**
|
|
176
|
+
* Keep a copy of the last non-empty data so that when you change the
|
|
177
|
+
* query arguments, you can still display the old data while waiting for the
|
|
178
|
+
* new data to load.
|
|
179
|
+
*/
|
|
180
|
+
keepLastNonEmptyData?: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Customize the logger.
|
|
183
|
+
*/
|
|
139
184
|
log?: (...args: unknown[]) => void;
|
|
140
185
|
} & SharedStateOptions<SharedQueryState<TData>>;
|
|
141
186
|
/** Users can override these values globally. */
|
|
142
187
|
export declare const SharedQueryDefaults: {
|
|
188
|
+
/** Refetch on every page load. */
|
|
143
189
|
staleMs: number;
|
|
190
|
+
/** Keep in cache for a long time. */
|
|
144
191
|
expiryMs: number;
|
|
192
|
+
/** Always refetch automatically if the data is stale. */
|
|
145
193
|
refetchOnStale: boolean;
|
|
194
|
+
/** No default persistence configured. */
|
|
146
195
|
persistTo: PersistTo | undefined;
|
|
196
|
+
/** 100 is not too large, but please tweak accordingly. */
|
|
147
197
|
maxSize: number;
|
|
198
|
+
/** 100kb limit before discarding old records. */
|
|
148
199
|
maxBytes: number;
|
|
200
|
+
/** Default to log to console. */
|
|
149
201
|
log: (...args: unknown[]) => void;
|
|
150
202
|
};
|
|
151
203
|
/** Please use sharedQuery() instead. */
|
|
@@ -165,6 +217,7 @@ export declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
165
217
|
readonly refetchOnStale: boolean;
|
|
166
218
|
readonly maxSize: number;
|
|
167
219
|
readonly maxBytes: number;
|
|
220
|
+
readonly keepLastNonEmptyData: boolean;
|
|
168
221
|
readonly log: (...args: unknown[]) => void;
|
|
169
222
|
constructor(options: SharedQueryOptions<TArgs, TData>);
|
|
170
223
|
/** Returns a key to identify requests based on the given args. */
|
package/sharedQuery.min.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var q=Object.create;var m=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var _=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var J=(i,e)=>{for(var t in e)m(i,t,{get:e[t],enumerable:!0})},K=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of R(e))!j.call(i,s)&&s!==t&&m(i,s,{get:()=>e[s],enumerable:!(r=L(e,s))||r.enumerable});return i};var Q=(i,e,t)=>(t=i!=null?q(_(i)):{},K(e||!i||!i.__esModule?m(t,"default",{value:i,enumerable:!0}):t,i)),W=i=>K(m({},"__esModule",{value:!0}),i);var X={};J(X,{SharedQuery:()=>x,SharedQueryDefaults:()=>h,sharedQuery:()=>I,useSharedQuery:()=>H});module.exports=W(X);var U=require("@choksheak/ts-utils/timeConstants"),S=Q(require("object-sizeof")),p=require("react");var O=require("@choksheak/ts-utils/timeConstants"),z=require("react");var f=require("@choksheak/ts-utils/kvStore");function k(i,e){let{localStorageKey:t,indexedDbKey:r,storeExpiryMs:s,store:a}=i??{};if(a)return a;let n=s||e;return t?new w(t,n):r?new D(r,n):null}var w=class{constructor(e,t){this.keyPrefix=e;this.expiryMs=t}save(e,t){if(typeof window>"u")return;let r=this.keyPrefix+":"+e;window.localStorage.setItem(r,JSON.stringify({value:t,expiresAt:this.expiryMs?Date.now()+this.expiryMs:0}))}load(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e,r=window.localStorage.getItem(t);if(r)try{let s=JSON.parse(r);if(!s||s.expiresAt&&Date.now()>=s.expiresAt){window.localStorage.removeItem(t);return}return s.value}catch{window.localStorage.removeItem(t);return}}delete(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e;window.localStorage.removeItem(t)}clear(){if(!(typeof window>"u"))for(let e of Object.keys(window.localStorage))e.startsWith(this.keyPrefix)&&window.localStorage.removeItem(e)}},D=class{constructor(e,t){this.keyPrefix=e;this.expiryMs=t}async save(e,t){if(typeof window>"u")return;let r=this.keyPrefix+":"+e;await f.kvStore.set(r,JSON.stringify(t),this.expiryMs)}async load(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e,r=await f.kvStore.get(t);if(r!==void 0)try{return JSON.parse(r)}catch{await f.kvStore.delete(t);return}}async delete(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e;await f.kvStore.delete(t)}async clear(){typeof window>"u"||await f.kvStore.forEach(async e=>{e.startsWith(this.keyPrefix)&&await f.kvStore.delete(e)})}};var B={storeExpiryMs:30*O.MS_PER_DAY,lazyLoad:!1},A=class{dataByKey=new Map;subscribersByKey=new Map;get(e){return this.dataByKey.get(e)}set(e,t){let r=this.dataByKey.get(e);r!==t&&(this.dataByKey.set(e,t),this.notify(e,t,r))}delete(e){this.dataByKey.delete(e)}setNoNotify(e,t){this.dataByKey.set(e,t)}subscribe(e,t){let r=this.subscribersByKey.get(e);return r||(r=new Set,this.subscribersByKey.set(e,r)),r.add(t),()=>{r.delete(t),r.size===0&&this.subscribersByKey.delete(e)}}notify(e,t,r){let s=this.subscribersByKey.get(e);if(!s)return;let a=Array.from(s.values());for(let n of a)try{n(t,r)}catch(y){console.error("Error invoking subscriber:",y)}}},g=new A,v="state",Y=0,M=class{constructor(e,t){this.defaultValue=e;this.pubSubKey=String(Y++),this.storageAdapter=k(t,B.storeExpiryMs),this.setValueBounded=a=>this.setValue(a),g.setNoNotify(this.pubSubKey,e);let r=t?.lazyLoad;(r!==void 0?r:B.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;initDone=!1;subscribe(e){return g.subscribe(this.pubSubKey,()=>e())}getSnapshot(){return g.get(this.pubSubKey)}setValue(e){if(typeof e=="function"){let t=g.get(this.pubSubKey);e=e(t)}g.set(this.pubSubKey,e),this.storageAdapter?.save(v,e)}delete(){g.set(this.pubSubKey,this.defaultValue),this.storageAdapter?.save(v,this.defaultValue)}async initDefaultValueOnce(){if(this.initDone)return;this.initDone=!0;let e=await this.storageAdapter?.load(v);g.set(this.pubSubKey,e!==void 0?e:this.defaultValue)}};function V(i,e){return new M(i,e)}function $(i){return i.initDefaultValueOnce(),[(0,z.useSyncExternalStore)(t=>i.subscribe(t),()=>i.getSnapshot(),()=>i.getSnapshot()),i.setValueBounded]}function P(i){let e=new Set;return JSON.stringify(i,(t,r)=>{if(typeof r=="function")throw new Error("Functions should not be passed into query parameters");if(!r||typeof r!="object"||Array.isArray(r))return r;if(e.has(r))return"[Circular]";if(e.add(r),r!==i)try{JSON.stringify(r)}catch{return"[Unserializable]"}return Object.keys(r).sort().reduce((s,a)=>(s[a]=r[a],s),{})})}var C=Q(require("lodash/isEqual")),T=require("react");function E(i){let[e,t]=(0,T.useState)(i);return(0,T.useMemo)(()=>e===i||(0,C.default)(e,i)?e:(t(i),i),[e,i])}var h={staleMs:0,expiryMs:30*U.MS_PER_DAY,refetchOnStale:!0,persistTo:void 0,maxSize:100,maxBytes:1e5,log:(...i)=>console.log("[sharedQuery]",...i)},x=class{inflightQueries=new Map;queryState;mountedKeys=new Map;queryName;queryFn;expiryMs;staleMs;refetchOnStale;maxSize;maxBytes;log;constructor(e){if(this.queryName=e.queryName,this.queryFn=e.queryFn,this.staleMs=e?.staleMs??h.staleMs,this.expiryMs=e?.expiryMs??h.expiryMs,this.refetchOnStale=e?.refetchOnStale??h.refetchOnStale,!e.localStorageKey&&!e.indexedDbKey){let t=e.persistTo??h.persistTo;t==="localStorage"?e.localStorageKey=e.queryName:t==="indexedDb"&&(e.indexedDbKey=e.queryName)}this.maxSize=e?.maxSize??h.maxSize,this.maxBytes=e?.maxBytes??h.maxBytes,this.log=e?.log??h.log,this.queryState=V({},{...e,storeExpiryMs:e?.storeExpiryMs||this.expiryMs})}getQueryKey(e){return this.queryName+":"+P(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),r=this.queryState.getSnapshot()?.[t];return r?.data&&!this.isExpired(r.dataUpdatedMs??0)?this.isStale(r.dataUpdatedMs??0)?(this.refetchOnStale&&(this.log(`refetchOnStale for ${t}`),this.dedupedFetch(t,...e)),this.log(`Returning stale data for ${t}`),r.data):(this.log(`Return fresh data ${t} from cache without fetching`),r.data):await this.dedupedFetch(t,...e)}dedupedFetch(e,...t){let r=this.inflightQueries.get(e);if(r)return this.log(`Deduplicating inflight fetch for ${e}`),r.promise;let s=this.startFetching(e,t),a=new AbortController;return this.inflightQueries.set(e,{promise:s,abortController:a}),s}async startFetching(e,t){this.log(`Start fetching ${e}`);try{let r=await this.queryFn(...t);return this.log(`Successfully fetched ${e}`),this.setData(e,r,Date.now()),r}catch(r){throw this.log(`Failed to fetch ${e}: ${r}`),r}finally{this.inflightQueries.delete(e)}}getQueryValue(e){return this.queryState.getSnapshot()?.[e]}getData(e){return this.queryState.getSnapshot()?.[e]?.data}setData(e,t,r=Date.now()){let s={lastUpdatedMs:Date.now(),loading:!1,data:t,dataUpdatedMs:r};this.queryState.setValue(a=>({...a,[e]:s})),this.enforceSizeLimit()}async updateFromSource(...e){let t=this.getQueryKey(e);return await this.dedupedFetch(t,...e)}deleteData(e){let r={...this.queryState.getSnapshot()};delete r[e],this.queryState.setValue(r);let s=this.inflightQueries.get(e);s&&(this.inflightQueries.delete(e),s.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 r=(0,S.default)(e),s=e,a=!1;if(this.maxSize){let n=t-this.maxSize;if(n>0){this.log(`enforceSizeLimit for ${this.queryName} needed due to size ${t} > ${this.maxSize}`),s={...s},a=!0;let y=Object.entries(s).sort((l,o)=>l[1].lastUpdatedMs-o[1].lastUpdatedMs);for(let l=0;l<y.length&&n>0;l++){let o=y[l][0];this.mountedKeys.has(o)?this.log(`Cannot clean up ${o} as it is mounted`):(this.log(`Cleaning up unmounted ${o}`),delete s[o],n--)}}}if(this.maxBytes&&Object.keys(s).length>1){let n=a?(0,S.default)(s):r,y=n-this.maxBytes;if(y>0){this.log(`enforceSizeLimit for ${this.queryName} needed due to byte size ${n.toLocaleString()} > ${this.maxBytes.toLocaleString()}`),a||(s={...s},a=!0);let l=Object.entries(s).sort((o,d)=>o[1].lastUpdatedMs-d[1].lastUpdatedMs);for(let o=0;o<l.length&&y>0;o++){let d=l[o][0];if(this.mountedKeys.has(d))this.log(`Cannot clean up ${d} as it is mounted`);else{this.log(`Cleaning up unmounted ${d}`);let c=(0,S.default)(d)+(0,S.default)(s[d]);delete s[d],y-=c}}}}if(a){this.queryState.setValue(s),this.log(`Trimmed data for ${this.queryName}: size=(${t} -> ${Object.keys(s).length}) (limit=${this.maxSize}), byteSize=(${r.toLocaleString()} -> ${(0,S.default)(s).toLocaleString()}) (limit=${this.maxBytes.toLocaleString()})`);return}this.log(`No need to trim data for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${r.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`)}},N=new Set;function I(i){if(N.has(i.queryName))throw new Error(`Duplicate shared query "${i.queryName}"`);return N.add(i.queryName),new x(i)}var G={lastUpdatedMs:0,loading:!0};function H(i,e=[]){let t=E(e),[r,s]=$(i.queryState),a=(0,p.useRef)(!0),n=(0,p.useMemo)(()=>i.getQueryKey(t),[i,t]),y=r[n],l=(0,p.useCallback)(d=>{s(c=>{let u={...c};return u[n]=typeof d=="function"?d(u[n]):d,u})},[n,s]),o=(0,p.useCallback)(async d=>{i.log(`Begin executing shared query ${n}`),l(c=>({...c??{},lastUpdatedMs:Date.now(),loading:!0}));try{let c=await(d?i.updateFromSource(...t):i.getCachedOrFetch(...t));if(a.current){let u=Date.now();l({lastUpdatedMs:u,loading:!1,data:c,dataUpdatedMs:u})}}catch(c){if(a.current){let u=Date.now();l(b=>({...b??{},lastUpdatedMs:u,loading:!1,error:c,errorUpdatedMs:u}))}}},[i,n,l,t]);return(0,p.useEffect)(()=>(a.current=!0,i.mount(n),o(!1),()=>{a.current=!1,i.unmount(n)}),[o,i,n]),(0,p.useMemo)(()=>({...y??G,abortCurrentQuery:c=>{let u=i.getAbortControllerByKey(n);return u?.abort(c),!!u},refetch:()=>{o(!0)},setData:(c,u)=>{let b=Date.now();l(F=>({...F??{},data:c,dataUpdatedMs:u??b,lastUpdatedMs:b,loading:!1}))},deleteData:()=>{s(c=>{let u={...c};return delete u[n],u})}}),[o,i,n,y,s,l])}0&&(module.exports={SharedQuery,SharedQueryDefaults,sharedQuery,useSharedQuery});
|
|
1
|
+
"use strict";var q=Object.create;var T=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var Y=(i,e)=>{for(var t in e)T(i,t,{get:e[t],enumerable:!0})},k=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of j(e))!W.call(i,a)&&a!==t&&T(i,a,{get:()=>e[a],enumerable:!(r=_(e,a))||r.enumerable});return i};var B=(i,e,t)=>(t=i!=null?q(J(i)):{},k(e||!i||!i.__esModule?T(t,"default",{value:i,enumerable:!0}):t,i)),I=i=>k(T({},"__esModule",{value:!0}),i);var Z={};Y(Z,{SharedQuery:()=>w,SharedQueryDefaults:()=>h,sharedQuery:()=>H,useSharedQuery:()=>X});module.exports=I(Z);var R=require("@choksheak/ts-utils/timeConstants"),b=B(require("object-sizeof")),u=require("react");var E=require("@choksheak/ts-utils/timeConstants"),z=require("react");var f=require("@choksheak/ts-utils/kvStore");function N(i,e){let{localStorageKey:t,indexedDbKey:r,storeExpiryMs:a,store:n}=i??{};if(n)return n;let s=a||e;return t?new A(t,s):r?new M(r,s):null}var A=class{constructor(e,t){this.keyPrefix=e;this.expiryMs=t}save(e,t){if(typeof window>"u")return;let r=this.keyPrefix+":"+e;window.localStorage.setItem(r,JSON.stringify({value:t,expiresAt:this.expiryMs?Date.now()+this.expiryMs:0}))}load(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e,r=window.localStorage.getItem(t);if(r)try{let a=JSON.parse(r);if(!a||a.expiresAt&&Date.now()>=a.expiresAt){window.localStorage.removeItem(t);return}return a.value}catch{window.localStorage.removeItem(t);return}}delete(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e;window.localStorage.removeItem(t)}clear(){if(!(typeof window>"u"))for(let e of Object.keys(window.localStorage))e.startsWith(this.keyPrefix)&&window.localStorage.removeItem(e)}},M=class{constructor(e,t){this.keyPrefix=e;this.expiryMs=t}async save(e,t){if(typeof window>"u")return;let r=this.keyPrefix+":"+e;await f.kvStore.set(r,JSON.stringify(t),this.expiryMs)}async load(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e,r=await f.kvStore.get(t);if(r!==void 0)try{return JSON.parse(r)}catch{await f.kvStore.delete(t);return}}async delete(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e;await f.kvStore.delete(t)}async clear(){typeof window>"u"||await f.kvStore.forEach(async e=>{e.startsWith(this.keyPrefix)&&await f.kvStore.delete(e)})}};var O={storeExpiryMs:30*E.MS_PER_DAY,lazyLoad:!1},K=class{dataByKey=new Map;subscribersByKey=new Map;get(e){return this.dataByKey.get(e)}set(e,t){let r=this.dataByKey.get(e);r!==t&&(this.dataByKey.set(e,t),this.notify(e,t,r))}delete(e){this.dataByKey.delete(e)}setNoNotify(e,t){this.dataByKey.set(e,t)}subscribe(e,t){let r=this.subscribersByKey.get(e);return r||(r=new Set,this.subscribersByKey.set(e,r)),r.add(t),()=>{r.delete(t),r.size===0&&this.subscribersByKey.delete(e)}}notify(e,t,r){let a=this.subscribersByKey.get(e);if(!a)return;let n=Array.from(a.values());for(let s of n)try{s(t,r)}catch(y){console.error("Error invoking subscriber:",y)}}},g=new K,v="state",G=0,Q=class{constructor(e,t){this.defaultValue=e;this.pubSubKey=String(G++),this.storageAdapter=N(t,O.storeExpiryMs),this.setValueBounded=n=>{this.setValue(n)},g.setNoNotify(this.pubSubKey,e);let r=t?.lazyLoad;(r!==void 0?r:O.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;initDone=!1;subscribe(e){return g.subscribe(this.pubSubKey,()=>e())}getSnapshot(){return g.get(this.pubSubKey)}setValue(e){if(typeof e=="function"){let t=g.get(this.pubSubKey);e=e(t)}g.set(this.pubSubKey,e),this.storageAdapter?.save(v,e)}delete(){g.set(this.pubSubKey,this.defaultValue),this.storageAdapter?.save(v,this.defaultValue)}async initDefaultValueOnce(){if(this.initDone)return;this.initDone=!0;let e=await this.storageAdapter?.load(v);g.set(this.pubSubKey,e!==void 0?e:this.defaultValue)}};function $(i,e){return new Q(i,e)}function P(i){return i.initDefaultValueOnce(),[(0,z.useSyncExternalStore)(t=>i.subscribe(t),()=>i.getSnapshot(),()=>i.getSnapshot()),i.setValueBounded]}function V(i){let e=new Set;return JSON.stringify(i,(t,r)=>{if(typeof r=="function")throw new Error("Functions should not be passed into query parameters");if(!r||typeof r!="object"||Array.isArray(r))return r;if(e.has(r))return"[Circular]";if(e.add(r),r!==i)try{JSON.stringify(r)}catch{return"[Unserializable]"}return Object.keys(r).sort().reduce((a,n)=>(a[n]=r[n],a),{})})}var C=B(require("lodash/isEqual")),x=require("react");function U(i){let[e,t]=(0,x.useState)(i);return(0,x.useMemo)(()=>e===i||(0,C.default)(e,i)?e:(t(i),i),[e,i])}var h={staleMs:0,expiryMs:30*R.MS_PER_DAY,refetchOnStale:!0,persistTo:void 0,maxSize:100,maxBytes:1e5,log:(...i)=>console.log("[sharedQuery]",...i)},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??h.staleMs,this.expiryMs=e?.expiryMs??h.expiryMs,this.refetchOnStale=e?.refetchOnStale??h.refetchOnStale,!e.localStorageKey&&!e.indexedDbKey){let t=e.persistTo??h.persistTo;t==="localStorage"?e.localStorageKey=e.queryName:t==="indexedDb"&&(e.indexedDbKey=e.queryName)}this.maxSize=e?.maxSize??h.maxSize,this.maxBytes=e?.maxBytes??h.maxBytes,this.keepLastNonEmptyData=!!e?.keepLastNonEmptyData,this.log=e?.log??h.log,this.queryState=$({},{...e,storeExpiryMs:e?.storeExpiryMs||this.expiryMs})}getQueryKey(e){return this.queryName+":"+V(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),r=this.queryState.getSnapshot()?.[t];return r?.data&&!this.isExpired(r.dataUpdatedMs??0)?this.isStale(r.dataUpdatedMs??0)?(this.refetchOnStale&&(this.log(`refetchOnStale for ${t}`),this.dedupedFetch(t,...e)),this.log(`Returning stale data for ${t}`),r.data):(this.log(`Return fresh data ${t} from cache without fetching`),r.data):await this.dedupedFetch(t,...e)}dedupedFetch(e,...t){let r=this.inflightQueries.get(e);if(r)return this.log(`Deduplicating inflight fetch for ${e}`),r.promise;let a=this.startFetching(e,t),n=new AbortController;return this.inflightQueries.set(e,{promise:a,abortController:n}),a}async startFetching(e,t){this.log(`Start fetching ${e}`);try{let r=await this.queryFn(...t);return this.log(`Successfully fetched ${e}`),this.setData(e,r,Date.now()),r}catch(r){throw this.log(`Failed to fetch ${e}: ${r}`),r}finally{this.inflightQueries.delete(e)}}getQueryValue(e){return this.queryState.getSnapshot()?.[e]}getData(e){return this.queryState.getSnapshot()?.[e]?.data}setData(e,t,r=Date.now()){let a={lastUpdatedMs:Date.now(),loading:!1,data:t,dataUpdatedMs:r};this.queryState.setValue(n=>({...n,[e]:a})),this.enforceSizeLimit()}async updateFromSource(...e){let t=this.getQueryKey(e);return await this.dedupedFetch(t,...e)}deleteData(e){let r={...this.queryState.getSnapshot()};delete r[e],this.queryState.setValue(r);let a=this.inflightQueries.get(e);a&&(this.inflightQueries.delete(e),a.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 r=(0,b.default)(e),a=this.maxSize?t-this.maxSize:0,n=this.maxBytes?r-this.maxBytes:0;if(a<=0&&n<=0){this.log(`No need to trim data for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${r.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.log(`Need to trim ${this.queryName}: numToCut=${a}, bytesToCut=${n}`);let s={...e},y=!1,p=Object.entries(s).sort((d,S)=>d[1].lastUpdatedMs-S[1].lastUpdatedMs);for(let[d]of p){if(this.mountedKeys.has(d)){this.log(`Cannot clean up ${d} as it is mounted`);continue}let S=(0,b.default)(d)+(0,b.default)(s[d]);if(this.log(`Cleaning up unmounted ${d} (byteSize=${S.toLocaleString()})`),a--,n-=S,delete s[d],y=!0,a<=0&&n<=0)break}if(!y){this.log(`No trimmable entries found for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${r.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=[${r.toLocaleString()} -> ${(0,b.default)(s).toLocaleString()}] (limit=${this.maxBytes.toLocaleString()})`)}},F=new Set;function H(i){if(F.has(i.queryName))throw new Error(`Duplicate shared query "${i.queryName}"`);return F.add(i.queryName),new w(i)}var L={lastUpdatedMs:0,loading:!0};function X(i,e=[]){let t=U(e),[r,a]=P(i.queryState),n=(0,u.useRef)(!0),s=(0,u.useMemo)(()=>i.getQueryKey(t),[i,t]),y=r[s]??L,p=(0,u.useCallback)(c=>{a(o=>{let l={...o};return l[s]=typeof c=="function"?c(l[s]??L):c,l})},[s,a]),[d,S]=(0,u.useState)();(0,u.useEffect)(()=>{i.keepLastNonEmptyData&&y.data!==void 0&&S(y.data)},[i.keepLastNonEmptyData,y.data]);let m=(0,u.useCallback)(async c=>{i.log(`Begin executing shared query ${s}`),p(o=>({...o,lastUpdatedMs:Date.now(),loading:!0}));try{let o=await(c?i.updateFromSource(...t):i.getCachedOrFetch(...t));if(n.current){let l=Date.now();p({lastUpdatedMs:l,loading:!1,data:o,dataUpdatedMs:l})}}catch(o){if(n.current){let l=Date.now();p(D=>({...D,lastUpdatedMs:l,loading:!1,error:o,errorUpdatedMs:l}))}}},[i,s,p,t]);return(0,u.useEffect)(()=>(n.current=!0,i.mount(s),m(!1),()=>{n.current=!1,i.unmount(s)}),[m,i,s]),(0,u.useMemo)(()=>({...y,lastNonEmptyData:d,abortCurrentQuery:c=>{let o=i.getAbortControllerByKey(s);return o?.abort(c),!!o},refetch:()=>{m(!0)},setData:(c,o)=>{let l=Date.now();p(D=>({...D,data:c,dataUpdatedMs:o??l,lastUpdatedMs:l,loading:!1}))},deleteData:()=>{a(c=>{let o={...c};return delete o[s],o})}}),[m,d,i,s,y,a,p])}0&&(module.exports={SharedQuery,SharedQueryDefaults,sharedQuery,useSharedQuery});
|
|
2
2
|
//# sourceMappingURL=sharedQuery.min.cjs.map
|