floppy-disk 2.0.0-alpha.3 → 2.0.0-alpha.5
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 +16 -5
- package/esm/index.d.ts +1 -5
- package/esm/index.js +1 -5
- package/esm/preact/create-mutation.d.ts +28 -0
- package/esm/preact/create-mutation.js +50 -0
- package/esm/preact/create-query.d.ts +197 -0
- package/esm/preact/create-query.js +333 -0
- package/esm/preact/create-store.d.ts +28 -0
- package/esm/preact/create-store.js +35 -0
- package/esm/preact/create-stores.d.ts +45 -0
- package/esm/preact/create-stores.js +96 -0
- package/esm/preact/index.d.ts +5 -0
- package/esm/preact/index.js +5 -0
- package/esm/preact/with-context.d.ts +6 -0
- package/esm/preact/with-context.js +16 -0
- package/esm/react/create-query.d.ts +19 -1
- package/esm/react/create-query.js +13 -2
- package/esm/react/index.d.ts +5 -0
- package/esm/react/index.js +5 -0
- package/lib/index.d.ts +1 -5
- package/lib/index.js +1 -5
- package/lib/preact/create-mutation.d.ts +28 -0
- package/lib/preact/create-mutation.js +54 -0
- package/lib/preact/create-query.d.ts +197 -0
- package/lib/preact/create-query.js +337 -0
- package/lib/preact/create-store.d.ts +28 -0
- package/lib/preact/create-store.js +39 -0
- package/lib/preact/create-stores.d.ts +45 -0
- package/lib/preact/create-stores.js +100 -0
- package/lib/preact/index.d.ts +5 -0
- package/lib/preact/index.js +8 -0
- package/lib/preact/with-context.d.ts +6 -0
- package/lib/preact/with-context.js +20 -0
- package/lib/react/create-query.d.ts +19 -1
- package/lib/react/create-query.js +13 -2
- package/lib/react/index.d.ts +5 -0
- package/lib/react/index.js +8 -0
- package/package.json +2 -5
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createQuery = void 0;
|
|
4
|
+
const hooks_1 = require("preact/hooks");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
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
|
+
const DEFAULT_STALE_TIME = 3000; // 3 seconds
|
|
16
|
+
const INITIAL_QUERY_STATE = {
|
|
17
|
+
isWaiting: false,
|
|
18
|
+
isWaitingNextPage: false,
|
|
19
|
+
status: 'loading',
|
|
20
|
+
isLoading: true,
|
|
21
|
+
isSuccess: false,
|
|
22
|
+
isError: false,
|
|
23
|
+
isRefetching: false,
|
|
24
|
+
isRefetchError: false,
|
|
25
|
+
isPreviousData: false,
|
|
26
|
+
isOptimisticData: false,
|
|
27
|
+
data: null,
|
|
28
|
+
response: null,
|
|
29
|
+
responseUpdatedAt: null,
|
|
30
|
+
error: null,
|
|
31
|
+
errorUpdatedAt: null,
|
|
32
|
+
retryCount: 0,
|
|
33
|
+
isGoingToRetry: false,
|
|
34
|
+
pageParam: undefined,
|
|
35
|
+
pageParams: [undefined],
|
|
36
|
+
hasNextPage: false,
|
|
37
|
+
retryNextPageCount: 0,
|
|
38
|
+
isGoingToRetryNextPage: false,
|
|
39
|
+
};
|
|
40
|
+
const useQueryDefaultDeps = (state) => [
|
|
41
|
+
state.data,
|
|
42
|
+
state.error,
|
|
43
|
+
state.isWaitingNextPage,
|
|
44
|
+
state.hasNextPage,
|
|
45
|
+
];
|
|
46
|
+
const createQuery = (queryFn, options = {}) => {
|
|
47
|
+
const { onFirstSubscribe = utils_1.noop, onSubscribe = utils_1.noop, onLastUnsubscribe = utils_1.noop, onBeforeChangeKey = utils_1.noop, defaultDeps = useQueryDefaultDeps, select = utils_1.identityFn, staleTime = DEFAULT_STALE_TIME, fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, keepPreviousData, getNextPageParam = () => undefined, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, hashKeyFn = utils_1.hashStoreKey, ...createStoresOptions } = options;
|
|
48
|
+
const retryTimeoutId = new Map();
|
|
49
|
+
const retryNextPageTimeoutId = new Map();
|
|
50
|
+
const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
|
|
51
|
+
const useQuery = (0, create_stores_1.createStores)(({ key: _key, get, set }) => {
|
|
52
|
+
const key = _key;
|
|
53
|
+
const getRetryProps = (error, retryCount) => {
|
|
54
|
+
const maxRetryCount = (typeof retry === 'function' ? retry(error, key) : retry) || 0;
|
|
55
|
+
const shouldRetry = retryCount < maxRetryCount;
|
|
56
|
+
const delay = (typeof retryDelay === 'function' ? retryDelay(error, key) : retryDelay) || 0;
|
|
57
|
+
return { shouldRetry, delay };
|
|
58
|
+
};
|
|
59
|
+
const forceFetch = () => {
|
|
60
|
+
const responseAllPages = [];
|
|
61
|
+
const newPageParams = [undefined];
|
|
62
|
+
let pageParam = undefined;
|
|
63
|
+
const { isWaiting, isLoading, pageParams } = get();
|
|
64
|
+
if (isWaiting || enabled === false || (typeof enabled === 'function' && !enabled(key)))
|
|
65
|
+
return;
|
|
66
|
+
if (isLoading)
|
|
67
|
+
set({ isWaiting: true });
|
|
68
|
+
else
|
|
69
|
+
set({ isWaiting: true, isRefetching: true });
|
|
70
|
+
const callQuery = () => {
|
|
71
|
+
if (get().isGoingToRetry) {
|
|
72
|
+
if (isLoading)
|
|
73
|
+
set({ isGoingToRetry: false, isWaiting: true });
|
|
74
|
+
else
|
|
75
|
+
set({ isGoingToRetry: false, isWaiting: true, isRefetching: true });
|
|
76
|
+
clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
|
|
77
|
+
}
|
|
78
|
+
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
79
|
+
preventReplaceResponse.set(hashKeyFn(key), false);
|
|
80
|
+
queryFn(key, stateBeforeCallQuery)
|
|
81
|
+
.then((response) => {
|
|
82
|
+
if (preventReplaceResponse.get(hashKeyFn(key))) {
|
|
83
|
+
set({ isWaiting: false });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
responseAllPages.push(response);
|
|
87
|
+
const newPageParam = getNextPageParam(response, responseAllPages.length);
|
|
88
|
+
newPageParams.push(newPageParam);
|
|
89
|
+
if (newPageParam !== undefined && newPageParams.length < pageParams.length) {
|
|
90
|
+
pageParam = newPageParam;
|
|
91
|
+
callQuery();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
set({
|
|
95
|
+
isWaiting: false,
|
|
96
|
+
status: 'success',
|
|
97
|
+
isLoading: false,
|
|
98
|
+
isSuccess: true,
|
|
99
|
+
isError: false,
|
|
100
|
+
isRefetching: false,
|
|
101
|
+
isRefetchError: false,
|
|
102
|
+
isPreviousData: false,
|
|
103
|
+
isOptimisticData: false,
|
|
104
|
+
data: responseAllPages.reduce((prev, responseCurrentPage) => {
|
|
105
|
+
return select(responseCurrentPage, { key, data: prev });
|
|
106
|
+
}, null),
|
|
107
|
+
response,
|
|
108
|
+
responseUpdatedAt: Date.now(),
|
|
109
|
+
error: null,
|
|
110
|
+
errorUpdatedAt: null,
|
|
111
|
+
retryCount: 0,
|
|
112
|
+
pageParam: newPageParam,
|
|
113
|
+
pageParams: newPageParams,
|
|
114
|
+
hasNextPage: newPageParam !== undefined,
|
|
115
|
+
});
|
|
116
|
+
onSuccess(response, stateBeforeCallQuery);
|
|
117
|
+
})
|
|
118
|
+
.catch((error) => {
|
|
119
|
+
const prevState = get();
|
|
120
|
+
const errorUpdatedAt = Date.now();
|
|
121
|
+
const { shouldRetry, delay } = getRetryProps(error, prevState.retryCount);
|
|
122
|
+
set(prevState.isSuccess
|
|
123
|
+
? {
|
|
124
|
+
isWaiting: false,
|
|
125
|
+
isRefetching: false,
|
|
126
|
+
isRefetchError: true,
|
|
127
|
+
data: responseAllPages.length
|
|
128
|
+
? responseAllPages.reduce((prev, response) => {
|
|
129
|
+
return select(response, { key, data: prev });
|
|
130
|
+
}, null)
|
|
131
|
+
: prevState.data,
|
|
132
|
+
error,
|
|
133
|
+
errorUpdatedAt,
|
|
134
|
+
isGoingToRetry: shouldRetry,
|
|
135
|
+
pageParam,
|
|
136
|
+
hasNextPage: pageParam !== undefined,
|
|
137
|
+
}
|
|
138
|
+
: {
|
|
139
|
+
isWaiting: false,
|
|
140
|
+
isError: true,
|
|
141
|
+
error,
|
|
142
|
+
errorUpdatedAt,
|
|
143
|
+
isGoingToRetry: shouldRetry,
|
|
144
|
+
pageParam,
|
|
145
|
+
hasNextPage: pageParam !== undefined,
|
|
146
|
+
});
|
|
147
|
+
if (shouldRetry) {
|
|
148
|
+
retryTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
|
|
149
|
+
set({ retryCount: prevState.retryCount + 1 });
|
|
150
|
+
callQuery();
|
|
151
|
+
}, delay));
|
|
152
|
+
}
|
|
153
|
+
onError(error, stateBeforeCallQuery);
|
|
154
|
+
})
|
|
155
|
+
.finally(() => {
|
|
156
|
+
onSettled(stateBeforeCallQuery);
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
callQuery();
|
|
160
|
+
};
|
|
161
|
+
const fetch = () => {
|
|
162
|
+
const { responseUpdatedAt } = get();
|
|
163
|
+
const isStale = Date.now() > Number(responseUpdatedAt) + staleTime;
|
|
164
|
+
if (!isStale)
|
|
165
|
+
return;
|
|
166
|
+
forceFetch();
|
|
167
|
+
};
|
|
168
|
+
const fetchNextPage = () => {
|
|
169
|
+
const state = get();
|
|
170
|
+
const { isLoading, isWaitingNextPage, data, hasNextPage, pageParam, pageParams } = state;
|
|
171
|
+
if (isLoading)
|
|
172
|
+
return forceFetch();
|
|
173
|
+
if (isWaitingNextPage || !hasNextPage)
|
|
174
|
+
return;
|
|
175
|
+
set({ isWaitingNextPage: true, isGoingToRetryNextPage: false });
|
|
176
|
+
clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(key)));
|
|
177
|
+
queryFn(key, { ...state, pageParam })
|
|
178
|
+
.then((response) => {
|
|
179
|
+
const newPageParam = getNextPageParam(response, pageParams.length);
|
|
180
|
+
set({
|
|
181
|
+
isWaitingNextPage: false,
|
|
182
|
+
response,
|
|
183
|
+
responseUpdatedAt: Date.now(),
|
|
184
|
+
data: select(response, { key, data }),
|
|
185
|
+
pageParam: newPageParam,
|
|
186
|
+
pageParams: pageParams.concat(newPageParam),
|
|
187
|
+
hasNextPage: newPageParam !== undefined,
|
|
188
|
+
});
|
|
189
|
+
})
|
|
190
|
+
.catch((error) => {
|
|
191
|
+
const prevState = get();
|
|
192
|
+
const { shouldRetry, delay } = getRetryProps(error, prevState.retryNextPageCount);
|
|
193
|
+
set({
|
|
194
|
+
isWaitingNextPage: false,
|
|
195
|
+
isError: true,
|
|
196
|
+
error,
|
|
197
|
+
errorUpdatedAt: Date.now(),
|
|
198
|
+
isGoingToRetryNextPage: shouldRetry,
|
|
199
|
+
});
|
|
200
|
+
if (shouldRetry) {
|
|
201
|
+
retryNextPageTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
|
|
202
|
+
set({ retryNextPageCount: prevState.retryNextPageCount + 1 });
|
|
203
|
+
fetchNextPage();
|
|
204
|
+
}, delay));
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
...INITIAL_QUERY_STATE,
|
|
210
|
+
key,
|
|
211
|
+
fetch,
|
|
212
|
+
forceFetch,
|
|
213
|
+
fetchNextPage,
|
|
214
|
+
reset: () => set(INITIAL_QUERY_STATE),
|
|
215
|
+
optimisticUpdate: (response) => useQuery.optimisticUpdate({ key, response }),
|
|
216
|
+
};
|
|
217
|
+
}, (() => {
|
|
218
|
+
const fetchWindowFocusHandler = () => {
|
|
219
|
+
useQuery.getAllWithSubscriber().forEach((state) => {
|
|
220
|
+
getDecision(fetchOnWindowFocus, state.key, {
|
|
221
|
+
ifTrue: state.fetch,
|
|
222
|
+
ifAlways: state.forceFetch,
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
return {
|
|
227
|
+
...createStoresOptions,
|
|
228
|
+
defaultDeps,
|
|
229
|
+
hashKeyFn,
|
|
230
|
+
onFirstSubscribe: (state) => {
|
|
231
|
+
if (typeof window !== 'undefined' && fetchOnWindowFocus) {
|
|
232
|
+
window.addEventListener('focus', fetchWindowFocusHandler);
|
|
233
|
+
}
|
|
234
|
+
onFirstSubscribe(state);
|
|
235
|
+
},
|
|
236
|
+
onSubscribe: (state) => {
|
|
237
|
+
getDecision(fetchOnMount, state.key, {
|
|
238
|
+
ifTrue: state.fetch,
|
|
239
|
+
ifAlways: state.forceFetch,
|
|
240
|
+
});
|
|
241
|
+
onSubscribe(state);
|
|
242
|
+
},
|
|
243
|
+
onLastUnsubscribe: (state) => {
|
|
244
|
+
if (typeof window !== 'undefined' && fetchOnWindowFocus) {
|
|
245
|
+
window.removeEventListener('focus', fetchWindowFocusHandler);
|
|
246
|
+
}
|
|
247
|
+
useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
|
|
248
|
+
clearTimeout(retryTimeoutId.get(hashKeyFn(state.key)));
|
|
249
|
+
clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(state.key)));
|
|
250
|
+
onLastUnsubscribe(state);
|
|
251
|
+
},
|
|
252
|
+
onBeforeChangeKey: (nextKey, prevKey) => {
|
|
253
|
+
if (keepPreviousData) {
|
|
254
|
+
const nextData = useQuery.get(nextKey);
|
|
255
|
+
if (!nextData.data) {
|
|
256
|
+
const prevData = useQuery.get(prevKey);
|
|
257
|
+
if (prevData.data) {
|
|
258
|
+
useQuery.set(nextKey, {
|
|
259
|
+
data: prevData.data,
|
|
260
|
+
response: prevData.response,
|
|
261
|
+
isLoading: false,
|
|
262
|
+
isPreviousData: true,
|
|
263
|
+
}, true);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
onBeforeChangeKey(nextKey, prevKey);
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
})());
|
|
271
|
+
useQuery.setInitialResponse = ({ key, response }) => {
|
|
272
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
273
|
+
(0, hooks_1.useState)(() => {
|
|
274
|
+
if (response === undefined)
|
|
275
|
+
return;
|
|
276
|
+
const newPageParam = getNextPageParam(response, 1);
|
|
277
|
+
useQuery.set(key, {
|
|
278
|
+
status: 'success',
|
|
279
|
+
isLoading: false,
|
|
280
|
+
isSuccess: true,
|
|
281
|
+
isError: false,
|
|
282
|
+
response,
|
|
283
|
+
responseUpdatedAt: Date.now(),
|
|
284
|
+
data: select(response, { key: key, data: null }),
|
|
285
|
+
pageParam: newPageParam,
|
|
286
|
+
pageParams: [undefined, newPageParam],
|
|
287
|
+
hasNextPage: newPageParam !== undefined,
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
useQuery.reset = () => {
|
|
292
|
+
useQuery.getStores().forEach((store) => {
|
|
293
|
+
store.set(INITIAL_QUERY_STATE);
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
useQuery.resetSpecificKey = (key) => {
|
|
297
|
+
const store = useQuery.getStore(key);
|
|
298
|
+
store.set(INITIAL_QUERY_STATE);
|
|
299
|
+
};
|
|
300
|
+
useQuery.invalidate = () => {
|
|
301
|
+
useQuery.getStores().forEach((store) => {
|
|
302
|
+
const { set, getSubscribers } = store;
|
|
303
|
+
set({ responseUpdatedAt: null });
|
|
304
|
+
if (getSubscribers().size > 0)
|
|
305
|
+
store.get().forceFetch();
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
useQuery.invalidateSpecificKey = (key) => {
|
|
309
|
+
const { set, getSubscribers } = useQuery.getStore(key);
|
|
310
|
+
set({ responseUpdatedAt: null });
|
|
311
|
+
if (getSubscribers().size > 0)
|
|
312
|
+
useQuery.get(key).forceFetch();
|
|
313
|
+
};
|
|
314
|
+
useQuery.optimisticUpdate = ({ key, response }) => {
|
|
315
|
+
const prevState = useQuery.get(key);
|
|
316
|
+
const optimisticResponse = typeof response === 'function'
|
|
317
|
+
? response(prevState)
|
|
318
|
+
: response;
|
|
319
|
+
useQuery.set(key, {
|
|
320
|
+
isOptimisticData: true,
|
|
321
|
+
response: optimisticResponse,
|
|
322
|
+
data: select(optimisticResponse, { key: key, data: null }),
|
|
323
|
+
});
|
|
324
|
+
preventReplaceResponse.set(hashKeyFn(key), true);
|
|
325
|
+
const revert = () => {
|
|
326
|
+
useQuery.set(key, {
|
|
327
|
+
isOptimisticData: false,
|
|
328
|
+
response: prevState.response,
|
|
329
|
+
data: prevState.data,
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
const invalidate = () => useQuery.invalidateSpecificKey(key);
|
|
333
|
+
return { revert, invalidate };
|
|
334
|
+
};
|
|
335
|
+
return useQuery;
|
|
336
|
+
};
|
|
337
|
+
exports.createQuery = createQuery;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { InitStoreOptions, SelectDeps, SetStoreData, StoreData, StoreInitializer, Subscribers } from '../vanilla';
|
|
2
|
+
export type WatchProps<T> = {
|
|
3
|
+
selectDeps?: SelectDeps<T>;
|
|
4
|
+
render: (state: T) => any;
|
|
5
|
+
};
|
|
6
|
+
export type UseStore<T extends StoreData> = {
|
|
7
|
+
/**
|
|
8
|
+
* @param selectDeps A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
|
|
9
|
+
* Defaults to `undefined` (reactive to all state change) if you didn't set `defaultDeps` on `createStore`.
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT NOTE: `selectDeps` must not be changed after initialization.
|
|
12
|
+
*/
|
|
13
|
+
(selectDeps?: SelectDeps<T>): T;
|
|
14
|
+
get: () => T;
|
|
15
|
+
set: (value: SetStoreData<T>, silent?: boolean) => void;
|
|
16
|
+
subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
|
|
17
|
+
getSubscribers: () => Subscribers<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Set default values inside a component.
|
|
20
|
+
*
|
|
21
|
+
* IMPORTANT NOTE: Put this on the root component or parent component, before any component subscribed!
|
|
22
|
+
*/
|
|
23
|
+
setDefaultValues: (values: SetStoreData<T>) => void;
|
|
24
|
+
Watch: (props: WatchProps<T>) => any;
|
|
25
|
+
};
|
|
26
|
+
export declare const createStore: <T extends StoreData>(initializer: StoreInitializer<T>, options?: InitStoreOptions<T> & {
|
|
27
|
+
defaultDeps?: SelectDeps<T>;
|
|
28
|
+
}) => UseStore<T>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStore = void 0;
|
|
4
|
+
const hooks_1 = require("preact/hooks");
|
|
5
|
+
const vanilla_1 = require("../vanilla");
|
|
6
|
+
const createStore = (initializer, options = {}) => {
|
|
7
|
+
const { get, set, subscribe, getSubscribers } = (0, vanilla_1.initStore)(initializer, options);
|
|
8
|
+
const { defaultDeps } = options;
|
|
9
|
+
/**
|
|
10
|
+
* IMPORTANT NOTE: selectDeps function must not be changed after initialization.
|
|
11
|
+
*/
|
|
12
|
+
const useStore = (selectDeps = defaultDeps) => {
|
|
13
|
+
const [state, setState] = (0, hooks_1.useState)(get);
|
|
14
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
15
|
+
(0, hooks_1.useEffect)(() => subscribe(setState, selectDeps), []);
|
|
16
|
+
return state;
|
|
17
|
+
};
|
|
18
|
+
useStore.get = get;
|
|
19
|
+
useStore.set = set;
|
|
20
|
+
useStore.subscribe = subscribe;
|
|
21
|
+
useStore.getSubscribers = getSubscribers;
|
|
22
|
+
useStore.setDefaultValues = (value) => {
|
|
23
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
24
|
+
(0, hooks_1.useState)(() => {
|
|
25
|
+
const subscribers = getSubscribers();
|
|
26
|
+
if (subscribers.size > 0) {
|
|
27
|
+
console.warn('Put setDefaultValues on the root component or parent component, before any component subscribed!');
|
|
28
|
+
}
|
|
29
|
+
set(value);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const Watch = ({ selectDeps, render }) => {
|
|
33
|
+
const store = useStore(selectDeps);
|
|
34
|
+
return render(store);
|
|
35
|
+
};
|
|
36
|
+
useStore.Watch = Watch;
|
|
37
|
+
return useStore;
|
|
38
|
+
};
|
|
39
|
+
exports.createStore = createStore;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { InitStoreOptions, InitStoreReturn, SelectDeps, SetStoreData, StoreData, Subscribers } from '../vanilla';
|
|
2
|
+
import { WatchProps } from './create-store';
|
|
3
|
+
type Maybe<T> = T | null | undefined;
|
|
4
|
+
export type StoreKey = Record<string, any> | undefined;
|
|
5
|
+
export type StoresInitializer<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = (api: {
|
|
6
|
+
key: TKey;
|
|
7
|
+
get: () => T;
|
|
8
|
+
set: (value: SetStoreData<T>, silent?: boolean) => void;
|
|
9
|
+
}) => T;
|
|
10
|
+
export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = {
|
|
11
|
+
/**
|
|
12
|
+
* @param key (Optional) Store key, an object that will be hashed into a string as a store identifier.
|
|
13
|
+
*
|
|
14
|
+
* @param selectDeps A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
|
|
15
|
+
* Defaults to `undefined` (reactive to all state change) if you didn't set `defaultDeps` on `createStores`.
|
|
16
|
+
*
|
|
17
|
+
* IMPORTANT NOTE: `selectDeps` must not be changed after initialization.
|
|
18
|
+
*/
|
|
19
|
+
(...args: [Maybe<TKey>, SelectDeps<T>?] | [SelectDeps<T>?]): T;
|
|
20
|
+
get: (key?: Maybe<TKey>) => T;
|
|
21
|
+
getAll: () => T[];
|
|
22
|
+
getAllWithSubscriber: () => T[];
|
|
23
|
+
set: (key: Maybe<TKey>, value: SetStoreData<T>, silent?: boolean) => void;
|
|
24
|
+
setAll: (value: SetStoreData<T>, silent?: boolean) => void;
|
|
25
|
+
subscribe: (key: Maybe<TKey>, fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
|
|
26
|
+
getSubscribers: (key: Maybe<TKey>) => Subscribers<T>;
|
|
27
|
+
getStore: (key?: Maybe<TKey>) => InitStoreReturn<T>;
|
|
28
|
+
getStores: () => Map<string, InitStoreReturn<T>>;
|
|
29
|
+
/**
|
|
30
|
+
* Set default values inside a component.
|
|
31
|
+
*
|
|
32
|
+
* IMPORTANT NOTE: Put this on the root component or parent component, before any component subscribed!
|
|
33
|
+
*/
|
|
34
|
+
setDefaultValues: (key: Maybe<TKey>, values: SetStoreData<T>) => void;
|
|
35
|
+
Watch: (props: WatchProps<T> & {
|
|
36
|
+
storeKey?: Maybe<TKey>;
|
|
37
|
+
}) => any;
|
|
38
|
+
};
|
|
39
|
+
export type CreateStoresOptions<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = InitStoreOptions<T> & {
|
|
40
|
+
onBeforeChangeKey?: (nextKey: TKey, prevKey: TKey) => void;
|
|
41
|
+
defaultDeps?: SelectDeps<T>;
|
|
42
|
+
hashKeyFn?: (obj: TKey) => string;
|
|
43
|
+
};
|
|
44
|
+
export declare const createStores: <TKey extends StoreKey = StoreKey, T extends StoreData = StoreData>(initializer: StoresInitializer<TKey, T>, options?: CreateStoresOptions<TKey, T>) => UseStores<TKey, T>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStores = void 0;
|
|
4
|
+
const hooks_1 = require("preact/hooks");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const vanilla_1 = require("../vanilla");
|
|
7
|
+
const createStores = (initializer, options = {}) => {
|
|
8
|
+
const { onBeforeChangeKey = utils_1.noop, defaultDeps, hashKeyFn = utils_1.hashStoreKey } = options;
|
|
9
|
+
const stores = new Map();
|
|
10
|
+
const getStore = (_key) => {
|
|
11
|
+
const key = _key || {};
|
|
12
|
+
const normalizedKey = hashKeyFn(key);
|
|
13
|
+
if (!stores.has(normalizedKey)) {
|
|
14
|
+
stores.set(normalizedKey, (0, vanilla_1.initStore)((api) => initializer({ key, ...api }), options));
|
|
15
|
+
}
|
|
16
|
+
return stores.get(normalizedKey);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* IMPORTANT NOTE: selectDeps function must not be changed after initialization.
|
|
20
|
+
*/
|
|
21
|
+
const useStores = (...args) => {
|
|
22
|
+
const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
|
|
23
|
+
const key = _key || {};
|
|
24
|
+
const normalizedKey = hashKeyFn(key);
|
|
25
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
|
+
const { get, subscribe } = (0, hooks_1.useMemo)(() => getStore(key), [normalizedKey]);
|
|
27
|
+
const [state, setState] = (0, hooks_1.useState)(get);
|
|
28
|
+
const isFirstRender = (0, hooks_1.useRef)(true);
|
|
29
|
+
const prevKey = (0, hooks_1.useRef)(key);
|
|
30
|
+
(0, hooks_1.useEffect)(() => {
|
|
31
|
+
if (!isFirstRender.current) {
|
|
32
|
+
onBeforeChangeKey(key, prevKey.current);
|
|
33
|
+
setState(get);
|
|
34
|
+
}
|
|
35
|
+
isFirstRender.current = false;
|
|
36
|
+
prevKey.current = key;
|
|
37
|
+
const unsubs = subscribe(setState, selectDeps);
|
|
38
|
+
return unsubs;
|
|
39
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
+
}, [normalizedKey]);
|
|
41
|
+
return state;
|
|
42
|
+
};
|
|
43
|
+
useStores.get = (key) => {
|
|
44
|
+
const store = getStore(key);
|
|
45
|
+
return store.get();
|
|
46
|
+
};
|
|
47
|
+
useStores.getAll = () => {
|
|
48
|
+
const allStores = [];
|
|
49
|
+
stores.forEach((store) => {
|
|
50
|
+
allStores.push(store.get());
|
|
51
|
+
});
|
|
52
|
+
return allStores;
|
|
53
|
+
};
|
|
54
|
+
useStores.getAllWithSubscriber = () => {
|
|
55
|
+
const allStores = [];
|
|
56
|
+
stores.forEach((store) => {
|
|
57
|
+
const subscribers = store.getSubscribers();
|
|
58
|
+
if (subscribers.size > 0)
|
|
59
|
+
allStores.push(store.get());
|
|
60
|
+
});
|
|
61
|
+
return allStores;
|
|
62
|
+
};
|
|
63
|
+
useStores.set = (key, value, silent) => {
|
|
64
|
+
const store = getStore(key);
|
|
65
|
+
store.set(value, silent);
|
|
66
|
+
};
|
|
67
|
+
useStores.setAll = (value, silent) => {
|
|
68
|
+
stores.forEach((store) => {
|
|
69
|
+
store.set(value, silent);
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
useStores.subscribe = (key, fn, selectDeps) => {
|
|
73
|
+
const store = getStore(key);
|
|
74
|
+
return store.subscribe(fn, selectDeps);
|
|
75
|
+
};
|
|
76
|
+
useStores.getSubscribers = (key) => {
|
|
77
|
+
const store = getStore(key);
|
|
78
|
+
return store.getSubscribers();
|
|
79
|
+
};
|
|
80
|
+
useStores.getStore = (key) => getStore(key);
|
|
81
|
+
useStores.getStores = () => stores;
|
|
82
|
+
useStores.setDefaultValues = (key, value) => {
|
|
83
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
84
|
+
(0, hooks_1.useState)(() => {
|
|
85
|
+
const store = getStore(key);
|
|
86
|
+
const subscribers = store.getSubscribers();
|
|
87
|
+
if (subscribers.size > 0) {
|
|
88
|
+
console.warn('Put setDefaultValues on the root component or parent component, before any component subscribed!');
|
|
89
|
+
}
|
|
90
|
+
store.set(value);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
const Watch = ({ storeKey, selectDeps, render }) => {
|
|
94
|
+
const store = useStores(storeKey, selectDeps);
|
|
95
|
+
return render(store);
|
|
96
|
+
};
|
|
97
|
+
useStores.Watch = Watch;
|
|
98
|
+
return useStores;
|
|
99
|
+
};
|
|
100
|
+
exports.createStores = createStores;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./create-store"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./create-stores"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./create-query"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./create-mutation"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./with-context"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { ComponentChildren } from 'preact';
|
|
3
|
+
export declare const withContext: <T>(initFn: () => T) => readonly [({ children, onInitialize, }: {
|
|
4
|
+
children: ComponentChildren;
|
|
5
|
+
onInitialize?: ((value: T) => void) | undefined;
|
|
6
|
+
}) => import("react").JSX.Element, () => T | null];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withContext = void 0;
|
|
4
|
+
const preact_1 = require("preact");
|
|
5
|
+
const hooks_1 = require("preact/hooks");
|
|
6
|
+
const withContext = (initFn) => {
|
|
7
|
+
const Context = (0, preact_1.createContext)(null);
|
|
8
|
+
const Provider = ({ children, onInitialize, }) => {
|
|
9
|
+
const [value] = (0, hooks_1.useState)(() => {
|
|
10
|
+
const store = initFn();
|
|
11
|
+
onInitialize && onInitialize(store);
|
|
12
|
+
return store;
|
|
13
|
+
});
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
return React.createElement(Context.Provider, { value: value }, children);
|
|
16
|
+
};
|
|
17
|
+
const useCurrentContext = () => (0, hooks_1.useContext)(Context);
|
|
18
|
+
return [Provider, useCurrentContext];
|
|
19
|
+
};
|
|
20
|
+
exports.withContext = withContext;
|
|
@@ -21,11 +21,21 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
|
|
|
21
21
|
* You can ignore this if your query is not paginated.
|
|
22
22
|
*/
|
|
23
23
|
fetchNextPage: () => void;
|
|
24
|
-
markAsStale: () => void;
|
|
25
24
|
/**
|
|
26
25
|
* Set query state (data, error, etc) to initial state.
|
|
27
26
|
*/
|
|
28
27
|
reset: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Optimistic update.
|
|
30
|
+
*
|
|
31
|
+
* @returns function to revert the changes & function to invalidate the query
|
|
32
|
+
*
|
|
33
|
+
* IMPORTANT NOTE: This won't work well on infinite query.
|
|
34
|
+
*/
|
|
35
|
+
optimisticUpdate: (response: TResponse | ((prevState: QueryState<TKey, TResponse, TData, TError>) => TResponse)) => {
|
|
36
|
+
revert: () => void;
|
|
37
|
+
invalidate: () => void;
|
|
38
|
+
};
|
|
29
39
|
/**
|
|
30
40
|
* Network fetching status.
|
|
31
41
|
*/
|
|
@@ -153,6 +163,14 @@ export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData =
|
|
|
153
163
|
key?: TKey;
|
|
154
164
|
response: TResponse;
|
|
155
165
|
}) => void;
|
|
166
|
+
/**
|
|
167
|
+
* Set query state (data, error, etc) to initial state.
|
|
168
|
+
*/
|
|
169
|
+
reset: () => void;
|
|
170
|
+
/**
|
|
171
|
+
* Set query state (data, error, etc) to initial state.
|
|
172
|
+
*/
|
|
173
|
+
resetSpecificKey: (key?: TKey | null) => void;
|
|
156
174
|
/**
|
|
157
175
|
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
158
176
|
*/
|
|
@@ -79,8 +79,10 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
79
79
|
preventReplaceResponse.set(hashKeyFn(key), false);
|
|
80
80
|
queryFn(key, stateBeforeCallQuery)
|
|
81
81
|
.then((response) => {
|
|
82
|
-
if (preventReplaceResponse.get(hashKeyFn(key)))
|
|
82
|
+
if (preventReplaceResponse.get(hashKeyFn(key))) {
|
|
83
|
+
set({ isWaiting: false });
|
|
83
84
|
return;
|
|
85
|
+
}
|
|
84
86
|
responseAllPages.push(response);
|
|
85
87
|
const newPageParam = getNextPageParam(response, responseAllPages.length);
|
|
86
88
|
newPageParams.push(newPageParam);
|
|
@@ -209,8 +211,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
209
211
|
fetch,
|
|
210
212
|
forceFetch,
|
|
211
213
|
fetchNextPage,
|
|
212
|
-
markAsStale: () => set({ responseUpdatedAt: null }),
|
|
213
214
|
reset: () => set(INITIAL_QUERY_STATE),
|
|
215
|
+
optimisticUpdate: (response) => useQuery.optimisticUpdate({ key, response }),
|
|
214
216
|
};
|
|
215
217
|
}, (() => {
|
|
216
218
|
const fetchWindowFocusHandler = () => {
|
|
@@ -286,6 +288,15 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
286
288
|
});
|
|
287
289
|
});
|
|
288
290
|
};
|
|
291
|
+
useQuery.reset = () => {
|
|
292
|
+
useQuery.getStores().forEach((store) => {
|
|
293
|
+
store.set(INITIAL_QUERY_STATE);
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
useQuery.resetSpecificKey = (key) => {
|
|
297
|
+
const store = useQuery.getStore(key);
|
|
298
|
+
store.set(INITIAL_QUERY_STATE);
|
|
299
|
+
};
|
|
289
300
|
useQuery.invalidate = () => {
|
|
290
301
|
useQuery.getStores().forEach((store) => {
|
|
291
302
|
const { set, getSubscribers } = store;
|