floppy-disk 3.3.0-beta.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,11 +2,10 @@
2
2
 
3
3
  A unified state model for **sync & async** data.
4
4
 
5
- If you know [Zustand](https://zustand.docs.pmnd.rs) & [TanStack-Query](https://tanstack.com/query), you already know FloppyDisk(.ts).\
6
- It keeps what works, removes unnecessary complexity, and unifies everything into a simpler API.\
7
- No relearning—just a better experience.
5
+ Built on the patterns you know. Refined into something simpler.\
6
+ Inspired by [Zustand](https://zustand.docs.pmnd.rs) & [TanStack-Query](https://tanstack.com/query).
8
7
 
9
- _Smaller bundle. Zero dependencies._
8
+ _Fine-grained reactivity, minimal boilerplate, zero dependencies._
10
9
 
11
10
  Demo: https://afiiif.github.io/floppy-disk/
12
11
 
@@ -16,18 +15,25 @@ Demo: https://afiiif.github.io/floppy-disk/
16
15
  npm install floppy-disk
17
16
  ```
18
17
 
19
- ## In short, it is:
18
+ **Read the docs https://floppy-disk.vercel.app**
20
19
 
21
- - **Like Zustand, but has additional capabilities:**
22
- - No selectors: automatically optimizes re-renders
23
- - Store events: `onFirstSubscribe`, `onSubscribe`, `onUnsubscribe`, `onLastUnsubscribe`
24
- - Easier to set initial state on SSR/SSG
25
- - Smaller bundle
26
- - **Like TanStack Query, but:**
27
- - DX is very similar to Zustand → One mental model for sync & async
28
- - Much smaller bundle than TanStack Query → With nearly the same capabilities
20
+ <br>
29
21
 
30
- **Docs: https://floppy-disk.vercel.app**
22
+ ---
23
+
24
+ **Like Zustand, but has additional capabilities:**
25
+ - No selectors: automatically optimizes re-renders
26
+ - Store events: `onFirstSubscribe`, `onSubscribe`, `onUnsubscribe`, `onLastUnsubscribe`
27
+ - Easier to set initial state on SSR/SSG
28
+ - Smaller bundle
29
+
30
+ **Like TanStack Query, but:**
31
+ - DX is very similar to Zustand → One mental model for sync & async
32
+ - Much smaller bundle than TanStack Query → With nearly the same capabilities
33
+
34
+ ---
35
+
36
+ <br>
31
37
 
32
38
  ## Store (Global State)
33
39
 
@@ -104,6 +104,8 @@ export type MutationOptions<TData, TVariable, TError = Error> = InitStoreOptions
104
104
  *
105
105
  * const { isPending } = useCreateUser();
106
106
  * const result = await useCreateUser.execute({ name: 'John' });
107
+ *
108
+ * @see https://floppy-disk.vercel.app/docs/async/mutation
107
109
  */
108
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>) & {
109
111
  subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<MutationState<TData, TVariable, TError>>) => () => void;
@@ -68,7 +68,9 @@ export type QueryState<TData, TError> = {
68
68
  * @remarks
69
69
  * Controls caching, retry behavior, lifecycle, and side effects of an async operation.
70
70
  */
71
- export type QueryOptions<TData, TVariable extends Record<string, any>, TError = Error> = InitStoreOptions<QueryState<TData, TError>> & {
71
+ export type QueryOptions<TData, TVariable extends Record<string, any>, TError = Error> = InitStoreOptions<QueryState<TData, TError>, {
72
+ variableHash: string;
73
+ }> & {
72
74
  /**
73
75
  * Time (in milliseconds) that data is considered fresh.
74
76
  *
@@ -161,6 +163,8 @@ export type QueryOptions<TData, TVariable extends Record<string, any>, TError =
161
163
  * const state = useUserQuery();
162
164
  * // ...
163
165
  * }
166
+ *
167
+ * @see https://floppy-disk.vercel.app/docs/async/query
164
168
  */
165
169
  export declare const createQuery: <TData, TVariable extends Record<string, any> = never, TError = Error>(queryFn: (variable: TVariable, currentState: QueryState<TData, TError>, variableHash: string) => Promise<TData>, options?: QueryOptions<TData, TVariable, TError>) => ((variable?: TVariable) => ((options?: {
166
170
  /**
@@ -218,7 +222,6 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
218
222
  * Internal data, do not mutate!
219
223
  */
220
224
  metadata: {
221
- variableHash: string;
222
225
  isInvalidated?: boolean;
223
226
  promise?: Promise<QueryState<TData, TError>> | undefined;
224
227
  promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
@@ -24,6 +24,8 @@ import { type InitStoreOptions } from "../vanilla.mjs";
24
24
  * }
25
25
  *
26
26
  * useMyStore.setState({ foo: 2 }); // only components using foo will re-render
27
+ *
28
+ * @see https://floppy-disk.vercel.app/docs/sync/store
27
29
  */
28
30
  export declare const createStore: <TState extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => ((options?: {
29
31
  /**
@@ -29,8 +29,13 @@ import { type InitStoreOptions } from "../vanilla.mjs";
29
29
  * const state = useUserStore();
30
30
  * return <div>{state.name}</div>;
31
31
  * }
32
+ *
33
+ * @see https://floppy-disk.vercel.app/docs/sync/stores
32
34
  */
33
- export declare const createStores: <TState extends Record<string, any>, TKey extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => (key?: TKey) => ((options?: {
35
+ export declare const createStores: <TState extends Record<string, any>, TKey extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState, {
36
+ key: TKey;
37
+ keyHash: string;
38
+ }>) => (key?: TKey) => ((options?: {
34
39
  /**
35
40
  * Initial state used on first render (and will also update the store state right after that)
36
41
  *
@@ -20,6 +20,8 @@ import { type MutationOptions, type MutationState } from "./create-mutation.mjs"
20
20
  * - If multiple executions triggered at the same time:
21
21
  * - Only the latest execution is allowed to update the state.
22
22
  * - Results from previous executions are ignored if a newer one exists.
23
+ *
24
+ * @see https://floppy-disk.vercel.app/docs/async/mutation
23
25
  */
24
26
  export declare const useMutation: <TData, TVariable = undefined, TError = Error>(
25
27
  /**
package/esm/react.mjs CHANGED
@@ -94,7 +94,11 @@ const createStores = (initialState, options) => {
94
94
  if (stores.has(keyHash)) {
95
95
  store = stores.get(keyHash);
96
96
  } else {
97
- store = initStore(initialState, options);
97
+ store = initStore(
98
+ initialState,
99
+ options
100
+ // Intentionally using as any: don't want to add generic on `initStore`
101
+ );
98
102
  store.key = key;
99
103
  store.keyHash = keyHash;
100
104
  stores.set(keyHash, store);
@@ -207,8 +211,8 @@ const createQuery = (queryFn, options = {}) => {
207
211
  }
208
212
  });
209
213
  const internals = /* @__PURE__ */ new WeakMap();
210
- const configureInternals = (store, variable, variableHash) => ({
211
- metadata: { variableHash },
214
+ const configureInternals = (store, variable) => ({
215
+ metadata: {},
212
216
  setInitialData: (data, revalidate2 = false) => {
213
217
  const state = store.getState();
214
218
  if (state.state === "INITIAL" && state.data === void 0) {
@@ -263,7 +267,7 @@ const createQuery = (queryFn, options = {}) => {
263
267
  return false;
264
268
  }
265
269
  internals.get(store).reset();
266
- return stores.delete(variableHash);
270
+ return stores.delete(store.variableHash);
267
271
  },
268
272
  optimisticUpdate: (optimisticData) => {
269
273
  const { metadata, revalidate: revalidate2, rollbackOptimisticUpdate } = internals.get(store);
@@ -292,7 +296,7 @@ const createQuery = (queryFn, options = {}) => {
292
296
  isRetrying: !!metadata.retryResolver,
293
297
  retryCount: metadata.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
294
298
  });
295
- queryFn(variable, stateBeforeExecute, metadata.variableHash).then((data) => {
299
+ queryFn(variable, stateBeforeExecute, store.variableHash).then((data) => {
296
300
  var _a;
297
301
  if (data === void 0) {
298
302
  console.error(
@@ -396,9 +400,14 @@ const createQuery = (queryFn, options = {}) => {
396
400
  if (stores.has(variableHash)) {
397
401
  store = stores.get(variableHash);
398
402
  } else {
399
- store = initStore(initialState, configureStoreEvents(variableHash));
403
+ store = initStore(
404
+ initialState,
405
+ configureStoreEvents(variableHash)
406
+ // Intentionally using as any: don't want to add generic on `initStore`
407
+ );
408
+ store.variableHash = variableHash;
400
409
  stores.set(variableHash, store);
401
- internals.set(store, configureInternals(store, variable, variableHash));
410
+ internals.set(store, configureInternals(store, variable));
402
411
  }
403
412
  const useStore = (options2 = {}) => {
404
413
  const {
@@ -46,11 +46,11 @@ export type StoreApi<TState extends Record<string, any>> = {
46
46
  * - Cleanup (e.g. cancel timers, disconnect sockets)
47
47
  * - Resource management (e.g. garbage collection)
48
48
  */
49
- export type InitStoreOptions<TState extends Record<string, any>> = {
50
- onFirstSubscribe?: (state: TState, store: StoreApi<TState>) => void;
51
- onSubscribe?: (state: TState, store: StoreApi<TState>) => void;
52
- onUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
53
- onLastUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
49
+ export type InitStoreOptions<TState extends Record<string, any>, TStoreProps extends Record<string, any> = object> = {
50
+ onFirstSubscribe?: (state: TState, store: StoreApi<TState> & TStoreProps) => void;
51
+ onSubscribe?: (state: TState, store: StoreApi<TState> & TStoreProps) => void;
52
+ onUnsubscribe?: (state: TState, store: StoreApi<TState> & TStoreProps) => void;
53
+ onLastUnsubscribe?: (state: TState, store: StoreApi<TState> & TStoreProps) => void;
54
54
  /**
55
55
  * Called whenever the state changes, without counting as a subscriber.
56
56
  * Acts like a "spy" on state updates.
package/package.json CHANGED
@@ -2,10 +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.3.0-beta.1",
6
- "publishConfig": {
7
- "tag": "beta"
8
- },
5
+ "version": "3.4.0",
9
6
  "keywords": [
10
7
  "utilities",
11
8
  "store",
@@ -71,7 +68,6 @@
71
68
  }
72
69
  }
73
70
  },
74
- "packageManager": "pnpm@10.32.1",
75
71
  "peerDependencies": {
76
72
  "@types/react": ">=17.0",
77
73
  "react": ">=17.0"
@@ -84,4 +80,4 @@
84
80
  "optional": true
85
81
  }
86
82
  }
87
- }
83
+ }
@@ -104,6 +104,8 @@ export type MutationOptions<TData, TVariable, TError = Error> = InitStoreOptions
104
104
  *
105
105
  * const { isPending } = useCreateUser();
106
106
  * const result = await useCreateUser.execute({ name: 'John' });
107
+ *
108
+ * @see https://floppy-disk.vercel.app/docs/async/mutation
107
109
  */
108
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>) & {
109
111
  subscribe: (subscriber: import("../vanilla.ts").Subscriber<MutationState<TData, TVariable, TError>>) => () => void;
@@ -68,7 +68,9 @@ export type QueryState<TData, TError> = {
68
68
  * @remarks
69
69
  * Controls caching, retry behavior, lifecycle, and side effects of an async operation.
70
70
  */
71
- export type QueryOptions<TData, TVariable extends Record<string, any>, TError = Error> = InitStoreOptions<QueryState<TData, TError>> & {
71
+ export type QueryOptions<TData, TVariable extends Record<string, any>, TError = Error> = InitStoreOptions<QueryState<TData, TError>, {
72
+ variableHash: string;
73
+ }> & {
72
74
  /**
73
75
  * Time (in milliseconds) that data is considered fresh.
74
76
  *
@@ -161,6 +163,8 @@ export type QueryOptions<TData, TVariable extends Record<string, any>, TError =
161
163
  * const state = useUserQuery();
162
164
  * // ...
163
165
  * }
166
+ *
167
+ * @see https://floppy-disk.vercel.app/docs/async/query
164
168
  */
165
169
  export declare const createQuery: <TData, TVariable extends Record<string, any> = never, TError = Error>(queryFn: (variable: TVariable, currentState: QueryState<TData, TError>, variableHash: string) => Promise<TData>, options?: QueryOptions<TData, TVariable, TError>) => ((variable?: TVariable) => ((options?: {
166
170
  /**
@@ -218,7 +222,6 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
218
222
  * Internal data, do not mutate!
219
223
  */
220
224
  metadata: {
221
- variableHash: string;
222
225
  isInvalidated?: boolean;
223
226
  promise?: Promise<QueryState<TData, TError>> | undefined;
224
227
  promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
@@ -24,6 +24,8 @@ import { type InitStoreOptions } from "../vanilla.ts";
24
24
  * }
25
25
  *
26
26
  * useMyStore.setState({ foo: 2 }); // only components using foo will re-render
27
+ *
28
+ * @see https://floppy-disk.vercel.app/docs/sync/store
27
29
  */
28
30
  export declare const createStore: <TState extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => ((options?: {
29
31
  /**
@@ -29,8 +29,13 @@ import { type InitStoreOptions } from "../vanilla.ts";
29
29
  * const state = useUserStore();
30
30
  * return <div>{state.name}</div>;
31
31
  * }
32
+ *
33
+ * @see https://floppy-disk.vercel.app/docs/sync/stores
32
34
  */
33
- export declare const createStores: <TState extends Record<string, any>, TKey extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState>) => (key?: TKey) => ((options?: {
35
+ export declare const createStores: <TState extends Record<string, any>, TKey extends Record<string, any>>(initialState: TState, options?: InitStoreOptions<TState, {
36
+ key: TKey;
37
+ keyHash: string;
38
+ }>) => (key?: TKey) => ((options?: {
34
39
  /**
35
40
  * Initial state used on first render (and will also update the store state right after that)
36
41
  *
@@ -20,6 +20,8 @@ import { type MutationOptions, type MutationState } from "./create-mutation.ts";
20
20
  * - If multiple executions triggered at the same time:
21
21
  * - Only the latest execution is allowed to update the state.
22
22
  * - Results from previous executions are ignored if a newer one exists.
23
+ *
24
+ * @see https://floppy-disk.vercel.app/docs/async/mutation
23
25
  */
24
26
  export declare const useMutation: <TData, TVariable = undefined, TError = Error>(
25
27
  /**
package/react.js CHANGED
@@ -96,7 +96,11 @@ const createStores = (initialState, options) => {
96
96
  if (stores.has(keyHash)) {
97
97
  store = stores.get(keyHash);
98
98
  } else {
99
- store = vanilla.initStore(initialState, options);
99
+ store = vanilla.initStore(
100
+ initialState,
101
+ options
102
+ // Intentionally using as any: don't want to add generic on `initStore`
103
+ );
100
104
  store.key = key;
101
105
  store.keyHash = keyHash;
102
106
  stores.set(keyHash, store);
@@ -209,8 +213,8 @@ const createQuery = (queryFn, options = {}) => {
209
213
  }
210
214
  });
211
215
  const internals = /* @__PURE__ */ new WeakMap();
212
- const configureInternals = (store, variable, variableHash) => ({
213
- metadata: { variableHash },
216
+ const configureInternals = (store, variable) => ({
217
+ metadata: {},
214
218
  setInitialData: (data, revalidate2 = false) => {
215
219
  const state = store.getState();
216
220
  if (state.state === "INITIAL" && state.data === void 0) {
@@ -265,7 +269,7 @@ const createQuery = (queryFn, options = {}) => {
265
269
  return false;
266
270
  }
267
271
  internals.get(store).reset();
268
- return stores.delete(variableHash);
272
+ return stores.delete(store.variableHash);
269
273
  },
270
274
  optimisticUpdate: (optimisticData) => {
271
275
  const { metadata, revalidate: revalidate2, rollbackOptimisticUpdate } = internals.get(store);
@@ -294,7 +298,7 @@ const createQuery = (queryFn, options = {}) => {
294
298
  isRetrying: !!metadata.retryResolver,
295
299
  retryCount: metadata.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
296
300
  });
297
- queryFn(variable, stateBeforeExecute, metadata.variableHash).then((data) => {
301
+ queryFn(variable, stateBeforeExecute, store.variableHash).then((data) => {
298
302
  var _a;
299
303
  if (data === void 0) {
300
304
  console.error(
@@ -398,9 +402,14 @@ const createQuery = (queryFn, options = {}) => {
398
402
  if (stores.has(variableHash)) {
399
403
  store = stores.get(variableHash);
400
404
  } else {
401
- store = vanilla.initStore(initialState, configureStoreEvents(variableHash));
405
+ store = vanilla.initStore(
406
+ initialState,
407
+ configureStoreEvents(variableHash)
408
+ // Intentionally using as any: don't want to add generic on `initStore`
409
+ );
410
+ store.variableHash = variableHash;
402
411
  stores.set(variableHash, store);
403
- internals.set(store, configureInternals(store, variable, variableHash));
412
+ internals.set(store, configureInternals(store, variable));
404
413
  }
405
414
  const useStore = (options2 = {}) => {
406
415
  const {
@@ -46,11 +46,11 @@ export type StoreApi<TState extends Record<string, any>> = {
46
46
  * - Cleanup (e.g. cancel timers, disconnect sockets)
47
47
  * - Resource management (e.g. garbage collection)
48
48
  */
49
- export type InitStoreOptions<TState extends Record<string, any>> = {
50
- onFirstSubscribe?: (state: TState, store: StoreApi<TState>) => void;
51
- onSubscribe?: (state: TState, store: StoreApi<TState>) => void;
52
- onUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
53
- onLastUnsubscribe?: (state: TState, store: StoreApi<TState>) => void;
49
+ export type InitStoreOptions<TState extends Record<string, any>, TStoreProps extends Record<string, any> = object> = {
50
+ onFirstSubscribe?: (state: TState, store: StoreApi<TState> & TStoreProps) => void;
51
+ onSubscribe?: (state: TState, store: StoreApi<TState> & TStoreProps) => void;
52
+ onUnsubscribe?: (state: TState, store: StoreApi<TState> & TStoreProps) => void;
53
+ onLastUnsubscribe?: (state: TState, store: StoreApi<TState> & TStoreProps) => void;
54
54
  /**
55
55
  * Called whenever the state changes, without counting as a subscriber.
56
56
  * Acts like a "spy" on state updates.