@data-client/core 0.1.0
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/LICENSE +201 -0
- package/README.md +71 -0
- package/dist/index.js +1643 -0
- package/dist/index.umd.min.js +1 -0
- package/dist/next.js +461 -0
- package/dist/package.json +1 -0
- package/legacy/actionTypes.js +12 -0
- package/legacy/compatibleActions.js +2 -0
- package/legacy/controller/BaseController.js +289 -0
- package/legacy/controller/Controller.js +20 -0
- package/legacy/controller/createFetch.js +42 -0
- package/legacy/controller/createInvalidate.js +12 -0
- package/legacy/controller/createInvalidateAll.js +8 -0
- package/legacy/controller/createOptimistic.js +33 -0
- package/legacy/controller/createReceive.js +36 -0
- package/legacy/controller/createReset.js +8 -0
- package/legacy/controller/createSubscription.js +30 -0
- package/legacy/controller/types.js +2 -0
- package/legacy/endpoint/index.js +2 -0
- package/legacy/endpoint/shapes.js +2 -0
- package/legacy/endpoint/types.js +2 -0
- package/legacy/fsa.js +2 -0
- package/legacy/index.js +22 -0
- package/legacy/internal.js +4 -0
- package/legacy/legacyActions.js +2 -0
- package/legacy/manager/ConnectionListener.js +2 -0
- package/legacy/manager/DefaultConnectionListener.js +40 -0
- package/legacy/manager/DevtoolsManager.js +73 -0
- package/legacy/manager/LogoutManager.js +34 -0
- package/legacy/manager/NetworkManager.js +291 -0
- package/legacy/manager/PollingSubscription.js +159 -0
- package/legacy/manager/SubscriptionManager.js +117 -0
- package/legacy/manager/applyManager.js +23 -0
- package/legacy/manager/devtoolsTypes.js +2 -0
- package/legacy/manager/index.js +6 -0
- package/legacy/middlewareTypes.js +2 -0
- package/legacy/newActions.js +2 -0
- package/legacy/next/Controller.js +24 -0
- package/legacy/next/index.js +3 -0
- package/legacy/previousActions.js +2 -0
- package/legacy/state/RIC.js +3 -0
- package/legacy/state/applyUpdatersToResults.js +4 -0
- package/legacy/state/legacy-actions/createFetch.js +62 -0
- package/legacy/state/legacy-actions/createReceive.js +37 -0
- package/legacy/state/legacy-actions/createReceiveError.js +28 -0
- package/legacy/state/legacy-actions/index.js +4 -0
- package/legacy/state/reducer/createReducer.js +54 -0
- package/legacy/state/reducer/fetchReducer.js +32 -0
- package/legacy/state/reducer/invalidateReducer.js +28 -0
- package/legacy/state/reducer/setReducer.js +113 -0
- package/legacy/state/reducerInstance.js +9 -0
- package/legacy/state/selectMeta.js +4 -0
- package/legacy/types.js +8 -0
- package/lib/actionTypes.d.ts +11 -0
- package/lib/actionTypes.d.ts.map +1 -0
- package/lib/actionTypes.js +12 -0
- package/lib/compatibleActions.d.ts +47 -0
- package/lib/compatibleActions.d.ts.map +1 -0
- package/lib/compatibleActions.js +2 -0
- package/lib/controller/BaseController.d.ts +128 -0
- package/lib/controller/BaseController.d.ts.map +1 -0
- package/lib/controller/BaseController.js +289 -0
- package/lib/controller/Controller.d.ts +14 -0
- package/lib/controller/Controller.d.ts.map +1 -0
- package/lib/controller/Controller.js +20 -0
- package/lib/controller/createFetch.d.ts +12 -0
- package/lib/controller/createFetch.d.ts.map +1 -0
- package/lib/controller/createFetch.js +42 -0
- package/lib/controller/createInvalidate.d.ts +6 -0
- package/lib/controller/createInvalidate.d.ts.map +1 -0
- package/lib/controller/createInvalidate.js +12 -0
- package/lib/controller/createInvalidateAll.d.ts +3 -0
- package/lib/controller/createInvalidateAll.d.ts.map +1 -0
- package/lib/controller/createInvalidateAll.js +8 -0
- package/lib/controller/createOptimistic.d.ts +10 -0
- package/lib/controller/createOptimistic.d.ts.map +1 -0
- package/lib/controller/createOptimistic.js +33 -0
- package/lib/controller/createReceive.d.ts +20 -0
- package/lib/controller/createReceive.d.ts.map +1 -0
- package/lib/controller/createReceive.js +36 -0
- package/lib/controller/createReset.d.ts +3 -0
- package/lib/controller/createReset.d.ts.map +1 -0
- package/lib/controller/createReset.js +8 -0
- package/lib/controller/createSubscription.d.ts +9 -0
- package/lib/controller/createSubscription.d.ts.map +1 -0
- package/lib/controller/createSubscription.js +30 -0
- package/lib/controller/types.d.ts +6 -0
- package/lib/controller/types.d.ts.map +1 -0
- package/lib/controller/types.js +2 -0
- package/lib/endpoint/index.d.ts +3 -0
- package/lib/endpoint/index.d.ts.map +1 -0
- package/lib/endpoint/index.js +2 -0
- package/lib/endpoint/shapes.d.ts +25 -0
- package/lib/endpoint/shapes.d.ts.map +1 -0
- package/lib/endpoint/shapes.js +2 -0
- package/lib/endpoint/types.d.ts +45 -0
- package/lib/endpoint/types.d.ts.map +1 -0
- package/lib/endpoint/types.js +2 -0
- package/lib/fsa.d.ts +41 -0
- package/lib/fsa.d.ts.map +1 -0
- package/lib/fsa.js +2 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +22 -0
- package/lib/internal.d.ts +4 -0
- package/lib/internal.d.ts.map +1 -0
- package/lib/internal.js +4 -0
- package/lib/legacyActions.d.ts +92 -0
- package/lib/legacyActions.d.ts.map +1 -0
- package/lib/legacyActions.js +2 -0
- package/lib/manager/ConnectionListener.d.ts +8 -0
- package/lib/manager/ConnectionListener.d.ts.map +1 -0
- package/lib/manager/ConnectionListener.js +2 -0
- package/lib/manager/DefaultConnectionListener.d.ts +20 -0
- package/lib/manager/DefaultConnectionListener.d.ts.map +1 -0
- package/lib/manager/DefaultConnectionListener.js +40 -0
- package/lib/manager/DevtoolsManager.d.ts +24 -0
- package/lib/manager/DevtoolsManager.d.ts.map +1 -0
- package/lib/manager/DevtoolsManager.js +74 -0
- package/lib/manager/LogoutManager.d.ts +25 -0
- package/lib/manager/LogoutManager.d.ts.map +1 -0
- package/lib/manager/LogoutManager.js +34 -0
- package/lib/manager/NetworkManager.d.ts +82 -0
- package/lib/manager/NetworkManager.d.ts.map +1 -0
- package/lib/manager/NetworkManager.js +295 -0
- package/lib/manager/PollingSubscription.d.ts +45 -0
- package/lib/manager/PollingSubscription.d.ts.map +1 -0
- package/lib/manager/PollingSubscription.js +159 -0
- package/lib/manager/SubscriptionManager.d.ts +55 -0
- package/lib/manager/SubscriptionManager.d.ts.map +1 -0
- package/lib/manager/SubscriptionManager.js +117 -0
- package/lib/manager/applyManager.d.ts +10 -0
- package/lib/manager/applyManager.d.ts.map +1 -0
- package/lib/manager/applyManager.js +23 -0
- package/lib/manager/devtoolsTypes.d.ts +205 -0
- package/lib/manager/devtoolsTypes.d.ts.map +1 -0
- package/lib/manager/devtoolsTypes.js +2 -0
- package/lib/manager/index.d.ts +8 -0
- package/lib/manager/index.d.ts.map +1 -0
- package/lib/manager/index.js +6 -0
- package/lib/middlewareTypes.d.ts +18 -0
- package/lib/middlewareTypes.d.ts.map +1 -0
- package/lib/middlewareTypes.js +2 -0
- package/lib/newActions.d.ts +85 -0
- package/lib/newActions.d.ts.map +1 -0
- package/lib/newActions.js +2 -0
- package/lib/next/Controller.d.ts +14 -0
- package/lib/next/Controller.d.ts.map +1 -0
- package/lib/next/Controller.js +24 -0
- package/lib/next/index.d.ts +3 -0
- package/lib/next/index.d.ts.map +1 -0
- package/lib/next/index.js +3 -0
- package/lib/previousActions.d.ts +91 -0
- package/lib/previousActions.d.ts.map +1 -0
- package/lib/previousActions.js +2 -0
- package/lib/state/RIC.d.ts +2 -0
- package/lib/state/RIC.js +3 -0
- package/lib/state/applyUpdatersToResults.d.ts +13 -0
- package/lib/state/applyUpdatersToResults.d.ts.map +1 -0
- package/lib/state/applyUpdatersToResults.js +7 -0
- package/lib/state/legacy-actions/createFetch.d.ts +19 -0
- package/lib/state/legacy-actions/createFetch.d.ts.map +1 -0
- package/lib/state/legacy-actions/createFetch.js +62 -0
- package/lib/state/legacy-actions/createReceive.d.ts +14 -0
- package/lib/state/legacy-actions/createReceive.d.ts.map +1 -0
- package/lib/state/legacy-actions/createReceive.js +37 -0
- package/lib/state/legacy-actions/createReceiveError.d.ts +9 -0
- package/lib/state/legacy-actions/createReceiveError.d.ts.map +1 -0
- package/lib/state/legacy-actions/createReceiveError.js +28 -0
- package/lib/state/legacy-actions/index.d.ts +4 -0
- package/lib/state/legacy-actions/index.d.ts.map +1 -0
- package/lib/state/legacy-actions/index.js +4 -0
- package/lib/state/reducer/createReducer.d.ts +7 -0
- package/lib/state/reducer/createReducer.d.ts.map +1 -0
- package/lib/state/reducer/createReducer.js +55 -0
- package/lib/state/reducer/fetchReducer.d.ts +4 -0
- package/lib/state/reducer/fetchReducer.d.ts.map +1 -0
- package/lib/state/reducer/fetchReducer.js +34 -0
- package/lib/state/reducer/invalidateReducer.d.ts +37 -0
- package/lib/state/reducer/invalidateReducer.d.ts.map +1 -0
- package/lib/state/reducer/invalidateReducer.js +34 -0
- package/lib/state/reducer/setReducer.d.ts +40 -0
- package/lib/state/reducer/setReducer.d.ts.map +1 -0
- package/lib/state/reducer/setReducer.js +119 -0
- package/lib/state/reducerInstance.d.ts +7 -0
- package/lib/state/reducerInstance.d.ts.map +1 -0
- package/lib/state/reducerInstance.js +9 -0
- package/lib/state/selectMeta.d.ts +3 -0
- package/lib/state/selectMeta.d.ts.map +1 -0
- package/lib/state/selectMeta.js +4 -0
- package/lib/types.d.ts +71 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +8 -0
- package/node.mjs +1 -0
- package/package.json +127 -0
- package/src/actionTypes.ts +11 -0
- package/src/compatibleActions.ts +96 -0
- package/src/controller/BaseController.ts +508 -0
- package/src/controller/Controller.ts +32 -0
- package/src/controller/__tests__/Controller.ts +29 -0
- package/src/controller/__tests__/__snapshots__/getResponse.ts.snap +35 -0
- package/src/controller/__tests__/getResponse.ts +182 -0
- package/src/controller/createFetch.ts +54 -0
- package/src/controller/createInvalidate.ts +16 -0
- package/src/controller/createInvalidateAll.ts +11 -0
- package/src/controller/createOptimistic.ts +47 -0
- package/src/controller/createReceive.ts +85 -0
- package/src/controller/createReset.ts +9 -0
- package/src/controller/createSubscription.ts +39 -0
- package/src/controller/types.ts +22 -0
- package/src/endpoint/index.ts +14 -0
- package/src/endpoint/shapes.ts +53 -0
- package/src/endpoint/types.ts +72 -0
- package/src/fsa.ts +99 -0
- package/src/index.ts +61 -0
- package/src/internal.ts +3 -0
- package/src/legacyActions.ts +163 -0
- package/src/manager/ConnectionListener.ts +7 -0
- package/src/manager/DefaultConnectionListener.ts +54 -0
- package/src/manager/DevtoolsManager.ts +99 -0
- package/src/manager/LogoutManager.ts +57 -0
- package/src/manager/NetworkManager.ts +346 -0
- package/src/manager/PollingSubscription.ts +190 -0
- package/src/manager/SubscriptionManager.ts +156 -0
- package/src/manager/__tests__/__snapshots__/pollingSubscription-endpoint.ts.snap +49 -0
- package/src/manager/__tests__/__snapshots__/pollingSubscription.ts.snap +43 -0
- package/src/manager/__tests__/logoutManager.ts +112 -0
- package/src/manager/__tests__/manager.ts +44 -0
- package/src/manager/__tests__/networkManager-legacy.ts +394 -0
- package/src/manager/__tests__/networkManager.ts +426 -0
- package/src/manager/__tests__/pollingSubscription-endpoint.ts +423 -0
- package/src/manager/__tests__/pollingSubscription.ts +313 -0
- package/src/manager/__tests__/subscriptionManager.ts +208 -0
- package/src/manager/applyManager.ts +33 -0
- package/src/manager/devtoolsTypes.ts +210 -0
- package/src/manager/index.ts +7 -0
- package/src/middlewareTypes.ts +49 -0
- package/src/newActions.ts +140 -0
- package/src/next/Controller.ts +39 -0
- package/src/next/index.ts +2 -0
- package/src/package.json +1 -0
- package/src/previousActions.ts +159 -0
- package/src/state/RIC.d.ts +2 -0
- package/src/state/RIC.js +5 -0
- package/src/state/__tests__/RIC.web.ts +16 -0
- package/src/state/__tests__/__snapshots__/reducer.ts.snap +56 -0
- package/src/state/__tests__/applyUpdatersToResults.ts +40 -0
- package/src/state/__tests__/reducer.ts +868 -0
- package/src/state/applyUpdatersToResults.ts +29 -0
- package/src/state/legacy-actions/createFetch.ts +95 -0
- package/src/state/legacy-actions/createReceive.ts +68 -0
- package/src/state/legacy-actions/createReceiveError.ts +43 -0
- package/src/state/legacy-actions/index.ts +3 -0
- package/src/state/reducer/createReducer.ts +80 -0
- package/src/state/reducer/fetchReducer.ts +48 -0
- package/src/state/reducer/invalidateReducer.ts +39 -0
- package/src/state/reducer/setReducer.ts +157 -0
- package/src/state/reducerInstance.ts +14 -0
- package/src/state/selectMeta.ts +8 -0
- package/src/types.ts +125 -0
- package/ts3.4/actionTypes.d.ts +11 -0
- package/ts3.4/compatibleActions.d.ts +47 -0
- package/ts3.4/controller/BaseController.d.ts +170 -0
- package/ts3.4/controller/Controller.d.ts +14 -0
- package/ts3.4/controller/createFetch.d.ts +14 -0
- package/ts3.4/controller/createInvalidate.d.ts +8 -0
- package/ts3.4/controller/createInvalidateAll.d.ts +3 -0
- package/ts3.4/controller/createOptimistic.d.ts +12 -0
- package/ts3.4/controller/createReceive.d.ts +24 -0
- package/ts3.4/controller/createReset.d.ts +3 -0
- package/ts3.4/controller/createSubscription.d.ts +13 -0
- package/ts3.4/controller/types.d.ts +6 -0
- package/ts3.4/endpoint/index.d.ts +3 -0
- package/ts3.4/endpoint/shapes.d.ts +25 -0
- package/ts3.4/endpoint/types.d.ts +45 -0
- package/ts3.4/fsa.d.ts +41 -0
- package/ts3.4/index.d.ts +22 -0
- package/ts3.4/internal.d.ts +4 -0
- package/ts3.4/legacyActions.d.ts +95 -0
- package/ts3.4/manager/ConnectionListener.d.ts +8 -0
- package/ts3.4/manager/DefaultConnectionListener.d.ts +20 -0
- package/ts3.4/manager/DevtoolsManager.d.ts +24 -0
- package/ts3.4/manager/LogoutManager.d.ts +25 -0
- package/ts3.4/manager/NetworkManager.d.ts +82 -0
- package/ts3.4/manager/PollingSubscription.d.ts +45 -0
- package/ts3.4/manager/SubscriptionManager.d.ts +55 -0
- package/ts3.4/manager/applyManager.d.ts +10 -0
- package/ts3.4/manager/devtoolsTypes.d.ts +205 -0
- package/ts3.4/manager/index.d.ts +8 -0
- package/ts3.4/middlewareTypes.d.ts +18 -0
- package/ts3.4/newActions.d.ts +88 -0
- package/ts3.4/next/Controller.d.ts +14 -0
- package/ts3.4/next/index.d.ts +3 -0
- package/ts3.4/previousActions.d.ts +94 -0
- package/ts3.4/state/RIC.d.ts +2 -0
- package/ts3.4/state/applyUpdatersToResults.d.ts +13 -0
- package/ts3.4/state/legacy-actions/createFetch.d.ts +19 -0
- package/ts3.4/state/legacy-actions/createReceive.d.ts +14 -0
- package/ts3.4/state/legacy-actions/createReceiveError.d.ts +9 -0
- package/ts3.4/state/legacy-actions/index.d.ts +4 -0
- package/ts3.4/state/reducer/createReducer.d.ts +7 -0
- package/ts3.4/state/reducer/fetchReducer.d.ts +4 -0
- package/ts3.4/state/reducer/invalidateReducer.d.ts +37 -0
- package/ts3.4/state/reducer/setReducer.d.ts +40 -0
- package/ts3.4/state/reducerInstance.d.ts +7 -0
- package/ts3.4/state/selectMeta.d.ts +3 -0
- package/ts3.4/types.d.ts +73 -0
- package/typescript.svg +8 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { SET_TYPE, FETCH_TYPE, RESET_TYPE } from '../actionTypes.js';
|
|
2
|
+
import Controller from '../controller/Controller.js';
|
|
3
|
+
import { initialState } from '../internal.js';
|
|
4
|
+
import {
|
|
5
|
+
createReceive,
|
|
6
|
+
createReceiveError,
|
|
7
|
+
} from '../state/legacy-actions/index.js';
|
|
8
|
+
import RIC from '../state/RIC.js';
|
|
9
|
+
import type {
|
|
10
|
+
FetchAction,
|
|
11
|
+
ReceiveAction,
|
|
12
|
+
Manager,
|
|
13
|
+
ActionTypes,
|
|
14
|
+
MiddlewareAPI,
|
|
15
|
+
Middleware,
|
|
16
|
+
State,
|
|
17
|
+
} from '../types.js';
|
|
18
|
+
|
|
19
|
+
export class ResetError extends Error {
|
|
20
|
+
name = 'ResetError';
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super('Aborted due to RESET');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Handles all async network dispatches
|
|
28
|
+
*
|
|
29
|
+
* Dedupes concurrent requests by keeping track of all fetches in flight
|
|
30
|
+
* and returning existing promises for requests already in flight.
|
|
31
|
+
*
|
|
32
|
+
* Interfaces with store via a redux-compatible middleware.
|
|
33
|
+
*
|
|
34
|
+
* @see https://resthooks.io/docs/api/NetworkManager
|
|
35
|
+
*/
|
|
36
|
+
export default class NetworkManager implements Manager {
|
|
37
|
+
protected fetched: { [k: string]: Promise<any> } = Object.create(null);
|
|
38
|
+
protected resolvers: { [k: string]: (value?: any) => void } = {};
|
|
39
|
+
protected rejectors: { [k: string]: (value?: any) => void } = {};
|
|
40
|
+
protected fetchedAt: { [k: string]: number } = {};
|
|
41
|
+
declare readonly dataExpiryLength: number;
|
|
42
|
+
declare readonly errorExpiryLength: number;
|
|
43
|
+
protected declare middleware: Middleware;
|
|
44
|
+
protected getState: () => State<unknown> = () => initialState;
|
|
45
|
+
protected controller: Controller = new Controller();
|
|
46
|
+
declare cleanupDate?: number;
|
|
47
|
+
|
|
48
|
+
constructor(dataExpiryLength = 60000, errorExpiryLength = 1000) {
|
|
49
|
+
this.dataExpiryLength = dataExpiryLength;
|
|
50
|
+
this.errorExpiryLength = errorExpiryLength;
|
|
51
|
+
|
|
52
|
+
this.middleware = <C extends MiddlewareAPI>({
|
|
53
|
+
dispatch,
|
|
54
|
+
getState,
|
|
55
|
+
controller,
|
|
56
|
+
}: C) => {
|
|
57
|
+
this.getState = getState;
|
|
58
|
+
this.controller = controller;
|
|
59
|
+
return (next: C['dispatch']): C['dispatch'] =>
|
|
60
|
+
(action): Promise<void> => {
|
|
61
|
+
switch (action.type) {
|
|
62
|
+
case FETCH_TYPE:
|
|
63
|
+
this.handleFetch(action, dispatch, controller);
|
|
64
|
+
// This is the only case that causes any state change
|
|
65
|
+
// It's important to intercept other fetches as we don't want to trigger reducers during
|
|
66
|
+
// render - so we need to stop 'readonly' fetches which can be triggered in render
|
|
67
|
+
if (
|
|
68
|
+
action.meta.optimisticResponse !== undefined ||
|
|
69
|
+
(action.endpoint?.getOptimisticResponse !== undefined &&
|
|
70
|
+
action.endpoint.sideEffect)
|
|
71
|
+
) {
|
|
72
|
+
return next(action);
|
|
73
|
+
}
|
|
74
|
+
return Promise.resolve();
|
|
75
|
+
case SET_TYPE:
|
|
76
|
+
// only receive after new state is computed
|
|
77
|
+
return next(action).then(() => {
|
|
78
|
+
if (action.meta.key in this.fetched) {
|
|
79
|
+
// Note: meta *must* be set by reducer so this should be safe
|
|
80
|
+
const error =
|
|
81
|
+
controller.getState().meta[action.meta.key]?.error;
|
|
82
|
+
// processing errors result in state meta having error, so we should reject the promise
|
|
83
|
+
if (error) {
|
|
84
|
+
// TODO: use only new action types
|
|
85
|
+
this.handleReceive(createReceiveError(error, action.meta));
|
|
86
|
+
} else {
|
|
87
|
+
this.handleReceive(action);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
case RESET_TYPE: {
|
|
92
|
+
const rejectors = { ...this.rejectors };
|
|
93
|
+
|
|
94
|
+
this.clearAll();
|
|
95
|
+
return next(action).then(() => {
|
|
96
|
+
// there could be external listeners to the promise
|
|
97
|
+
// this must happen after commit so our own rejector knows not to dispatch an error based on this
|
|
98
|
+
for (const k in rejectors) {
|
|
99
|
+
rejectors[k](new ResetError());
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
default:
|
|
104
|
+
return next(action);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Used by DevtoolsManager to determine whether to log an action */
|
|
111
|
+
skipLogging(action: ActionTypes) {
|
|
112
|
+
/* istanbul ignore next */
|
|
113
|
+
return action.type === FETCH_TYPE && action.meta.key in this.fetched;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** On mount */
|
|
117
|
+
init() {
|
|
118
|
+
delete this.cleanupDate;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Ensures all promises are completed by rejecting remaining. */
|
|
122
|
+
cleanup() {
|
|
123
|
+
// ensure no dispatches after unmount
|
|
124
|
+
// this must be reversible (done in init) so useEffect() remains symmetric
|
|
125
|
+
this.cleanupDate = Date.now();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
allSettled() {
|
|
129
|
+
const fetches = Object.values(this.fetched);
|
|
130
|
+
if (fetches.length) return Promise.allSettled(fetches);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Clear all promise state */
|
|
134
|
+
protected clearAll() {
|
|
135
|
+
for (const k in this.rejectors) {
|
|
136
|
+
this.clear(k);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Clear promise state for a given key */
|
|
141
|
+
protected clear(key: string) {
|
|
142
|
+
this.fetched[key].catch(() => {});
|
|
143
|
+
delete this.resolvers[key];
|
|
144
|
+
delete this.rejectors[key];
|
|
145
|
+
delete this.fetched[key];
|
|
146
|
+
delete this.fetchedAt[key];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
protected getLastReset() {
|
|
150
|
+
if (this.cleanupDate) return this.cleanupDate;
|
|
151
|
+
const lastReset = this.controller.getState().lastReset;
|
|
152
|
+
if (lastReset instanceof Date) return lastReset.valueOf();
|
|
153
|
+
if (typeof lastReset !== 'number') return -Infinity;
|
|
154
|
+
return lastReset;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Called when middleware intercepts 'rest-hooks/fetch' action.
|
|
158
|
+
*
|
|
159
|
+
* Will then start a promise for a key and potentially start the network
|
|
160
|
+
* fetch.
|
|
161
|
+
*
|
|
162
|
+
* Uses throttle only when instructed by action meta. This is valuable
|
|
163
|
+
* for ensures mutation requests always go through.
|
|
164
|
+
*/
|
|
165
|
+
protected handleFetch(
|
|
166
|
+
action: FetchAction,
|
|
167
|
+
dispatch: (action: any) => Promise<void>,
|
|
168
|
+
controller: Controller,
|
|
169
|
+
) {
|
|
170
|
+
const fetch = action.payload;
|
|
171
|
+
const { key, throttle, resolve, reject } = action.meta;
|
|
172
|
+
// TODO(breaking): remove support for Date type in 'Receive' action
|
|
173
|
+
const createdAt =
|
|
174
|
+
typeof action.meta.createdAt !== 'number'
|
|
175
|
+
? action.meta.createdAt.getTime()
|
|
176
|
+
: action.meta.createdAt;
|
|
177
|
+
|
|
178
|
+
const deferedFetch = () => {
|
|
179
|
+
let promise = fetch();
|
|
180
|
+
const resolvePromise = (
|
|
181
|
+
promise: Promise<string | number | object | null>,
|
|
182
|
+
) =>
|
|
183
|
+
promise
|
|
184
|
+
.then(data => {
|
|
185
|
+
resolve(data);
|
|
186
|
+
return data;
|
|
187
|
+
})
|
|
188
|
+
.catch(error => {
|
|
189
|
+
reject(error);
|
|
190
|
+
throw error;
|
|
191
|
+
});
|
|
192
|
+
// schedule non-throttled resolutions in a microtask before receive
|
|
193
|
+
// this enables users awaiting their fetch to trigger any react updates needed to deal
|
|
194
|
+
// with upcoming changes because of the fetch (for instance avoiding suspense if something is deleted)
|
|
195
|
+
if (!throttle && action.endpoint) {
|
|
196
|
+
promise = resolvePromise(promise);
|
|
197
|
+
}
|
|
198
|
+
promise = promise
|
|
199
|
+
.then(data => {
|
|
200
|
+
let lastReset = this.getLastReset();
|
|
201
|
+
|
|
202
|
+
/* istanbul ignore else */
|
|
203
|
+
if (process.env.NODE_ENV !== 'production' && isNaN(lastReset)) {
|
|
204
|
+
console.error(
|
|
205
|
+
'state.lastReset is NaN. Only positive timestamps are valid.',
|
|
206
|
+
);
|
|
207
|
+
lastReset = 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// don't update state with promises started before last clear
|
|
211
|
+
if (createdAt >= lastReset) {
|
|
212
|
+
// we still check for controller in case someone didn't have type protection since this didn't always exist
|
|
213
|
+
if (action.endpoint && this.controller) {
|
|
214
|
+
this.controller.resolve(action.endpoint, {
|
|
215
|
+
args: action.meta.args as any,
|
|
216
|
+
response: data,
|
|
217
|
+
fetchedAt: createdAt,
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
// TODO(breaking): is this branch still possible? remove in next major update
|
|
221
|
+
// does this throw if the reducer fails? - no because reducer is wrapped in try/catch
|
|
222
|
+
this.controller.dispatch(
|
|
223
|
+
createReceive(data, {
|
|
224
|
+
...action.meta,
|
|
225
|
+
fetchedAt: createdAt,
|
|
226
|
+
dataExpiryLength:
|
|
227
|
+
action.meta.options?.dataExpiryLength ??
|
|
228
|
+
this.dataExpiryLength,
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return data;
|
|
234
|
+
})
|
|
235
|
+
.catch(error => {
|
|
236
|
+
const lastReset = this.getLastReset();
|
|
237
|
+
// don't update state with promises started before last clear
|
|
238
|
+
if (createdAt >= lastReset) {
|
|
239
|
+
if (action.endpoint && this.controller) {
|
|
240
|
+
this.controller.resolve(action.endpoint, {
|
|
241
|
+
args: action.meta.args as any,
|
|
242
|
+
response: error,
|
|
243
|
+
fetchedAt: createdAt,
|
|
244
|
+
error: true,
|
|
245
|
+
});
|
|
246
|
+
} else {
|
|
247
|
+
this.controller.dispatch(
|
|
248
|
+
createReceiveError(error, {
|
|
249
|
+
...action.meta,
|
|
250
|
+
errorExpiryLength:
|
|
251
|
+
action.meta.options?.errorExpiryLength ??
|
|
252
|
+
this.errorExpiryLength,
|
|
253
|
+
fetchedAt: createdAt,
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
throw error;
|
|
259
|
+
});
|
|
260
|
+
// legacy behavior schedules resolution after dispatch
|
|
261
|
+
if (!throttle && !action.endpoint) {
|
|
262
|
+
promise = resolvePromise(promise);
|
|
263
|
+
}
|
|
264
|
+
return promise;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
if (throttle) {
|
|
268
|
+
return this.throttle(key, deferedFetch, createdAt)
|
|
269
|
+
.then(data => resolve(data))
|
|
270
|
+
.catch(error => reject(error));
|
|
271
|
+
} else {
|
|
272
|
+
return deferedFetch().catch(() => {});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Called when middleware intercepts a receive action.
|
|
277
|
+
*
|
|
278
|
+
* Will resolve the promise associated with receive key.
|
|
279
|
+
*/
|
|
280
|
+
protected handleReceive(action: ReceiveAction) {
|
|
281
|
+
// this can still turn out to be untrue since this is async
|
|
282
|
+
if (action.meta.key in this.fetched) {
|
|
283
|
+
let promiseHandler: (value?: any) => void;
|
|
284
|
+
if (action.error) {
|
|
285
|
+
promiseHandler = this.rejectors[action.meta.key];
|
|
286
|
+
} else {
|
|
287
|
+
promiseHandler = this.resolvers[action.meta.key];
|
|
288
|
+
}
|
|
289
|
+
promiseHandler(action.payload);
|
|
290
|
+
// since we're resolved we no longer need to keep track of this promise
|
|
291
|
+
this.clear(action.meta.key);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Attaches NetworkManager to store
|
|
296
|
+
*
|
|
297
|
+
* Intercepts 'rest-hooks/fetch' actions to start requests.
|
|
298
|
+
*
|
|
299
|
+
* Resolve/rejects a request when matching 'rest-hooks/receive' event
|
|
300
|
+
* is seen.
|
|
301
|
+
*/
|
|
302
|
+
getMiddleware() {
|
|
303
|
+
return this.middleware;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Ensures only one request for a given key is in flight at any time
|
|
307
|
+
*
|
|
308
|
+
* Uses key to either retrieve in-flight promise, or if not
|
|
309
|
+
* create a new promise and call fetch.
|
|
310
|
+
*
|
|
311
|
+
* Note: The new promise is not actually tied to fetch at all,
|
|
312
|
+
* but is resolved when the expected 'recieve' action is processed.
|
|
313
|
+
* This ensures promises are resolved only once their data is processed
|
|
314
|
+
* by the reducer.
|
|
315
|
+
*/
|
|
316
|
+
protected throttle(
|
|
317
|
+
key: string,
|
|
318
|
+
fetch: () => Promise<any>,
|
|
319
|
+
createdAt: number,
|
|
320
|
+
) {
|
|
321
|
+
const lastReset = this.getLastReset();
|
|
322
|
+
// we're already fetching so reuse the promise
|
|
323
|
+
// fetches after reset do not count
|
|
324
|
+
if (key in this.fetched && this.fetchedAt[key] > lastReset) {
|
|
325
|
+
return this.fetched[key];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
this.fetched[key] = new Promise((resolve, reject) => {
|
|
329
|
+
this.resolvers[key] = resolve;
|
|
330
|
+
this.rejectors[key] = reject;
|
|
331
|
+
});
|
|
332
|
+
this.fetchedAt[key] = createdAt;
|
|
333
|
+
|
|
334
|
+
// since our real promise is resolved via the wrapReducer(),
|
|
335
|
+
// we should just stop all errors here.
|
|
336
|
+
// TODO: decouple this from useFetcher() (that's what's dispatching the error the resolves in here)
|
|
337
|
+
RIC(
|
|
338
|
+
() => {
|
|
339
|
+
fetch().catch(() => null);
|
|
340
|
+
},
|
|
341
|
+
{ timeout: 500 },
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
return this.fetched[key];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { EndpointInterface, Schema } from '@data-client/normalizr';
|
|
2
|
+
|
|
3
|
+
import ConnectionListener from './ConnectionListener.js';
|
|
4
|
+
import DefaultConnectionListener from './DefaultConnectionListener.js';
|
|
5
|
+
import { Subscription, SubscriptionInit } from './SubscriptionManager.js';
|
|
6
|
+
import createFetch from '../controller/createFetch.js';
|
|
7
|
+
import type { State, Dispatch } from '../types.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PollingSubscription keeps a given resource updated by
|
|
11
|
+
* dispatching a fetch at a rate equal to the minimum update
|
|
12
|
+
* interval requested.
|
|
13
|
+
*
|
|
14
|
+
* @see https://resthooks.io/docs/api/PollingSubscription
|
|
15
|
+
*/
|
|
16
|
+
export default class PollingSubscription implements Subscription {
|
|
17
|
+
protected declare readonly schema: Schema | undefined;
|
|
18
|
+
protected declare readonly fetch: () => Promise<any>;
|
|
19
|
+
protected declare readonly key: string;
|
|
20
|
+
protected declare frequency: number;
|
|
21
|
+
protected frequencyHistogram: Map<number, number> = new Map();
|
|
22
|
+
protected declare dispatch: Dispatch<any>;
|
|
23
|
+
protected declare getState: () => State<unknown>;
|
|
24
|
+
protected declare intervalId?: ReturnType<typeof setInterval>;
|
|
25
|
+
protected declare lastIntervalId?: ReturnType<typeof setInterval>;
|
|
26
|
+
protected declare startId?: ReturnType<typeof setTimeout>;
|
|
27
|
+
private declare connectionListener: ConnectionListener;
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
{ key, schema, fetch, frequency, getState }: SubscriptionInit,
|
|
31
|
+
dispatch: Dispatch<any>,
|
|
32
|
+
connectionListener?: ConnectionListener,
|
|
33
|
+
) {
|
|
34
|
+
if (frequency === undefined)
|
|
35
|
+
throw new Error('frequency needed for polling subscription');
|
|
36
|
+
this.schema = schema;
|
|
37
|
+
this.fetch = fetch;
|
|
38
|
+
this.frequency = frequency;
|
|
39
|
+
this.key = key;
|
|
40
|
+
this.frequencyHistogram.set(this.frequency, 1);
|
|
41
|
+
this.dispatch = dispatch;
|
|
42
|
+
this.getState = getState;
|
|
43
|
+
this.connectionListener =
|
|
44
|
+
connectionListener || new DefaultConnectionListener();
|
|
45
|
+
|
|
46
|
+
// Kickstart running since this is initialized after the online notif is sent
|
|
47
|
+
if (this.connectionListener.isOnline()) {
|
|
48
|
+
this.onlineListener();
|
|
49
|
+
} else {
|
|
50
|
+
this.offlineListener();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Subscribe to a frequency */
|
|
55
|
+
add(frequency?: number) {
|
|
56
|
+
if (frequency === undefined) return;
|
|
57
|
+
if (this.frequencyHistogram.has(frequency)) {
|
|
58
|
+
this.frequencyHistogram.set(
|
|
59
|
+
frequency,
|
|
60
|
+
(this.frequencyHistogram.get(frequency) as number) + 1,
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
this.frequencyHistogram.set(frequency, 1);
|
|
64
|
+
|
|
65
|
+
// new min so restart service
|
|
66
|
+
if (frequency < this.frequency) {
|
|
67
|
+
this.frequency = frequency;
|
|
68
|
+
this.run();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Unsubscribe from a frequency */
|
|
74
|
+
remove(frequency?: number) {
|
|
75
|
+
if (frequency === undefined) return false;
|
|
76
|
+
if (this.frequencyHistogram.has(frequency)) {
|
|
77
|
+
this.frequencyHistogram.set(
|
|
78
|
+
frequency,
|
|
79
|
+
(this.frequencyHistogram.get(frequency) as number) - 1,
|
|
80
|
+
);
|
|
81
|
+
if ((this.frequencyHistogram.get(frequency) as number) < 1) {
|
|
82
|
+
this.frequencyHistogram.delete(frequency);
|
|
83
|
+
|
|
84
|
+
// nothing subscribed to this anymore...it is invalid
|
|
85
|
+
if (this.frequencyHistogram.size === 0) {
|
|
86
|
+
this.cleanup();
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// this was the min, so find the next size
|
|
91
|
+
if (frequency <= this.frequency) {
|
|
92
|
+
this.frequency = Math.min(...this.frequencyHistogram.keys());
|
|
93
|
+
this.run();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} /* istanbul ignore next */ else if (
|
|
97
|
+
process.env.NODE_ENV !== 'production'
|
|
98
|
+
) {
|
|
99
|
+
console.error(
|
|
100
|
+
`Mismatched remove: ${frequency} is not subscribed for ${this.key}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Cleanup means clearing out background interval. */
|
|
107
|
+
cleanup() {
|
|
108
|
+
if (this.intervalId) {
|
|
109
|
+
clearInterval(this.intervalId);
|
|
110
|
+
delete this.intervalId;
|
|
111
|
+
}
|
|
112
|
+
if (this.lastIntervalId) {
|
|
113
|
+
clearInterval(this.lastIntervalId);
|
|
114
|
+
delete this.lastIntervalId;
|
|
115
|
+
}
|
|
116
|
+
if (this.startId) {
|
|
117
|
+
clearTimeout(this.startId);
|
|
118
|
+
delete this.startId;
|
|
119
|
+
}
|
|
120
|
+
this.connectionListener.removeOnlineListener(this.onlineListener);
|
|
121
|
+
this.connectionListener.removeOfflineListener(this.offlineListener);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Trigger request for latest resource */
|
|
125
|
+
protected update() {
|
|
126
|
+
const endpoint: EndpointInterface = () => this.fetch();
|
|
127
|
+
(endpoint as any).schema = this.schema;
|
|
128
|
+
endpoint.key = () => this.key;
|
|
129
|
+
(endpoint as any).dataExpiryLength = this.frequency / 2;
|
|
130
|
+
(endpoint as any).errorExpiryLength = this.frequency / 10;
|
|
131
|
+
endpoint.errorPolicy = () => 'soft' as const;
|
|
132
|
+
const action = createFetch(endpoint, { args: [] });
|
|
133
|
+
// stop any errors here from bubbling
|
|
134
|
+
(action.meta.promise as Promise<any>).catch(e => null);
|
|
135
|
+
this.dispatch(action);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** What happens when browser goes offline */
|
|
139
|
+
protected offlineListener = () => {
|
|
140
|
+
// this clears existing listeners, so no need to clear offline listener
|
|
141
|
+
this.cleanup();
|
|
142
|
+
this.connectionListener.addOnlineListener(this.onlineListener);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/** What happens when browser comes online */
|
|
146
|
+
protected onlineListener = () => {
|
|
147
|
+
this.connectionListener.removeOnlineListener(this.onlineListener);
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
this.startId = setTimeout(() => {
|
|
150
|
+
if (this.startId) {
|
|
151
|
+
delete this.startId;
|
|
152
|
+
this.update();
|
|
153
|
+
this.run();
|
|
154
|
+
} else if (process.env.NODE_ENV !== 'production') {
|
|
155
|
+
console.warn(
|
|
156
|
+
`Poll setTimeout for ${this.key} still running, but timeoutId deleted`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}, Math.max(0, this.lastFetchTime() - now + this.frequency));
|
|
160
|
+
this.connectionListener.addOfflineListener(this.offlineListener);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** Run polling process with current frequency
|
|
164
|
+
*
|
|
165
|
+
* Will clean up old poll interval on next run
|
|
166
|
+
*/
|
|
167
|
+
protected run() {
|
|
168
|
+
if (this.startId) return;
|
|
169
|
+
if (this.intervalId) this.lastIntervalId = this.intervalId;
|
|
170
|
+
this.intervalId = setInterval(() => {
|
|
171
|
+
// since we don't know how long into the last poll it was before resetting
|
|
172
|
+
// we wait til the next fetch to clear old intervals
|
|
173
|
+
if (this.lastIntervalId) {
|
|
174
|
+
clearInterval(this.lastIntervalId);
|
|
175
|
+
delete this.lastIntervalId;
|
|
176
|
+
}
|
|
177
|
+
if (this.intervalId) this.update();
|
|
178
|
+
else if (process.env.NODE_ENV !== 'production') {
|
|
179
|
+
console.warn(
|
|
180
|
+
`Poll intervalId for ${this.key} still running, but intervalId deleted`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}, this.frequency);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Last fetch time */
|
|
187
|
+
protected lastFetchTime() {
|
|
188
|
+
return this.getState().meta[this.key]?.date ?? 0;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { Schema } from '@data-client/normalizr';
|
|
2
|
+
|
|
3
|
+
import { SUBSCRIBE_TYPE, UNSUBSCRIBE_TYPE } from '../actionTypes.js';
|
|
4
|
+
import type {
|
|
5
|
+
Manager,
|
|
6
|
+
State,
|
|
7
|
+
MiddlewareAPI,
|
|
8
|
+
Middleware,
|
|
9
|
+
Dispatch,
|
|
10
|
+
UnsubscribeAction,
|
|
11
|
+
SubscribeAction,
|
|
12
|
+
} from '../types.js';
|
|
13
|
+
|
|
14
|
+
type Actions = UnsubscribeAction | SubscribeAction;
|
|
15
|
+
|
|
16
|
+
/** Properties sent to Subscription constructor */
|
|
17
|
+
export interface SubscriptionInit {
|
|
18
|
+
schema?: Schema | undefined;
|
|
19
|
+
fetch: () => Promise<any>;
|
|
20
|
+
key: string;
|
|
21
|
+
getState: () => State<unknown>;
|
|
22
|
+
frequency?: number | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Interface handling a single resource subscription */
|
|
26
|
+
export interface Subscription {
|
|
27
|
+
add(frequency?: number): void;
|
|
28
|
+
remove(frequency?: number): boolean;
|
|
29
|
+
cleanup(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The static class that constructs Subscription */
|
|
33
|
+
export interface SubscriptionConstructable {
|
|
34
|
+
new (init: SubscriptionInit, dispatch: Dispatch<any>): Subscription;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Handles subscription actions -> fetch or receive actions
|
|
38
|
+
*
|
|
39
|
+
* Constructor takes a SubscriptionConstructable class to control how
|
|
40
|
+
* subscriptions are handled. (e.g., polling, websockets)
|
|
41
|
+
*
|
|
42
|
+
* @see https://resthooks.io/docs/api/SubscriptionManager
|
|
43
|
+
*/
|
|
44
|
+
export default class SubscriptionManager<S extends SubscriptionConstructable>
|
|
45
|
+
implements Manager
|
|
46
|
+
{
|
|
47
|
+
protected subscriptions: {
|
|
48
|
+
[key: string]: InstanceType<S>;
|
|
49
|
+
} = {};
|
|
50
|
+
|
|
51
|
+
protected declare readonly Subscription: S;
|
|
52
|
+
protected declare middleware: Middleware;
|
|
53
|
+
|
|
54
|
+
constructor(Subscription: S) {
|
|
55
|
+
this.Subscription = Subscription;
|
|
56
|
+
|
|
57
|
+
this.middleware = <C extends MiddlewareAPI>({ dispatch, getState }: C) => {
|
|
58
|
+
return (next: C['dispatch']): C['dispatch'] =>
|
|
59
|
+
action => {
|
|
60
|
+
switch (action.type) {
|
|
61
|
+
case SUBSCRIBE_TYPE:
|
|
62
|
+
try {
|
|
63
|
+
this.handleSubscribe(action, dispatch, getState);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error(e);
|
|
66
|
+
}
|
|
67
|
+
return Promise.resolve();
|
|
68
|
+
case UNSUBSCRIBE_TYPE:
|
|
69
|
+
this.handleUnsubscribe(action, dispatch);
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
default:
|
|
72
|
+
return next(action);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Ensures all subscriptions are cleaned up. */
|
|
79
|
+
cleanup() {
|
|
80
|
+
for (const key in this.subscriptions) {
|
|
81
|
+
this.subscriptions[key].cleanup();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Called when middleware intercepts 'rest-hooks/subscribe' action.
|
|
86
|
+
*
|
|
87
|
+
*/
|
|
88
|
+
protected handleSubscribe(
|
|
89
|
+
action: SubscribeAction,
|
|
90
|
+
dispatch: (action: any) => Promise<void>,
|
|
91
|
+
getState: () => State<unknown>,
|
|
92
|
+
) {
|
|
93
|
+
let options: SubscriptionInit;
|
|
94
|
+
if (action.endpoint) {
|
|
95
|
+
const { endpoint } = action;
|
|
96
|
+
const { args } = action.meta;
|
|
97
|
+
options = {
|
|
98
|
+
schema: endpoint.schema,
|
|
99
|
+
fetch: () => endpoint(...args),
|
|
100
|
+
frequency: endpoint.pollFrequency,
|
|
101
|
+
key: endpoint.key(...args),
|
|
102
|
+
getState,
|
|
103
|
+
};
|
|
104
|
+
} else {
|
|
105
|
+
options = {
|
|
106
|
+
key: action.meta.key,
|
|
107
|
+
frequency: action.meta.options?.pollFrequency,
|
|
108
|
+
schema: action.meta.schema,
|
|
109
|
+
fetch: action.meta.fetch,
|
|
110
|
+
getState,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (options.key in this.subscriptions) {
|
|
115
|
+
this.subscriptions[options.key].add(options.frequency);
|
|
116
|
+
} else {
|
|
117
|
+
this.subscriptions[options.key] = new this.Subscription(
|
|
118
|
+
options,
|
|
119
|
+
dispatch,
|
|
120
|
+
) as InstanceType<S>;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Called when middleware intercepts 'rest-hooks/unsubscribe' action.
|
|
125
|
+
*
|
|
126
|
+
*/
|
|
127
|
+
protected handleUnsubscribe(
|
|
128
|
+
action: UnsubscribeAction,
|
|
129
|
+
dispatch: (action: any) => Promise<void>,
|
|
130
|
+
) {
|
|
131
|
+
const key = action.meta.key;
|
|
132
|
+
const frequency = action.meta.options?.pollFrequency;
|
|
133
|
+
|
|
134
|
+
/* istanbul ignore else */
|
|
135
|
+
if (key in this.subscriptions) {
|
|
136
|
+
const empty = this.subscriptions[key].remove(frequency);
|
|
137
|
+
if (empty) {
|
|
138
|
+
delete this.subscriptions[key];
|
|
139
|
+
}
|
|
140
|
+
} else if (process.env.NODE_ENV !== 'production') {
|
|
141
|
+
console.error(`Mismatched unsubscribe: ${key} is not subscribed`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Attaches Manager to store
|
|
146
|
+
*
|
|
147
|
+
* Intercepts 'rest-hooks/subscribe'/'rest-hooks/unsubscribe' to register resources that
|
|
148
|
+
* need to be kept up to date.
|
|
149
|
+
*
|
|
150
|
+
* Will possibly dispatch 'rest-hooks/fetch' or 'rest-hooks/receive' to keep resources fresh
|
|
151
|
+
*
|
|
152
|
+
*/
|
|
153
|
+
getMiddleware() {
|
|
154
|
+
return this.middleware;
|
|
155
|
+
}
|
|
156
|
+
}
|