floppy-disk 3.6.1 → 3.7.0-beta.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.
- package/esm/react/create-mutation.d.mts +1 -11
- package/esm/react/create-query.d.mts +153 -18
- package/esm/react/create-stores.d.mts +32 -14
- package/esm/react.mjs +77 -74
- package/package.json +1 -1
- package/react/create-mutation.d.ts +1 -11
- package/react/create-query.d.ts +153 -18
- package/react/create-stores.d.ts +32 -14
- package/react.js +77 -74
|
@@ -44,17 +44,7 @@ export type MutationState<TData, TVariable, TError> = {
|
|
|
44
44
|
error: TError;
|
|
45
45
|
errorUpdatedAt: number;
|
|
46
46
|
});
|
|
47
|
-
export declare const INITIAL_STATE:
|
|
48
|
-
state: string;
|
|
49
|
-
isPending: boolean;
|
|
50
|
-
isSuccess: boolean;
|
|
51
|
-
isError: boolean;
|
|
52
|
-
variable: undefined;
|
|
53
|
-
data: undefined;
|
|
54
|
-
dataUpdatedAt: undefined;
|
|
55
|
-
error: undefined;
|
|
56
|
-
errorUpdatedAt: undefined;
|
|
57
|
-
};
|
|
47
|
+
export declare const INITIAL_STATE: MutationState<any, any, any>;
|
|
58
48
|
/**
|
|
59
49
|
* Configuration options for a mutation.
|
|
60
50
|
*
|
|
@@ -63,15 +63,149 @@ export type QueryState<TData, TError> = {
|
|
|
63
63
|
error: TError;
|
|
64
64
|
errorUpdatedAt: number;
|
|
65
65
|
});
|
|
66
|
+
type Internal<TData, TError> = {
|
|
67
|
+
isInvalidated?: boolean;
|
|
68
|
+
promise?: Promise<QueryState<TData, TError>> | undefined;
|
|
69
|
+
promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
70
|
+
retryTimeoutId?: number;
|
|
71
|
+
retryResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
72
|
+
garbageCollectionTimeoutId?: number;
|
|
73
|
+
rollbackData?: TData | undefined;
|
|
74
|
+
};
|
|
75
|
+
type AdditionalStoreApi<TData, TError> = {
|
|
76
|
+
/**
|
|
77
|
+
* A deterministic hash string derived from the query variable.
|
|
78
|
+
*
|
|
79
|
+
* Used as the unique identifier for this query instance in the internal cache.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* - Structurally identical variables will produce the same hash.
|
|
83
|
+
*/
|
|
84
|
+
variableHash: string;
|
|
85
|
+
/**
|
|
86
|
+
* Sets initial data for the query if it has not been initialized.
|
|
87
|
+
*
|
|
88
|
+
* @param data - Initial data
|
|
89
|
+
* @param revalidate - Whether to mark the data as invalidated (will trigger revalidation)
|
|
90
|
+
*
|
|
91
|
+
* @returns `true` if the data was set, `false` otherwise
|
|
92
|
+
*
|
|
93
|
+
* @remarks
|
|
94
|
+
* - Only applies when the query is in the `INITIAL` state.
|
|
95
|
+
* - Useful for hydration or preloaded data.
|
|
96
|
+
*/
|
|
97
|
+
setInitialData: (data: TData, revalidate?: boolean) => boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Executes the query function.
|
|
100
|
+
*
|
|
101
|
+
* @param options - Execution options
|
|
102
|
+
* @param options.overwriteOngoingExecution - Whether to start a new execution instead of reusing an ongoing one (default: `true`)
|
|
103
|
+
*
|
|
104
|
+
* @returns A promise resolving to the latest query state
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* - By default, each call **starts a new execution** even if one is already in progress.
|
|
108
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
109
|
+
* - Handles:
|
|
110
|
+
* - Pending state
|
|
111
|
+
* - Success state
|
|
112
|
+
* - Error state
|
|
113
|
+
* - Retry logic
|
|
114
|
+
*/
|
|
115
|
+
execute: (options?: {
|
|
116
|
+
overwriteOngoingExecution?: boolean;
|
|
117
|
+
}) => Promise<QueryState<TData, TError>>;
|
|
118
|
+
/**
|
|
119
|
+
* Re-executes the query if needed based on freshness or invalidation.
|
|
120
|
+
*
|
|
121
|
+
* @param options - Revalidation options
|
|
122
|
+
* @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
|
|
123
|
+
*
|
|
124
|
+
* @returns The current state if still fresh, otherwise a promise of the new state
|
|
125
|
+
*
|
|
126
|
+
* @remarks
|
|
127
|
+
* - Skips execution if data is still fresh (`staleTime`) **AND** the query has not been invalidated.
|
|
128
|
+
* - If execution is not skipped, by default it will start a new execution even if one is already in progress.
|
|
129
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
130
|
+
*/
|
|
131
|
+
revalidate: (options?: {
|
|
132
|
+
overwriteOngoingExecution?: boolean;
|
|
133
|
+
}) => Promise<QueryState<TData, TError>>;
|
|
134
|
+
/**
|
|
135
|
+
* Marks the query as invalidated and optionally triggers re-execution.
|
|
136
|
+
*
|
|
137
|
+
* @param options - Invalidation options
|
|
138
|
+
* @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
|
|
139
|
+
*
|
|
140
|
+
* @returns `true` if execution was triggered, `false` otherwise
|
|
141
|
+
*
|
|
142
|
+
* @remarks
|
|
143
|
+
* - Invalidated queries are treated as stale regardless of `staleTime`.
|
|
144
|
+
* - The next `revalidate` will always execute until a successful result clears the invalidation.
|
|
145
|
+
* - If there are active subscribers: Execution is triggered immediately.
|
|
146
|
+
* - Otherwise: The query remains invalidated and will execute on the next revalidation.
|
|
147
|
+
* - By default, starts a new execution even if one is already in progress.
|
|
148
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
149
|
+
*/
|
|
150
|
+
invalidate: (options?: {
|
|
151
|
+
overwriteOngoingExecution?: boolean;
|
|
152
|
+
}) => boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Resets the query state to its initial state.
|
|
155
|
+
*
|
|
156
|
+
* @remarks
|
|
157
|
+
* - Cancels retry logic and ignores any ongoing execution results.
|
|
158
|
+
*/
|
|
159
|
+
reset: () => void;
|
|
160
|
+
/**
|
|
161
|
+
* Deletes the query store for the current variable.
|
|
162
|
+
*
|
|
163
|
+
* @returns `true` if deleted, `false` otherwise
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* - Cannot delete while there are active subscribers.
|
|
167
|
+
*/
|
|
168
|
+
delete: () => boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Performs an optimistic update on the query data.
|
|
171
|
+
*
|
|
172
|
+
* @param data - Optimistic data to set
|
|
173
|
+
*
|
|
174
|
+
* @returns Controls for managing the optimistic update
|
|
175
|
+
*
|
|
176
|
+
* @remarks
|
|
177
|
+
* - Temporarily replaces the current data.
|
|
178
|
+
* - Stores previous data for rollback.
|
|
179
|
+
* - Commonly used with mutations for instant UI updates.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const { rollback, revalidate } = query.optimisticUpdate(newData);
|
|
183
|
+
*/
|
|
184
|
+
optimisticUpdate: (data: TData) => {
|
|
185
|
+
revalidate: () => Promise<QueryState<TData, TError>>;
|
|
186
|
+
rollback: () => TData;
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Restores the data before the last optimistic update.
|
|
190
|
+
*
|
|
191
|
+
* @returns The restored data
|
|
192
|
+
*
|
|
193
|
+
* @remarks
|
|
194
|
+
* - Should be used if an optimistic update fails.
|
|
195
|
+
*/
|
|
196
|
+
rollbackOptimisticUpdate: () => TData;
|
|
197
|
+
/**
|
|
198
|
+
* Internal data, do not mutate!
|
|
199
|
+
*/
|
|
200
|
+
internal: Readonly<Internal<TData, TError>>;
|
|
201
|
+
};
|
|
66
202
|
/**
|
|
67
203
|
* Configuration options for a query.
|
|
68
204
|
*
|
|
69
205
|
* @remarks
|
|
70
206
|
* Controls caching, retry behavior, lifecycle, and side effects of an async operation.
|
|
71
207
|
*/
|
|
72
|
-
export type QueryOptions<TData, TVariable extends StoreKey, TError = Error> = InitStoreOptions<QueryState<TData, TError>, {
|
|
73
|
-
variableHash: string;
|
|
74
|
-
}> & {
|
|
208
|
+
export type QueryOptions<TData, TVariable extends StoreKey, TError = Error> = InitStoreOptions<QueryState<TData, TError>, AdditionalStoreApi<TData, TError>> & {
|
|
75
209
|
/**
|
|
76
210
|
* Time (in milliseconds) that data is considered fresh.
|
|
77
211
|
*
|
|
@@ -219,19 +353,19 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
|
|
|
219
353
|
initialData?: never;
|
|
220
354
|
initialDataIsStale?: never;
|
|
221
355
|
})) => QueryState<TData, TError>) & {
|
|
222
|
-
|
|
356
|
+
setState: (value: SetStateInput<QueryState<TData, TError>>) => void;
|
|
357
|
+
getState: () => QueryState<TData, TError>;
|
|
358
|
+
subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<QueryState<TData, TError>>) => () => void;
|
|
359
|
+
getSubscriberCount: () => number;
|
|
223
360
|
/**
|
|
224
|
-
*
|
|
361
|
+
* A deterministic hash string derived from the query variable.
|
|
362
|
+
*
|
|
363
|
+
* Used as the unique identifier for this query instance in the internal cache.
|
|
364
|
+
*
|
|
365
|
+
* @remarks
|
|
366
|
+
* - Structurally identical variables will produce the same hash.
|
|
225
367
|
*/
|
|
226
|
-
|
|
227
|
-
isInvalidated?: boolean;
|
|
228
|
-
promise?: Promise<QueryState<TData, TError>> | undefined;
|
|
229
|
-
promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
230
|
-
retryTimeoutId?: number;
|
|
231
|
-
retryResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
232
|
-
garbageCollectionTimeoutId?: number;
|
|
233
|
-
rollbackData?: TData | undefined;
|
|
234
|
-
};
|
|
368
|
+
variableHash: string;
|
|
235
369
|
/**
|
|
236
370
|
* Sets initial data for the query if it has not been initialized.
|
|
237
371
|
*
|
|
@@ -344,10 +478,10 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
|
|
|
344
478
|
* - Should be used if an optimistic update fails.
|
|
345
479
|
*/
|
|
346
480
|
rollbackOptimisticUpdate: () => TData;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
481
|
+
/**
|
|
482
|
+
* Internal data, do not mutate!
|
|
483
|
+
*/
|
|
484
|
+
internal: Readonly<Internal<TData, TError>>;
|
|
351
485
|
}) & {
|
|
352
486
|
/**
|
|
353
487
|
* Executes all query instances.
|
|
@@ -382,3 +516,4 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
|
|
|
382
516
|
*/
|
|
383
517
|
resetAll: () => void;
|
|
384
518
|
};
|
|
519
|
+
export {};
|
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import { type InitStoreOptions } from "../vanilla.mjs";
|
|
1
|
+
import { type InitStoreOptions, type StoreApi } from "../vanilla.mjs";
|
|
2
2
|
type GoodInputForHash = string | number | boolean | null | Date;
|
|
3
3
|
export type StoreKey = GoodInputForHash | {
|
|
4
4
|
[key: string | number]: StoreKey | StoreKey[];
|
|
5
5
|
};
|
|
6
|
+
type AdditionalStoreApi<TKey> = {
|
|
7
|
+
/**
|
|
8
|
+
* The original key used to identify this store instance.\
|
|
9
|
+
* This value is not hashed and is preserved as-is.
|
|
10
|
+
*/
|
|
11
|
+
key: TKey;
|
|
12
|
+
/**
|
|
13
|
+
* A deterministic hash string derived from {@link key}.
|
|
14
|
+
*
|
|
15
|
+
* Used internally as the unique identifier for caching and retrieving store instances.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* - Guarantees that structurally identical keys produce the same hash.
|
|
19
|
+
*/
|
|
20
|
+
keyHash: string;
|
|
21
|
+
/**
|
|
22
|
+
* Deletes this store instance from the internal cache.
|
|
23
|
+
*
|
|
24
|
+
* @returns `true` if the store was successfully deleted, otherwise `false`.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* - If there are active subscribers, the deletion is ignored and `false` is returned.
|
|
28
|
+
* - When deletion succeeds:
|
|
29
|
+
* - The store is removed from the cache.
|
|
30
|
+
* - Its state is reset to the initial state.
|
|
31
|
+
* - Intended for manual cleanup of unused or ephemeral stores.
|
|
32
|
+
*/
|
|
33
|
+
delete: () => boolean;
|
|
34
|
+
};
|
|
6
35
|
/**
|
|
7
36
|
* Creates a factory for multiple stores identified by a key.
|
|
8
37
|
*
|
|
@@ -36,23 +65,12 @@ export type StoreKey = GoodInputForHash | {
|
|
|
36
65
|
*
|
|
37
66
|
* @see https://floppy-disk.vercel.app/docs/stores
|
|
38
67
|
*/
|
|
39
|
-
export declare const createStores: <TState extends Record<string, any>, TKey extends StoreKey>(initialState: TState, options?: InitStoreOptions<TState, {
|
|
40
|
-
key: TKey;
|
|
41
|
-
keyHash: string;
|
|
42
|
-
}>) => (key?: TKey) => ((options?: {
|
|
68
|
+
export declare const createStores: <TState extends Record<string, any>, TKey extends StoreKey>(initialState: TState, options?: InitStoreOptions<TState, AdditionalStoreApi<TKey>>) => (key?: TKey) => ((options?: {
|
|
43
69
|
/**
|
|
44
70
|
* Initial state used on first render (and will also update the store state right after that)
|
|
45
71
|
*
|
|
46
72
|
* If provided, `initialState` will be applied **once per store instance**
|
|
47
73
|
*/
|
|
48
74
|
initialState?: Partial<TState>;
|
|
49
|
-
}) => TState) &
|
|
50
|
-
delete: () => boolean;
|
|
51
|
-
setState: (value: import("../vanilla.d.mts").SetStateInput<TState>) => void;
|
|
52
|
-
getState: () => TState;
|
|
53
|
-
subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<TState>) => () => void;
|
|
54
|
-
getSubscriberCount: () => number;
|
|
55
|
-
key: TKey;
|
|
56
|
-
keyHash: string;
|
|
57
|
-
};
|
|
75
|
+
}) => TState) & StoreApi<TState> & AdditionalStoreApi<TKey>;
|
|
58
76
|
export {};
|
package/esm/react.mjs
CHANGED
|
@@ -102,11 +102,7 @@ const createStores = (initialState, options) => {
|
|
|
102
102
|
store.key = key;
|
|
103
103
|
store.keyHash = keyHash;
|
|
104
104
|
stores.set(keyHash, store);
|
|
105
|
-
|
|
106
|
-
const useStore = (options2) => useStoreState(store, options2);
|
|
107
|
-
return Object.assign(useStore, {
|
|
108
|
-
...store,
|
|
109
|
-
delete: () => {
|
|
105
|
+
store.delete = () => {
|
|
110
106
|
if (store.getSubscriberCount() > 0) {
|
|
111
107
|
console.warn(
|
|
112
108
|
"Cannot delete store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
|
|
@@ -115,8 +111,10 @@ const createStores = (initialState, options) => {
|
|
|
115
111
|
}
|
|
116
112
|
store.setState(initialState);
|
|
117
113
|
return stores.delete(keyHash);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const useStore = (options2) => useStoreState(store, options2);
|
|
117
|
+
return Object.assign(useStore, store);
|
|
120
118
|
};
|
|
121
119
|
return getStore;
|
|
122
120
|
};
|
|
@@ -156,8 +154,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
156
154
|
onFirstSubscribe: (state, store) => {
|
|
157
155
|
var _a;
|
|
158
156
|
(_a = options.onFirstSubscribe) == null ? void 0 : _a.call(options, state, store);
|
|
159
|
-
const {
|
|
160
|
-
clearTimeout(
|
|
157
|
+
const { internal, revalidate: revalidate2 } = store;
|
|
158
|
+
clearTimeout(internal.garbageCollectionTimeoutId);
|
|
161
159
|
if (isClient) {
|
|
162
160
|
if (revalidateOnFocus) {
|
|
163
161
|
focusListeners.add(revalidate2);
|
|
@@ -178,15 +176,15 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
178
176
|
onLastUnsubscribe: (state, store) => {
|
|
179
177
|
var _a;
|
|
180
178
|
(_a = options.onLastUnsubscribe) == null ? void 0 : _a.call(options, state, store);
|
|
181
|
-
const {
|
|
182
|
-
clearTimeout(
|
|
183
|
-
if (
|
|
179
|
+
const { internal, revalidate: revalidate2 } = store;
|
|
180
|
+
clearTimeout(internal.retryTimeoutId);
|
|
181
|
+
if (internal.retryResolver) {
|
|
184
182
|
store.setState({ willRetryAt: void 0 });
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
internal.retryResolver(store.getState());
|
|
184
|
+
internal.retryResolver = void 0;
|
|
187
185
|
}
|
|
188
|
-
|
|
189
|
-
if (
|
|
186
|
+
internal.garbageCollectionTimeoutId = setTimeout(() => {
|
|
187
|
+
if (internal.promiseResolver || internal.retryResolver) {
|
|
190
188
|
store.setState(initialState);
|
|
191
189
|
} else {
|
|
192
190
|
stores.delete(variableHash);
|
|
@@ -210,14 +208,13 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
210
208
|
}
|
|
211
209
|
}
|
|
212
210
|
});
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
metadata: {},
|
|
211
|
+
const getApis = (store, variable) => ({
|
|
212
|
+
internal: {},
|
|
216
213
|
setInitialData: (data, revalidate2 = false) => {
|
|
217
214
|
const state = store.getState();
|
|
218
215
|
if (state.state === "INITIAL" && state.data === void 0) {
|
|
219
|
-
const {
|
|
220
|
-
if (revalidate2)
|
|
216
|
+
const { internal } = store;
|
|
217
|
+
if (revalidate2) internal.isInvalidated = true;
|
|
221
218
|
store.setState({
|
|
222
219
|
state: "SUCCESS",
|
|
223
220
|
isSuccess: true,
|
|
@@ -235,28 +232,28 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
235
232
|
return revalidate(store, variable, overwriteOngoingExecution);
|
|
236
233
|
},
|
|
237
234
|
invalidate: (options2) => {
|
|
238
|
-
const {
|
|
239
|
-
|
|
235
|
+
const { internal } = store;
|
|
236
|
+
internal.isInvalidated = true;
|
|
240
237
|
if (store.getSubscriberCount() > 0) {
|
|
241
|
-
|
|
238
|
+
store.execute(options2);
|
|
242
239
|
return true;
|
|
243
240
|
}
|
|
244
241
|
return false;
|
|
245
242
|
},
|
|
246
243
|
reset: () => {
|
|
247
244
|
var _a, _b;
|
|
248
|
-
const {
|
|
249
|
-
clearTimeout(
|
|
250
|
-
if (
|
|
245
|
+
const { internal } = store;
|
|
246
|
+
clearTimeout(internal.retryTimeoutId);
|
|
247
|
+
if (internal.retryResolver || internal.promiseResolver) {
|
|
251
248
|
console.debug(
|
|
252
249
|
"Ongoing query execution was ignored due to reset(). The result will not update the store state."
|
|
253
250
|
);
|
|
254
|
-
(_a =
|
|
255
|
-
(_b =
|
|
256
|
-
|
|
257
|
-
|
|
251
|
+
(_a = internal.promiseResolver) == null ? void 0 : _a.call(internal, initialState);
|
|
252
|
+
(_b = internal.retryResolver) == null ? void 0 : _b.call(internal, initialState);
|
|
253
|
+
internal.promiseResolver = void 0;
|
|
254
|
+
internal.retryResolver = void 0;
|
|
258
255
|
}
|
|
259
|
-
|
|
256
|
+
internal.promise = void 0;
|
|
260
257
|
store.setState(initialState);
|
|
261
258
|
},
|
|
262
259
|
delete: () => {
|
|
@@ -266,35 +263,36 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
266
263
|
);
|
|
267
264
|
return false;
|
|
268
265
|
}
|
|
269
|
-
|
|
266
|
+
store.reset();
|
|
270
267
|
return stores.delete(store.variableHash);
|
|
271
268
|
},
|
|
272
269
|
optimisticUpdate: (optimisticData) => {
|
|
273
|
-
const {
|
|
274
|
-
|
|
270
|
+
const { internal, revalidate: revalidate2, rollbackOptimisticUpdate } = store;
|
|
271
|
+
internal.rollbackData = store.getState().data;
|
|
275
272
|
store.setState({ data: optimisticData });
|
|
276
273
|
return { revalidate: revalidate2, rollback: rollbackOptimisticUpdate };
|
|
277
274
|
},
|
|
278
275
|
rollbackOptimisticUpdate: () => {
|
|
279
|
-
const {
|
|
280
|
-
store.setState({ data:
|
|
281
|
-
return
|
|
276
|
+
const { internal } = store;
|
|
277
|
+
store.setState({ data: internal.rollbackData });
|
|
278
|
+
return internal.rollbackData;
|
|
282
279
|
}
|
|
283
280
|
});
|
|
284
281
|
const execute = async (store, variable, overwriteOngoingExecution = false) => {
|
|
285
|
-
const {
|
|
286
|
-
|
|
287
|
-
|
|
282
|
+
const { internal: _internal } = store;
|
|
283
|
+
const internal = _internal;
|
|
284
|
+
if (!overwriteOngoingExecution && internal.promise) return internal.promise;
|
|
285
|
+
clearTimeout(internal.retryTimeoutId);
|
|
288
286
|
const createPromise = () => {
|
|
289
287
|
const promise = new Promise((resolve) => {
|
|
290
|
-
|
|
288
|
+
internal.promiseResolver = resolve;
|
|
291
289
|
const stateBeforeExecute = store.getState();
|
|
292
290
|
store.setState({
|
|
293
291
|
isPending: true,
|
|
294
292
|
isRevalidating: stateBeforeExecute.state === "SUCCESS",
|
|
295
293
|
willRetryAt: void 0,
|
|
296
|
-
isRetrying: !!
|
|
297
|
-
retryCount:
|
|
294
|
+
isRetrying: !!internal.retryResolver,
|
|
295
|
+
retryCount: internal.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
|
|
298
296
|
});
|
|
299
297
|
queryFn(variable, stateBeforeExecute, store.variableHash).then((data) => {
|
|
300
298
|
var _a;
|
|
@@ -303,8 +301,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
303
301
|
"Query function returned undefined. Successful responses must not be undefined."
|
|
304
302
|
);
|
|
305
303
|
}
|
|
306
|
-
if (!
|
|
307
|
-
if (promise !==
|
|
304
|
+
if (!internal.promiseResolver) return;
|
|
305
|
+
if (promise !== internal.promise) return resolve(internal.promise);
|
|
308
306
|
const now = Date.now();
|
|
309
307
|
store.setState({
|
|
310
308
|
isPending: false,
|
|
@@ -320,17 +318,17 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
320
318
|
error: void 0,
|
|
321
319
|
errorUpdatedAt: void 0
|
|
322
320
|
});
|
|
323
|
-
|
|
324
|
-
|
|
321
|
+
internal.isInvalidated = false;
|
|
322
|
+
internal.rollbackData = data;
|
|
325
323
|
resolve(store.getState());
|
|
326
|
-
(_a =
|
|
327
|
-
|
|
324
|
+
(_a = internal.retryResolver) == null ? void 0 : _a.call(internal, store.getState());
|
|
325
|
+
internal.retryResolver = void 0;
|
|
328
326
|
onSuccess(data, variable, stateBeforeExecute);
|
|
329
327
|
onSettled(variable, stateBeforeExecute);
|
|
330
328
|
}).catch((error) => {
|
|
331
329
|
var _a;
|
|
332
|
-
if (!
|
|
333
|
-
if (promise !==
|
|
330
|
+
if (!internal.promiseResolver && !internal.retryResolver) return;
|
|
331
|
+
if (promise !== internal.promise) return resolve(internal.promise);
|
|
334
332
|
const nextState = {
|
|
335
333
|
...store.getState(),
|
|
336
334
|
isPending: false,
|
|
@@ -340,8 +338,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
340
338
|
const [shouldRetry, retryDelay] = shouldRetryFn(error, nextState);
|
|
341
339
|
const hasSubscriber = store.getSubscriberCount() > 0;
|
|
342
340
|
if (shouldRetry && hasSubscriber) {
|
|
343
|
-
|
|
344
|
-
|
|
341
|
+
internal.retryResolver = resolve;
|
|
342
|
+
internal.retryTimeoutId = setTimeout(createPromise, retryDelay);
|
|
345
343
|
store.setState({
|
|
346
344
|
isPending: false,
|
|
347
345
|
isRevalidating: false,
|
|
@@ -366,31 +364,31 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
366
364
|
});
|
|
367
365
|
const state = store.getState();
|
|
368
366
|
resolve(state);
|
|
369
|
-
(_a =
|
|
370
|
-
|
|
367
|
+
(_a = internal.retryResolver) == null ? void 0 : _a.call(internal, state);
|
|
368
|
+
internal.retryResolver = void 0;
|
|
371
369
|
if (onError) onError(error, variable, stateBeforeExecute);
|
|
372
370
|
else console.error(state);
|
|
373
371
|
onSettled(variable, stateBeforeExecute);
|
|
374
372
|
}
|
|
375
373
|
}).finally(() => {
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
374
|
+
if (internal.promise === promise) {
|
|
375
|
+
internal.promise = void 0;
|
|
376
|
+
internal.promiseResolver = void 0;
|
|
379
377
|
}
|
|
380
378
|
});
|
|
381
379
|
});
|
|
382
|
-
|
|
380
|
+
internal.promise = promise;
|
|
383
381
|
return promise;
|
|
384
382
|
};
|
|
385
383
|
return createPromise();
|
|
386
384
|
};
|
|
387
385
|
const revalidate = async (store, variable, overwriteOngoingExecution) => {
|
|
388
|
-
const {
|
|
389
|
-
if (!overwriteOngoingExecution &&
|
|
386
|
+
const { internal } = store;
|
|
387
|
+
if (!overwriteOngoingExecution && internal.promise) return internal.promise;
|
|
390
388
|
const state = store.getState();
|
|
391
389
|
if (state.dataUpdatedAt) {
|
|
392
390
|
const isFresh = state.dataUpdatedAt + staleTime > Date.now();
|
|
393
|
-
if (isFresh && !
|
|
391
|
+
if (isFresh && !internal.isInvalidated) return state;
|
|
394
392
|
}
|
|
395
393
|
return execute(store, variable, overwriteOngoingExecution);
|
|
396
394
|
};
|
|
@@ -407,7 +405,16 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
407
405
|
);
|
|
408
406
|
store.variableHash = variableHash;
|
|
409
407
|
stores.set(variableHash, store);
|
|
410
|
-
|
|
408
|
+
const apis = getApis(store, variable);
|
|
409
|
+
store.setInitialData = apis.setInitialData;
|
|
410
|
+
store.execute = apis.execute;
|
|
411
|
+
store.revalidate = apis.revalidate;
|
|
412
|
+
store.invalidate = apis.invalidate;
|
|
413
|
+
store.reset = apis.reset;
|
|
414
|
+
store.delete = apis.delete;
|
|
415
|
+
store.optimisticUpdate = apis.optimisticUpdate;
|
|
416
|
+
store.rollbackOptimisticUpdate = apis.rollbackOptimisticUpdate;
|
|
417
|
+
store.internal = apis.internal;
|
|
411
418
|
}
|
|
412
419
|
const useStore = (options2 = {}) => {
|
|
413
420
|
const {
|
|
@@ -475,15 +482,11 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
475
482
|
return trackedState;
|
|
476
483
|
};
|
|
477
484
|
return Object.assign(useStore, {
|
|
478
|
-
|
|
479
|
-
getSubscriberCount: store.getSubscriberCount,
|
|
480
|
-
getState: store.getState,
|
|
485
|
+
...store,
|
|
481
486
|
setState: (value) => {
|
|
482
487
|
console.debug("Manual setState (not via provided actions) on query store");
|
|
483
488
|
store.setState(value);
|
|
484
|
-
}
|
|
485
|
-
...internals.get(store),
|
|
486
|
-
variableHash
|
|
489
|
+
}
|
|
487
490
|
});
|
|
488
491
|
};
|
|
489
492
|
return Object.assign(getStore, {
|
|
@@ -494,7 +497,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
494
497
|
* - Useful for bulk refetching.
|
|
495
498
|
*/
|
|
496
499
|
executeAll: (options2) => {
|
|
497
|
-
stores.forEach((store) =>
|
|
500
|
+
stores.forEach((store) => store.execute(options2));
|
|
498
501
|
},
|
|
499
502
|
/**
|
|
500
503
|
* Revalidates all query instances.
|
|
@@ -503,7 +506,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
503
506
|
* - Only re-fetches stale queries.
|
|
504
507
|
*/
|
|
505
508
|
revalidateAll: (options2) => {
|
|
506
|
-
stores.forEach((store) =>
|
|
509
|
+
stores.forEach((store) => store.revalidate(options2));
|
|
507
510
|
},
|
|
508
511
|
/**
|
|
509
512
|
* Invalidates all query instances.
|
|
@@ -513,13 +516,13 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
513
516
|
* - Invalidated queries bypass `staleTime` until successfully executed again.
|
|
514
517
|
*/
|
|
515
518
|
invalidateAll: (options2) => {
|
|
516
|
-
stores.forEach((store) =>
|
|
519
|
+
stores.forEach((store) => store.invalidate(options2));
|
|
517
520
|
},
|
|
518
521
|
/**
|
|
519
522
|
* Resets all query instances.
|
|
520
523
|
*/
|
|
521
524
|
resetAll: () => {
|
|
522
|
-
stores.forEach((store) =>
|
|
525
|
+
stores.forEach((store) => store.reset());
|
|
523
526
|
}
|
|
524
527
|
});
|
|
525
528
|
};
|
package/package.json
CHANGED
|
@@ -44,17 +44,7 @@ export type MutationState<TData, TVariable, TError> = {
|
|
|
44
44
|
error: TError;
|
|
45
45
|
errorUpdatedAt: number;
|
|
46
46
|
});
|
|
47
|
-
export declare const INITIAL_STATE:
|
|
48
|
-
state: string;
|
|
49
|
-
isPending: boolean;
|
|
50
|
-
isSuccess: boolean;
|
|
51
|
-
isError: boolean;
|
|
52
|
-
variable: undefined;
|
|
53
|
-
data: undefined;
|
|
54
|
-
dataUpdatedAt: undefined;
|
|
55
|
-
error: undefined;
|
|
56
|
-
errorUpdatedAt: undefined;
|
|
57
|
-
};
|
|
47
|
+
export declare const INITIAL_STATE: MutationState<any, any, any>;
|
|
58
48
|
/**
|
|
59
49
|
* Configuration options for a mutation.
|
|
60
50
|
*
|
package/react/create-query.d.ts
CHANGED
|
@@ -63,15 +63,149 @@ export type QueryState<TData, TError> = {
|
|
|
63
63
|
error: TError;
|
|
64
64
|
errorUpdatedAt: number;
|
|
65
65
|
});
|
|
66
|
+
type Internal<TData, TError> = {
|
|
67
|
+
isInvalidated?: boolean;
|
|
68
|
+
promise?: Promise<QueryState<TData, TError>> | undefined;
|
|
69
|
+
promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
70
|
+
retryTimeoutId?: number;
|
|
71
|
+
retryResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
72
|
+
garbageCollectionTimeoutId?: number;
|
|
73
|
+
rollbackData?: TData | undefined;
|
|
74
|
+
};
|
|
75
|
+
type AdditionalStoreApi<TData, TError> = {
|
|
76
|
+
/**
|
|
77
|
+
* A deterministic hash string derived from the query variable.
|
|
78
|
+
*
|
|
79
|
+
* Used as the unique identifier for this query instance in the internal cache.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* - Structurally identical variables will produce the same hash.
|
|
83
|
+
*/
|
|
84
|
+
variableHash: string;
|
|
85
|
+
/**
|
|
86
|
+
* Sets initial data for the query if it has not been initialized.
|
|
87
|
+
*
|
|
88
|
+
* @param data - Initial data
|
|
89
|
+
* @param revalidate - Whether to mark the data as invalidated (will trigger revalidation)
|
|
90
|
+
*
|
|
91
|
+
* @returns `true` if the data was set, `false` otherwise
|
|
92
|
+
*
|
|
93
|
+
* @remarks
|
|
94
|
+
* - Only applies when the query is in the `INITIAL` state.
|
|
95
|
+
* - Useful for hydration or preloaded data.
|
|
96
|
+
*/
|
|
97
|
+
setInitialData: (data: TData, revalidate?: boolean) => boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Executes the query function.
|
|
100
|
+
*
|
|
101
|
+
* @param options - Execution options
|
|
102
|
+
* @param options.overwriteOngoingExecution - Whether to start a new execution instead of reusing an ongoing one (default: `true`)
|
|
103
|
+
*
|
|
104
|
+
* @returns A promise resolving to the latest query state
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* - By default, each call **starts a new execution** even if one is already in progress.
|
|
108
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
109
|
+
* - Handles:
|
|
110
|
+
* - Pending state
|
|
111
|
+
* - Success state
|
|
112
|
+
* - Error state
|
|
113
|
+
* - Retry logic
|
|
114
|
+
*/
|
|
115
|
+
execute: (options?: {
|
|
116
|
+
overwriteOngoingExecution?: boolean;
|
|
117
|
+
}) => Promise<QueryState<TData, TError>>;
|
|
118
|
+
/**
|
|
119
|
+
* Re-executes the query if needed based on freshness or invalidation.
|
|
120
|
+
*
|
|
121
|
+
* @param options - Revalidation options
|
|
122
|
+
* @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
|
|
123
|
+
*
|
|
124
|
+
* @returns The current state if still fresh, otherwise a promise of the new state
|
|
125
|
+
*
|
|
126
|
+
* @remarks
|
|
127
|
+
* - Skips execution if data is still fresh (`staleTime`) **AND** the query has not been invalidated.
|
|
128
|
+
* - If execution is not skipped, by default it will start a new execution even if one is already in progress.
|
|
129
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
130
|
+
*/
|
|
131
|
+
revalidate: (options?: {
|
|
132
|
+
overwriteOngoingExecution?: boolean;
|
|
133
|
+
}) => Promise<QueryState<TData, TError>>;
|
|
134
|
+
/**
|
|
135
|
+
* Marks the query as invalidated and optionally triggers re-execution.
|
|
136
|
+
*
|
|
137
|
+
* @param options - Invalidation options
|
|
138
|
+
* @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
|
|
139
|
+
*
|
|
140
|
+
* @returns `true` if execution was triggered, `false` otherwise
|
|
141
|
+
*
|
|
142
|
+
* @remarks
|
|
143
|
+
* - Invalidated queries are treated as stale regardless of `staleTime`.
|
|
144
|
+
* - The next `revalidate` will always execute until a successful result clears the invalidation.
|
|
145
|
+
* - If there are active subscribers: Execution is triggered immediately.
|
|
146
|
+
* - Otherwise: The query remains invalidated and will execute on the next revalidation.
|
|
147
|
+
* - By default, starts a new execution even if one is already in progress.
|
|
148
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
149
|
+
*/
|
|
150
|
+
invalidate: (options?: {
|
|
151
|
+
overwriteOngoingExecution?: boolean;
|
|
152
|
+
}) => boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Resets the query state to its initial state.
|
|
155
|
+
*
|
|
156
|
+
* @remarks
|
|
157
|
+
* - Cancels retry logic and ignores any ongoing execution results.
|
|
158
|
+
*/
|
|
159
|
+
reset: () => void;
|
|
160
|
+
/**
|
|
161
|
+
* Deletes the query store for the current variable.
|
|
162
|
+
*
|
|
163
|
+
* @returns `true` if deleted, `false` otherwise
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* - Cannot delete while there are active subscribers.
|
|
167
|
+
*/
|
|
168
|
+
delete: () => boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Performs an optimistic update on the query data.
|
|
171
|
+
*
|
|
172
|
+
* @param data - Optimistic data to set
|
|
173
|
+
*
|
|
174
|
+
* @returns Controls for managing the optimistic update
|
|
175
|
+
*
|
|
176
|
+
* @remarks
|
|
177
|
+
* - Temporarily replaces the current data.
|
|
178
|
+
* - Stores previous data for rollback.
|
|
179
|
+
* - Commonly used with mutations for instant UI updates.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const { rollback, revalidate } = query.optimisticUpdate(newData);
|
|
183
|
+
*/
|
|
184
|
+
optimisticUpdate: (data: TData) => {
|
|
185
|
+
revalidate: () => Promise<QueryState<TData, TError>>;
|
|
186
|
+
rollback: () => TData;
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Restores the data before the last optimistic update.
|
|
190
|
+
*
|
|
191
|
+
* @returns The restored data
|
|
192
|
+
*
|
|
193
|
+
* @remarks
|
|
194
|
+
* - Should be used if an optimistic update fails.
|
|
195
|
+
*/
|
|
196
|
+
rollbackOptimisticUpdate: () => TData;
|
|
197
|
+
/**
|
|
198
|
+
* Internal data, do not mutate!
|
|
199
|
+
*/
|
|
200
|
+
internal: Readonly<Internal<TData, TError>>;
|
|
201
|
+
};
|
|
66
202
|
/**
|
|
67
203
|
* Configuration options for a query.
|
|
68
204
|
*
|
|
69
205
|
* @remarks
|
|
70
206
|
* Controls caching, retry behavior, lifecycle, and side effects of an async operation.
|
|
71
207
|
*/
|
|
72
|
-
export type QueryOptions<TData, TVariable extends StoreKey, TError = Error> = InitStoreOptions<QueryState<TData, TError>, {
|
|
73
|
-
variableHash: string;
|
|
74
|
-
}> & {
|
|
208
|
+
export type QueryOptions<TData, TVariable extends StoreKey, TError = Error> = InitStoreOptions<QueryState<TData, TError>, AdditionalStoreApi<TData, TError>> & {
|
|
75
209
|
/**
|
|
76
210
|
* Time (in milliseconds) that data is considered fresh.
|
|
77
211
|
*
|
|
@@ -219,19 +353,19 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
|
|
|
219
353
|
initialData?: never;
|
|
220
354
|
initialDataIsStale?: never;
|
|
221
355
|
})) => QueryState<TData, TError>) & {
|
|
222
|
-
|
|
356
|
+
setState: (value: SetStateInput<QueryState<TData, TError>>) => void;
|
|
357
|
+
getState: () => QueryState<TData, TError>;
|
|
358
|
+
subscribe: (subscriber: import("../vanilla.ts").Subscriber<QueryState<TData, TError>>) => () => void;
|
|
359
|
+
getSubscriberCount: () => number;
|
|
223
360
|
/**
|
|
224
|
-
*
|
|
361
|
+
* A deterministic hash string derived from the query variable.
|
|
362
|
+
*
|
|
363
|
+
* Used as the unique identifier for this query instance in the internal cache.
|
|
364
|
+
*
|
|
365
|
+
* @remarks
|
|
366
|
+
* - Structurally identical variables will produce the same hash.
|
|
225
367
|
*/
|
|
226
|
-
|
|
227
|
-
isInvalidated?: boolean;
|
|
228
|
-
promise?: Promise<QueryState<TData, TError>> | undefined;
|
|
229
|
-
promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
230
|
-
retryTimeoutId?: number;
|
|
231
|
-
retryResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
232
|
-
garbageCollectionTimeoutId?: number;
|
|
233
|
-
rollbackData?: TData | undefined;
|
|
234
|
-
};
|
|
368
|
+
variableHash: string;
|
|
235
369
|
/**
|
|
236
370
|
* Sets initial data for the query if it has not been initialized.
|
|
237
371
|
*
|
|
@@ -344,10 +478,10 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
|
|
|
344
478
|
* - Should be used if an optimistic update fails.
|
|
345
479
|
*/
|
|
346
480
|
rollbackOptimisticUpdate: () => TData;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
481
|
+
/**
|
|
482
|
+
* Internal data, do not mutate!
|
|
483
|
+
*/
|
|
484
|
+
internal: Readonly<Internal<TData, TError>>;
|
|
351
485
|
}) & {
|
|
352
486
|
/**
|
|
353
487
|
* Executes all query instances.
|
|
@@ -382,3 +516,4 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
|
|
|
382
516
|
*/
|
|
383
517
|
resetAll: () => void;
|
|
384
518
|
};
|
|
519
|
+
export {};
|
package/react/create-stores.d.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import { type InitStoreOptions } from "../vanilla.ts";
|
|
1
|
+
import { type InitStoreOptions, type StoreApi } from "../vanilla.ts";
|
|
2
2
|
type GoodInputForHash = string | number | boolean | null | Date;
|
|
3
3
|
export type StoreKey = GoodInputForHash | {
|
|
4
4
|
[key: string | number]: StoreKey | StoreKey[];
|
|
5
5
|
};
|
|
6
|
+
type AdditionalStoreApi<TKey> = {
|
|
7
|
+
/**
|
|
8
|
+
* The original key used to identify this store instance.\
|
|
9
|
+
* This value is not hashed and is preserved as-is.
|
|
10
|
+
*/
|
|
11
|
+
key: TKey;
|
|
12
|
+
/**
|
|
13
|
+
* A deterministic hash string derived from {@link key}.
|
|
14
|
+
*
|
|
15
|
+
* Used internally as the unique identifier for caching and retrieving store instances.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* - Guarantees that structurally identical keys produce the same hash.
|
|
19
|
+
*/
|
|
20
|
+
keyHash: string;
|
|
21
|
+
/**
|
|
22
|
+
* Deletes this store instance from the internal cache.
|
|
23
|
+
*
|
|
24
|
+
* @returns `true` if the store was successfully deleted, otherwise `false`.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* - If there are active subscribers, the deletion is ignored and `false` is returned.
|
|
28
|
+
* - When deletion succeeds:
|
|
29
|
+
* - The store is removed from the cache.
|
|
30
|
+
* - Its state is reset to the initial state.
|
|
31
|
+
* - Intended for manual cleanup of unused or ephemeral stores.
|
|
32
|
+
*/
|
|
33
|
+
delete: () => boolean;
|
|
34
|
+
};
|
|
6
35
|
/**
|
|
7
36
|
* Creates a factory for multiple stores identified by a key.
|
|
8
37
|
*
|
|
@@ -36,23 +65,12 @@ export type StoreKey = GoodInputForHash | {
|
|
|
36
65
|
*
|
|
37
66
|
* @see https://floppy-disk.vercel.app/docs/stores
|
|
38
67
|
*/
|
|
39
|
-
export declare const createStores: <TState extends Record<string, any>, TKey extends StoreKey>(initialState: TState, options?: InitStoreOptions<TState, {
|
|
40
|
-
key: TKey;
|
|
41
|
-
keyHash: string;
|
|
42
|
-
}>) => (key?: TKey) => ((options?: {
|
|
68
|
+
export declare const createStores: <TState extends Record<string, any>, TKey extends StoreKey>(initialState: TState, options?: InitStoreOptions<TState, AdditionalStoreApi<TKey>>) => (key?: TKey) => ((options?: {
|
|
43
69
|
/**
|
|
44
70
|
* Initial state used on first render (and will also update the store state right after that)
|
|
45
71
|
*
|
|
46
72
|
* If provided, `initialState` will be applied **once per store instance**
|
|
47
73
|
*/
|
|
48
74
|
initialState?: Partial<TState>;
|
|
49
|
-
}) => TState) &
|
|
50
|
-
delete: () => boolean;
|
|
51
|
-
setState: (value: import("../vanilla.ts").SetStateInput<TState>) => void;
|
|
52
|
-
getState: () => TState;
|
|
53
|
-
subscribe: (subscriber: import("../vanilla.ts").Subscriber<TState>) => () => void;
|
|
54
|
-
getSubscriberCount: () => number;
|
|
55
|
-
key: TKey;
|
|
56
|
-
keyHash: string;
|
|
57
|
-
};
|
|
75
|
+
}) => TState) & StoreApi<TState> & AdditionalStoreApi<TKey>;
|
|
58
76
|
export {};
|
package/react.js
CHANGED
|
@@ -104,11 +104,7 @@ const createStores = (initialState, options) => {
|
|
|
104
104
|
store.key = key;
|
|
105
105
|
store.keyHash = keyHash;
|
|
106
106
|
stores.set(keyHash, store);
|
|
107
|
-
|
|
108
|
-
const useStore = (options2) => useStoreState(store, options2);
|
|
109
|
-
return Object.assign(useStore, {
|
|
110
|
-
...store,
|
|
111
|
-
delete: () => {
|
|
107
|
+
store.delete = () => {
|
|
112
108
|
if (store.getSubscriberCount() > 0) {
|
|
113
109
|
console.warn(
|
|
114
110
|
"Cannot delete store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
|
|
@@ -117,8 +113,10 @@ const createStores = (initialState, options) => {
|
|
|
117
113
|
}
|
|
118
114
|
store.setState(initialState);
|
|
119
115
|
return stores.delete(keyHash);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const useStore = (options2) => useStoreState(store, options2);
|
|
119
|
+
return Object.assign(useStore, store);
|
|
122
120
|
};
|
|
123
121
|
return getStore;
|
|
124
122
|
};
|
|
@@ -158,8 +156,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
158
156
|
onFirstSubscribe: (state, store) => {
|
|
159
157
|
var _a;
|
|
160
158
|
(_a = options.onFirstSubscribe) == null ? void 0 : _a.call(options, state, store);
|
|
161
|
-
const {
|
|
162
|
-
clearTimeout(
|
|
159
|
+
const { internal, revalidate: revalidate2 } = store;
|
|
160
|
+
clearTimeout(internal.garbageCollectionTimeoutId);
|
|
163
161
|
if (vanilla.isClient) {
|
|
164
162
|
if (revalidateOnFocus) {
|
|
165
163
|
focusListeners.add(revalidate2);
|
|
@@ -180,15 +178,15 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
180
178
|
onLastUnsubscribe: (state, store) => {
|
|
181
179
|
var _a;
|
|
182
180
|
(_a = options.onLastUnsubscribe) == null ? void 0 : _a.call(options, state, store);
|
|
183
|
-
const {
|
|
184
|
-
clearTimeout(
|
|
185
|
-
if (
|
|
181
|
+
const { internal, revalidate: revalidate2 } = store;
|
|
182
|
+
clearTimeout(internal.retryTimeoutId);
|
|
183
|
+
if (internal.retryResolver) {
|
|
186
184
|
store.setState({ willRetryAt: void 0 });
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
internal.retryResolver(store.getState());
|
|
186
|
+
internal.retryResolver = void 0;
|
|
189
187
|
}
|
|
190
|
-
|
|
191
|
-
if (
|
|
188
|
+
internal.garbageCollectionTimeoutId = setTimeout(() => {
|
|
189
|
+
if (internal.promiseResolver || internal.retryResolver) {
|
|
192
190
|
store.setState(initialState);
|
|
193
191
|
} else {
|
|
194
192
|
stores.delete(variableHash);
|
|
@@ -212,14 +210,13 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
212
210
|
}
|
|
213
211
|
}
|
|
214
212
|
});
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
metadata: {},
|
|
213
|
+
const getApis = (store, variable) => ({
|
|
214
|
+
internal: {},
|
|
218
215
|
setInitialData: (data, revalidate2 = false) => {
|
|
219
216
|
const state = store.getState();
|
|
220
217
|
if (state.state === "INITIAL" && state.data === void 0) {
|
|
221
|
-
const {
|
|
222
|
-
if (revalidate2)
|
|
218
|
+
const { internal } = store;
|
|
219
|
+
if (revalidate2) internal.isInvalidated = true;
|
|
223
220
|
store.setState({
|
|
224
221
|
state: "SUCCESS",
|
|
225
222
|
isSuccess: true,
|
|
@@ -237,28 +234,28 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
237
234
|
return revalidate(store, variable, overwriteOngoingExecution);
|
|
238
235
|
},
|
|
239
236
|
invalidate: (options2) => {
|
|
240
|
-
const {
|
|
241
|
-
|
|
237
|
+
const { internal } = store;
|
|
238
|
+
internal.isInvalidated = true;
|
|
242
239
|
if (store.getSubscriberCount() > 0) {
|
|
243
|
-
|
|
240
|
+
store.execute(options2);
|
|
244
241
|
return true;
|
|
245
242
|
}
|
|
246
243
|
return false;
|
|
247
244
|
},
|
|
248
245
|
reset: () => {
|
|
249
246
|
var _a, _b;
|
|
250
|
-
const {
|
|
251
|
-
clearTimeout(
|
|
252
|
-
if (
|
|
247
|
+
const { internal } = store;
|
|
248
|
+
clearTimeout(internal.retryTimeoutId);
|
|
249
|
+
if (internal.retryResolver || internal.promiseResolver) {
|
|
253
250
|
console.debug(
|
|
254
251
|
"Ongoing query execution was ignored due to reset(). The result will not update the store state."
|
|
255
252
|
);
|
|
256
|
-
(_a =
|
|
257
|
-
(_b =
|
|
258
|
-
|
|
259
|
-
|
|
253
|
+
(_a = internal.promiseResolver) == null ? void 0 : _a.call(internal, initialState);
|
|
254
|
+
(_b = internal.retryResolver) == null ? void 0 : _b.call(internal, initialState);
|
|
255
|
+
internal.promiseResolver = void 0;
|
|
256
|
+
internal.retryResolver = void 0;
|
|
260
257
|
}
|
|
261
|
-
|
|
258
|
+
internal.promise = void 0;
|
|
262
259
|
store.setState(initialState);
|
|
263
260
|
},
|
|
264
261
|
delete: () => {
|
|
@@ -268,35 +265,36 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
268
265
|
);
|
|
269
266
|
return false;
|
|
270
267
|
}
|
|
271
|
-
|
|
268
|
+
store.reset();
|
|
272
269
|
return stores.delete(store.variableHash);
|
|
273
270
|
},
|
|
274
271
|
optimisticUpdate: (optimisticData) => {
|
|
275
|
-
const {
|
|
276
|
-
|
|
272
|
+
const { internal, revalidate: revalidate2, rollbackOptimisticUpdate } = store;
|
|
273
|
+
internal.rollbackData = store.getState().data;
|
|
277
274
|
store.setState({ data: optimisticData });
|
|
278
275
|
return { revalidate: revalidate2, rollback: rollbackOptimisticUpdate };
|
|
279
276
|
},
|
|
280
277
|
rollbackOptimisticUpdate: () => {
|
|
281
|
-
const {
|
|
282
|
-
store.setState({ data:
|
|
283
|
-
return
|
|
278
|
+
const { internal } = store;
|
|
279
|
+
store.setState({ data: internal.rollbackData });
|
|
280
|
+
return internal.rollbackData;
|
|
284
281
|
}
|
|
285
282
|
});
|
|
286
283
|
const execute = async (store, variable, overwriteOngoingExecution = false) => {
|
|
287
|
-
const {
|
|
288
|
-
|
|
289
|
-
|
|
284
|
+
const { internal: _internal } = store;
|
|
285
|
+
const internal = _internal;
|
|
286
|
+
if (!overwriteOngoingExecution && internal.promise) return internal.promise;
|
|
287
|
+
clearTimeout(internal.retryTimeoutId);
|
|
290
288
|
const createPromise = () => {
|
|
291
289
|
const promise = new Promise((resolve) => {
|
|
292
|
-
|
|
290
|
+
internal.promiseResolver = resolve;
|
|
293
291
|
const stateBeforeExecute = store.getState();
|
|
294
292
|
store.setState({
|
|
295
293
|
isPending: true,
|
|
296
294
|
isRevalidating: stateBeforeExecute.state === "SUCCESS",
|
|
297
295
|
willRetryAt: void 0,
|
|
298
|
-
isRetrying: !!
|
|
299
|
-
retryCount:
|
|
296
|
+
isRetrying: !!internal.retryResolver,
|
|
297
|
+
retryCount: internal.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
|
|
300
298
|
});
|
|
301
299
|
queryFn(variable, stateBeforeExecute, store.variableHash).then((data) => {
|
|
302
300
|
var _a;
|
|
@@ -305,8 +303,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
305
303
|
"Query function returned undefined. Successful responses must not be undefined."
|
|
306
304
|
);
|
|
307
305
|
}
|
|
308
|
-
if (!
|
|
309
|
-
if (promise !==
|
|
306
|
+
if (!internal.promiseResolver) return;
|
|
307
|
+
if (promise !== internal.promise) return resolve(internal.promise);
|
|
310
308
|
const now = Date.now();
|
|
311
309
|
store.setState({
|
|
312
310
|
isPending: false,
|
|
@@ -322,17 +320,17 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
322
320
|
error: void 0,
|
|
323
321
|
errorUpdatedAt: void 0
|
|
324
322
|
});
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
internal.isInvalidated = false;
|
|
324
|
+
internal.rollbackData = data;
|
|
327
325
|
resolve(store.getState());
|
|
328
|
-
(_a =
|
|
329
|
-
|
|
326
|
+
(_a = internal.retryResolver) == null ? void 0 : _a.call(internal, store.getState());
|
|
327
|
+
internal.retryResolver = void 0;
|
|
330
328
|
onSuccess(data, variable, stateBeforeExecute);
|
|
331
329
|
onSettled(variable, stateBeforeExecute);
|
|
332
330
|
}).catch((error) => {
|
|
333
331
|
var _a;
|
|
334
|
-
if (!
|
|
335
|
-
if (promise !==
|
|
332
|
+
if (!internal.promiseResolver && !internal.retryResolver) return;
|
|
333
|
+
if (promise !== internal.promise) return resolve(internal.promise);
|
|
336
334
|
const nextState = {
|
|
337
335
|
...store.getState(),
|
|
338
336
|
isPending: false,
|
|
@@ -342,8 +340,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
342
340
|
const [shouldRetry, retryDelay] = shouldRetryFn(error, nextState);
|
|
343
341
|
const hasSubscriber = store.getSubscriberCount() > 0;
|
|
344
342
|
if (shouldRetry && hasSubscriber) {
|
|
345
|
-
|
|
346
|
-
|
|
343
|
+
internal.retryResolver = resolve;
|
|
344
|
+
internal.retryTimeoutId = setTimeout(createPromise, retryDelay);
|
|
347
345
|
store.setState({
|
|
348
346
|
isPending: false,
|
|
349
347
|
isRevalidating: false,
|
|
@@ -368,31 +366,31 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
368
366
|
});
|
|
369
367
|
const state = store.getState();
|
|
370
368
|
resolve(state);
|
|
371
|
-
(_a =
|
|
372
|
-
|
|
369
|
+
(_a = internal.retryResolver) == null ? void 0 : _a.call(internal, state);
|
|
370
|
+
internal.retryResolver = void 0;
|
|
373
371
|
if (onError) onError(error, variable, stateBeforeExecute);
|
|
374
372
|
else console.error(state);
|
|
375
373
|
onSettled(variable, stateBeforeExecute);
|
|
376
374
|
}
|
|
377
375
|
}).finally(() => {
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
|
|
376
|
+
if (internal.promise === promise) {
|
|
377
|
+
internal.promise = void 0;
|
|
378
|
+
internal.promiseResolver = void 0;
|
|
381
379
|
}
|
|
382
380
|
});
|
|
383
381
|
});
|
|
384
|
-
|
|
382
|
+
internal.promise = promise;
|
|
385
383
|
return promise;
|
|
386
384
|
};
|
|
387
385
|
return createPromise();
|
|
388
386
|
};
|
|
389
387
|
const revalidate = async (store, variable, overwriteOngoingExecution) => {
|
|
390
|
-
const {
|
|
391
|
-
if (!overwriteOngoingExecution &&
|
|
388
|
+
const { internal } = store;
|
|
389
|
+
if (!overwriteOngoingExecution && internal.promise) return internal.promise;
|
|
392
390
|
const state = store.getState();
|
|
393
391
|
if (state.dataUpdatedAt) {
|
|
394
392
|
const isFresh = state.dataUpdatedAt + staleTime > Date.now();
|
|
395
|
-
if (isFresh && !
|
|
393
|
+
if (isFresh && !internal.isInvalidated) return state;
|
|
396
394
|
}
|
|
397
395
|
return execute(store, variable, overwriteOngoingExecution);
|
|
398
396
|
};
|
|
@@ -409,7 +407,16 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
409
407
|
);
|
|
410
408
|
store.variableHash = variableHash;
|
|
411
409
|
stores.set(variableHash, store);
|
|
412
|
-
|
|
410
|
+
const apis = getApis(store, variable);
|
|
411
|
+
store.setInitialData = apis.setInitialData;
|
|
412
|
+
store.execute = apis.execute;
|
|
413
|
+
store.revalidate = apis.revalidate;
|
|
414
|
+
store.invalidate = apis.invalidate;
|
|
415
|
+
store.reset = apis.reset;
|
|
416
|
+
store.delete = apis.delete;
|
|
417
|
+
store.optimisticUpdate = apis.optimisticUpdate;
|
|
418
|
+
store.rollbackOptimisticUpdate = apis.rollbackOptimisticUpdate;
|
|
419
|
+
store.internal = apis.internal;
|
|
413
420
|
}
|
|
414
421
|
const useStore = (options2 = {}) => {
|
|
415
422
|
const {
|
|
@@ -477,15 +484,11 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
477
484
|
return trackedState;
|
|
478
485
|
};
|
|
479
486
|
return Object.assign(useStore, {
|
|
480
|
-
|
|
481
|
-
getSubscriberCount: store.getSubscriberCount,
|
|
482
|
-
getState: store.getState,
|
|
487
|
+
...store,
|
|
483
488
|
setState: (value) => {
|
|
484
489
|
console.debug("Manual setState (not via provided actions) on query store");
|
|
485
490
|
store.setState(value);
|
|
486
|
-
}
|
|
487
|
-
...internals.get(store),
|
|
488
|
-
variableHash
|
|
491
|
+
}
|
|
489
492
|
});
|
|
490
493
|
};
|
|
491
494
|
return Object.assign(getStore, {
|
|
@@ -496,7 +499,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
496
499
|
* - Useful for bulk refetching.
|
|
497
500
|
*/
|
|
498
501
|
executeAll: (options2) => {
|
|
499
|
-
stores.forEach((store) =>
|
|
502
|
+
stores.forEach((store) => store.execute(options2));
|
|
500
503
|
},
|
|
501
504
|
/**
|
|
502
505
|
* Revalidates all query instances.
|
|
@@ -505,7 +508,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
505
508
|
* - Only re-fetches stale queries.
|
|
506
509
|
*/
|
|
507
510
|
revalidateAll: (options2) => {
|
|
508
|
-
stores.forEach((store) =>
|
|
511
|
+
stores.forEach((store) => store.revalidate(options2));
|
|
509
512
|
},
|
|
510
513
|
/**
|
|
511
514
|
* Invalidates all query instances.
|
|
@@ -515,13 +518,13 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
515
518
|
* - Invalidated queries bypass `staleTime` until successfully executed again.
|
|
516
519
|
*/
|
|
517
520
|
invalidateAll: (options2) => {
|
|
518
|
-
stores.forEach((store) =>
|
|
521
|
+
stores.forEach((store) => store.invalidate(options2));
|
|
519
522
|
},
|
|
520
523
|
/**
|
|
521
524
|
* Resets all query instances.
|
|
522
525
|
*/
|
|
523
526
|
resetAll: () => {
|
|
524
|
-
stores.forEach((store) =>
|
|
527
|
+
stores.forEach((store) => store.reset());
|
|
525
528
|
}
|
|
526
529
|
});
|
|
527
530
|
};
|