floppy-disk 2.16.0 → 3.0.0-alpha.2
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 +0 -835
- package/esm/index.d.mts +1 -0
- package/esm/index.mjs +1 -0
- package/esm/react/create-mutation.d.mts +135 -0
- package/esm/react/create-query.d.mts +319 -0
- package/esm/react/create-store.d.mts +25 -0
- package/esm/react/create-stores.d.mts +32 -0
- package/esm/react/use-isomorphic-layout-effect.d.mts +6 -0
- package/esm/react/use-store.d.mts +18 -0
- package/esm/react.d.mts +6 -0
- package/esm/react.mjs +503 -0
- package/esm/vanilla/basic.d.mts +21 -0
- package/esm/vanilla/hash.d.mts +7 -0
- package/esm/vanilla/shallow.d.mts +6 -0
- package/esm/vanilla/store.d.mts +75 -0
- package/esm/vanilla.d.mts +4 -0
- package/esm/vanilla.mjs +109 -0
- package/index.d.ts +1 -0
- package/index.js +12 -0
- package/package.json +50 -45
- package/react/create-mutation.d.ts +135 -0
- package/react/create-query.d.ts +319 -0
- package/react/create-store.d.ts +25 -0
- package/react/create-stores.d.ts +32 -0
- package/react/use-isomorphic-layout-effect.d.ts +6 -0
- package/react/use-store.d.ts +18 -0
- package/{lib/index.d.ts → react.d.ts} +2 -4
- package/react.js +511 -0
- package/ts_version_4.5_and_above_is_required.d.ts +0 -0
- package/vanilla/basic.d.ts +21 -0
- package/vanilla/hash.d.ts +7 -0
- package/vanilla/shallow.d.ts +6 -0
- package/vanilla/store.d.ts +75 -0
- package/vanilla.d.ts +4 -0
- package/vanilla.js +118 -0
- package/esm/fetcher.d.ts +0 -27
- package/esm/fetcher.js +0 -95
- 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 -51
- package/esm/react/create-store.js +0 -38
- package/esm/react/create-stores.d.ts +0 -77
- package/esm/react/create-stores.js +0 -125
- package/esm/react/with-context.d.ts +0 -5
- package/esm/react/with-context.js +0 -14
- package/esm/store.d.ts +0 -24
- package/esm/store.js +0 -51
- package/esm/utils.d.ts +0 -24
- package/esm/utils.js +0 -31
- package/lib/fetcher.d.ts +0 -27
- package/lib/fetcher.js +0 -99
- 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 -51
- package/lib/react/create-store.js +0 -42
- package/lib/react/create-stores.d.ts +0 -77
- package/lib/react/create-stores.js +0 -130
- package/lib/react/with-context.d.ts +0 -5
- package/lib/react/with-context.js +0 -18
- package/lib/store.d.ts +0 -24
- package/lib/store.js +0 -55
- package/lib/utils.d.ts +0 -24
- package/lib/utils.js +0 -39
- package/utils/package.json +0 -6
package/react.js
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
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 useStoreUpdateNotifier = (store, selector) => {
|
|
9
|
+
const [, reRender] = react.useState({});
|
|
10
|
+
const selectorRef = react.useRef(selector);
|
|
11
|
+
selectorRef.current = selector;
|
|
12
|
+
useIsomorphicLayoutEffect(
|
|
13
|
+
() => store.subscribe((state, prevState) => {
|
|
14
|
+
if (selectorRef.current === vanilla.identity) return reRender({});
|
|
15
|
+
const prevSlice = selectorRef.current(prevState);
|
|
16
|
+
const nextSlice = selectorRef.current(state);
|
|
17
|
+
if (!vanilla.shallow(prevSlice, nextSlice)) reRender({});
|
|
18
|
+
}),
|
|
19
|
+
[store]
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
const useStoreState = (store, selector = vanilla.identity) => {
|
|
23
|
+
useStoreUpdateNotifier(store, selector);
|
|
24
|
+
return selector(store.getState());
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const createStore = (initialState, options) => {
|
|
28
|
+
const store = vanilla.initStore(initialState, options);
|
|
29
|
+
const useStore = (selector) => useStoreState(store, selector);
|
|
30
|
+
return Object.assign(useStore, store);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const createStores = (initialState, options) => {
|
|
34
|
+
const stores = /* @__PURE__ */ new Map();
|
|
35
|
+
const getStore = (key = {}) => {
|
|
36
|
+
const keyHash = vanilla.getHash(key);
|
|
37
|
+
let store;
|
|
38
|
+
if (stores.has(keyHash)) {
|
|
39
|
+
store = stores.get(keyHash);
|
|
40
|
+
} else {
|
|
41
|
+
store = vanilla.initStore(initialState, options);
|
|
42
|
+
stores.set(keyHash, store);
|
|
43
|
+
}
|
|
44
|
+
const useStore = (selector) => {
|
|
45
|
+
return useStoreState(store, selector);
|
|
46
|
+
};
|
|
47
|
+
return Object.assign(useStore, {
|
|
48
|
+
...store,
|
|
49
|
+
delete: () => {
|
|
50
|
+
if (store.getSubscribers().size > 0) {
|
|
51
|
+
console.warn(
|
|
52
|
+
"Cannot delete store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
|
|
53
|
+
);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
store.setState(initialState);
|
|
57
|
+
return stores.delete(keyHash);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
return getStore;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const INITIAL_STATE$1 = {
|
|
65
|
+
isPending: false,
|
|
66
|
+
isRevalidating: false,
|
|
67
|
+
isRetrying: false,
|
|
68
|
+
retryCount: 0,
|
|
69
|
+
state: "INITIAL",
|
|
70
|
+
isSuccess: false,
|
|
71
|
+
isError: false,
|
|
72
|
+
data: void 0,
|
|
73
|
+
dataUpdatedAt: void 0,
|
|
74
|
+
error: void 0,
|
|
75
|
+
errorUpdatedAt: void 0
|
|
76
|
+
};
|
|
77
|
+
const createQuery = (queryFn, options = {}) => {
|
|
78
|
+
const {
|
|
79
|
+
staleTime = 2500,
|
|
80
|
+
// 2.5 seconds,
|
|
81
|
+
gcTime = 5 * 60 * 1e3,
|
|
82
|
+
// 5 minutes
|
|
83
|
+
revalidateOnFocus = true,
|
|
84
|
+
revalidateOnReconnect = true,
|
|
85
|
+
onSuccess = vanilla.noop,
|
|
86
|
+
onError,
|
|
87
|
+
onSettled = vanilla.noop,
|
|
88
|
+
shouldRetry: shouldRetryFn = (_, s) => s.retryCount === 0 ? [true, 1500] : [false]
|
|
89
|
+
} = options;
|
|
90
|
+
const initialState = INITIAL_STATE$1;
|
|
91
|
+
const stores = /* @__PURE__ */ new Map();
|
|
92
|
+
const configureStoreEvents = () => ({
|
|
93
|
+
...options,
|
|
94
|
+
onFirstSubscribe: (state, store) => {
|
|
95
|
+
var _a;
|
|
96
|
+
(_a = options.onFirstSubscribe) == null ? void 0 : _a.call(options, state, store);
|
|
97
|
+
const { metadata, revalidate: revalidate2 } = internals.get(store);
|
|
98
|
+
clearTimeout(metadata.garbageCollectionTimeoutId);
|
|
99
|
+
if (vanilla.isClient) {
|
|
100
|
+
if (revalidateOnFocus) {
|
|
101
|
+
focusListeners.add(revalidate2);
|
|
102
|
+
if (!focusListenersAdded) {
|
|
103
|
+
window.addEventListener("focus", onWindowFocus);
|
|
104
|
+
focusListenersAdded = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (revalidateOnReconnect) {
|
|
108
|
+
onlineListeners.add(revalidate2);
|
|
109
|
+
if (!onlineListenersAdded) {
|
|
110
|
+
window.addEventListener("online", onWindowOnline);
|
|
111
|
+
onlineListenersAdded = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
onLastUnsubscribe: (state, store) => {
|
|
117
|
+
var _a, _b;
|
|
118
|
+
(_a = options.onLastUnsubscribe) == null ? void 0 : _a.call(options, state, store);
|
|
119
|
+
const { metadata, revalidate: revalidate2 } = internals.get(store);
|
|
120
|
+
clearTimeout(metadata.retryTimeoutId);
|
|
121
|
+
(_b = metadata.retryResolver) == null ? void 0 : _b.call(metadata, state);
|
|
122
|
+
metadata.retryResolver = void 0;
|
|
123
|
+
metadata.garbageCollectionTimeoutId = setTimeout(() => {
|
|
124
|
+
store.setState(initialState);
|
|
125
|
+
}, gcTime);
|
|
126
|
+
if (vanilla.isClient) {
|
|
127
|
+
if (revalidateOnFocus) {
|
|
128
|
+
focusListeners.delete(revalidate2);
|
|
129
|
+
if (focusListeners.size === 0) {
|
|
130
|
+
window.removeEventListener("focus", onWindowFocus);
|
|
131
|
+
focusListenersAdded = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (revalidateOnReconnect) {
|
|
135
|
+
onlineListeners.delete(revalidate2);
|
|
136
|
+
if (onlineListeners.size === 0) {
|
|
137
|
+
window.removeEventListener("online", onWindowOnline);
|
|
138
|
+
onlineListenersAdded = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
const internals = /* @__PURE__ */ new WeakMap();
|
|
145
|
+
const configureInternals = (store, variable, variableHash) => ({
|
|
146
|
+
metadata: {},
|
|
147
|
+
setInitialData: (data, revalidate2 = false) => {
|
|
148
|
+
const state = store.getState();
|
|
149
|
+
if (state.state === "INITIAL" && state.data === void 0) {
|
|
150
|
+
const { metadata } = internals.get(store);
|
|
151
|
+
if (revalidate2) metadata.isInvalidated = true;
|
|
152
|
+
store.setState({
|
|
153
|
+
state: "SUCCESS",
|
|
154
|
+
isSuccess: true,
|
|
155
|
+
data,
|
|
156
|
+
dataUpdatedAt: Date.now()
|
|
157
|
+
});
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
},
|
|
162
|
+
execute: (overwriteOngoingExecution = false) => {
|
|
163
|
+
return execute(store, variable, overwriteOngoingExecution);
|
|
164
|
+
},
|
|
165
|
+
revalidate: (overwriteOngoingExecution = false) => {
|
|
166
|
+
return revalidate(store, variable, overwriteOngoingExecution);
|
|
167
|
+
},
|
|
168
|
+
invalidate: (overwriteOngoingExecution = false) => {
|
|
169
|
+
const { metadata } = internals.get(store);
|
|
170
|
+
metadata.isInvalidated = true;
|
|
171
|
+
if (store.getSubscribers().size > 0) {
|
|
172
|
+
internals.get(store).execute(overwriteOngoingExecution);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
},
|
|
177
|
+
reset: () => {
|
|
178
|
+
var _a, _b;
|
|
179
|
+
const { metadata } = internals.get(store);
|
|
180
|
+
clearTimeout(metadata.retryTimeoutId);
|
|
181
|
+
if (metadata.retryResolver || metadata.promiseResolver) {
|
|
182
|
+
console.debug(
|
|
183
|
+
"Ongoing query execution was ignored due to reset(). The result will not update the store state."
|
|
184
|
+
);
|
|
185
|
+
(_a = metadata.promiseResolver) == null ? void 0 : _a.call(metadata, initialState);
|
|
186
|
+
(_b = metadata.retryResolver) == null ? void 0 : _b.call(metadata, initialState);
|
|
187
|
+
metadata.promiseResolver = void 0;
|
|
188
|
+
metadata.retryResolver = void 0;
|
|
189
|
+
}
|
|
190
|
+
metadata.promise = void 0;
|
|
191
|
+
store.setState(initialState);
|
|
192
|
+
},
|
|
193
|
+
delete: () => {
|
|
194
|
+
if (store.getSubscribers().size > 0) {
|
|
195
|
+
console.warn(
|
|
196
|
+
"Cannot delete query store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
|
|
197
|
+
);
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
internals.get(store).reset();
|
|
201
|
+
return stores.delete(variableHash);
|
|
202
|
+
},
|
|
203
|
+
optimisticUpdate: (optimisticData) => {
|
|
204
|
+
const { metadata, revalidate: revalidate2, rollbackOptimisticUpdate } = internals.get(store);
|
|
205
|
+
metadata.rollbackData = store.getState().data;
|
|
206
|
+
store.setState({ data: optimisticData });
|
|
207
|
+
return { revalidate: revalidate2, rollback: rollbackOptimisticUpdate };
|
|
208
|
+
},
|
|
209
|
+
rollbackOptimisticUpdate: () => {
|
|
210
|
+
const { metadata } = internals.get(store);
|
|
211
|
+
store.setState({ data: metadata.rollbackData });
|
|
212
|
+
return metadata.rollbackData;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
const execute = async (store, variable, overwriteOngoingExecution = false) => {
|
|
216
|
+
const { metadata } = internals.get(store);
|
|
217
|
+
if (!overwriteOngoingExecution && metadata.promise) return metadata.promise;
|
|
218
|
+
clearTimeout(metadata.retryTimeoutId);
|
|
219
|
+
const createPromise = () => {
|
|
220
|
+
const promise = new Promise((resolve) => {
|
|
221
|
+
metadata.promiseResolver = resolve;
|
|
222
|
+
const stateBeforeExecute = store.getState();
|
|
223
|
+
store.setState({
|
|
224
|
+
isPending: true,
|
|
225
|
+
isRevalidating: stateBeforeExecute.state === "SUCCESS",
|
|
226
|
+
isRetrying: !!metadata.retryResolver,
|
|
227
|
+
retryCount: metadata.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
|
|
228
|
+
});
|
|
229
|
+
queryFn(variable, stateBeforeExecute).then((data) => {
|
|
230
|
+
var _a;
|
|
231
|
+
if (data === void 0) {
|
|
232
|
+
console.error(
|
|
233
|
+
"Query function returned undefined. Successful responses must not be undefined."
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (!metadata.promiseResolver) return;
|
|
237
|
+
if (promise !== metadata.promise) return resolve(metadata.promise);
|
|
238
|
+
store.setState({
|
|
239
|
+
isPending: false,
|
|
240
|
+
isRevalidating: false,
|
|
241
|
+
isRetrying: false,
|
|
242
|
+
retryCount: 0,
|
|
243
|
+
state: "SUCCESS",
|
|
244
|
+
isSuccess: true,
|
|
245
|
+
isError: false,
|
|
246
|
+
data,
|
|
247
|
+
dataUpdatedAt: Date.now(),
|
|
248
|
+
error: void 0,
|
|
249
|
+
errorUpdatedAt: void 0
|
|
250
|
+
});
|
|
251
|
+
metadata.isInvalidated = false;
|
|
252
|
+
metadata.rollbackData = data;
|
|
253
|
+
resolve(store.getState());
|
|
254
|
+
(_a = metadata.retryResolver) == null ? void 0 : _a.call(metadata, store.getState());
|
|
255
|
+
metadata.retryResolver = void 0;
|
|
256
|
+
onSuccess(data, variable, stateBeforeExecute);
|
|
257
|
+
onSettled(variable, stateBeforeExecute);
|
|
258
|
+
}).catch((error) => {
|
|
259
|
+
var _a;
|
|
260
|
+
if (!metadata.promiseResolver && !metadata.retryResolver) return;
|
|
261
|
+
if (promise !== metadata.promise) return resolve(metadata.promise);
|
|
262
|
+
store.setState({
|
|
263
|
+
isPending: false,
|
|
264
|
+
isRevalidating: false,
|
|
265
|
+
isRetrying: false
|
|
266
|
+
});
|
|
267
|
+
const [shouldRetry, retryDelay] = shouldRetryFn(error, store.getState());
|
|
268
|
+
const hasSubscriber = store.getSubscribers().size > 0;
|
|
269
|
+
if (shouldRetry && hasSubscriber) {
|
|
270
|
+
metadata.retryResolver = resolve;
|
|
271
|
+
metadata.retryTimeoutId = setTimeout(createPromise, retryDelay);
|
|
272
|
+
} else {
|
|
273
|
+
store.setState({
|
|
274
|
+
isPending: false,
|
|
275
|
+
isRevalidating: false,
|
|
276
|
+
isRetrying: false,
|
|
277
|
+
retryCount: 0,
|
|
278
|
+
error,
|
|
279
|
+
errorUpdatedAt: Date.now(),
|
|
280
|
+
...store.getState().data ? {
|
|
281
|
+
state: "SUCCESS_BUT_REVALIDATION_ERROR",
|
|
282
|
+
isError: false
|
|
283
|
+
} : {
|
|
284
|
+
state: "ERROR",
|
|
285
|
+
isError: true
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
const state = store.getState();
|
|
289
|
+
resolve(state);
|
|
290
|
+
(_a = metadata.retryResolver) == null ? void 0 : _a.call(metadata, state);
|
|
291
|
+
metadata.retryResolver = void 0;
|
|
292
|
+
if (onError) onError(error, variable, stateBeforeExecute);
|
|
293
|
+
else console.error(state);
|
|
294
|
+
onSettled(variable, stateBeforeExecute);
|
|
295
|
+
}
|
|
296
|
+
}).finally(() => {
|
|
297
|
+
if (metadata.promise === promise) {
|
|
298
|
+
metadata.promise = void 0;
|
|
299
|
+
metadata.promiseResolver = void 0;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
metadata.promise = promise;
|
|
304
|
+
return promise;
|
|
305
|
+
};
|
|
306
|
+
return createPromise();
|
|
307
|
+
};
|
|
308
|
+
const revalidate = async (store, variable, overwriteOngoingExecution) => {
|
|
309
|
+
const { metadata } = internals.get(store);
|
|
310
|
+
if (!overwriteOngoingExecution && metadata.promise) return metadata.promise;
|
|
311
|
+
const state = store.getState();
|
|
312
|
+
if (state.dataUpdatedAt) {
|
|
313
|
+
const isFresh = state.dataUpdatedAt + staleTime > Date.now();
|
|
314
|
+
if (isFresh && !metadata.isInvalidated) return state;
|
|
315
|
+
}
|
|
316
|
+
return execute(store, variable, overwriteOngoingExecution);
|
|
317
|
+
};
|
|
318
|
+
const getStore = (variable = {}) => {
|
|
319
|
+
const variableHash = vanilla.getHash(variable);
|
|
320
|
+
let store;
|
|
321
|
+
if (stores.has(variableHash)) {
|
|
322
|
+
store = stores.get(variableHash);
|
|
323
|
+
} else {
|
|
324
|
+
store = vanilla.initStore(initialState, configureStoreEvents());
|
|
325
|
+
stores.set(variableHash, store);
|
|
326
|
+
internals.set(store, configureInternals(store, variable, variableHash));
|
|
327
|
+
}
|
|
328
|
+
const useStore = (options2 = {}, selector = vanilla.identity) => {
|
|
329
|
+
useStoreUpdateNotifier(store, selector);
|
|
330
|
+
useIsomorphicLayoutEffect(() => {
|
|
331
|
+
if (options2.enabled !== false) revalidate(store, variable);
|
|
332
|
+
}, [store, options2.enabled]);
|
|
333
|
+
const storeState = store.getState();
|
|
334
|
+
let storeStateToBeUsed = storeState;
|
|
335
|
+
const prevState = react.useRef({});
|
|
336
|
+
if (storeState.isSuccess) {
|
|
337
|
+
prevState.current = { data: storeState.data, dataUpdatedAt: storeState.dataUpdatedAt };
|
|
338
|
+
} else if (storeState.state === "INITIAL" && options2.keepPreviousData) {
|
|
339
|
+
storeStateToBeUsed = { ...storeState, ...prevState.current };
|
|
340
|
+
}
|
|
341
|
+
return selector(storeStateToBeUsed);
|
|
342
|
+
};
|
|
343
|
+
return Object.assign(useStore, {
|
|
344
|
+
subscribe: store.subscribe,
|
|
345
|
+
getSubscribers: store.getSubscribers,
|
|
346
|
+
getState: store.getState,
|
|
347
|
+
setState: (value) => {
|
|
348
|
+
console.debug("Manual setState (not via provided actions) on query store");
|
|
349
|
+
store.setState(value);
|
|
350
|
+
},
|
|
351
|
+
...internals.get(store)
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
return Object.assign(getStore, {
|
|
355
|
+
/**
|
|
356
|
+
* Executes all query instances.
|
|
357
|
+
*
|
|
358
|
+
* @remarks
|
|
359
|
+
* - Useful for bulk refetching.
|
|
360
|
+
*/
|
|
361
|
+
executeAll: (overwriteOngoingExecution) => {
|
|
362
|
+
stores.forEach((store) => internals.get(store).execute(overwriteOngoingExecution));
|
|
363
|
+
},
|
|
364
|
+
/**
|
|
365
|
+
* Revalidates all query instances.
|
|
366
|
+
*
|
|
367
|
+
* @remarks
|
|
368
|
+
* - Only re-fetches stale queries.
|
|
369
|
+
*/
|
|
370
|
+
revalidateAll: (overwriteOngoingExecution) => {
|
|
371
|
+
stores.forEach((store) => internals.get(store).revalidate(overwriteOngoingExecution));
|
|
372
|
+
},
|
|
373
|
+
/**
|
|
374
|
+
* Invalidates all query instances.
|
|
375
|
+
*
|
|
376
|
+
* @remarks
|
|
377
|
+
* - Marks all queries as invalidated and triggers revalidation if active.
|
|
378
|
+
* - Invalidated queries bypass `staleTime` until successfully executed again.
|
|
379
|
+
*/
|
|
380
|
+
invalidateAll: (overwriteOngoingExecution) => {
|
|
381
|
+
stores.forEach((store) => internals.get(store).invalidate(overwriteOngoingExecution));
|
|
382
|
+
},
|
|
383
|
+
/**
|
|
384
|
+
* Resets all query instances.
|
|
385
|
+
*/
|
|
386
|
+
resetAll: () => {
|
|
387
|
+
stores.forEach((store) => internals.get(store).reset());
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
let focusListenersAdded = false;
|
|
392
|
+
const focusListeners = /* @__PURE__ */ new Set();
|
|
393
|
+
const onWindowFocus = () => [...focusListeners].forEach((fn) => fn());
|
|
394
|
+
let onlineListenersAdded = false;
|
|
395
|
+
const onlineListeners = /* @__PURE__ */ new Set();
|
|
396
|
+
const onWindowOnline = () => [...onlineListeners].forEach((fn) => fn());
|
|
397
|
+
|
|
398
|
+
const INITIAL_STATE = {
|
|
399
|
+
state: "INITIAL",
|
|
400
|
+
isPending: false,
|
|
401
|
+
isSuccess: false,
|
|
402
|
+
isError: false,
|
|
403
|
+
variable: void 0,
|
|
404
|
+
data: void 0,
|
|
405
|
+
dataUpdatedAt: void 0,
|
|
406
|
+
error: void 0,
|
|
407
|
+
errorUpdatedAt: void 0
|
|
408
|
+
};
|
|
409
|
+
const createMutation = (mutationFn, options = {}) => {
|
|
410
|
+
const { onSuccess = vanilla.noop, onError, onSettled = vanilla.noop } = options;
|
|
411
|
+
const initialState = INITIAL_STATE;
|
|
412
|
+
const store = vanilla.initStore(initialState, options);
|
|
413
|
+
const useStore = (selector) => useStoreState(store, selector);
|
|
414
|
+
const execute = (variable) => {
|
|
415
|
+
const stateBeforeExecute = store.getState();
|
|
416
|
+
if (stateBeforeExecute.isPending) {
|
|
417
|
+
console.warn(
|
|
418
|
+
"Mutation executed while a previous execution is still pending. This may cause race conditions or unexpected state updates."
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
store.setState({ isPending: true });
|
|
422
|
+
return new Promise((resolve) => {
|
|
423
|
+
mutationFn(variable, stateBeforeExecute).then((data) => {
|
|
424
|
+
store.setState({
|
|
425
|
+
state: "SUCCESS",
|
|
426
|
+
isPending: false,
|
|
427
|
+
isSuccess: true,
|
|
428
|
+
isError: false,
|
|
429
|
+
variable,
|
|
430
|
+
data,
|
|
431
|
+
dataUpdatedAt: Date.now(),
|
|
432
|
+
error: void 0,
|
|
433
|
+
errorUpdatedAt: void 0
|
|
434
|
+
});
|
|
435
|
+
resolve({ data, variable });
|
|
436
|
+
onSuccess(data, variable, stateBeforeExecute);
|
|
437
|
+
}).catch((error) => {
|
|
438
|
+
store.setState({
|
|
439
|
+
state: "ERROR",
|
|
440
|
+
isPending: false,
|
|
441
|
+
isSuccess: false,
|
|
442
|
+
isError: true,
|
|
443
|
+
variable,
|
|
444
|
+
data: void 0,
|
|
445
|
+
dataUpdatedAt: void 0,
|
|
446
|
+
error,
|
|
447
|
+
errorUpdatedAt: Date.now()
|
|
448
|
+
});
|
|
449
|
+
resolve({ error, variable });
|
|
450
|
+
if (onError) onError(error, variable, stateBeforeExecute);
|
|
451
|
+
else console.error(store.getState());
|
|
452
|
+
}).finally(() => {
|
|
453
|
+
onSettled(variable, stateBeforeExecute);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
return Object.assign(useStore, {
|
|
458
|
+
subscribe: store.subscribe,
|
|
459
|
+
getSubscribers: store.getSubscribers,
|
|
460
|
+
getState: store.getState,
|
|
461
|
+
/**
|
|
462
|
+
* Manually updates the mutation state.
|
|
463
|
+
*
|
|
464
|
+
* @remarks
|
|
465
|
+
* - Intended for advanced use cases.
|
|
466
|
+
* - Prefer using provided mutation actions (`execute`, `reset`) instead.
|
|
467
|
+
*/
|
|
468
|
+
setState: (value) => {
|
|
469
|
+
console.debug("Manual setState (not via provided actions) on mutation store");
|
|
470
|
+
store.setState(value);
|
|
471
|
+
},
|
|
472
|
+
/**
|
|
473
|
+
* Executes the mutation.
|
|
474
|
+
*
|
|
475
|
+
* @param variable - Input passed to the mutation function
|
|
476
|
+
*
|
|
477
|
+
* @returns A promise that always resolves with:
|
|
478
|
+
* - `{ data, variable }` on success
|
|
479
|
+
* - `{ error, variable }` on failure
|
|
480
|
+
*
|
|
481
|
+
* @remarks
|
|
482
|
+
* - If a mutation is already in progress, a warning is logged.
|
|
483
|
+
* - Concurrent executions are allowed but may lead to race conditions.
|
|
484
|
+
* - The promise never rejects to simplify async handling.
|
|
485
|
+
*/
|
|
486
|
+
execute,
|
|
487
|
+
/**
|
|
488
|
+
* Resets the mutation state back to its initial state.
|
|
489
|
+
*
|
|
490
|
+
* @remarks
|
|
491
|
+
* - Does not cancel any ongoing request.
|
|
492
|
+
* - If a request is still pending, its result may override the reset state.
|
|
493
|
+
*/
|
|
494
|
+
reset: () => {
|
|
495
|
+
if (store.getState().isPending) {
|
|
496
|
+
console.warn(
|
|
497
|
+
"Mutation state was reset while a request is still pending. The request will continue, but its result may override the reset state."
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
store.setState(initialState);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
exports.createMutation = createMutation;
|
|
506
|
+
exports.createQuery = createQuery;
|
|
507
|
+
exports.createStore = createStore;
|
|
508
|
+
exports.createStores = createStores;
|
|
509
|
+
exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
|
|
510
|
+
exports.useStoreState = useStoreState;
|
|
511
|
+
exports.useStoreUpdateNotifier = useStoreUpdateNotifier;
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if this runs on browser.
|
|
3
|
+
*/
|
|
4
|
+
export declare const isClient: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Empty function.
|
|
7
|
+
*/
|
|
8
|
+
export declare const noop: () => void;
|
|
9
|
+
/**
|
|
10
|
+
* Identity function.
|
|
11
|
+
*
|
|
12
|
+
* It accepts 1 argument, and simply return it.
|
|
13
|
+
*
|
|
14
|
+
* `const identity = value => value`
|
|
15
|
+
*/
|
|
16
|
+
export declare const identity: <T>(value: T) => T;
|
|
17
|
+
/**
|
|
18
|
+
* If the value is a function, it will invoke the function.\
|
|
19
|
+
* If the value is not a function, it will just return it.
|
|
20
|
+
*/
|
|
21
|
+
export declare const getValue: <T, P extends any[]>(valueOrComputeValueFn: T | ((...params: P) => T), ...params: P) => T;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const isPlainObject: (value: any) => boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Get stable hash string from any value.
|
|
4
|
+
*
|
|
5
|
+
* Reference: https://github.com/TanStack/query/blob/v5.90.3/packages/query-core/src/utils.ts#L216
|
|
6
|
+
*/
|
|
7
|
+
export declare const getHash: (value?: any) => string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a partial state update.
|
|
3
|
+
*
|
|
4
|
+
* Can be either:
|
|
5
|
+
* - A partial object to merge into the current state
|
|
6
|
+
* - A function that receives the current state and returns a partial update
|
|
7
|
+
*/
|
|
8
|
+
export type SetState<TState> = Partial<TState> | ((state: TState) => Partial<TState>);
|
|
9
|
+
/**
|
|
10
|
+
* A subscriber function that is called whenever the state updates.
|
|
11
|
+
*
|
|
12
|
+
* @param state - The latest state
|
|
13
|
+
* @param prevState - The previous state before the update
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* - Subscribers are only called when the state actually changes.
|
|
17
|
+
* - Change detection is performed per key using `Object.is`.
|
|
18
|
+
*/
|
|
19
|
+
export type Subscriber<TState> = (state: TState, prevState: TState) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Core store API for managing state.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* - The store performs **shallow change detection per key** before notifying subscribers.
|
|
25
|
+
* - Subscribers are only notified when at least one field changes.
|
|
26
|
+
* - Designed to be framework-agnostic (React bindings are built separately).
|
|
27
|
+
*/
|
|
28
|
+
export type StoreApi<TState extends Record<string, any>> = {
|
|
29
|
+
setState: (value: SetState<TState>) => void;
|
|
30
|
+
getState: () => TState;
|
|
31
|
+
subscribe: (subscriber: Subscriber<TState>) => () => void;
|
|
32
|
+
getSubscribers: () => Set<Subscriber<TState>>;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Lifecycle hooks for the store.
|
|
36
|
+
*
|
|
37
|
+
* These hooks allow you to attach side effects based on subscription lifecycle.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* Useful for:
|
|
41
|
+
* - Lazy initialization (e.g. start fetching on first subscribe)
|
|
42
|
+
* - Cleanup (e.g. cancel timers, disconnect sockets)
|
|
43
|
+
* - Resource management (e.g. garbage collection)
|
|
44
|
+
*/
|
|
45
|
+
export type InitStoreOptions<TState extends Record<string, any>> = {
|
|
46
|
+
onFirstSubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
47
|
+
onSubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
48
|
+
onUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
49
|
+
onLastUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Creates a vanilla store with pub-sub capabilities.
|
|
53
|
+
*
|
|
54
|
+
* The store state is expected to be an **object**.\
|
|
55
|
+
* Updates are applied as partial merges, so non-object states are not supported.
|
|
56
|
+
*
|
|
57
|
+
* @param initialState - The initial state of the store
|
|
58
|
+
* @param options - Optional lifecycle hooks
|
|
59
|
+
*
|
|
60
|
+
* @returns A store API for managing state and subscriptions
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* - State updates are **shallowly compared per key** before notifying subscribers.
|
|
64
|
+
* - Subscribers are only notified when at least one updated field changes (using `Object.is` comparison).
|
|
65
|
+
* - Subscribers receive both the new state and the previous state.
|
|
66
|
+
* - Lifecycle hooks allow side-effect management tied to subscription count.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const store = initStore({ count: 0 });
|
|
70
|
+
*
|
|
71
|
+
* store.subscribe((state) => console.log(state.count));
|
|
72
|
+
* store.setState({ count: 1 }); // triggers subscriber
|
|
73
|
+
* store.setState({ count: 1 }); // no-op (no change)
|
|
74
|
+
*/
|
|
75
|
+
export declare const initStore: <TState extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => StoreApi<TState>;
|
package/vanilla.d.ts
ADDED