@choksheak/react-utils 0.3.8 → 0.4.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 (87) hide show
  1. package/NoSSR.cjs +7 -30
  2. package/NoSSR.min.cjs +1 -1
  3. package/NoSSR.min.cjs.map +1 -1
  4. package/NoSSR.min.mjs +1 -1
  5. package/NoSSR.min.mjs.map +1 -1
  6. package/NoSSR.mjs +2 -0
  7. package/chunk-33UMQLKY.mjs +43 -0
  8. package/chunk-3V7WBGWQ.mjs +16 -0
  9. package/chunk-3X25NN6X.cjs +195 -0
  10. package/chunk-5DO7KC3L.cjs +43 -0
  11. package/chunk-5PDGU37E.cjs +104 -0
  12. package/chunk-6ZZ54XM5.min.mjs +2 -0
  13. package/chunk-6ZZ54XM5.min.mjs.map +1 -0
  14. package/chunk-74BS2FAO.cjs +12 -0
  15. package/chunk-F5L32IUC.min.cjs +2 -0
  16. package/chunk-F5L32IUC.min.cjs.map +1 -0
  17. package/chunk-GPV6JIQK.mjs +17 -0
  18. package/chunk-KE3RXVMS.min.cjs +2 -0
  19. package/chunk-KE3RXVMS.min.cjs.map +1 -0
  20. package/chunk-KILGR7YA.min.mjs +2 -0
  21. package/chunk-KILGR7YA.min.mjs.map +1 -0
  22. package/chunk-KRXENFK3.mjs +195 -0
  23. package/chunk-MWDH7YON.min.mjs +2 -0
  24. package/chunk-MWDH7YON.min.mjs.map +1 -0
  25. package/chunk-NTFFK7ZH.min.cjs +2 -0
  26. package/chunk-NTFFK7ZH.min.cjs.map +1 -0
  27. package/chunk-O3RBYG3W.min.cjs +2 -0
  28. package/chunk-O3RBYG3W.min.cjs.map +1 -0
  29. package/chunk-PHLY3LR4.mjs +104 -0
  30. package/chunk-RM4ZGGZ2.min.mjs +2 -0
  31. package/chunk-RM4ZGGZ2.min.mjs.map +1 -0
  32. package/chunk-TEZPF6BG.min.cjs +2 -0
  33. package/chunk-TEZPF6BG.min.cjs.map +1 -0
  34. package/chunk-UFJNJ3WV.cjs +16 -0
  35. package/chunk-UFJQLQFI.min.cjs +2 -0
  36. package/chunk-UFJQLQFI.min.cjs.map +1 -0
  37. package/chunk-V7L3UICC.min.mjs +2 -0
  38. package/chunk-V7L3UICC.min.mjs.map +1 -0
  39. package/chunk-VHMO75EY.min.mjs +2 -0
  40. package/chunk-VHMO75EY.min.mjs.map +1 -0
  41. package/chunk-WVLPJPWG.cjs +17 -0
  42. package/chunk-X6BYQHVC.mjs +12 -0
  43. package/fetcher.cjs +8 -50
  44. package/fetcher.d.mts +3 -3
  45. package/fetcher.d.ts +2 -2
  46. package/fetcher.min.cjs +1 -1
  47. package/fetcher.min.cjs.map +1 -1
  48. package/fetcher.min.mjs +1 -1
  49. package/fetcher.min.mjs.map +1 -1
  50. package/fetcher.mjs +5 -11
  51. package/package.json +124 -29
  52. package/sharedQuery.cjs +132 -392
  53. package/sharedQuery.d.mts +58 -13
  54. package/sharedQuery.d.ts +57 -12
  55. package/sharedQuery.min.cjs +1 -1
  56. package/sharedQuery.min.cjs.map +1 -1
  57. package/sharedQuery.min.mjs +1 -1
  58. package/sharedQuery.min.mjs.map +1 -1
  59. package/sharedQuery.mjs +94 -322
  60. package/sharedState.cjs +15 -304
  61. package/sharedState.d.mts +4 -4
  62. package/sharedState.d.ts +4 -4
  63. package/sharedState.min.cjs +1 -1
  64. package/sharedState.min.cjs.map +1 -1
  65. package/sharedState.min.mjs +1 -1
  66. package/sharedState.min.mjs.map +1 -1
  67. package/sharedState.mjs +10 -269
  68. package/storage.cjs +10 -123
  69. package/storage.d.mts +1 -0
  70. package/storage.d.ts +1 -0
  71. package/storage.min.cjs +1 -1
  72. package/storage.min.cjs.map +1 -1
  73. package/storage.min.mjs +1 -1
  74. package/storage.min.mjs.map +1 -1
  75. package/storage.mjs +6 -92
  76. package/stringify.cjs +8 -57
  77. package/stringify.min.cjs +1 -1
  78. package/stringify.min.cjs.map +1 -1
  79. package/stringify.min.mjs +1 -1
  80. package/stringify.min.mjs.map +1 -1
  81. package/stringify.mjs +6 -30
  82. package/useDeepMemo.cjs +6 -50
  83. package/useDeepMemo.min.cjs +1 -1
  84. package/useDeepMemo.min.cjs.map +1 -1
  85. package/useDeepMemo.min.mjs +1 -1
  86. package/useDeepMemo.min.mjs.map +1 -1
  87. package/useDeepMemo.mjs +4 -13
