@choksheak/react-utils 0.2.8 → 0.3.0

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 +119 -130
  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 +117 -129
  10. package/sharedState.cjs +69 -101
  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 +67 -100
  18. package/storage.cjs +90 -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 +65 -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.3.0",
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,71 @@ 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;
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
+ const adapter = new import_localStore.LocalStore(store.key, {
61
+ defaultExpiryMs: expiryMs
62
+ });
63
+ return {
64
+ set: (key, value) => {
65
+ if (typeof window === "undefined") return;
66
+ adapter.set(key, value);
67
+ },
68
+ get: (key) => {
69
+ if (typeof window === "undefined") return void 0;
70
+ return adapter.get(key);
71
+ },
72
+ delete: (key) => {
73
+ if (typeof window === "undefined") return;
74
+ adapter.delete(key);
75
+ },
76
+ clear: () => {
77
+ if (typeof window === "undefined") return;
78
+ adapter.clear();
79
+ }
80
+ };
89
81
  }
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);
82
+ case "indexedDb": {
83
+ const keyPrefix = store.key + ":";
84
+ return {
85
+ set: async (key, value) => {
86
+ if (typeof window === "undefined") return;
87
+ await import_kvStore.kvStore.set(keyPrefix + key, value, expiryMs);
88
+ },
89
+ get: async (key) => {
90
+ if (typeof window === "undefined") return void 0;
91
+ return await import_kvStore.kvStore.get(keyPrefix + key);
92
+ },
93
+ delete: async (key) => {
94
+ if (typeof window === "undefined") return;
95
+ await import_kvStore.kvStore.delete(keyPrefix + key);
96
+ },
97
+ clear: async () => {
98
+ if (typeof window === "undefined") return;
99
+ await import_kvStore.kvStore.clear();
100
+ }
101
+ };
106
102
  }
103
+ default:
104
+ store.persistTo;
105
+ throw new Error(`Unknown store.adapter ${store.persistTo}`);
107
106
  }
108
107
  }
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
- };
108
+ return store;
109
+ }
146
110
 
147
111
  // src/sharedState.ts
