floppy-disk 3.0.0-alpha.1 → 3.0.0-alpha.3
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 +3 -3
- package/esm/react/create-query.d.mts +37 -18
- package/esm/react/create-store.d.mts +2 -2
- package/esm/react/create-stores.d.mts +4 -4
- package/esm/react/use-store.d.mts +1 -1
- package/esm/react.mjs +12 -12
- package/esm/vanilla/store.d.mts +9 -4
- package/esm/vanilla.mjs +8 -1
- package/package.json +1 -1
- package/react/create-mutation.d.ts +3 -3
- package/react/create-query.d.ts +37 -18
- package/react/create-store.d.ts +2 -2
- package/react/create-stores.d.ts +4 -4
- package/react/use-store.d.ts +1 -1
- package/react.js +28 -28
- package/vanilla/store.d.ts +9 -4
- package/vanilla.js +8 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitStoreOptions, type SetState } from 'floppy-disk';
|
|
1
|
+
import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
|
|
2
2
|
/**
|
|
3
3
|
* Represents the state of a mutation.
|
|
4
4
|
*
|
|
@@ -90,8 +90,8 @@ export type MutationOptions<TData, TVariable> = InitStoreOptions<MutationState<T
|
|
|
90
90
|
* const result = await useCreateUser.execute({ name: 'John' });
|
|
91
91
|
*/
|
|
92
92
|
export declare const createMutation: <TData, TVariable = undefined>(mutationFn: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable>) => Promise<TData>, options?: MutationOptions<TData, TVariable>) => (<TStateSlice = MutationState<TData, TVariable>>(selector?: (state: MutationState<TData, TVariable>) => TStateSlice) => TStateSlice) & {
|
|
93
|
-
subscribe: (subscriber: import("
|
|
94
|
-
getSubscribers: () => Set<import("
|
|
93
|
+
subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<MutationState<TData, TVariable>>) => () => void;
|
|
94
|
+
getSubscribers: () => Set<import("../vanilla.d.mts").Subscriber<MutationState<TData, TVariable>>>;
|
|
95
95
|
getState: () => MutationState<TData, TVariable>;
|
|
96
96
|
/**
|
|
97
97
|
* Manually updates the mutation state.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitStoreOptions, type SetState } from 'floppy-disk';
|
|
1
|
+
import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
|
|
2
2
|
/**
|
|
3
3
|
* Represents the state of a query.
|
|
4
4
|
*
|
|
@@ -202,45 +202,58 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
202
202
|
/**
|
|
203
203
|
* Executes the query function.
|
|
204
204
|
*
|
|
205
|
-
* @param
|
|
205
|
+
* @param options - Execution options
|
|
206
|
+
* @param options.overwriteOngoingExecution - Whether to start a new execution instead of reusing an ongoing one (default: `true`)
|
|
206
207
|
*
|
|
207
208
|
* @returns A promise resolving to the latest query state
|
|
208
209
|
*
|
|
209
210
|
* @remarks
|
|
210
|
-
* -
|
|
211
|
-
* -
|
|
211
|
+
* - By default, each call starts a new execution even if one is already in progress.
|
|
212
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
212
213
|
* - Handles:
|
|
213
214
|
* - Pending state
|
|
214
215
|
* - Success state
|
|
215
216
|
* - Error state
|
|
216
217
|
* - Retry logic
|
|
217
218
|
*/
|
|
218
|
-
execute: (
|
|
219
|
+
execute: (options?: {
|
|
220
|
+
overwriteOngoingExecution?: boolean;
|
|
221
|
+
}) => Promise<QueryState<TData>>;
|
|
219
222
|
/**
|
|
220
|
-
* Re-executes the query if
|
|
223
|
+
* Re-executes the query if needed based on freshness or invalidation.
|
|
221
224
|
*
|
|
222
|
-
* @param
|
|
225
|
+
* @param options - Revalidation options
|
|
226
|
+
* @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
|
|
223
227
|
*
|
|
224
228
|
* @returns The current state if still fresh, otherwise a promise of the new state
|
|
225
229
|
*
|
|
226
230
|
* @remarks
|
|
227
|
-
* - Skips execution if data is still fresh (`staleTime`)
|
|
228
|
-
* -
|
|
231
|
+
* - Skips execution if data is still fresh (`staleTime`) **AND** the query has not been invalidated.
|
|
232
|
+
* - If execution is not skipped, by default it will start a new execution even if one is already in progress.
|
|
233
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
229
234
|
*/
|
|
230
|
-
revalidate: (
|
|
235
|
+
revalidate: (options?: {
|
|
236
|
+
overwriteOngoingExecution?: boolean;
|
|
237
|
+
}) => Promise<QueryState<TData>>;
|
|
231
238
|
/**
|
|
232
239
|
* Marks the query as invalidated and optionally triggers re-execution.
|
|
233
240
|
*
|
|
234
|
-
* @param
|
|
241
|
+
* @param options - Invalidation options
|
|
242
|
+
* @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
|
|
235
243
|
*
|
|
236
244
|
* @returns `true` if execution was triggered, `false` otherwise
|
|
237
245
|
*
|
|
238
246
|
* @remarks
|
|
239
247
|
* - Invalidated queries are treated as stale regardless of `staleTime`.
|
|
240
248
|
* - The next `revalidate` will always execute until a successful result clears the invalidation.
|
|
241
|
-
* - If there are active subscribers
|
|
249
|
+
* - If there are active subscribers: Execution is triggered immediately.
|
|
250
|
+
* - Otherwise: The query remains invalidated and will execute on the next revalidation.
|
|
251
|
+
* - By default, starts a new execution even if one is already in progress.
|
|
252
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
242
253
|
*/
|
|
243
|
-
invalidate: (
|
|
254
|
+
invalidate: (options?: {
|
|
255
|
+
overwriteOngoingExecution?: boolean;
|
|
256
|
+
}) => boolean;
|
|
244
257
|
/**
|
|
245
258
|
* Resets the query state to its initial state.
|
|
246
259
|
*
|
|
@@ -285,8 +298,8 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
285
298
|
* - Should be used if an optimistic update fails.
|
|
286
299
|
*/
|
|
287
300
|
rollbackOptimisticUpdate: () => TData;
|
|
288
|
-
subscribe: (subscriber: import("
|
|
289
|
-
getSubscribers: () => Set<import("
|
|
301
|
+
subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<QueryState<TData>>) => () => void;
|
|
302
|
+
getSubscribers: () => Set<import("../vanilla.d.mts").Subscriber<QueryState<TData>>>;
|
|
290
303
|
getState: () => QueryState<TData>;
|
|
291
304
|
setState: (value: SetState<QueryState<TData>>) => void;
|
|
292
305
|
}) & {
|
|
@@ -296,14 +309,18 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
296
309
|
* @remarks
|
|
297
310
|
* - Useful for bulk refetching.
|
|
298
311
|
*/
|
|
299
|
-
executeAll: (
|
|
312
|
+
executeAll: (options?: {
|
|
313
|
+
overwriteOngoingExecution?: boolean;
|
|
314
|
+
}) => void;
|
|
300
315
|
/**
|
|
301
316
|
* Revalidates all query instances.
|
|
302
317
|
*
|
|
303
318
|
* @remarks
|
|
304
319
|
* - Only re-fetches stale queries.
|
|
305
320
|
*/
|
|
306
|
-
revalidateAll: (
|
|
321
|
+
revalidateAll: (options?: {
|
|
322
|
+
overwriteOngoingExecution?: boolean;
|
|
323
|
+
}) => void;
|
|
307
324
|
/**
|
|
308
325
|
* Invalidates all query instances.
|
|
309
326
|
*
|
|
@@ -311,7 +328,9 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
311
328
|
* - Marks all queries as invalidated and triggers revalidation if active.
|
|
312
329
|
* - Invalidated queries bypass `staleTime` until successfully executed again.
|
|
313
330
|
*/
|
|
314
|
-
invalidateAll: (
|
|
331
|
+
invalidateAll: (options?: {
|
|
332
|
+
overwriteOngoingExecution?: boolean;
|
|
333
|
+
}) => void;
|
|
315
334
|
/**
|
|
316
335
|
* Resets all query instances.
|
|
317
336
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitStoreOptions } from 'floppy-disk';
|
|
1
|
+
import { type InitStoreOptions } from 'floppy-disk/vanilla';
|
|
2
2
|
/**
|
|
3
3
|
* Creates a single store with a bound React hook.
|
|
4
4
|
*
|
|
@@ -22,4 +22,4 @@ import { type InitStoreOptions } from 'floppy-disk';
|
|
|
22
22
|
*
|
|
23
23
|
* useCounter.setState({ count: 1 });
|
|
24
24
|
*/
|
|
25
|
-
export declare const createStore: <TState extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => (<TStateSlice = TState>(selector?: (state: TState) => TStateSlice) => TStateSlice) & import("
|
|
25
|
+
export declare const createStore: <TState extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => (<TStateSlice = TState>(selector?: (state: TState) => TStateSlice) => TStateSlice) & import("../vanilla.d.mts").StoreApi<TState>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitStoreOptions } from 'floppy-disk';
|
|
1
|
+
import { type InitStoreOptions } from 'floppy-disk/vanilla';
|
|
2
2
|
/**
|
|
3
3
|
* Creates a factory for multiple stores identified by a key.
|
|
4
4
|
*
|
|
@@ -25,8 +25,8 @@ import { type InitStoreOptions } from 'floppy-disk';
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const createStores: <TState extends Record<string, any>, TKey extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => (key?: TKey) => (<TStateSlice = TState>(selector?: (state: TState) => TStateSlice) => TStateSlice) & {
|
|
27
27
|
delete: () => boolean;
|
|
28
|
-
setState: (value: import("
|
|
28
|
+
setState: (value: import("../vanilla.d.mts").SetState<TState>) => void;
|
|
29
29
|
getState: () => TState;
|
|
30
|
-
subscribe: (subscriber: import("
|
|
31
|
-
getSubscribers: () => Set<import("
|
|
30
|
+
subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<TState>) => () => void;
|
|
31
|
+
getSubscribers: () => Set<import("../vanilla.d.mts").Subscriber<TState>>;
|
|
32
32
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StoreApi } from 'floppy-disk';
|
|
1
|
+
import { type StoreApi } from 'floppy-disk/vanilla';
|
|
2
2
|
export declare const useStoreUpdateNotifier: <TState extends Record<string, any>, TStateSlice = TState>(store: StoreApi<TState>, selector: (state: TState) => TStateSlice) => void;
|
|
3
3
|
/**
|
|
4
4
|
* React hook for subscribing to a store with optional state selection.
|
package/esm/react.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useLayoutEffect, useEffect, useState, useRef } from 'react';
|
|
2
|
-
import { isClient, identity, shallow, initStore, getHash, noop } from 'floppy-disk';
|
|
2
|
+
import { isClient, identity, shallow, initStore, getHash, noop } from 'floppy-disk/vanilla';
|
|
3
3
|
|
|
4
4
|
const useIsomorphicLayoutEffect = isClient ? useLayoutEffect : useEffect;
|
|
5
5
|
|
|
@@ -157,17 +157,17 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
157
157
|
}
|
|
158
158
|
return false;
|
|
159
159
|
},
|
|
160
|
-
execute: (overwriteOngoingExecution =
|
|
160
|
+
execute: ({ overwriteOngoingExecution = true } = {}) => {
|
|
161
161
|
return execute(store, variable, overwriteOngoingExecution);
|
|
162
162
|
},
|
|
163
|
-
revalidate: (overwriteOngoingExecution =
|
|
163
|
+
revalidate: ({ overwriteOngoingExecution = true } = {}) => {
|
|
164
164
|
return revalidate(store, variable, overwriteOngoingExecution);
|
|
165
165
|
},
|
|
166
|
-
invalidate: (
|
|
166
|
+
invalidate: (options2) => {
|
|
167
167
|
const { metadata } = internals.get(store);
|
|
168
168
|
metadata.isInvalidated = true;
|
|
169
169
|
if (store.getSubscribers().size > 0) {
|
|
170
|
-
internals.get(store).execute(
|
|
170
|
+
internals.get(store).execute(options2);
|
|
171
171
|
return true;
|
|
172
172
|
}
|
|
173
173
|
return false;
|
|
@@ -326,7 +326,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
326
326
|
const useStore = (options2 = {}, selector = identity) => {
|
|
327
327
|
useStoreUpdateNotifier(store, selector);
|
|
328
328
|
useIsomorphicLayoutEffect(() => {
|
|
329
|
-
if (options2.enabled !== false) revalidate(store, variable);
|
|
329
|
+
if (options2.enabled !== false) revalidate(store, variable, false);
|
|
330
330
|
}, [store, options2.enabled]);
|
|
331
331
|
const storeState = store.getState();
|
|
332
332
|
let storeStateToBeUsed = storeState;
|
|
@@ -356,8 +356,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
356
356
|
* @remarks
|
|
357
357
|
* - Useful for bulk refetching.
|
|
358
358
|
*/
|
|
359
|
-
executeAll: (
|
|
360
|
-
stores.forEach((store) => internals.get(store).execute(
|
|
359
|
+
executeAll: (options2) => {
|
|
360
|
+
stores.forEach((store) => internals.get(store).execute(options2));
|
|
361
361
|
},
|
|
362
362
|
/**
|
|
363
363
|
* Revalidates all query instances.
|
|
@@ -365,8 +365,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
365
365
|
* @remarks
|
|
366
366
|
* - Only re-fetches stale queries.
|
|
367
367
|
*/
|
|
368
|
-
revalidateAll: (
|
|
369
|
-
stores.forEach((store) => internals.get(store).revalidate(
|
|
368
|
+
revalidateAll: (options2) => {
|
|
369
|
+
stores.forEach((store) => internals.get(store).revalidate(options2));
|
|
370
370
|
},
|
|
371
371
|
/**
|
|
372
372
|
* Invalidates all query instances.
|
|
@@ -375,8 +375,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
375
375
|
* - Marks all queries as invalidated and triggers revalidation if active.
|
|
376
376
|
* - Invalidated queries bypass `staleTime` until successfully executed again.
|
|
377
377
|
*/
|
|
378
|
-
invalidateAll: (
|
|
379
|
-
stores.forEach((store) => internals.get(store).invalidate(
|
|
378
|
+
invalidateAll: (options2) => {
|
|
379
|
+
stores.forEach((store) => internals.get(store).invalidate(options2));
|
|
380
380
|
},
|
|
381
381
|
/**
|
|
382
382
|
* Resets all query instances.
|
package/esm/vanilla/store.d.mts
CHANGED
|
@@ -13,17 +13,18 @@ export type SetState<TState> = Partial<TState> | ((state: TState) => Partial<TSt
|
|
|
13
13
|
* @param prevState - The previous state before the update
|
|
14
14
|
*
|
|
15
15
|
* @remarks
|
|
16
|
-
* - Subscribers are
|
|
17
|
-
* -
|
|
16
|
+
* - Subscribers are only called when the state actually changes.
|
|
17
|
+
* - Change detection is performed per key using `Object.is`.
|
|
18
18
|
*/
|
|
19
19
|
export type Subscriber<TState> = (state: TState, prevState: TState) => void;
|
|
20
20
|
/**
|
|
21
21
|
* Core store API for managing state.
|
|
22
22
|
*
|
|
23
23
|
* @remarks
|
|
24
|
-
* - The store
|
|
25
|
-
* -
|
|
24
|
+
* - The store performs **shallow change detection per key** before notifying subscribers.
|
|
25
|
+
* - Subscribers are only notified when at least one field changes.
|
|
26
26
|
* - Designed to be framework-agnostic (React bindings are built separately).
|
|
27
|
+
* - By default, `setState` is **disabled on the server** to prevent accidental shared state between requests.
|
|
27
28
|
*/
|
|
28
29
|
export type StoreApi<TState extends Record<string, any>> = {
|
|
29
30
|
setState: (value: SetState<TState>) => void;
|
|
@@ -47,6 +48,7 @@ export type InitStoreOptions<TState extends Record<string, any>> = {
|
|
|
47
48
|
onSubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
48
49
|
onUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
49
50
|
onLastUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
51
|
+
allowSetStateServerSide?: boolean;
|
|
50
52
|
};
|
|
51
53
|
/**
|
|
52
54
|
* Creates a vanilla store with pub-sub capabilities.
|
|
@@ -64,6 +66,9 @@ export type InitStoreOptions<TState extends Record<string, any>> = {
|
|
|
64
66
|
* - Subscribers are only notified when at least one updated field changes (using `Object.is` comparison).
|
|
65
67
|
* - Subscribers receive both the new state and the previous state.
|
|
66
68
|
* - Lifecycle hooks allow side-effect management tied to subscription count.
|
|
69
|
+
* - By default, `setState` is **disabled on the server** to prevent accidental shared state between requests.
|
|
70
|
+
* - This avoids leaking data between users in server environments.
|
|
71
|
+
* - You can override this by setting `allowSetStateServerSide: true`.
|
|
67
72
|
*
|
|
68
73
|
* @example
|
|
69
74
|
* const store = initStore({ count: 0 });
|
package/esm/vanilla.mjs
CHANGED
|
@@ -70,7 +70,8 @@ const initStore = (initialState, options = {}) => {
|
|
|
70
70
|
onFirstSubscribe = noop,
|
|
71
71
|
onSubscribe = noop,
|
|
72
72
|
onUnsubscribe = noop,
|
|
73
|
-
onLastUnsubscribe = noop
|
|
73
|
+
onLastUnsubscribe = noop,
|
|
74
|
+
allowSetStateServerSide = false
|
|
74
75
|
} = options;
|
|
75
76
|
const subscribers = /* @__PURE__ */ new Set();
|
|
76
77
|
const getSubscribers = () => subscribers;
|
|
@@ -87,6 +88,12 @@ const initStore = (initialState, options = {}) => {
|
|
|
87
88
|
let state = initialState;
|
|
88
89
|
const getState = () => state;
|
|
89
90
|
const setState = (value) => {
|
|
91
|
+
if (!isClient && !allowSetStateServerSide) {
|
|
92
|
+
console.error(
|
|
93
|
+
"setState on the server is not allowed by default. Set `allowSetStateServerSide: true` to allow it."
|
|
94
|
+
);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
90
97
|
const prevState = state;
|
|
91
98
|
const newValue = getValue(value, state);
|
|
92
99
|
for (const key in newValue) {
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitStoreOptions, type SetState } from 'floppy-disk';
|
|
1
|
+
import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
|
|
2
2
|
/**
|
|
3
3
|
* Represents the state of a mutation.
|
|
4
4
|
*
|
|
@@ -90,8 +90,8 @@ export type MutationOptions<TData, TVariable> = InitStoreOptions<MutationState<T
|
|
|
90
90
|
* const result = await useCreateUser.execute({ name: 'John' });
|
|
91
91
|
*/
|
|
92
92
|
export declare const createMutation: <TData, TVariable = undefined>(mutationFn: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable>) => Promise<TData>, options?: MutationOptions<TData, TVariable>) => (<TStateSlice = MutationState<TData, TVariable>>(selector?: (state: MutationState<TData, TVariable>) => TStateSlice) => TStateSlice) & {
|
|
93
|
-
subscribe: (subscriber: import("
|
|
94
|
-
getSubscribers: () => Set<import("
|
|
93
|
+
subscribe: (subscriber: import("../vanilla.ts").Subscriber<MutationState<TData, TVariable>>) => () => void;
|
|
94
|
+
getSubscribers: () => Set<import("../vanilla.ts").Subscriber<MutationState<TData, TVariable>>>;
|
|
95
95
|
getState: () => MutationState<TData, TVariable>;
|
|
96
96
|
/**
|
|
97
97
|
* Manually updates the mutation state.
|
package/react/create-query.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitStoreOptions, type SetState } from 'floppy-disk';
|
|
1
|
+
import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
|
|
2
2
|
/**
|
|
3
3
|
* Represents the state of a query.
|
|
4
4
|
*
|
|
@@ -202,45 +202,58 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
202
202
|
/**
|
|
203
203
|
* Executes the query function.
|
|
204
204
|
*
|
|
205
|
-
* @param
|
|
205
|
+
* @param options - Execution options
|
|
206
|
+
* @param options.overwriteOngoingExecution - Whether to start a new execution instead of reusing an ongoing one (default: `true`)
|
|
206
207
|
*
|
|
207
208
|
* @returns A promise resolving to the latest query state
|
|
208
209
|
*
|
|
209
210
|
* @remarks
|
|
210
|
-
* -
|
|
211
|
-
* -
|
|
211
|
+
* - By default, each call starts a new execution even if one is already in progress.
|
|
212
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
212
213
|
* - Handles:
|
|
213
214
|
* - Pending state
|
|
214
215
|
* - Success state
|
|
215
216
|
* - Error state
|
|
216
217
|
* - Retry logic
|
|
217
218
|
*/
|
|
218
|
-
execute: (
|
|
219
|
+
execute: (options?: {
|
|
220
|
+
overwriteOngoingExecution?: boolean;
|
|
221
|
+
}) => Promise<QueryState<TData>>;
|
|
219
222
|
/**
|
|
220
|
-
* Re-executes the query if
|
|
223
|
+
* Re-executes the query if needed based on freshness or invalidation.
|
|
221
224
|
*
|
|
222
|
-
* @param
|
|
225
|
+
* @param options - Revalidation options
|
|
226
|
+
* @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
|
|
223
227
|
*
|
|
224
228
|
* @returns The current state if still fresh, otherwise a promise of the new state
|
|
225
229
|
*
|
|
226
230
|
* @remarks
|
|
227
|
-
* - Skips execution if data is still fresh (`staleTime`)
|
|
228
|
-
* -
|
|
231
|
+
* - Skips execution if data is still fresh (`staleTime`) **AND** the query has not been invalidated.
|
|
232
|
+
* - If execution is not skipped, by default it will start a new execution even if one is already in progress.
|
|
233
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
229
234
|
*/
|
|
230
|
-
revalidate: (
|
|
235
|
+
revalidate: (options?: {
|
|
236
|
+
overwriteOngoingExecution?: boolean;
|
|
237
|
+
}) => Promise<QueryState<TData>>;
|
|
231
238
|
/**
|
|
232
239
|
* Marks the query as invalidated and optionally triggers re-execution.
|
|
233
240
|
*
|
|
234
|
-
* @param
|
|
241
|
+
* @param options - Invalidation options
|
|
242
|
+
* @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
|
|
235
243
|
*
|
|
236
244
|
* @returns `true` if execution was triggered, `false` otherwise
|
|
237
245
|
*
|
|
238
246
|
* @remarks
|
|
239
247
|
* - Invalidated queries are treated as stale regardless of `staleTime`.
|
|
240
248
|
* - The next `revalidate` will always execute until a successful result clears the invalidation.
|
|
241
|
-
* - If there are active subscribers
|
|
249
|
+
* - If there are active subscribers: Execution is triggered immediately.
|
|
250
|
+
* - Otherwise: The query remains invalidated and will execute on the next revalidation.
|
|
251
|
+
* - By default, starts a new execution even if one is already in progress.
|
|
252
|
+
* - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
|
|
242
253
|
*/
|
|
243
|
-
invalidate: (
|
|
254
|
+
invalidate: (options?: {
|
|
255
|
+
overwriteOngoingExecution?: boolean;
|
|
256
|
+
}) => boolean;
|
|
244
257
|
/**
|
|
245
258
|
* Resets the query state to its initial state.
|
|
246
259
|
*
|
|
@@ -285,8 +298,8 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
285
298
|
* - Should be used if an optimistic update fails.
|
|
286
299
|
*/
|
|
287
300
|
rollbackOptimisticUpdate: () => TData;
|
|
288
|
-
subscribe: (subscriber: import("
|
|
289
|
-
getSubscribers: () => Set<import("
|
|
301
|
+
subscribe: (subscriber: import("../vanilla.ts").Subscriber<QueryState<TData>>) => () => void;
|
|
302
|
+
getSubscribers: () => Set<import("../vanilla.ts").Subscriber<QueryState<TData>>>;
|
|
290
303
|
getState: () => QueryState<TData>;
|
|
291
304
|
setState: (value: SetState<QueryState<TData>>) => void;
|
|
292
305
|
}) & {
|
|
@@ -296,14 +309,18 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
296
309
|
* @remarks
|
|
297
310
|
* - Useful for bulk refetching.
|
|
298
311
|
*/
|
|
299
|
-
executeAll: (
|
|
312
|
+
executeAll: (options?: {
|
|
313
|
+
overwriteOngoingExecution?: boolean;
|
|
314
|
+
}) => void;
|
|
300
315
|
/**
|
|
301
316
|
* Revalidates all query instances.
|
|
302
317
|
*
|
|
303
318
|
* @remarks
|
|
304
319
|
* - Only re-fetches stale queries.
|
|
305
320
|
*/
|
|
306
|
-
revalidateAll: (
|
|
321
|
+
revalidateAll: (options?: {
|
|
322
|
+
overwriteOngoingExecution?: boolean;
|
|
323
|
+
}) => void;
|
|
307
324
|
/**
|
|
308
325
|
* Invalidates all query instances.
|
|
309
326
|
*
|
|
@@ -311,7 +328,9 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
311
328
|
* - Marks all queries as invalidated and triggers revalidation if active.
|
|
312
329
|
* - Invalidated queries bypass `staleTime` until successfully executed again.
|
|
313
330
|
*/
|
|
314
|
-
invalidateAll: (
|
|
331
|
+
invalidateAll: (options?: {
|
|
332
|
+
overwriteOngoingExecution?: boolean;
|
|
333
|
+
}) => void;
|
|
315
334
|
/**
|
|
316
335
|
* Resets all query instances.
|
|
317
336
|
*/
|
package/react/create-store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitStoreOptions } from 'floppy-disk';
|
|
1
|
+
import { type InitStoreOptions } from 'floppy-disk/vanilla';
|
|
2
2
|
/**
|
|
3
3
|
* Creates a single store with a bound React hook.
|
|
4
4
|
*
|
|
@@ -22,4 +22,4 @@ import { type InitStoreOptions } from 'floppy-disk';
|
|
|
22
22
|
*
|
|
23
23
|
* useCounter.setState({ count: 1 });
|
|
24
24
|
*/
|
|
25
|
-
export declare const createStore: <TState extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => (<TStateSlice = TState>(selector?: (state: TState) => TStateSlice) => TStateSlice) & import("
|
|
25
|
+
export declare const createStore: <TState extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => (<TStateSlice = TState>(selector?: (state: TState) => TStateSlice) => TStateSlice) & import("../vanilla.ts").StoreApi<TState>;
|
package/react/create-stores.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InitStoreOptions } from 'floppy-disk';
|
|
1
|
+
import { type InitStoreOptions } from 'floppy-disk/vanilla';
|
|
2
2
|
/**
|
|
3
3
|
* Creates a factory for multiple stores identified by a key.
|
|
4
4
|
*
|
|
@@ -25,8 +25,8 @@ import { type InitStoreOptions } from 'floppy-disk';
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const createStores: <TState extends Record<string, any>, TKey extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => (key?: TKey) => (<TStateSlice = TState>(selector?: (state: TState) => TStateSlice) => TStateSlice) & {
|
|
27
27
|
delete: () => boolean;
|
|
28
|
-
setState: (value: import("
|
|
28
|
+
setState: (value: import("../vanilla.ts").SetState<TState>) => void;
|
|
29
29
|
getState: () => TState;
|
|
30
|
-
subscribe: (subscriber: import("
|
|
31
|
-
getSubscribers: () => Set<import("
|
|
30
|
+
subscribe: (subscriber: import("../vanilla.ts").Subscriber<TState>) => () => void;
|
|
31
|
+
getSubscribers: () => Set<import("../vanilla.ts").Subscriber<TState>>;
|
|
32
32
|
};
|
package/react/use-store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StoreApi } from 'floppy-disk';
|
|
1
|
+
import { type StoreApi } from 'floppy-disk/vanilla';
|
|
2
2
|
export declare const useStoreUpdateNotifier: <TState extends Record<string, any>, TStateSlice = TState>(store: StoreApi<TState>, selector: (state: TState) => TStateSlice) => void;
|
|
3
3
|
/**
|
|
4
4
|
* React hook for subscribing to a store with optional state selection.
|
package/react.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
-
var
|
|
4
|
+
var vanilla = require('floppy-disk/vanilla');
|
|
5
5
|
|
|
6
|
-
const useIsomorphicLayoutEffect =
|
|
6
|
+
const useIsomorphicLayoutEffect = vanilla.isClient ? react.useLayoutEffect : react.useEffect;
|
|
7
7
|
|
|
8
8
|
const useStoreUpdateNotifier = (store, selector) => {
|
|
9
9
|
const [, reRender] = react.useState({});
|
|
@@ -11,21 +11,21 @@ const useStoreUpdateNotifier = (store, selector) => {
|
|
|
11
11
|
selectorRef.current = selector;
|
|
12
12
|
useIsomorphicLayoutEffect(
|
|
13
13
|
() => store.subscribe((state, prevState) => {
|
|
14
|
-
if (selectorRef.current ===
|
|
14
|
+
if (selectorRef.current === vanilla.identity) return reRender({});
|
|
15
15
|
const prevSlice = selectorRef.current(prevState);
|
|
16
16
|
const nextSlice = selectorRef.current(state);
|
|
17
|
-
if (!
|
|
17
|
+
if (!vanilla.shallow(prevSlice, nextSlice)) reRender({});
|
|
18
18
|
}),
|
|
19
19
|
[store]
|
|
20
20
|
);
|
|
21
21
|
};
|
|
22
|
-
const useStoreState = (store, selector =
|
|
22
|
+
const useStoreState = (store, selector = vanilla.identity) => {
|
|
23
23
|
useStoreUpdateNotifier(store, selector);
|
|
24
24
|
return selector(store.getState());
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const createStore = (initialState, options) => {
|
|
28
|
-
const store =
|
|
28
|
+
const store = vanilla.initStore(initialState, options);
|
|
29
29
|
const useStore = (selector) => useStoreState(store, selector);
|
|
30
30
|
return Object.assign(useStore, store);
|
|
31
31
|
};
|
|
@@ -33,12 +33,12 @@ const createStore = (initialState, options) => {
|
|
|
33
33
|
const createStores = (initialState, options) => {
|
|
34
34
|
const stores = /* @__PURE__ */ new Map();
|
|
35
35
|
const getStore = (key = {}) => {
|
|
36
|
-
const keyHash =
|
|
36
|
+
const keyHash = vanilla.getHash(key);
|
|
37
37
|
let store;
|
|
38
38
|
if (stores.has(keyHash)) {
|
|
39
39
|
store = stores.get(keyHash);
|
|
40
40
|
} else {
|
|
41
|
-
store =
|
|
41
|
+
store = vanilla.initStore(initialState, options);
|
|
42
42
|
stores.set(keyHash, store);
|
|
43
43
|
}
|
|
44
44
|
const useStore = (selector) => {
|
|
@@ -82,9 +82,9 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
82
82
|
// 5 minutes
|
|
83
83
|
revalidateOnFocus = true,
|
|
84
84
|
revalidateOnReconnect = true,
|
|
85
|
-
onSuccess =
|
|
85
|
+
onSuccess = vanilla.noop,
|
|
86
86
|
onError,
|
|
87
|
-
onSettled =
|
|
87
|
+
onSettled = vanilla.noop,
|
|
88
88
|
shouldRetry: shouldRetryFn = (_, s) => s.retryCount === 0 ? [true, 1500] : [false]
|
|
89
89
|
} = options;
|
|
90
90
|
const initialState = INITIAL_STATE$1;
|
|
@@ -96,7 +96,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
96
96
|
(_a = options.onFirstSubscribe) == null ? void 0 : _a.call(options, state, store);
|
|
97
97
|
const { metadata, revalidate: revalidate2 } = internals.get(store);
|
|
98
98
|
clearTimeout(metadata.garbageCollectionTimeoutId);
|
|
99
|
-
if (
|
|
99
|
+
if (vanilla.isClient) {
|
|
100
100
|
if (revalidateOnFocus) {
|
|
101
101
|
focusListeners.add(revalidate2);
|
|
102
102
|
if (!focusListenersAdded) {
|
|
@@ -123,7 +123,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
123
123
|
metadata.garbageCollectionTimeoutId = setTimeout(() => {
|
|
124
124
|
store.setState(initialState);
|
|
125
125
|
}, gcTime);
|
|
126
|
-
if (
|
|
126
|
+
if (vanilla.isClient) {
|
|
127
127
|
if (revalidateOnFocus) {
|
|
128
128
|
focusListeners.delete(revalidate2);
|
|
129
129
|
if (focusListeners.size === 0) {
|
|
@@ -159,17 +159,17 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
159
159
|
}
|
|
160
160
|
return false;
|
|
161
161
|
},
|
|
162
|
-
execute: (overwriteOngoingExecution =
|
|
162
|
+
execute: ({ overwriteOngoingExecution = true } = {}) => {
|
|
163
163
|
return execute(store, variable, overwriteOngoingExecution);
|
|
164
164
|
},
|
|
165
|
-
revalidate: (overwriteOngoingExecution =
|
|
165
|
+
revalidate: ({ overwriteOngoingExecution = true } = {}) => {
|
|
166
166
|
return revalidate(store, variable, overwriteOngoingExecution);
|
|
167
167
|
},
|
|
168
|
-
invalidate: (
|
|
168
|
+
invalidate: (options2) => {
|
|
169
169
|
const { metadata } = internals.get(store);
|
|
170
170
|
metadata.isInvalidated = true;
|
|
171
171
|
if (store.getSubscribers().size > 0) {
|
|
172
|
-
internals.get(store).execute(
|
|
172
|
+
internals.get(store).execute(options2);
|
|
173
173
|
return true;
|
|
174
174
|
}
|
|
175
175
|
return false;
|
|
@@ -316,19 +316,19 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
316
316
|
return execute(store, variable, overwriteOngoingExecution);
|
|
317
317
|
};
|
|
318
318
|
const getStore = (variable = {}) => {
|
|
319
|
-
const variableHash =
|
|
319
|
+
const variableHash = vanilla.getHash(variable);
|
|
320
320
|
let store;
|
|
321
321
|
if (stores.has(variableHash)) {
|
|
322
322
|
store = stores.get(variableHash);
|
|
323
323
|
} else {
|
|
324
|
-
store =
|
|
324
|
+
store = vanilla.initStore(initialState, configureStoreEvents());
|
|
325
325
|
stores.set(variableHash, store);
|
|
326
326
|
internals.set(store, configureInternals(store, variable, variableHash));
|
|
327
327
|
}
|
|
328
|
-
const useStore = (options2 = {}, selector =
|
|
328
|
+
const useStore = (options2 = {}, selector = vanilla.identity) => {
|
|
329
329
|
useStoreUpdateNotifier(store, selector);
|
|
330
330
|
useIsomorphicLayoutEffect(() => {
|
|
331
|
-
if (options2.enabled !== false) revalidate(store, variable);
|
|
331
|
+
if (options2.enabled !== false) revalidate(store, variable, false);
|
|
332
332
|
}, [store, options2.enabled]);
|
|
333
333
|
const storeState = store.getState();
|
|
334
334
|
let storeStateToBeUsed = storeState;
|
|
@@ -358,8 +358,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
358
358
|
* @remarks
|
|
359
359
|
* - Useful for bulk refetching.
|
|
360
360
|
*/
|
|
361
|
-
executeAll: (
|
|
362
|
-
stores.forEach((store) => internals.get(store).execute(
|
|
361
|
+
executeAll: (options2) => {
|
|
362
|
+
stores.forEach((store) => internals.get(store).execute(options2));
|
|
363
363
|
},
|
|
364
364
|
/**
|
|
365
365
|
* Revalidates all query instances.
|
|
@@ -367,8 +367,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
367
367
|
* @remarks
|
|
368
368
|
* - Only re-fetches stale queries.
|
|
369
369
|
*/
|
|
370
|
-
revalidateAll: (
|
|
371
|
-
stores.forEach((store) => internals.get(store).revalidate(
|
|
370
|
+
revalidateAll: (options2) => {
|
|
371
|
+
stores.forEach((store) => internals.get(store).revalidate(options2));
|
|
372
372
|
},
|
|
373
373
|
/**
|
|
374
374
|
* Invalidates all query instances.
|
|
@@ -377,8 +377,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
377
377
|
* - Marks all queries as invalidated and triggers revalidation if active.
|
|
378
378
|
* - Invalidated queries bypass `staleTime` until successfully executed again.
|
|
379
379
|
*/
|
|
380
|
-
invalidateAll: (
|
|
381
|
-
stores.forEach((store) => internals.get(store).invalidate(
|
|
380
|
+
invalidateAll: (options2) => {
|
|
381
|
+
stores.forEach((store) => internals.get(store).invalidate(options2));
|
|
382
382
|
},
|
|
383
383
|
/**
|
|
384
384
|
* Resets all query instances.
|
|
@@ -407,9 +407,9 @@ const INITIAL_STATE = {
|
|
|
407
407
|
errorUpdatedAt: void 0
|
|
408
408
|
};
|
|
409
409
|
const createMutation = (mutationFn, options = {}) => {
|
|
410
|
-
const { onSuccess =
|
|
410
|
+
const { onSuccess = vanilla.noop, onError, onSettled = vanilla.noop } = options;
|
|
411
411
|
const initialState = INITIAL_STATE;
|
|
412
|
-
const store =
|
|
412
|
+
const store = vanilla.initStore(initialState, options);
|
|
413
413
|
const useStore = (selector) => useStoreState(store, selector);
|
|
414
414
|
const execute = (variable) => {
|
|
415
415
|
const stateBeforeExecute = store.getState();
|
package/vanilla/store.d.ts
CHANGED
|
@@ -13,17 +13,18 @@ export type SetState<TState> = Partial<TState> | ((state: TState) => Partial<TSt
|
|
|
13
13
|
* @param prevState - The previous state before the update
|
|
14
14
|
*
|
|
15
15
|
* @remarks
|
|
16
|
-
* - Subscribers are
|
|
17
|
-
* -
|
|
16
|
+
* - Subscribers are only called when the state actually changes.
|
|
17
|
+
* - Change detection is performed per key using `Object.is`.
|
|
18
18
|
*/
|
|
19
19
|
export type Subscriber<TState> = (state: TState, prevState: TState) => void;
|
|
20
20
|
/**
|
|
21
21
|
* Core store API for managing state.
|
|
22
22
|
*
|
|
23
23
|
* @remarks
|
|
24
|
-
* - The store
|
|
25
|
-
* -
|
|
24
|
+
* - The store performs **shallow change detection per key** before notifying subscribers.
|
|
25
|
+
* - Subscribers are only notified when at least one field changes.
|
|
26
26
|
* - Designed to be framework-agnostic (React bindings are built separately).
|
|
27
|
+
* - By default, `setState` is **disabled on the server** to prevent accidental shared state between requests.
|
|
27
28
|
*/
|
|
28
29
|
export type StoreApi<TState extends Record<string, any>> = {
|
|
29
30
|
setState: (value: SetState<TState>) => void;
|
|
@@ -47,6 +48,7 @@ export type InitStoreOptions<TState extends Record<string, any>> = {
|
|
|
47
48
|
onSubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
48
49
|
onUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
49
50
|
onLastUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
|
|
51
|
+
allowSetStateServerSide?: boolean;
|
|
50
52
|
};
|
|
51
53
|
/**
|
|
52
54
|
* Creates a vanilla store with pub-sub capabilities.
|
|
@@ -64,6 +66,9 @@ export type InitStoreOptions<TState extends Record<string, any>> = {
|
|
|
64
66
|
* - Subscribers are only notified when at least one updated field changes (using `Object.is` comparison).
|
|
65
67
|
* - Subscribers receive both the new state and the previous state.
|
|
66
68
|
* - Lifecycle hooks allow side-effect management tied to subscription count.
|
|
69
|
+
* - By default, `setState` is **disabled on the server** to prevent accidental shared state between requests.
|
|
70
|
+
* - This avoids leaking data between users in server environments.
|
|
71
|
+
* - You can override this by setting `allowSetStateServerSide: true`.
|
|
67
72
|
*
|
|
68
73
|
* @example
|
|
69
74
|
* const store = initStore({ count: 0 });
|
package/vanilla.js
CHANGED
|
@@ -72,7 +72,8 @@ const initStore = (initialState, options = {}) => {
|
|
|
72
72
|
onFirstSubscribe = noop,
|
|
73
73
|
onSubscribe = noop,
|
|
74
74
|
onUnsubscribe = noop,
|
|
75
|
-
onLastUnsubscribe = noop
|
|
75
|
+
onLastUnsubscribe = noop,
|
|
76
|
+
allowSetStateServerSide = false
|
|
76
77
|
} = options;
|
|
77
78
|
const subscribers = /* @__PURE__ */ new Set();
|
|
78
79
|
const getSubscribers = () => subscribers;
|
|
@@ -89,6 +90,12 @@ const initStore = (initialState, options = {}) => {
|
|
|
89
90
|
let state = initialState;
|
|
90
91
|
const getState = () => state;
|
|
91
92
|
const setState = (value) => {
|
|
93
|
+
if (!isClient && !allowSetStateServerSide) {
|
|
94
|
+
console.error(
|
|
95
|
+
"setState on the server is not allowed by default. Set `allowSetStateServerSide: true` to allow it."
|
|
96
|
+
);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
92
99
|
const prevState = state;
|
|
93
100
|
const newValue = getValue(value, state);
|
|
94
101
|
for (const key in newValue) {
|