@data-client/core 0.12.3 → 0.13.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/README.md +1 -1
- package/dist/index.js +89 -15
- package/dist/index.umd.min.js +1 -1
- package/legacy/actionTypes.js +2 -1
- package/legacy/actions.js +1 -1
- package/legacy/controller/Controller.js +21 -7
- package/legacy/controller/createSet.js +7 -11
- package/legacy/controller/createSetResponse.js +32 -0
- package/legacy/index.js +2 -1
- package/legacy/manager/DevtoolsManager.js +5 -5
- package/legacy/manager/LogoutManager.js +3 -3
- package/legacy/manager/NetworkManager.js +5 -5
- package/legacy/manager/PollingSubscription.js +1 -1
- package/legacy/manager/SubscriptionManager.js +1 -1
- package/legacy/next/index.js +1 -1
- package/legacy/state/reducer/createReducer.js +6 -3
- package/legacy/state/reducer/fetchReducer.js +1 -1
- package/legacy/state/reducer/setReducer.js +7 -85
- package/legacy/state/reducer/setResponseReducer.js +107 -0
- package/legacy/types.js +1 -1
- package/lib/actionTypes.d.ts +1 -0
- package/lib/actionTypes.d.ts.map +1 -1
- package/lib/actionTypes.js +2 -1
- package/lib/actions.d.ts +23 -11
- package/lib/actions.d.ts.map +1 -1
- package/lib/actions.js +1 -1
- package/lib/controller/Controller.d.ts +7 -2
- package/lib/controller/Controller.d.ts.map +1 -1
- package/lib/controller/Controller.js +21 -7
- package/lib/controller/createSet.d.ts +5 -17
- package/lib/controller/createSet.d.ts.map +1 -1
- package/lib/controller/createSet.js +7 -11
- package/lib/controller/createSetResponse.d.ts +20 -0
- package/lib/controller/createSetResponse.d.ts.map +1 -0
- package/lib/controller/createSetResponse.js +32 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/manager/DevtoolsManager.d.ts.map +1 -1
- package/lib/manager/DevtoolsManager.js +5 -3
- package/lib/manager/LogoutManager.js +3 -3
- package/lib/manager/NetworkManager.d.ts +2 -2
- package/lib/manager/NetworkManager.d.ts.map +1 -1
- package/lib/manager/NetworkManager.js +5 -5
- package/lib/manager/PollingSubscription.js +1 -1
- package/lib/manager/SubscriptionManager.js +1 -1
- package/lib/next/index.js +1 -1
- package/lib/state/reducer/createReducer.d.ts.map +1 -1
- package/lib/state/reducer/createReducer.js +6 -3
- package/lib/state/reducer/expireReducer.d.ts +1 -1
- package/lib/state/reducer/fetchReducer.js +1 -1
- package/lib/state/reducer/invalidateReducer.d.ts +1 -1
- package/lib/state/reducer/setReducer.d.ts +2 -3
- package/lib/state/reducer/setReducer.d.ts.map +1 -1
- package/lib/state/reducer/setReducer.js +7 -90
- package/lib/state/reducer/setResponseReducer.d.ts +4 -0
- package/lib/state/reducer/setResponseReducer.d.ts.map +1 -0
- package/lib/state/reducer/setResponseReducer.js +112 -0
- package/lib/types.d.ts +2 -4
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +1 -1
- package/package.json +1 -1
- package/src/actionTypes.ts +1 -0
- package/src/actions.ts +29 -10
- package/src/controller/Controller.ts +25 -8
- package/src/controller/createSet.ts +11 -51
- package/src/controller/createSetResponse.ts +79 -0
- package/src/index.ts +1 -0
- package/src/manager/DevtoolsManager.ts +4 -2
- package/src/manager/LogoutManager.ts +2 -2
- package/src/manager/NetworkManager.ts +6 -6
- package/src/manager/__tests__/logoutManager.ts +5 -5
- package/src/manager/__tests__/networkManager.ts +6 -6
- package/src/manager/__tests__/subscriptionManager.ts +2 -2
- package/src/state/__tests__/reducer.ts +64 -25
- package/src/state/reducer/createReducer.ts +6 -1
- package/src/state/reducer/fetchReducer.ts +2 -2
- package/src/state/reducer/setReducer.ts +9 -121
- package/src/state/reducer/setResponseReducer.ts +151 -0
- package/src/types.ts +6 -5
- package/ts3.4/actionTypes.d.ts +1 -0
- package/ts3.4/actions.d.ts +23 -11
- package/ts3.4/controller/Controller.d.ts +10 -2
- package/ts3.4/controller/createSet.d.ts +5 -19
- package/ts3.4/controller/createSetResponse.d.ts +24 -0
- package/ts3.4/index.d.ts +1 -0
- package/ts3.4/manager/NetworkManager.d.ts +2 -2
- package/ts3.4/state/reducer/expireReducer.d.ts +1 -1
- package/ts3.4/state/reducer/invalidateReducer.d.ts +1 -1
- package/ts3.4/state/reducer/setReducer.d.ts +2 -3
- package/ts3.4/state/reducer/setResponseReducer.d.ts +4 -0
- package/ts3.4/types.d.ts +2 -4
|
@@ -2,7 +2,7 @@ import { CoolerArticleResource } from '__tests__/new';
|
|
|
2
2
|
|
|
3
3
|
import { Controller, initialState } from '../..';
|
|
4
4
|
import { FETCH_TYPE, RESET_TYPE } from '../../actionTypes';
|
|
5
|
-
import
|
|
5
|
+
import createSetResponse from '../../controller/createSetResponse';
|
|
6
6
|
import LogoutManager from '../LogoutManager.js';
|
|
7
7
|
|
|
8
8
|
function onError(e: any) {
|
|
@@ -44,7 +44,7 @@ describe('LogoutManager', () => {
|
|
|
44
44
|
},
|
|
45
45
|
);
|
|
46
46
|
it('should ignore non-error set', async () => {
|
|
47
|
-
const action =
|
|
47
|
+
const action = createSetResponse(CoolerArticleResource.get, {
|
|
48
48
|
args: [{ id: 5 }],
|
|
49
49
|
response: { id: 5, title: 'hi' },
|
|
50
50
|
});
|
|
@@ -55,7 +55,7 @@ describe('LogoutManager', () => {
|
|
|
55
55
|
it('should ignore non-401 set', async () => {
|
|
56
56
|
const error: any = new Error('network failed');
|
|
57
57
|
error.status = 404;
|
|
58
|
-
const action =
|
|
58
|
+
const action = createSetResponse(CoolerArticleResource.get, {
|
|
59
59
|
args: [{ id: 5 }],
|
|
60
60
|
response: error,
|
|
61
61
|
error: true,
|
|
@@ -67,7 +67,7 @@ describe('LogoutManager', () => {
|
|
|
67
67
|
it('should dispatch reset on 401', async () => {
|
|
68
68
|
const error: any = new Error('network failed');
|
|
69
69
|
error.status = 401;
|
|
70
|
-
const action =
|
|
70
|
+
const action = createSetResponse(CoolerArticleResource.get, {
|
|
71
71
|
args: [{ id: 5 }],
|
|
72
72
|
response: error,
|
|
73
73
|
error: true,
|
|
@@ -88,7 +88,7 @@ describe('LogoutManager', () => {
|
|
|
88
88
|
}).getMiddleware();
|
|
89
89
|
const error: any = new Error('network failed');
|
|
90
90
|
error.status = 403;
|
|
91
|
-
const action =
|
|
91
|
+
const action = createSetResponse(CoolerArticleResource.get, {
|
|
92
92
|
args: [{ id: 5 }],
|
|
93
93
|
response: error,
|
|
94
94
|
error: true,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Endpoint } from '@data-client/endpoint';
|
|
2
2
|
import { Article, ArticleResource } from '__tests__/new';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { SET_RESPONSE_TYPE } from '../../actionTypes';
|
|
5
5
|
import Controller from '../../controller/Controller';
|
|
6
6
|
import createFetch from '../../controller/createFetch';
|
|
7
7
|
import NetworkManager from '../../manager/NetworkManager';
|
|
@@ -160,7 +160,7 @@ describe('NetworkManager', () => {
|
|
|
160
160
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
161
161
|
|
|
162
162
|
const action = {
|
|
163
|
-
type:
|
|
163
|
+
type: SET_RESPONSE_TYPE,
|
|
164
164
|
endpoint: fetchResolveAction.endpoint,
|
|
165
165
|
payload: data,
|
|
166
166
|
meta: {
|
|
@@ -193,7 +193,7 @@ describe('NetworkManager', () => {
|
|
|
193
193
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
194
194
|
|
|
195
195
|
const action = {
|
|
196
|
-
type:
|
|
196
|
+
type: SET_RESPONSE_TYPE,
|
|
197
197
|
endpoint: fetchSetWithUpdatersAction.endpoint,
|
|
198
198
|
payload: data,
|
|
199
199
|
meta: {
|
|
@@ -226,7 +226,7 @@ describe('NetworkManager', () => {
|
|
|
226
226
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
227
227
|
|
|
228
228
|
const action = {
|
|
229
|
-
type:
|
|
229
|
+
type: SET_RESPONSE_TYPE,
|
|
230
230
|
endpoint: fetchRpcWithUpdatersAction.endpoint,
|
|
231
231
|
payload: data,
|
|
232
232
|
meta: {
|
|
@@ -259,7 +259,7 @@ describe('NetworkManager', () => {
|
|
|
259
259
|
// mutations resolve before dispatch, so we must wait for next tick to see set
|
|
260
260
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
261
261
|
expect(dispatch).toHaveBeenCalledWith({
|
|
262
|
-
type:
|
|
262
|
+
type: SET_RESPONSE_TYPE,
|
|
263
263
|
endpoint: fetchRpcWithUpdatersAndOptimisticAction.endpoint,
|
|
264
264
|
payload: data,
|
|
265
265
|
meta: {
|
|
@@ -329,7 +329,7 @@ describe('NetworkManager', () => {
|
|
|
329
329
|
} catch (error) {
|
|
330
330
|
expect(next).not.toHaveBeenCalled();
|
|
331
331
|
expect(dispatch).toHaveBeenCalledWith({
|
|
332
|
-
type:
|
|
332
|
+
type: SET_RESPONSE_TYPE,
|
|
333
333
|
payload: error,
|
|
334
334
|
meta: {
|
|
335
335
|
key: fetchRejectAction.meta.key,
|
|
@@ -4,7 +4,7 @@ import { actionTypes, Controller, initialState } from '../..';
|
|
|
4
4
|
import { SubscribeAction, UnsubscribeAction } from '../../types';
|
|
5
5
|
import SubscriptionManager, { Subscription } from '../SubscriptionManager.js';
|
|
6
6
|
|
|
7
|
-
const { UNSUBSCRIBE_TYPE, SUBSCRIBE_TYPE,
|
|
7
|
+
const { UNSUBSCRIBE_TYPE, SUBSCRIBE_TYPE, SET_RESPONSE_TYPE } = actionTypes;
|
|
8
8
|
|
|
9
9
|
function onError(e: any) {
|
|
10
10
|
e.preventDefault();
|
|
@@ -192,7 +192,7 @@ describe('SubscriptionManager', () => {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
it('should let other actions pass through', () => {
|
|
195
|
-
const action = { type:
|
|
195
|
+
const action = { type: SET_RESPONSE_TYPE };
|
|
196
196
|
next.mockReset();
|
|
197
197
|
|
|
198
198
|
middleware(API)(next)(action as any);
|
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
import { INVALID } from '@data-client/endpoint';
|
|
2
|
-
import {
|
|
3
|
-
ArticleResource,
|
|
4
|
-
PaginatedArticleResource,
|
|
5
|
-
Article,
|
|
6
|
-
PaginatedArticle,
|
|
7
|
-
} from '__tests__/new';
|
|
2
|
+
import { ArticleResource, Article, PaginatedArticle } from '__tests__/new';
|
|
8
3
|
|
|
9
4
|
import { Controller } from '../..';
|
|
10
5
|
import {
|
|
11
|
-
SET_TYPE,
|
|
12
6
|
INVALIDATE_TYPE,
|
|
13
7
|
FETCH_TYPE,
|
|
14
8
|
RESET_TYPE,
|
|
15
9
|
GC_TYPE,
|
|
10
|
+
SET_RESPONSE_TYPE,
|
|
11
|
+
SET_TYPE,
|
|
16
12
|
} from '../../actionTypes';
|
|
17
|
-
import createSet from '../../controller/createSet';
|
|
18
13
|
import {
|
|
19
|
-
UpdateFunction,
|
|
20
14
|
State,
|
|
21
15
|
ActionTypes,
|
|
22
16
|
FetchAction,
|
|
23
|
-
|
|
17
|
+
SetResponseAction,
|
|
24
18
|
ResetAction,
|
|
25
19
|
InvalidateAction,
|
|
26
20
|
GCAction,
|
|
21
|
+
SetAction,
|
|
27
22
|
} from '../../types';
|
|
28
23
|
import createReducer, { initialState } from '../reducer/createReducer';
|
|
29
24
|
|
|
@@ -40,8 +35,8 @@ describe('reducer', () => {
|
|
|
40
35
|
describe('singles', () => {
|
|
41
36
|
const id = 20;
|
|
42
37
|
const payload = { id, title: 'hi', content: 'this is the content' };
|
|
43
|
-
const action:
|
|
44
|
-
type:
|
|
38
|
+
const action: SetResponseAction = {
|
|
39
|
+
type: SET_RESPONSE_TYPE,
|
|
45
40
|
payload,
|
|
46
41
|
endpoint: ArticleResource.get,
|
|
47
42
|
meta: {
|
|
@@ -207,11 +202,55 @@ describe('reducer', () => {
|
|
|
207
202
|
});
|
|
208
203
|
});
|
|
209
204
|
|
|
210
|
-
it('
|
|
205
|
+
it('set should add entity when it does not exist', () => {
|
|
211
206
|
const id = 20;
|
|
212
|
-
const
|
|
207
|
+
const value = { id, title: 'hi', content: 'this is the content' };
|
|
208
|
+
const action: SetAction = {
|
|
209
|
+
type: SET_TYPE,
|
|
210
|
+
value,
|
|
211
|
+
schema: Article,
|
|
212
|
+
meta: {
|
|
213
|
+
args: [{ id }],
|
|
214
|
+
date: 0,
|
|
215
|
+
fetchedAt: 0,
|
|
216
|
+
expiresAt: 1000000000000,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
const iniState = {
|
|
220
|
+
...initialState,
|
|
221
|
+
endpoints: { abc: '5', [ArticleResource.get.key(value)]: `${id}` },
|
|
222
|
+
};
|
|
223
|
+
const newState = reducer(iniState, action);
|
|
224
|
+
expect(newState.entities[Article.key]?.[id]).toEqual(value);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('set should never change endpoints', () => {
|
|
228
|
+
const id = 20;
|
|
229
|
+
const value = { id, title: 'hi', content: 'this is the content' };
|
|
213
230
|
const action: SetAction = {
|
|
214
231
|
type: SET_TYPE,
|
|
232
|
+
value,
|
|
233
|
+
schema: Article,
|
|
234
|
+
meta: {
|
|
235
|
+
args: [{ id }],
|
|
236
|
+
date: 0,
|
|
237
|
+
fetchedAt: 0,
|
|
238
|
+
expiresAt: 1000000000000,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
const iniState = {
|
|
242
|
+
...initialState,
|
|
243
|
+
endpoints: { abc: '5', [ArticleResource.get.key(value)]: `${id}` },
|
|
244
|
+
};
|
|
245
|
+
const newState = reducer(iniState, action);
|
|
246
|
+
expect(newState.endpoints).toStrictEqual(iniState.endpoints);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('mutate should never change endpoints', () => {
|
|
250
|
+
const id = 20;
|
|
251
|
+
const payload = { id, title: 'hi', content: 'this is the content' };
|
|
252
|
+
const action: SetResponseAction = {
|
|
253
|
+
type: SET_RESPONSE_TYPE,
|
|
215
254
|
payload,
|
|
216
255
|
endpoint: ArticleResource.get,
|
|
217
256
|
meta: {
|
|
@@ -231,8 +270,8 @@ describe('reducer', () => {
|
|
|
231
270
|
});
|
|
232
271
|
it('purge should delete entities', () => {
|
|
233
272
|
const id = 20;
|
|
234
|
-
const action:
|
|
235
|
-
type:
|
|
273
|
+
const action: SetResponseAction = {
|
|
274
|
+
type: SET_RESPONSE_TYPE,
|
|
236
275
|
payload: { id },
|
|
237
276
|
endpoint: ArticleResource.delete,
|
|
238
277
|
meta: {
|
|
@@ -283,7 +322,7 @@ describe('reducer', () => {
|
|
|
283
322
|
};
|
|
284
323
|
|
|
285
324
|
it('should insert a new page of resources into a list request', () => {
|
|
286
|
-
const action =
|
|
325
|
+
const action = createSetResponse(
|
|
287
326
|
{ results: [{ id: 11 }, { id: 12 }] },
|
|
288
327
|
{
|
|
289
328
|
...endpoint,
|
|
@@ -310,7 +349,7 @@ describe('reducer', () => {
|
|
|
310
349
|
it('should insert correctly into the beginning of the list request', () => {
|
|
311
350
|
const newState = reducer(
|
|
312
351
|
iniState,
|
|
313
|
-
|
|
352
|
+
createSetResponse(
|
|
314
353
|
{ results: [{ id: 11 }, { id: 12 }] },
|
|
315
354
|
{
|
|
316
355
|
...endpoint,
|
|
@@ -350,7 +389,7 @@ describe('reducer', () => {
|
|
|
350
389
|
};
|
|
351
390
|
const newState = reducer(
|
|
352
391
|
iniState,
|
|
353
|
-
|
|
392
|
+
createSetResponse(
|
|
354
393
|
{ results: [{ id: 11 }, { id: 12 }] },
|
|
355
394
|
{
|
|
356
395
|
...endpoint,
|
|
@@ -420,8 +459,8 @@ describe('reducer', () => {
|
|
|
420
459
|
it('should set error in meta for "set"', () => {
|
|
421
460
|
const id = 20;
|
|
422
461
|
const error = new Error('hi');
|
|
423
|
-
const action:
|
|
424
|
-
type:
|
|
462
|
+
const action: SetResponseAction = {
|
|
463
|
+
type: SET_RESPONSE_TYPE,
|
|
425
464
|
payload: error,
|
|
426
465
|
endpoint: ArticleResource.get,
|
|
427
466
|
meta: {
|
|
@@ -440,8 +479,8 @@ describe('reducer', () => {
|
|
|
440
479
|
it('should not modify state on error for "rpc"', () => {
|
|
441
480
|
const id = 20;
|
|
442
481
|
const error = new Error('hi');
|
|
443
|
-
const action:
|
|
444
|
-
type:
|
|
482
|
+
const action: SetResponseAction = {
|
|
483
|
+
type: SET_RESPONSE_TYPE,
|
|
445
484
|
payload: error,
|
|
446
485
|
endpoint: ArticleResource.get,
|
|
447
486
|
meta: {
|
|
@@ -461,8 +500,8 @@ describe('reducer', () => {
|
|
|
461
500
|
it('should not delete on error for "purge"', () => {
|
|
462
501
|
const id = 20;
|
|
463
502
|
const error = new Error('hi');
|
|
464
|
-
const action:
|
|
465
|
-
type:
|
|
503
|
+
const action: SetResponseAction = {
|
|
504
|
+
type: SET_RESPONSE_TYPE,
|
|
466
505
|
payload: error,
|
|
467
506
|
endpoint: ArticleResource.delete,
|
|
468
507
|
meta: {
|
|
@@ -2,6 +2,7 @@ import { expireReducer } from './expireReducer.js';
|
|
|
2
2
|
import { fetchReducer } from './fetchReducer.js';
|
|
3
3
|
import { invalidateReducer } from './invalidateReducer.js';
|
|
4
4
|
import { setReducer } from './setReducer.js';
|
|
5
|
+
import { setResponseReducer } from './setResponseReducer.js';
|
|
5
6
|
import {
|
|
6
7
|
SET_TYPE,
|
|
7
8
|
INVALIDATE_TYPE,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
OPTIMISTIC_TYPE,
|
|
12
13
|
INVALIDATEALL_TYPE,
|
|
13
14
|
EXPIREALL_TYPE,
|
|
15
|
+
SET_RESPONSE_TYPE,
|
|
14
16
|
} from '../../actionTypes.js';
|
|
15
17
|
import type Controller from '../../controller/Controller.js';
|
|
16
18
|
import type { ActionTypes, State } from '../../types.js';
|
|
@@ -38,8 +40,11 @@ export default function createReducer(controller: Controller): ReducerType {
|
|
|
38
40
|
|
|
39
41
|
case OPTIMISTIC_TYPE:
|
|
40
42
|
// eslint-disable-next-line no-fallthrough
|
|
43
|
+
case SET_RESPONSE_TYPE:
|
|
44
|
+
return setResponseReducer(state, action, controller);
|
|
45
|
+
|
|
41
46
|
case SET_TYPE:
|
|
42
|
-
return setReducer(state, action
|
|
47
|
+
return setReducer(state, action);
|
|
43
48
|
|
|
44
49
|
case INVALIDATEALL_TYPE:
|
|
45
50
|
case INVALIDATE_TYPE:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import createOptimistic from '../../controller/createOptimistic.js';
|
|
2
2
|
import type {
|
|
3
3
|
State,
|
|
4
|
-
|
|
4
|
+
SetResponseAction,
|
|
5
5
|
OptimisticAction,
|
|
6
6
|
FetchAction,
|
|
7
7
|
} from '../../types.js';
|
|
8
8
|
|
|
9
9
|
export function fetchReducer(state: State<unknown>, action: FetchAction) {
|
|
10
|
-
let setAction:
|
|
10
|
+
let setAction: SetResponseAction | OptimisticAction;
|
|
11
11
|
|
|
12
12
|
if (action.endpoint.getOptimisticResponse && action.endpoint.sideEffect) {
|
|
13
13
|
setAction = createOptimistic(action.endpoint, {
|
|
@@ -1,146 +1,34 @@
|
|
|
1
1
|
import { normalize } from '@data-client/normalizr';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import AbortOptimistic from '../../controller/AbortOptimistic.js';
|
|
5
|
-
import type Controller from '../../controller/Controller.js';
|
|
6
|
-
import type { State, SetAction, OptimisticAction } from '../../types.js';
|
|
3
|
+
import type { State, SetAction } from '../../types.js';
|
|
7
4
|
|
|
8
|
-
export function setReducer(
|
|
9
|
-
state: State<unknown>,
|
|
10
|
-
action: OptimisticAction | SetAction,
|
|
11
|
-
controller: Controller,
|
|
12
|
-
) {
|
|
13
|
-
if (action.error) {
|
|
14
|
-
return reduceError(state, action, action.payload);
|
|
15
|
-
}
|
|
5
|
+
export function setReducer(state: State<unknown>, action: SetAction) {
|
|
16
6
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// this should never happen
|
|
21
|
-
if (!action.endpoint.getOptimisticResponse) return state;
|
|
22
|
-
try {
|
|
23
|
-
// compute optimistic response based on current state
|
|
24
|
-
payload = action.endpoint.getOptimisticResponse.call(
|
|
25
|
-
action.endpoint,
|
|
26
|
-
controller.snapshot(state, action.meta.fetchedAt),
|
|
27
|
-
...action.meta.args,
|
|
28
|
-
);
|
|
29
|
-
} catch (e: any) {
|
|
30
|
-
// AbortOptimistic means 'do nothing', otherwise we count the exception as endpoint failure
|
|
31
|
-
if (e.constructor === AbortOptimistic) {
|
|
32
|
-
return state;
|
|
33
|
-
}
|
|
34
|
-
throw e;
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
payload = action.payload;
|
|
38
|
-
}
|
|
39
|
-
const { result, entities, indexes, entityMeta } = normalize(
|
|
40
|
-
payload,
|
|
41
|
-
action.endpoint.schema,
|
|
7
|
+
const { entities, indexes, entityMeta } = normalize(
|
|
8
|
+
action.value,
|
|
9
|
+
action.schema,
|
|
42
10
|
action.meta.args as any,
|
|
43
11
|
state.entities,
|
|
44
12
|
state.indexes,
|
|
45
13
|
state.entityMeta,
|
|
46
14
|
action.meta,
|
|
47
15
|
);
|
|
48
|
-
const endpoints = {
|
|
49
|
-
...state.endpoints,
|
|
50
|
-
[action.meta.key]: result,
|
|
51
|
-
};
|
|
52
|
-
try {
|
|
53
|
-
if (action.endpoint.update) {
|
|
54
|
-
const updaters = action.endpoint.update(result, ...action.meta.args);
|
|
55
|
-
Object.keys(updaters).forEach(key => {
|
|
56
|
-
endpoints[key] = updaters[key](endpoints[key]);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
// no reason to completely fail because of user-code error
|
|
60
|
-
// integrity of this state update is still guaranteed
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.error(
|
|
63
|
-
`The following error occured during Endpoint.update() for ${action.meta.key}`,
|
|
64
|
-
);
|
|
65
|
-
console.error(error);
|
|
66
|
-
}
|
|
67
16
|
return {
|
|
68
17
|
entities,
|
|
69
18
|
indexes,
|
|
70
|
-
endpoints,
|
|
19
|
+
endpoints: state.endpoints,
|
|
71
20
|
entityMeta,
|
|
72
|
-
meta:
|
|
73
|
-
|
|
74
|
-
[action.meta.key]: {
|
|
75
|
-
date: action.meta.date,
|
|
76
|
-
expiresAt: action.meta.expiresAt,
|
|
77
|
-
prevExpiresAt: state.meta[action.meta.key]?.expiresAt,
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
optimistic: filterOptimistic(state, action),
|
|
21
|
+
meta: state.meta,
|
|
22
|
+
optimistic: state.optimistic,
|
|
81
23
|
lastReset: state.lastReset,
|
|
82
24
|
};
|
|
83
25
|
// reducer must update the state, so in case of processing errors we simply compute the endpoints inline
|
|
84
26
|
} catch (error: any) {
|
|
85
|
-
if (typeof error === 'object') {
|
|
86
|
-
error.message = `Error processing ${
|
|
87
|
-
action.meta.key
|
|
88
|
-
}\n\nFull Schema: ${JSON.stringify(
|
|
89
|
-
action.endpoint.schema,
|
|
90
|
-
undefined,
|
|
91
|
-
2,
|
|
92
|
-
)}\n\nError:\n${error.message}`;
|
|
93
|
-
if ('payload' in action) error.payload = action.payload;
|
|
94
|
-
error.status = 400;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
27
|
// this is not always bubbled up, so let's double sure this doesn't fail silently
|
|
98
28
|
/* istanbul ignore else */
|
|
99
29
|
if (process.env.NODE_ENV !== 'production') {
|
|
100
30
|
console.error(error);
|
|
101
31
|
}
|
|
102
|
-
return
|
|
32
|
+
return state;
|
|
103
33
|
}
|
|
104
34
|
}
|
|
105
|
-
|
|
106
|
-
function reduceError(
|
|
107
|
-
state: State<unknown>,
|
|
108
|
-
action: SetAction | OptimisticAction,
|
|
109
|
-
error: any,
|
|
110
|
-
): State<unknown> {
|
|
111
|
-
if (error.name === 'AbortError') {
|
|
112
|
-
// In case we abort simply undo the optimistic update and act like no fetch even occured
|
|
113
|
-
// We still want those watching promises from fetch directly to observed the abort, but we don't want to
|
|
114
|
-
// Trigger errors in this case. This means theoretically improperly built abortes useResource() could suspend forever.
|
|
115
|
-
return {
|
|
116
|
-
...state,
|
|
117
|
-
optimistic: filterOptimistic(state, action),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
...state,
|
|
122
|
-
meta: {
|
|
123
|
-
...state.meta,
|
|
124
|
-
[action.meta.key]: {
|
|
125
|
-
date: action.meta.date,
|
|
126
|
-
error,
|
|
127
|
-
expiresAt: action.meta.expiresAt,
|
|
128
|
-
errorPolicy: action.endpoint.errorPolicy?.(error),
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
optimistic: filterOptimistic(state, action),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
/** Filter all requests with same serialization that did not start after the resolving request */
|
|
135
|
-
function filterOptimistic(
|
|
136
|
-
state: State<unknown>,
|
|
137
|
-
resolvingAction: SetAction | OptimisticAction,
|
|
138
|
-
) {
|
|
139
|
-
return state.optimistic.filter(
|
|
140
|
-
optimisticAction =>
|
|
141
|
-
optimisticAction.meta.key !== resolvingAction.meta.key ||
|
|
142
|
-
(optimisticAction.type === OPTIMISTIC_TYPE ?
|
|
143
|
-
optimisticAction.meta.fetchedAt !== resolvingAction.meta.fetchedAt
|
|
144
|
-
: optimisticAction.meta.date > resolvingAction.meta.date),
|
|
145
|
-
);
|
|
146
|
-
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { normalize } from '@data-client/normalizr';
|
|
2
|
+
|
|
3
|
+
import { OPTIMISTIC_TYPE } from '../../actionTypes.js';
|
|
4
|
+
import AbortOptimistic from '../../controller/AbortOptimistic.js';
|
|
5
|
+
import type Controller from '../../controller/Controller.js';
|
|
6
|
+
import type {
|
|
7
|
+
State,
|
|
8
|
+
SetResponseAction,
|
|
9
|
+
OptimisticAction,
|
|
10
|
+
} from '../../types.js';
|
|
11
|
+
|
|
12
|
+
export function setResponseReducer(
|
|
13
|
+
state: State<unknown>,
|
|
14
|
+
action: OptimisticAction | SetResponseAction,
|
|
15
|
+
controller: Controller,
|
|
16
|
+
) {
|
|
17
|
+
if (action.error) {
|
|
18
|
+
return reduceError(state, action, action.payload);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
let payload: any;
|
|
22
|
+
// for true set's payload is contained in action
|
|
23
|
+
if (action.type === OPTIMISTIC_TYPE) {
|
|
24
|
+
// this should never happen
|
|
25
|
+
/* istanbul ignore if */
|
|
26
|
+
if (!action.endpoint.getOptimisticResponse) return state;
|
|
27
|
+
try {
|
|
28
|
+
// compute optimistic response based on current state
|
|
29
|
+
payload = action.endpoint.getOptimisticResponse.call(
|
|
30
|
+
action.endpoint,
|
|
31
|
+
controller.snapshot(state, action.meta.fetchedAt),
|
|
32
|
+
...action.meta.args,
|
|
33
|
+
);
|
|
34
|
+
} catch (e: any) {
|
|
35
|
+
// AbortOptimistic means 'do nothing', otherwise we count the exception as endpoint failure
|
|
36
|
+
if (e.constructor === AbortOptimistic) {
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
payload = action.payload;
|
|
43
|
+
}
|
|
44
|
+
const { result, entities, indexes, entityMeta } = normalize(
|
|
45
|
+
payload,
|
|
46
|
+
action.endpoint.schema,
|
|
47
|
+
action.meta.args as any,
|
|
48
|
+
state.entities,
|
|
49
|
+
state.indexes,
|
|
50
|
+
state.entityMeta,
|
|
51
|
+
action.meta,
|
|
52
|
+
);
|
|
53
|
+
const endpoints: Record<string, unknown> = {
|
|
54
|
+
...state.endpoints,
|
|
55
|
+
[action.meta.key]: result,
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
if (action.endpoint.update) {
|
|
59
|
+
const updaters = action.endpoint.update(result, ...action.meta.args);
|
|
60
|
+
Object.keys(updaters).forEach(key => {
|
|
61
|
+
endpoints[key] = updaters[key](endpoints[key]);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// no reason to completely fail because of user-code error
|
|
65
|
+
// integrity of this state update is still guaranteed
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(
|
|
68
|
+
`The following error occured during Endpoint.update() for ${action.meta.key}`,
|
|
69
|
+
);
|
|
70
|
+
console.error(error);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
entities,
|
|
74
|
+
indexes,
|
|
75
|
+
endpoints,
|
|
76
|
+
entityMeta,
|
|
77
|
+
meta: {
|
|
78
|
+
...state.meta,
|
|
79
|
+
[action.meta.key]: {
|
|
80
|
+
date: action.meta.date,
|
|
81
|
+
expiresAt: action.meta.expiresAt,
|
|
82
|
+
prevExpiresAt: state.meta[action.meta.key]?.expiresAt,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
optimistic: filterOptimistic(state, action),
|
|
86
|
+
lastReset: state.lastReset,
|
|
87
|
+
};
|
|
88
|
+
// reducer must update the state, so in case of processing errors we simply compute the endpoints inline
|
|
89
|
+
} catch (error: any) {
|
|
90
|
+
if (typeof error === 'object') {
|
|
91
|
+
error.message = `Error processing ${
|
|
92
|
+
action.meta.key
|
|
93
|
+
}\n\nFull Schema: ${JSON.stringify(
|
|
94
|
+
action.endpoint.schema,
|
|
95
|
+
undefined,
|
|
96
|
+
2,
|
|
97
|
+
)}\n\nError:\n${error.message}`;
|
|
98
|
+
if ('payload' in action) error.payload = action.payload;
|
|
99
|
+
error.status = 400;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// this is not always bubbled up, so let's double sure this doesn't fail silently
|
|
103
|
+
/* istanbul ignore else */
|
|
104
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
105
|
+
console.error(error);
|
|
106
|
+
}
|
|
107
|
+
return reduceError(state, action, error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function reduceError(
|
|
112
|
+
state: State<unknown>,
|
|
113
|
+
action: SetResponseAction | OptimisticAction,
|
|
114
|
+
error: any,
|
|
115
|
+
): State<unknown> {
|
|
116
|
+
if (error.name === 'AbortError') {
|
|
117
|
+
// In case we abort simply undo the optimistic update and act like no fetch even occured
|
|
118
|
+
// We still want those watching promises from fetch directly to observed the abort, but we don't want to
|
|
119
|
+
// Trigger errors in this case. This means theoretically improperly built abortes useResource() could suspend forever.
|
|
120
|
+
return {
|
|
121
|
+
...state,
|
|
122
|
+
optimistic: filterOptimistic(state, action),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...state,
|
|
127
|
+
meta: {
|
|
128
|
+
...state.meta,
|
|
129
|
+
[action.meta.key]: {
|
|
130
|
+
date: action.meta.date,
|
|
131
|
+
error,
|
|
132
|
+
expiresAt: action.meta.expiresAt,
|
|
133
|
+
errorPolicy: action.endpoint.errorPolicy?.(error),
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
optimistic: filterOptimistic(state, action),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Filter all requests with same serialization that did not start after the resolving request */
|
|
140
|
+
function filterOptimistic(
|
|
141
|
+
state: State<unknown>,
|
|
142
|
+
resolvingAction: SetResponseAction | OptimisticAction,
|
|
143
|
+
) {
|
|
144
|
+
return state.optimistic.filter(
|
|
145
|
+
optimisticAction =>
|
|
146
|
+
optimisticAction.meta.key !== resolvingAction.meta.key ||
|
|
147
|
+
(optimisticAction.type === OPTIMISTIC_TYPE ?
|
|
148
|
+
optimisticAction.meta.fetchedAt !== resolvingAction.meta.fetchedAt
|
|
149
|
+
: optimisticAction.meta.date > resolvingAction.meta.date),
|
|
150
|
+
);
|
|
151
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -5,14 +5,15 @@ import type {
|
|
|
5
5
|
} from '@data-client/normalizr';
|
|
6
6
|
import type { ErrorTypes } from '@data-client/normalizr';
|
|
7
7
|
|
|
8
|
-
import type {
|
|
9
|
-
|
|
8
|
+
import type {
|
|
9
|
+
ActionTypes,
|
|
10
|
+
SetResponseAction,
|
|
11
|
+
OptimisticAction,
|
|
12
|
+
} from './actions.js';
|
|
10
13
|
import type { Dispatch, Middleware, MiddlewareAPI } from './middlewareTypes.js';
|
|
11
14
|
|
|
12
15
|
export type { AbstractInstanceType, UpdateFunction };
|
|
13
16
|
|
|
14
|
-
export type SetTypes = typeof SET_TYPE;
|
|
15
|
-
|
|
16
17
|
export type PK = string;
|
|
17
18
|
|
|
18
19
|
export interface State<T> {
|
|
@@ -42,7 +43,7 @@ export interface State<T> {
|
|
|
42
43
|
};
|
|
43
44
|
};
|
|
44
45
|
};
|
|
45
|
-
readonly optimistic: (
|
|
46
|
+
readonly optimistic: (SetResponseAction | OptimisticAction)[];
|
|
46
47
|
readonly lastReset: number;
|
|
47
48
|
}
|
|
48
49
|
|
package/ts3.4/actionTypes.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const FETCH_TYPE: "rdc/fetch";
|
|
2
2
|
export declare const SET_TYPE: "rdc/set";
|
|
3
|
+
export declare const SET_RESPONSE_TYPE: "rdc/setresponse";
|
|
3
4
|
export declare const OPTIMISTIC_TYPE: "rdc/optimistic";
|
|
4
5
|
export declare const RESET_TYPE: "rdc/reset";
|
|
5
6
|
export declare const SUBSCRIBE_TYPE: "rdc/subscribe";
|