148
- var SharedStateDefaults = {
112
+ var SharedStateConfig = {
149
113
  /**
150
114
  * Default duration for storage expiration in milliseconds. Initial value
151
115
  * is 30 days, but can be changed to anything.
@@ -212,14 +176,14 @@ var SharedState = class {
212
176
  this.pubSubKey = String(stateKey++);
213
177
  this.storageAdapter = getStorageAdapter(
214
178
  options,
215
- SharedStateDefaults.storeExpiryMs
179
+ SharedStateConfig.storeExpiryMs
216
180
  );
217
181
  this.setValueBounded = (next) => {
218
182
  this.setValue(next);
219
183
  };
220
184
  pubSubStore.setNoNotify(this.pubSubKey, defaultValue);
221
185
  const lazyLoad = options?.lazyLoad;
222
- const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateDefaults.lazyLoad;
186
+ const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateConfig.lazyLoad;
223
187
  if (!lazy) {
224
188
  void this.initDefaultValueOnce();
225
189
  }
@@ -244,19 +208,19 @@ var SharedState = class {
244
208
  next = next(prev);
245
209
  }
246
210
  pubSubStore.set(this.pubSubKey, next);
247
- void this.storageAdapter?.save(STORAGE_KEY, next);
211
+ void this.storageAdapter?.set(STORAGE_KEY, next);
248
212
  }
249
213
  // Remove the value of the shared state. Actually this just sets the value
250
214
  // back to the given default value.
251
215
  delete() {
252
216
  pubSubStore.set(this.pubSubKey, this.defaultValue);
253
- void this.storageAdapter?.save(STORAGE_KEY, this.defaultValue);
217
+ void this.storageAdapter?.set(STORAGE_KEY, this.defaultValue);
254
218
  }
255
219
  // Initial default handling
256
220
  async initDefaultValueOnce() {
257
221
  if (this.initDone) return;
258
222
  this.initDone = true;
259
- const v = await this.storageAdapter?.load(STORAGE_KEY);
223
+ const v = await this.storageAdapter?.get(STORAGE_KEY);
260
224
  pubSubStore.set(this.pubSubKey, v !== void 0 ? v : this.defaultValue);
261
225
  }
262
226
  };
@@ -274,7 +238,7 @@ function useSharedState(state) {
274
238
  return [value, state.setValueBounded];
275
239
  }
276
240
 
277
- // src/utils/stringify.ts
241
+ // src/stringify.ts
278
242
  function stringifyDeterministicForKeys(params) {
279
243
  const cache = /* @__PURE__ */ new Set();
280
244
  return JSON.stringify(params, (_, value) => {
@@ -305,7 +269,7 @@ function stringifyDeterministicForKeys(params) {
305
269
  });
306
270
  }
307
271
 
308
- // src/utils/useDeepMemo.ts
272
+ // src/useDeepMemo.ts
309
273
  var import_isEqual = __toESM(require("lodash/isEqual"));
310
274
  var import_react2 = require("react");
311
275
  function useDeepMemo(value) {
@@ -320,7 +284,7 @@ function useDeepMemo(value) {
320
284
  }
321
285
 
322
286
  // src/sharedQuery.ts
323
- var SharedQueryDefaults = {
287
+ var SharedQueryConfig = {
324
288
  /** Refetch on every page load. */
325
289
  staleMs: 0,
326
290
  /** Keep in cache for a long time. */
@@ -336,6 +300,9 @@ var SharedQueryDefaults = {
336
300
  /** Default to log to console. */
337
301
  log: (...args) => console.log("[sharedQuery]", ...args)
338
302
  };
303
+ function configureSharedQuery(config) {
304
+ Object.assign(SharedQueryConfig, config);
305
+ }
339
306
  var SharedQuery = class {
340
307
  inflightQueries = /* @__PURE__ */ new Map();
341
308
  queryState;
@@ -357,29 +324,27 @@ var SharedQuery = class {
357
324
  constructor(options) {
358
325
  this.queryName = options.queryName;
359
326
  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;
327
+ this.staleMs = options?.staleMs ?? SharedQueryConfig.staleMs;
328
+ this.expiryMs = options?.expiryMs ?? SharedQueryConfig.expiryMs;
329
+ this.refetchOnStale = options?.refetchOnStale ?? SharedQueryConfig.refetchOnStale;
330
+ if (!options.store) {
331
+ const persistTo = options.persistTo ?? SharedQueryConfig.persistTo;
332
+ if (persistTo) {
333
+ options.store = {
334
+ persistTo,
335
+ key: options.queryName,
336
+ expiryMs: this.expiryMs
337
+ };
369
338
  }
370
339
  }
371
- this.maxSize = options?.maxSize ?? SharedQueryDefaults.maxSize;
372
- this.maxBytes = options?.maxBytes ?? SharedQueryDefaults.maxBytes;
340
+ this.maxSize = options?.maxSize ?? SharedQueryConfig.maxSize;
341
+ this.maxBytes = options?.maxBytes ?? SharedQueryConfig.maxBytes;
373
342
  this.keepLastNonEmptyData = Boolean(options?.keepLastNonEmptyData);
374
- this.log = options?.log ?? SharedQueryDefaults.log;
343
+ this.log = options?.log ?? SharedQueryConfig.log;
375
344
  this.queryState = sharedState(
376
345
  {},
377
346
  // defaultValue
378
- {
379
- ...options,
380
- // Fallback to use expiryMs for the storeExpiryMs.
381
- storeExpiryMs: options?.storeExpiryMs || this.expiryMs
382
- }
347
+ options
383
348
  );
384
349
  }
385
350
  /** Returns a key to identify requests based on the given args. */
@@ -490,7 +455,14 @@ var SharedQuery = class {
490
455
  }
491
456
  /** Get the current stored data for a query key. */
492
457
  getData(queryKey) {
493
- return this.queryState.getSnapshot()?.[queryKey]?.data;
458
+ const stateValue = this.queryState.getSnapshot()?.[queryKey];
459
+ if (stateValue?.lastUpdatedMs) {
460
+ if (this.isExpired(stateValue.lastUpdatedMs)) {
461
+ this.deleteData(queryKey);
462
+ return void 0;
463
+ }
464
+ }
465
+ return stateValue?.data;
494
466
  }
495
467
  /** Set the data directly if the user obtained it from somewhere else. */
496
468
  setData(queryKey, data, dataUpdatedMs = Date.now()) {
@@ -573,25 +545,41 @@ var SharedQuery = class {
573
545
  );
574
546
  const newState = { ...oldState };
575
547
  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) {
548
+ const deleteKeyIfNotMounted = (key, expired) => {
580
549
  if (this.mountedKeys.has(key)) {
581
550
  this.log(`Cannot clean up ${key} as it is mounted`);
582
- continue;
551
+ return;
583
552
  }
584
553
  const byteSize = (0, import_object_sizeof.default)(key) + (0, import_object_sizeof.default)(newState[key]);
585
554
  this.log(
586
- `Cleaning up unmounted ${key} (byteSize=${byteSize.toLocaleString()})`
555
+ `Cleaning up unmounted${expired ? ", expired" : ""} ${key} (byteSize=${byteSize.toLocaleString()})`
587
556
  );
588
557
  numToCut--;
589
558
  bytesToCut -= byteSize;
590
559
  delete newState[key];
591
560
  needUpdate = true;
592
- if (numToCut <= 0 && bytesToCut <= 0) {
561
+ };
562
+ let entriesByTimeAscending = Object.entries(newState).sort(
563
+ (entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
564
+ );
565
+ for (const [key] of entriesByTimeAscending) {
566
+ if (!this.isExpired(newState[key].lastUpdatedMs)) {
593
567
  break;
594
568
  }
569
+ deleteKeyIfNotMounted(key, true);
570
+ }
571
+ if (numToCut > 0 || bytesToCut > 0) {
572
+ if (needUpdate) {
573
+ entriesByTimeAscending = Object.entries(newState).sort(
574
+ (entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
575
+ );
576
+ }
577
+ for (const [key] of entriesByTimeAscending) {
578
+ deleteKeyIfNotMounted(key, false);
579
+ if (numToCut <= 0 && bytesToCut <= 0) {
580
+ break;
581
+ }
582
+ }
595
583
  }
596
584
  if (!needUpdate) {
597
585
  this.log(
@@ -617,7 +605,7 @@ var DEFAULT_QUERY_STATE_ENTRY = {
617
605
  lastUpdatedMs: 0,
618
606
  loading: true
619
607
  };
620
- function useSharedQuery(query, args = []) {
608
+ function useSharedQuery(query, ...args) {
621
609
  const stableArgs = useDeepMemo(args);
622
610
  const [queryState, setQueryState] = useSharedState(query.queryState);
623
611
  const isMounted = (0, import_react3.useRef)(true);
@@ -734,7 +722,8 @@ function useSharedQuery(query, args = []) {
734
722
  // Annotate the CommonJS export names for ESM import in node:
735
723
  0 && (module.exports = {
736
724
  SharedQuery,
737
- SharedQueryDefaults,
725
+ SharedQueryConfig,
726
+ configureSharedQuery,
738
727
  sharedQuery,
739
728
  useSharedQuery
740
729
  });
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,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 C(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 K={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=C(t,K.storeExpiryMs),this.setValueBounded=s=>{this.setValue(s)},f.setNoNotify(this.pubSubKey,e);let a=t?.lazyLoad;(a!==void 0?a:K.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;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)return;this.initDone=!0;let e=await this.storageAdapter?.get(A);f.set(this.pubSubKey,e!==void 0?e:this.defaultValue)}};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});
2
2
  //# sourceMappingURL=sharedQuery.min.cjs.map