floppy-disk 3.0.0-experimental.1 → 3.0.1

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 (76) hide show
  1. package/README.md +256 -676
  2. package/esm/index.d.mts +1 -0
  3. package/esm/index.mjs +1 -0
  4. package/esm/react/create-mutation.d.mts +151 -0
  5. package/esm/react/create-query.d.mts +344 -0
  6. package/esm/react/create-store.d.mts +28 -0
  7. package/esm/react/create-stores.d.mts +39 -0
  8. package/esm/react/use-isomorphic-layout-effect.d.mts +6 -0
  9. package/esm/react/use-mutation.d.mts +82 -0
  10. package/esm/react/use-store.d.mts +28 -0
  11. package/esm/react.d.mts +7 -0
  12. package/esm/react.mjs +697 -0
  13. package/esm/vanilla/basic.d.mts +13 -0
  14. package/esm/vanilla/hash.d.mts +7 -0
  15. package/esm/vanilla/store.d.mts +89 -0
  16. package/esm/vanilla.d.mts +3 -0
  17. package/esm/vanilla.mjs +82 -0
  18. package/index.d.ts +1 -0
  19. package/index.js +12 -0
  20. package/package.json +47 -45
  21. package/react/create-mutation.d.ts +151 -0
  22. package/react/create-query.d.ts +344 -0
  23. package/react/create-store.d.ts +28 -0
  24. package/react/create-stores.d.ts +39 -0
  25. package/react/use-isomorphic-layout-effect.d.ts +6 -0
  26. package/react/use-mutation.d.ts +82 -0
  27. package/react/use-store.d.ts +28 -0
  28. package/react.d.ts +7 -0
  29. package/react.js +705 -0
  30. package/ts_version_4.5_and_above_is_required.d.ts +0 -0
  31. package/vanilla/basic.d.ts +13 -0
  32. package/vanilla/hash.d.ts +7 -0
  33. package/vanilla/store.d.ts +89 -0
  34. package/vanilla.d.ts +3 -0
  35. package/vanilla.js +89 -0
  36. package/esm/index.d.ts +0 -8
  37. package/esm/index.js +0 -8
  38. package/esm/react/create-bi-direction-query.d.ts +0 -166
  39. package/esm/react/create-bi-direction-query.js +0 -74
  40. package/esm/react/create-mutation.d.ts +0 -39
  41. package/esm/react/create-mutation.js +0 -56
  42. package/esm/react/create-query.d.ts +0 -319
  43. package/esm/react/create-query.js +0 -434
  44. package/esm/react/create-store.d.ts +0 -38
  45. package/esm/react/create-store.js +0 -38
  46. package/esm/react/create-stores.d.ts +0 -61
  47. package/esm/react/create-stores.js +0 -99
  48. package/esm/react/with-context.d.ts +0 -5
  49. package/esm/react/with-context.js +0 -14
  50. package/esm/utils.d.ts +0 -24
  51. package/esm/utils.js +0 -31
  52. package/esm/vanilla/fetcher.d.ts +0 -27
  53. package/esm/vanilla/fetcher.js +0 -95
  54. package/esm/vanilla/init-store.d.ts +0 -24
  55. package/esm/vanilla/init-store.js +0 -51
  56. package/lib/index.d.ts +0 -8
  57. package/lib/index.js +0 -11
  58. package/lib/react/create-bi-direction-query.d.ts +0 -166
  59. package/lib/react/create-bi-direction-query.js +0 -78
  60. package/lib/react/create-mutation.d.ts +0 -39
  61. package/lib/react/create-mutation.js +0 -60
  62. package/lib/react/create-query.d.ts +0 -319
  63. package/lib/react/create-query.js +0 -438
  64. package/lib/react/create-store.d.ts +0 -38
  65. package/lib/react/create-store.js +0 -42
  66. package/lib/react/create-stores.d.ts +0 -61
  67. package/lib/react/create-stores.js +0 -104
  68. package/lib/react/with-context.d.ts +0 -5
  69. package/lib/react/with-context.js +0 -18
  70. package/lib/utils.d.ts +0 -24
  71. package/lib/utils.js +0 -39
  72. package/lib/vanilla/fetcher.d.ts +0 -27
  73. package/lib/vanilla/fetcher.js +0 -99
  74. package/lib/vanilla/init-store.d.ts +0 -24
  75. package/lib/vanilla/init-store.js +0 -55
  76. package/utils/package.json +0 -6
