floppy-disk 3.2.1 → 3.2.2

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.
Files changed (2) hide show
  1. package/README.md +282 -140
  2. package/package.json +3 -2
package/README.md CHANGED
@@ -2,7 +2,7 @@
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.\
5
+ If you know [Zustand](https://zustand.docs.pmnd.rs) & [TanStack-Query](https://tanstack.com/query), you already know FloppyDisk(.ts).\
6
6
  It keeps what works, removes unnecessary complexity, and unifies everything into a simpler API.\
7
7
  No relearning—just a better experience.
8
8
 
@@ -16,79 +16,125 @@ Demo: https://afiiif.github.io/floppy-disk/
16
16
  npm install floppy-disk
17
17
  ```
18
18
 
19
- ## Global Store
19
+ ## In short, it is:
20
20
 
21
- Here's how to create and use a store:
21
+ - **Like Zustand, but has additional capability:**
22
+ - No selector: auto optimize re-render.
23
+ - Store events: `onSubscribe`, `onUnSubscribe`, etc.
24
+ - Easier to set initial state from server
25
+ - Smaller bundle
26
+ - **Like TanStack Query, but:**
27
+ - DX is very similar to Zustand → One mental model for sync & async
28
+ - Extremely less bundle size → With almost the same capabilities
29
+
30
+ # Store (Global State)
31
+
32
+ A store is a global state container that can be used both **inside and outside** React.\
33
+ With FloppyDisk, creating a store is simple:
22
34
 
23
35
  ```tsx
24
36
  import { createStore } from "floppy-disk/react";
25
37
 
26
- const useDigimon = createStore({
27
- age: 7,
28
- level: "Rookie",
38
+ const useLawn = createStore({
39
+ plants: 3,
40
+ zombies: 1,
29
41
  });
30
42
  ```
31
43
 
32
- You can use the store both inside and outside of React components.
44
+ Use it inside a component:
33
45
 
34
46
  ```tsx
35
- function MyDigimon() {
36
- const { age } = useDigimon();
37
- return <div>Digimon age: {age}</div>;
38
- // This component will only re-render when `age` changes.
39
- // Changes to `level` will NOT trigger a re-render.
47
+ function MyPlants() {
48
+ const { plants } = useLawn(); // No selectors needed.
49
+
50
+ return <div>Plants: {plants}</div>; // Only re-render when plants state changes
40
51
  }
52
+ ```
41
53
 
42
- function Control() {
43
- return (
44
- <>
45
- <button
46
- onClick={() => {
47
- // You can setState directly
48
- useDigimon.setState((prev) => ({ age: prev.age + 1 }));
49
- }}
50
- >
51
- Increase digimon's age
52
- </button>
54
+ Update the state **anywhere**:
53
55
 
54
- <button onClick={evolve}>Evolve</button>
55
- </>
56
- );
57
- }
56
+ ```tsx
57
+ const addPlant = () => {
58
+ useLawn.setState(prev => ({ plants: prev.plants + 1 }));
59
+ };
60
+ ```
61
+
62
+ ## Updating State
58
63
 
59
- // Create your own custom action
60
- const evolve = () => {
61
- const { level } = useDigimon.getState();
64
+ You can update state using `setState`:
62
65
 
63
- const order = ["In-Training", "Rookie", "Champion", "Ultimate"];
64
- const nextLevel = order[order.indexOf(level) + 1];
66
+ ```tsx
67
+ const useLawn = createStore({ plants: 3, zombies: 1 });
68
+ // Current state: { plants: 3, zombies: 1 }
65
69
 
66
- if (!nextLevel) return console.warn("Already at ultimate level");
70
+ useLawn.setState({ plants: 5, zombies: 5 });
71
+ // Current state: { plants: 5, zombies: 5 }
67
72
 
68
- useDigimon.setState({ level: nextLevel });
69
- };
73
+ useLawn.setState({ plants: 7 }); // 👈 Partial update
74
+ // Current state: { plants: 7, zombies: 5 }
75
+
76
+ useLawn.setState(prev => ({ plants: prev.plant + 2 })); // 👈 Using function
77
+ // Current state: { plants: 9, zombies: 5 }
70
78
  ```
71
79
 
72
- ### Store Subscription
80
+ ## Reading State Outside React
81
+
82
+ Stores are not limited to React. You can access state **anywhere**:
83
+
84
+ ```tsx
85
+ const state = useLawn.getState();
86
+ console.log(state.plants);
87
+ ```
73
88
 
74
- At its core, FloppyDisk is a **pub-sub store**.
89
+ ## Subscribing to Changes
75
90
 
76
- You can subscribe manually:
91
+ You can subscribe to state changes:
77
92
 
78
93
  ```tsx
79
- const unsubscribe = useMyStore.subscribe((state, prev) => {
80
- console.log("New state:", state);
94
+ const unsubscribeLawn = useLawn.subscribe((currentState, prevState) => {
95
+ console.log("State changed:", currentState);
81
96
  });
82
97
 
83
98
  // Later
84
- unsubscribe();
99
+ unsubscribeLawn(); // when you no longer need it
100
+ ```
101
+
102
+ ## Transient Updates (No Re-render)
103
+
104
+ Sometimes you want to listen to changes **without triggering re-renders**.
105
+ You can do this by simply subscribing to the store:
106
+
107
+ ```tsx
108
+ function MyComponent() {
109
+
110
+ useEffect(() => useLawn.subscribe((currentState, prevState) => {
111
+ if (currentState.zombies !== prevState.zombies) {
112
+ console.log("Zombie updated");
113
+ // Do something ...
114
+ }
115
+ }), []);
116
+
117
+ ...
118
+ }
85
119
  ```
86
120
 
87
- FloppyDisk also provides lifecycle hooks tied to subscription count:
121
+ # Store Events
122
+
123
+ FloppyDisk provides lifecycle events to help you understand when **subscribers are added or removed**, and react accordingly.
124
+
125
+ Each store exposes the following events:
126
+
127
+ - `onFirstSubscribe` → triggered right after the first subscriber is added
128
+ - `onSubscribe` → triggered after any subscriber is added (including the first)
129
+ - `onUnsubscribe` → triggered right after a subscriber is removed
130
+ - `onLastUnsubscribe` → triggered after the last subscriber is removed
88
131
 
89
132
  ```tsx
90
- const useTowerDefense = createStore(
91
- { archers: 3, mages: 1, barracks: 2, artillery: 1 },
133
+ const useLawn = createStore(
134
+ {
135
+ plants: 3,
136
+ zombies: 1,
137
+ },
92
138
  {
93
139
  onFirstSubscribe: () => {
94
140
  console.log("First subscriber! We’re officially popular 🎉");
@@ -106,46 +152,47 @@ const useTowerDefense = createStore(
106
152
  );
107
153
  ```
108
154
 
109
- ### Differences from Zustand
155
+ ## Use Cases
110
156
 
111
- If you're coming from Zustand, this should feel very familiar.\
112
- Key differences:
157
+ These events let you control resource lifecycle based on usage.\
158
+ You know exactly:
159
+ - when something starts being used
160
+ - when it's no longer needed
113
161
 
114
- 1. **No Selectors Needed**\
115
- You don't need selectors when using hooks.
116
- FloppyDisk automatically tracks which parts of the state are used and optimizes re-renders accordingly.
117
- 2. **Object-Only Store Initialization**\
118
- In FloppyDisk, stores **must** be initialized with an object. Primitive values or function initializers are not allowed.
119
162
 
120
- Zustand examples:
163
+ **Perfect for:**
164
+ - opening / closing connections
165
+ - starting / stopping polling
166
+ - initializing expensive resources
167
+ - adding / removing window event listeners
121
168
 
122
- ```tsx
123
- const useDate = create(new Date(2021, 01, 11));
124
-
125
- const useCounter = create((set) => ({
126
- value: 1,
127
- increment: () => set((prev) => ({ value: prev.value + 1 })),
128
- }));
129
- ```
169
+ ## State Changes Event
130
170
 
131
- FloppyDisk equivalents:
171
+ Sometimes you want to observe state changes **without becoming a subscriber**.
132
172
 
133
- ```tsx
134
- const useDate = createStore({ value: new Date(2021, 01, 11) });
173
+ In addition to lifecycle events, FloppyDisk provides `onStateChange` event.
174
+ It listens to changes, but does NOT count as a subscriber.
175
+ It Acts like a "**spy**" on state updates.
135
176
 
136
- const useCounter = createStore({ value: 1 });
137
- const increment = () => useCounter.setState((prev) => ({ value: prev.value + 1 }));
138
- // Unlike Zustand, defining actions inside the store is **discouraged** in FloppyDisk.
139
- // This improves tree-shakeability and keeps your store minimal.
177
+ Useful for devtools, logging, or debugging state changes.
140
178
 
141
- // However, it's still possible to mix actions with the state if you understand how closures work:
142
- const useCounterAlt = createStore({
143
- value: 1,
144
- increment: () => useCounterAlt.setState((prev) => ({ value: prev.value + 1 })),
145
- });
179
+ ```tsx
180
+ const useLawn = createStore(
181
+ {
182
+ plants: 3,
183
+ zombies: 1,
184
+ },
185
+ {
186
+ onStateChange: (currentState, prevState) => {
187
+ if (currentState.zombies === 0 && prevState.zombies > 30) {
188
+ toast("🏆 Achievement unlocked! Clear more than 30 zombies at once!");
189
+ }
190
+ }
191
+ },
192
+ );
146
193
  ```
147
194
 
148
- ## Async State (Query & Mutation)
195
+ # Query & Mutation Store for Async State
149
196
 
150
197
  FloppyDisk also provides a powerful async state layer, inspired by [TanStack-Query](https://tanstack.com/query) but with a simpler API.
151
198
 
@@ -158,7 +205,7 @@ Instead, we use:
158
205
  - **execute** → run the async operation (same as "fetch" in TanStack-Query)
159
206
  - **revalidate** → re-run while keeping existing data (same as "refetch" in TanStack-Query)
160
207
 
161
- ### Query vs Mutation
208
+ ## Query vs Mutation
162
209
 
163
210
  <details>
164
211
 
@@ -210,7 +257,7 @@ If you need retry mechanism, then you can always add it manually.
210
257
 
211
258
  </details>
212
259
 
213
- ### Single Query
260
+ ## Single Query
214
261
 
215
262
  Create a query using `createQuery`:
216
263
 
@@ -221,10 +268,12 @@ const myQuery = createQuery(
221
268
  myAsyncFn,
222
269
  // { staleTime: 5000, revalidateOnFocus: false } <-- optional options
223
270
  );
271
+ ```
224
272
 
225
- const useMyQuery = myQuery();
273
+ Use it inside your component:
226
274
 
227
- // Use it inside your component:
275
+ ```tsx
276
+ const useMyQuery = myQuery();
228
277
 
229
278
  function MyComponent() {
230
279
  const { data, error } = useMyQuery();
@@ -236,7 +285,7 @@ function MyComponent() {
236
285
  }
237
286
  ```
238
287
 
239
- ### Query State: Two Independent Dimensions
288
+ ## Query State: Two Independent Dimensions
240
289
 
241
290
  FloppyDisk tracks two things separately:
242
291
 
@@ -245,19 +294,19 @@ FloppyDisk tracks two things separately:
245
294
 
246
295
  They are **independent**.
247
296
 
248
- ### Keyed Query (Dynamic Params)
297
+ ## Keyed Query (Dynamic Params)
249
298
 
250
299
  You can create parameterized queries:
251
300
 
252
301
  ```tsx
253
302
  import { createQuery } from "floppy-disk/react";
254
303
 
255
- import { getUserById, type GetUserByIdResponse } from "../utils"; // Your own module
304
+ import { getZombieById, type GetZombieByIdResponse } from "../utils"; // Your own module
256
305
 
257
- type MyQueryParam = { id: string };
306
+ type ZombieQueryParam = { id: string };
258
307
 
259
- const userQuery = createQuery<GetUserByIdResponse, MyQueryParam>(
260
- getUserById,
308
+ const zombieQuery = createQuery<GetZombieByIdResponse, ZombieQueryParam>(
309
+ getZombieById,
261
310
  // { staleTime: 5000, revalidateOnFocus: false } <-- optional options
262
311
  );
263
312
  ```
@@ -265,20 +314,20 @@ const userQuery = createQuery<GetUserByIdResponse, MyQueryParam>(
265
314
  Use it with parameters:
266
315
 
267
316
  ```tsx
268
- function UserDetail({ id }) {
269
- const useUserQuery = userQuery({ id });
270
- const { data, error } = useUserQuery();
317
+ function ZombieDetail({ id }) {
318
+ const useZombieQuery = zombieQuery({ id });
319
+ const { data, error } = useZombieQuery();
271
320
 
272
321
  if (!data && !error) return <div>Loading...</div>;
273
322
  if (error) return <div>Error: {error.message}</div>;
274
323
 
275
- return <div>Name: {data.name}, email: {data.email}</div>;
324
+ return <div>Name: {data.name}, hp: {data.hp}</div>;
276
325
  }
277
326
  ```
278
327
 
279
328
  Each unique parameter creates its own cache entry.
280
329
 
281
- ### Store Inheritance
330
+ ## Store Inheritance
282
331
 
283
332
  Queries in FloppyDisk are built on top of the core store.
284
333
  This means every query inherits the same capabilities, such as `subscribe`, `getState`, and store events.
@@ -295,14 +344,14 @@ const value = useMyQuery().data?.foo.bar.baz;
295
344
  Get state outside React:
296
345
 
297
346
  ```tsx
298
- const myQuery = createQuery<AsyncResponse>(myAsyncFn); // Query without paramerer
299
- const userQuery = createQuery<UserDetail, { id: string }>(getUserById); // Parameterized query
347
+ const myPlantQuery = createQuery<MyPlantResponse>(getMyPlant); // Query without paramerer
348
+ const zombieQuery = createQuery<GetZombieByIdResponse, { id: string }>(getZombieById); // Parameterized query
300
349
 
301
- const getMyQueryData = () => myQuery().getState().data;
302
- const getUserQueryData = ({ id }) => userQuery({ id }).getState().data;
350
+ const getMyPlantQueryData = () => myPlantQuery().getState().data;
351
+ const getUserQueryData = ({ id }) => zombieQuery({ id }).getState().data;
303
352
  ```
304
353
 
305
- ### Infinite Query
354
+ ## Infinite Query
306
355
 
307
356
  FloppyDisk does **not provide** a dedicated "infinite query" API.\
308
357
  Instead, it embraces a simpler and more flexible approach:
@@ -320,15 +369,15 @@ No special abstraction needed.
320
369
  Here is the example on how to implement infinite query properly:
321
370
 
322
371
  ```tsx
323
- type GetPostParams = {
372
+ type GetPlantParams = {
324
373
  cursor?: string; // For pagination
325
374
  };
326
- type GetPostsResponse = {
327
- posts: Post[];
375
+ type GetPlantsResponse = {
376
+ plants: Plant[];
328
377
  meta: { nextCursor: string };
329
378
  };
330
379
 
331
- const postsQuery = createQuery<GetPostsResponse, GetPostParams>(getPosts, {
380
+ const plantsQuery = createQuery<GetPlantsResponse, GetPlantParams>(getPlants, {
332
381
  staleTime: Infinity,
333
382
  revalidateOnFocus: false,
334
383
  revalidateOnReconnect: false,
@@ -339,16 +388,16 @@ function Main() {
339
388
  }
340
389
 
341
390
  function Page({ cursor }: { cursor?: string }) {
342
- const usePostsQuery = postsQuery({ cursor });
343
- const { state, data, error } = usePostsQuery();
391
+ const usePlantsQuery = plantsQuery({ cursor });
392
+ const { state, data, error } = usePlantsQuery();
344
393
 
345
394
  if (!data && !error) return <div>Loading...</div>;
346
395
  if (error) return <div>Error</div>;
347
396
 
348
397
  return (
349
398
  <>
350
- {data.posts.map((post) => (
351
- <PostCard key={post.id} post={post} />
399
+ {data.plants.map((plant) => (
400
+ <PlantCard key={plant.id} plant={plant} />
352
401
  ))}
353
402
  {data.meta.nextCursor && <LoadMore nextCursor={data.meta.nextCursor} />}
354
403
  </>
@@ -357,7 +406,7 @@ function Page({ cursor }: { cursor?: string }) {
357
406
 
358
407
  function LoadMore({ nextCursor }: { nextCursor?: string }) {
359
408
  const [isNextPageRequested, setIsNextPageRequested] = useState(() => {
360
- const stateOfNextPageQuery = postsQuery({ cursor: nextCursor }).getState();
409
+ const stateOfNextPageQuery = plantsQuery({ cursor: nextCursor }).getState();
361
410
  return stateOfNextPageQuery.isPending || stateOfNextPageQuery.isSuccess;
362
411
  });
363
412
 
@@ -365,7 +414,7 @@ function LoadMore({ nextCursor }: { nextCursor?: string }) {
365
414
  return <Page cursor={nextCursor} />;
366
415
  }
367
416
 
368
- return <BottomObserver onReachBottom={() => setIsNextPageRequested(true)} />;
417
+ return <DomObserver onReachBottom={() => setIsNextPageRequested(true)} />;
369
418
  }
370
419
  ```
371
420
 
@@ -382,11 +431,104 @@ If revalidation is triggered:
382
431
  This leads to a **confusing and unstable user experience**.\
383
432
  Revalidating dozens of previously viewed pages rarely provides value to the user.
384
433
 
385
- ## SSR Guidance
434
+ ## Mutation
435
+
436
+ Mutations are used to perform write operations—such as creating, updating, or deleting data.
437
+
438
+ FloppyDisk provides two ways to use mutations:
439
+ - Global mutation → shared state across components
440
+ - Local mutation → isolated per component
441
+
442
+ ### Global Mutation
443
+
444
+ Create a global mutation using `createMutation`:
445
+
446
+ ```tsx
447
+ import { createMutation } from "floppy-disk/react";
448
+
449
+ const useUpdatePlant = createMutation(updatePlant, {
450
+ onSuccess: (data) => {
451
+ console.log("Global success:", data);
452
+ },
453
+ });
454
+ ```
455
+
456
+ Use it inside any component:
457
+
458
+ ```tsx
459
+ function UpdateButton() {
460
+ const { isPending } = useUpdatePlant();
461
+
462
+ return (
463
+ <button
464
+ disabled={isPending}
465
+ onClick={() => {
466
+ useUpdatePlant.execute({ id: 1, name: "Sunflower", hp: 300 });
467
+ }}
468
+ >
469
+ Update User
470
+ </button>
471
+ );
472
+ }
473
+ ```
474
+
475
+ Characteristics:
476
+ - Shared across all components
477
+ - Single source of truth for mutation state
478
+ - Can be triggered from anywhere using `.execute()`
479
+ - Useful for global actions (e.g. forms, shared actions)
480
+
481
+ ### Local Mutation
482
+
483
+ Create a local mutation using `useMutation`:
484
+
485
+ ```tsx
486
+ import { useMutation } from "floppy-disk/react";
487
+
488
+ function UpdateForm() {
489
+ const [result, { execute }] = useMutation(updateZombie, {
490
+ onSuccess: (data) => {
491
+ console.log("Local success:", data);
492
+ },
493
+ });
494
+
495
+ return (
496
+ <div>
497
+ <button
498
+ disabled={result.isPending}
499
+ onClick={() => {
500
+ execute({ id: 27, name: "Gargantuar", hp: 3000 });
501
+ }}
502
+ >
503
+ Submit
504
+ </button>
505
+
506
+ {result.isError && <div>Error occurred</div>}
507
+ </div>
508
+ );
509
+ }
510
+ ```
511
+
512
+ Characteristics:
513
+ - Isolated per component instance
514
+ - Each usage has its own state
515
+ - No shared side effects
516
+ - Ideal for component-scoped interactions
517
+
518
+ ### Execution
519
+
520
+ Both global and local mutations:
521
+
522
+ - Execute via `execute(input)`
523
+ - Return a Promise that **never throw**.\
524
+ It returns `{ variable: TVariable; data?: TData; error?: TError }` instead.
525
+ - Update state automatically (`isPending`, `isSuccess`, `isError`, etc.)
526
+
527
+ # SSR Guidance
386
528
 
387
529
  Examples for using stores and queries in SSR with isolated data (no shared state between users).
388
530
 
389
- ### Initialize Store State from Server
531
+ ## Initialize Store State from Server
390
532
 
391
533
  ```tsx
392
534
  const useCountStore = createStore({ count: 0 });
@@ -400,7 +542,7 @@ function Page({ initialCount }) {
400
542
  }
401
543
  ```
402
544
 
403
- ### Initialize Query Data from Server
545
+ ## Initialize Query Data from Server
404
546
 
405
547
  ```tsx
406
548
  async function MyServerComponent() {
@@ -421,24 +563,24 @@ function MyClientComponent({ initialData }) {
421
563
  }
422
564
  ```
423
565
 
424
- ## Query State Machine
566
+ # Query State Machine
425
567
 
426
568
  This is how the query state transition flow looks like:
427
569
 
428
570
  ```
429
571
  initial failed, won't retry
430
572
  ┌────────────────────────────┐ ┌────────────────────────────┐
431
- │ isPending: false │ Δ│ isPending: false │
573
+ │ isPending: false │ ■│ isPending: false │
432
574
  │ isRevalidating: false │ │ isRevalidating: false │
433
575
  │ │ ┌──────────────────▶ │ │
434
- │ state: "INITIAL" │ │ Δ│ state: "ERROR" │
576
+ │ state: "INITIAL" │ │ ■│ state: "ERROR" │
435
577
  │ isSuccess: false │ │ │ isSuccess: false │
436
578
  │ data: undefined │ │ │ data: undefined │
437
579
  │ dataUpdatedAt: undefined │ │ │ dataUpdatedAt: undefined │
438
580
  │ dataStaleAt: undefined │ │ │ dataStaleAt: undefined │
439
- │ isError: false │ │ Δ│ isError: true │
440
- │ error: undefined │ │ Δ│ error: <TError> │
441
- │ errorUpdatedAt: undefined │ │ Δ│ errorUpdatedAt: <number> │
581
+ │ isError: false │ │ ■│ isError: true │
582
+ │ error: undefined │ │ ■│ error: <TError> │
583
+ │ errorUpdatedAt: undefined │ │ ■│ errorUpdatedAt: <number> │
442
584
  │ │ │ │ │
443
585
  │ willRetryAt: undefined │ │ │ willRetryAt: undefined │
444
586
  │ isRetrying: false │ │ •│ isRetrying: false │
@@ -448,7 +590,7 @@ This is how the query state transition flow looks like:
448
590
  │ execute │
449
591
  ▼ │ waiting retry delay
450
592
  ┌────────────────────────────┐ (N) ┌────────────────────────────┐
451
- Δ│ isPending: true [ƒ]│ │ Δ│ isPending: false │
593
+ ■│ isPending: true [ƒ]│ │ ■│ isPending: false │
452
594
  │ isRevalidating: false │ fail │ │ isRevalidating: false │
453
595
  │ ├──────────▶ Should retry? ────(Y)────▶ │ │
454
596
  │ state: "INITIAL" │ ▲ │ state: "INITIAL" │
@@ -460,7 +602,7 @@ This is how the query state transition flow looks like:
460
602
  │ error: undefined │ │ │ error: undefined │
461
603
  │ errorUpdatedAt: undefined │ │ │ errorUpdatedAt: undefined │
462
604
  │ │ │ │ │
463
- │ willRetryAt: undefined │ │ Δ│ willRetryAt: <number> │
605
+ │ willRetryAt: undefined │ │ ■│ willRetryAt: <number> │
464
606
  │ isRetrying: false │ │ │ isRetrying: false │
465
607
  │ retryCount: 0 │ │ │ retryCount: <number> │
466
608
  └─────────────┬──────────────┘ │ └─────────────┬──────────────┘
@@ -468,21 +610,21 @@ This is how the query state transition flow looks like:
468
610
  │ success │ │ retrying
469
611
  ▼ │ ▼
470
612
  ┌────────────────────────────┐ │ ┌────────────────────────────┐
471
- Δ│ isPending: false │ │ Δ│ isPending: true [ƒ]│
613
+ ■│ isPending: false │ │ ■│ isPending: true [ƒ]│
472
614
  │ isRevalidating: false │ │ fail │ isRevalidating: false │
473
615
  │ │ └────────────────────┤ │
474
- Δ│ state: "SUCCESS" │ │ state: "INITIAL" │
475
- Δ│ isSuccess: true │ │ isSuccess: false │
476
- Δ│ data: <TData> │ │ data: undefined │
477
- Δ│ dataUpdatedAt: <number> │ │ dataUpdatedAt: undefined │
478
- Δ│ dataStaleAt: <number> │ │ dataStaleAt: undefined │
616
+ ■│ state: "SUCCESS" │ │ state: "INITIAL" │
617
+ ■│ isSuccess: true │ │ isSuccess: false │
618
+ ■│ data: <TData> │ │ data: undefined │
619
+ ■│ dataUpdatedAt: <number> │ │ dataUpdatedAt: undefined │
620
+ ■│ dataStaleAt: <number> │ │ dataStaleAt: undefined │
479
621
  │ isError: false │ │ isError: false │
480
622
  │ error: undefined │ │ error: undefined │
481
623
  │ errorUpdatedAt: undefined │ success │ errorUpdatedAt: undefined │
482
624
  │ │ ◀─────────────────────────────────────┤ │
483
- │ willRetryAt: undefined │ Δ│ willRetryAt: undefined │
484
- •│ isRetrying: false │ Δ│ isRetrying: true │
485
- •│ retryCount: 0 (reset) │ Δ│ retryCount: <number> (+1) │
625
+ │ willRetryAt: undefined │ ■│ willRetryAt: undefined │
626
+ •│ isRetrying: false │ ■│ isRetrying: true │
627
+ •│ retryCount: 0 (reset) │ ■│ retryCount: <number> (+1) │
486
628
  └────────────────────────────┘ └────────────────────────────┘
487
629
  ```
488
630
 
@@ -491,17 +633,17 @@ And then after success:
491
633
  ```
492
634
  success failed, won't retry
493
635
  ┌────────────────────────────┐ ┌─────────────────────────────────────────┐
494
- │ isPending: false │ Δ│ isPending: false │
495
- │ isRevalidating: false │ Δ│ isRevalidating: false │
636
+ │ isPending: false │ ■│ isPending: false │
637
+ │ isRevalidating: false │ ■│ isRevalidating: false │
496
638
  │ │ ┌──────────────────▶ │ │
497
- │ state: "SUCCESS" │ │ Δ│ state: "SUCCESS_BUT_REVALIDATION_ERROR" │
639
+ │ state: "SUCCESS" │ │ ■│ state: "SUCCESS_BUT_REVALIDATION_ERROR" │
498
640
  │ isSuccess: true │ │ │ isSuccess: true │
499
641
  │ data: <TData> │ │ │ data: <TData> │
500
642
  │ dataUpdatedAt: <number> │ │ │ dataUpdatedAt: <number> │
501
643
  │ dataStaleAt: <number> │ │ │ dataStaleAt: <number> │
502
644
  │ isError: false │ │ │ isError: false │
503
- │ error: undefined │ │ Δ│ error: <TError> │
504
- │ errorUpdatedAt: undefined │ │ Δ│ errorUpdatedAt: <number> │
645
+ │ error: undefined │ │ ■│ error: <TError> │
646
+ │ errorUpdatedAt: undefined │ │ ■│ errorUpdatedAt: <number> │
505
647
  │ │ │ │ │
506
648
  │ willRetryAt: undefined │ │ │ willRetryAt: undefined │
507
649
  │ isRetrying: false │ │ •│ isRetrying: false │
@@ -511,8 +653,8 @@ And then after success:
511
653
  │ revalidate │
512
654
  ▼ │ waiting retry delay
513
655
  ┌────────────────────────────┐ (N) ┌────────────────────────────┐
514
- Δ│ isPending: true [ƒ]│ │ Δ│ isPending: false │
515
- Δ│ isRevalidating: true │ fail │ Δ│ isRevalidating: false │
656
+ ■│ isPending: true [ƒ]│ │ ■│ isPending: false │
657
+ ■│ isRevalidating: true │ fail │ ■│ isRevalidating: false │
516
658
  │ ├──────────▶ Should retry? ────(Y)────▶ │ │
517
659
  │ state: "SUCCESS" │ ▲ │ state: "SUCCESS" │
518
660
  │ isSuccess: true │ │ │ isSuccess: true │
@@ -523,7 +665,7 @@ And then after success:
523
665
  │ error: undefined │ │ │ error: undefined │
524
666
  │ errorUpdatedAt: undefined │ │ │ errorUpdatedAt: undefined │
525
667
  │ │ │ │ │
526
- │ willRetryAt: undefined │ │ Δ│ willRetryAt: <number> │
668
+ │ willRetryAt: undefined │ │ ■│ willRetryAt: <number> │
527
669
  │ isRetrying: false │ │ │ isRetrying: false │
528
670
  │ retryCount: 0 │ │ │ retryCount: <number> │
529
671
  └─────────────┬──────────────┘ │ └─────────────┬──────────────┘
@@ -531,20 +673,20 @@ And then after success:
531
673
  │ success │ │ retrying
532
674
  ▼ │ ▼
533
675
  ┌────────────────────────────┐ │ ┌────────────────────────────┐
534
- Δ│ isPending: false │ │ Δ│ isPending: true [ƒ]│
535
- Δ│ isRevalidating: false │ │ fail Δ│ isRevalidating: true │
676
+ ■│ isPending: false │ │ ■│ isPending: true [ƒ]│
677
+ ■│ isRevalidating: false │ │ fail ■│ isRevalidating: true │
536
678
  │ │ └────────────────────┤ │
537
679
  │ state: "SUCCESS" │ │ state: "SUCCESS" │
538
680
  │ isSuccess: true │ │ isSuccess: true │
539
- Δ│ data: <TData> │ │ data: <TData> │
540
- Δ│ dataUpdatedAt: <number> │ │ dataUpdatedAt: <number> │
541
- Δ│ dataStaleAt: <number> │ │ dataStaleAt: <number> │
681
+ ■│ data: <TData> │ │ data: <TData> │
682
+ ■│ dataUpdatedAt: <number> │ │ dataUpdatedAt: <number> │
683
+ ■│ dataStaleAt: <number> │ │ dataStaleAt: <number> │
542
684
  │ isError: false │ │ isError: false │
543
685
  │ error: undefined │ │ error: undefined │
544
686
  │ errorUpdatedAt: undefined │ success │ errorUpdatedAt: undefined │
545
687
  │ │ ◀─────────────────────────────────────┤ │
546
- │ willRetryAt: undefined │ Δ│ willRetryAt: undefined │
547
- •│ isRetrying: false │ Δ│ isRetrying: true │
548
- •│ retryCount: 0 (reset) │ Δ│ retryCount: <number> (+1) │
688
+ │ willRetryAt: undefined │ ■│ willRetryAt: undefined │
689
+ •│ isRetrying: false │ ■│ isRetrying: true │
690
+ •│ retryCount: 0 (reset) │ ■│ retryCount: <number> (+1) │
549
691
  └────────────────────────────┘ └────────────────────────────┘
550
692
  ```
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.2.1",
5
+ "version": "3.2.2",
6
6
  "keywords": [
7
7
  "utilities",
8
8
  "store",
@@ -68,6 +68,7 @@
68
68
  }
69
69
  }
70
70
  },
71
+ "packageManager": "pnpm@10.32.1",
71
72
  "peerDependencies": {
72
73
  "@types/react": ">=17.0",
73
74
  "react": ">=17.0"
@@ -80,4 +81,4 @@
80
81
  "optional": true
81
82
  }
82
83
  }
83
- }
84
+ }