floppy-disk 3.5.1 → 3.6.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.
@@ -109,7 +109,7 @@ export type MutationOptions<TData, TVariable, TError = Error> = InitStoreOptions
109
109
  */
110
110
  export declare const createMutation: <TData, TVariable = undefined, TError = Error>(mutationFn: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable, TError>) => Promise<TData>, options?: MutationOptions<TData, TVariable, TError>) => (() => MutationState<TData, TVariable, TError>) & {
111
111
  subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<MutationState<TData, TVariable, TError>>) => () => void;
112
- getSubscribers: () => Set<import("../vanilla.d.mts").Subscriber<MutationState<TData, TVariable, TError>>>;
112
+ getSubscriberCount: () => number;
113
113
  getState: () => MutationState<TData, TVariable, TError>;
114
114
  /**
115
115
  * Manually updates the mutation state.
@@ -345,7 +345,7 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
345
345
  */
346
346
  rollbackOptimisticUpdate: () => TData;
347
347
  subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<QueryState<TData, TError>>) => () => void;
348
- getSubscribers: () => Set<import("../vanilla.d.mts").Subscriber<QueryState<TData, TError>>>;
348
+ getSubscriberCount: () => number;
349
349
  getState: () => QueryState<TData, TError>;
350
350
  setState: (value: SetStateInput<QueryState<TData, TError>>) => void;
351
351
  }) & {
@@ -51,7 +51,7 @@ export declare const createStores: <TState extends Record<string, any>, TKey ext
51
51
  setState: (value: import("../vanilla.d.mts").SetStateInput<TState>) => void;
52
52
  getState: () => TState;
53
53
  subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<TState>) => () => void;
54
- getSubscribers: () => Set<import("../vanilla.d.mts").Subscriber<TState>>;
54
+ getSubscriberCount: () => number;
55
55
  key: TKey;
56
56
  keyHash: string;
57
57
  };
