@choksheak/react-utils 0.2.8 → 0.2.9

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.
Files changed (55) hide show
  1. package/package.json +50 -2
  2. package/sharedQuery.cjs +97 -131
  3. package/sharedQuery.d.mts +14 -8
  4. package/sharedQuery.d.ts +12 -6
  5. package/sharedQuery.min.cjs +1 -1
  6. package/sharedQuery.min.cjs.map +1 -1
  7. package/sharedQuery.min.mjs +1 -1
  8. package/sharedQuery.min.mjs.map +1 -1
  9. package/sharedQuery.mjs +95 -130
  10. package/sharedState.cjs +47 -102
  11. package/sharedState.d.mts +7 -3
  12. package/sharedState.d.ts +5 -2
  13. package/sharedState.min.cjs +1 -1
  14. package/sharedState.min.cjs.map +1 -1
  15. package/sharedState.min.mjs +1 -1
  16. package/sharedState.min.mjs.map +1 -1
  17. package/sharedState.mjs +45 -101
  18. package/storage.cjs +67 -0
  19. package/storage.d.mts +19 -0
  20. package/storage.d.ts +16 -0
  21. package/storage.min.cjs +2 -0
  22. package/storage.min.cjs.map +1 -0
  23. package/storage.min.mjs +2 -0
  24. package/storage.min.mjs.map +1 -0
  25. package/storage.mjs +42 -0
  26. package/{utils/stringify.cjs → stringify.cjs} +1 -1
  27. package/stringify.min.cjs.map +1 -0
  28. package/stringify.min.mjs.map +1 -0
  29. package/{utils/stringify.mjs → stringify.mjs} +1 -1
  30. package/{utils/useDeepMemo.cjs → useDeepMemo.cjs} +1 -1
  31. package/useDeepMemo.d.mts +7 -0
  32. package/useDeepMemo.d.ts +5 -0
  33. package/useDeepMemo.min.cjs.map +1 -0
  34. package/useDeepMemo.min.mjs.map +1 -0
  35. package/{utils/useDeepMemo.mjs → useDeepMemo.mjs} +1 -1
  36. package/utils/storage.cjs +0 -162
  37. package/utils/storage.d.mts +0 -46
  38. package/utils/storage.d.ts +0 -44
  39. package/utils/storage.min.cjs +0 -2
  40. package/utils/storage.min.cjs.map +0 -1
  41. package/utils/storage.min.mjs +0 -2
  42. package/utils/storage.min.mjs.map +0 -1
  43. package/utils/storage.mjs +0 -134
  44. package/utils/stringify.min.cjs.map +0 -1
  45. package/utils/stringify.min.mjs.map +0 -1
  46. package/utils/useDeepMemo.d.mts +0 -4
  47. package/utils/useDeepMemo.d.ts +0 -2
  48. package/utils/useDeepMemo.min.cjs.map +0 -1
  49. package/utils/useDeepMemo.min.mjs.map +0 -1
  50. /package/{utils/stringify.d.mts → stringify.d.mts} +0 -0
  51. /package/{utils/stringify.d.ts → stringify.d.ts} +0 -0
  52. /package/{utils/stringify.min.cjs → stringify.min.cjs} +0 -0
  53. /package/{utils/stringify.min.mjs → stringify.min.mjs} +0 -0
  54. /package/{utils/useDeepMemo.min.cjs → useDeepMemo.min.cjs} +0 -0
  55. /package/{utils/useDeepMemo.min.mjs → useDeepMemo.min.mjs} +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@choksheak/react-utils",
3
3
  "license": "The Unlicense",
4
- "version": "0.2.8",
4
+ "version": "0.2.9",
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.2.9",
98
+ "@choksheak/ts-utils": ">=0.3.1",
99
99
  "lodash": ">=4.17.21",
100
100
  "object-sizeof": ">=2.6.5",
101
101
  "react": ">=16",
@@ -142,6 +142,45 @@
142
142
  "development": "./sharedState.cjs",
143
143
  "default": "./sharedState.cjs"
144
144
  }
145
+ },
146
+ "./storage": {
147
+ "types": "./storage.d.ts",
148
+ "import": {
149
+ "production": "./storage.min.mjs",
150
+ "development": "./storage.mjs",
151
+ "default": "./storage.mjs"
152
+ },
153
+ "require": {
154
+ "production": "./storage.min.cjs",
155
+ "development": "./storage.cjs",
156
+ "default": "./storage.cjs"
157
+ }
158
+ },
159
+ "./stringify": {
160
+ "types": "./stringify.d.ts",
161
+ "import": {
162
+ "production": "./stringify.min.mjs",
163
+ "development": "./stringify.mjs",
164
+ "default": "./stringify.mjs"
165
+ },
166
+ "require": {
167
+ "production": "./stringify.min.cjs",
168
+ "development": "./stringify.cjs",
169
+ "default": "./stringify.cjs"
170
+ }
171
+ },
172
+ "./useDeepMemo": {
173
+ "types": "./useDeepMemo.d.ts",
174
+ "import": {
175
+ "production": "./useDeepMemo.min.mjs",
176
+ "development": "./useDeepMemo.mjs",
177
+ "default": "./useDeepMemo.mjs"
178
+ },
179
+ "require": {
180
+ "production": "./useDeepMemo.min.cjs",
181
+ "development": "./useDeepMemo.cjs",
182
+ "default": "./useDeepMemo.cjs"
183
+ }
145
184
  }
