floppy-disk 3.0.0-alpha.2 → 3.0.0-alpha.4

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.
@@ -153,30 +153,33 @@ export type QueryOptions<TData, TVariable extends Record<string, any>> = InitSto
153
153
  * // ...
154
154
  * }
155
155
  */
156
- export declare const createQuery: <TData, TVariable extends Record<string, any> = never>(queryFn: (variable: TVariable, currentState: QueryState<TData>) => Promise<TData>, options?: QueryOptions<TData, TVariable>) => ((variable?: TVariable) => (<TStateSlice = QueryState<TData>>(options?: {
157
- /**
158
- * Whether the query should execute automatically on mount.
159
- *
160
- * @default true
161
- */
162
- enabled?: boolean;
163
- /**
164
- * Whether to keep previous successful data while a new variable is loading.
165
- *
166
- * @remarks
167
- * - Only applies when the query is in the `INITIAL` state (no data & no error).
168
- * - Intended for variable changes:
169
- * when switching from one variable to another, the previous data is temporarily shown
170
- * while the new execution is in progress.
171
- * - Once the new execution resolves (success or error), the previous data is no longer used.
172
- * - Prevents UI flicker (e.g. empty/loading state) during transitions.
173
- *
174
- * @example
175
- * // Switching from userId=1 → userId=2
176
- * // While loading userId=2, still show userId=1 data
177
- * useQuery({ id: userId }, { keepPreviousData: true });
178
- */ keepPreviousData?: boolean;
179
- }, selector?: (state: QueryState<TData>) => TStateSlice) => TStateSlice) & {
156
+ export declare const createQuery: <TData, TVariable extends Record<string, any> = never>(queryFn: (variable: TVariable, currentState: QueryState<TData>) => Promise<TData>, options?: QueryOptions<TData, TVariable>) => ((variable?: TVariable) => {
157
+ <TStateSlice = QueryState<TData>>(options?: {
158
+ /**
159
+ * Whether the query should execute automatically on mount.
160
+ *
161
+ * @default true
162
+ */
163
+ enabled?: boolean;
164
+ /**
165
+ * Whether to keep previous successful data while a new variable is loading.
166
+ *
167
+ * @remarks
168
+ * - Only applies when the query is in the `INITIAL` state (no data & no error).
169
+ * - Intended for variable changes:
170
+ * when switching from one variable to another, the previous data is temporarily shown
171
+ * while the new execution is in progress.
172
+ * - Once the new execution resolves (success or error), the previous data is no longer used.
173
+ * - Prevents UI flicker (e.g. empty/loading state) during transitions.
174
+ *
175
+ * @example
176
+ * // Switching from userId=1 userId=2
177
+ * // While loading userId=2, still show userId=1 data
178
+ * useQuery({ id: userId }, { keepPreviousData: true });
179
+ */ keepPreviousData?: boolean;
180
+ }, selector?: (state: QueryState<TData>) => TStateSlice): TStateSlice;
181
+ <TStateSlice = QueryState<TData>>(selector?: (state: QueryState<TData>) => TStateSlice): TStateSlice;
182
+ } & {
180
183
  metadata: {
181
184
  isInvalidated?: boolean;
182
185
  promise?: Promise<QueryState<TData>> | undefined;
@@ -202,45 +205,58 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
202
205
  /**
203
206
  * Executes the query function.
204
207
  *
205
- * @param overwriteOngoingExecution - Whether to start a new execution instead of reusing an ongoing one
208
+ * @param options - Execution options
209
+ * @param options.overwriteOngoingExecution - Whether to start a new execution instead of reusing an ongoing one (default: `true`)
206
210
  *
207
211
  * @returns A promise resolving to the latest query state
208
212
  *
209
213
  * @remarks
210
- * - Deduplicates concurrent executions by default.
211
- * - If `overwriteOngoingExecution` is true, a new execution replaces the current one.
214
+ * - By default, each call starts a new execution even if one is already in progress.
215
+ * - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
212
216
  * - Handles:
213
217
  * - Pending state
214
218
  * - Success state
215
219
  * - Error state
216
220
  * - Retry logic
217
221
  */
218
- execute: (overwriteOngoingExecution?: boolean) => Promise<QueryState<TData>>;
222
+ execute: (options?: {
223
+ overwriteOngoingExecution?: boolean;
224
+ }) => Promise<QueryState<TData>>;
219
225
  /**
220
- * Re-executes the query if the data is stale.
226
+ * Re-executes the query if needed based on freshness or invalidation.
221
227
  *
222
- * @param overwriteOngoingExecution - Whether to overwrite an ongoing execution
228
+ * @param options - Revalidation options
229
+ * @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
223
230
  *
224
231
  * @returns The current state if still fresh, otherwise a promise of the new state
225
232
  *
226
233
  * @remarks
227
- * - Skips execution if data is still fresh (`staleTime`) and the query has not been invalidated.
228
- * - Used for automatic revalidation (e.g. focus or reconnect).
234
+ * - Skips execution if data is still fresh (`staleTime`) **AND** the query has not been invalidated.
235
+ * - If execution is not skipped, by default it will start a new execution even if one is already in progress.
236
+ * - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
229
237
  */
230
- revalidate: (overwriteOngoingExecution?: boolean) => Promise<QueryState<TData>>;
238
+ revalidate: (options?: {
239
+ overwriteOngoingExecution?: boolean;
240
+ }) => Promise<QueryState<TData>>;
231
241
  /**
232
242
  * Marks the query as invalidated and optionally triggers re-execution.
233
243
  *
234
- * @param overwriteOngoingExecution - Whether to overwrite an ongoing execution
244
+ * @param options - Invalidation options
245
+ * @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
235
246
  *
236
247
  * @returns `true` if execution was triggered, `false` otherwise
237
248
  *
238
249
  * @remarks
239
250
  * - Invalidated queries are treated as stale regardless of `staleTime`.
240
251
  * - The next `revalidate` will always execute until a successful result clears the invalidation.
241
- * - If there are active subscribers, execution will be triggered immediately.
252
+ * - If there are active subscribers: Execution is triggered immediately.
253
+ * - Otherwise: The query remains invalidated and will execute on the next revalidation.
254
+ * - By default, starts a new execution even if one is already in progress.
255
+ * - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
242
256
  */
243
- invalidate: (overwriteOngoingExecution?: boolean) => boolean;
257
+ invalidate: (options?: {
258
+ overwriteOngoingExecution?: boolean;
259
+ }) => boolean;
244
260
  /**
245
261
  * Resets the query state to its initial state.
246
262
  *
@@ -296,14 +312,18 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
296
312
  * @remarks
297
313
  * - Useful for bulk refetching.
298
314
  */
299
- executeAll: (overwriteOngoingExecution?: boolean) => void;
315
+ executeAll: (options?: {
316
+ overwriteOngoingExecution?: boolean;
317
+ }) => void;
300
318
  /**
301
319
  * Revalidates all query instances.
302
320
  *
303
321
  * @remarks
304
322
  * - Only re-fetches stale queries.
305
323
  */
306
- revalidateAll: (overwriteOngoingExecution?: boolean) => void;
324
+ revalidateAll: (options?: {
325
+ overwriteOngoingExecution?: boolean;
326
+ }) => void;
307
327
  /**
308
328
  * Invalidates all query instances.
309
329
  *
@@ -311,7 +331,9 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
311
331
  * - Marks all queries as invalidated and triggers revalidation if active.
312
332
  * - Invalidated queries bypass `staleTime` until successfully executed again.
313
333
  */
314
- invalidateAll: (overwriteOngoingExecution?: boolean) => void;
334
+ invalidateAll: (options?: {
335
+ overwriteOngoingExecution?: boolean;
336
+ }) => void;
315
337
  /**
316
338
  * Resets all query instances.
317
339
  */
package/esm/react.mjs CHANGED
@@ -157,17 +157,17 @@ const createQuery = (queryFn, options = {}) => {
157
157
  }
158
158
  return false;
159
159
  },
160
- execute: (overwriteOngoingExecution = false) => {
160
+ execute: ({ overwriteOngoingExecution = true } = {}) => {
161
161
  return execute(store, variable, overwriteOngoingExecution);
162
162
  },
163
- revalidate: (overwriteOngoingExecution = false) => {
163
+ revalidate: ({ overwriteOngoingExecution = true } = {}) => {
164
164
  return revalidate(store, variable, overwriteOngoingExecution);
165
165
  },
166
- invalidate: (overwriteOngoingExecution = false) => {
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(overwriteOngoingExecution);
170
+ internals.get(store).execute(options2);
171
171
  return true;
172
172
  }
173
173
  return false;
@@ -323,10 +323,19 @@ const createQuery = (queryFn, options = {}) => {
323
323
  stores.set(variableHash, store);
324
324
  internals.set(store, configureInternals(store, variable, variableHash));
325
325
  }
326
- const useStore = (options2 = {}, selector = identity) => {
326
+ function useStore(optionsOrSelector = {}, maybeSelector) {
327
+ let selector;
328
+ let options2;
329
+ if (typeof optionsOrSelector === "function") {
330
+ options2 = {};
331
+ selector = optionsOrSelector;
332
+ } else {
333
+ options2 = optionsOrSelector;
334
+ selector = maybeSelector || identity;
335
+ }
327
336
  useStoreUpdateNotifier(store, selector);
328
337
  useIsomorphicLayoutEffect(() => {
329
- if (options2.enabled !== false) revalidate(store, variable);
338
+ if (options2.enabled !== false) revalidate(store, variable, false);
330
339
  }, [store, options2.enabled]);
331
340
  const storeState = store.getState();
332
341
  let storeStateToBeUsed = storeState;
@@ -337,7 +346,7 @@ const createQuery = (queryFn, options = {}) => {
337
346
  storeStateToBeUsed = { ...storeState, ...prevState.current };
338
347
  }
339
348
  return selector(storeStateToBeUsed);
340
- };
349
+ }
341
350
  return Object.assign(useStore, {
342
351
  subscribe: store.subscribe,
343
352
  getSubscribers: store.getSubscribers,
@@ -356,8 +365,8 @@ const createQuery = (queryFn, options = {}) => {
356
365
  * @remarks
357
366
  * - Useful for bulk refetching.
358
367
  */
359
- executeAll: (overwriteOngoingExecution) => {
360
- stores.forEach((store) => internals.get(store).execute(overwriteOngoingExecution));
368
+ executeAll: (options2) => {
369
+ stores.forEach((store) => internals.get(store).execute(options2));
361
370
  },
362
371
  /**
363
372
  * Revalidates all query instances.
@@ -365,8 +374,8 @@ const createQuery = (queryFn, options = {}) => {
365
374
  * @remarks
366
375
  * - Only re-fetches stale queries.
367
376
  */
368
- revalidateAll: (overwriteOngoingExecution) => {
369
- stores.forEach((store) => internals.get(store).revalidate(overwriteOngoingExecution));
377
+ revalidateAll: (options2) => {
378
+ stores.forEach((store) => internals.get(store).revalidate(options2));
370
379
  },
371
380
  /**
372
381
  * Invalidates all query instances.
@@ -375,8 +384,8 @@ const createQuery = (queryFn, options = {}) => {
375
384
  * - Marks all queries as invalidated and triggers revalidation if active.
376
385
  * - Invalidated queries bypass `staleTime` until successfully executed again.
377
386
  */
378
- invalidateAll: (overwriteOngoingExecution) => {
379
- stores.forEach((store) => internals.get(store).invalidate(overwriteOngoingExecution));
387
+ invalidateAll: (options2) => {
388
+ stores.forEach((store) => internals.get(store).invalidate(options2));
380
389
  },
381
390
  /**
382
391
  * Resets all query instances.
@@ -24,6 +24,7 @@ export type Subscriber<TState> = (state: TState, prevState: TState) => void;
24
24
  * - The store performs **shallow change detection per key** before notifying subscribers.
25
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
@@ -2,7 +2,7 @@
2
2
  "name": "floppy-disk",
3
3
  "description": "Lightweight, simple, and powerful state management library",
4
4
  "private": false,
5
- "version": "3.0.0-alpha.2",
5
+ "version": "3.0.0-alpha.4",
6
6
  "publishConfig": {
7
7
  "tag": "alpha"
8
8
  },
@@ -153,30 +153,33 @@ export type QueryOptions<TData, TVariable extends Record<string, any>> = InitSto
153
153
  * // ...
154
154
  * }
155
155
  */
156
- export declare const createQuery: <TData, TVariable extends Record<string, any> = never>(queryFn: (variable: TVariable, currentState: QueryState<TData>) => Promise<TData>, options?: QueryOptions<TData, TVariable>) => ((variable?: TVariable) => (<TStateSlice = QueryState<TData>>(options?: {
157
- /**
158
- * Whether the query should execute automatically on mount.
159
- *
160
- * @default true
161
- */
162
- enabled?: boolean;
163
- /**
164
- * Whether to keep previous successful data while a new variable is loading.
165
- *
166
- * @remarks
167
- * - Only applies when the query is in the `INITIAL` state (no data & no error).
168
- * - Intended for variable changes:
169
- * when switching from one variable to another, the previous data is temporarily shown
170
- * while the new execution is in progress.
171
- * - Once the new execution resolves (success or error), the previous data is no longer used.
172
- * - Prevents UI flicker (e.g. empty/loading state) during transitions.
173
- *
174
- * @example
175
- * // Switching from userId=1 → userId=2
176
- * // While loading userId=2, still show userId=1 data
177
- * useQuery({ id: userId }, { keepPreviousData: true });
178
- */ keepPreviousData?: boolean;
179
- }, selector?: (state: QueryState<TData>) => TStateSlice) => TStateSlice) & {
156
+ export declare const createQuery: <TData, TVariable extends Record<string, any> = never>(queryFn: (variable: TVariable, currentState: QueryState<TData>) => Promise<TData>, options?: QueryOptions<TData, TVariable>) => ((variable?: TVariable) => {
157
+ <TStateSlice = QueryState<TData>>(options?: {
158
+ /**
159
+ * Whether the query should execute automatically on mount.
160
+ *
161
+ * @default true
162
+ */
163
+ enabled?: boolean;
164
+ /**
165
+ * Whether to keep previous successful data while a new variable is loading.
166
+ *
167
+ * @remarks
168
+ * - Only applies when the query is in the `INITIAL` state (no data & no error).
169
+ * - Intended for variable changes:
170
+ * when switching from one variable to another, the previous data is temporarily shown
171
+ * while the new execution is in progress.
172
+ * - Once the new execution resolves (success or error), the previous data is no longer used.
173
+ * - Prevents UI flicker (e.g. empty/loading state) during transitions.
174
+ *
175
+ * @example
176
+ * // Switching from userId=1 userId=2
177
+ * // While loading userId=2, still show userId=1 data
178
+ * useQuery({ id: userId }, { keepPreviousData: true });
179
+ */ keepPreviousData?: boolean;
180
+ }, selector?: (state: QueryState<TData>) => TStateSlice): TStateSlice;
181
+ <TStateSlice = QueryState<TData>>(selector?: (state: QueryState<TData>) => TStateSlice): TStateSlice;
182
+ } & {
180
183
  metadata: {
181
184
  isInvalidated?: boolean;
182
185
  promise?: Promise<QueryState<TData>> | undefined;
@@ -202,45 +205,58 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
202
205
  /**
203
206
  * Executes the query function.
204
207
  *
205
- * @param overwriteOngoingExecution - Whether to start a new execution instead of reusing an ongoing one
208
+ * @param options - Execution options
209
+ * @param options.overwriteOngoingExecution - Whether to start a new execution instead of reusing an ongoing one (default: `true`)
206
210
  *
207
211
  * @returns A promise resolving to the latest query state
208
212
  *
209
213
  * @remarks
210
- * - Deduplicates concurrent executions by default.
211
- * - If `overwriteOngoingExecution` is true, a new execution replaces the current one.
214
+ * - By default, each call starts a new execution even if one is already in progress.
215
+ * - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
212
216
  * - Handles:
213
217
  * - Pending state
214
218
  * - Success state
215
219
  * - Error state
216
220
  * - Retry logic
217
221
  */
218
- execute: (overwriteOngoingExecution?: boolean) => Promise<QueryState<TData>>;
222
+ execute: (options?: {
223
+ overwriteOngoingExecution?: boolean;
224
+ }) => Promise<QueryState<TData>>;
219
225
  /**
220
- * Re-executes the query if the data is stale.
226
+ * Re-executes the query if needed based on freshness or invalidation.
221
227
  *
222
- * @param overwriteOngoingExecution - Whether to overwrite an ongoing execution
228
+ * @param options - Revalidation options
229
+ * @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
223
230
  *
224
231
  * @returns The current state if still fresh, otherwise a promise of the new state
225
232
  *
226
233
  * @remarks
227
- * - Skips execution if data is still fresh (`staleTime`) and the query has not been invalidated.
228
- * - Used for automatic revalidation (e.g. focus or reconnect).
234
+ * - Skips execution if data is still fresh (`staleTime`) **AND** the query has not been invalidated.
235
+ * - If execution is not skipped, by default it will start a new execution even if one is already in progress.
236
+ * - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
229
237
  */
230
- revalidate: (overwriteOngoingExecution?: boolean) => Promise<QueryState<TData>>;
238
+ revalidate: (options?: {
239
+ overwriteOngoingExecution?: boolean;
240
+ }) => Promise<QueryState<TData>>;
231
241
  /**
232
242
  * Marks the query as invalidated and optionally triggers re-execution.
233
243
  *
234
- * @param overwriteOngoingExecution - Whether to overwrite an ongoing execution
244
+ * @param options - Invalidation options
245
+ * @param options.overwriteOngoingExecution - Whether to overwrite an ongoing execution (default: `true`)
235
246
  *
236
247
  * @returns `true` if execution was triggered, `false` otherwise
237
248
  *
238
249
  * @remarks
239
250
  * - Invalidated queries are treated as stale regardless of `staleTime`.
240
251
  * - The next `revalidate` will always execute until a successful result clears the invalidation.
241
- * - If there are active subscribers, execution will be triggered immediately.
252
+ * - If there are active subscribers: Execution is triggered immediately.
253
+ * - Otherwise: The query remains invalidated and will execute on the next revalidation.
254
+ * - By default, starts a new execution even if one is already in progress.
255
+ * - Set `overwriteOngoingExecution: false` to reuse an ongoing execution (deduplication).
242
256
  */
243
- invalidate: (overwriteOngoingExecution?: boolean) => boolean;
257
+ invalidate: (options?: {
258
+ overwriteOngoingExecution?: boolean;
259
+ }) => boolean;
244
260
  /**
245
261
  * Resets the query state to its initial state.
246
262
  *
@@ -296,14 +312,18 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
296
312
  * @remarks
297
313
  * - Useful for bulk refetching.
298
314
  */
299
- executeAll: (overwriteOngoingExecution?: boolean) => void;
315
+ executeAll: (options?: {
316
+ overwriteOngoingExecution?: boolean;
317
+ }) => void;
300
318
  /**
301
319
  * Revalidates all query instances.
302
320
  *
303
321
  * @remarks
304
322
  * - Only re-fetches stale queries.
305
323
  */
306
- revalidateAll: (overwriteOngoingExecution?: boolean) => void;
324
+ revalidateAll: (options?: {
325
+ overwriteOngoingExecution?: boolean;
326
+ }) => void;
307
327
  /**
308
328
  * Invalidates all query instances.
309
329
  *
@@ -311,7 +331,9 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
311
331
  * - Marks all queries as invalidated and triggers revalidation if active.
312
332
  * - Invalidated queries bypass `staleTime` until successfully executed again.
313
333
  */
314
- invalidateAll: (overwriteOngoingExecution?: boolean) => void;
334
+ invalidateAll: (options?: {
335
+ overwriteOngoingExecution?: boolean;
336
+ }) => void;
315
337
  /**
316
338
  * Resets all query instances.
317
339
  */
package/react.js CHANGED
@@ -159,17 +159,17 @@ const createQuery = (queryFn, options = {}) => {
159
159
  }
160
160
  return false;
161
161
  },
162
- execute: (overwriteOngoingExecution = false) => {
162
+ execute: ({ overwriteOngoingExecution = true } = {}) => {
163
163
  return execute(store, variable, overwriteOngoingExecution);
164
164
  },
165
- revalidate: (overwriteOngoingExecution = false) => {
165
+ revalidate: ({ overwriteOngoingExecution = true } = {}) => {
166
166
  return revalidate(store, variable, overwriteOngoingExecution);
167
167
  },
168
- invalidate: (overwriteOngoingExecution = false) => {
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(overwriteOngoingExecution);
172
+ internals.get(store).execute(options2);
173
173
  return true;
174
174
  }
175
175
  return false;
@@ -325,10 +325,19 @@ const createQuery = (queryFn, options = {}) => {
325
325
  stores.set(variableHash, store);
326
326
  internals.set(store, configureInternals(store, variable, variableHash));
327
327
  }
328
- const useStore = (options2 = {}, selector = vanilla.identity) => {
328
+ function useStore(optionsOrSelector = {}, maybeSelector) {
329
+ let selector;
330
+ let options2;
331
+ if (typeof optionsOrSelector === "function") {
332
+ options2 = {};
333
+ selector = optionsOrSelector;
334
+ } else {
335
+ options2 = optionsOrSelector;
336
+ selector = maybeSelector || vanilla.identity;
337
+ }
329
338
  useStoreUpdateNotifier(store, selector);
330
339
  useIsomorphicLayoutEffect(() => {
331
- if (options2.enabled !== false) revalidate(store, variable);
340
+ if (options2.enabled !== false) revalidate(store, variable, false);
332
341
  }, [store, options2.enabled]);
333
342
  const storeState = store.getState();
334
343
  let storeStateToBeUsed = storeState;
@@ -339,7 +348,7 @@ const createQuery = (queryFn, options = {}) => {
339
348
  storeStateToBeUsed = { ...storeState, ...prevState.current };
340
349
  }
341
350
  return selector(storeStateToBeUsed);
342
- };
351
+ }
343
352
  return Object.assign(useStore, {
344
353
  subscribe: store.subscribe,
345
354
  getSubscribers: store.getSubscribers,
@@ -358,8 +367,8 @@ const createQuery = (queryFn, options = {}) => {
358
367
  * @remarks
359
368
  * - Useful for bulk refetching.
360
369
  */
361
- executeAll: (overwriteOngoingExecution) => {
362
- stores.forEach((store) => internals.get(store).execute(overwriteOngoingExecution));
370
+ executeAll: (options2) => {
371
+ stores.forEach((store) => internals.get(store).execute(options2));
363
372
  },
364
373
  /**
365
374
  * Revalidates all query instances.
@@ -367,8 +376,8 @@ const createQuery = (queryFn, options = {}) => {
367
376
  * @remarks
368
377
  * - Only re-fetches stale queries.
369
378
  */
370
- revalidateAll: (overwriteOngoingExecution) => {
371
- stores.forEach((store) => internals.get(store).revalidate(overwriteOngoingExecution));
379
+ revalidateAll: (options2) => {
380
+ stores.forEach((store) => internals.get(store).revalidate(options2));
372
381
  },
373
382
  /**
374
383
  * Invalidates all query instances.
@@ -377,8 +386,8 @@ const createQuery = (queryFn, options = {}) => {
377
386
  * - Marks all queries as invalidated and triggers revalidation if active.
378
387
  * - Invalidated queries bypass `staleTime` until successfully executed again.
379
388
  */
380
- invalidateAll: (overwriteOngoingExecution) => {
381
- stores.forEach((store) => internals.get(store).invalidate(overwriteOngoingExecution));
389
+ invalidateAll: (options2) => {
390
+ stores.forEach((store) => internals.get(store).invalidate(options2));
382
391
  },
383
392
  /**
384
393
  * Resets all query instances.
@@ -24,6 +24,7 @@ export type Subscriber<TState> = (state: TState, prevState: TState) => void;
24
24
  * - The store performs **shallow change detection per key** before notifying subscribers.
25
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) {