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