floppy-disk 2.3.1 → 2.4.0-beta.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.
@@ -72,7 +72,7 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
72
72
  *
73
73
  * `"success"` = has data.
74
74
  *
75
- * `"error"` = has error.
75
+ * `"error"` = has error and no data.
76
76
  *
77
77
  * It has no relation with network fetching state.
78
78
  * If you're looking for network fetching state, use `isWaiting` instead.
@@ -178,9 +178,29 @@ export type CreateQueryOptions<TKey extends StoreKey = StoreKey, TResponse = any
178
178
  * This function should return a variable that will be used when fetching next page (`pageParam`).
179
179
  */
180
180
  getNextPageParam?: (lastPage: TResponse, index: number) => any;
181
+ onBeforeFetch?: (cancel: () => void, state: QueryState<TKey, TResponse, TData, TError>) => void;
181
182
  onSuccess?: (response: TResponse, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
182
183
  onError?: (error: TError, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
183
184
  onSettled?: (stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
185
+ /**
186
+ * Cache time in miliseconds.
187
+ *
188
+ * When a query becomes inactive (no longer have subscribers), it will be reset after this duration,
189
+ * and the cache data will be garbage collected.
190
+ *
191
+ * Set it to `Infinity` to disable garbage collection.
192
+ *
193
+ * Defaults to `5 * 60 * 1000` (5 minutes).
194
+ */
195
+ cacheTime?: number;
196
+ /**
197
+ * Polling interval in milliseconds.
198
+ *
199
+ * Disabled by default.
200
+ *
201
+ * If the query is on error state, the polling interval will be disabled, and it will use `retry` instead.
202
+ */
203
+ refetchInterval?: number | false | (() => number | false);
184
204
  };
185
205
  export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = UseStores<TKey, QueryState<TKey, TResponse, TData, TError>> & {
186
206
  /**
@@ -1,15 +1,7 @@
1
1
  import { h as createElement } from 'preact';
2
2
  import { useState } from 'preact/hooks';
3
- import { hasValue, identityFn, noop } from '../utils';
3
+ import { getValueOrComputedValue, hasValue, identityFn, noop } from '../utils';
4
4
  import { createStores } from './create-stores';
5
- const getDecision = (value, param, { ifTrue, ifAlways }) => {
6
- if (value === true || (typeof value === 'function' && value(param) === true)) {
7
- ifTrue();
8
- }
9
- else if (value === 'always' || (typeof value === 'function' && value(param) === 'always')) {
10
- ifAlways();
11
- }
12
- };
13
5
  const INITIAL_QUERY_STATE = {
14
6
  isWaiting: false,
15
7
  isWaitingNextPage: false,
@@ -44,26 +36,36 @@ export const createQuery = (queryFn, options = {}) => {
44
36
  const defaultFetchOnWindowFocus = options.fetchOnMount ?? true;
45
37
  const { onFirstSubscribe = noop, onSubscribe = noop, onLastUnsubscribe = noop, onBeforeChangeKey = noop, defaultDeps = useQueryDefaultDeps, select = identityFn, staleTime = 3000, // 3 seconds
46
38
  fetchOnMount = true, fetchOnWindowFocus = defaultFetchOnWindowFocus, enabled = true, retry = 1, retryDelay = 2000, // 2 seconds
47
- keepPreviousData, getNextPageParam = () => undefined, onSuccess = noop, onError = noop, onSettled = noop, ...createStoresOptions } = options;
39
+ keepPreviousData, getNextPageParam = () => undefined, onBeforeFetch = noop, onSuccess = noop, onError = noop, onSettled = noop, cacheTime = 5 * 60 * 1000, refetchInterval = false, ...createStoresOptions } = options;
48
40
  const retryTimeoutId = new Map();
49
41
  const retryNextPageTimeoutId = new Map();
42
+ const resetTimeoutId = new Map();
43
+ const refetchIntervalTimeoutId = new Map();
50
44
  const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
51
45
  const useQuery = createStores(({ get, set, key: _key, keyHash }) => {
52
46
  const key = _key;
53
47
  const getRetryProps = (error, retryCount) => {
54
48
  const prevState = get();
55
- const maxRetryCount = (typeof retry === 'function' ? retry(error, prevState) : retry) || 0;
56
- const shouldRetry = retryCount < maxRetryCount;
57
- const delay = (typeof retryDelay === 'function' ? retryDelay(error, prevState) : retryDelay) || 0;
58
- return { shouldRetry, delay };
49
+ const maxRetryCount = getValueOrComputedValue(retry, error, prevState) || 0;
50
+ const delay = getValueOrComputedValue(retryDelay, error, prevState) || 0;
51
+ return { shouldRetry: retryCount < maxRetryCount, delay };
59
52
  };
60
53
  const forceFetch = () => new Promise((resolve) => {
61
54
  const responseAllPages = [];
62
55
  const newPageParams = [undefined];
63
56
  let pageParam = undefined;
64
- const { isWaiting, isLoading, pageParams } = get();
65
- if (isWaiting || enabled === false || (typeof enabled === 'function' && !enabled(key)))
66
- return;
57
+ clearTimeout(refetchIntervalTimeoutId.get(keyHash));
58
+ const state = get();
59
+ const { isWaiting, isLoading, pageParams } = state;
60
+ if (isWaiting || !getValueOrComputedValue(enabled, key))
61
+ return resolve(state);
62
+ let shouldcancel = false;
63
+ const cancel = () => {
64
+ shouldcancel = true;
65
+ };
66
+ onBeforeFetch(cancel, state);
67
+ if (shouldcancel)
68
+ return resolve(state);
67
69
  if (isLoading)
68
70
  set({ isWaiting: true });
69
71
  else
@@ -114,6 +116,12 @@ export const createQuery = (queryFn, options = {}) => {
114
116
  pageParams: newPageParams,
115
117
  hasNextPage: hasValue(newPageParam),
116
118
  });
119
+ const refetchIntervalValue = typeof window !== 'undefined' && getValueOrComputedValue(refetchInterval);
120
+ if (refetchIntervalValue) {
121
+ refetchIntervalTimeoutId.set(keyHash, window.setTimeout(() => {
122
+ forceFetch();
123
+ }, refetchIntervalValue));
124
+ }
117
125
  onSuccess(response, stateBeforeCallQuery);
118
126
  resolve(get());
119
127
  })
@@ -227,26 +235,37 @@ export const createQuery = (queryFn, options = {}) => {
227
235
  }, (() => {
228
236
  const fetchWindowFocusHandler = () => {
229
237
  useQuery.getAllWithSubscriber().forEach((state) => {
230
- getDecision(fetchOnWindowFocus, state.key, {
231
- ifTrue: state.fetch,
232
- ifAlways: state.forceFetch,
233
- });
238
+ const result = getValueOrComputedValue(fetchOnWindowFocus, state.key);
239
+ if (result === 'always')
240
+ state.forceFetch();
241
+ else if (result)
242
+ state.fetch();
234
243
  });
235
244
  };
236
245
  return {
237
246
  ...createStoresOptions,
238
247
  defaultDeps,
239
248
  onFirstSubscribe: (state) => {
249
+ if (state.isSuccess) {
250
+ const refetchIntervalValue = typeof window !== 'undefined' && getValueOrComputedValue(refetchInterval);
251
+ if (refetchIntervalValue) {
252
+ refetchIntervalTimeoutId.set(state.keyHash, window.setTimeout(() => {
253
+ state.forceFetch();
254
+ }, refetchIntervalValue));
255
+ }
256
+ }
240
257
  if (typeof window !== 'undefined' && fetchOnWindowFocus) {
241
258
  window.addEventListener('focus', fetchWindowFocusHandler);
242
259
  }
260
+ clearTimeout(resetTimeoutId.get(state.keyHash));
243
261
  onFirstSubscribe(state);
244
262
  },
245
263
  onSubscribe: (state) => {
246
- getDecision(fetchOnMount, state.key, {
247
- ifTrue: state.fetch,
248
- ifAlways: state.forceFetch,
249
- });
264
+ const result = getValueOrComputedValue(fetchOnMount, state.key);
265
+ if (result === 'always')
266
+ state.forceFetch();
267
+ else if (result)
268
+ state.fetch();
250
269
  onSubscribe(state);
251
270
  },
252
271
  onLastUnsubscribe: (state) => {
@@ -256,6 +275,12 @@ export const createQuery = (queryFn, options = {}) => {
256
275
  useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
257
276
  clearTimeout(retryTimeoutId.get(state.keyHash));
258
277
  clearTimeout(retryNextPageTimeoutId.get(state.keyHash));
278
+ clearTimeout(refetchIntervalTimeoutId.get(state.keyHash));
279
+ if (typeof window !== 'undefined' && cacheTime !== Infinity) {
280
+ resetTimeoutId.set(state.keyHash, window.setTimeout(() => {
281
+ useQuery.set(state.key, INITIAL_QUERY_STATE);
282
+ }, cacheTime));
283
+ }
259
284
  onLastUnsubscribe(state);
260
285
  },
261
286
  onBeforeChangeKey: (nextKey, prevKey) => {
@@ -71,7 +71,7 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
71
71
  *
72
72
  * `"success"` = has data.
73
73
  *
74
- * `"error"` = has error.
74
+ * `"error"` = has error and no data.
75
75
  *
76
76
  * It has no relation with network fetching state.
77
77
  * If you're looking for network fetching state, use `isWaiting` instead.
@@ -177,9 +177,29 @@ export type CreateQueryOptions<TKey extends StoreKey = StoreKey, TResponse = any
177
177
  * This function should return a variable that will be used when fetching next page (`pageParam`).
178
178
  */
179
179
  getNextPageParam?: (lastPage: TResponse, index: number) => any;
180
+ onBeforeFetch?: (cancel: () => void, state: QueryState<TKey, TResponse, TData, TError>) => void;
180
181
  onSuccess?: (response: TResponse, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
181
182
  onError?: (error: TError, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
182
183
  onSettled?: (stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
184
+ /**
185
+ * Cache time in miliseconds.
186
+ *
187
+ * When a query becomes inactive (no longer have subscribers), it will be reset after this duration,
188
+ * and the cache data will be garbage collected.
189
+ *
190
+ * Set it to `Infinity` to disable garbage collection.
191
+ *
192
+ * Defaults to `5 * 60 * 1000` (5 minutes).
193
+ */
194
+ cacheTime?: number;
195
+ /**
196
+ * Polling interval in milliseconds.
197
+ *
198
+ * Disabled by default.
199
+ *
200
+ * If the query is on error state, the polling interval will be disabled, and it will use `retry` instead.
201
+ */
202
+ refetchInterval?: number | false | (() => number | false);
183
203
  };
184
204
  export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = UseStores<TKey, QueryState<TKey, TResponse, TData, TError>> & {
185
205
  /**
@@ -1,14 +1,6 @@
1
1
  import { createElement, useState } from 'react';
2
- import { hasValue, identityFn, noop } from '../utils';
2
+ import { getValueOrComputedValue, hasValue, identityFn, noop } from '../utils';
3
3
  import { createStores } from './create-stores';
4
- const getDecision = (value, param, { ifTrue, ifAlways }) => {
5
- if (value === true || (typeof value === 'function' && value(param) === true)) {
6
- ifTrue();
7
- }
8
- else if (value === 'always' || (typeof value === 'function' && value(param) === 'always')) {
9
- ifAlways();
10
- }
11
- };
12
4
  const INITIAL_QUERY_STATE = {
13
5
  isWaiting: false,
14
6
  isWaitingNextPage: false,
@@ -43,26 +35,36 @@ export const createQuery = (queryFn, options = {}) => {
43
35
  const defaultFetchOnWindowFocus = options.fetchOnMount ?? true;
44
36
  const { onFirstSubscribe = noop, onSubscribe = noop, onLastUnsubscribe = noop, onBeforeChangeKey = noop, defaultDeps = useQueryDefaultDeps, select = identityFn, staleTime = 3000, // 3 seconds
45
37
  fetchOnMount = true, fetchOnWindowFocus = defaultFetchOnWindowFocus, enabled = true, retry = 1, retryDelay = 2000, // 2 seconds
46
- keepPreviousData, getNextPageParam = () => undefined, onSuccess = noop, onError = noop, onSettled = noop, ...createStoresOptions } = options;
38
+ keepPreviousData, getNextPageParam = () => undefined, onBeforeFetch = noop, onSuccess = noop, onError = noop, onSettled = noop, cacheTime = 5 * 60 * 1000, refetchInterval = false, ...createStoresOptions } = options;
47
39
  const retryTimeoutId = new Map();
48
40
  const retryNextPageTimeoutId = new Map();
41
+ const resetTimeoutId = new Map();
42
+ const refetchIntervalTimeoutId = new Map();
49
43
  const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
50
44
  const useQuery = createStores(({ get, set, key: _key, keyHash }) => {
51
45
  const key = _key;
52
46
  const getRetryProps = (error, retryCount) => {
53
47
  const prevState = get();
54
- const maxRetryCount = (typeof retry === 'function' ? retry(error, prevState) : retry) || 0;
55
- const shouldRetry = retryCount < maxRetryCount;
56
- const delay = (typeof retryDelay === 'function' ? retryDelay(error, prevState) : retryDelay) || 0;
57
- return { shouldRetry, delay };
48
+ const maxRetryCount = getValueOrComputedValue(retry, error, prevState) || 0;
49
+ const delay = getValueOrComputedValue(retryDelay, error, prevState) || 0;
50
+ return { shouldRetry: retryCount < maxRetryCount, delay };
58
51
  };
59
52
  const forceFetch = () => new Promise((resolve) => {
60
53
  const responseAllPages = [];
61
54
  const newPageParams = [undefined];
62
55
  let pageParam = undefined;
63
- const { isWaiting, isLoading, pageParams } = get();
64
- if (isWaiting || enabled === false || (typeof enabled === 'function' && !enabled(key)))
65
- return;
56
+ clearTimeout(refetchIntervalTimeoutId.get(keyHash));
57
+ const state = get();
58
+ const { isWaiting, isLoading, pageParams } = state;
59
+ if (isWaiting || !getValueOrComputedValue(enabled, key))
60
+ return resolve(state);
61
+ let shouldcancel = false;
62
+ const cancel = () => {
63
+ shouldcancel = true;
64
+ };
65
+ onBeforeFetch(cancel, state);
66
+ if (shouldcancel)
67
+ return resolve(state);
66
68
  if (isLoading)
67
69
  set({ isWaiting: true });
68
70
  else
@@ -113,6 +115,12 @@ export const createQuery = (queryFn, options = {}) => {
113
115
  pageParams: newPageParams,
114
116
  hasNextPage: hasValue(newPageParam),
115
117
  });
118
+ const refetchIntervalValue = typeof window !== 'undefined' && getValueOrComputedValue(refetchInterval);
119
+ if (refetchIntervalValue) {
120
+ refetchIntervalTimeoutId.set(keyHash, window.setTimeout(() => {
121
+ forceFetch();
122
+ }, refetchIntervalValue));
123
+ }
116
124
  onSuccess(response, stateBeforeCallQuery);
117
125
  resolve(get());
118
126
  })
@@ -226,26 +234,37 @@ export const createQuery = (queryFn, options = {}) => {
226
234
  }, (() => {
227
235
  const fetchWindowFocusHandler = () => {
228
236
  useQuery.getAllWithSubscriber().forEach((state) => {
229
- getDecision(fetchOnWindowFocus, state.key, {
230
- ifTrue: state.fetch,
231
- ifAlways: state.forceFetch,
232
- });
237
+ const result = getValueOrComputedValue(fetchOnWindowFocus, state.key);
238
+ if (result === 'always')
239
+ state.forceFetch();
240
+ else if (result)
241
+ state.fetch();
233
242
  });
234
243
  };
235
244
  return {
236
245
  ...createStoresOptions,
237
246
  defaultDeps,
238
247
  onFirstSubscribe: (state) => {
248
+ if (state.isSuccess) {
249
+ const refetchIntervalValue = typeof window !== 'undefined' && getValueOrComputedValue(refetchInterval);
250
+ if (refetchIntervalValue) {
251
+ refetchIntervalTimeoutId.set(state.keyHash, window.setTimeout(() => {
252
+ state.forceFetch();
253
+ }, refetchIntervalValue));
254
+ }
255
+ }
239
256
  if (typeof window !== 'undefined' && fetchOnWindowFocus) {
240
257
  window.addEventListener('focus', fetchWindowFocusHandler);
241
258
  }
259
+ clearTimeout(resetTimeoutId.get(state.keyHash));
242
260
  onFirstSubscribe(state);
243
261
  },
244
262
  onSubscribe: (state) => {
245
- getDecision(fetchOnMount, state.key, {
246
- ifTrue: state.fetch,
247
- ifAlways: state.forceFetch,
248
- });
263
+ const result = getValueOrComputedValue(fetchOnMount, state.key);
264
+ if (result === 'always')
265
+ state.forceFetch();
266
+ else if (result)
267
+ state.fetch();
249
268
  onSubscribe(state);
250
269
  },
251
270
  onLastUnsubscribe: (state) => {
@@ -255,6 +274,12 @@ export const createQuery = (queryFn, options = {}) => {
255
274
  useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
256
275
  clearTimeout(retryTimeoutId.get(state.keyHash));
257
276
  clearTimeout(retryNextPageTimeoutId.get(state.keyHash));
277
+ clearTimeout(refetchIntervalTimeoutId.get(state.keyHash));
278
+ if (typeof window !== 'undefined' && cacheTime !== Infinity) {
279
+ resetTimeoutId.set(state.keyHash, window.setTimeout(() => {
280
+ useQuery.set(state.key, INITIAL_QUERY_STATE);
281
+ }, cacheTime));
282
+ }
258
283
  onLastUnsubscribe(state);
259
284
  },
260
285
  onBeforeChangeKey: (nextKey, prevKey) => {
@@ -2,3 +2,4 @@ export declare const noop: () => void;
2
2
  export declare const identityFn: <T>(value: T) => T;
3
3
  export declare const hasValue: (value: any) => boolean;
4
4
  export declare const hashStoreKey: (obj?: any) => string;
5
+ export declare const getValueOrComputedValue: <T, P extends any[]>(valueOrComputeValueFn: T | ((...params: P) => T), ...params: P) => T;
@@ -2,3 +2,9 @@ export const noop = () => { };
2
2
  export const identityFn = (value) => value;
3
3
  export const hasValue = (value) => value !== undefined && value !== null;
4
4
  export const hashStoreKey = (obj) => JSON.stringify(obj, Object.keys(obj).sort());
5
+ export const getValueOrComputedValue = (valueOrComputeValueFn, ...params) => {
6
+ if (typeof valueOrComputeValueFn === 'function') {
7
+ return valueOrComputeValueFn(...params);
8
+ }
9
+ return valueOrComputeValueFn;
10
+ };
@@ -72,7 +72,7 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
72
72
  *
73
73
  * `"success"` = has data.
74
74
  *
75
- * `"error"` = has error.
75
+ * `"error"` = has error and no data.
76
76
  *
77
77
  * It has no relation with network fetching state.
78
78
  * If you're looking for network fetching state, use `isWaiting` instead.
@@ -178,9 +178,29 @@ export type CreateQueryOptions<TKey extends StoreKey = StoreKey, TResponse = any
178
178
  * This function should return a variable that will be used when fetching next page (`pageParam`).
179
179
  */
180
180
  getNextPageParam?: (lastPage: TResponse, index: number) => any;
181
+ onBeforeFetch?: (cancel: () => void, state: QueryState<TKey, TResponse, TData, TError>) => void;
181
182
  onSuccess?: (response: TResponse, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
182
183
  onError?: (error: TError, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
183
184
  onSettled?: (stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
185
+ /**
186
+ * Cache time in miliseconds.
187
+ *
188
+ * When a query becomes inactive (no longer have subscribers), it will be reset after this duration,
189
+ * and the cache data will be garbage collected.
190
+ *
191
+ * Set it to `Infinity` to disable garbage collection.
192
+ *
193
+ * Defaults to `5 * 60 * 1000` (5 minutes).
194
+ */
195
+ cacheTime?: number;
196
+ /**
197
+ * Polling interval in milliseconds.
198
+ *
199
+ * Disabled by default.
200
+ *
201
+ * If the query is on error state, the polling interval will be disabled, and it will use `retry` instead.
202
+ */
203
+ refetchInterval?: number | false | (() => number | false);
184
204
  };
185
205
  export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = UseStores<TKey, QueryState<TKey, TResponse, TData, TError>> & {
186
206
  /**
@@ -5,14 +5,6 @@ const preact_1 = require("preact");
5
5
  const hooks_1 = require("preact/hooks");
6
6
  const utils_1 = require("../utils");
7
7
  const create_stores_1 = require("./create-stores");
8
- const getDecision = (value, param, { ifTrue, ifAlways }) => {
9
- if (value === true || (typeof value === 'function' && value(param) === true)) {
10
- ifTrue();
11
- }
12
- else if (value === 'always' || (typeof value === 'function' && value(param) === 'always')) {
13
- ifAlways();
14
- }
15
- };
16
8
  const INITIAL_QUERY_STATE = {
17
9
  isWaiting: false,
18
10
  isWaitingNextPage: false,
@@ -47,26 +39,36 @@ const createQuery = (queryFn, options = {}) => {
47
39
  const defaultFetchOnWindowFocus = options.fetchOnMount ?? true;
48
40
  const { onFirstSubscribe = utils_1.noop, onSubscribe = utils_1.noop, onLastUnsubscribe = utils_1.noop, onBeforeChangeKey = utils_1.noop, defaultDeps = useQueryDefaultDeps, select = utils_1.identityFn, staleTime = 3000, // 3 seconds
49
41
  fetchOnMount = true, fetchOnWindowFocus = defaultFetchOnWindowFocus, enabled = true, retry = 1, retryDelay = 2000, // 2 seconds
50
- keepPreviousData, getNextPageParam = () => undefined, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, ...createStoresOptions } = options;
42
+ keepPreviousData, getNextPageParam = () => undefined, onBeforeFetch = utils_1.noop, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, cacheTime = 5 * 60 * 1000, refetchInterval = false, ...createStoresOptions } = options;
51
43
  const retryTimeoutId = new Map();
52
44
  const retryNextPageTimeoutId = new Map();
45
+ const resetTimeoutId = new Map();
46
+ const refetchIntervalTimeoutId = new Map();
53
47
  const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
54
48
  const useQuery = (0, create_stores_1.createStores)(({ get, set, key: _key, keyHash }) => {
55
49
  const key = _key;
56
50
  const getRetryProps = (error, retryCount) => {
57
51
  const prevState = get();
58
- const maxRetryCount = (typeof retry === 'function' ? retry(error, prevState) : retry) || 0;
59
- const shouldRetry = retryCount < maxRetryCount;
60
- const delay = (typeof retryDelay === 'function' ? retryDelay(error, prevState) : retryDelay) || 0;
61
- return { shouldRetry, delay };
52
+ const maxRetryCount = (0, utils_1.getValueOrComputedValue)(retry, error, prevState) || 0;
53
+ const delay = (0, utils_1.getValueOrComputedValue)(retryDelay, error, prevState) || 0;
54
+ return { shouldRetry: retryCount < maxRetryCount, delay };
62
55
  };
63
56
  const forceFetch = () => new Promise((resolve) => {
64
57
  const responseAllPages = [];
65
58
  const newPageParams = [undefined];
66
59
  let pageParam = undefined;
67
- const { isWaiting, isLoading, pageParams } = get();
68
- if (isWaiting || enabled === false || (typeof enabled === 'function' && !enabled(key)))
69
- return;
60
+ clearTimeout(refetchIntervalTimeoutId.get(keyHash));
61
+ const state = get();
62
+ const { isWaiting, isLoading, pageParams } = state;
63
+ if (isWaiting || !(0, utils_1.getValueOrComputedValue)(enabled, key))
64
+ return resolve(state);
65
+ let shouldcancel = false;
66
+ const cancel = () => {
67
+ shouldcancel = true;
68
+ };
69
+ onBeforeFetch(cancel, state);
70
+ if (shouldcancel)
71
+ return resolve(state);
70
72
  if (isLoading)
71
73
  set({ isWaiting: true });
72
74
  else
@@ -117,6 +119,12 @@ const createQuery = (queryFn, options = {}) => {
117
119
  pageParams: newPageParams,
118
120
  hasNextPage: (0, utils_1.hasValue)(newPageParam),
119
121
  });
122
+ const refetchIntervalValue = typeof window !== 'undefined' && (0, utils_1.getValueOrComputedValue)(refetchInterval);
123
+ if (refetchIntervalValue) {
124
+ refetchIntervalTimeoutId.set(keyHash, window.setTimeout(() => {
125
+ forceFetch();
126
+ }, refetchIntervalValue));
127
+ }
120
128
  onSuccess(response, stateBeforeCallQuery);
121
129
  resolve(get());
122
130
  })
@@ -230,26 +238,37 @@ const createQuery = (queryFn, options = {}) => {
230
238
  }, (() => {
231
239
  const fetchWindowFocusHandler = () => {
232
240
  useQuery.getAllWithSubscriber().forEach((state) => {
233
- getDecision(fetchOnWindowFocus, state.key, {
234
- ifTrue: state.fetch,
235
- ifAlways: state.forceFetch,
236
- });
241
+ const result = (0, utils_1.getValueOrComputedValue)(fetchOnWindowFocus, state.key);
242
+ if (result === 'always')
243
+ state.forceFetch();
244
+ else if (result)
245
+ state.fetch();
237
246
  });
238
247
  };
239
248
  return {
240
249
  ...createStoresOptions,
241
250
  defaultDeps,
242
251
  onFirstSubscribe: (state) => {
252
+ if (state.isSuccess) {
253
+ const refetchIntervalValue = typeof window !== 'undefined' && (0, utils_1.getValueOrComputedValue)(refetchInterval);
254
+ if (refetchIntervalValue) {
255
+ refetchIntervalTimeoutId.set(state.keyHash, window.setTimeout(() => {
256
+ state.forceFetch();
257
+ }, refetchIntervalValue));
258
+ }
259
+ }
243
260
  if (typeof window !== 'undefined' && fetchOnWindowFocus) {
244
261
  window.addEventListener('focus', fetchWindowFocusHandler);
245
262
  }
263
+ clearTimeout(resetTimeoutId.get(state.keyHash));
246
264
  onFirstSubscribe(state);
247
265
  },
248
266
  onSubscribe: (state) => {
249
- getDecision(fetchOnMount, state.key, {
250
- ifTrue: state.fetch,
251
- ifAlways: state.forceFetch,
252
- });
267
+ const result = (0, utils_1.getValueOrComputedValue)(fetchOnMount, state.key);
268
+ if (result === 'always')
269
+ state.forceFetch();
270
+ else if (result)
271
+ state.fetch();
253
272
  onSubscribe(state);
254
273
  },
255
274
  onLastUnsubscribe: (state) => {
@@ -259,6 +278,12 @@ const createQuery = (queryFn, options = {}) => {
259
278
  useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
260
279
  clearTimeout(retryTimeoutId.get(state.keyHash));
261
280
  clearTimeout(retryNextPageTimeoutId.get(state.keyHash));
281
+ clearTimeout(refetchIntervalTimeoutId.get(state.keyHash));
282
+ if (typeof window !== 'undefined' && cacheTime !== Infinity) {
283
+ resetTimeoutId.set(state.keyHash, window.setTimeout(() => {
284
+ useQuery.set(state.key, INITIAL_QUERY_STATE);
285
+ }, cacheTime));
286
+ }
262
287
  onLastUnsubscribe(state);
263
288
  },
264
289
  onBeforeChangeKey: (nextKey, prevKey) => {
@@ -71,7 +71,7 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
71
71
  *
72
72
  * `"success"` = has data.
73
73
  *
74
- * `"error"` = has error.
74
+ * `"error"` = has error and no data.
75
75
  *
76
76
  * It has no relation with network fetching state.
77
77
  * If you're looking for network fetching state, use `isWaiting` instead.
@@ -177,9 +177,29 @@ export type CreateQueryOptions<TKey extends StoreKey = StoreKey, TResponse = any
177
177
  * This function should return a variable that will be used when fetching next page (`pageParam`).
178
178
  */
179
179
  getNextPageParam?: (lastPage: TResponse, index: number) => any;
180
+ onBeforeFetch?: (cancel: () => void, state: QueryState<TKey, TResponse, TData, TError>) => void;
180
181
  onSuccess?: (response: TResponse, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
181
182
  onError?: (error: TError, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
182
183
  onSettled?: (stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
184
+ /**
185
+ * Cache time in miliseconds.
186
+ *
187
+ * When a query becomes inactive (no longer have subscribers), it will be reset after this duration,
188
+ * and the cache data will be garbage collected.
189
+ *
190
+ * Set it to `Infinity` to disable garbage collection.
191
+ *
192
+ * Defaults to `5 * 60 * 1000` (5 minutes).
193
+ */
194
+ cacheTime?: number;
195
+ /**
196
+ * Polling interval in milliseconds.
197
+ *
198
+ * Disabled by default.
199
+ *
200
+ * If the query is on error state, the polling interval will be disabled, and it will use `retry` instead.
201
+ */
202
+ refetchInterval?: number | false | (() => number | false);
183
203
  };
184
204
  export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = UseStores<TKey, QueryState<TKey, TResponse, TData, TError>> & {
185
205
  /**
@@ -4,14 +4,6 @@ exports.createQuery = void 0;
4
4
  const react_1 = require("react");
5
5
  const utils_1 = require("../utils");
6
6
  const create_stores_1 = require("./create-stores");
7
- const getDecision = (value, param, { ifTrue, ifAlways }) => {
8
- if (value === true || (typeof value === 'function' && value(param) === true)) {
9
- ifTrue();
10
- }
11
- else if (value === 'always' || (typeof value === 'function' && value(param) === 'always')) {
12
- ifAlways();
13
- }
14
- };
15
7
  const INITIAL_QUERY_STATE = {
16
8
  isWaiting: false,
17
9
  isWaitingNextPage: false,
@@ -46,26 +38,36 @@ const createQuery = (queryFn, options = {}) => {
46
38
  const defaultFetchOnWindowFocus = options.fetchOnMount ?? true;
47
39
  const { onFirstSubscribe = utils_1.noop, onSubscribe = utils_1.noop, onLastUnsubscribe = utils_1.noop, onBeforeChangeKey = utils_1.noop, defaultDeps = useQueryDefaultDeps, select = utils_1.identityFn, staleTime = 3000, // 3 seconds
48
40
  fetchOnMount = true, fetchOnWindowFocus = defaultFetchOnWindowFocus, enabled = true, retry = 1, retryDelay = 2000, // 2 seconds
49
- keepPreviousData, getNextPageParam = () => undefined, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, ...createStoresOptions } = options;
41
+ keepPreviousData, getNextPageParam = () => undefined, onBeforeFetch = utils_1.noop, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, cacheTime = 5 * 60 * 1000, refetchInterval = false, ...createStoresOptions } = options;
50
42
  const retryTimeoutId = new Map();
51
43
  const retryNextPageTimeoutId = new Map();
44
+ const resetTimeoutId = new Map();
45
+ const refetchIntervalTimeoutId = new Map();
52
46
  const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
53
47
  const useQuery = (0, create_stores_1.createStores)(({ get, set, key: _key, keyHash }) => {
54
48
  const key = _key;
55
49
  const getRetryProps = (error, retryCount) => {
56
50
  const prevState = get();
57
- const maxRetryCount = (typeof retry === 'function' ? retry(error, prevState) : retry) || 0;
58
- const shouldRetry = retryCount < maxRetryCount;
59
- const delay = (typeof retryDelay === 'function' ? retryDelay(error, prevState) : retryDelay) || 0;
60
- return { shouldRetry, delay };
51
+ const maxRetryCount = (0, utils_1.getValueOrComputedValue)(retry, error, prevState) || 0;
52
+ const delay = (0, utils_1.getValueOrComputedValue)(retryDelay, error, prevState) || 0;
53
+ return { shouldRetry: retryCount < maxRetryCount, delay };
61
54
  };
62
55
  const forceFetch = () => new Promise((resolve) => {
63
56
  const responseAllPages = [];
64
57
  const newPageParams = [undefined];
65
58
  let pageParam = undefined;
66
- const { isWaiting, isLoading, pageParams } = get();
67
- if (isWaiting || enabled === false || (typeof enabled === 'function' && !enabled(key)))
68
- return;
59
+ clearTimeout(refetchIntervalTimeoutId.get(keyHash));
60
+ const state = get();
61
+ const { isWaiting, isLoading, pageParams } = state;
62
+ if (isWaiting || !(0, utils_1.getValueOrComputedValue)(enabled, key))
63
+ return resolve(state);
64
+ let shouldcancel = false;
65
+ const cancel = () => {
66
+ shouldcancel = true;
67
+ };
68
+ onBeforeFetch(cancel, state);
69
+ if (shouldcancel)
70
+ return resolve(state);
69
71
  if (isLoading)
70
72
  set({ isWaiting: true });
71
73
  else
@@ -116,6 +118,12 @@ const createQuery = (queryFn, options = {}) => {
116
118
  pageParams: newPageParams,
117
119
  hasNextPage: (0, utils_1.hasValue)(newPageParam),
118
120
  });
121
+ const refetchIntervalValue = typeof window !== 'undefined' && (0, utils_1.getValueOrComputedValue)(refetchInterval);
122
+ if (refetchIntervalValue) {
123
+ refetchIntervalTimeoutId.set(keyHash, window.setTimeout(() => {
124
+ forceFetch();
125
+ }, refetchIntervalValue));
126
+ }
119
127
  onSuccess(response, stateBeforeCallQuery);
120
128
  resolve(get());
121
129
  })
@@ -229,26 +237,37 @@ const createQuery = (queryFn, options = {}) => {
229
237
  }, (() => {
230
238
  const fetchWindowFocusHandler = () => {
231
239
  useQuery.getAllWithSubscriber().forEach((state) => {
232
- getDecision(fetchOnWindowFocus, state.key, {
233
- ifTrue: state.fetch,
234
- ifAlways: state.forceFetch,
235
- });
240
+ const result = (0, utils_1.getValueOrComputedValue)(fetchOnWindowFocus, state.key);
241
+ if (result === 'always')
242
+ state.forceFetch();
243
+ else if (result)
244
+ state.fetch();
236
245
  });
237
246
  };
238
247
  return {
239
248
  ...createStoresOptions,
240
249
  defaultDeps,
241
250
  onFirstSubscribe: (state) => {
251
+ if (state.isSuccess) {
252
+ const refetchIntervalValue = typeof window !== 'undefined' && (0, utils_1.getValueOrComputedValue)(refetchInterval);
253
+ if (refetchIntervalValue) {
254
+ refetchIntervalTimeoutId.set(state.keyHash, window.setTimeout(() => {
255
+ state.forceFetch();
256
+ }, refetchIntervalValue));
257
+ }
258
+ }
242
259
  if (typeof window !== 'undefined' && fetchOnWindowFocus) {
243
260
  window.addEventListener('focus', fetchWindowFocusHandler);
244
261
  }
262
+ clearTimeout(resetTimeoutId.get(state.keyHash));
245
263
  onFirstSubscribe(state);
246
264
  },
247
265
  onSubscribe: (state) => {
248
- getDecision(fetchOnMount, state.key, {
249
- ifTrue: state.fetch,
250
- ifAlways: state.forceFetch,
251
- });
266
+ const result = (0, utils_1.getValueOrComputedValue)(fetchOnMount, state.key);
267
+ if (result === 'always')
268
+ state.forceFetch();
269
+ else if (result)
270
+ state.fetch();
252
271
  onSubscribe(state);
253
272
  },
254
273
  onLastUnsubscribe: (state) => {
@@ -258,6 +277,12 @@ const createQuery = (queryFn, options = {}) => {
258
277
  useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
259
278
  clearTimeout(retryTimeoutId.get(state.keyHash));
260
279
  clearTimeout(retryNextPageTimeoutId.get(state.keyHash));
280
+ clearTimeout(refetchIntervalTimeoutId.get(state.keyHash));
281
+ if (typeof window !== 'undefined' && cacheTime !== Infinity) {
282
+ resetTimeoutId.set(state.keyHash, window.setTimeout(() => {
283
+ useQuery.set(state.key, INITIAL_QUERY_STATE);
284
+ }, cacheTime));
285
+ }
261
286
  onLastUnsubscribe(state);
262
287
  },
263
288
  onBeforeChangeKey: (nextKey, prevKey) => {
@@ -2,3 +2,4 @@ export declare const noop: () => void;
2
2
  export declare const identityFn: <T>(value: T) => T;
3
3
  export declare const hasValue: (value: any) => boolean;
4
4
  export declare const hashStoreKey: (obj?: any) => string;
5
+ export declare const getValueOrComputedValue: <T, P extends any[]>(valueOrComputeValueFn: T | ((...params: P) => T), ...params: P) => T;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hashStoreKey = exports.hasValue = exports.identityFn = exports.noop = void 0;
3
+ exports.getValueOrComputedValue = exports.hashStoreKey = exports.hasValue = exports.identityFn = exports.noop = void 0;
4
4
  const noop = () => { };
5
5
  exports.noop = noop;
6
6
  const identityFn = (value) => value;
@@ -9,3 +9,10 @@ const hasValue = (value) => value !== undefined && value !== null;
9
9
  exports.hasValue = hasValue;
10
10
  const hashStoreKey = (obj) => JSON.stringify(obj, Object.keys(obj).sort());
11
11
  exports.hashStoreKey = hashStoreKey;
12
+ const getValueOrComputedValue = (valueOrComputeValueFn, ...params) => {
13
+ if (typeof valueOrComputeValueFn === 'function') {
14
+ return valueOrComputeValueFn(...params);
15
+ }
16
+ return valueOrComputeValueFn;
17
+ };
18
+ exports.getValueOrComputedValue = getValueOrComputedValue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "floppy-disk",
3
- "version": "2.3.1",
3
+ "version": "2.4.0-beta.2",
4
4
  "description": "FloppyDisk - lightweight, simple, and powerful state management library",
5
5
  "keywords": [
6
6
  "state",