package/sharedQuery.mjs CHANGED
@@ -1,325 +1,36 @@
1
+ import {
2
+ fetcher
3
+ } from "./chunk-3V7WBGWQ.mjs";
4
+ import {
5
+ sharedState,
6
+ useSharedState
7
+ } from "./chunk-KRXENFK3.mjs";
8
+ import "./chunk-PHLY3LR4.mjs";
9
+ import {
10
+ init_stringify,
11
+ stringifyDeterministicForKeys
12
+ } from "./chunk-33UMQLKY.mjs";
13
+ import {
14
+ useDeepMemo
15
+ } from "./chunk-GPV6JIQK.mjs";
16
+ import "./chunk-X6BYQHVC.mjs";
17
+
1
18
  // src/sharedQuery.ts
2
- import { MS_PER_DAY as MS_PER_DAY2 } from "@choksheak/ts-utils/timeConstants";
3
19
  import getByteSize from "object-sizeof";
4
20
  import {
5
21
  useCallback,
6
22
  useEffect,
7
- useMemo as useMemo2,
23
+ useMemo,
8
24
  useRef,
9
- useState as useState2
25
+ useState
10
26
  } from "react";
11
-
12
- // src/sharedState.ts
13
- import { MS_PER_DAY } from "@choksheak/ts-utils/timeConstants";
14
- import { useSyncExternalStore } from "react";
15
-
16
- // src/storage.ts
17
- import { kvStore } from "@choksheak/ts-utils/kvStore";
18
- import { createLocalStore } from "@choksheak/ts-utils/localStore";
19
- var IS_NOT_BROWSER = typeof window === "undefined";
20
- function getStorageAdapter(options, defaultStoreExpiryMs) {
21
- const { store } = options ?? {};
22
- if (!store) {
23
- return null;
24
- }
25
- if ("persistTo" in store && typeof store.persistTo === "string" && "key" in store && typeof store.key === "string") {
26
- const expiryMs = store.expiryMs || defaultStoreExpiryMs;
27
- const config = { ...store, expiryMs };
28
- switch (store.persistTo) {
29
- case "localStorage":
30
- return localStoreAdapter(config);
31
- case "indexedDb":
32
- return kvStoreAdapter(config);
33
- default:
34
- store.persistTo;
35
- throw new Error(`Unknown store.adapter ${store.persistTo}`);
36
- }
37
- }
38
- return store;
39
- }
40
- function localStoreAdapter({
41
- key,
42
- expiryMs,
43
- isValid
44
- }) {
45
- const adapter = createLocalStore(key, { defaultExpiryMs: expiryMs });
46
- return {
47
- set: (key2, value) => {
48
- if (IS_NOT_BROWSER) return;
49
- adapter.set(key2, value);
50
- },
51
- get: (key2) => {
52
- if (IS_NOT_BROWSER) return void 0;
53
- const value = adapter.get(key2);
54
- if (isValid && !isValid(value)) {
55
- console.warn(
56
- `localStorage adapter: Auto-discard invalid value for ${key2}: ${JSON.stringify(value)}`
57
- );
58
- adapter.delete(key2);
59
- return void 0;
60
- }
61
- return value;
62
- },
63
- delete: (key2) => {
64
- if (IS_NOT_BROWSER) return;
65
- adapter.delete(key2);
66
- },
67
- clear: () => {
68
- if (IS_NOT_BROWSER) return;
69
- adapter.clear();
70
- }
71
- };
72
- }
73
- function kvStoreAdapter({
74
- key,
75
- expiryMs,
76
- isValid
77
- }) {
78
- const keyPrefix = key + ":";
79
- return {
80
- set: async (key2, value) => {
81
- if (IS_NOT_BROWSER) return;
82
- await kvStore.set(keyPrefix + key2, value, expiryMs);
83
- },
84
- get: async (key2) => {
85
- if (IS_NOT_BROWSER) return void 0;
86
- const fullKey = keyPrefix + key2;
87
- const value = await kvStore.get(fullKey);
88
- if (value !== void 0 && isValid && !isValid(value)) {
89
- console.warn(
90
- `indexedDb adapter: Auto-discard invalid value for ${key2}:`,
91
- value
92
- );
93
- await kvStore.delete(fullKey);
94
- return void 0;
95
- }
96
- return value;
97
- },
98
- delete: async (key2) => {
99
- if (IS_NOT_BROWSER) return;
100
- await kvStore.delete(keyPrefix + key2);
101
- },
102
- clear: async () => {
103
- if (IS_NOT_BROWSER) return;
104
- await kvStore.clear();
105
- }
106
- };
107
- }
108
-
109
- // src/sharedState.ts
110
- var SharedStateConfig = {
111
- /**
112
- * Default duration for storage expiration in milliseconds. Initial value
113
- * is 30 days, but can be changed to anything.
114
- */
115
- storeExpiryMs: 30 * MS_PER_DAY,
116
- /**
117
- * True to load from storage only on mount, false (default) to load in
118
- * the top level scope when the shared state is defined.
119
- */
120
- lazyLoad: false
121
- };
122
- function createPubSubStore() {
123
- const dataByKey = /* @__PURE__ */ new Map();
124
- const subscribersByKey = /* @__PURE__ */ new Map();
125
- function notify(key, next, prev) {
126
- const subscribers = subscribersByKey.get(key);
127
- if (!subscribers) return;
128
- const copy = Array.from(subscribers.values());
129
- for (const subscriber of copy) {
130
- try {
131
- subscriber(next, prev);
132
- } catch (e) {
133
- console.error(`Error invoking subscriber:`, e);
134
- }
135
- }
136
- }
137
- return {
138
- // This could be undefined if T includes undefined, else it should never
139
- // return undefined because the user has to always specify the default value.
140
- get(key) {
141
- return dataByKey.get(key);
142
- },
143
- set(key, value) {
144
- const prev = dataByKey.get(key);
145
- if (prev === value) return;
146
- dataByKey.set(key, value);
147
- notify(key, value, prev);
148
- },
149
- delete(key) {
150
- dataByKey.delete(key);
151
- },
152
- setNoNotify(key, value) {
153
- dataByKey.set(key, value);
154
- },
155
- // Subscribe to changes of a specific key
156
- subscribe(key, subscriber) {
157
- let subscribers = subscribersByKey.get(key);
158
- if (!subscribers) {
159
- subscribers = /* @__PURE__ */ new Set();
160
- subscribersByKey.set(key, subscribers);
161
- }
162
- subscribers.add(subscriber);
163
- return () => {
164
- subscribers.delete(subscriber);
165
- if (subscribers.size === 0) subscribersByKey.delete(key);
166
- };
167
- }
168
- };
169
- }
170
- var pubSubStore = createPubSubStore();
171
- var STORAGE_KEY = "state";
172
- var stateKey = 0;
173
- function sharedState(defaultValue, options) {
174
- let readyResolve = () => {
175
- };
176
- const readyPromise = new Promise((resolve) => {
177
- readyResolve = resolve;
178
- });
179
- const resolveReadyPromise = readyResolve;
180
- const pubSubKey = String(stateKey++);
181
- const storageAdapter = getStorageAdapter(
182
- options,
183
- SharedStateConfig.storeExpiryMs
184
- );
185
- pubSubStore.setNoNotify(pubSubKey, defaultValue);
186
- const lazyLoad = options?.lazyLoad;
187
- const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateConfig.lazyLoad;
188
- const obj = {
189
- /** Allow users to await for the completion of data init. */
190
- readyPromise,
191
- lazy,
192
- initStarted: false,
193
- initDone: false,
194
- /** `subscribe` function required by useSyncExternalStore. */
195
- subscribe(subscriber) {
196
- return pubSubStore.subscribe(pubSubKey, () => subscriber());
197
- },
198
- /** Returns true if there is a backing store. */
199
- hasStore() {
200
- return Boolean(storageAdapter);
201
- },
202
- /**
203
- * Set a value directly into the store. Does nothing if there is no
204
- * backing store.
205
- *
206
- * Warning: If you want to set the value properly, you should use
207
- * `setValue()` instead which also updates the in-memory copy.
208
- */
209
- async setInStore(value) {
210
- return await storageAdapter?.set(STORAGE_KEY, value);
211
- },
212
- /**
213
- * Get a value directly from the store. Returns undefined if there is no
214
- * backing store.
215
- */
216
- async getFromStore() {
217
- return await storageAdapter?.get(STORAGE_KEY);
218
- },
219
- /**
220
- * Get the value of the shared state.
221
- * `getSnapshot` function — must return same ref if value unchanged.
222
- * Note that this would return the default value if data init from store is
223
- * not completed yet.
224
- */
225
- getSnapshot() {
226
- return pubSubStore.get(pubSubKey);
227
- },
228
- /** Set the value of the shared state. */
229
- setValue(next) {
230
- if (typeof next === "function") {
231
- const prev = pubSubStore.get(pubSubKey);
232
- next = next(prev);
233
- }
234
- pubSubStore.set(pubSubKey, next);
235
- void obj.setInStore(next);
236
- },
237
- /**
238
- * Remove the value of the shared state. Actually this just sets the value
239
- * back to the given default value.
240
- */
241
- delete() {
242
- pubSubStore.set(pubSubKey, defaultValue);
243
- void storageAdapter?.set(STORAGE_KEY, defaultValue);
244
- },
245
- /** Initial default handling. */
246
- async initDefaultValueOnce() {
247
- if (obj.initDone || obj.initStarted) return;
248
- obj.initStarted = true;
249
- const stored = await obj.getFromStore();
250
- const value = stored !== void 0 ? stored : defaultValue;
251
- pubSubStore.set(pubSubKey, value);
252
- obj.initDone = true;
253
- resolveReadyPromise();
254
- }
255
- };
256
- if (!lazy) {
257
- void obj.initDefaultValueOnce();
258
- }
259
- return obj;
260
- }
261
- function useSharedState(state) {
262
- void state.initDefaultValueOnce();
263
- const value = useSyncExternalStore(
264
- (onStoreChange) => state.subscribe(onStoreChange),
265
- () => state.getSnapshot(),
266
- // getServerSnapshot is needed to prevent SSR dev errors in nextjs.
267
- () => state.getSnapshot()
268
- );
269
- return [value, state.setValue];
270
- }
271
-
272
- // src/stringify.ts
273
- function stringifyDeterministicForKeys(params) {
274
- const cache = /* @__PURE__ */ new Set();
275
- return JSON.stringify(params, (_, value) => {
276
- if (typeof value === "function") {
277
- throw new Error(`Functions should not be passed into query parameters`);
278
- }
279
- if (!value || typeof value !== "object" || Array.isArray(value)) {
280
- return value;
281
- }
282
- if (cache.has(value)) {
283
- return "[Circular]";
284
- }
285
- cache.add(value);
286
- if (value !== params) {
287
- try {
288
- JSON.stringify(value);
289
- } catch {
290
- return "[Unserializable]";
291
- }
292
- }
293
- return Object.keys(value).sort().reduce(
294
- (result, key) => {
295
- result[key] = value[key];
296
- return result;
297
- },
298
- {}
299
- );
300
- });
301
- }
302
-
303
- // src/useDeepMemo.ts
304
- import isEqual from "lodash/isEqual";
305
- import { useMemo, useState } from "react";
306
- function useDeepMemo(value) {
307
- const [state, setState] = useState(value);
308
- return useMemo(() => {
309
- if (isEqual(state, value)) {
310
- return state;
311
- }
312
- setState(value);
313
- return value;
314
- }, [state, value]);
315
- }
316
-
317
- // src/sharedQuery.ts
27
+ import XXH from "xxhashjs";
28
+ init_stringify();
318
29
  var SharedQueryConfig = {
319
30
  /** Refetch on every page load. */
320
31
  staleMs: 0,
321
32
  /** Keep in cache for a long time. */
322
- expiryMs: 30 * MS_PER_DAY2,
33
+ expiryMs: 30 * 864e5,
323
34
  /** Always refetch automatically if the data is stale. */
324
35
  refetchOnStale: true,
325
36
  /** No default persistence configured. */
@@ -336,14 +47,55 @@ function configureSharedQuery(config) {
336
47
  }
337
48
  var seenQueryNames = /* @__PURE__ */ new Set();
338
49
  function sharedQuery(options) {
339
- if (seenQueryNames.has(options.queryName)) {
340
- throw new Error(`Duplicate shared query name "${options.queryName}"`);
50
+ const url = options.url || "";
51
+ const queryName = options.queryName || url;
52
+ if (!queryName) {
53
+ throw new Error(`Either queryName or url must be specified.`);
341
54
  }
342
- seenQueryNames.add(options.queryName);
55
+ if (seenQueryNames.has(queryName)) {
56
+ throw new Error(`Duplicate shared query name "${queryName}"`);
57
+ }
58
+ seenQueryNames.add(queryName);
343
59
  const inflightQueries = /* @__PURE__ */ new Map();
344
60
  const mountedKeys = /* @__PURE__ */ new Map();
345
- const queryName = options.queryName;
346
- const queryFn = options.queryFn;
61
+ let queryFn;
62
+ if (options.queryFn) {
63
+ queryFn = options.queryFn;
64
+ } else if (url) {
65
+ queryFn = (...args) => {
66
+ let params = "";
67
+ const firstArg = args?.[0];
68
+ if (firstArg) {
69
+ if (typeof firstArg === "string") {
70
+ params = firstArg;
71
+ } else if (typeof firstArg === "object") {
72
+ const entries = [];
73
+ for (const [k, v] of Object.entries(firstArg)) {
74
+ if (v === void 0) continue;
75
+ if (Array.isArray(v)) {
76
+ for (const item of v) {
77
+ if (item === void 0) continue;
78
+ entries.push([k, String(item)]);
79
+ }
80
+ } else {
81
+ entries.push([k, String(v)]);
82
+ }
83
+ }
84
+ params = new URLSearchParams(entries).toString();
85
+ }
86
+ if (params) {
87
+ if (url.includes("?")) {
88
+ params = "&" + params;
89
+ } else {
90
+ params = "?" + params;
91
+ }
92
+ }
93
+ }
94
+ return fetcher.url(url + params).get().json();
95
+ };
96
+ } else {
97
+ throw new Error(`Either queryFn or url must be specified`);
98
+ }
347
99
  const refetchFn = options.refetchFn;
348
100
  const staleMs = options?.staleMs ?? SharedQueryConfig.staleMs;
349
101
  const expiryMs = options?.expiryMs ?? SharedQueryConfig.expiryMs;
@@ -523,7 +275,11 @@ function sharedQuery(options) {
523
275
  queryState,
524
276
  /** Returns a key to identify requests based on the given args. */
525
277
  getQueryKey(args) {
526
- return queryName + ":" + stringifyDeterministicForKeys(args);
278
+ let s = stringifyDeterministicForKeys(args);
279
+ if (s.length > 30) {
280
+ s = XXH.h32(s, 39406).toString(16).slice(2);
281
+ }
282
+ return queryName + ":" + s;
527
283
  },
528
284
  /**
529
285
  * Get the AbortController to abort the inflight query (if any).
@@ -542,7 +298,7 @@ function sharedQuery(options) {
542
298
  /**
543
299
  * Get the AbortSignal to check for query abortions.
544
300
  *
545
- * Example:
301
+ * Example using fetch:
546
302
  * ```
547
303
  * const getUserQuery = sharedQuery({
548
304
  * queryName: "getUser",
@@ -563,6 +319,20 @@ function sharedQuery(options) {
563
319
  * },
564
320
  * });
565
321
  * ```
322
+ *
323
+ * Example using `fetcher.ts`:
324
+ * ```
325
+ * const getUserQuery = sharedQuery({
326
+ * queryName: "getUser",
327
+ * queryFn: (userId: string) => {
328
+ * // Get the signal for this current execution.
329
+ * const signal = getUserQuery.getAbortSignal([userId]);
330
+ *
331
+ * // Pass the signal to fetch so that it can be aborted.
332
+ * return fetcher.url(`/users/${userId}`).options({ signal }).get().json();
333
+ * },
334
+ * });
335
+ * ```
566
336
  */
567
337
  getAbortSignal(args) {
568
338
  const queryKey = obj.getQueryKey(args);
@@ -685,7 +455,7 @@ function useSharedQuery(query, ...args) {
685
455
  const stableArgs = useDeepMemo(args);
686
456
  const [queryState, setQueryState] = useSharedState(query.queryState);
687
457
  const isMounted = useRef(true);
688
- const queryKey = useMemo2(
458
+ const queryKey = useMemo(
689
459
  () => query.getQueryKey(stableArgs),
690
460
  [query, stableArgs]
691
461
  );
@@ -700,7 +470,7 @@ function useSharedQuery(query, ...args) {
700
470
  },
701
471
  [queryKey, setQueryState]
702
472
  );
703
- const [lastNonEmptyData, setLastNonEmptyData] = useState2();
473
+ const [lastNonEmptyData, setLastNonEmptyData] = useState();
704
474
  useEffect(() => {
705
475
  if (query.keepLastNonEmptyData && queryStateValue.data !== void 0) {
706
476
  setLastNonEmptyData(queryStateValue.data);
@@ -759,10 +529,12 @@ function useSharedQuery(query, ...args) {
759
529
  query.unmount(queryKey);
760
530
  };
761
531
  }, [execute, query, queryKey]);
762
- return useMemo2(() => {
532
+ return useMemo(() => {
763
533
  return {
764
534
  ...queryStateValue,
765
- lastNonEmptyData,
535
+ // Fill with lastNonEmptyData when data is undefined.
536
+ data: queryStateValue.data !== void 0 ? queryStateValue.data : lastNonEmptyData,
537
+ latestData: queryStateValue.data,
766
538
  // This works only if the user-given queryFn supports abort. If not,
767
539
  // this function doesn't do anything, since we don't have any means to
768
540
  // abort the running queryFn.