package/esm/react.mjs CHANGED
@@ -107,7 +107,7 @@ const createStores = (initialState, options) => {
107
107
  return Object.assign(useStore, {
108
108
  ...store,
109
109
  delete: () => {
110
- if (store.getSubscribers().size > 0) {
110
+ if (store.getSubscriberCount() > 0) {
111
111
  console.warn(
112
112
  "Cannot delete store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
113
113
  );
@@ -237,7 +237,7 @@ const createQuery = (queryFn, options = {}) => {
237
237
  invalidate: (options2) => {
238
238
  const { metadata } = internals.get(store);
239
239
  metadata.isInvalidated = true;
240
- if (store.getSubscribers().size > 0) {
240
+ if (store.getSubscriberCount() > 0) {
241
241
  internals.get(store).execute(options2);
242
242
  return true;
243
243
  }
@@ -260,7 +260,7 @@ const createQuery = (queryFn, options = {}) => {
260
260
  store.setState(initialState);
261
261
  },
262
262
  delete: () => {
263
- if (store.getSubscribers().size > 0) {
263
+ if (store.getSubscriberCount() > 0) {
264
264
  console.warn(
265
265
  "Cannot delete query store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
266
266
  );
@@ -338,7 +338,7 @@ const createQuery = (queryFn, options = {}) => {
338
338
  isRetrying: false
339
339
  };
340
340
  const [shouldRetry, retryDelay] = shouldRetryFn(error, nextState);
341
- const hasSubscriber = store.getSubscribers().size > 0;
341
+ const hasSubscriber = store.getSubscriberCount() > 0;
342
342
  if (shouldRetry && hasSubscriber) {
343
343
  metadata.retryResolver = resolve;
344
344
  metadata.retryTimeoutId = setTimeout(createPromise, retryDelay);
@@ -476,7 +476,7 @@ const createQuery = (queryFn, options = {}) => {
476
476
  };
477
477
  return Object.assign(useStore, {
478
478
  subscribe: store.subscribe,
479
- getSubscribers: store.getSubscribers,
479
+ getSubscriberCount: store.getSubscriberCount,
480
480
  getState: store.getState,
481
481
  setState: (value) => {
482
482
  console.debug("Manual setState (not via provided actions) on query store");
@@ -609,7 +609,7 @@ const createMutation = (mutationFn, options = {}) => {
609
609
  };
610
610
  return Object.assign(useStore, {
611
611
  subscribe: store.subscribe,
612
- getSubscribers: store.getSubscribers,
612
+ getSubscriberCount: store.getSubscriberCount,
613
613
  getState: store.getState,
614
614
  /**
615
615
  * Manually updates the mutation state.
@@ -23,10 +23,43 @@ export type Subscriber<TState> = (state: TState, prevState: TState, changedKeys:
23
23
  * - By default, `setState` is **disabled on the server** to prevent accidental shared state between requests.
24
24
  */
25
25
  export type StoreApi<TState extends Record<string, any>> = {
26
+ /**
27
+ * Updates the state.
28
+ *
29
+ * @param value - Partial state or updater function
30
+ *
31
+ * @remarks
32
+ * - Accepts either a partial object or a function `(state) => partial`
33
+ * - Updates are shallowly merged into the current state
34
+ */
26
35
  setState: (value: SetStateInput<TState>) => void;
36
+ /**
37
+ * Get the current state.
38
+ *
39
+ * @returns The latest state snapshot
40
+ */
27
41
  getState: () => TState;
42
+ /**
43
+ * Subscribes to state changes.
44
+ *
45
+ * @param subscriber - Function called when state updates
46
+ *
47
+ * @returns Unsubscribe function
48
+ *
49
+ * @remarks
50
+ * - Subscribers are only called when at least one root key changes
51
+ * - Receives `(state, prevState, changedKeys)`
52
+ */
28
53
  subscribe: (subscriber: Subscriber<TState>) => () => void;
29
- getSubscribers: () => Set<Subscriber<TState>>;
54
+ /**
55
+ * Returns the number of active subscribers.
56
+ *
57
+ * @returns Total subscriber count
58
+ *
59
+ * @remarks
60
+ * - Can be used to determine if the store is currently in use
61
+ */
62
+ getSubscriberCount: () => number;
30
63
  };
31
64
  /**
32
65
  * Lifecycle hooks for the store.
package/esm/vanilla.mjs CHANGED
@@ -39,7 +39,7 @@ const initStore = (initialState, options = {}) => {
39
39
  allowSetStateServerSide = false
40
40
  } = options;
41
41
  const subscribers = /* @__PURE__ */ new Set();
42
- const getSubscribers = () => subscribers;
42
+ const getSubscriberCount = () => subscribers.size;
43
43
  const subscribe = (subscriber) => {
44
44
  subscribers.add(subscriber);
45
45
  if (subscribers.size === 1) onFirstSubscribe(state, storeApi);
@@ -62,7 +62,13 @@ const initStore = (initialState, options = {}) => {
62
62
  const prevState = state;
63
63
  const newValue = getValue(value, state);
64
64
  const changedKeys = [];
65
- for (const key in newValue) {
65
+ for (const key of Object.keys(newValue)) {
66
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
67
+ console.warn(
68
+ `Ignored unsafe key "${String(key)}" in setState(). This key is reserved and may indicate a prototype pollution attempt or malformed payload.`
69
+ );
70
+ continue;
71
+ }
66
72
  if (!Object.is(prevState[key], newValue[key])) {
67
73
  changedKeys.push(key);
68
74
  }
@@ -76,7 +82,7 @@ const initStore = (initialState, options = {}) => {
76
82
  getState,
77
83
  setState,
78
84
  subscribe,
79
- getSubscribers
85
+ getSubscriberCount
80
86
  };
81
87
  return storeApi;
82
88
  };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "floppy-disk",
3
3
  "description": "Lightweight unified state management for sync and async data.",
4
4
  "private": false,
5
- "version": "3.5.1",
5
+ "version": "3.6.1",
6
6
  "keywords": [
7
7
  "utilities",
8
8
  "store",
@@ -109,7 +109,7 @@ export type MutationOptions<TData, TVariable, TError = Error> = InitStoreOptions
109
109
  */
110
110
  export declare const createMutation: <TData, TVariable = undefined, TError = Error>(mutationFn: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable, TError>) => Promise<TData>, options?: MutationOptions<TData, TVariable, TError>) => (() => MutationState<TData, TVariable, TError>) & {
111
111
  subscribe: (subscriber: import("../vanilla.ts").Subscriber<MutationState<TData, TVariable, TError>>) => () => void;
112
- getSubscribers: () => Set<import("../vanilla.ts").Subscriber<MutationState<TData, TVariable, TError>>>;
112
+ getSubscriberCount: () => number;
113
113
  getState: () => MutationState<TData, TVariable, TError>;
114
114
  /**
115
115
  * Manually updates the mutation state.
@@ -345,7 +345,7 @@ export declare const createQuery: <TData, TVariable extends StoreKey = never, TE
345
345
  */
346
346
  rollbackOptimisticUpdate: () => TData;
347
347
  subscribe: (subscriber: import("../vanilla.ts").Subscriber<QueryState<TData, TError>>) => () => void;
348
- getSubscribers: () => Set<import("../vanilla.ts").Subscriber<QueryState<TData, TError>>>;
348
+ getSubscriberCount: () => number;
349
349
  getState: () => QueryState<TData, TError>;
350
350
  setState: (value: SetStateInput<QueryState<TData, TError>>) => void;
351
351
  }) & {
@@ -51,7 +51,7 @@ export declare const createStores: <TState extends Record<string, any>, TKey ext
51
51
  setState: (value: import("../vanilla.ts").SetStateInput<TState>) => void;
52
52
  getState: () => TState;
53
53
  subscribe: (subscriber: import("../vanilla.ts").Subscriber<TState>) => () => void;
54
- getSubscribers: () => Set<import("../vanilla.ts").Subscriber<TState>>;
54
+ getSubscriberCount: () => number;
55
55
  key: TKey;
56
56
  keyHash: string;
57
57
  };
package/react.js CHANGED
@@ -109,7 +109,7 @@ const createStores = (initialState, options) => {
109
109
  return Object.assign(useStore, {
110
110
  ...store,
111
111
  delete: () => {
112
- if (store.getSubscribers().size > 0) {
112
+ if (store.getSubscriberCount() > 0) {
113
113
  console.warn(
114
114
  "Cannot delete store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
115
115
  );
@@ -239,7 +239,7 @@ const createQuery = (queryFn, options = {}) => {
239
239
  invalidate: (options2) => {
240
240
  const { metadata } = internals.get(store);
241
241
  metadata.isInvalidated = true;
242
- if (store.getSubscribers().size > 0) {
242
+ if (store.getSubscriberCount() > 0) {
243
243
  internals.get(store).execute(options2);
244
244
  return true;
245
245
  }
@@ -262,7 +262,7 @@ const createQuery = (queryFn, options = {}) => {
262
262
  store.setState(initialState);
263
263
  },
264
264
  delete: () => {
265
- if (store.getSubscribers().size > 0) {
265
+ if (store.getSubscriberCount() > 0) {
266
266
  console.warn(
267
267
  "Cannot delete query store while it still has active subscribers. Unsubscribe all listeners before deleting the store."
268
268
  );
@@ -340,7 +340,7 @@ const createQuery = (queryFn, options = {}) => {
340
340
  isRetrying: false
341
341
  };
342
342
  const [shouldRetry, retryDelay] = shouldRetryFn(error, nextState);
343
- const hasSubscriber = store.getSubscribers().size > 0;
343
+ const hasSubscriber = store.getSubscriberCount() > 0;
344
344
  if (shouldRetry && hasSubscriber) {
345
345
  metadata.retryResolver = resolve;
346
346
  metadata.retryTimeoutId = setTimeout(createPromise, retryDelay);
@@ -478,7 +478,7 @@ const createQuery = (queryFn, options = {}) => {
478
478
  };
479
479
  return Object.assign(useStore, {
480
480
  subscribe: store.subscribe,
481
- getSubscribers: store.getSubscribers,
481
+ getSubscriberCount: store.getSubscriberCount,
482
482
  getState: store.getState,
483
483
  setState: (value) => {
484
484
  console.debug("Manual setState (not via provided actions) on query store");
@@ -611,7 +611,7 @@ const createMutation = (mutationFn, options = {}) => {
611
611
  };
612
612
  return Object.assign(useStore, {
613
613
  subscribe: store.subscribe,
614
- getSubscribers: store.getSubscribers,
614
+ getSubscriberCount: store.getSubscriberCount,
615
615
  getState: store.getState,
616
616
  /**
617
617
  * Manually updates the mutation state.
@@ -23,10 +23,43 @@ export type Subscriber<TState> = (state: TState, prevState: TState, changedKeys:
23
23
  * - By default, `setState` is **disabled on the server** to prevent accidental shared state between requests.
24
24
  */
25
25
  export type StoreApi<TState extends Record<string, any>> = {
26
+ /**
27
+ * Updates the state.
28
+ *
29
+ * @param value - Partial state or updater function
30
+ *
31
+ * @remarks
32
+ * - Accepts either a partial object or a function `(state) => partial`
33
+ * - Updates are shallowly merged into the current state
34
+ */
26
35
  setState: (value: SetStateInput<TState>) => void;
36
+ /**
37
+ * Get the current state.
38
+ *
39
+ * @returns The latest state snapshot
40
+ */
27
41
  getState: () => TState;
42
+ /**
43
+ * Subscribes to state changes.
44
+ *
45
+ * @param subscriber - Function called when state updates
46
+ *
47
+ * @returns Unsubscribe function
48
+ *
49
+ * @remarks
50
+ * - Subscribers are only called when at least one root key changes
51
+ * - Receives `(state, prevState, changedKeys)`
52
+ */
28
53
  subscribe: (subscriber: Subscriber<TState>) => () => void;
29
- getSubscribers: () => Set<Subscriber<TState>>;
54
+ /**
55
+ * Returns the number of active subscribers.
56
+ *
57
+ * @returns Total subscriber count
58
+ *
59
+ * @remarks
60
+ * - Can be used to determine if the store is currently in use
61
+ */
62
+ getSubscriberCount: () => number;
30
63
  };
31
64
  /**
32
65
  * Lifecycle hooks for the store.
package/vanilla.js CHANGED
@@ -41,7 +41,7 @@ const initStore = (initialState, options = {}) => {
41
41
  allowSetStateServerSide = false
42
42
  } = options;
43
43
  const subscribers = /* @__PURE__ */ new Set();
44
- const getSubscribers = () => subscribers;
44
+ const getSubscriberCount = () => subscribers.size;
45
45
  const subscribe = (subscriber) => {
46
46
  subscribers.add(subscriber);
47
47
  if (subscribers.size === 1) onFirstSubscribe(state, storeApi);
@@ -64,7 +64,13 @@ const initStore = (initialState, options = {}) => {
64
64
  const prevState = state;
65
65
  const newValue = getValue(value, state);
66
66
  const changedKeys = [];
67
- for (const key in newValue) {
67
+ for (const key of Object.keys(newValue)) {
68
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
69
+ console.warn(
70
+ `Ignored unsafe key "${String(key)}" in setState(). This key is reserved and may indicate a prototype pollution attempt or malformed payload.`
71
+ );
72
+ continue;
73
+ }
68
74
  if (!Object.is(prevState[key], newValue[key])) {
69
75
  changedKeys.push(key);
70
76
  }
@@ -78,7 +84,7 @@ const initStore = (initialState, options = {}) => {
78
84
  getState,
79
85
  setState,
80
86
  subscribe,
81
- getSubscribers
87
+ getSubscriberCount
82
88
  };
83
89
  return storeApi;
84
90
  };