@data-client/core 0.14.2 → 0.14.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/dist/index.js +73 -101
- package/dist/index.umd.min.js +1 -1
- package/legacy/index.js +1 -1
- package/legacy/manager/DevtoolsManager.js +50 -52
- package/legacy/manager/LogoutManager.js +3 -8
- package/legacy/manager/NetworkManager.js +13 -22
- package/legacy/manager/SubscriptionManager.js +2 -15
- package/legacy/manager/applyManager.js +5 -3
- package/legacy/middlewareTypes.js +1 -1
- package/legacy/state/reducer/createReducer.js +2 -2
- package/legacy/state/reducer/setReducer.js +3 -3
- package/legacy/state/reducer/setResponseReducer.js +3 -3
- package/legacy/types.js +13 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/manager/DevtoolsManager.d.ts +4 -7
- package/lib/manager/DevtoolsManager.d.ts.map +1 -1
- package/lib/manager/DevtoolsManager.js +50 -52
- package/lib/manager/LogoutManager.d.ts +5 -8
- package/lib/manager/LogoutManager.d.ts.map +1 -1
- package/lib/manager/LogoutManager.js +3 -8
- package/lib/manager/NetworkManager.d.ts +7 -12
- package/lib/manager/NetworkManager.d.ts.map +1 -1
- package/lib/manager/NetworkManager.js +13 -22
- package/lib/manager/SubscriptionManager.d.ts +2 -11
- package/lib/manager/SubscriptionManager.d.ts.map +1 -1
- package/lib/manager/SubscriptionManager.js +2 -15
- package/lib/manager/applyManager.d.ts +8 -5
- package/lib/manager/applyManager.d.ts.map +1 -1
- package/lib/manager/applyManager.js +5 -3
- package/lib/middlewareTypes.d.ts +6 -10
- package/lib/middlewareTypes.d.ts.map +1 -1
- package/lib/middlewareTypes.js +1 -1
- package/lib/state/reducer/createReducer.js +2 -2
- package/lib/state/reducer/expireReducer.d.ts +1 -1
- package/lib/state/reducer/setReducer.d.ts +4 -4
- package/lib/state/reducer/setReducer.js +3 -3
- package/lib/state/reducer/setResponseReducer.d.ts +4 -4
- package/lib/state/reducer/setResponseReducer.js +3 -3
- package/lib/types.d.ts +17 -2
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +13 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/manager/DevtoolsManager.ts +37 -40
- package/src/manager/LogoutManager.ts +15 -27
- package/src/manager/NetworkManager.ts +58 -71
- package/src/manager/SubscriptionManager.ts +21 -34
- package/src/manager/__tests__/applyManager.ts +61 -1
- package/src/manager/__tests__/logoutManager.ts +8 -10
- package/src/manager/__tests__/manager.ts +2 -3
- package/src/manager/__tests__/networkManager.ts +16 -19
- package/src/manager/__tests__/subscriptionManager.ts +14 -16
- package/src/manager/applyManager.ts +20 -8
- package/src/middlewareTypes.ts +7 -17
- package/src/state/reducer/createReducer.ts +1 -1
- package/src/state/reducer/setReducer.ts +2 -2
- package/src/state/reducer/setResponseReducer.ts +2 -2
- package/src/types.ts +17 -2
- package/ts3.4/index.d.ts +1 -1
- package/ts3.4/manager/DevtoolsManager.d.ts +3 -6
- package/ts3.4/manager/LogoutManager.d.ts +4 -7
- package/ts3.4/manager/NetworkManager.d.ts +7 -12
- package/ts3.4/manager/SubscriptionManager.d.ts +2 -11
- package/ts3.4/manager/applyManager.d.ts +8 -5
- package/ts3.4/middlewareTypes.d.ts +5 -9
- package/ts3.4/state/reducer/expireReducer.d.ts +1 -1
- package/ts3.4/state/reducer/setReducer.d.ts +4 -4
- package/ts3.4/state/reducer/setResponseReducer.d.ts +4 -4
- package/ts3.4/types.d.ts +17 -2
|
@@ -34,76 +34,68 @@ export default class NetworkManager implements Manager {
|
|
|
34
34
|
protected fetchedAt: { [k: string]: number } = {};
|
|
35
35
|
declare readonly dataExpiryLength: number;
|
|
36
36
|
declare readonly errorExpiryLength: number;
|
|
37
|
-
protected declare middleware: Middleware;
|
|
38
37
|
protected controller: Controller = new Controller();
|
|
39
38
|
declare cleanupDate?: number;
|
|
40
39
|
|
|
41
|
-
constructor(dataExpiryLength = 60000, errorExpiryLength = 1000) {
|
|
40
|
+
constructor({ dataExpiryLength = 60000, errorExpiryLength = 1000 } = {}) {
|
|
42
41
|
this.dataExpiryLength = dataExpiryLength;
|
|
43
42
|
this.errorExpiryLength = errorExpiryLength;
|
|
43
|
+
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
middleware: Middleware = controller => {
|
|
46
|
+
this.controller = controller;
|
|
47
|
+
return next => action => {
|
|
48
|
+
switch (action.type) {
|
|
49
|
+
case FETCH_TYPE:
|
|
50
|
+
this.handleFetch(action);
|
|
51
|
+
// This is the only case that causes any state change
|
|
52
|
+
// It's important to intercept other fetches as we don't want to trigger reducers during
|
|
53
|
+
// render - so we need to stop 'readonly' fetches which can be triggered in render
|
|
54
|
+
if (
|
|
55
|
+
action.endpoint.getOptimisticResponse !== undefined &&
|
|
56
|
+
action.endpoint.sideEffect
|
|
57
|
+
) {
|
|
58
|
+
return next(action);
|
|
59
|
+
}
|
|
60
|
+
return Promise.resolve();
|
|
61
|
+
case SET_RESPONSE_TYPE:
|
|
62
|
+
// only set after new state is computed
|
|
63
|
+
return next(action).then(() => {
|
|
64
|
+
if (action.key in this.fetched) {
|
|
65
|
+
// Note: meta *must* be set by reducer so this should be safe
|
|
66
|
+
const error = controller.getState().meta[action.key]?.error;
|
|
67
|
+
// processing errors result in state meta having error, so we should reject the promise
|
|
68
|
+
if (error) {
|
|
69
|
+
this.handleSet(
|
|
70
|
+
createSetResponse(action.endpoint, {
|
|
71
|
+
args: action.args,
|
|
72
|
+
response: error,
|
|
73
|
+
fetchedAt: action.meta.fetchedAt,
|
|
74
|
+
error: true,
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
this.handleSet(action);
|
|
60
79
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (action.key in this.fetched) {
|
|
66
|
-
// Note: meta *must* be set by reducer so this should be safe
|
|
67
|
-
const error = controller.getState().meta[action.key]?.error;
|
|
68
|
-
// processing errors result in state meta having error, so we should reject the promise
|
|
69
|
-
if (error) {
|
|
70
|
-
this.handleSet(
|
|
71
|
-
createSetResponse(action.endpoint, {
|
|
72
|
-
args: action.args,
|
|
73
|
-
response: error,
|
|
74
|
-
fetchedAt: action.meta.fetchedAt,
|
|
75
|
-
error: true,
|
|
76
|
-
}),
|
|
77
|
-
);
|
|
78
|
-
} else {
|
|
79
|
-
this.handleSet(action);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
case RESET_TYPE: {
|
|
84
|
-
const rejectors = { ...this.rejectors };
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
case RESET_TYPE: {
|
|
83
|
+
const rejectors = { ...this.rejectors };
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
});
|
|
85
|
+
this.clearAll();
|
|
86
|
+
return next(action).then(() => {
|
|
87
|
+
// there could be external listeners to the promise
|
|
88
|
+
// this must happen after commit so our own rejector knows not to dispatch an error based on this
|
|
89
|
+
for (const k in rejectors) {
|
|
90
|
+
rejectors[k](new ResetError());
|
|
94
91
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
default:
|
|
95
|
+
return next(action);
|
|
96
|
+
}
|
|
99
97
|
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Used by DevtoolsManager to determine whether to log an action */
|
|
103
|
-
skipLogging(action: ActionTypes) {
|
|
104
|
-
/* istanbul ignore next */
|
|
105
|
-
return action.type === FETCH_TYPE && action.key in this.fetched;
|
|
106
|
-
}
|
|
98
|
+
};
|
|
107
99
|
|
|
108
100
|
/** On mount */
|
|
109
101
|
init() {
|
|
@@ -117,6 +109,12 @@ export default class NetworkManager implements Manager {
|
|
|
117
109
|
this.cleanupDate = Date.now();
|
|
118
110
|
}
|
|
119
111
|
|
|
112
|
+
/** Used by DevtoolsManager to determine whether to log an action */
|
|
113
|
+
skipLogging(action: ActionTypes) {
|
|
114
|
+
/* istanbul ignore next */
|
|
115
|
+
return action.type === FETCH_TYPE && action.key in this.fetched;
|
|
116
|
+
}
|
|
117
|
+
|
|
120
118
|
allSettled() {
|
|
121
119
|
const fetches = Object.values(this.fetched);
|
|
122
120
|
if (fetches.length) return Promise.allSettled(fetches);
|
|
@@ -241,17 +239,6 @@ export default class NetworkManager implements Manager {
|
|
|
241
239
|
}
|
|
242
240
|
}
|
|
243
241
|
|
|
244
|
-
/** Attaches NetworkManager to store
|
|
245
|
-
*
|
|
246
|
-
* Intercepts 'rdc/fetch' actions to start requests.
|
|
247
|
-
*
|
|
248
|
-
* Resolve/rejects a request when matching 'rdc/set' event
|
|
249
|
-
* is seen.
|
|
250
|
-
*/
|
|
251
|
-
getMiddleware() {
|
|
252
|
-
return this.middleware;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
242
|
/** Ensures only one request for a given key is in flight at any time
|
|
256
243
|
*
|
|
257
244
|
* Uses key to either retrieve in-flight promise, or if not
|
|
@@ -32,41 +32,40 @@ export interface SubscriptionConstructable {
|
|
|
32
32
|
*
|
|
33
33
|
* @see https://dataclient.io/docs/api/SubscriptionManager
|
|
34
34
|
*/
|
|
35
|
-
export default class SubscriptionManager<
|
|
36
|
-
|
|
35
|
+
export default class SubscriptionManager<
|
|
36
|
+
S extends SubscriptionConstructable = SubscriptionConstructable,
|
|
37
|
+
> implements Manager<Actions>
|
|
37
38
|
{
|
|
38
39
|
protected subscriptions: {
|
|
39
40
|
[key: string]: InstanceType<S>;
|
|
40
41
|
} = {};
|
|
41
42
|
|
|
42
43
|
protected declare readonly Subscription: S;
|
|
43
|
-
protected declare middleware: Middleware;
|
|
44
44
|
protected controller: Controller = new Controller();
|
|
45
45
|
|
|
46
46
|
constructor(Subscription: S) {
|
|
47
47
|
this.Subscription = Subscription;
|
|
48
|
+
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
console.error(e);
|
|
59
|
-
}
|
|
60
|
-
return Promise.resolve();
|
|
61
|
-
case UNSUBSCRIBE_TYPE:
|
|
62
|
-
this.handleUnsubscribe(action);
|
|
63
|
-
return Promise.resolve();
|
|
64
|
-
default:
|
|
65
|
-
return next(action);
|
|
50
|
+
middleware: Middleware = controller => {
|
|
51
|
+
this.controller = controller;
|
|
52
|
+
return next => action => {
|
|
53
|
+
switch (action.type) {
|
|
54
|
+
case SUBSCRIBE_TYPE:
|
|
55
|
+
try {
|
|
56
|
+
this.handleSubscribe(action);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error(e);
|
|
66
59
|
}
|
|
67
|
-
|
|
60
|
+
return Promise.resolve();
|
|
61
|
+
case UNSUBSCRIBE_TYPE:
|
|
62
|
+
this.handleUnsubscribe(action);
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
default:
|
|
65
|
+
return next(action);
|
|
66
|
+
}
|
|
68
67
|
};
|
|
69
|
-
}
|
|
68
|
+
};
|
|
70
69
|
|
|
71
70
|
/** Ensures all subscriptions are cleaned up. */
|
|
72
71
|
cleanup() {
|
|
@@ -109,16 +108,4 @@ export default class SubscriptionManager<S extends SubscriptionConstructable>
|
|
|
109
108
|
console.error(`Mismatched unsubscribe: ${key} is not subscribed`);
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
|
-
|
|
113
|
-
/** Attaches Manager to store
|
|
114
|
-
*
|
|
115
|
-
* Intercepts 'rdc/subscribe'/'rest-hordc/ribe' to register resources that
|
|
116
|
-
* need to be kept up to date.
|
|
117
|
-
*
|
|
118
|
-
* Will possibly dispatch 'rdc/fetch' or 'rest-hordc/' to keep resources fresh
|
|
119
|
-
*
|
|
120
|
-
*/
|
|
121
|
-
getMiddleware() {
|
|
122
|
-
return this.middleware;
|
|
123
|
-
}
|
|
124
111
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { Article } from '__tests__/new';
|
|
2
|
+
|
|
3
|
+
import { createSet } from '../../controller/actions';
|
|
1
4
|
import Controller from '../../controller/Controller';
|
|
2
|
-
import
|
|
5
|
+
import { Dispatch, Middleware } from '../../middlewareTypes';
|
|
6
|
+
import { Manager } from '../../types';
|
|
7
|
+
import applyManager, {
|
|
8
|
+
ReduxMiddleware,
|
|
9
|
+
ReduxMiddlewareAPI,
|
|
10
|
+
} from '../applyManager';
|
|
3
11
|
import NetworkManager from '../NetworkManager';
|
|
4
12
|
|
|
5
13
|
function onError(e: any) {
|
|
@@ -36,3 +44,55 @@ it('applyManagers should not console.warn() when NetworkManager is provided', ()
|
|
|
36
44
|
warnspy.mockRestore();
|
|
37
45
|
}
|
|
38
46
|
});
|
|
47
|
+
it('applyManagers should handle legacy Manager.getMiddleware()', () => {
|
|
48
|
+
let initCount = 0;
|
|
49
|
+
let actionCount = 0;
|
|
50
|
+
class MyManager implements Manager {
|
|
51
|
+
getMiddleware = (): Middleware => ctrl => {
|
|
52
|
+
initCount++;
|
|
53
|
+
return next => action => {
|
|
54
|
+
actionCount++;
|
|
55
|
+
return next(action);
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
cleanup() {}
|
|
60
|
+
}
|
|
61
|
+
const middlewares = applyManager(
|
|
62
|
+
[new MyManager(), new NetworkManager()],
|
|
63
|
+
new Controller(),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const rootDispatch = jest.fn((action: any) => {
|
|
67
|
+
return Promise.resolve();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const dispatch = middlewareDispatch(middlewares, rootDispatch);
|
|
71
|
+
|
|
72
|
+
expect(initCount).toBe(1);
|
|
73
|
+
expect(actionCount).toBe(0);
|
|
74
|
+
expect(rootDispatch.mock.calls.length).toBe(0);
|
|
75
|
+
dispatch(
|
|
76
|
+
createSet(Article, { args: [{ id: 1 }], value: { id: 1, title: 'hi' } }),
|
|
77
|
+
);
|
|
78
|
+
expect(initCount).toBe(1);
|
|
79
|
+
expect(actionCount).toBe(1);
|
|
80
|
+
expect(rootDispatch.mock.calls.length).toBe(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function middlewareDispatch(
|
|
84
|
+
middlewares: ReduxMiddleware[],
|
|
85
|
+
rootDispatch: Dispatch,
|
|
86
|
+
) {
|
|
87
|
+
const middlewareAPI: ReduxMiddlewareAPI = {
|
|
88
|
+
getState: () => ({}),
|
|
89
|
+
dispatch: action => rootDispatch(action),
|
|
90
|
+
};
|
|
91
|
+
const comp = compose(
|
|
92
|
+
middlewares.map(middleware => middleware(middlewareAPI)),
|
|
93
|
+
);
|
|
94
|
+
const dispatch = comp(rootDispatch);
|
|
95
|
+
return dispatch;
|
|
96
|
+
}
|
|
97
|
+
const compose = (fns: ((...args: any) => any)[]) => (initial: any) =>
|
|
98
|
+
fns.reduceRight((v, f) => f(v), initial);
|
|
@@ -21,11 +21,10 @@ describe('LogoutManager', () => {
|
|
|
21
21
|
const manager = new LogoutManager();
|
|
22
22
|
const getState = () => initialState;
|
|
23
23
|
|
|
24
|
-
describe('
|
|
24
|
+
describe('middleware', () => {
|
|
25
25
|
it('should return the same value every call', () => {
|
|
26
|
-
const a = manager.
|
|
27
|
-
expect(a).toBe(manager.
|
|
28
|
-
expect(a).toBe(manager.getMiddleware());
|
|
26
|
+
const a = manager.middleware;
|
|
27
|
+
expect(a).toBe(manager.middleware);
|
|
29
28
|
});
|
|
30
29
|
});
|
|
31
30
|
|
|
@@ -33,7 +32,6 @@ describe('LogoutManager', () => {
|
|
|
33
32
|
afterEach(() => {
|
|
34
33
|
jest.useRealTimers();
|
|
35
34
|
});
|
|
36
|
-
const middleware = manager.getMiddleware();
|
|
37
35
|
const next = jest.fn();
|
|
38
36
|
const dispatch = jest.fn(action => Promise.resolve());
|
|
39
37
|
const controller = new Controller({ dispatch, getState });
|
|
@@ -48,7 +46,7 @@ describe('LogoutManager', () => {
|
|
|
48
46
|
args: [{ id: 5 }],
|
|
49
47
|
response: { id: 5, title: 'hi' },
|
|
50
48
|
});
|
|
51
|
-
await middleware(API)(next)(action);
|
|
49
|
+
await manager.middleware(API)(next)(action);
|
|
52
50
|
|
|
53
51
|
expect(dispatch.mock.calls.length).toBe(0);
|
|
54
52
|
});
|
|
@@ -60,7 +58,7 @@ describe('LogoutManager', () => {
|
|
|
60
58
|
response: error,
|
|
61
59
|
error: true,
|
|
62
60
|
});
|
|
63
|
-
await middleware(API)(next)(action);
|
|
61
|
+
await manager.middleware(API)(next)(action);
|
|
64
62
|
|
|
65
63
|
expect(dispatch.mock.calls.length).toBe(0);
|
|
66
64
|
});
|
|
@@ -72,7 +70,7 @@ describe('LogoutManager', () => {
|
|
|
72
70
|
response: error,
|
|
73
71
|
error: true,
|
|
74
72
|
});
|
|
75
|
-
await middleware(API)(next)(action);
|
|
73
|
+
await manager.middleware(API)(next)(action);
|
|
76
74
|
|
|
77
75
|
expect(dispatch.mock.calls.length).toBe(1);
|
|
78
76
|
expect(dispatch.mock.calls[0][0]?.type).toBe(RESET_TYPE);
|
|
@@ -85,7 +83,7 @@ describe('LogoutManager', () => {
|
|
|
85
83
|
return error.status === 403;
|
|
86
84
|
},
|
|
87
85
|
handleLogout,
|
|
88
|
-
}).
|
|
86
|
+
}).middleware;
|
|
89
87
|
const error: any = new Error('network failed');
|
|
90
88
|
error.status = 403;
|
|
91
89
|
const action = createSetResponse(CoolerArticleResource.get, {
|
|
@@ -102,7 +100,7 @@ describe('LogoutManager', () => {
|
|
|
102
100
|
const action = { type: FETCH_TYPE };
|
|
103
101
|
next.mockReset();
|
|
104
102
|
|
|
105
|
-
await middleware(API)(next)(action as any);
|
|
103
|
+
await manager.middleware(API)(next)(action as any);
|
|
106
104
|
|
|
107
105
|
expect(next.mock.calls.length).toBe(1);
|
|
108
106
|
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import Controller from '../../controller/Controller';
|
|
2
|
-
import { Middleware } from '../../middlewareTypes';
|
|
3
2
|
import { ActionTypes } from '../../types';
|
|
4
3
|
import NetworkManager from '../NetworkManager';
|
|
5
4
|
|
|
6
|
-
const
|
|
5
|
+
const netMgr = new NetworkManager();
|
|
7
6
|
it('middlewares should compose with non-data-client middlewares', () => {
|
|
8
7
|
type AnotherAction = {
|
|
9
8
|
type: 'BOB';
|
|
@@ -30,7 +29,7 @@ it('middlewares should compose with non-data-client middlewares', () => {
|
|
|
30
29
|
counter++;
|
|
31
30
|
};
|
|
32
31
|
|
|
33
|
-
const [a, b] = [middleware(API), nonRHMiddleware(API)];
|
|
32
|
+
const [a, b] = [netMgr.middleware(API), nonRHMiddleware(API)];
|
|
34
33
|
const dispA = a(b(dispatch));
|
|
35
34
|
const dispB = b(a(dispatch));
|
|
36
35
|
expect(dispatch.mock.calls.length).toBe(0);
|
|
@@ -33,18 +33,17 @@ describe('NetworkManager', () => {
|
|
|
33
33
|
expect(hacked.getHacked()).toEqual(initialState);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
describe('
|
|
36
|
+
describe('middleware', () => {
|
|
37
37
|
it('should return the same value every call', () => {
|
|
38
|
-
const a = manager.
|
|
39
|
-
expect(a).toBe(manager.
|
|
40
|
-
expect(a).toBe(manager.getMiddleware());
|
|
38
|
+
const a = manager.middleware;
|
|
39
|
+
expect(a).toBe(manager.middleware);
|
|
41
40
|
});
|
|
42
41
|
it('should return the different value for a different instance', () => {
|
|
43
|
-
const a = manager.
|
|
42
|
+
const a = manager.middleware;
|
|
44
43
|
const manager2 = new NetworkManager();
|
|
45
|
-
const a2 = manager2.
|
|
44
|
+
const a2 = manager2.middleware;
|
|
46
45
|
expect(a).not.toBe(a2);
|
|
47
|
-
expect(a2).toBe(manager2.
|
|
46
|
+
expect(a2).toBe(manager2.middleware);
|
|
48
47
|
manager2.cleanup();
|
|
49
48
|
});
|
|
50
49
|
});
|
|
@@ -132,10 +131,8 @@ describe('NetworkManager', () => {
|
|
|
132
131
|
(fetchRejectAction.meta.promise as any).catch((e: unknown) => {});
|
|
133
132
|
|
|
134
133
|
let NM: NetworkManager;
|
|
135
|
-
let middleware: Middleware;
|
|
136
134
|
beforeEach(() => {
|
|
137
|
-
NM = new NetworkManager(42, 7);
|
|
138
|
-
middleware = NM.getMiddleware();
|
|
135
|
+
NM = new NetworkManager({ dataExpiryLength: 42, errorExpiryLength: 7 });
|
|
139
136
|
});
|
|
140
137
|
afterEach(() => {
|
|
141
138
|
NM.cleanup();
|
|
@@ -152,7 +149,7 @@ describe('NetworkManager', () => {
|
|
|
152
149
|
},
|
|
153
150
|
);
|
|
154
151
|
|
|
155
|
-
middleware(API)(next)(fetchResolveAction);
|
|
152
|
+
NM.middleware(API)(next)(fetchResolveAction);
|
|
156
153
|
|
|
157
154
|
const response = await fetchResolveAction.endpoint(
|
|
158
155
|
...fetchResolveAction.args,
|
|
@@ -188,7 +185,7 @@ describe('NetworkManager', () => {
|
|
|
188
185
|
},
|
|
189
186
|
);
|
|
190
187
|
|
|
191
|
-
middleware(API)(next)(fetchSetWithUpdatersAction);
|
|
188
|
+
NM.middleware(API)(next)(fetchSetWithUpdatersAction);
|
|
192
189
|
|
|
193
190
|
const response = await fetchSetWithUpdatersAction.endpoint(
|
|
194
191
|
...fetchSetWithUpdatersAction.args,
|
|
@@ -224,7 +221,7 @@ describe('NetworkManager', () => {
|
|
|
224
221
|
},
|
|
225
222
|
);
|
|
226
223
|
|
|
227
|
-
middleware(API)(next)(fetchRpcWithUpdatersAction);
|
|
224
|
+
NM.middleware(API)(next)(fetchRpcWithUpdatersAction);
|
|
228
225
|
|
|
229
226
|
const response = await fetchRpcWithUpdatersAction.endpoint(
|
|
230
227
|
...fetchRpcWithUpdatersAction.args,
|
|
@@ -260,7 +257,7 @@ describe('NetworkManager', () => {
|
|
|
260
257
|
},
|
|
261
258
|
);
|
|
262
259
|
|
|
263
|
-
middleware(API)(next)(fetchRpcWithUpdatersAndOptimisticAction);
|
|
260
|
+
NM.middleware(API)(next)(fetchRpcWithUpdatersAndOptimisticAction);
|
|
264
261
|
|
|
265
262
|
const response = await fetchRpcWithUpdatersAndOptimisticAction.endpoint(
|
|
266
263
|
...fetchRpcWithUpdatersAndOptimisticAction.args,
|
|
@@ -293,7 +290,7 @@ describe('NetworkManager', () => {
|
|
|
293
290
|
},
|
|
294
291
|
);
|
|
295
292
|
|
|
296
|
-
middleware(API)(() => Promise.resolve())({
|
|
293
|
+
NM.middleware(API)(() => Promise.resolve())({
|
|
297
294
|
...fetchResolveAction,
|
|
298
295
|
endpoint: detailEndpoint.extend({ dataExpiryLength: 314 }),
|
|
299
296
|
});
|
|
@@ -314,7 +311,7 @@ describe('NetworkManager', () => {
|
|
|
314
311
|
},
|
|
315
312
|
);
|
|
316
313
|
|
|
317
|
-
middleware(API)(() => Promise.resolve())({
|
|
314
|
+
NM.middleware(API)(() => Promise.resolve())({
|
|
318
315
|
...fetchResolveAction,
|
|
319
316
|
endpoint: detailEndpoint.extend({ dataExpiryLength: undefined }),
|
|
320
317
|
});
|
|
@@ -337,7 +334,7 @@ describe('NetworkManager', () => {
|
|
|
337
334
|
);
|
|
338
335
|
|
|
339
336
|
try {
|
|
340
|
-
await middleware(API)(next)(fetchRejectAction);
|
|
337
|
+
await NM.middleware(API)(next)(fetchRejectAction);
|
|
341
338
|
} catch (error) {
|
|
342
339
|
expect(next).not.toHaveBeenCalled();
|
|
343
340
|
expect(dispatch).toHaveBeenCalledWith({
|
|
@@ -363,7 +360,7 @@ describe('NetworkManager', () => {
|
|
|
363
360
|
);
|
|
364
361
|
|
|
365
362
|
try {
|
|
366
|
-
await middleware(API)(() => Promise.resolve())({
|
|
363
|
+
await NM.middleware(API)(() => Promise.resolve())({
|
|
367
364
|
...fetchRejectAction,
|
|
368
365
|
meta: {
|
|
369
366
|
...fetchRejectAction.meta,
|
|
@@ -386,7 +383,7 @@ describe('NetworkManager', () => {
|
|
|
386
383
|
);
|
|
387
384
|
|
|
388
385
|
try {
|
|
389
|
-
await middleware(API)(() => Promise.resolve())({
|
|
386
|
+
await NM.middleware(API)(() => Promise.resolve())({
|
|
390
387
|
...fetchRejectAction,
|
|
391
388
|
meta: {
|
|
392
389
|
...fetchRejectAction.meta,
|
|
@@ -27,11 +27,10 @@ describe('SubscriptionManager', () => {
|
|
|
27
27
|
const manager = new SubscriptionManager(TestSubscription);
|
|
28
28
|
const getState = () => initialState;
|
|
29
29
|
|
|
30
|
-
describe('
|
|
30
|
+
describe('middleware', () => {
|
|
31
31
|
it('should return the same value every call', () => {
|
|
32
|
-
const a = manager.
|
|
33
|
-
expect(a).toBe(manager.
|
|
34
|
-
expect(a).toBe(manager.getMiddleware());
|
|
32
|
+
const a = manager.middleware;
|
|
33
|
+
expect(a).toBe(manager.middleware);
|
|
35
34
|
});
|
|
36
35
|
});
|
|
37
36
|
|
|
@@ -74,7 +73,6 @@ describe('SubscriptionManager', () => {
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
const manager = new SubscriptionManager(TestSubscription);
|
|
77
|
-
const middleware = manager.getMiddleware();
|
|
78
76
|
const next = jest.fn();
|
|
79
77
|
const dispatch = () => Promise.resolve();
|
|
80
78
|
const controller = new Controller({ dispatch, getState });
|
|
@@ -86,14 +84,14 @@ describe('SubscriptionManager', () => {
|
|
|
86
84
|
);
|
|
87
85
|
it('subscribe should add a subscription', () => {
|
|
88
86
|
const action = createSubscribeAction({ id: 5 });
|
|
89
|
-
middleware(API)(next)(action);
|
|
87
|
+
manager.middleware(API)(next)(action);
|
|
90
88
|
|
|
91
89
|
expect(next).not.toHaveBeenCalled();
|
|
92
90
|
expect((manager as any).subscriptions[action.key]).toBeDefined();
|
|
93
91
|
});
|
|
94
92
|
it('subscribe should add a subscription (no frequency)', () => {
|
|
95
93
|
const action = createSubscribeAction({ id: 597 });
|
|
96
|
-
middleware(API)(next)(action);
|
|
94
|
+
manager.middleware(API)(next)(action);
|
|
97
95
|
|
|
98
96
|
expect(next).not.toHaveBeenCalled();
|
|
99
97
|
expect((manager as any).subscriptions[action.key]).toBeDefined();
|
|
@@ -101,19 +99,19 @@ describe('SubscriptionManager', () => {
|
|
|
101
99
|
|
|
102
100
|
it('subscribe with same should call subscription.add', () => {
|
|
103
101
|
const action = createSubscribeAction({ id: 5, title: 'four' });
|
|
104
|
-
middleware(API)(next)(action);
|
|
102
|
+
manager.middleware(API)(next)(action);
|
|
105
103
|
|
|
106
104
|
expect(
|
|
107
105
|
(manager as any).subscriptions[action.key].add.mock.calls.length,
|
|
108
106
|
).toBe(1);
|
|
109
|
-
middleware(API)(next)(action);
|
|
107
|
+
manager.middleware(API)(next)(action);
|
|
110
108
|
expect(
|
|
111
109
|
(manager as any).subscriptions[action.key].add.mock.calls.length,
|
|
112
110
|
).toBe(2);
|
|
113
111
|
});
|
|
114
112
|
it('subscribe with another should create another', () => {
|
|
115
113
|
const action = createSubscribeAction({ id: 7, title: 'four cakes' });
|
|
116
|
-
middleware(API)(next)(action);
|
|
114
|
+
manager.middleware(API)(next)(action);
|
|
117
115
|
|
|
118
116
|
expect((manager as any).subscriptions[action.key]).toBeDefined();
|
|
119
117
|
expect(
|
|
@@ -134,13 +132,13 @@ describe('SubscriptionManager', () => {
|
|
|
134
132
|
() => true,
|
|
135
133
|
);
|
|
136
134
|
|
|
137
|
-
middleware(API)(next)(action);
|
|
135
|
+
manager.middleware(API)(next)(action);
|
|
138
136
|
|
|
139
137
|
expect((manager as any).subscriptions[action.key]).not.toBeDefined();
|
|
140
138
|
});
|
|
141
139
|
|
|
142
140
|
it('unsubscribe should delete when remove returns true (no frequency)', () => {
|
|
143
|
-
middleware(API)(next)(
|
|
141
|
+
manager.middleware(API)(next)(
|
|
144
142
|
createSubscribeAction({ id: 50, title: 'four cakes' }),
|
|
145
143
|
);
|
|
146
144
|
|
|
@@ -149,7 +147,7 @@ describe('SubscriptionManager', () => {
|
|
|
149
147
|
() => true,
|
|
150
148
|
);
|
|
151
149
|
|
|
152
|
-
middleware(API)(next)(action);
|
|
150
|
+
manager.middleware(API)(next)(action);
|
|
153
151
|
|
|
154
152
|
expect((manager as any).subscriptions[action.key]).not.toBeDefined();
|
|
155
153
|
});
|
|
@@ -160,7 +158,7 @@ describe('SubscriptionManager', () => {
|
|
|
160
158
|
() => false,
|
|
161
159
|
);
|
|
162
160
|
|
|
163
|
-
middleware(API)(next)(action);
|
|
161
|
+
manager.middleware(API)(next)(action);
|
|
164
162
|
|
|
165
163
|
expect((manager as any).subscriptions[action.key]).toBeDefined();
|
|
166
164
|
expect(
|
|
@@ -174,7 +172,7 @@ describe('SubscriptionManager', () => {
|
|
|
174
172
|
|
|
175
173
|
const action = createUnsubscribeAction({ id: 25 });
|
|
176
174
|
|
|
177
|
-
middleware(API)(next)(action);
|
|
175
|
+
manager.middleware(API)(next)(action);
|
|
178
176
|
|
|
179
177
|
expect((manager as any).subscriptions[action.key]).not.toBeDefined();
|
|
180
178
|
|
|
@@ -190,7 +188,7 @@ describe('SubscriptionManager', () => {
|
|
|
190
188
|
const action = { type: SET_RESPONSE_TYPE };
|
|
191
189
|
next.mockReset();
|
|
192
190
|
|
|
193
|
-
middleware(API)(next)(action as any);
|
|
191
|
+
manager.middleware(API)(next)(action as any);
|
|
194
192
|
|
|
195
193
|
expect(next.mock.calls.length).toBe(1);
|
|
196
194
|
});
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import NetworkManager from './NetworkManager.js';
|
|
2
2
|
import type Controller from '../controller/Controller.js';
|
|
3
|
-
import type { Reducer, Dispatch, ReducerState } from '../middlewareTypes.js';
|
|
4
3
|
import { Manager } from '../types.js';
|
|
5
4
|
|
|
6
5
|
export default function applyManager(
|
|
7
6
|
managers: Manager[],
|
|
8
7
|
controller: Controller,
|
|
9
|
-
):
|
|
8
|
+
): ReduxMiddleware[] {
|
|
10
9
|
/* istanbul ignore next */
|
|
11
10
|
if (
|
|
12
11
|
process.env.NODE_ENV !== 'production' &&
|
|
@@ -18,25 +17,38 @@ export default function applyManager(
|
|
|
18
17
|
);
|
|
19
18
|
}
|
|
20
19
|
return managers.map((manager, i) => {
|
|
21
|
-
|
|
20
|
+
if (!manager.middleware) manager.middleware = manager.getMiddleware?.();
|
|
22
21
|
return ({ dispatch, getState }) => {
|
|
23
22
|
if (i === 0) {
|
|
24
23
|
(controller as any).dispatch = dispatch;
|
|
25
24
|
(controller as any).getState = getState;
|
|
26
25
|
}
|
|
27
26
|
// controller is a superset of the middleware API
|
|
28
|
-
return
|
|
27
|
+
return (manager as Manager & { middleware: ReduxMiddleware }).middleware(
|
|
28
|
+
controller as Controller<any>,
|
|
29
|
+
);
|
|
29
30
|
};
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/* These should be compatible with redux */
|
|
34
|
-
export interface
|
|
35
|
+
export interface ReduxMiddlewareAPI<
|
|
35
36
|
R extends Reducer<any, any> = Reducer<any, any>,
|
|
36
37
|
> {
|
|
37
38
|
getState: () => ReducerState<R>;
|
|
38
|
-
dispatch:
|
|
39
|
+
dispatch: ReactDispatch<R>;
|
|
39
40
|
}
|
|
40
|
-
export type
|
|
41
|
+
export type ReduxMiddleware = <R extends Reducer<any, any>>({
|
|
41
42
|
dispatch,
|
|
42
|
-
}:
|
|
43
|
+
}: ReduxMiddlewareAPI<R>) => (next: ReactDispatch<R>) => ReactDispatch<R>;
|
|
44
|
+
|
|
45
|
+
/* The next are types from React; but we don't want dependencies on it */
|
|
46
|
+
export type ReactDispatch<R extends Reducer<any, any>> = (
|
|
47
|
+
action: ReducerAction<R>,
|
|
48
|
+
) => Promise<void>;
|
|
49
|
+
|
|
50
|
+
export type Reducer<S, A> = (prevState: S, action: A) => S;
|
|
51
|
+
export type ReducerState<R extends Reducer<any, any>> =
|
|
52
|
+
R extends Reducer<infer S, any> ? S : never;
|
|
53
|
+
export type ReducerAction<R extends Reducer<any, any>> =
|
|
54
|
+
R extends Reducer<any, infer A> ? A : never;
|