package/react.js ADDED
@@ -0,0 +1,705 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var vanilla = require('floppy-disk/vanilla');
5
+
6
+ const useIsomorphicLayoutEffect = vanilla.isClient ? react.useLayoutEffect : react.useEffect;
7
+
8
+ const getValueByPath = (obj, path) => path.reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
9
+ const isPrefixPath = (candidatePrefix, targetPath) => {
10
+ if (candidatePrefix.length >= targetPath.length) return false;
11
+ for (let i = 0; i < candidatePrefix.length; i++) {
12
+ if (candidatePrefix[i] !== targetPath[i]) return false;
13
+ }
14
+ return true;
15
+ };
16
+ const compressPaths = (paths) => {
17
+ const result = [];
18
+ let prev = null;
19
+ for (let i = paths.length - 1; i >= 0; i--) {
20
+ const current = paths[i];
21
+ if (!prev || !isPrefixPath(current, prev)) result.push(current);
22
+ prev = current;
23
+ }
24
+ return result;
25
+ };
26
+ const useStoreStateProxy = (storeState) => {
27
+ const usedPathsRef = react.useRef([]);
28
+ usedPathsRef.current = [];
29
+ const trackedState = react.useMemo(() => {
30
+ const track = (path) => usedPathsRef.current.push(path);
31
+ const proxyCache = /* @__PURE__ */ new WeakMap();
32
+ const createDeepProxy = (target, path = []) => {
33
+ if (typeof target !== "object" || target === null) {
34
+ return target;
35
+ }
36
+ if (proxyCache.has(target)) {
37
+ return proxyCache.get(target);
38
+ }
39
+ const proxy = new Proxy(target, {
40
+ get(obj, key) {
41
+ const newPath = [...path, key];
42
+ track(newPath);
43
+ const value = obj[key];
44
+ return createDeepProxy(value, newPath);
45
+ }
46
+ });
47
+ proxyCache.set(target, proxy);
48
+ return proxy;
49
+ };
50
+ return createDeepProxy(storeState);
51
+ }, [storeState]);
52
+ return [trackedState, usedPathsRef];
53
+ };
54
+ const useStoreState = (storeState, subscribe) => {
55
+ const [trackedState, usedPathsRef] = useStoreStateProxy(storeState);
56
+ const [, reRender] = react.useState({});
57
+ useIsomorphicLayoutEffect(() => {
58
+ return subscribe((nextState, prevState, changedKeys) => {
59
+ const paths = compressPaths(usedPathsRef.current);
60
+ for (const path of paths) {
61
+ const rootKey = path[0];
62
+ if (!changedKeys.includes(rootKey)) continue;
63
+ const prevVal = getValueByPath(prevState, path);
64
+ const nextVal = getValueByPath(nextState, path);
65
+ if (!Object.is(prevVal, nextVal)) return reRender({});
66
+ }
67
+ });
68
+ }, [subscribe]);
69
+ return trackedState;
70
+ };
71
+
72
+ const createStore = (initialState, options) => {
73
+ const store = vanilla.initStore(initialState, options);
74
+ const useStore = () => useStoreState(store.getState(), store.subscribe);
75
+ return Object.assign(useStore, store);
76
+ };
77
+
78
+ const createStores = (initialState, options) => {
79
+ const stores = /* @__PURE__ */ new Map();
80
+ const getStore = (key = {}) => {
81
+ const keyHash = vanilla.getHash(key);
82
+ let store;
83
+ if (stores.has(keyHash)) {
84
+ store = stores.get(keyHash);
85
+ } else {
86
+ store = vanilla.initStore(initialState, options);
87
+ stores.set(keyHash, store);
88
+ }
89
+ const useStore = () => useStoreState(store.getState(), store.subscribe);
90
+ return Object.assign(useStore, {
91
+ ...store,
92
+ delete: () => {
93
+ if (store.getSubscribers().size > 0) {
94
+ console.warn(
95
+ "Cannot delete store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
96
+ );
97
+ return false;
98
+ }
99
+ store.setState(initialState);
100
+ return stores.delete(keyHash);
101
+ }
102
+ });
103
+ };
104
+ return getStore;
105
+ };
106
+
107
+ const INITIAL_STATE$1 = {
108
+ isPending: false,
109
+ isRevalidating: false,
110
+ isRetrying: false,
111
+ retryCount: 0,
112
+ state: "INITIAL",
113
+ isSuccess: false,
114
+ isError: false,
115
+ data: void 0,
116
+ dataUpdatedAt: void 0,
117
+ error: void 0,
118
+ errorUpdatedAt: void 0
119
+ };
120
+ const createQuery = (queryFn, options = {}) => {
121
+ const {
122
+ staleTime = 2500,
123
+ // 2.5 seconds,
124
+ gcTime = 5 * 60 * 1e3,
125
+ // 5 minutes
126
+ revalidateOnFocus = true,
127
+ revalidateOnReconnect = true,
128
+ onSuccess = vanilla.noop,
129
+ onError,
130
+ onSettled = vanilla.noop,
131
+ shouldRetry: shouldRetryFn = (_, s) => s.retryCount === 0 ? [true, 1500] : [false]
132
+ } = options;
133
+ const initialState = { ...INITIAL_STATE$1 };
134
+ const stores = /* @__PURE__ */ new Map();
135
+ const configureStoreEvents = () => ({
136
+ ...options,
137
+ onFirstSubscribe: (state, store) => {
138
+ var _a;
139
+ (_a = options.onFirstSubscribe) == null ? void 0 : _a.call(options, state, store);
140
+ const { metadata, revalidate: revalidate2 } = internals.get(store);
141
+ clearTimeout(metadata.garbageCollectionTimeoutId);
142
+ if (vanilla.isClient) {
143
+ if (revalidateOnFocus) {
144
+ focusListeners.add(revalidate2);
145
+ if (!focusListenersAdded) {
146
+ window.addEventListener("focus", onWindowFocus);
147
+ focusListenersAdded = true;
148
+ }
149
+ }
150
+ if (revalidateOnReconnect) {
151
+ onlineListeners.add(revalidate2);
152
+ if (!onlineListenersAdded) {
153
+ window.addEventListener("online", onWindowOnline);
154
+ onlineListenersAdded = true;
155
+ }
156
+ }
157
+ }
158
+ },
159
+ onLastUnsubscribe: (state, store) => {
160
+ var _a, _b;
161
+ (_a = options.onLastUnsubscribe) == null ? void 0 : _a.call(options, state, store);
162
+ const { metadata, revalidate: revalidate2 } = internals.get(store);
163
+ clearTimeout(metadata.retryTimeoutId);
164
+ (_b = metadata.retryResolver) == null ? void 0 : _b.call(metadata, state);
165
+ metadata.retryResolver = void 0;
166
+ metadata.garbageCollectionTimeoutId = setTimeout(() => {
167
+ store.setState(initialState);
168
+ }, gcTime);
169
+ if (vanilla.isClient) {
170
+ if (revalidateOnFocus) {
171
+ focusListeners.delete(revalidate2);
172
+ if (focusListeners.size === 0) {
173
+ window.removeEventListener("focus", onWindowFocus);
174
+ focusListenersAdded = false;
175
+ }
176
+ }
177
+ if (revalidateOnReconnect) {
178
+ onlineListeners.delete(revalidate2);
179
+ if (onlineListeners.size === 0) {
180
+ window.removeEventListener("online", onWindowOnline);
181
+ onlineListenersAdded = false;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ });
187
+ const internals = /* @__PURE__ */ new WeakMap();
188
+ const configureInternals = (store, variable, variableHash) => ({
189
+ metadata: {},
190
+ setInitialData: (data, revalidate2 = false) => {
191
+ const state = store.getState();
192
+ if (state.state === "INITIAL" && state.data === void 0) {
193
+ const { metadata } = internals.get(store);
194
+ if (revalidate2) metadata.isInvalidated = true;
195
+ store.setState({
196
+ state: "SUCCESS",
197
+ isSuccess: true,
198
+ data,
199
+ dataUpdatedAt: Date.now()
200
+ });
201
+ return true;
202
+ }
203
+ return false;
204
+ },
205
+ execute: ({ overwriteOngoingExecution = true } = {}) => {
206
+ return execute(store, variable, overwriteOngoingExecution);
207
+ },
208
+ revalidate: ({ overwriteOngoingExecution = true } = {}) => {
209
+ return revalidate(store, variable, overwriteOngoingExecution);
210
+ },
211
+ invalidate: (options2) => {
212
+ const { metadata } = internals.get(store);
213
+ metadata.isInvalidated = true;
214
+ if (store.getSubscribers().size > 0) {
215
+ internals.get(store).execute(options2);
216
+ return true;
217
+ }
218
+ return false;
219
+ },
220
+ reset: () => {
221
+ var _a, _b;
222
+ const { metadata } = internals.get(store);
223
+ clearTimeout(metadata.retryTimeoutId);
224
+ if (metadata.retryResolver || metadata.promiseResolver) {
225
+ console.debug(
226
+ "Ongoing query execution was ignored due to reset(). The result will not update the store state."
227
+ );
228
+ (_a = metadata.promiseResolver) == null ? void 0 : _a.call(metadata, initialState);
229
+ (_b = metadata.retryResolver) == null ? void 0 : _b.call(metadata, initialState);
230
+ metadata.promiseResolver = void 0;
231
+ metadata.retryResolver = void 0;
232
+ }
233
+ metadata.promise = void 0;
234
+ store.setState(initialState);
235
+ },
236
+ delete: () => {
237
+ if (store.getSubscribers().size > 0) {
238
+ console.warn(
239
+ "Cannot delete query store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
240
+ );
241
+ return false;
242
+ }
243
+ internals.get(store).reset();
244
+ return stores.delete(variableHash);
245
+ },
246
+ optimisticUpdate: (optimisticData) => {
247
+ const { metadata, revalidate: revalidate2, rollbackOptimisticUpdate } = internals.get(store);
248
+ metadata.rollbackData = store.getState().data;
249
+ store.setState({ data: optimisticData });
250
+ return { revalidate: revalidate2, rollback: rollbackOptimisticUpdate };
251
+ },
252
+ rollbackOptimisticUpdate: () => {
253
+ const { metadata } = internals.get(store);
254
+ store.setState({ data: metadata.rollbackData });
255
+ return metadata.rollbackData;
256
+ }
257
+ });
258
+ const execute = async (store, variable, overwriteOngoingExecution = false) => {
259
+ const { metadata } = internals.get(store);
260
+ if (!overwriteOngoingExecution && metadata.promise) return metadata.promise;
261
+ clearTimeout(metadata.retryTimeoutId);
262
+ const createPromise = () => {
263
+ const promise = new Promise((resolve) => {
264
+ metadata.promiseResolver = resolve;
265
+ const stateBeforeExecute = store.getState();
266
+ store.setState({
267
+ isPending: true,
268
+ isRevalidating: stateBeforeExecute.state === "SUCCESS",
269
+ isRetrying: !!metadata.retryResolver,
270
+ retryCount: metadata.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
271
+ });
272
+ queryFn(variable, stateBeforeExecute).then((data) => {
273
+ var _a;
274
+ if (data === void 0) {
275
+ console.error(
276
+ "Query function returned undefined. Successful responses must not be undefined."
277
+ );
278
+ }
279
+ if (!metadata.promiseResolver) return;
280
+ if (promise !== metadata.promise) return resolve(metadata.promise);
281
+ store.setState({
282
+ isPending: false,
283
+ isRevalidating: false,
284
+ isRetrying: false,
285
+ retryCount: 0,
286
+ state: "SUCCESS",
287
+ isSuccess: true,
288
+ isError: false,
289
+ data,
290
+ dataUpdatedAt: Date.now(),
291
+ error: void 0,
292
+ errorUpdatedAt: void 0
293
+ });
294
+ metadata.isInvalidated = false;
295
+ metadata.rollbackData = data;
296
+ resolve(store.getState());
297
+ (_a = metadata.retryResolver) == null ? void 0 : _a.call(metadata, store.getState());
298
+ metadata.retryResolver = void 0;
299
+ onSuccess(data, variable, stateBeforeExecute);
300
+ onSettled(variable, stateBeforeExecute);
301
+ }).catch((error) => {
302
+ var _a;
303
+ if (!metadata.promiseResolver && !metadata.retryResolver) return;
304
+ if (promise !== metadata.promise) return resolve(metadata.promise);
305
+ store.setState({
306
+ isPending: false,
307
+ isRevalidating: false,
308
+ isRetrying: false
309
+ });
310
+ const [shouldRetry, retryDelay] = shouldRetryFn(error, store.getState());
311
+ const hasSubscriber = store.getSubscribers().size > 0;
312
+ if (shouldRetry && hasSubscriber) {
313
+ metadata.retryResolver = resolve;
314
+ metadata.retryTimeoutId = setTimeout(createPromise, retryDelay);
315
+ } else {
316
+ store.setState({
317
+ isPending: false,
318
+ isRevalidating: false,
319
+ isRetrying: false,
320
+ retryCount: 0,
321
+ error,
322
+ errorUpdatedAt: Date.now(),
323
+ ...store.getState().data ? {
324
+ state: "SUCCESS_BUT_REVALIDATION_ERROR",
325
+ isError: false
326
+ } : {
327
+ state: "ERROR",
328
+ isError: true
329
+ }
330
+ });
331
+ const state = store.getState();
332
+ resolve(state);
333
+ (_a = metadata.retryResolver) == null ? void 0 : _a.call(metadata, state);
334
+ metadata.retryResolver = void 0;
335
+ if (onError) onError(error, variable, stateBeforeExecute);
336
+ else console.error(state);
337
+ onSettled(variable, stateBeforeExecute);
338
+ }
339
+ }).finally(() => {
340
+ if (metadata.promise === promise) {
341
+ metadata.promise = void 0;
342
+ metadata.promiseResolver = void 0;
343
+ }
344
+ });
345
+ });
346
+ metadata.promise = promise;
347
+ return promise;
348
+ };
349
+ return createPromise();
350
+ };
351
+ const revalidate = async (store, variable, overwriteOngoingExecution) => {
352
+ const { metadata } = internals.get(store);
353
+ if (!overwriteOngoingExecution && metadata.promise) return metadata.promise;
354
+ const state = store.getState();
355
+ if (state.dataUpdatedAt) {
356
+ const isFresh = state.dataUpdatedAt + staleTime > Date.now();
357
+ if (isFresh && !metadata.isInvalidated) return state;
358
+ }
359
+ return execute(store, variable, overwriteOngoingExecution);
360
+ };
361
+ const getStore = (variable = {}) => {
362
+ const variableHash = vanilla.getHash(variable);
363
+ let store;
364
+ if (stores.has(variableHash)) {
365
+ store = stores.get(variableHash);
366
+ } else {
367
+ store = vanilla.initStore(initialState, configureStoreEvents());
368
+ stores.set(variableHash, store);
369
+ internals.set(store, configureInternals(store, variable, variableHash));
370
+ }
371
+ const useStore = (options2 = {}) => {
372
+ const { revalidateOnMount = true, keepPreviousData } = options2;
373
+ const storeState = store.getState();
374
+ const prevState = react.useRef({});
375
+ let storeStateToBeUsed = storeState;
376
+ if (storeState.state !== "INITIAL") {
377
+ prevState.current = {
378
+ data: storeState.data,
379
+ dataUpdatedAt: storeState.dataUpdatedAt
380
+ };
381
+ } else if (keepPreviousData) {
382
+ storeStateToBeUsed = { ...storeState, ...prevState.current };
383
+ }
384
+ const [trackedState, usedPathsRef] = useStoreStateProxy(
385
+ revalidateOnMount && storeState.state === "INITIAL" ? (
386
+ // Optimize rendering on initial state
387
+ // Do { isPending: true } → result
388
+ // instead of { isPending: false } → { isPending: true } → result
389
+ { ...storeStateToBeUsed, isPending: true }
390
+ ) : storeStateToBeUsed
391
+ );
392
+ const [, reRender] = react.useState({});
393
+ useIsomorphicLayoutEffect(() => {
394
+ return store.subscribe((nextState, prevState2, changedKeys) => {
395
+ if (prevState2.state === "INITIAL" && !prevState2.isPending && nextState.isPending) {
396
+ return;
397
+ }
398
+ const paths = compressPaths(usedPathsRef.current);
399
+ for (const path of paths) {
400
+ const rootKey = path[0];
401
+ if (!changedKeys.includes(rootKey)) continue;
402
+ const prevVal = getValueByPath(prevState2, path);
403
+ const nextVal = getValueByPath(nextState, path);
404
+ if (!Object.is(prevVal, nextVal)) return reRender({});
405
+ }
406
+ });
407
+ }, [store]);
408
+ useIsomorphicLayoutEffect(() => {
409
+ if (revalidateOnMount !== false) revalidate(store, variable, false);
410
+ }, [store, revalidateOnMount]);
411
+ if (keepPreviousData) {
412
+ !!trackedState.error;
413
+ }
414
+ return trackedState;
415
+ };
416
+ return Object.assign(useStore, {
417
+ subscribe: store.subscribe,
418
+ getSubscribers: store.getSubscribers,
419
+ getState: store.getState,
420
+ setState: (value) => {
421
+ console.debug("Manual setState (not via provided actions) on query store");
422
+ store.setState(value);
423
+ },
424
+ ...internals.get(store)
425
+ });
426
+ };
427
+ return Object.assign(getStore, {
428
+ /**
429
+ * Executes all query instances.
430
+ *
431
+ * @remarks
432
+ * - Useful for bulk refetching.
433
+ */
434
+ executeAll: (options2) => {
435
+ stores.forEach((store) => internals.get(store).execute(options2));
436
+ },
437
+ /**
438
+ * Revalidates all query instances.
439
+ *
440
+ * @remarks
441
+ * - Only re-fetches stale queries.
442
+ */
443
+ revalidateAll: (options2) => {
444
+ stores.forEach((store) => internals.get(store).revalidate(options2));
445
+ },
446
+ /**
447
+ * Invalidates all query instances.
448
+ *
449
+ * @remarks
450
+ * - Marks all queries as invalidated and triggers revalidation if active.
451
+ * - Invalidated queries bypass `staleTime` until successfully executed again.
452
+ */
453
+ invalidateAll: (options2) => {
454
+ stores.forEach((store) => internals.get(store).invalidate(options2));
455
+ },
456
+ /**
457
+ * Resets all query instances.
458
+ */
459
+ resetAll: () => {
460
+ stores.forEach((store) => internals.get(store).reset());
461
+ }
462
+ });
463
+ };
464
+ let focusListenersAdded = false;
465
+ const focusListeners = /* @__PURE__ */ new Set();
466
+ const onWindowFocus = () => [...focusListeners].forEach((fn) => fn());
467
+ let onlineListenersAdded = false;
468
+ const onlineListeners = /* @__PURE__ */ new Set();
469
+ const onWindowOnline = () => [...onlineListeners].forEach((fn) => fn());
470
+
471
+ const INITIAL_STATE = {
472
+ state: "INITIAL",
473
+ isPending: false,
474
+ isSuccess: false,
475
+ isError: false,
476
+ variable: void 0,
477
+ data: void 0,
478
+ dataUpdatedAt: void 0,
479
+ error: void 0,
480
+ errorUpdatedAt: void 0
481
+ };
482
+ const createMutation = (mutationFn, options = {}) => {
483
+ const { onSuccess = vanilla.noop, onError, onSettled = vanilla.noop } = options;
484
+ const initialState = { ...INITIAL_STATE };
485
+ let ongoingPromise;
486
+ const resolveFns = /* @__PURE__ */ new Set([]);
487
+ const store = vanilla.initStore(initialState, options);
488
+ const useStore = () => useStoreState(store.getState(), store.subscribe);
489
+ const execute = (variable) => {
490
+ let currentResolveFn;
491
+ const stateBeforeExecute = store.getState();
492
+ if (stateBeforeExecute.isPending) {
493
+ console.warn(
494
+ "A mutation was executed while a previous execution is still pending. The previous execution will be ignored (latest execution wins)."
495
+ );
496
+ }
497
+ store.setState({ isPending: true });
498
+ const promise = new Promise((resolve) => {
499
+ currentResolveFn = resolve;
500
+ mutationFn(variable, stateBeforeExecute).then((data) => {
501
+ if (promise !== ongoingPromise) {
502
+ return resolve({ data, variable });
503
+ }
504
+ store.setState({
505
+ state: "SUCCESS",
506
+ isPending: false,
507
+ isSuccess: true,
508
+ isError: false,
509
+ variable,
510
+ data,
511
+ dataUpdatedAt: Date.now(),
512
+ error: void 0,
513
+ errorUpdatedAt: void 0
514
+ });
515
+ resolve({ data, variable });
516
+ resolveFns.clear();
517
+ onSuccess(data, variable, stateBeforeExecute);
518
+ }).catch((error) => {
519
+ if (promise !== ongoingPromise) {
520
+ return resolve({ error, variable });
521
+ }
522
+ store.setState({
523
+ state: "ERROR",
524
+ isPending: false,
525
+ isSuccess: false,
526
+ isError: true,
527
+ variable,
528
+ data: void 0,
529
+ dataUpdatedAt: void 0,
530
+ error,
531
+ errorUpdatedAt: Date.now()
532
+ });
533
+ resolve({ error, variable });
534
+ resolveFns.clear();
535
+ if (onError) onError(error, variable, stateBeforeExecute);
536
+ else console.error(store.getState());
537
+ }).finally(() => {
538
+ if (promise !== ongoingPromise) return;
539
+ onSettled(variable, stateBeforeExecute);
540
+ ongoingPromise = void 0;
541
+ });
542
+ });
543
+ if (ongoingPromise) resolveFns.forEach((resolveFn) => resolveFn(promise));
544
+ resolveFns.add(currentResolveFn);
545
+ ongoingPromise = promise;
546
+ return promise;
547
+ };
548
+ return Object.assign(useStore, {
549
+ subscribe: store.subscribe,
550
+ getSubscribers: store.getSubscribers,
551
+ getState: store.getState,
552
+ /**
553
+ * Manually updates the mutation state.
554
+ *
555
+ * @remarks
556
+ * - Intended for advanced use cases.
557
+ * - Prefer using provided mutation actions (`execute`, `reset`) instead.
558
+ */
559
+ setState: (value) => {
560
+ console.debug("Manual setState (not via provided actions) on mutation store");
561
+ store.setState(value);
562
+ },
563
+ /**
564
+ * Executes the mutation.
565
+ *
566
+ * @param variable - Input passed to the mutation function
567
+ *
568
+ * @returns A promise that always resolves with:
569
+ * - `{ data, variable }` on success
570
+ * - `{ error, variable }` on failure
571
+ *
572
+ * @remarks
573
+ * - The promise never rejects to simplify async handling.
574
+ * - If a mutation is already in progress, a warning is logged.
575
+ * - When a new execution starts, all previous pending executions will resolve with the result of the latest execution.
576
+ */
577
+ execute,
578
+ /**
579
+ * Resets the mutation state back to its initial state.
580
+ *
581
+ * @remarks
582
+ * - Does not cancel any ongoing execution.
583
+ * - If an execution is still pending, its result may override the reset state.
584
+ */
585
+ reset: () => {
586
+ if (store.getState().isPending) {
587
+ console.warn(
588
+ "Mutation state was reset while a request is still pending. The request will continue, but its result may override the reset state."
589
+ );
590
+ }
591
+ store.setState(initialState);
592
+ }
593
+ });
594
+ };
595
+
596
+ const useMutation = (mutationFn, options = {}) => {
597
+ const { onSuccess = vanilla.noop, onError, onSettled = vanilla.noop } = options;
598
+ const callbackRef = react.useRef({ onSuccess, onError, onSettled });
599
+ callbackRef.current.onSuccess = onSuccess;
600
+ callbackRef.current.onError = onError;
601
+ callbackRef.current.onSettled = onSettled;
602
+ const stateRef = react.useRef({ ...INITIAL_STATE });
603
+ const [, reRender] = react.useState({});
604
+ const refs = react.useRef({
605
+ mutationFn,
606
+ ongoingPromise: void 0,
607
+ resolveFns: /* @__PURE__ */ new Set()
608
+ });
609
+ refs.current.mutationFn = mutationFn;
610
+ const execute = react.useCallback((variable) => {
611
+ let currentResolveFn;
612
+ const stateBeforeExecute = stateRef.current;
613
+ if (stateBeforeExecute.isPending) {
614
+ console.warn(
615
+ "A mutation was executed while a previous execution is still pending. The previous execution will be ignored (latest execution wins)."
616
+ );
617
+ }
618
+ stateRef.current.isPending = true;
619
+ reRender({});
620
+ const promise = new Promise(
621
+ (resolve) => {
622
+ currentResolveFn = resolve;
623
+ refs.current.mutationFn(variable, stateBeforeExecute).then((data) => {
624
+ if (promise !== refs.current.ongoingPromise) {
625
+ return resolve({ data, variable });
626
+ }
627
+ stateRef.current = {
628
+ state: "SUCCESS",
629
+ isPending: false,
630
+ isSuccess: true,
631
+ isError: false,
632
+ variable,
633
+ data,
634
+ dataUpdatedAt: Date.now(),
635
+ error: void 0,
636
+ errorUpdatedAt: void 0
637
+ };
638
+ reRender({});
639
+ resolve({ data, variable });
640
+ refs.current.resolveFns.clear();
641
+ callbackRef.current.onSuccess(data, variable, stateBeforeExecute);
642
+ }).catch((error) => {
643
+ if (promise !== refs.current.ongoingPromise) {
644
+ return resolve({ error, variable });
645
+ }
646
+ stateRef.current = {
647
+ state: "ERROR",
648
+ isPending: false,
649
+ isSuccess: false,
650
+ isError: true,
651
+ variable,
652
+ data: void 0,
653
+ dataUpdatedAt: void 0,
654
+ error,
655
+ errorUpdatedAt: Date.now()
656
+ };
657
+ reRender({});
658
+ resolve({ error, variable });
659
+ refs.current.resolveFns.clear();
660
+ if (callbackRef.current.onError) {
661
+ callbackRef.current.onError(error, variable, stateBeforeExecute);
662
+ } else {
663
+ console.error(stateRef.current);
664
+ }
665
+ }).finally(() => {
666
+ if (promise !== refs.current.ongoingPromise) return;
667
+ callbackRef.current.onSettled(variable, stateBeforeExecute);
668
+ refs.current.ongoingPromise = void 0;
669
+ });
670
+ }
671
+ );
672
+ if (refs.current.ongoingPromise) {
673
+ refs.current.resolveFns.forEach((resolveFn) => resolveFn(promise));
674
+ }
675
+ refs.current.resolveFns.add(currentResolveFn);
676
+ refs.current.ongoingPromise = promise;
677
+ return promise;
678
+ }, []);
679
+ const reset = react.useCallback(() => {
680
+ if (stateRef.current.isPending) {
681
+ console.warn(
682
+ "Mutation state was reset while a request is still pending. The request will continue, but its result may override the reset state."
683
+ );
684
+ }
685
+ stateRef.current = { ...INITIAL_STATE };
686
+ reRender({});
687
+ }, []);
688
+ const r = [
689
+ stateRef.current,
690
+ {
691
+ execute,
692
+ reset,
693
+ getLatestState: () => stateRef.current
694
+ }
695
+ ];
696
+ return r;
697
+ };
698
+
699
+ exports.createMutation = createMutation;
700
+ exports.createQuery = createQuery;
701
+ exports.createStore = createStore;
702
+ exports.createStores = createStores;
703
+ exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
704
+ exports.useMutation = useMutation;
705
+ exports.useStoreState = useStoreState;
File without changes