@data-client/core 0.14.24 → 0.15.0-beta-20251022142546-a457d1596871fb28f1a91f2531cc259db4d55a9c
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/CHANGELOG.md +1174 -0
- package/README.md +2 -2
- package/dist/index.js +100 -97
- package/dist/index.umd.min.js +1 -1
- package/legacy/controller/Controller.js +31 -46
- package/legacy/index.js +1 -1
- package/legacy/manager/NetworkManager.js +44 -33
- package/legacy/state/GCPolicy.js +20 -12
- package/legacy/state/reducer/createReducer.js +4 -4
- package/legacy/state/reducer/setReducer.js +3 -3
- package/legacy/state/reducer/setResponseReducer.js +4 -4
- package/legacy/types.js +1 -1
- package/lib/controller/Controller.d.ts +3 -3
- package/lib/controller/Controller.d.ts.map +1 -1
- package/lib/controller/Controller.js +31 -46
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/manager/NetworkManager.d.ts +7 -12
- package/lib/manager/NetworkManager.d.ts.map +1 -1
- package/lib/manager/NetworkManager.js +44 -34
- package/lib/state/GCPolicy.d.ts.map +1 -1
- package/lib/state/GCPolicy.js +20 -12
- package/lib/state/reducer/createReducer.js +4 -4
- package/lib/state/reducer/expireReducer.d.ts +1 -1
- package/lib/state/reducer/invalidateReducer.d.ts +1 -1
- package/lib/state/reducer/setReducer.d.ts +1 -1
- package/lib/state/reducer/setReducer.js +3 -3
- package/lib/state/reducer/setResponseReducer.d.ts +1 -1
- package/lib/state/reducer/setResponseReducer.js +4 -4
- package/lib/types.d.ts +1 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +1 -1
- package/package.json +6 -6
- package/src/controller/Controller.ts +38 -80
- package/src/controller/__tests__/Controller.ts +2 -2
- package/src/controller/__tests__/__snapshots__/get.ts.snap +34 -8
- package/src/controller/__tests__/__snapshots__/getResponse.ts.snap +1 -1
- package/src/controller/__tests__/get.ts +163 -22
- package/src/controller/__tests__/getResponse.ts +1 -1
- package/src/index.ts +4 -0
- package/src/manager/NetworkManager.ts +46 -32
- package/src/manager/__tests__/__snapshots__/pollingSubscription.ts.snap +1 -1
- package/src/manager/__tests__/pollingSubscription.ts +9 -5
- package/src/state/GCPolicy.ts +12 -10
- package/src/state/__tests__/GCPolicy.test.ts +6 -6
- package/src/state/__tests__/__snapshots__/reducer.ts.snap +3 -3
- package/src/state/__tests__/reducer.ts +17 -9
- package/src/state/reducer/createReducer.ts +2 -2
- package/src/state/reducer/setReducer.ts +2 -2
- package/src/state/reducer/setResponseReducer.ts +3 -3
- package/src/types.ts +1 -1
- package/ts3.4/controller/Controller.d.ts +3 -3
- package/ts3.4/index.d.ts +2 -1
- package/ts3.4/manager/NetworkManager.d.ts +7 -12
- 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 +1 -1
- package/ts3.4/state/reducer/setResponseReducer.d.ts +1 -1
- package/ts3.4/types.d.ts +1 -1
|
@@ -5,7 +5,6 @@ import type {
|
|
|
5
5
|
FetchAction,
|
|
6
6
|
Manager,
|
|
7
7
|
ActionTypes,
|
|
8
|
-
MiddlewareAPI,
|
|
9
8
|
Middleware,
|
|
10
9
|
SetResponseAction,
|
|
11
10
|
} from '../types.js';
|
|
@@ -18,6 +17,13 @@ export class ResetError extends Error {
|
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
|
|
20
|
+
export interface FetchingMeta {
|
|
21
|
+
promise: Promise<any>;
|
|
22
|
+
resolve: (value?: any) => void;
|
|
23
|
+
reject: (value?: any) => void;
|
|
24
|
+
fetchedAt: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
/** Handles all async network dispatches
|
|
22
28
|
*
|
|
23
29
|
* Dedupes concurrent requests by keeping track of all fetches in flight
|
|
@@ -28,10 +34,7 @@ export class ResetError extends Error {
|
|
|
28
34
|
* @see https://dataclient.io/docs/api/NetworkManager
|
|
29
35
|
*/
|
|
30
36
|
export default class NetworkManager implements Manager {
|
|
31
|
-
protected
|
|
32
|
-
protected resolvers: { [k: string]: (value?: any) => void } = {};
|
|
33
|
-
protected rejectors: { [k: string]: (value?: any) => void } = {};
|
|
34
|
-
protected fetchedAt: { [k: string]: number } = {};
|
|
37
|
+
protected fetching: Map<string, FetchingMeta> = new Map();
|
|
35
38
|
declare readonly dataExpiryLength: number;
|
|
36
39
|
declare readonly errorExpiryLength: number;
|
|
37
40
|
protected controller: Controller = new Controller();
|
|
@@ -61,7 +64,7 @@ export default class NetworkManager implements Manager {
|
|
|
61
64
|
case SET_RESPONSE:
|
|
62
65
|
// only set after new state is computed
|
|
63
66
|
return next(action).then(() => {
|
|
64
|
-
if (action.key
|
|
67
|
+
if (this.fetching.has(action.key)) {
|
|
65
68
|
// Note: meta *must* be set by reducer so this should be safe
|
|
66
69
|
const error = controller.getState().meta[action.key]?.error;
|
|
67
70
|
// processing errors result in state meta having error, so we should reject the promise
|
|
@@ -80,14 +83,16 @@ export default class NetworkManager implements Manager {
|
|
|
80
83
|
}
|
|
81
84
|
});
|
|
82
85
|
case RESET: {
|
|
83
|
-
|
|
86
|
+
// take snapshot of rejectors at this point in time
|
|
87
|
+
// we must use Array.from since iteration does not freeze state at this point in time
|
|
88
|
+
const fetches = Array.from(this.fetching.values());
|
|
84
89
|
|
|
85
90
|
this.clearAll();
|
|
86
91
|
return next(action).then(() => {
|
|
87
92
|
// there could be external listeners to the promise
|
|
88
93
|
// this must happen after commit so our own rejector knows not to dispatch an error based on this
|
|
89
|
-
for (const
|
|
90
|
-
|
|
94
|
+
for (const { reject } of fetches) {
|
|
95
|
+
reject(new ResetError());
|
|
91
96
|
}
|
|
92
97
|
});
|
|
93
98
|
}
|
|
@@ -112,28 +117,29 @@ export default class NetworkManager implements Manager {
|
|
|
112
117
|
/** Used by DevtoolsManager to determine whether to log an action */
|
|
113
118
|
skipLogging(action: ActionTypes) {
|
|
114
119
|
/* istanbul ignore next */
|
|
115
|
-
return action.type === FETCH && action.key
|
|
120
|
+
return action.type === FETCH && this.fetching.has(action.key);
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
allSettled() {
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
if (this.fetching.size)
|
|
125
|
+
return Promise.allSettled(
|
|
126
|
+
this.fetching.values().map(({ promise }) => promise),
|
|
127
|
+
);
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
/** Clear all promise state */
|
|
124
131
|
protected clearAll() {
|
|
125
|
-
for (const k
|
|
132
|
+
for (const k of this.fetching.keys()) {
|
|
126
133
|
this.clear(k);
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
/** Clear promise state for a given key */
|
|
131
138
|
protected clear(key: string) {
|
|
132
|
-
this.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
delete this.fetchedAt[key];
|
|
139
|
+
if (this.fetching.has(key)) {
|
|
140
|
+
(this.fetching.get(key) as FetchingMeta).promise.catch(() => {});
|
|
141
|
+
this.fetching.delete(key);
|
|
142
|
+
}
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
protected getLastReset() {
|
|
@@ -226,14 +232,14 @@ export default class NetworkManager implements Manager {
|
|
|
226
232
|
*/
|
|
227
233
|
protected handleSet(action: SetResponseAction) {
|
|
228
234
|
// this can still turn out to be untrue since this is async
|
|
229
|
-
if (action.key
|
|
230
|
-
|
|
235
|
+
if (this.fetching.has(action.key)) {
|
|
236
|
+
const { reject, resolve } = this.fetching.get(action.key) as FetchingMeta;
|
|
231
237
|
if (action.error) {
|
|
232
|
-
|
|
238
|
+
reject(action.response);
|
|
233
239
|
} else {
|
|
234
|
-
|
|
240
|
+
resolve(action.response);
|
|
235
241
|
}
|
|
236
|
-
|
|
242
|
+
|
|
237
243
|
// since we're resolved we no longer need to keep track of this promise
|
|
238
244
|
this.clear(action.key);
|
|
239
245
|
}
|
|
@@ -253,19 +259,18 @@ export default class NetworkManager implements Manager {
|
|
|
253
259
|
key: string,
|
|
254
260
|
fetch: () => Promise<any>,
|
|
255
261
|
fetchedAt: number,
|
|
256
|
-
) {
|
|
262
|
+
): Promise<any> {
|
|
257
263
|
const lastReset = this.getLastReset();
|
|
264
|
+
let fetchMeta = this.fetching.get(key);
|
|
265
|
+
|
|
258
266
|
// we're already fetching so reuse the promise
|
|
259
267
|
// fetches after reset do not count
|
|
260
|
-
if (
|
|
261
|
-
return
|
|
268
|
+
if (fetchMeta && fetchMeta.fetchedAt > lastReset) {
|
|
269
|
+
return fetchMeta.promise;
|
|
262
270
|
}
|
|
263
271
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
this.rejectors[key] = reject;
|
|
267
|
-
});
|
|
268
|
-
this.fetchedAt[key] = fetchedAt;
|
|
272
|
+
fetchMeta = newFetchMeta(fetchedAt);
|
|
273
|
+
this.fetching.set(key, fetchMeta);
|
|
269
274
|
|
|
270
275
|
this.idleCallback(
|
|
271
276
|
() => {
|
|
@@ -277,7 +282,7 @@ export default class NetworkManager implements Manager {
|
|
|
277
282
|
{ timeout: 500 },
|
|
278
283
|
);
|
|
279
284
|
|
|
280
|
-
return
|
|
285
|
+
return fetchMeta.promise;
|
|
281
286
|
}
|
|
282
287
|
|
|
283
288
|
/** Calls the callback when client is not 'busy' with high priority interaction tasks
|
|
@@ -291,3 +296,12 @@ export default class NetworkManager implements Manager {
|
|
|
291
296
|
callback();
|
|
292
297
|
}
|
|
293
298
|
}
|
|
299
|
+
|
|
300
|
+
function newFetchMeta(fetchedAt: number): FetchingMeta {
|
|
301
|
+
const fetchMeta = { fetchedAt } as FetchingMeta;
|
|
302
|
+
fetchMeta.promise = new Promise((resolve, reject) => {
|
|
303
|
+
fetchMeta.resolve = resolve;
|
|
304
|
+
fetchMeta.reject = reject;
|
|
305
|
+
});
|
|
306
|
+
return fetchMeta;
|
|
307
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Endpoint } from '@data-client/endpoint';
|
|
2
|
-
import { Article
|
|
2
|
+
import { Article } from '__tests__/new';
|
|
3
3
|
|
|
4
4
|
import { createSubscription } from '../../controller/actions/createSubscription';
|
|
5
5
|
import Controller from '../../controller/Controller';
|
|
@@ -284,13 +284,13 @@ describe('PollingSubscription', () => {
|
|
|
284
284
|
});
|
|
285
285
|
|
|
286
286
|
describe('cleanup()', () => {
|
|
287
|
-
let warnSpy: jest.
|
|
287
|
+
let warnSpy: jest.Spied<typeof console.warn>;
|
|
288
288
|
afterEach(() => {
|
|
289
289
|
warnSpy.mockRestore();
|
|
290
290
|
});
|
|
291
|
-
beforeEach(() =>
|
|
292
|
-
(warnSpy = jest.spyOn(console, 'warn')).mockImplementation(() => {})
|
|
293
|
-
);
|
|
291
|
+
beforeEach(() => {
|
|
292
|
+
(warnSpy = jest.spyOn(console, 'warn')).mockImplementation(() => {});
|
|
293
|
+
});
|
|
294
294
|
|
|
295
295
|
it('should stop all timers', () => {
|
|
296
296
|
dispatch.mockClear();
|
|
@@ -394,6 +394,8 @@ describe('PollingSubscription', () => {
|
|
|
394
394
|
});
|
|
395
395
|
|
|
396
396
|
it('should not run when timeoutId is deleted after coming online', () => {
|
|
397
|
+
// Silence console.warn for this test
|
|
398
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
397
399
|
const listener = new MockConnectionListener(false);
|
|
398
400
|
const { dispatch, pollingSubscription } = createMocks(listener);
|
|
399
401
|
expect(dispatch.mock.calls.length).toBe(0);
|
|
@@ -406,6 +408,8 @@ describe('PollingSubscription', () => {
|
|
|
406
408
|
expect(dispatch.mock.calls.length).toBe(0);
|
|
407
409
|
expect(listener.offlineHandlers.length).toBe(1);
|
|
408
410
|
expect(listener.onlineHandlers.length).toBe(0);
|
|
411
|
+
expect(warnSpy.mock.calls.length).toBe(1);
|
|
412
|
+
warnSpy.mockRestore();
|
|
409
413
|
});
|
|
410
414
|
|
|
411
415
|
it('should stop dispatching when offline again', () => {
|
package/src/state/GCPolicy.ts
CHANGED
|
@@ -46,11 +46,12 @@ export class GCPolicy implements GCInterface {
|
|
|
46
46
|
if (key)
|
|
47
47
|
this.endpointCount.set(key, (this.endpointCount.get(key) ?? 0) + 1);
|
|
48
48
|
paths.forEach(path => {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const { key, pk } = path;
|
|
50
|
+
if (!this.entityCount.has(key)) {
|
|
51
|
+
this.entityCount.set(key, new Map<string, number>());
|
|
51
52
|
}
|
|
52
|
-
const instanceCount = this.entityCount.get(
|
|
53
|
-
instanceCount.set(
|
|
53
|
+
const instanceCount = this.entityCount.get(key)!;
|
|
54
|
+
instanceCount.set(pk, (instanceCount.get(pk) ?? 0) + 1);
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
// decrement
|
|
@@ -68,18 +69,19 @@ export class GCPolicy implements GCInterface {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
paths.forEach(path => {
|
|
71
|
-
|
|
72
|
+
const { key, pk } = path;
|
|
73
|
+
if (!this.entityCount.has(key)) {
|
|
72
74
|
return;
|
|
73
75
|
}
|
|
74
|
-
const instanceCount = this.entityCount.get(
|
|
75
|
-
const entityCount = instanceCount.get(
|
|
76
|
+
const instanceCount = this.entityCount.get(key)!;
|
|
77
|
+
const entityCount = instanceCount.get(pk)!;
|
|
76
78
|
if (entityCount !== undefined) {
|
|
77
79
|
if (entityCount <= 1) {
|
|
78
|
-
instanceCount.delete(
|
|
80
|
+
instanceCount.delete(pk);
|
|
79
81
|
// queue for cleanup
|
|
80
82
|
this.entitiesQ.push(path);
|
|
81
83
|
} else {
|
|
82
|
-
instanceCount.set(
|
|
84
|
+
instanceCount.set(pk, entityCount - 1);
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
});
|
|
@@ -133,7 +135,7 @@ export class GCPolicy implements GCInterface {
|
|
|
133
135
|
if (
|
|
134
136
|
!this.entityCount.get(path.key)?.has(path.pk) &&
|
|
135
137
|
this.expiresAt(
|
|
136
|
-
state.
|
|
138
|
+
state.entitiesMeta[path.key]?.[path.pk] ?? {
|
|
137
139
|
fetchedAt: 0,
|
|
138
140
|
date: 0,
|
|
139
141
|
expiresAt: 0,
|
|
@@ -48,7 +48,7 @@ describe('GCPolicy', () => {
|
|
|
48
48
|
expiresAt: 0,
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
|
-
|
|
51
|
+
entitiesMeta: {
|
|
52
52
|
testEntity: {
|
|
53
53
|
'1': {
|
|
54
54
|
date: 0,
|
|
@@ -90,7 +90,7 @@ describe('GCPolicy', () => {
|
|
|
90
90
|
expiresAt: 0,
|
|
91
91
|
},
|
|
92
92
|
},
|
|
93
|
-
|
|
93
|
+
entitiesMeta: {
|
|
94
94
|
testEntity: {
|
|
95
95
|
'1': {
|
|
96
96
|
date: 0,
|
|
@@ -127,7 +127,7 @@ describe('GCPolicy', () => {
|
|
|
127
127
|
const paths: EntityPath[] = [{ key: 'testEntity', pk: '1' }];
|
|
128
128
|
const state = {
|
|
129
129
|
meta: {},
|
|
130
|
-
|
|
130
|
+
entitiesMeta: {},
|
|
131
131
|
};
|
|
132
132
|
(controller.getState as jest.Mock).mockReturnValue(state);
|
|
133
133
|
|
|
@@ -163,7 +163,7 @@ describe('GCPolicy', () => {
|
|
|
163
163
|
expiresAt: futureTime,
|
|
164
164
|
},
|
|
165
165
|
},
|
|
166
|
-
|
|
166
|
+
entitiesMeta: {
|
|
167
167
|
testEntity: {
|
|
168
168
|
'1': {
|
|
169
169
|
date: futureTime - 100,
|
|
@@ -196,7 +196,7 @@ describe('GCPolicy', () => {
|
|
|
196
196
|
expiresAt: 0,
|
|
197
197
|
},
|
|
198
198
|
},
|
|
199
|
-
|
|
199
|
+
entitiesMeta: {
|
|
200
200
|
testEntity: {
|
|
201
201
|
'1': {
|
|
202
202
|
date: 0,
|
|
@@ -233,7 +233,7 @@ describe('GCPolicy', () => {
|
|
|
233
233
|
expiresAt: futureTime,
|
|
234
234
|
},
|
|
235
235
|
},
|
|
236
|
-
|
|
236
|
+
entitiesMeta: {
|
|
237
237
|
testEntity: {
|
|
238
238
|
'1': {
|
|
239
239
|
date: futureTime - 100,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://
|
|
1
|
+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
2
|
|
|
3
3
|
exports[`reducer should set error in meta for "set" 1`] = `
|
|
4
4
|
{
|
|
5
5
|
"endpoints": {},
|
|
6
6
|
"entities": {},
|
|
7
|
-
"
|
|
7
|
+
"entitiesMeta": {},
|
|
8
8
|
"indexes": {},
|
|
9
9
|
"lastReset": 0,
|
|
10
10
|
"meta": {
|
|
@@ -34,7 +34,7 @@ exports[`reducer singles should update state correctly 1`] = `
|
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
},
|
|
37
|
-
"
|
|
37
|
+
"entitiesMeta": {
|
|
38
38
|
"Article": {
|
|
39
39
|
"20": {
|
|
40
40
|
"date": 5000000000,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Entity } from '@data-client/endpoint';
|
|
2
|
+
import { INVALID } from '@data-client/normalizr';
|
|
2
3
|
import { ArticleResource, Article, PaginatedArticle } from '__tests__/new';
|
|
3
4
|
|
|
4
5
|
import { Controller } from '../..';
|
|
@@ -84,10 +85,10 @@ describe('reducer', () => {
|
|
|
84
85
|
expect(nextEntity.content).not.toBe(undefined);
|
|
85
86
|
|
|
86
87
|
expect(
|
|
87
|
-
nextState.
|
|
88
|
+
nextState.entitiesMeta[Article.key][`${Article.pk(action.response)}`],
|
|
88
89
|
).toBeDefined();
|
|
89
90
|
expect(
|
|
90
|
-
nextState.
|
|
91
|
+
nextState.entitiesMeta[Article.key][`${Article.pk(action.response)}`]
|
|
91
92
|
.date,
|
|
92
93
|
).toBe(action.meta.date);
|
|
93
94
|
});
|
|
@@ -102,7 +103,7 @@ describe('reducer', () => {
|
|
|
102
103
|
},
|
|
103
104
|
};
|
|
104
105
|
const getMeta = (state: any): { expiresAt: number } =>
|
|
105
|
-
state.
|
|
106
|
+
state.entitiesMeta[Article.key][`${Article.pk(action.response)}`];
|
|
106
107
|
const prevMeta = getMeta(newState);
|
|
107
108
|
expect(prevMeta).toBeDefined();
|
|
108
109
|
const nextState = reducer(newState, localAction);
|
|
@@ -123,7 +124,7 @@ describe('reducer', () => {
|
|
|
123
124
|
},
|
|
124
125
|
};
|
|
125
126
|
const getMeta = (state: any): { date: number } =>
|
|
126
|
-
state.
|
|
127
|
+
state.entitiesMeta[Article.key][`${Article.pk(action.response)}`];
|
|
127
128
|
const getEntity = (state: any): Article =>
|
|
128
129
|
state.entities[Article.key][`${Article.pk(action.response)}`];
|
|
129
130
|
const prevEntity = getEntity(newState);
|
|
@@ -182,7 +183,9 @@ describe('reducer', () => {
|
|
|
182
183
|
},
|
|
183
184
|
};
|
|
184
185
|
const getMeta = (state: any): { date: number; expiresAt: number } =>
|
|
185
|
-
state.
|
|
186
|
+
state.entitiesMeta[ExpiresSoon.key][
|
|
187
|
+
`${ExpiresSoon.pk(action.response)}`
|
|
188
|
+
];
|
|
186
189
|
const getEntity = (state: any): ExpiresSoon =>
|
|
187
190
|
state.entities[ExpiresSoon.key][`${ExpiresSoon.pk(action.response)}`];
|
|
188
191
|
|
|
@@ -259,6 +262,11 @@ describe('reducer', () => {
|
|
|
259
262
|
[id]: { id, counter: 5 },
|
|
260
263
|
},
|
|
261
264
|
},
|
|
265
|
+
entitiesMeta: {
|
|
266
|
+
[Counter.key]: {
|
|
267
|
+
[id]: { date: 0, fetchedAt: 0, expiresAt: 0 },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
262
270
|
};
|
|
263
271
|
const newState = reducer(state, action);
|
|
264
272
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
@@ -630,7 +638,7 @@ describe('reducer', () => {
|
|
|
630
638
|
expect(newState).toBe(iniState);
|
|
631
639
|
});
|
|
632
640
|
describe('RESET', () => {
|
|
633
|
-
let warnspy: jest.
|
|
641
|
+
let warnspy: jest.Spied<any>;
|
|
634
642
|
beforeEach(() => {
|
|
635
643
|
warnspy = jest.spyOn(global.console, 'warn').mockImplementation(() => {});
|
|
636
644
|
});
|
|
@@ -683,7 +691,7 @@ describe('reducer', () => {
|
|
|
683
691
|
},
|
|
684
692
|
'5': undefined,
|
|
685
693
|
},
|
|
686
|
-
|
|
694
|
+
entitiesMeta: {
|
|
687
695
|
[Article.key]: {
|
|
688
696
|
'10': { date: 0, expiresAt: 10000, fetchedAt: 0 },
|
|
689
697
|
'20': { date: 0, expiresAt: 10000, fetchedAt: 0 },
|
|
@@ -721,7 +729,7 @@ describe('reducer', () => {
|
|
|
721
729
|
const newState = reducer(iniState, action);
|
|
722
730
|
expect(newState).toBe(iniState);
|
|
723
731
|
expect(Object.keys(newState.entities[Article.key] ?? {}).length).toBe(2);
|
|
724
|
-
expect(Object.keys(newState.
|
|
732
|
+
expect(Object.keys(newState.entitiesMeta[Article.key] ?? {}).length).toBe(
|
|
725
733
|
2,
|
|
726
734
|
);
|
|
727
735
|
expect(Object.keys(newState.endpoints).length).toBe(0);
|
|
@@ -28,7 +28,7 @@ export default function createReducer(controller: Controller): ReducerType {
|
|
|
28
28
|
// inline deletes are fine as these should have 0 refcounts
|
|
29
29
|
action.entities.forEach(({ key, pk }) => {
|
|
30
30
|
delete (state as any).entities[key]?.[pk];
|
|
31
|
-
delete (state as any).
|
|
31
|
+
delete (state as any).entitiesMeta[key]?.[pk];
|
|
32
32
|
});
|
|
33
33
|
action.endpoints.forEach(fetchKey => {
|
|
34
34
|
delete (state as any).endpoints[fetchKey];
|
|
@@ -69,7 +69,7 @@ export const initialState: State<unknown> = {
|
|
|
69
69
|
endpoints: {},
|
|
70
70
|
indexes: {},
|
|
71
71
|
meta: {},
|
|
72
|
-
|
|
72
|
+
entitiesMeta: {},
|
|
73
73
|
optimistic: [],
|
|
74
74
|
lastReset: 0,
|
|
75
75
|
};
|
|
@@ -17,7 +17,7 @@ export function setReducer(
|
|
|
17
17
|
value = action.value;
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
|
-
const { entities, indexes,
|
|
20
|
+
const { entities, indexes, entitiesMeta } = normalize(
|
|
21
21
|
action.schema,
|
|
22
22
|
value,
|
|
23
23
|
action.args,
|
|
@@ -29,7 +29,7 @@ export function setReducer(
|
|
|
29
29
|
endpoints: state.endpoints,
|
|
30
30
|
indexes,
|
|
31
31
|
meta: state.meta,
|
|
32
|
-
|
|
32
|
+
entitiesMeta,
|
|
33
33
|
optimistic: state.optimistic,
|
|
34
34
|
lastReset: state.lastReset,
|
|
35
35
|
};
|
|
@@ -41,7 +41,7 @@ export function setResponseReducer(
|
|
|
41
41
|
} else {
|
|
42
42
|
response = action.response;
|
|
43
43
|
}
|
|
44
|
-
const { result, entities, indexes,
|
|
44
|
+
const { result, entities, indexes, entitiesMeta } = normalize(
|
|
45
45
|
action.endpoint.schema,
|
|
46
46
|
response,
|
|
47
47
|
action.args,
|
|
@@ -80,7 +80,7 @@ export function setResponseReducer(
|
|
|
80
80
|
prevExpiresAt: state.meta[action.key]?.expiresAt,
|
|
81
81
|
},
|
|
82
82
|
},
|
|
83
|
-
|
|
83
|
+
entitiesMeta,
|
|
84
84
|
optimistic: filterOptimistic(state, action),
|
|
85
85
|
lastReset: state.lastReset,
|
|
86
86
|
};
|
|
@@ -115,7 +115,7 @@ function reduceError(
|
|
|
115
115
|
if (error.name === 'AbortError') {
|
|
116
116
|
// In case we abort simply undo the optimistic update and act like no fetch even occured
|
|
117
117
|
// We still want those watching promises from fetch directly to observed the abort, but we don't want to
|
|
118
|
-
// Trigger errors in this case. This means theoretically improperly built
|
|
118
|
+
// Trigger errors in this case. This means theoretically improperly built aborts useSuspense() could suspend forever.
|
|
119
119
|
return {
|
|
120
120
|
...state,
|
|
121
121
|
optimistic: filterOptimistic(state, action),
|
package/src/types.ts
CHANGED
|
@@ -254,7 +254,7 @@ export default class Controller<D extends GenericDispatch = DataClientDispatch>
|
|
|
254
254
|
*/
|
|
255
255
|
get<S extends Queryable>(schema: S, ...rest: readonly [
|
|
256
256
|
...SchemaArgs<S>,
|
|
257
|
-
Pick<State<unknown>, 'entities' | '
|
|
257
|
+
Pick<State<unknown>, 'entities' | 'indexes'>
|
|
258
258
|
]): DenormalizeNullable<S> | undefined;
|
|
259
259
|
/**
|
|
260
260
|
* Queries the store for a Querable schema; providing related metadata
|
|
@@ -262,12 +262,12 @@ export default class Controller<D extends GenericDispatch = DataClientDispatch>
|
|
|
262
262
|
*/
|
|
263
263
|
getQueryMeta<S extends Queryable>(schema: S, ...rest: readonly [
|
|
264
264
|
...SchemaArgs<S>,
|
|
265
|
-
Pick<State<unknown>, 'entities' | '
|
|
265
|
+
Pick<State<unknown>, 'entities' | 'indexes'>
|
|
266
266
|
]): {
|
|
267
267
|
data: DenormalizeNullable<S> | undefined;
|
|
268
268
|
countRef: () => () => void;
|
|
269
269
|
};
|
|
270
|
-
private
|
|
270
|
+
private getExpiryStatus;
|
|
271
271
|
}
|
|
272
272
|
export { ErrorTypes };
|
|
273
273
|
declare class Snapshot<T = unknown> implements SnapshotInterface {
|
package/ts3.4/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as __INTERNAL__1 from './internal.js';
|
|
2
2
|
export { __INTERNAL__1 as __INTERNAL__ };
|
|
3
|
-
export { NetworkError, UnknownError, ErrorTypes, Schema, EndpointInterface, EntityInterface, SchemaClass, ResolveType, DenormalizeNullable, Denormalize, Normalize, NormalizeNullable, FetchFunction, EndpointExtraOptions, Queryable, SchemaArgs, NI, } from '@data-client/normalizr';
|
|
3
|
+
export { NetworkError, UnknownError, ErrorTypes, Schema, EndpointInterface, EntityInterface, SchemaClass, ResolveType, DenormalizeNullable, Denormalize, Normalize, NormalizeNullable, FetchFunction, EndpointExtraOptions, Queryable, SchemaArgs, Mergeable, IQueryDelegate, INormalizeDelegate, NI, } from '@data-client/normalizr';
|
|
4
4
|
export { ExpiryStatus } from '@data-client/normalizr';
|
|
5
5
|
export { default as NetworkManager, ResetError, } from './manager/NetworkManager.js';
|
|
6
|
+
export { FetchingMeta } from './manager/NetworkManager.js';
|
|
6
7
|
export * from './state/GCPolicy.js';
|
|
7
8
|
export { default as createReducer, initialState, } from './state/reducer/createReducer.js';
|
|
8
9
|
export { default as applyManager } from './manager/applyManager.js';
|
|
@@ -4,6 +4,12 @@ export declare class ResetError extends Error {
|
|
|
4
4
|
name: string;
|
|
5
5
|
constructor();
|
|
6
6
|
}
|
|
7
|
+
export interface FetchingMeta {
|
|
8
|
+
promise: Promise<any>;
|
|
9
|
+
resolve: (value?: any) => void;
|
|
10
|
+
reject: (value?: any) => void;
|
|
11
|
+
fetchedAt: number;
|
|
12
|
+
}
|
|
7
13
|
/** Handles all async network dispatches
|
|
8
14
|
*
|
|
9
15
|
* Dedupes concurrent requests by keeping track of all fetches in flight
|
|
@@ -14,18 +20,7 @@ export declare class ResetError extends Error {
|
|
|
14
20
|
* @see https://dataclient.io/docs/api/NetworkManager
|
|
15
21
|
*/
|
|
16
22
|
export default class NetworkManager implements Manager {
|
|
17
|
-
protected
|
|
18
|
-
[k: string]: Promise<any>;
|
|
19
|
-
};
|
|
20
|
-
protected resolvers: {
|
|
21
|
-
[k: string]: (value?: any) => void;
|
|
22
|
-
};
|
|
23
|
-
protected rejectors: {
|
|
24
|
-
[k: string]: (value?: any) => void;
|
|
25
|
-
};
|
|
26
|
-
protected fetchedAt: {
|
|
27
|
-
[k: string]: number;
|
|
28
|
-
};
|
|
23
|
+
protected fetching: Map<string, FetchingMeta>;
|
|
29
24
|
readonly dataExpiryLength: number;
|
|
30
25
|
readonly errorExpiryLength: number;
|
|
31
26
|
protected controller: Controller;
|
|
@@ -20,7 +20,7 @@ export declare function expireReducer(state: State<unknown>, action: ExpireAllAc
|
|
|
20
20
|
readonly [key: string]: unknown | import("../../types.js").PK[] | import("../../types.js").PK | undefined;
|
|
21
21
|
};
|
|
22
22
|
indexes: import("packages/normalizr/lib/interface.js").NormalizedIndex;
|
|
23
|
-
|
|
23
|
+
entitiesMeta: {
|
|
24
24
|
readonly [entityKey: string]: {
|
|
25
25
|
readonly [pk: string]: {
|
|
26
26
|
readonly fetchedAt: number;
|
|
@@ -20,7 +20,7 @@ export declare function invalidateReducer(state: State<unknown>, action: Invalid
|
|
|
20
20
|
} | undefined;
|
|
21
21
|
};
|
|
22
22
|
indexes: import("packages/normalizr/lib/interface.js").NormalizedIndex;
|
|
23
|
-
|
|
23
|
+
entitiesMeta: {
|
|
24
24
|
readonly [entityKey: string]: {
|
|
25
25
|
readonly [pk: string]: {
|
|
26
26
|
readonly fetchedAt: number;
|
|
@@ -19,7 +19,7 @@ export declare function setReducer(state: State<unknown>, action: SetAction, con
|
|
|
19
19
|
readonly errorPolicy?: "hard" | "soft" | undefined;
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
|
-
|
|
22
|
+
entitiesMeta: import("packages/normalizr/lib/types.js").EntitiesToMeta<{
|
|
23
23
|
[x: string]: any;
|
|
24
24
|
}>;
|
|
25
25
|
optimistic: (import("../../actions.js").OptimisticAction<import("@data-client/normalizr").EndpointInterface<import("@data-client/normalizr").FetchFunction, import("@data-client/normalizr").Schema | undefined, boolean | undefined> & {
|
|
@@ -22,7 +22,7 @@ export declare function setResponseReducer(state: State<unknown>, action: Optimi
|
|
|
22
22
|
prevExpiresAt: number;
|
|
23
23
|
};
|
|
24
24
|
};
|
|
25
|
-
|
|
25
|
+
entitiesMeta: import("packages/normalizr/lib/types.js").EntitiesToMeta<{
|
|
26
26
|
[x: string]: any;
|
|
27
27
|
}>;
|
|
28
28
|
optimistic: (OptimisticAction<import("@data-client/normalizr").EndpointInterface<import("@data-client/normalizr").FetchFunction, import("@data-client/normalizr").Schema | undefined, boolean | undefined> & {
|
package/ts3.4/types.d.ts
CHANGED