146
185
  },
147
186
  "typesVersions": {
@@ -154,6 +193,15 @@
154
193
  ],
155
194
  "sharedState": [
156
195
  "./sharedState.d.ts"
196
+ ],
197
+ "storage": [
198
+ "./storage.d.ts"
199
+ ],
200
+ "stringify": [
201
+ "./stringify.d.ts"
202
+ ],
203
+ "useDeepMemo": [
204
+ "./useDeepMemo.d.ts"
157
205
  ]
158
206
  }
159
207
  }
package/sharedQuery.cjs CHANGED
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var sharedQuery_exports = {};
32
32
  __export(sharedQuery_exports, {
33
33
  SharedQuery: () => SharedQuery,
34
- SharedQueryDefaults: () => SharedQueryDefaults,
34
+ SharedQueryConfig: () => SharedQueryConfig,
35
+ configureSharedQuery: () => configureSharedQuery,
35
36
  sharedQuery: () => sharedQuery,
36
37
  useSharedQuery: () => useSharedQuery
37
38
  });
@@ -44,108 +45,48 @@ var import_react3 = require("react");
44
45
  var import_timeConstants = require("@choksheak/ts-utils/timeConstants");
45
46
  var import_react = require("react");
46
47
 
47
- // src/utils/storage.ts
48
+ // src/storage.ts
48
49
  var import_kvStore = require("@choksheak/ts-utils/kvStore");
