@choksheak/react-utils 0.2.7 → 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 +146 -191
  3. package/sharedQuery.d.mts +72 -12
  4. package/sharedQuery.d.ts +69 -10
  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 +151 -191
  10. package/sharedState.cjs +50 -103
  11. package/sharedState.d.mts +12 -7
  12. package/sharedState.d.ts +10 -6
  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 +48 -102
  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.7",
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,12 +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
- this.setValueBounded = (arg) => this.setValue(arg);
158
+ this.setValueBounded = (next) => {
159
+ this.setValue(next);
160
+ };
218
161
  pubSubStore.setNoNotify(this.pubSubKey, defaultValue);
219
162
  const lazyLoad = options?.lazyLoad;
220
- const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateDefaults.lazyLoad;
163
+ const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateConfig.lazyLoad;
221
164
  if (!lazy) {
222
165
  void this.initDefaultValueOnce();
223
166
  }
@@ -242,19 +185,19 @@ var SharedState = class {
242
185
  next = next(prev);
243
186
  }
244
187
  pubSubStore.set(this.pubSubKey, next);
245
- void this.storageAdapter?.save(STORAGE_KEY, next);
188
+ void this.storageAdapter?.set(STORAGE_KEY, next);
246
189
  }
247
190
  // Remove the value of the shared state. Actually this just sets the value
248
191
  // back to the given default value.
249
192
  delete() {
250
193
  pubSubStore.set(this.pubSubKey, this.defaultValue);
251
- void this.storageAdapter?.save(STORAGE_KEY, this.defaultValue);
194
+ void this.storageAdapter?.set(STORAGE_KEY, this.defaultValue);
252
195
  }
253
196
  // Initial default handling
254
197
  async initDefaultValueOnce() {
255
198
  if (this.initDone) return;
256
199
  this.initDone = true;
257
- const v = await this.storageAdapter?.load(STORAGE_KEY);
200
+ const v = await this.storageAdapter?.get(STORAGE_KEY);
258
201
  pubSubStore.set(this.pubSubKey, v !== void 0 ? v : this.defaultValue);
259
202
  }
260
203
  };
@@ -272,7 +215,7 @@ function useSharedState(state) {
272
215
  return [value, state.setValueBounded];
273
216
  }
274
217
 
