floppy-disk 3.2.1 → 3.3.0-beta.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.
- package/README.md +41 -496
- package/esm/react/create-query.d.mts +2 -1
- package/esm/react.mjs +2 -2
- package/package.json +6 -2
- package/react/create-query.d.ts +2 -1
- package/react.js +2 -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,535 +16,80 @@ Demo: https://afiiif.github.io/floppy-disk/
|
|
|
16
16
|
npm install floppy-disk
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## In short, it is:
|
|
20
20
|
|
|
21
|
-
|
|
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
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
import { createStore } from "floppy-disk/react";
|
|
30
|
+
**Docs: https://floppy-disk.vercel.app**
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
age: 7,
|
|
28
|
-
level: "Rookie",
|
|
29
|
-
});
|
|
30
|
-
```
|
|
32
|
+
## Store (Global State)
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
A store is a global state container that can be used both **inside and outside** React.\
|
|
35
|
+
With FloppyDisk, creating a store is simple:
|
|
33
36
|
|
|
34
37
|
```tsx
|
|
35
|
-
|
|
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.
|
|
40
|
-
}
|
|
41
|
-
|
|
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>
|
|
53
|
-
|
|
54
|
-
<button onClick={evolve}>Evolve</button>
|
|
55
|
-
</>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Create your own custom action
|
|
60
|
-
const evolve = () => {
|
|
61
|
-
const { level } = useDigimon.getState();
|
|
62
|
-
|
|
63
|
-
const order = ["In-Training", "Rookie", "Champion", "Ultimate"];
|
|
64
|
-
const nextLevel = order[order.indexOf(level) + 1];
|
|
65
|
-
|
|
66
|
-
if (!nextLevel) return console.warn("Already at ultimate level");
|
|
67
|
-
|
|
68
|
-
useDigimon.setState({ level: nextLevel });
|
|
69
|
-
};
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Store Subscription
|
|
73
|
-
|
|
74
|
-
At its core, FloppyDisk is a **pub-sub store**.
|
|
75
|
-
|
|
76
|
-
You can subscribe manually:
|
|
38
|
+
import { createStore } from "floppy-disk/react";
|
|
77
39
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
40
|
+
const useLawn = createStore({
|
|
41
|
+
plants: 3,
|
|
42
|
+
zombies: 1,
|
|
81
43
|
});
|
|
82
|
-
|
|
83
|
-
// Later
|
|
84
|
-
unsubscribe();
|
|
85
44
|
```
|
|
86
45
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
const useTowerDefense = createStore(
|
|
91
|
-
{ archers: 3, mages: 1, barracks: 2, artillery: 1 },
|
|
92
|
-
{
|
|
93
|
-
onFirstSubscribe: () => {
|
|
94
|
-
console.log("First subscriber! We’re officially popular 🎉");
|
|
95
|
-
},
|
|
96
|
-
onSubscribe: () => {
|
|
97
|
-
console.log("New subscriber joined. Welcome aboard 🫡");
|
|
98
|
-
},
|
|
99
|
-
onUnsubscribe: () => {
|
|
100
|
-
console.log("Subscriber left... was it something I said? 😭");
|
|
101
|
-
},
|
|
102
|
-
onLastUnsubscribe: () => {
|
|
103
|
-
console.log("Everyone left. Guess I’ll just exist quietly now...");
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
);
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Differences from Zustand
|
|
110
|
-
|
|
111
|
-
If you're coming from Zustand, this should feel very familiar.\
|
|
112
|
-
Key differences:
|
|
113
|
-
|
|
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
|
-
|
|
120
|
-
Zustand examples:
|
|
46
|
+
Use it inside a component:
|
|
121
47
|
|
|
122
48
|
```tsx
|
|
123
|
-
|
|
49
|
+
function MyPlants() {
|
|
50
|
+
const { plants } = useLawn(); // No selectors needed.
|
|
124
51
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
increment: () => set((prev) => ({ value: prev.value + 1 })),
|
|
128
|
-
}));
|
|
52
|
+
return <div>Plants: {plants}</div>; // Only re-render when plants state changes
|
|
53
|
+
}
|
|
129
54
|
```
|
|
130
55
|
|
|
131
|
-
|
|
56
|
+
Update the state **anywhere**:
|
|
132
57
|
|
|
133
58
|
```tsx
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
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.
|
|
140
|
-
|
|
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
|
-
});
|
|
59
|
+
const addPlant = () => {
|
|
60
|
+
useLawn.setState(prev => ({ plants: prev.plants + 1 }));
|
|
61
|
+
};
|
|
146
62
|
```
|
|
147
63
|
|
|
148
|
-
##
|
|
149
|
-
|
|
150
|
-
FloppyDisk also provides a powerful async state layer, inspired by [TanStack-Query](https://tanstack.com/query) but with a simpler API.
|
|
151
|
-
|
|
152
|
-
It is agnostic to the type of async operation,
|
|
153
|
-
it works with any Promise-based operation—whether it's a network request, local computation, storage access, or something else.
|
|
154
|
-
|
|
155
|
-
Because of that, we intentionally avoid terms like "fetch" or "refetch".\
|
|
156
|
-
Instead, we use:
|
|
64
|
+
## Query Store for Async State
|
|
157
65
|
|
|
158
|
-
|
|
159
|
-
- **revalidate** → re-run while keeping existing data (same as "refetch" in TanStack-Query)
|
|
160
|
-
|
|
161
|
-
### Query vs Mutation
|
|
162
|
-
|
|
163
|
-
<details>
|
|
164
|
-
|
|
165
|
-
<summary>Query → Read Operations</summary>
|
|
166
|
-
|
|
167
|
-
Queries are designed for reading data.\
|
|
168
|
-
They assume:
|
|
169
|
-
|
|
170
|
-
- no side effects
|
|
171
|
-
- no data mutation
|
|
172
|
-
- safe to run multiple times
|
|
173
|
-
|
|
174
|
-
Because of this, queries come with helpful defaults:
|
|
175
|
-
|
|
176
|
-
- ✅ Retry mechanism (for transient failures)
|
|
177
|
-
- ✅ Revalidation (keep data fresh automatically)
|
|
178
|
-
- ✅ Caching & staleness control
|
|
179
|
-
|
|
180
|
-
Use queries when:
|
|
181
|
-
|
|
182
|
-
- fetching data
|
|
183
|
-
- reading from storage
|
|
184
|
-
- running idempotent async logic
|
|
185
|
-
|
|
186
|
-
</details>
|
|
187
|
-
|
|
188
|
-
<details>
|
|
189
|
-
|
|
190
|
-
<summary>Mutation → Write Operations</summary>
|
|
191
|
-
|
|
192
|
-
Mutations are designed for changing data.\
|
|
193
|
-
Examples:
|
|
194
|
-
|
|
195
|
-
- insert
|
|
196
|
-
- update
|
|
197
|
-
- delete
|
|
198
|
-
- triggering side effects
|
|
199
|
-
|
|
200
|
-
Because mutations are **not safe to repeat blindly**, FloppyDisk does **not** include:
|
|
201
|
-
|
|
202
|
-
- ❌ automatic retry
|
|
203
|
-
- ❌ automatic revalidation
|
|
204
|
-
- ❌ implicit re-execution
|
|
205
|
-
|
|
206
|
-
This is intentional.\
|
|
207
|
-
Mutations should be explicit and controlled, not automatic.
|
|
208
|
-
|
|
209
|
-
If you need retry mechanism, then you can always add it manually.
|
|
210
|
-
|
|
211
|
-
</details>
|
|
212
|
-
|
|
213
|
-
### Single Query
|
|
214
|
-
|
|
215
|
-
Create a query using `createQuery`:
|
|
66
|
+
Create a query store for async data with `createQuery`:
|
|
216
67
|
|
|
217
68
|
```tsx
|
|
218
69
|
import { createQuery } from "floppy-disk/react";
|
|
219
70
|
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
const useMyQuery = myQuery();
|
|
226
|
-
|
|
227
|
-
// Use it inside your component:
|
|
228
|
-
|
|
229
|
-
function MyComponent() {
|
|
230
|
-
const { data, error } = useMyQuery();
|
|
231
|
-
|
|
232
|
-
if (!data && !error) return <div>Loading...</div>;
|
|
233
|
-
if (error) return <div>Error: {error.message}</div>;
|
|
234
|
-
|
|
235
|
-
return <div>{data.foo} {data.bar}</div>;
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Query State: Two Independent Dimensions
|
|
240
|
-
|
|
241
|
-
FloppyDisk tracks two things separately:
|
|
242
|
-
|
|
243
|
-
- Is it running? → `isPending`
|
|
244
|
-
- What's the result? → `state`
|
|
245
|
-
|
|
246
|
-
They are **independent**.
|
|
247
|
-
|
|
248
|
-
### Keyed Query (Dynamic Params)
|
|
249
|
-
|
|
250
|
-
You can create parameterized queries:
|
|
251
|
-
|
|
252
|
-
```tsx
|
|
253
|
-
import { createQuery } from "floppy-disk/react";
|
|
254
|
-
|
|
255
|
-
import { getUserById, type GetUserByIdResponse } from "../utils"; // Your own module
|
|
256
|
-
|
|
257
|
-
type MyQueryParam = { id: string };
|
|
258
|
-
|
|
259
|
-
const userQuery = createQuery<GetUserByIdResponse, MyQueryParam>(
|
|
260
|
-
getUserById,
|
|
261
|
-
// { staleTime: 5000, revalidateOnFocus: false } <-- optional options
|
|
71
|
+
const plantDetailQuery = createQuery(
|
|
72
|
+
async ({ id }) => {
|
|
73
|
+
const res = await fetch(`/api/plants/${id}`);
|
|
74
|
+
return res.json();
|
|
75
|
+
},
|
|
262
76
|
);
|
|
263
77
|
```
|
|
264
78
|
|
|
265
|
-
Use it
|
|
79
|
+
Use it in your component:
|
|
266
80
|
|
|
267
81
|
```tsx
|
|
268
|
-
function
|
|
269
|
-
const
|
|
270
|
-
const { data, error } =
|
|
82
|
+
function PlantDetail({ id }) {
|
|
83
|
+
const usePlantDetailQuery = plantDetailQuery({ id });
|
|
84
|
+
const { data, error } = usePlantDetailQuery();
|
|
271
85
|
|
|
272
86
|
if (!data && !error) return <div>Loading...</div>;
|
|
273
87
|
if (error) return <div>Error: {error.message}</div>;
|
|
274
88
|
|
|
275
|
-
return <div>Name: {data.name},
|
|
89
|
+
return <div>Name: {data.name}, damage: {data.damage}</div>;
|
|
276
90
|
}
|
|
277
91
|
```
|
|
278
92
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
### Store Inheritance
|
|
282
|
-
|
|
283
|
-
Queries in FloppyDisk are built on top of the core store.
|
|
284
|
-
This means every query inherits the same capabilities, such as `subscribe`, `getState`, and store events.
|
|
285
|
-
It also gets **automatic reactivity** out of the box, so components rerender only when the state they use actually changes.
|
|
286
|
-
|
|
287
|
-
```tsx
|
|
288
|
-
const { data } = useMyQuery();
|
|
289
|
-
// ^Only data changes will trigger a re-render
|
|
290
|
-
|
|
291
|
-
const value = useMyQuery().data?.foo.bar.baz;
|
|
292
|
-
// ^Only data.foo.bar.baz changes will trigger a re-render
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
Get state outside React:
|
|
296
|
-
|
|
297
|
-
```tsx
|
|
298
|
-
const myQuery = createQuery<AsyncResponse>(myAsyncFn); // Query without paramerer
|
|
299
|
-
const userQuery = createQuery<UserDetail, { id: string }>(getUserById); // Parameterized query
|
|
300
|
-
|
|
301
|
-
const getMyQueryData = () => myQuery().getState().data;
|
|
302
|
-
const getUserQueryData = ({ id }) => userQuery({ id }).getState().data;
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### Infinite Query
|
|
93
|
+
---
|
|
306
94
|
|
|
307
|
-
|
|
308
|
-
Instead, it embraces a simpler and more flexible approach:
|
|
309
|
-
|
|
310
|
-
> Infinite queries are just **composition** + **recursion**.
|
|
311
|
-
|
|
312
|
-
Why? Because async state is already powerful enough:
|
|
313
|
-
|
|
314
|
-
- keyed queries handle parameters
|
|
315
|
-
- components handle composition
|
|
316
|
-
- recursion handles pagination
|
|
317
|
-
|
|
318
|
-
No special abstraction needed.
|
|
319
|
-
|
|
320
|
-
Here is the example on how to implement infinite query properly:
|
|
321
|
-
|
|
322
|
-
```tsx
|
|
323
|
-
type GetPostParams = {
|
|
324
|
-
cursor?: string; // For pagination
|
|
325
|
-
};
|
|
326
|
-
type GetPostsResponse = {
|
|
327
|
-
posts: Post[];
|
|
328
|
-
meta: { nextCursor: string };
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const postsQuery = createQuery<GetPostsResponse, GetPostParams>(getPosts, {
|
|
332
|
-
staleTime: Infinity,
|
|
333
|
-
revalidateOnFocus: false,
|
|
334
|
-
revalidateOnReconnect: false,
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
function Main() {
|
|
338
|
-
return <Page cursor={undefined} />;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function Page({ cursor }: { cursor?: string }) {
|
|
342
|
-
const usePostsQuery = postsQuery({ cursor });
|
|
343
|
-
const { state, data, error } = usePostsQuery();
|
|
344
|
-
|
|
345
|
-
if (!data && !error) return <div>Loading...</div>;
|
|
346
|
-
if (error) return <div>Error</div>;
|
|
347
|
-
|
|
348
|
-
return (
|
|
349
|
-
<>
|
|
350
|
-
{data.posts.map((post) => (
|
|
351
|
-
<PostCard key={post.id} post={post} />
|
|
352
|
-
))}
|
|
353
|
-
{data.meta.nextCursor && <LoadMore nextCursor={data.meta.nextCursor} />}
|
|
354
|
-
</>
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function LoadMore({ nextCursor }: { nextCursor?: string }) {
|
|
359
|
-
const [isNextPageRequested, setIsNextPageRequested] = useState(() => {
|
|
360
|
-
const stateOfNextPageQuery = postsQuery({ cursor: nextCursor }).getState();
|
|
361
|
-
return stateOfNextPageQuery.isPending || stateOfNextPageQuery.isSuccess;
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
if (isNextPageRequested) {
|
|
365
|
-
return <Page cursor={nextCursor} />;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return <BottomObserver onReachBottom={() => setIsNextPageRequested(true)} />;
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
When implementing infinite queries, it is **highly recommended to disable automatic revalidation**.
|
|
373
|
-
|
|
374
|
-
Why?\
|
|
375
|
-
In an infinite list, users may scroll through many pages ("_doom-scrolling_").\
|
|
376
|
-
If revalidation is triggered:
|
|
377
|
-
|
|
378
|
-
- All previously loaded pages may re-execute
|
|
379
|
-
- Content at the top may change without the user noticing
|
|
380
|
-
- Layout shifts can occur unexpectedly
|
|
381
|
-
|
|
382
|
-
This leads to a **confusing and unstable user experience**.\
|
|
383
|
-
Revalidating dozens of previously viewed pages rarely provides value to the user.
|
|
384
|
-
|
|
385
|
-
## SSR Guidance
|
|
386
|
-
|
|
387
|
-
Examples for using stores and queries in SSR with isolated data (no shared state between users).
|
|
388
|
-
|
|
389
|
-
### Initialize Store State from Server
|
|
390
|
-
|
|
391
|
-
```tsx
|
|
392
|
-
const useCountStore = createStore({ count: 0 });
|
|
393
|
-
|
|
394
|
-
function Page({ initialCount }) {
|
|
395
|
-
const { count } = useCountStore({
|
|
396
|
-
initialState: { count: initialCount }, // e.g. 3
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
return <>count is {count}</>; // Output: count is 3
|
|
400
|
-
}
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### Initialize Query Data from Server
|
|
404
|
-
|
|
405
|
-
```tsx
|
|
406
|
-
async function MyServerComponent() {
|
|
407
|
-
const data = await getData(); // e.g. { count: 3 }
|
|
408
|
-
return <MyClientComponent initialData={data} />;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const myQuery = createQuery(getData);
|
|
412
|
-
const useMyQuery = myQuery();
|
|
413
|
-
|
|
414
|
-
function MyClientComponent({ initialData }) {
|
|
415
|
-
const { data } = useMyQuery({
|
|
416
|
-
initialData: initialData,
|
|
417
|
-
// initialDataIsStale: true <-- Optional, default to false (no immediate revalidation)
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
return <>count is {data.count}</>; // Output: count is 3
|
|
421
|
-
}
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
## Query State Machine
|
|
425
|
-
|
|
426
|
-
This is how the query state transition flow looks like:
|
|
427
|
-
|
|
428
|
-
```
|
|
429
|
-
initial failed, won't retry
|
|
430
|
-
┌────────────────────────────┐ ┌────────────────────────────┐
|
|
431
|
-
│ isPending: false │ Δ│ isPending: false │
|
|
432
|
-
│ isRevalidating: false │ │ isRevalidating: false │
|
|
433
|
-
│ │ ┌──────────────────▶ │ │
|
|
434
|
-
│ state: "INITIAL" │ │ Δ│ state: "ERROR" │
|
|
435
|
-
│ isSuccess: false │ │ │ isSuccess: false │
|
|
436
|
-
│ data: undefined │ │ │ data: undefined │
|
|
437
|
-
│ dataUpdatedAt: undefined │ │ │ dataUpdatedAt: undefined │
|
|
438
|
-
│ dataStaleAt: undefined │ │ │ dataStaleAt: undefined │
|
|
439
|
-
│ isError: false │ │ Δ│ isError: true │
|
|
440
|
-
│ error: undefined │ │ Δ│ error: <TError> │
|
|
441
|
-
│ errorUpdatedAt: undefined │ │ Δ│ errorUpdatedAt: <number> │
|
|
442
|
-
│ │ │ │ │
|
|
443
|
-
│ willRetryAt: undefined │ │ │ willRetryAt: undefined │
|
|
444
|
-
│ isRetrying: false │ │ •│ isRetrying: false │
|
|
445
|
-
│ retryCount: 0 │ │ •│ retryCount: 0 (reset) │
|
|
446
|
-
└─────────────┬──────────────┘ │ └────────────────────────────┘
|
|
447
|
-
│ │
|
|
448
|
-
│ execute │
|
|
449
|
-
▼ │ waiting retry delay
|
|
450
|
-
┌────────────────────────────┐ (N) ┌────────────────────────────┐
|
|
451
|
-
Δ│ isPending: true [ƒ]│ │ Δ│ isPending: false │
|
|
452
|
-
│ isRevalidating: false │ fail │ │ isRevalidating: false │
|
|
453
|
-
│ ├──────────▶ Should retry? ────(Y)────▶ │ │
|
|
454
|
-
│ state: "INITIAL" │ ▲ │ state: "INITIAL" │
|
|
455
|
-
│ isSuccess: false │ │ │ isSuccess: false │
|
|
456
|
-
│ data: undefined │ │ │ data: undefined │
|
|
457
|
-
│ dataUpdatedAt: undefined │ │ │ dataUpdatedAt: undefined │
|
|
458
|
-
│ dataStaleAt: undefined │ │ │ dataStaleAt: undefined │
|
|
459
|
-
│ isError: false │ │ │ isError: false │
|
|
460
|
-
│ error: undefined │ │ │ error: undefined │
|
|
461
|
-
│ errorUpdatedAt: undefined │ │ │ errorUpdatedAt: undefined │
|
|
462
|
-
│ │ │ │ │
|
|
463
|
-
│ willRetryAt: undefined │ │ Δ│ willRetryAt: <number> │
|
|
464
|
-
│ isRetrying: false │ │ │ isRetrying: false │
|
|
465
|
-
│ retryCount: 0 │ │ │ retryCount: <number> │
|
|
466
|
-
└─────────────┬──────────────┘ │ └─────────────┬──────────────┘
|
|
467
|
-
│ │ │
|
|
468
|
-
│ success │ │ retrying
|
|
469
|
-
▼ │ ▼
|
|
470
|
-
┌────────────────────────────┐ │ ┌────────────────────────────┐
|
|
471
|
-
Δ│ isPending: false │ │ Δ│ isPending: true [ƒ]│
|
|
472
|
-
│ isRevalidating: false │ │ fail │ isRevalidating: false │
|
|
473
|
-
│ │ └────────────────────┤ │
|
|
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 │
|
|
479
|
-
│ isError: false │ │ isError: false │
|
|
480
|
-
│ error: undefined │ │ error: undefined │
|
|
481
|
-
│ errorUpdatedAt: undefined │ success │ errorUpdatedAt: undefined │
|
|
482
|
-
│ │ ◀─────────────────────────────────────┤ │
|
|
483
|
-
│ willRetryAt: undefined │ Δ│ willRetryAt: undefined │
|
|
484
|
-
•│ isRetrying: false │ Δ│ isRetrying: true │
|
|
485
|
-
•│ retryCount: 0 (reset) │ Δ│ retryCount: <number> (+1) │
|
|
486
|
-
└────────────────────────────┘ └────────────────────────────┘
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
And then after success:
|
|
490
|
-
|
|
491
|
-
```
|
|
492
|
-
success failed, won't retry
|
|
493
|
-
┌────────────────────────────┐ ┌─────────────────────────────────────────┐
|
|
494
|
-
│ isPending: false │ Δ│ isPending: false │
|
|
495
|
-
│ isRevalidating: false │ Δ│ isRevalidating: false │
|
|
496
|
-
│ │ ┌──────────────────▶ │ │
|
|
497
|
-
│ state: "SUCCESS" │ │ Δ│ state: "SUCCESS_BUT_REVALIDATION_ERROR" │
|
|
498
|
-
│ isSuccess: true │ │ │ isSuccess: true │
|
|
499
|
-
│ data: <TData> │ │ │ data: <TData> │
|
|
500
|
-
│ dataUpdatedAt: <number> │ │ │ dataUpdatedAt: <number> │
|
|
501
|
-
│ dataStaleAt: <number> │ │ │ dataStaleAt: <number> │
|
|
502
|
-
│ isError: false │ │ │ isError: false │
|
|
503
|
-
│ error: undefined │ │ Δ│ error: <TError> │
|
|
504
|
-
│ errorUpdatedAt: undefined │ │ Δ│ errorUpdatedAt: <number> │
|
|
505
|
-
│ │ │ │ │
|
|
506
|
-
│ willRetryAt: undefined │ │ │ willRetryAt: undefined │
|
|
507
|
-
│ isRetrying: false │ │ •│ isRetrying: false │
|
|
508
|
-
│ retryCount: 0 │ │ •│ retryCount: 0 (reset) │
|
|
509
|
-
└─────────────┬──────────────┘ │ └─────────────────────────────────────────┘
|
|
510
|
-
│ │
|
|
511
|
-
│ revalidate │
|
|
512
|
-
▼ │ waiting retry delay
|
|
513
|
-
┌────────────────────────────┐ (N) ┌────────────────────────────┐
|
|
514
|
-
Δ│ isPending: true [ƒ]│ │ Δ│ isPending: false │
|
|
515
|
-
Δ│ isRevalidating: true │ fail │ Δ│ isRevalidating: false │
|
|
516
|
-
│ ├──────────▶ Should retry? ────(Y)────▶ │ │
|
|
517
|
-
│ state: "SUCCESS" │ ▲ │ state: "SUCCESS" │
|
|
518
|
-
│ isSuccess: true │ │ │ isSuccess: true │
|
|
519
|
-
│ data: <TData> │ │ │ data: <TData> │
|
|
520
|
-
│ dataUpdatedAt: <number> │ │ │ dataUpdatedAt: <number> │
|
|
521
|
-
│ dataStaleAt: <number> │ │ │ dataStaleAt: <number> │
|
|
522
|
-
│ isError: false │ │ │ isError: false │
|
|
523
|
-
│ error: undefined │ │ │ error: undefined │
|
|
524
|
-
│ errorUpdatedAt: undefined │ │ │ errorUpdatedAt: undefined │
|
|
525
|
-
│ │ │ │ │
|
|
526
|
-
│ willRetryAt: undefined │ │ Δ│ willRetryAt: <number> │
|
|
527
|
-
│ isRetrying: false │ │ │ isRetrying: false │
|
|
528
|
-
│ retryCount: 0 │ │ │ retryCount: <number> │
|
|
529
|
-
└─────────────┬──────────────┘ │ └─────────────┬──────────────┘
|
|
530
|
-
│ │ │
|
|
531
|
-
│ success │ │ retrying
|
|
532
|
-
▼ │ ▼
|
|
533
|
-
┌────────────────────────────┐ │ ┌────────────────────────────┐
|
|
534
|
-
Δ│ isPending: false │ │ Δ│ isPending: true [ƒ]│
|
|
535
|
-
Δ│ isRevalidating: false │ │ fail Δ│ isRevalidating: true │
|
|
536
|
-
│ │ └────────────────────┤ │
|
|
537
|
-
│ state: "SUCCESS" │ │ state: "SUCCESS" │
|
|
538
|
-
│ isSuccess: true │ │ isSuccess: true │
|
|
539
|
-
Δ│ data: <TData> │ │ data: <TData> │
|
|
540
|
-
Δ│ dataUpdatedAt: <number> │ │ dataUpdatedAt: <number> │
|
|
541
|
-
Δ│ dataStaleAt: <number> │ │ dataStaleAt: <number> │
|
|
542
|
-
│ isError: false │ │ isError: false │
|
|
543
|
-
│ error: undefined │ │ error: undefined │
|
|
544
|
-
│ errorUpdatedAt: undefined │ success │ errorUpdatedAt: undefined │
|
|
545
|
-
│ │ ◀─────────────────────────────────────┤ │
|
|
546
|
-
│ willRetryAt: undefined │ Δ│ willRetryAt: undefined │
|
|
547
|
-
•│ isRetrying: false │ Δ│ isRetrying: true │
|
|
548
|
-
•│ retryCount: 0 (reset) │ Δ│ retryCount: <number> (+1) │
|
|
549
|
-
└────────────────────────────┘ └────────────────────────────┘
|
|
550
|
-
```
|
|
95
|
+
Read the docs → https://floppy-disk.vercel.app
|
|
@@ -162,7 +162,7 @@ export type QueryOptions<TData, TVariable extends Record<string, any>, TError =
|
|
|
162
162
|
* // ...
|
|
163
163
|
* }
|
|
164
164
|
*/
|
|
165
|
-
export declare const createQuery: <TData, TVariable extends Record<string, any> = never, TError = Error>(queryFn: (variable: TVariable, currentState: QueryState<TData, TError
|
|
165
|
+
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
166
|
/**
|
|
167
167
|
* Whether the query should be ravalidated automatically on mount.
|
|
168
168
|
*
|
|
@@ -218,6 +218,7 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
218
218
|
* Internal data, do not mutate!
|
|
219
219
|
*/
|
|
220
220
|
metadata: {
|
|
221
|
+
variableHash: string;
|
|
221
222
|
isInvalidated?: boolean;
|
|
222
223
|
promise?: Promise<QueryState<TData, TError>> | undefined;
|
|
223
224
|
promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
package/esm/react.mjs
CHANGED
|
@@ -208,7 +208,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
208
208
|
});
|
|
209
209
|
const internals = /* @__PURE__ */ new WeakMap();
|
|
210
210
|
const configureInternals = (store, variable, variableHash) => ({
|
|
211
|
-
metadata: {},
|
|
211
|
+
metadata: { variableHash },
|
|
212
212
|
setInitialData: (data, revalidate2 = false) => {
|
|
213
213
|
const state = store.getState();
|
|
214
214
|
if (state.state === "INITIAL" && state.data === void 0) {
|
|
@@ -292,7 +292,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
292
292
|
isRetrying: !!metadata.retryResolver,
|
|
293
293
|
retryCount: metadata.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
|
|
294
294
|
});
|
|
295
|
-
queryFn(variable, stateBeforeExecute).then((data) => {
|
|
295
|
+
queryFn(variable, stateBeforeExecute, metadata.variableHash).then((data) => {
|
|
296
296
|
var _a;
|
|
297
297
|
if (data === void 0) {
|
|
298
298
|
console.error(
|
package/package.json
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
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
|
+
"version": "3.3.0-beta.1",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"tag": "beta"
|
|
8
|
+
},
|
|
6
9
|
"keywords": [
|
|
7
10
|
"utilities",
|
|
8
11
|
"store",
|
|
@@ -68,6 +71,7 @@
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
},
|
|
74
|
+
"packageManager": "pnpm@10.32.1",
|
|
71
75
|
"peerDependencies": {
|
|
72
76
|
"@types/react": ">=17.0",
|
|
73
77
|
"react": ">=17.0"
|
|
@@ -80,4 +84,4 @@
|
|
|
80
84
|
"optional": true
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
|
-
}
|
|
87
|
+
}
|
package/react/create-query.d.ts
CHANGED
|
@@ -162,7 +162,7 @@ export type QueryOptions<TData, TVariable extends Record<string, any>, TError =
|
|
|
162
162
|
* // ...
|
|
163
163
|
* }
|
|
164
164
|
*/
|
|
165
|
-
export declare const createQuery: <TData, TVariable extends Record<string, any> = never, TError = Error>(queryFn: (variable: TVariable, currentState: QueryState<TData, TError
|
|
165
|
+
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
166
|
/**
|
|
167
167
|
* Whether the query should be ravalidated automatically on mount.
|
|
168
168
|
*
|
|
@@ -218,6 +218,7 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
218
218
|
* Internal data, do not mutate!
|
|
219
219
|
*/
|
|
220
220
|
metadata: {
|
|
221
|
+
variableHash: string;
|
|
221
222
|
isInvalidated?: boolean;
|
|
222
223
|
promise?: Promise<QueryState<TData, TError>> | undefined;
|
|
223
224
|
promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
package/react.js
CHANGED
|
@@ -210,7 +210,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
210
210
|
});
|
|
211
211
|
const internals = /* @__PURE__ */ new WeakMap();
|
|
212
212
|
const configureInternals = (store, variable, variableHash) => ({
|
|
213
|
-
metadata: {},
|
|
213
|
+
metadata: { variableHash },
|
|
214
214
|
setInitialData: (data, revalidate2 = false) => {
|
|
215
215
|
const state = store.getState();
|
|
216
216
|
if (state.state === "INITIAL" && state.data === void 0) {
|
|
@@ -294,7 +294,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
294
294
|
isRetrying: !!metadata.retryResolver,
|
|
295
295
|
retryCount: metadata.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
|
|
296
296
|
});
|
|
297
|
-
queryFn(variable, stateBeforeExecute).then((data) => {
|
|
297
|
+
queryFn(variable, stateBeforeExecute, metadata.variableHash).then((data) => {
|
|
298
298
|
var _a;
|
|
299
299
|
if (data === void 0) {
|
|
300
300
|
console.error(
|