50
+ var import_localStore = require("@choksheak/ts-utils/localStore");
49
51
  function getStorageAdapter(options, defaultStoreExpiryMs) {
50
- const { localStorageKey, indexedDbKey, storeExpiryMs, store } = options ?? {};
51
- if (store) {
52
- return store;
53
- }
54
- const expiryMs = storeExpiryMs || defaultStoreExpiryMs;
55
- if (localStorageKey) {
56
- return new LocalStorageAdapter(localStorageKey, expiryMs);
57
- }
58
- if (indexedDbKey) {
59
- return new IndexedDbAdapter(indexedDbKey, expiryMs);
60
- }
61
- return null;
62
- }
63
- var LocalStorageAdapter = class {
64
- constructor(keyPrefix, expiryMs) {
65
- this.keyPrefix = keyPrefix;
66
- this.expiryMs = expiryMs;
67
- }
68
- save(key, value) {
69
- if (typeof window === "undefined") return;
70
- const fullKey = this.keyPrefix + ":" + key;
71
- window.localStorage.setItem(
72
- fullKey,
73
- JSON.stringify({
74
- value,
75
- expiresAt: this.expiryMs ? Date.now() + this.expiryMs : 0
76
- })
77
- );
78
- }
79
- load(key) {
80
- if (typeof window === "undefined") return void 0;
81
- const fullKey = this.keyPrefix + ":" + key;
82
- const raw = window.localStorage.getItem(fullKey);
83
- if (!raw) return void 0;
84
- try {
85
- const box = JSON.parse(raw);
86
- if (!box || box.expiresAt && Date.now() >= box.expiresAt) {
87
- window.localStorage.removeItem(fullKey);
88
- return void 0;
89
- }
90
- return box.value;
91
- } catch {
92
- window.localStorage.removeItem(fullKey);
93
- return void 0;
94
- }
95
- }
96
- delete(key) {
97
- if (typeof window === "undefined") return;
98
- const fullKey = this.keyPrefix + ":" + key;
99
- window.localStorage.removeItem(fullKey);
100
- }
101
- clear() {
102
- if (typeof window === "undefined") return;
103
- for (const key of Object.keys(window.localStorage)) {
104
- if (key.startsWith(this.keyPrefix)) {
105
- window.localStorage.removeItem(key);
52
+ const { store } = options ?? {};
53
+ if (!store) {
54
+ return null;
55
+ }
56
+ if ("persistTo" in store && typeof store.persistTo === "string" && "key" in store && typeof store.key === "string") {
57
+ const expiryMs = store.expiryMs || defaultStoreExpiryMs;
58
+ switch (store.persistTo) {
59
+ case "localStorage":
60
+ return new import_localStore.LocalStore(store.key, {
61
+ defaultExpiryMs: expiryMs
62
+ }).asStorageAdapter();
63
+ case "indexedDb": {
64
+ const keyPrefix = store.key + ":";
65
+ return {
66
+ set: async (key, value) => {
67
+ await import_kvStore.kvStore.set(keyPrefix + key, value, expiryMs);
68
+ },
69
+ get: async (key) => {
70
+ return await import_kvStore.kvStore.get(keyPrefix + key);
71
+ },
72
+ delete: async (key) => {
73
+ await import_kvStore.kvStore.delete(keyPrefix + key);
74
+ },
75
+ clear: async () => {
76
+ await import_kvStore.kvStore.clear();
77
+ }
78
+ };
106
79
  }
80
+ default:
81
+ store.persistTo;
82
+ throw new Error(`Unknown store.adapter ${store.persistTo}`);
107
83
  }
108
84
  }
109
- };
110
- var IndexedDbAdapter = class {
111
- constructor(keyPrefix, expiryMs) {
112
- this.keyPrefix = keyPrefix;
113
- this.expiryMs = expiryMs;
114
- }
115
- async save(key, value) {
116
- if (typeof window === "undefined") return;
117
- const fullKey = this.keyPrefix + ":" + key;
118
- await import_kvStore.kvStore.set(fullKey, JSON.stringify(value), this.expiryMs);
119
- }
120
- async load(key) {
121
- if (typeof window === "undefined") return void 0;
122
- const fullKey = this.keyPrefix + ":" + key;
123
- const raw = await import_kvStore.kvStore.get(fullKey);
124
- if (raw === void 0) return void 0;
125
- try {
126
- return JSON.parse(raw);
127
- } catch {
128
- await import_kvStore.kvStore.delete(fullKey);
129
- return void 0;
130
- }
131
- }
132
- async delete(key) {
133
- if (typeof window === "undefined") return;
134
- const fullKey = this.keyPrefix + ":" + key;
135
- await import_kvStore.kvStore.delete(fullKey);
136
- }
137
- async clear() {
138
- if (typeof window === "undefined") return;
139
- await import_kvStore.kvStore.forEach(async (k) => {
140
- if (k.startsWith(this.keyPrefix)) {
141
- await import_kvStore.kvStore.delete(k);
142
- }
143
- });
144
- }
145
- };
85
+ return store;
86
+ }
146
87
 
147
88
  // src/sharedState.ts
148
- var SharedStateDefaults = {
89
+ var SharedStateConfig = {
149
90
  /**
150
91
  * Default duration for storage expiration in milliseconds. Initial value
151
92
  * is 30 days, but can be changed to anything.
@@ -212,14 +153,14 @@ var SharedState = class {
212
153
  this.pubSubKey = String(stateKey++);
213
154
  this.storageAdapter = getStorageAdapter(
214
155
  options,
215
- SharedStateDefaults.storeExpiryMs
156
+ SharedStateConfig.storeExpiryMs
216
157
  );
217
158
  this.setValueBounded = (next) => {
218
159
  this.setValue(next);
219
160
  };
220
161
  pubSubStore.setNoNotify(this.pubSubKey, defaultValue);
221
162
  const lazyLoad = options?.lazyLoad;
222
- const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateDefaults.lazyLoad;
163
+ const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateConfig.lazyLoad;
223
164
  if (!lazy) {
224
165
  void this.initDefaultValueOnce();
225
166
  }
@@ -244,19 +185,19 @@ var SharedState = class {
244
185
  next = next(prev);
245
186
  }
246
187
  pubSubStore.set(this.pubSubKey, next);
247
- void this.storageAdapter?.save(STORAGE_KEY, next);
188
+ void this.storageAdapter?.set(STORAGE_KEY, next);
248
189
  }
249
190
  // Remove the value of the shared state. Actually this just sets the value
250
191
  // back to the given default value.
251
192
  delete() {
252
193
  pubSubStore.set(this.pubSubKey, this.defaultValue);
253
- void this.storageAdapter?.save(STORAGE_KEY, this.defaultValue);
194
+ void this.storageAdapter?.set(STORAGE_KEY, this.defaultValue);
254
195
  }
255
196
  // Initial default handling
256
197
  async initDefaultValueOnce() {
257
198
  if (this.initDone) return;
258
199
  this.initDone = true;
259
- const v = await this.storageAdapter?.load(STORAGE_KEY);
200
+ const v = await this.storageAdapter?.get(STORAGE_KEY);
260
201
  pubSubStore.set(this.pubSubKey, v !== void 0 ? v : this.defaultValue);
261
202
  }
262
203
  };
@@ -274,7 +215,7 @@ function useSharedState(state) {
274
215
  return [value, state.setValueBounded];
275
216
  }
276
217
 
277
- // src/utils/stringify.ts
218
+ // src/stringify.ts
278
219
  function stringifyDeterministicForKeys(params) {
279
220
  const cache = /* @__PURE__ */ new Set();
280
221
  return JSON.stringify(params, (_, value) => {
@@ -305,7 +246,7 @@ function stringifyDeterministicForKeys(params) {
305
246
  });
306
247
  }
307
248
 
308
- // src/utils/useDeepMemo.ts
249
+ // src/useDeepMemo.ts
309
250
  var import_isEqual = __toESM(require("lodash/isEqual"));
310
251
  var import_react2 = require("react");
311
252
  function useDeepMemo(value) {
@@ -320,7 +261,7 @@ function useDeepMemo(value) {
320
261
  }
321
262
 
322
263
  // src/sharedQuery.ts
323
- var SharedQueryDefaults = {
264
+ var SharedQueryConfig = {
324
265
  /** Refetch on every page load. */
325
266
  staleMs: 0,
326
267
  /** Keep in cache for a long time. */
@@ -336,6 +277,9 @@ var SharedQueryDefaults = {
336
277
  /** Default to log to console. */
337
278
  log: (...args) => console.log("[sharedQuery]", ...args)
338
279
  };
280
+ function configureSharedQuery(config) {
281
+ Object.assign(SharedQueryConfig, config);
282
+ }
339
283
  var SharedQuery = class {
340
284
  inflightQueries = /* @__PURE__ */ new Map();
341
285
  queryState;
@@ -357,29 +301,27 @@ var SharedQuery = class {
357
301
  constructor(options) {
358
302
  this.queryName = options.queryName;
359
303
  this.queryFn = options.queryFn;
360
- this.staleMs = options?.staleMs ?? SharedQueryDefaults.staleMs;
361
- this.expiryMs = options?.expiryMs ?? SharedQueryDefaults.expiryMs;
362
- this.refetchOnStale = options?.refetchOnStale ?? SharedQueryDefaults.refetchOnStale;
363
- if (!options.localStorageKey && !options.indexedDbKey) {
364
- const persistTo = options.persistTo ?? SharedQueryDefaults.persistTo;
365
- if (persistTo === "localStorage") {
366
- options.localStorageKey = options.queryName;
367
- } else if (persistTo === "indexedDb") {
368
- options.indexedDbKey = options.queryName;
304
+ this.staleMs = options?.staleMs ?? SharedQueryConfig.staleMs;
305
+ this.expiryMs = options?.expiryMs ?? SharedQueryConfig.expiryMs;
306
+ this.refetchOnStale = options?.refetchOnStale ?? SharedQueryConfig.refetchOnStale;
307
+ if (!options.store) {
308
+ const persistTo = options.persistTo ?? SharedQueryConfig.persistTo;
309
+ if (persistTo) {
310
+ options.store = {
311
+ persistTo,
312
+ key: options.queryName,
313
+ expiryMs: this.expiryMs
314
+ };
369
315
  }
370
316
  }
371
- this.maxSize = options?.maxSize ?? SharedQueryDefaults.maxSize;
372
- this.maxBytes = options?.maxBytes ?? SharedQueryDefaults.maxBytes;
317
+ this.maxSize = options?.maxSize ?? SharedQueryConfig.maxSize;
318
+ this.maxBytes = options?.maxBytes ?? SharedQueryConfig.maxBytes;
373
319
  this.keepLastNonEmptyData = Boolean(options?.keepLastNonEmptyData);
374
- this.log = options?.log ?? SharedQueryDefaults.log;
320
+ this.log = options?.log ?? SharedQueryConfig.log;
375
321
  this.queryState = sharedState(
376
322
  {},
377
323
  // defaultValue
378
- {
379
- ...options,
380
- // Fallback to use expiryMs for the storeExpiryMs.
381
- storeExpiryMs: options?.storeExpiryMs || this.expiryMs
382
- }
324
+ options
383
325
  );
384
326
  }
385
327
  /** Returns a key to identify requests based on the given args. */
@@ -490,7 +432,14 @@ var SharedQuery = class {
490
432
  }
491
433
  /** Get the current stored data for a query key. */
492
434
  getData(queryKey) {
493
- return this.queryState.getSnapshot()?.[queryKey]?.data;
435
+ const stateValue = this.queryState.getSnapshot()?.[queryKey];
436
+ if (stateValue?.lastUpdatedMs) {
437
+ if (this.isExpired(stateValue.lastUpdatedMs)) {
438
+ this.deleteData(queryKey);
439
+ return void 0;
440
+ }
441
+ }
442
+ return stateValue?.data;
494
443
  }
495
444
  /** Set the data directly if the user obtained it from somewhere else. */
496
445
  setData(queryKey, data, dataUpdatedMs = Date.now()) {
@@ -573,25 +522,41 @@ var SharedQuery = class {
573
522
  );
574
523
  const newState = { ...oldState };
575
524
  let needUpdate = false;
576
- const entriesByTimeAscending = Object.entries(newState).sort(
577
- (entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
578
- );
579
- for (const [key] of entriesByTimeAscending) {
525
+ const deleteKeyIfNotMounted = (key, expired) => {
580
526
  if (this.mountedKeys.has(key)) {
581
527
  this.log(`Cannot clean up ${key} as it is mounted`);
582
- continue;
528
+ return;
583
529
  }
584
530
  const byteSize = (0, import_object_sizeof.default)(key) + (0, import_object_sizeof.default)(newState[key]);
585
531
  this.log(
586
- `Cleaning up unmounted ${key} (byteSize=${byteSize.toLocaleString()})`
532
+ `Cleaning up unmounted${expired ? ", expired" : ""} ${key} (byteSize=${byteSize.toLocaleString()})`
587
533
  );
588
534
  numToCut--;
589
535
  bytesToCut -= byteSize;
590
536
  delete newState[key];
591
537
  needUpdate = true;
592
- if (numToCut <= 0 && bytesToCut <= 0) {
538
+ };
539
+ let entriesByTimeAscending = Object.entries(newState).sort(
540
+ (entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
541
+ );
542
+ for (const [key] of entriesByTimeAscending) {
543
+ if (!this.isExpired(newState[key].lastUpdatedMs)) {
593
544
  break;
594
545
  }
546
+ deleteKeyIfNotMounted(key, true);
547
+ }
548
+ if (numToCut > 0 || bytesToCut > 0) {
549
+ if (needUpdate) {
550
+ entriesByTimeAscending = Object.entries(newState).sort(
551
+ (entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
552
+ );
553
+ }
554
+ for (const [key] of entriesByTimeAscending) {
555
+ deleteKeyIfNotMounted(key, false);
556
+ if (numToCut <= 0 && bytesToCut <= 0) {
557
+ break;
558
+ }
559
+ }
595
560
  }
596
561
  if (!needUpdate) {
597
562
  this.log(
@@ -617,7 +582,7 @@ var DEFAULT_QUERY_STATE_ENTRY = {
617
582
  lastUpdatedMs: 0,
618
583
  loading: true
619
584
  };
620
- function useSharedQuery(query, args = []) {
585
+ function useSharedQuery(query, ...args) {
621
586
  const stableArgs = useDeepMemo(args);
622
587
  const [queryState, setQueryState] = useSharedState(query.queryState);
623
588
  const isMounted = (0, import_react3.useRef)(true);
@@ -734,7 +699,8 @@ function useSharedQuery(query, args = []) {
734
699
  // Annotate the CommonJS export names for ESM import in node:
735
700
  0 && (module.exports = {
736
701
  SharedQuery,
737
- SharedQueryDefaults,
702
+ SharedQueryConfig,
703
+ configureSharedQuery,
738
704
  sharedQuery,
739
705
  useSharedQuery
740
706
  });
package/sharedQuery.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { SharedStateOptions, SharedState } from './sharedState.mjs';
2
+ import { PersistTo } from './storage.mjs';
2
3
  import 'react';
3
- import './utils/storage.mjs';
4
+ import '@choksheak/ts-utils/storageAdapter';
4
5
 
5
6
  /**
6
7
  * @packageDocumentation
@@ -128,8 +129,6 @@ type UseQueryResult<TData> = QueryStateValue<TData> & Readonly<{
128
129
  setData: (data: TData, dataUpdatedMs?: number) => void;
129
130
  deleteData: () => void;
130
131
  }>;
131
- /** List of all available persistence options. */
132
- type PersistTo = "localStorage" | "indexedDb";
133
132
  /**
134
133
  * The full query state is a record of all the fetch states mapped by
135
134
  * query key to the state entry.
@@ -188,7 +187,7 @@ type SharedQueryOptions<TArgs extends unknown[], TData> = {
188
187
  log?: (...args: unknown[]) => void;
189
188
  } & SharedStateOptions<SharedQueryState<TData>>;
190
189
  /** Users can override these values globally. */
191
- declare const SharedQueryDefaults: {
190
+ declare const SharedQueryConfig: {
192
191
  /** Refetch on every page load. */
193
192
  staleMs: number;
194
193
  /** Keep in cache for a long time. */
@@ -204,6 +203,9 @@ declare const SharedQueryDefaults: {
204
203
  /** Default to log to console. */
205
204
  log: (...args: unknown[]) => void;
206
205
  };
206
+ type SharedQueryConfig = typeof SharedQueryConfig;
207
+ /** Convenience function to update global defaults. */
208
+ declare function configureSharedQuery(config: Partial<SharedQueryConfig>): void;
207
209
  /** Please use sharedQuery() instead. */
208
210
  declare class SharedQuery<TArgs extends unknown[], TData> {
209
211
  private readonly inflightQueries;
@@ -311,13 +313,17 @@ declare class SharedQuery<TArgs extends unknown[], TData> {
311
313
  */
312
314
  declare function sharedQuery<TArgs extends unknown[], TData>(options: SharedQueryOptions<TArgs, TData>): SharedQuery<TArgs, TData>;
313
315
  /**
314
- * React hook to make use of a shared query inside any React component.
316
+ * React hook to make use of a shared query inside any React component. The
317
+ * args do not need to be specified if no args are needed, i.e. this would work
318
+ * ```
319
+ * const allUsers = useSharedQuery(listUsersQuery);
320
+ * ```
315
321
  *
316
322
  * Example:
317
323
  * ```
318
- * const users = useSharedQuery(usersQuery, ["123"]);
324
+ * const users = useSharedQuery(usersQuery, "userid123");
319
325
  * ```
320
326
  */
321
- declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, args?: TArgs): UseQueryResult<TData>;
327
+ declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, ...args: TArgs): UseQueryResult<TData>;
322
328
 
323
- export { type PersistTo, type QueryFn, type QueryStateValue, SharedQuery, SharedQueryDefaults, type SharedQueryOptions, type SharedQueryState, type UseQueryResult, sharedQuery, useSharedQuery };
329
+ export { type QueryFn, type QueryStateValue, SharedQuery, SharedQueryConfig, type SharedQueryOptions, type SharedQueryState, type UseQueryResult, configureSharedQuery, sharedQuery, useSharedQuery };
package/sharedQuery.d.ts CHANGED
@@ -82,6 +82,7 @@
82
82
  * ```
83
83
  */
84
84
  import { SharedState, SharedStateOptions } from "./sharedState";
85
+ import { PersistTo } from "./storage";
85
86
  /** Type of the queryFn used to fetch the data from source. */
86
87
  export type QueryFn<TArgs extends unknown[], TData> = (...args: TArgs) => Promise<TData> | TData;
87
88
  /** An entry for a persisted state corresponding to one query key. */
@@ -124,8 +125,6 @@ export type UseQueryResult<TData> = QueryStateValue<TData> & Readonly<{
124
125
  setData: (data: TData, dataUpdatedMs?: number) => void;
125
126
  deleteData: () => void;
126
127
  }>;
127
- /** List of all available persistence options. */
128
- export type PersistTo = "localStorage" | "indexedDb";
129
128
  /**
130
129
  * The full query state is a record of all the fetch states mapped by
131
130
  * query key to the state entry.
@@ -184,7 +183,7 @@ export type SharedQueryOptions<TArgs extends unknown[], TData> = {
184
183
  log?: (...args: unknown[]) => void;
185
184
  } & SharedStateOptions<SharedQueryState<TData>>;
186
185
  /** Users can override these values globally. */
187
- export declare const SharedQueryDefaults: {
186
+ export declare const SharedQueryConfig: {
188
187
  /** Refetch on every page load. */
189
188
  staleMs: number;
190
189
  /** Keep in cache for a long time. */
@@ -200,6 +199,9 @@ export declare const SharedQueryDefaults: {
200
199
  /** Default to log to console. */
201
200
  log: (...args: unknown[]) => void;
202
201
  };
202
+ export type SharedQueryConfig = typeof SharedQueryConfig;
203
+ /** Convenience function to update global defaults. */
204
+ export declare function configureSharedQuery(config: Partial<SharedQueryConfig>): void;
203
205
  /** Please use sharedQuery() instead. */
204
206
  export declare class SharedQuery<TArgs extends unknown[], TData> {
205
207
  private readonly inflightQueries;
@@ -307,11 +309,15 @@ export declare class SharedQuery<TArgs extends unknown[], TData> {
307
309
  */
308
310
  export declare function sharedQuery<TArgs extends unknown[], TData>(options: SharedQueryOptions<TArgs, TData>): SharedQuery<TArgs, TData>;
309
311
  /**
310
- * React hook to make use of a shared query inside any React component.
312
+ * React hook to make use of a shared query inside any React component. The
313
+ * args do not need to be specified if no args are needed, i.e. this would work
314
+ * ```
315
+ * const allUsers = useSharedQuery(listUsersQuery);
316
+ * ```
311
317
  *
312
318
  * Example:
313
319
  * ```
314
- * const users = useSharedQuery(usersQuery, ["123"]);
320
+ * const users = useSharedQuery(usersQuery, "userid123");
315
321
  * ```
316
322
  */
317
- export declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, args?: TArgs): UseQueryResult<TData>;
323
+ export declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, ...args: TArgs): UseQueryResult<TData>;
@@ -1,2 +1,2 @@
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});
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,t)=>{for(var e in t)T(r,e,{get:t[e],enumerable:!0})},B=(r,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of _(t))!Y.call(r,s)&&s!==e&&T(r,s,{get:()=>t[s],enumerable:!(a=q(t,s))||a.enumerable});return r};var O=(r,t,e)=>(e=r!=null?R(j(r)):{},B(t||!r||!r.__esModule?T(e,"default",{value:r,enumerable:!0}):e,r)),G=r=>B(T({},"__esModule",{value:!0}),r);var Z={};J(Z,{SharedQuery:()=>x,SharedQueryConfig:()=>g,configureSharedQuery:()=>H,sharedQuery:()=>W,useSharedQuery:()=>X});module.exports=G(Z);var P=require("@choksheak/ts-utils/timeConstants"),m=O(require("object-sizeof")),d=require("react");var $=require("@choksheak/ts-utils/timeConstants"),z=require("react");var b=require("@choksheak/ts-utils/kvStore"),v=require("@choksheak/ts-utils/localStore");function C(r,t){let{store:e}=r??{};if(!e)return null;if("persistTo"in e&&typeof e.persistTo=="string"&&"key"in e&&typeof e.key=="string"){let a=e.expiryMs||t;switch(e.persistTo){case"localStorage":return new v.LocalStore(e.key,{defaultExpiryMs:a}).asStorageAdapter();case"indexedDb":{let s=e.key+":";return{set:async(i,n)=>{await b.kvStore.set(s+i,n,a)},get:async i=>await b.kvStore.get(s+i),delete:async i=>{await b.kvStore.delete(s+i)},clear:async()=>{await b.kvStore.clear()}}}default:throw e.persistTo,new Error(`Unknown store.adapter ${e.persistTo}`)}}return e}var K={storeExpiryMs:30*$.MS_PER_DAY,lazyLoad:!1};var M=class{dataByKey=new Map;subscribersByKey=new Map;get(t){return this.dataByKey.get(t)}set(t,e){let a=this.dataByKey.get(t);a!==e&&(this.dataByKey.set(t,e),this.notify(t,e,a))}delete(t){this.dataByKey.delete(t)}setNoNotify(t,e){this.dataByKey.set(t,e)}subscribe(t,e){let a=this.subscribersByKey.get(t);return a||(a=new Set,this.subscribersByKey.set(t,a)),a.add(e),()=>{a.delete(e),a.size===0&&this.subscribersByKey.delete(t)}}notify(t,e,a){let s=this.subscribersByKey.get(t);if(!s)return;let i=Array.from(s.values());for(let n of i)try{n(e,a)}catch(p){console.error("Error invoking subscriber:",p)}}},S=new M,w="state",I=0,Q=class{constructor(t,e){this.defaultValue=t;this.pubSubKey=String(I++),this.storageAdapter=C(e,K.storeExpiryMs),this.setValueBounded=i=>{this.setValue(i)},S.setNoNotify(this.pubSubKey,t);let a=e?.lazyLoad;(a!==void 0?a:K.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;initDone=!1;subscribe(t){return S.subscribe(this.pubSubKey,()=>t())}getSnapshot(){return S.get(this.pubSubKey)}setValue(t){if(typeof t=="function"){let e=S.get(this.pubSubKey);t=t(e)}S.set(this.pubSubKey,t),this.storageAdapter?.set(w,t)}delete(){S.set(this.pubSubKey,this.defaultValue),this.storageAdapter?.set(w,this.defaultValue)}async initDefaultValueOnce(){if(this.initDone)return;this.initDone=!0;let t=await this.storageAdapter?.get(w);S.set(this.pubSubKey,t!==void 0?t:this.defaultValue)}};function N(r,t){return new Q(r,t)}function k(r){return r.initDefaultValueOnce(),[(0,z.useSyncExternalStore)(e=>r.subscribe(e),()=>r.getSnapshot(),()=>r.getSnapshot()),r.setValueBounded]}function E(r){let t=new Set;return JSON.stringify(r,(e,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(t.has(a))return"[Circular]";if(t.add(a),a!==r)try{JSON.stringify(a)}catch{return"[Unserializable]"}return Object.keys(a).sort().reduce((s,i)=>(s[i]=a[i],s),{})})}var U=O(require("lodash/isEqual")),D=require("react");function V(r){let[t,e]=(0,D.useState)(r);return(0,D.useMemo)(()=>t===r||(0,U.default)(t,r)?t:(e(r),r),[t,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 x=class{inflightQueries=new Map;queryState;mountedKeys=new Map;queryName;queryFn;expiryMs;staleMs;refetchOnStale;maxSize;maxBytes;keepLastNonEmptyData;log;constructor(t){if(this.queryName=t.queryName,this.queryFn=t.queryFn,this.staleMs=t?.staleMs??g.staleMs,this.expiryMs=t?.expiryMs??g.expiryMs,this.refetchOnStale=t?.refetchOnStale??g.refetchOnStale,!t.store){let e=t.persistTo??g.persistTo;e&&(t.store={persistTo:e,key:t.queryName,expiryMs:this.expiryMs})}this.maxSize=t?.maxSize??g.maxSize,this.maxBytes=t?.maxBytes??g.maxBytes,this.keepLastNonEmptyData=!!t?.keepLastNonEmptyData,this.log=t?.log??g.log,this.queryState=N({},t)}getQueryKey(t){return this.queryName+":"+E(t)}getAbortController(t){let e=this.getQueryKey(t);return this.getAbortControllerByKey(e)}getAbortControllerByKey(t){return this.inflightQueries.get(t)?.abortController??null}getAbortSignal(t){let e=this.getQueryKey(t);return this.getAbortSignalByKey(e)}getAbortSignalByKey(t){return this.getAbortControllerByKey(t)?.signal??null}isStale(t){return this.staleMs===0?!0:Date.now()-t>this.staleMs}isExpired(t){return this.expiryMs===0?!1:Date.now()-t>this.expiryMs}async getCachedOrFetch(...t){let e=this.getQueryKey(t),a=this.queryState.getSnapshot()?.[e];return a?.data&&!this.isExpired(a.dataUpdatedMs??0)?this.isStale(a.dataUpdatedMs??0)?(this.refetchOnStale&&(this.log(`refetchOnStale for ${e}`),this.dedupedFetch(e,...t)),this.log(`Returning stale data for ${e}`),a.data):(this.log(`Return fresh data ${e} from cache without fetching`),a.data):await this.dedupedFetch(e,...t)}dedupedFetch(t,...e){let a=this.inflightQueries.get(t);if(a)return this.log(`Deduplicating inflight fetch for ${t}`),a.promise;let s=this.startFetching(t,e),i=new AbortController;return this.inflightQueries.set(t,{promise:s,abortController:i}),s}async startFetching(t,e){this.log(`Start fetching ${t}`);try{let a=await this.queryFn(...e);return this.log(`Successfully fetched ${t}`),this.setData(t,a,Date.now()),a}catch(a){throw this.log(`Failed to fetch ${t}: ${a}`),a}finally{this.inflightQueries.delete(t)}}getQueryValue(t){return this.queryState.getSnapshot()?.[t]}getData(t){let e=this.queryState.getSnapshot()?.[t];if(e?.lastUpdatedMs&&this.isExpired(e.lastUpdatedMs)){this.deleteData(t);return}return e?.data}setData(t,e,a=Date.now()){let s={lastUpdatedMs:Date.now(),loading:!1,data:e,dataUpdatedMs:a};this.queryState.setValue(i=>({...i,[t]:s})),this.enforceSizeLimit()}async updateFromSource(...t){let e=this.getQueryKey(t);return await this.dedupedFetch(e,...t)}deleteData(t){let a={...this.queryState.getSnapshot()};delete a[t],this.queryState.setValue(a);let s=this.inflightQueries.get(t);s&&(this.inflightQueries.delete(t),s.abortController.abort("Deleted"))}clear(){this.queryState.delete();for(let{abortController:t}of Array.from(this.inflightQueries.values()))t.abort("Cleared");this.inflightQueries.clear()}mount(t){this.mountedKeys.set(t,(this.mountedKeys.get(t)??0)+1)}unmount(t){let e=this.mountedKeys.get(t);e===1?this.mountedKeys.delete(t):this.mountedKeys.set(t,(e??0)-1)}enforceSizeLimit(){if(!this.maxSize&&!this.maxBytes)return;let t=this.queryState.getSnapshot(),e=Object.keys(t).length;if(e<=1)return;let a=(0,m.default)(t),s=this.maxSize?e-this.maxSize:0,i=this.maxBytes?a-this.maxBytes:0;if(s<=0&&i<=0){this.log(`No need to trim data for ${this.queryName}: size=${e} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.log(`Need to trim ${this.queryName}: numToCut=${s}, bytesToCut=${i}`);let n={...t},p=!1,h=(o,y)=>{if(this.mountedKeys.has(o)){this.log(`Cannot clean up ${o} as it is mounted`);return}let l=(0,m.default)(o)+(0,m.default)(n[o]);this.log(`Cleaning up unmounted${y?", expired":""} ${o} (byteSize=${l.toLocaleString()})`),s--,i-=l,delete n[o],p=!0},f=Object.entries(n).sort((o,y)=>o[1].lastUpdatedMs-y[1].lastUpdatedMs);for(let[o]of f){if(!this.isExpired(n[o].lastUpdatedMs))break;h(o,!0)}if(s>0||i>0){p&&(f=Object.entries(n).sort((o,y)=>o[1].lastUpdatedMs-y[1].lastUpdatedMs));for(let[o]of f)if(h(o,!1),s<=0&&i<=0)break}if(!p){this.log(`No trimmable entries found for ${this.queryName}: size=${e} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.queryState.setValue(n),this.log(`Trimmed data for ${this.queryName}: size=[${e} -> ${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 x(r)}var F={lastUpdatedMs:0,loading:!0};function X(r,...t){let e=V(t),[a,s]=k(r.queryState),i=(0,d.useRef)(!0),n=(0,d.useMemo)(()=>r.getQueryKey(e),[r,e]),p=a[n]??F,h=(0,d.useCallback)(l=>{s(u=>{let c={...u};return c[n]=typeof l=="function"?l(c[n]??F):l,c})},[n,s]),[f,o]=(0,d.useState)();(0,d.useEffect)(()=>{r.keepLastNonEmptyData&&p.data!==void 0&&o(p.data)},[r.keepLastNonEmptyData,p.data]);let y=(0,d.useCallback)(async l=>{r.log(`Begin executing shared query ${n}`),h(u=>({...u,lastUpdatedMs:Date.now(),loading:!0}));try{let u=await(l?r.updateFromSource(...e):r.getCachedOrFetch(...e));if(i.current){let c=Date.now();h({lastUpdatedMs:c,loading:!1,data:u,dataUpdatedMs:c})}}catch(u){if(i.current){let c=Date.now();h(A=>({...A,lastUpdatedMs:c,loading:!1,error:u,errorUpdatedMs:c}))}}},[r,n,h,e]);return(0,d.useEffect)(()=>(i.current=!0,r.mount(n),y(!1),()=>{i.current=!1,r.unmount(n)}),[y,r,n]),(0,d.useMemo)(()=>({...p,lastNonEmptyData:f,abortCurrentQuery:l=>{let u=r.getAbortControllerByKey(n);return u?.abort(l),!!u},refetch:()=>{y(!0)},setData:(l,u)=>{let c=Date.now();h(A=>({...A,data:l,dataUpdatedMs:u??c,lastUpdatedMs:c,loading:!1}))},deleteData:()=>{s(l=>{let u={...l};return delete u[n],u})}}),[y,f,r,n,p,s,h])}0&&(module.exports={SharedQuery,SharedQueryConfig,configureSharedQuery,sharedQuery,useSharedQuery});
2
2
  //# sourceMappingURL=sharedQuery.min.cjs.map