floppy-disk 3.1.0 → 3.2.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 CHANGED
@@ -1,9 +1,12 @@
1
1
  # FloppyDisk.ts 💾
2
2
 
3
- A lightweight, simple, and powerful state management library.
3
+ A unified state model for **sync & async** data.
4
4
 
5
- This library was highly-inspired by [Zustand](https://www.npmjs.com/package/zustand) and [TanStack-Query](https://tanstack.com/query), they're awesome state manager.
6
- FloppyDisk provides a very similar developer experience (DX), while introducing additional features and a smaller bundle size.
5
+ If you know [Zustand](https://zustand.docs.pmnd.rs) & [TanStack-Query](https://tanstack.com/query), you already know FloppyDisk.\
6
+ It keeps what works, removes unnecessary complexity, and unifies everything into a simpler API.\
7
+ No relearning—just a better experience.
8
+
9
+ _Smaller bundle. Zero dependencies._
7
10
 
8
11
  Demo: https://afiiif.github.io/floppy-disk/
9
12
 
@@ -18,11 +21,11 @@ npm install floppy-disk
18
21
  Here's how to create and use a store:
19
22
 
20
23
  ```tsx
21
- import { createStore } from 'floppy-disk/react';
24
+ import { createStore } from "floppy-disk/react";
22
25
 
23
26
  const useDigimon = createStore({
24
27
  age: 7,
25
- level: 'Rookie',
28
+ level: "Rookie",
26
29
  });
27
30
  ```
28
31
 
@@ -57,10 +60,10 @@ function Control() {
57
60
  const evolve = () => {
58
61
  const { level } = useDigimon.getState();
59
62
 
60
- const order = ['In-Training', 'Rookie', 'Champion', 'Ultimate'];
63
+ const order = ["In-Training", "Rookie", "Champion", "Ultimate"];
61
64
  const nextLevel = order[order.indexOf(level) + 1];
62
65
 
63
- if (!nextLevel) return console.warn('Already at ultimate level');
66
+ if (!nextLevel) return console.warn("Already at ultimate level");
64
67
 
65
68
  useDigimon.setState({ level: nextLevel });
66
69
  };
@@ -74,7 +77,7 @@ You can subscribe manually:
74
77
 
75
78
  ```tsx
76
79
  const unsubscribe = useMyStore.subscribe((state, prev) => {
77
- console.log('New state:', state);
80
+ console.log("New state:", state);
78
81
  });
79
82
 
80
83
  // Later
@@ -88,16 +91,16 @@ const useTowerDefense = createStore(
88
91
  { archers: 3, mages: 1, barracks: 2, artillery: 1 },
89
92
  {
90
93
  onFirstSubscribe: () => {
91
- console.log('First subscriber! We’re officially popular 🎉');
94
+ console.log("First subscriber! We’re officially popular 🎉");
92
95
  },
93
96
  onSubscribe: () => {
94
- console.log('New subscriber joined. Welcome aboard 🫡');
97
+ console.log("New subscriber joined. Welcome aboard 🫡");
95
98
  },
96
99
  onUnsubscribe: () => {
97
- console.log('Subscriber left... was it something I said? 😭');
100
+ console.log("Subscriber left... was it something I said? 😭");
98
101
  },
99
102
  onLastUnsubscribe: () => {
100
- console.log('Everyone left. Guess I’ll just exist quietly now...');
103
+ console.log("Everyone left. Guess I’ll just exist quietly now...");
101
104
  },
102
105
  },
103
106
  );
@@ -212,7 +215,7 @@ If you need retry mechanism, then you can always add it manually.
212
215
  Create a query using `createQuery`:
213
216
 
214
217
  ```tsx
215
- import { createQuery } from 'floppy-disk/react';
218
+ import { createQuery } from "floppy-disk/react";
216
219
 
217
220
  const myCoolQuery = createQuery(
218
221
  myAsyncFn,
@@ -225,7 +228,7 @@ const useMyCoolQuery = myCoolQuery();
225
228
 
226
229
  function MyComponent() {
227
230
  const query = useMyCoolQuery();
228
- if (query.state === 'INITIAL') return <div>Loading...</div>;
231
+ if (query.state === "INITIAL") return <div>Loading...</div>;
229
232
  if (query.error) return <div>Error: {query.error.message}</div>;
230
233
  return <div>{JSON.stringify(query.data)}</div>;
231
234
  }
@@ -259,7 +262,7 @@ const value = useMyQuery().data?.foo.bar.baz;
259
262
  You can create parameterized queries:
260
263
 
261
264
  ```tsx
262
- import { getUserById, type GetUserByIdResponse } from '../utils';
265
+ import { getUserById, type GetUserByIdResponse } from "../utils";
263
266
 
264
267
  type MyQueryParam = { id: string };
265
268
 
@@ -275,7 +278,7 @@ Use it with parameters:
275
278
  function UserDetail({ id }) {
276
279
  const useUserQuery = userQuery({ id: 1 });
277
280
  const query = useUserQuery();
278
- if (query.state === 'INITIAL') return <div>Loading...</div>;
281
+ if (query.state === "INITIAL") return <div>Loading...</div>;
279
282
  if (query.error) return <div>Error: {query.error.message}</div>;
280
283
  return <div>{JSON.stringify(query.data)}</div>;
281
284
  }
@@ -323,7 +326,7 @@ function Page({ cursor }: { cursor?: string }) {
323
326
  const usePostsQuery = postsQuery({ cursor });
324
327
  const { state, data, error } = usePostsQuery();
325
328
 
326
- if (state === 'INITIAL') return <div>Loading...</div>;
329
+ if (state === "INITIAL") return <div>Loading...</div>;
327
330
  if (error) return <div>Error</div>;
328
331
 
329
332
  return (
package/esm/index.d.mts CHANGED
@@ -1 +1 @@
1
- export * from 'floppy-disk/vanilla';
1
+ export * from "./vanilla.ts";
@@ -1,4 +1,4 @@
1
- import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
1
+ import { type InitStoreOptions, type SetState } from "../vanilla.ts";
2
2
  /**
3
3
  * Represents the state of a mutation.
4
4
  *
@@ -17,7 +17,7 @@ import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
17
17
  export type MutationState<TData, TVariable, TError> = {
18
18
  isPending: boolean;
19
19
  } & ({
20
- state: 'INITIAL';
20
+ state: "INITIAL";
21
21
  isSuccess: false;
22
22
  isError: false;
23
23
  variable: undefined;
@@ -26,7 +26,7 @@ export type MutationState<TData, TVariable, TError> = {
26
26
  error: undefined;
27
27
  errorUpdatedAt: undefined;
28
28
  } | {
29
- state: 'SUCCESS';
29
+ state: "SUCCESS";
30
30
  isSuccess: true;
31
31
  isError: false;
32
32
  variable: TVariable;
@@ -35,7 +35,7 @@ export type MutationState<TData, TVariable, TError> = {
35
35
  error: undefined;
36
36
  errorUpdatedAt: undefined;
37
37
  } | {
38
- state: 'ERROR';
38
+ state: "ERROR";
39
39
  isSuccess: false;
40
40
  isError: true;
41
41
  variable: TVariable;
@@ -1,4 +1,4 @@
1
- import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
1
+ import { type InitStoreOptions, type SetState } from "../vanilla.ts";
2
2
  /**
3
3
  * Represents the state of a query.
4
4
  *
@@ -22,38 +22,43 @@ import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
22
22
  export type QueryState<TData, TError> = {
23
23
  isPending: boolean;
24
24
  isRevalidating: boolean;
25
+ willRetryAt: undefined | number;
25
26
  isRetrying: boolean;
26
27
  retryCount: number;
27
28
  } & ({
28
- state: 'INITIAL';
29
+ state: "INITIAL";
29
30
  isSuccess: false;
30
31
  isError: false;
31
32
  data: undefined;
32
33
  dataUpdatedAt: undefined;
34
+ dataStaleAt: undefined;
33
35
  error: undefined;
34
36
  errorUpdatedAt: undefined;
35
37
  } | {
36
- state: 'SUCCESS';
38
+ state: "SUCCESS";
37
39
  isSuccess: true;
38
40
  isError: false;
39
41
  data: TData;
40
42
  dataUpdatedAt: number;
43
+ dataStaleAt: undefined | number;
41
44
  error: undefined;
42
45
  errorUpdatedAt: undefined;
43
46
  } | {
44
- state: 'ERROR';
47
+ state: "ERROR";
45
48
  isSuccess: false;
46
49
  isError: true;
47
50
  data: undefined;
48
51
  dataUpdatedAt: undefined;
52
+ dataStaleAt: undefined;
49
53
  error: TError;
50
54
  errorUpdatedAt: number;
51
55
  } | {
52
- state: 'SUCCESS_BUT_REVALIDATION_ERROR';
56
+ state: "SUCCESS_BUT_REVALIDATION_ERROR";
53
57
  isSuccess: true;
54
58
  isError: false;
55
59
  data: TData;
56
60
  dataUpdatedAt: number;
61
+ dataStaleAt: undefined | number;
57
62
  error: TError;
58
63
  errorUpdatedAt: number;
59
64
  });
@@ -209,6 +214,9 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
209
214
  initialData?: never;
210
215
  initialDataIsStale?: never;
211
216
  })) => QueryState<TData, TError>) & {
217
+ /**
218
+ * Internal data, do not mutate!
219
+ */
212
220
  metadata: {
213
221
  isInvalidated?: boolean;
214
222
  promise?: Promise<QueryState<TData, TError>> | undefined;
@@ -1,4 +1,4 @@
1
- import { type InitStoreOptions } from 'floppy-disk/vanilla';
1
+ import { type InitStoreOptions } from "../vanilla.ts";
2
2
  /**
3
3
  * Creates a single store with a bound React hook.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { type InitStoreOptions } from 'floppy-disk/vanilla';
1
+ import { type InitStoreOptions } from "../vanilla.ts";
2
2
  /**
3
3
  * Creates a factory for multiple stores identified by a key.
4
4
  *
@@ -43,4 +43,6 @@ export declare const createStores: <TState extends Record<string, any>, TKey ext
43
43
  getState: () => TState;
44
44
  subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<TState>) => () => void;
45
45
  getSubscribers: () => Set<import("../vanilla.d.mts").Subscriber<TState>>;
46
+ key: TKey;
47
+ keyHash: string;
46
48
  };
@@ -1,4 +1,4 @@
1
- import { useLayoutEffect } from 'react';
1
+ import { useLayoutEffect } from "react";
2
2
  /**
3
3
  * Does exactly same as `useLayoutEffect`.\
4
4
  * It will use `useEffect` in **server-side** to prevent warning when executed on server-side.
@@ -1,4 +1,4 @@
1
- import { type MutationOptions, type MutationState } from './create-mutation.mjs';
1
+ import { type MutationOptions, type MutationState } from "./create-mutation.ts";
2
2
  /**
3
3
  * A hook for managing async mutation state.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { type StoreApi } from 'floppy-disk/vanilla';
1
+ import { type StoreApi } from "../vanilla.ts";
2
2
  type Path = Array<string | number | symbol>;
3
3
  export declare const getValueByPath: (obj: any, path: Path) => any;
4
4
  export declare const isPrefixPath: (candidatePrefix: Path, targetPath: Path) => boolean;
package/esm/react.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- export * from './react/use-isomorphic-layout-effect.mjs';
2
- export { useStoreState } from './react/use-store.mjs';
3
- export * from './react/create-store.mjs';
4
- export * from './react/create-stores.mjs';
5
- export * from './react/create-query.mjs';
6
- export { createMutation, type MutationOptions, type MutationState, } from './react/create-mutation.mjs';
7
- export * from './react/use-mutation.mjs';
1
+ export * from "./react/use-isomorphic-layout-effect.ts";
2
+ export { useStoreState } from "./react/use-store.ts";
3
+ export * from "./react/create-store.ts";
4
+ export * from "./react/create-stores.ts";
5
+ export * from "./react/create-query.ts";
6
+ export { createMutation, type MutationOptions, type MutationState, } from "./react/create-mutation.ts";
7
+ export * from "./react/use-mutation.ts";
package/esm/react.mjs CHANGED
@@ -95,6 +95,8 @@ const createStores = (initialState, options) => {
95
95
  store = stores.get(keyHash);
96
96
  } else {
97
97
  store = initStore(initialState, options);
98
+ store.key = key;
99
+ store.keyHash = keyHash;
98
100
  stores.set(keyHash, store);
99
101
  }
100
102
  const useStore = (options2) => useStoreState(store, options2);
@@ -118,6 +120,7 @@ const createStores = (initialState, options) => {
118
120
  const INITIAL_STATE$1 = {
119
121
  isPending: false,
120
122
  isRevalidating: false,
123
+ willRetryAt: void 0,
121
124
  isRetrying: false,
122
125
  retryCount: 0,
123
126
  state: "INITIAL",
@@ -125,6 +128,7 @@ const INITIAL_STATE$1 = {
125
128
  isError: false,
126
129
  data: void 0,
127
130
  dataUpdatedAt: void 0,
131
+ dataStaleAt: void 0,
128
132
  error: void 0,
129
133
  errorUpdatedAt: void 0
130
134
  };
@@ -143,7 +147,7 @@ const createQuery = (queryFn, options = {}) => {
143
147
  } = options;
144
148
  const initialState = { ...INITIAL_STATE$1 };
145
149
  const stores = /* @__PURE__ */ new Map();
146
- const configureStoreEvents = () => ({
150
+ const configureStoreEvents = (variableHash) => ({
147
151
  ...options,
148
152
  onFirstSubscribe: (state, store) => {
149
153
  var _a;
@@ -168,14 +172,21 @@ const createQuery = (queryFn, options = {}) => {
168
172
  }
169
173
  },
170
174
  onLastUnsubscribe: (state, store) => {
171
- var _a, _b;
175
+ var _a;
172
176
  (_a = options.onLastUnsubscribe) == null ? void 0 : _a.call(options, state, store);
173
177
  const { metadata, revalidate: revalidate2 } = internals.get(store);
174
178
  clearTimeout(metadata.retryTimeoutId);
175
- (_b = metadata.retryResolver) == null ? void 0 : _b.call(metadata, state);
176
- metadata.retryResolver = void 0;
179
+ if (metadata.retryResolver) {
180
+ store.setState({ willRetryAt: void 0 });
181
+ metadata.retryResolver(store.getState());
182
+ metadata.retryResolver = void 0;
183
+ }
177
184
  metadata.garbageCollectionTimeoutId = setTimeout(() => {
178
- store.setState(initialState);
185
+ if (metadata.promiseResolver || metadata.retryResolver) {
186
+ store.setState(initialState);
187
+ } else {
188
+ stores.delete(variableHash);
189
+ }
179
190
  }, gcTime);
180
191
  if (isClient) {
181
192
  if (revalidateOnFocus) {
@@ -277,6 +288,7 @@ const createQuery = (queryFn, options = {}) => {
277
288
  store.setState({
278
289
  isPending: true,
279
290
  isRevalidating: stateBeforeExecute.state === "SUCCESS",
291
+ willRetryAt: void 0,
280
292
  isRetrying: !!metadata.retryResolver,
281
293
  retryCount: metadata.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
282
294
  });
@@ -289,6 +301,7 @@ const createQuery = (queryFn, options = {}) => {
289
301
  }
290
302
  if (!metadata.promiseResolver) return;
291
303
  if (promise !== metadata.promise) return resolve(metadata.promise);
304
+ const now = Date.now();
292
305
  store.setState({
293
306
  isPending: false,
294
307
  isRevalidating: false,
@@ -298,7 +311,8 @@ const createQuery = (queryFn, options = {}) => {
298
311
  isSuccess: true,
299
312
  isError: false,
300
313
  data,
301
- dataUpdatedAt: Date.now(),
314
+ dataUpdatedAt: now,
315
+ dataStaleAt: now + staleTime,
302
316
  error: void 0,
303
317
  errorUpdatedAt: void 0
304
318
  });
@@ -313,16 +327,23 @@ const createQuery = (queryFn, options = {}) => {
313
327
  var _a;
314
328
  if (!metadata.promiseResolver && !metadata.retryResolver) return;
315
329
  if (promise !== metadata.promise) return resolve(metadata.promise);
316
- store.setState({
330
+ const nextState = {
331
+ ...store.getState(),
317
332
  isPending: false,
318
333
  isRevalidating: false,
319
334
  isRetrying: false
320
- });
321
- const [shouldRetry, retryDelay] = shouldRetryFn(error, store.getState());
335
+ };
336
+ const [shouldRetry, retryDelay] = shouldRetryFn(error, nextState);
322
337
  const hasSubscriber = store.getSubscribers().size > 0;
323
338
  if (shouldRetry && hasSubscriber) {
324
339
  metadata.retryResolver = resolve;
325
340
  metadata.retryTimeoutId = setTimeout(createPromise, retryDelay);
341
+ store.setState({
342
+ isPending: false,
343
+ isRevalidating: false,
344
+ isRetrying: false,
345
+ willRetryAt: Date.now() + retryDelay
346
+ });
326
347
  } else {
327
348
  store.setState({
328
349
  isPending: false,
@@ -375,7 +396,7 @@ const createQuery = (queryFn, options = {}) => {
375
396
  if (stores.has(variableHash)) {
376
397
  store = stores.get(variableHash);
377
398
  } else {
378
- store = initStore(initialState, configureStoreEvents());
399
+ store = initStore(initialState, configureStoreEvents(variableHash));
379
400
  stores.set(variableHash, store);
380
401
  internals.set(store, configureInternals(store, variable, variableHash));
381
402
  }
package/esm/vanilla.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- export * from './vanilla/basic.mjs';
2
- export * from './vanilla/hash.mjs';
3
- export * from './vanilla/store.mjs';
1
+ export * from "./vanilla/basic.ts";
2
+ export * from "./vanilla/hash.ts";
3
+ export * from "./vanilla/store.ts";
package/index.d.ts CHANGED
@@ -1 +1 @@
1
- export * from 'floppy-disk/vanilla';
1
+ export * from "./vanilla.ts";
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "floppy-disk",
3
- "description": "Lightweight, simple, and powerful state management library",
3
+ "description": "Lightweight unified state management for sync and async data.",
4
4
  "private": false,
5
- "version": "3.1.0",
5
+ "version": "3.2.0-beta.1",
6
+ "publishConfig": {
7
+ "tag": "beta"
8
+ },
6
9
  "keywords": [
7
10
  "utilities",
8
11
  "store",
@@ -68,7 +71,6 @@
68
71
  }
69
72
  }
70
73
  },
71
- "packageManager": "pnpm@10.32.1",
72
74
  "peerDependencies": {
73
75
  "@types/react": ">=17.0",
74
76
  "react": ">=17.0"
@@ -81,4 +83,4 @@
81
83
  "optional": true
82
84
  }
83
85
  }
84
- }
86
+ }
@@ -1,4 +1,4 @@
1
- import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
1
+ import { type InitStoreOptions, type SetState } from "../vanilla.ts";
2
2
  /**
3
3
  * Represents the state of a mutation.
4
4
  *
@@ -17,7 +17,7 @@ import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
17
17
  export type MutationState<TData, TVariable, TError> = {
18
18
  isPending: boolean;
19
19
  } & ({
20
- state: 'INITIAL';
20
+ state: "INITIAL";
21
21
  isSuccess: false;
22
22
  isError: false;
23
23
  variable: undefined;
@@ -26,7 +26,7 @@ export type MutationState<TData, TVariable, TError> = {
26
26
  error: undefined;
27
27
  errorUpdatedAt: undefined;
28
28
  } | {
29
- state: 'SUCCESS';
29
+ state: "SUCCESS";
30
30
  isSuccess: true;
31
31
  isError: false;
32
32
  variable: TVariable;
@@ -35,7 +35,7 @@ export type MutationState<TData, TVariable, TError> = {
35
35
  error: undefined;
36
36
  errorUpdatedAt: undefined;
37
37
  } | {
38
- state: 'ERROR';
38
+ state: "ERROR";
39
39
  isSuccess: false;
40
40
  isError: true;
41
41
  variable: TVariable;
@@ -1,4 +1,4 @@
1
- import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
1
+ import { type InitStoreOptions, type SetState } from "../vanilla.ts";
2
2
  /**
3
3
  * Represents the state of a query.
4
4
  *
@@ -22,38 +22,43 @@ import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
22
22
  export type QueryState<TData, TError> = {
23
23
  isPending: boolean;
24
24
  isRevalidating: boolean;
25
+ willRetryAt: undefined | number;
25
26
  isRetrying: boolean;
26
27
  retryCount: number;
27
28
  } & ({
28
- state: 'INITIAL';
29
+ state: "INITIAL";
29
30
  isSuccess: false;
30
31
  isError: false;
31
32
  data: undefined;
32
33
  dataUpdatedAt: undefined;
34
+ dataStaleAt: undefined;
33
35
  error: undefined;
34
36
  errorUpdatedAt: undefined;
35
37
  } | {
36
- state: 'SUCCESS';
38
+ state: "SUCCESS";
37
39
  isSuccess: true;
38
40
  isError: false;
39
41
  data: TData;
40
42
  dataUpdatedAt: number;
43
+ dataStaleAt: undefined | number;
41
44
  error: undefined;
42
45
  errorUpdatedAt: undefined;
43
46
  } | {
44
- state: 'ERROR';
47
+ state: "ERROR";
45
48
  isSuccess: false;
46
49
  isError: true;
47
50
  data: undefined;
48
51
  dataUpdatedAt: undefined;
52
+ dataStaleAt: undefined;
49
53
  error: TError;
50
54
  errorUpdatedAt: number;
51
55
  } | {
52
- state: 'SUCCESS_BUT_REVALIDATION_ERROR';
56
+ state: "SUCCESS_BUT_REVALIDATION_ERROR";
53
57
  isSuccess: true;
54
58
  isError: false;
55
59
  data: TData;
56
60
  dataUpdatedAt: number;
61
+ dataStaleAt: undefined | number;
57
62
  error: TError;
58
63
  errorUpdatedAt: number;
59
64
  });
@@ -209,6 +214,9 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
209
214
  initialData?: never;
210
215
  initialDataIsStale?: never;
211
216
  })) => QueryState<TData, TError>) & {
217
+ /**
218
+ * Internal data, do not mutate!
219
+ */
212
220
  metadata: {
213
221
  isInvalidated?: boolean;
214
222
  promise?: Promise<QueryState<TData, TError>> | undefined;
@@ -1,4 +1,4 @@
1
- import { type InitStoreOptions } from 'floppy-disk/vanilla';
1
+ import { type InitStoreOptions } from "../vanilla.ts";
2
2
  /**
3
3
  * Creates a single store with a bound React hook.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { type InitStoreOptions } from 'floppy-disk/vanilla';
1
+ import { type InitStoreOptions } from "../vanilla.ts";
2
2
  /**
3
3
  * Creates a factory for multiple stores identified by a key.
4
4
  *
@@ -43,4 +43,6 @@ export declare const createStores: <TState extends Record<string, any>, TKey ext
43
43
  getState: () => TState;
44
44
  subscribe: (subscriber: import("../vanilla.ts").Subscriber<TState>) => () => void;
45
45
  getSubscribers: () => Set<import("../vanilla.ts").Subscriber<TState>>;
46
+ key: TKey;
47
+ keyHash: string;
46
48
  };
@@ -1,4 +1,4 @@
1
- import { useLayoutEffect } from 'react';
1
+ import { useLayoutEffect } from "react";
2
2
  /**
3
3
  * Does exactly same as `useLayoutEffect`.\
4
4
  * It will use `useEffect` in **server-side** to prevent warning when executed on server-side.
@@ -1,4 +1,4 @@
1
- import { type MutationOptions, type MutationState } from './create-mutation';
1
+ import { type MutationOptions, type MutationState } from "./create-mutation.ts";
2
2
  /**
3
3
  * A hook for managing async mutation state.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { type StoreApi } from 'floppy-disk/vanilla';
1
+ import { type StoreApi } from "../vanilla.ts";
2
2
  type Path = Array<string | number | symbol>;
3
3
  export declare const getValueByPath: (obj: any, path: Path) => any;
4
4
  export declare const isPrefixPath: (candidatePrefix: Path, targetPath: Path) => boolean;
package/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export * from './react/use-isomorphic-layout-effect';
2
- export { useStoreState } from './react/use-store';
3
- export * from './react/create-store';
4
- export * from './react/create-stores';
5
- export * from './react/create-query';
6
- export { createMutation, type MutationOptions, type MutationState, } from './react/create-mutation';
7
- export * from './react/use-mutation';
1
+ export * from "./react/use-isomorphic-layout-effect.ts";
2
+ export { useStoreState } from "./react/use-store.ts";
3
+ export * from "./react/create-store.ts";
4
+ export * from "./react/create-stores.ts";
5
+ export * from "./react/create-query.ts";
6
+ export { createMutation, type MutationOptions, type MutationState, } from "./react/create-mutation.ts";
7
+ export * from "./react/use-mutation.ts";
package/react.js CHANGED
@@ -97,6 +97,8 @@ const createStores = (initialState, options) => {
97
97
  store = stores.get(keyHash);
98
98
  } else {
99
99
  store = vanilla.initStore(initialState, options);
100
+ store.key = key;
101
+ store.keyHash = keyHash;
100
102
  stores.set(keyHash, store);
101
103
  }
102
104
  const useStore = (options2) => useStoreState(store, options2);
@@ -120,6 +122,7 @@ const createStores = (initialState, options) => {
120
122
  const INITIAL_STATE$1 = {
121
123
  isPending: false,
122
124
  isRevalidating: false,
125
+ willRetryAt: void 0,
123
126
  isRetrying: false,
124
127
  retryCount: 0,
125
128
  state: "INITIAL",
@@ -127,6 +130,7 @@ const INITIAL_STATE$1 = {
127
130
  isError: false,
128
131
  data: void 0,
129
132
  dataUpdatedAt: void 0,
133
+ dataStaleAt: void 0,
130
134
  error: void 0,
131
135
  errorUpdatedAt: void 0
132
136
  };
@@ -145,7 +149,7 @@ const createQuery = (queryFn, options = {}) => {
145
149
  } = options;
146
150
  const initialState = { ...INITIAL_STATE$1 };
147
151
  const stores = /* @__PURE__ */ new Map();
148
- const configureStoreEvents = () => ({
152
+ const configureStoreEvents = (variableHash) => ({
149
153
  ...options,
150
154
  onFirstSubscribe: (state, store) => {
151
155
  var _a;
@@ -170,14 +174,21 @@ const createQuery = (queryFn, options = {}) => {
170
174
  }
171
175
  },
172
176
  onLastUnsubscribe: (state, store) => {
173
- var _a, _b;
177
+ var _a;
174
178
  (_a = options.onLastUnsubscribe) == null ? void 0 : _a.call(options, state, store);
175
179
  const { metadata, revalidate: revalidate2 } = internals.get(store);
176
180
  clearTimeout(metadata.retryTimeoutId);
177
- (_b = metadata.retryResolver) == null ? void 0 : _b.call(metadata, state);
178
- metadata.retryResolver = void 0;
181
+ if (metadata.retryResolver) {
182
+ store.setState({ willRetryAt: void 0 });
183
+ metadata.retryResolver(store.getState());
184
+ metadata.retryResolver = void 0;
185
+ }
179
186
  metadata.garbageCollectionTimeoutId = setTimeout(() => {
180
- store.setState(initialState);
187
+ if (metadata.promiseResolver || metadata.retryResolver) {
188
+ store.setState(initialState);
189
+ } else {
190
+ stores.delete(variableHash);
191
+ }
181
192
  }, gcTime);
182
193
  if (vanilla.isClient) {
183
194
  if (revalidateOnFocus) {
@@ -279,6 +290,7 @@ const createQuery = (queryFn, options = {}) => {
279
290
  store.setState({
280
291
  isPending: true,
281
292
  isRevalidating: stateBeforeExecute.state === "SUCCESS",
293
+ willRetryAt: void 0,
282
294
  isRetrying: !!metadata.retryResolver,
283
295
  retryCount: metadata.retryResolver ? stateBeforeExecute.retryCount + 1 : 0
284
296
  });
@@ -291,6 +303,7 @@ const createQuery = (queryFn, options = {}) => {
291
303
  }
292
304
  if (!metadata.promiseResolver) return;
293
305
  if (promise !== metadata.promise) return resolve(metadata.promise);
306
+ const now = Date.now();
294
307
  store.setState({
295
308
  isPending: false,
296
309
  isRevalidating: false,
@@ -300,7 +313,8 @@ const createQuery = (queryFn, options = {}) => {
300
313
  isSuccess: true,
301
314
  isError: false,
302
315
  data,
303
- dataUpdatedAt: Date.now(),
316
+ dataUpdatedAt: now,
317
+ dataStaleAt: now + staleTime,
304
318
  error: void 0,
305
319
  errorUpdatedAt: void 0
306
320
  });
@@ -315,16 +329,23 @@ const createQuery = (queryFn, options = {}) => {
315
329
  var _a;
316
330
  if (!metadata.promiseResolver && !metadata.retryResolver) return;
317
331
  if (promise !== metadata.promise) return resolve(metadata.promise);
318
- store.setState({
332
+ const nextState = {
333
+ ...store.getState(),
319
334
  isPending: false,
320
335
  isRevalidating: false,
321
336
  isRetrying: false
322
- });
323
- const [shouldRetry, retryDelay] = shouldRetryFn(error, store.getState());
337
+ };
338
+ const [shouldRetry, retryDelay] = shouldRetryFn(error, nextState);
324
339
  const hasSubscriber = store.getSubscribers().size > 0;
325
340
  if (shouldRetry && hasSubscriber) {
326
341
  metadata.retryResolver = resolve;
327
342
  metadata.retryTimeoutId = setTimeout(createPromise, retryDelay);
343
+ store.setState({
344
+ isPending: false,
345
+ isRevalidating: false,
346
+ isRetrying: false,
347
+ willRetryAt: Date.now() + retryDelay
348
+ });
328
349
  } else {
329
350
  store.setState({
330
351
  isPending: false,
@@ -377,7 +398,7 @@ const createQuery = (queryFn, options = {}) => {
377
398
  if (stores.has(variableHash)) {
378
399
  store = stores.get(variableHash);
379
400
  } else {
380
- store = vanilla.initStore(initialState, configureStoreEvents());
401
+ store = vanilla.initStore(initialState, configureStoreEvents(variableHash));
381
402
  stores.set(variableHash, store);
382
403
  internals.set(store, configureInternals(store, variable, variableHash));
383
404
  }
package/vanilla.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * from './vanilla/basic';
2
- export * from './vanilla/hash';
3
- export * from './vanilla/store';
1
+ export * from "./vanilla/basic.ts";
2
+ export * from "./vanilla/hash.ts";
3
+ export * from "./vanilla/store.ts";