275
- // src/utils/stringify.ts
218
+ // src/stringify.ts
276
219
  function stringifyDeterministicForKeys(params) {
277
220
  const cache = /* @__PURE__ */ new Set();
278
221
  return JSON.stringify(params, (_, value) => {
@@ -303,7 +246,7 @@ function stringifyDeterministicForKeys(params) {
303
246
  });
304
247
  }
305
248
 
306
- // src/utils/useDeepMemo.ts
249
+ // src/useDeepMemo.ts
307
250
  var import_isEqual = __toESM(require("lodash/isEqual"));
308
251
  var import_react2 = require("react");
309
252
  function useDeepMemo(value) {
@@ -318,22 +261,25 @@ function useDeepMemo(value) {
318
261
  }
319
262
 
320
263
  // src/sharedQuery.ts
321
- var SharedQueryDefaults = {
322
- // Refetch on every page load.
264
+ var SharedQueryConfig = {
265
+ /** Refetch on every page load. */
323
266
  staleMs: 0,
324
- // Keep in cache for a long time.
267
+ /** Keep in cache for a long time. */
325
268
  expiryMs: 30 * import_timeConstants2.MS_PER_DAY,
326
- // Always refetch automatically if the data is stale.
269
+ /** Always refetch automatically if the data is stale. */
327
270
  refetchOnStale: true,
328
- // No default persistence configured.
271
+ /** No default persistence configured. */
329
272
  persistTo: void 0,
330
- // 100 is not too large, but please tweak accordingly.
273
+ /** 100 is not too large, but please tweak accordingly. */
331
274
  maxSize: 100,
332
- // 100kb limit before discarding old records.
275
+ /** 100kb limit before discarding old records. */
333
276
  maxBytes: 1e5,
334
- // Default to log to console.
277
+ /** Default to log to console. */
335
278
  log: (...args) => console.log("[sharedQuery]", ...args)
336
279
  };
280
+ function configureSharedQuery(config) {
281
+ Object.assign(SharedQueryConfig, config);
282
+ }
337
283
  var SharedQuery = class {
338
284
  inflightQueries = /* @__PURE__ */ new Map();
339
285
  queryState;
@@ -350,32 +296,32 @@ var SharedQuery = class {
350
296
  refetchOnStale;
351
297
  maxSize;
352
298
  maxBytes;
299
+ keepLastNonEmptyData;
353
300
  log;
354
301
  constructor(options) {
355
302
  this.queryName = options.queryName;
356
303
  this.queryFn = options.queryFn;
357
- this.staleMs = options?.staleMs ?? SharedQueryDefaults.staleMs;
358
- this.expiryMs = options?.expiryMs ?? SharedQueryDefaults.expiryMs;
359
- this.refetchOnStale = options?.refetchOnStale ?? SharedQueryDefaults.refetchOnStale;
360
- if (!options.localStorageKey && !options.indexedDbKey) {
361
- const persistTo = options.persistTo ?? SharedQueryDefaults.persistTo;
362
- if (persistTo === "localStorage") {
363
- options.localStorageKey = options.queryName;
364
- } else if (persistTo === "indexedDb") {
365
- 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
+ };
366
315
  }
367
316
  }
368
- this.maxSize = options?.maxSize ?? SharedQueryDefaults.maxSize;
369
- this.maxBytes = options?.maxBytes ?? SharedQueryDefaults.maxBytes;
370
- this.log = options?.log ?? SharedQueryDefaults.log;
317
+ this.maxSize = options?.maxSize ?? SharedQueryConfig.maxSize;
318
+ this.maxBytes = options?.maxBytes ?? SharedQueryConfig.maxBytes;
319
+ this.keepLastNonEmptyData = Boolean(options?.keepLastNonEmptyData);
320
+ this.log = options?.log ?? SharedQueryConfig.log;
371
321
  this.queryState = sharedState(
372
322
  {},
373
323
  // defaultValue
374
- {
375
- ...options,
376
- // Fallback to use expiryMs for the storeExpiryMs.
377
- storeExpiryMs: options?.storeExpiryMs || this.expiryMs
378
- }
324
+ options
379
325
  );
380
326
  }
381
327
  /** Returns a key to identify requests based on the given args. */
@@ -486,7 +432,14 @@ var SharedQuery = class {
486
432
  }
487
433
  /** Get the current stored data for a query key. */
488
434
  getData(queryKey) {
489
- 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;
490
443
  }
491
444
  /** Set the data directly if the user obtained it from somewhere else. */
492
445
  setData(queryKey, data, dataUpdatedMs = Date.now()) {
@@ -556,70 +509,64 @@ var SharedQuery = class {
556
509
  return;
557
510
  }
558
511
  const oldByteSize = (0, import_object_sizeof.default)(oldState);
559
- let newState = oldState;
512
+ let numToCut = this.maxSize ? oldSize - this.maxSize : 0;
513
+ let bytesToCut = this.maxBytes ? oldByteSize - this.maxBytes : 0;
514
+ if (numToCut <= 0 && bytesToCut <= 0) {
515
+ this.log(
516
+ `No need to trim data for ${this.queryName}: size=${oldSize} (limit=${this.maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`
517
+ );
518
+ return;
519
+ }
520
+ this.log(
521
+ `Need to trim ${this.queryName}: numToCut=${numToCut}, bytesToCut=${bytesToCut}`
522
+ );
523
+ const newState = { ...oldState };
560
524
  let needUpdate = false;
561
- if (this.maxSize) {
562
- let numToCut = oldSize - this.maxSize;
563
- if (numToCut > 0) {
564
- this.log(
565
- `enforceSizeLimit for ${this.queryName} needed due to size ${oldSize} > ${this.maxSize}`
566
- );
567
- newState = { ...newState };
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
- }
525
+ const deleteKeyIfNotMounted = (key, expired) => {
526
+ if (this.mountedKeys.has(key)) {
527
+ this.log(`Cannot clean up ${key} as it is mounted`);
528
+ return;
529
+ }
530
+ const byteSize = (0, import_object_sizeof.default)(key) + (0, import_object_sizeof.default)(newState[key]);
531
+ this.log(
532
+ `Cleaning up unmounted${expired ? ", expired" : ""} ${key} (byteSize=${byteSize.toLocaleString()})`
533
+ );
534
+ numToCut--;
535
+ bytesToCut -= byteSize;
536
+ delete newState[key];
537
+ needUpdate = true;
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)) {
544
+ break;
585
545
  }
546
+ deleteKeyIfNotMounted(key, true);
586
547
  }
587
- if (this.maxBytes && Object.keys(newState).length > 1) {
588
- const currentByteSize = needUpdate ? (0, import_object_sizeof.default)(newState) : oldByteSize;
589
- let bytesToCut = currentByteSize - this.maxBytes;
590
- if (bytesToCut > 0) {
591
- this.log(
592
- `enforceSizeLimit for ${this.queryName} needed due to byte size ${currentByteSize.toLocaleString()} > ${this.maxBytes.toLocaleString()}`
593
- );
594
- if (!needUpdate) {
595
- newState = { ...newState };
596
- needUpdate = true;
597
- }
598
- const entriesByTimeAscending = Object.entries(newState).sort(
548
+ if (numToCut > 0 || bytesToCut > 0) {
549
+ if (needUpdate) {
550
+ entriesByTimeAscending = Object.entries(newState).sort(
599
551
  (entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
600
552
  );
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
- }
553
+ }
554
+ for (const [key] of entriesByTimeAscending) {
555
+ deleteKeyIfNotMounted(key, false);
556
+ if (numToCut <= 0 && bytesToCut <= 0) {
557
+ break;
611
558
  }
612
559
  }
613
560
  }
614
- if (needUpdate) {
615
- this.queryState.setValue(newState);
561
+ if (!needUpdate) {
616
562
  this.log(
617
- `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()})`
563
+ `No trimmable entries found for ${this.queryName}: size=${oldSize} (limit=${this.maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`
618
564
  );
619
565
  return;
620
566
  }
567
+ this.queryState.setValue(newState);
621
568
  this.log(
622
- `No need to trim data for ${this.queryName}: size=${oldSize} (limit=${this.maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`
569
+ `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
570
  );
624
571
  }
625
572
  };
@@ -635,7 +582,7 @@ var DEFAULT_QUERY_STATE_ENTRY = {
635
582
  lastUpdatedMs: 0,
636
583
  loading: true
637
584
  };
638
- function useSharedQuery(query, args = []) {
585
+ function useSharedQuery(query, ...args) {
639
586
  const stableArgs = useDeepMemo(args);
640
587
  const [queryState, setQueryState] = useSharedState(query.queryState);
641
588
  const isMounted = (0, import_react3.useRef)(true);
@@ -643,23 +590,29 @@ function useSharedQuery(query, args = []) {
643
590
  () => query.getQueryKey(stableArgs),
644
591
  [query, stableArgs]
645
592
  );
646
- const queryStateValue = queryState[queryKey];
593
+ const queryStateValue = queryState[queryKey] ?? DEFAULT_QUERY_STATE_ENTRY;
647
594
  const setQueryStateValue = (0, import_react3.useCallback)(
648
- (arg) => {
595
+ (next) => {
649
596
  setQueryState((prev) => {
650
597
  const clone = { ...prev };
651
- clone[queryKey] = typeof arg === "function" ? arg(clone[queryKey]) : arg;
598
+ clone[queryKey] = typeof next === "function" ? next(clone[queryKey] ?? DEFAULT_QUERY_STATE_ENTRY) : next;
652
599
  return clone;
653
600
  });
654
601
  },
655
602
  [queryKey, setQueryState]
656
603
  );
604
+ const [lastNonEmptyData, setLastNonEmptyData] = (0, import_react3.useState)();
605
+ (0, import_react3.useEffect)(() => {
606
+ if (query.keepLastNonEmptyData && queryStateValue.data !== void 0) {
607
+ setLastNonEmptyData(queryStateValue.data);
608
+ }
609
+ }, [query.keepLastNonEmptyData, queryStateValue.data]);
657
610
  const execute = (0, import_react3.useCallback)(
658
611
  async (forceRefresh) => {
659
612
  query.log(`Begin executing shared query ${queryKey}`);
660
613
  setQueryStateValue((prev) => ({
661
614
  // Keep old data and error.
662
- ...prev ?? {},
615
+ ...prev,
663
616
  lastUpdatedMs: Date.now(),
664
617
  loading: true
665
618
  }));
@@ -680,7 +633,7 @@ function useSharedQuery(query, args = []) {
680
633
  const now = Date.now();
681
634
  setQueryStateValue((prev) => ({
682
635
  // Keep old data.
683
- ...prev ?? {},
636
+ ...prev,
684
637
  lastUpdatedMs: now,
685
638
  loading: false,
686
639
  error: e,
@@ -701,9 +654,9 @@ function useSharedQuery(query, args = []) {
701
654
  };
702
655
  }, [execute, query, queryKey]);
703
656
  return (0, import_react3.useMemo)(() => {
704
- const state = queryStateValue ?? DEFAULT_QUERY_STATE_ENTRY;
705
657
  return {
706
- ...state,
658
+ ...queryStateValue,
659
+ lastNonEmptyData,
707
660
  // This works only if the user-given queryFn supports abort. If not,
708
661
  // this function doesn't do anything, since we don't have any means to
709
662
  // abort the running queryFn.
@@ -718,7 +671,7 @@ function useSharedQuery(query, args = []) {
718
671
  setData: (data, dataUpdatedMs) => {
719
672
  const now = Date.now();
720
673
  setQueryStateValue((prev) => ({
721
- ...prev ?? {},
674
+ ...prev,
722
675
  data,
723
676
  dataUpdatedMs: dataUpdatedMs ?? now,
724
677
  lastUpdatedMs: now,
@@ -735,6 +688,7 @@ function useSharedQuery(query, args = []) {
735
688
  };
736
689
  }, [
737
690
  execute,
691
+ lastNonEmptyData,
738
692
  query,
739
693
  queryKey,
740
694
  queryStateValue,
@@ -745,7 +699,8 @@ function useSharedQuery(query, args = []) {
745
699
  // Annotate the CommonJS export names for ESM import in node:
746
700
  0 && (module.exports = {
747
701
  SharedQuery,
748
- SharedQueryDefaults,
702
+ SharedQueryConfig,
703
+ configureSharedQuery,
749
704
  sharedQuery,
750
705
  useSharedQuery
751
706
  });