@data-client/core 0.14.18 → 0.14.20
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 +549 -276
- package/dist/index.umd.min.js +1 -1
- package/legacy/actions.js +1 -1
- package/legacy/controller/Controller.js +96 -8
- package/legacy/index.js +3 -4
- package/legacy/manager/DevtoolsManager.js +7 -4
- package/legacy/manager/NetworkManager.js +3 -2
- package/legacy/manager/SubscriptionManager.js +1 -1
- package/legacy/manager/applyManager.js +3 -7
- package/legacy/manager/initManager.js +15 -0
- package/legacy/state/GCPolicy.js +151 -0
- package/legacy/state/reducer/createReducer.js +7 -3
- package/legacy/state/reducer/expireReducer.js +5 -4
- package/legacy/state/reducer/fetchReducer.js +3 -2
- package/legacy/state/reducer/invalidateReducer.js +6 -5
- package/legacy/state/reducer/setResponseReducer.js +10 -7
- package/legacy/types.js +1 -1
- package/lib/actions.d.ts +2 -2
- package/lib/actions.d.ts.map +1 -1
- package/lib/actions.js +1 -1
- package/lib/controller/Controller.d.ts +108 -5
- package/lib/controller/Controller.d.ts.map +1 -1
- package/lib/controller/Controller.js +96 -8
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -4
- package/lib/manager/SubscriptionManager.d.ts.map +1 -1
- package/lib/manager/SubscriptionManager.js +1 -1
- package/lib/manager/applyManager.d.ts.map +1 -1
- package/lib/manager/applyManager.js +3 -7
- package/lib/manager/initManager.d.ts +4 -0
- package/lib/manager/initManager.d.ts.map +1 -0
- package/lib/manager/initManager.js +15 -0
- package/lib/state/GCPolicy.d.ts +55 -0
- package/lib/state/GCPolicy.d.ts.map +1 -0
- package/lib/state/GCPolicy.js +151 -0
- package/lib/state/reducer/createReducer.js +5 -2
- package/lib/state/reducer/expireReducer.d.ts +1 -0
- package/lib/state/reducer/expireReducer.d.ts.map +1 -1
- package/lib/state/reducer/invalidateReducer.d.ts +1 -0
- package/lib/state/reducer/invalidateReducer.d.ts.map +1 -1
- package/lib/state/reducer/setReducer.d.ts +3 -2
- package/lib/state/reducer/setReducer.d.ts.map +1 -1
- package/lib/state/reducer/setResponseReducer.d.ts +4 -2
- package/lib/state/reducer/setResponseReducer.d.ts.map +1 -1
- package/lib/state/reducer/setResponseReducer.js +4 -2
- package/lib/types.d.ts +1 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +1 -1
- package/package.json +10 -5
- package/src/actions.ts +2 -1
- package/src/controller/Controller.ts +205 -8
- package/src/controller/__tests__/Controller.ts +8 -4
- package/src/controller/__tests__/__snapshots__/get.ts.snap +7 -0
- package/src/controller/__tests__/__snapshots__/getResponse.ts.snap +15 -0
- package/src/controller/__tests__/get.ts +45 -17
- package/src/controller/__tests__/getResponse.ts +46 -0
- package/src/index.ts +2 -6
- package/src/manager/SubscriptionManager.ts +0 -1
- package/src/manager/applyManager.ts +3 -4
- package/src/manager/initManager.ts +21 -0
- package/src/state/GCPolicy.ts +197 -0
- package/src/state/__tests__/GCPolicy.test.ts +258 -0
- package/src/state/__tests__/__snapshots__/reducer.ts.snap +2 -0
- package/src/state/__tests__/reducer.ts +4 -4
- package/src/state/reducer/createReducer.ts +1 -1
- package/src/state/reducer/setResponseReducer.ts +3 -1
- package/src/types.ts +1 -0
- package/ts3.4/actions.d.ts +2 -5
- package/ts3.4/controller/Controller.d.ts +141 -5
- package/ts3.4/index.d.ts +2 -0
- package/ts3.4/manager/initManager.d.ts +4 -0
- package/ts3.4/state/GCPolicy.d.ts +55 -0
- package/ts3.4/state/reducer/expireReducer.d.ts +1 -0
- package/ts3.4/state/reducer/invalidateReducer.d.ts +1 -0
- package/ts3.4/state/reducer/setReducer.d.ts +3 -2
- package/ts3.4/state/reducer/setResponseReducer.d.ts +4 -2
- package/ts3.4/types.d.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
3
|
var normalizr = require('@data-client/normalizr');
|
|
6
4
|
|
|
7
5
|
function expireReducer(state, action) {
|
|
@@ -57,28 +55,28 @@ const GC_TYPE = GC;
|
|
|
57
55
|
|
|
58
56
|
var actionTypes = /*#__PURE__*/Object.freeze({
|
|
59
57
|
__proto__: null,
|
|
58
|
+
EXPIREALL: EXPIREALL,
|
|
59
|
+
EXPIREALL_TYPE: EXPIREALL_TYPE,
|
|
60
60
|
FETCH: FETCH,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
RESET: RESET,
|
|
65
|
-
SUBSCRIBE: SUBSCRIBE,
|
|
66
|
-
UNSUBSCRIBE: UNSUBSCRIBE,
|
|
61
|
+
FETCH_TYPE: FETCH_TYPE,
|
|
62
|
+
GC: GC,
|
|
63
|
+
GC_TYPE: GC_TYPE,
|
|
67
64
|
INVALIDATE: INVALIDATE,
|
|
68
65
|
INVALIDATEALL: INVALIDATEALL,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
SET_TYPE: SET_TYPE,
|
|
73
|
-
SET_RESPONSE_TYPE: SET_RESPONSE_TYPE,
|
|
66
|
+
INVALIDATEALL_TYPE: INVALIDATEALL_TYPE,
|
|
67
|
+
INVALIDATE_TYPE: INVALIDATE_TYPE,
|
|
68
|
+
OPTIMISTIC: OPTIMISTIC,
|
|
74
69
|
OPTIMISTIC_TYPE: OPTIMISTIC_TYPE,
|
|
70
|
+
RESET: RESET,
|
|
75
71
|
RESET_TYPE: RESET_TYPE,
|
|
72
|
+
SET: SET,
|
|
73
|
+
SET_RESPONSE: SET_RESPONSE,
|
|
74
|
+
SET_RESPONSE_TYPE: SET_RESPONSE_TYPE,
|
|
75
|
+
SET_TYPE: SET_TYPE,
|
|
76
|
+
SUBSCRIBE: SUBSCRIBE,
|
|
76
77
|
SUBSCRIBE_TYPE: SUBSCRIBE_TYPE,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
INVALIDATEALL_TYPE: INVALIDATEALL_TYPE,
|
|
80
|
-
EXPIREALL_TYPE: EXPIREALL_TYPE,
|
|
81
|
-
GC_TYPE: GC_TYPE
|
|
78
|
+
UNSUBSCRIBE: UNSUBSCRIBE,
|
|
79
|
+
UNSUBSCRIBE_TYPE: UNSUBSCRIBE_TYPE
|
|
82
80
|
});
|
|
83
81
|
|
|
84
82
|
function createOptimistic(endpoint, args, fetchedAt) {
|
|
@@ -233,6 +231,7 @@ function setResponseReducer(state, action, controller) {
|
|
|
233
231
|
...state.meta,
|
|
234
232
|
[action.key]: {
|
|
235
233
|
date: action.meta.date,
|
|
234
|
+
fetchedAt: action.meta.fetchedAt,
|
|
236
235
|
expiresAt: action.meta.expiresAt,
|
|
237
236
|
prevExpiresAt: (_state$meta$action$ke = state.meta[action.key]) == null ? void 0 : _state$meta$action$ke.expiresAt
|
|
238
237
|
}
|
|
@@ -273,8 +272,9 @@ function reduceError(state, action, error) {
|
|
|
273
272
|
...state.meta,
|
|
274
273
|
[action.key]: {
|
|
275
274
|
date: action.meta.date,
|
|
276
|
-
|
|
275
|
+
fetchedAt: action.meta.fetchedAt,
|
|
277
276
|
expiresAt: action.meta.expiresAt,
|
|
277
|
+
error,
|
|
278
278
|
errorPolicy: action.endpoint.errorPolicy == null ? void 0 : action.endpoint.errorPolicy(error)
|
|
279
279
|
}
|
|
280
280
|
},
|
|
@@ -292,7 +292,10 @@ function createReducer(controller) {
|
|
|
292
292
|
switch (action.type) {
|
|
293
293
|
case GC:
|
|
294
294
|
// inline deletes are fine as these should have 0 refcounts
|
|
295
|
-
action.entities.forEach((
|
|
295
|
+
action.entities.forEach(({
|
|
296
|
+
key,
|
|
297
|
+
pk
|
|
298
|
+
}) => {
|
|
296
299
|
var _entities$key, _entityMeta$key;
|
|
297
300
|
(_entities$key = state.entities[key]) == null || delete _entities$key[pk];
|
|
298
301
|
(_entityMeta$key = state.entityMeta[key]) == null || delete _entityMeta$key[pk];
|
|
@@ -339,8 +342,8 @@ const initialState = {
|
|
|
339
342
|
|
|
340
343
|
var internal = /*#__PURE__*/Object.freeze({
|
|
341
344
|
__proto__: null,
|
|
342
|
-
MemoCache: normalizr.MemoCache,
|
|
343
345
|
INVALID: normalizr.INVALID,
|
|
346
|
+
MemoCache: normalizr.MemoCache,
|
|
344
347
|
initialState: initialState
|
|
345
348
|
});
|
|
346
349
|
|
|
@@ -369,7 +372,6 @@ const ensurePojo =
|
|
|
369
372
|
// FormData doesn't exist in node
|
|
370
373
|
/* istanbul ignore else we don't run coverage when we test node*/
|
|
371
374
|
typeof FormData !== 'undefined' ? body => body instanceof FormData ? Object.fromEntries(body.entries()) : body : /* istanbul ignore next */body => body;
|
|
372
|
-
var ensurePojo$1 = ensurePojo;
|
|
373
375
|
|
|
374
376
|
function createSetResponse(endpoint, {
|
|
375
377
|
args,
|
|
@@ -387,7 +389,7 @@ function createSetResponse(endpoint, {
|
|
|
387
389
|
type: SET_RESPONSE,
|
|
388
390
|
key: endpoint.key(...args),
|
|
389
391
|
response,
|
|
390
|
-
args: args.map(ensurePojo
|
|
392
|
+
args: args.map(ensurePojo),
|
|
391
393
|
endpoint,
|
|
392
394
|
meta: createMeta(expiryLength, fetchedAt),
|
|
393
395
|
error
|
|
@@ -402,7 +404,7 @@ function createSet(schema, {
|
|
|
402
404
|
return {
|
|
403
405
|
type: SET,
|
|
404
406
|
value,
|
|
405
|
-
args: args.map(ensurePojo
|
|
407
|
+
args: args.map(ensurePojo),
|
|
406
408
|
schema,
|
|
407
409
|
meta: createMeta(60000, fetchedAt)
|
|
408
410
|
};
|
|
@@ -466,19 +468,169 @@ function createExpireAll(testKey) {
|
|
|
466
468
|
|
|
467
469
|
var index = /*#__PURE__*/Object.freeze({
|
|
468
470
|
__proto__: null,
|
|
469
|
-
|
|
470
|
-
createUnsubscription: createUnsubscription,
|
|
471
|
-
createSetResponse: createSetResponse,
|
|
472
|
-
createSet: createSet,
|
|
473
|
-
createReset: createReset,
|
|
474
|
-
createOptimistic: createOptimistic,
|
|
475
|
-
createMeta: createMeta,
|
|
476
|
-
createInvalidateAll: createInvalidateAll,
|
|
477
|
-
createInvalidate: createInvalidate,
|
|
471
|
+
createExpireAll: createExpireAll,
|
|
478
472
|
createFetch: createFetch,
|
|
479
|
-
|
|
473
|
+
createInvalidate: createInvalidate,
|
|
474
|
+
createInvalidateAll: createInvalidateAll,
|
|
475
|
+
createMeta: createMeta,
|
|
476
|
+
createOptimistic: createOptimistic,
|
|
477
|
+
createReset: createReset,
|
|
478
|
+
createSet: createSet,
|
|
479
|
+
createSetResponse: createSetResponse,
|
|
480
|
+
createSubscription: createSubscription,
|
|
481
|
+
createUnsubscription: createUnsubscription
|
|
480
482
|
});
|
|
481
483
|
|
|
484
|
+
class GCPolicy {
|
|
485
|
+
endpointCount = new Map();
|
|
486
|
+
entityCount = new Map();
|
|
487
|
+
endpointsQ = new Set();
|
|
488
|
+
entitiesQ = [];
|
|
489
|
+
constructor({
|
|
490
|
+
// every 5 min
|
|
491
|
+
intervalMS = 60 * 1000 * 5,
|
|
492
|
+
expiryMultiplier = 2,
|
|
493
|
+
expiresAt
|
|
494
|
+
} = {}) {
|
|
495
|
+
if (expiresAt) {
|
|
496
|
+
this.expiresAt = expiresAt.bind(this);
|
|
497
|
+
}
|
|
498
|
+
this.options = {
|
|
499
|
+
intervalMS,
|
|
500
|
+
expiryMultiplier
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
init(controller) {
|
|
504
|
+
this.controller = controller;
|
|
505
|
+
this.intervalId = setInterval(() => {
|
|
506
|
+
this.idleCallback(() => this.runSweep(), {
|
|
507
|
+
timeout: 1000
|
|
508
|
+
});
|
|
509
|
+
}, this.options.intervalMS);
|
|
510
|
+
}
|
|
511
|
+
cleanup() {
|
|
512
|
+
clearInterval(this.intervalId);
|
|
513
|
+
}
|
|
514
|
+
createCountRef({
|
|
515
|
+
key,
|
|
516
|
+
paths = []
|
|
517
|
+
}) {
|
|
518
|
+
// increment
|
|
519
|
+
return () => {
|
|
520
|
+
var _this$endpointCount$g;
|
|
521
|
+
if (key) this.endpointCount.set(key, ((_this$endpointCount$g = this.endpointCount.get(key)) != null ? _this$endpointCount$g : 0) + 1);
|
|
522
|
+
paths.forEach(path => {
|
|
523
|
+
var _instanceCount$get;
|
|
524
|
+
if (!this.entityCount.has(path.key)) {
|
|
525
|
+
this.entityCount.set(path.key, new Map());
|
|
526
|
+
}
|
|
527
|
+
const instanceCount = this.entityCount.get(path.key);
|
|
528
|
+
instanceCount.set(path.pk, ((_instanceCount$get = instanceCount.get(path.pk)) != null ? _instanceCount$get : 0) + 1);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// decrement
|
|
532
|
+
return () => {
|
|
533
|
+
if (key) {
|
|
534
|
+
const currentCount = this.endpointCount.get(key);
|
|
535
|
+
if (currentCount !== undefined) {
|
|
536
|
+
if (currentCount <= 1) {
|
|
537
|
+
this.endpointCount.delete(key);
|
|
538
|
+
// queue for cleanup
|
|
539
|
+
this.endpointsQ.add(key);
|
|
540
|
+
} else {
|
|
541
|
+
this.endpointCount.set(key, currentCount - 1);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
paths.forEach(path => {
|
|
546
|
+
if (!this.entityCount.has(path.key)) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const instanceCount = this.entityCount.get(path.key);
|
|
550
|
+
const entityCount = instanceCount.get(path.pk);
|
|
551
|
+
if (entityCount !== undefined) {
|
|
552
|
+
if (entityCount <= 1) {
|
|
553
|
+
instanceCount.delete(path.pk);
|
|
554
|
+
// queue for cleanup
|
|
555
|
+
this.entitiesQ.push(path);
|
|
556
|
+
} else {
|
|
557
|
+
instanceCount.set(path.pk, entityCount - 1);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
expiresAt({
|
|
565
|
+
fetchedAt,
|
|
566
|
+
expiresAt
|
|
567
|
+
}) {
|
|
568
|
+
return Math.max((expiresAt - fetchedAt) * this.options.expiryMultiplier, 120000) + fetchedAt;
|
|
569
|
+
}
|
|
570
|
+
runSweep() {
|
|
571
|
+
const state = this.controller.getState();
|
|
572
|
+
const entities = [];
|
|
573
|
+
const endpoints = [];
|
|
574
|
+
const now = Date.now();
|
|
575
|
+
const nextEndpointsQ = new Set();
|
|
576
|
+
for (const key of this.endpointsQ) {
|
|
577
|
+
var _state$meta$key;
|
|
578
|
+
if (!this.endpointCount.has(key) && this.expiresAt((_state$meta$key = state.meta[key]) != null ? _state$meta$key : {
|
|
579
|
+
fetchedAt: 0,
|
|
580
|
+
date: 0,
|
|
581
|
+
expiresAt: 0
|
|
582
|
+
}) < now) {
|
|
583
|
+
endpoints.push(key);
|
|
584
|
+
} else {
|
|
585
|
+
nextEndpointsQ.add(key);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
this.endpointsQ = nextEndpointsQ;
|
|
589
|
+
const nextEntitiesQ = [];
|
|
590
|
+
for (const path of this.entitiesQ) {
|
|
591
|
+
var _this$entityCount$get, _state$entityMeta$pat, _state$entityMeta$pat2;
|
|
592
|
+
if (!((_this$entityCount$get = this.entityCount.get(path.key)) != null && _this$entityCount$get.has(path.pk)) && this.expiresAt((_state$entityMeta$pat = (_state$entityMeta$pat2 = state.entityMeta[path.key]) == null ? void 0 : _state$entityMeta$pat2[path.pk]) != null ? _state$entityMeta$pat : {
|
|
593
|
+
fetchedAt: 0,
|
|
594
|
+
date: 0,
|
|
595
|
+
expiresAt: 0
|
|
596
|
+
}) < now) {
|
|
597
|
+
entities.push(path);
|
|
598
|
+
} else {
|
|
599
|
+
nextEntitiesQ.push(path);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
this.entitiesQ = nextEntitiesQ;
|
|
603
|
+
if (entities.length || endpoints.length) {
|
|
604
|
+
this.controller.dispatch({
|
|
605
|
+
type: GC,
|
|
606
|
+
entities,
|
|
607
|
+
endpoints
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Calls the callback when client is not 'busy' with high priority interaction tasks
|
|
613
|
+
*
|
|
614
|
+
* Override for platform-specific implementations
|
|
615
|
+
*/
|
|
616
|
+
idleCallback(callback, options) {
|
|
617
|
+
if (typeof requestIdleCallback === 'function') {
|
|
618
|
+
requestIdleCallback(callback, options);
|
|
619
|
+
} else {
|
|
620
|
+
callback();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
class ImmortalGCPolicy {
|
|
625
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
626
|
+
init() {}
|
|
627
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
628
|
+
cleanup() {}
|
|
629
|
+
createCountRef() {
|
|
630
|
+
return () => () => undefined;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
482
634
|
function selectMeta(state, fetchKey) {
|
|
483
635
|
return state.meta[fetchKey];
|
|
484
636
|
}
|
|
@@ -515,131 +667,104 @@ class Controller {
|
|
|
515
667
|
* Singleton to maintain referential equality between calls
|
|
516
668
|
*/
|
|
517
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Handles garbage collection
|
|
672
|
+
*/
|
|
673
|
+
|
|
518
674
|
constructor({
|
|
519
675
|
dispatch = unsetDispatch,
|
|
520
676
|
getState = unsetState,
|
|
521
|
-
memo = new normalizr.MemoCache()
|
|
677
|
+
memo = new normalizr.MemoCache(),
|
|
678
|
+
gcPolicy = new ImmortalGCPolicy()
|
|
522
679
|
} = {}) {
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Fetches the endpoint with given args, updating the Reactive Data Client cache with the response or error upon completion.
|
|
526
|
-
* @see https://dataclient.io/docs/api/Controller#fetch
|
|
527
|
-
*/
|
|
528
|
-
this.fetch = (endpoint, ...args) => {
|
|
529
|
-
const action = createFetch(endpoint, {
|
|
530
|
-
args
|
|
531
|
-
});
|
|
532
|
-
this.dispatch(action);
|
|
533
|
-
if (endpoint.schema) {
|
|
534
|
-
return action.meta.promise.then(input => normalizr.denormalize(endpoint.schema, input, {}, args));
|
|
535
|
-
}
|
|
536
|
-
return action.meta.promise;
|
|
537
|
-
};
|
|
538
|
-
/**
|
|
539
|
-
* Fetches only if endpoint is considered 'stale'; otherwise returns undefined
|
|
540
|
-
* @see https://dataclient.io/docs/api/Controller#fetchIfStale
|
|
541
|
-
*/
|
|
542
|
-
this.fetchIfStale = (endpoint, ...args) => {
|
|
543
|
-
const {
|
|
544
|
-
data,
|
|
545
|
-
expiresAt,
|
|
546
|
-
expiryStatus
|
|
547
|
-
} = this.getResponse(endpoint, ...args, this.getState());
|
|
548
|
-
if (expiryStatus !== normalizr.ExpiryStatus.Invalid && Date.now() <= expiresAt) return data;
|
|
549
|
-
return this.fetch(endpoint, ...args);
|
|
550
|
-
};
|
|
551
|
-
/**
|
|
552
|
-
* Forces refetching and suspense on useSuspense with the same Endpoint and parameters.
|
|
553
|
-
* @see https://dataclient.io/docs/api/Controller#invalidate
|
|
554
|
-
*/
|
|
555
|
-
this.invalidate = (endpoint, ...args) => args[0] !== null ? this.dispatch(createInvalidate(endpoint, {
|
|
556
|
-
args: args
|
|
557
|
-
})) : Promise.resolve();
|
|
558
|
-
/**
|
|
559
|
-
* Forces refetching and suspense on useSuspense on all matching endpoint result keys.
|
|
560
|
-
* @see https://dataclient.io/docs/api/Controller#invalidateAll
|
|
561
|
-
* @returns Promise that resolves when invalidation is commited.
|
|
562
|
-
*/
|
|
563
|
-
this.invalidateAll = options => this.dispatch(createInvalidateAll(key => options.testKey(key)));
|
|
564
|
-
/**
|
|
565
|
-
* Sets all matching endpoint result keys to be STALE.
|
|
566
|
-
* @see https://dataclient.io/docs/api/Controller#expireAll
|
|
567
|
-
* @returns Promise that resolves when expiry is commited. *NOT* fetch promise
|
|
568
|
-
*/
|
|
569
|
-
this.expireAll = options => this.dispatch(createExpireAll(key => options.testKey(key)));
|
|
570
|
-
/**
|
|
571
|
-
* Resets the entire Reactive Data Client cache. All inflight requests will not resolve.
|
|
572
|
-
* @see https://dataclient.io/docs/api/Controller#resetEntireStore
|
|
573
|
-
*/
|
|
574
|
-
this.resetEntireStore = () => this.dispatch(createReset());
|
|
575
|
-
/**
|
|
576
|
-
* Sets response for the Endpoint and args.
|
|
577
|
-
* @see https://dataclient.io/docs/api/Controller#setResponse
|
|
578
|
-
*/
|
|
579
|
-
this.setResponse = (endpoint, ...rest) => {
|
|
580
|
-
const response = rest[rest.length - 1];
|
|
581
|
-
const action = createSetResponse(endpoint, {
|
|
582
|
-
args: rest.slice(0, rest.length - 1),
|
|
583
|
-
response
|
|
584
|
-
});
|
|
585
|
-
return this.dispatch(action);
|
|
586
|
-
};
|
|
587
|
-
/**
|
|
588
|
-
* Sets an error response for the Endpoint and args.
|
|
589
|
-
* @see https://dataclient.io/docs/api/Controller#setError
|
|
590
|
-
*/
|
|
591
|
-
this.setError = (endpoint, ...rest) => {
|
|
592
|
-
const response = rest[rest.length - 1];
|
|
593
|
-
const action = createSetResponse(endpoint, {
|
|
594
|
-
args: rest.slice(0, rest.length - 1),
|
|
595
|
-
response,
|
|
596
|
-
error: true
|
|
597
|
-
});
|
|
598
|
-
return this.dispatch(action);
|
|
599
|
-
};
|
|
600
|
-
/**
|
|
601
|
-
* Resolves an inflight fetch.
|
|
602
|
-
* @see https://dataclient.io/docs/api/Controller#resolve
|
|
603
|
-
*/
|
|
604
|
-
this.resolve = (endpoint, meta) => {
|
|
605
|
-
return this.dispatch(createSetResponse(endpoint, meta));
|
|
606
|
-
};
|
|
607
|
-
/**
|
|
608
|
-
* Marks a new subscription to a given Endpoint.
|
|
609
|
-
* @see https://dataclient.io/docs/api/Controller#subscribe
|
|
610
|
-
*/
|
|
611
|
-
this.subscribe = (endpoint, ...args) => args[0] !== null ? this.dispatch(createSubscription(endpoint, {
|
|
612
|
-
args: args
|
|
613
|
-
})) : Promise.resolve();
|
|
614
|
-
/**
|
|
615
|
-
* Marks completion of subscription to a given Endpoint.
|
|
616
|
-
* @see https://dataclient.io/docs/api/Controller#unsubscribe
|
|
617
|
-
*/
|
|
618
|
-
this.unsubscribe = (endpoint, ...args) => args[0] !== null ? this.dispatch(createUnsubscription(endpoint, {
|
|
619
|
-
args: args
|
|
620
|
-
})) : Promise.resolve();
|
|
621
|
-
/*************** More ***************/
|
|
622
|
-
/* TODO:
|
|
623
|
-
abort = <E extends EndpointInterface>(
|
|
624
|
-
endpoint: E,
|
|
625
|
-
...args: readonly [...Parameters<E>]
|
|
626
|
-
): Promise<void>
|
|
627
|
-
*/
|
|
628
|
-
/**
|
|
629
|
-
* Gets a snapshot (https://dataclient.io/docs/api/Snapshot)
|
|
630
|
-
* @see https://dataclient.io/docs/api/Controller#snapshot
|
|
631
|
-
*/
|
|
632
|
-
this.snapshot = (state, fetchedAt) => {
|
|
633
|
-
return new Snapshot(this, state, fetchedAt);
|
|
634
|
-
};
|
|
635
|
-
this.dispatch = dispatch;
|
|
680
|
+
this._dispatch = dispatch;
|
|
636
681
|
this.getState = getState;
|
|
637
682
|
this.memo = memo;
|
|
683
|
+
this.gcPolicy = gcPolicy;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// TODO: drop when drop support for destructuring (0.14 and below)
|
|
687
|
+
set dispatch(dispatch) {
|
|
688
|
+
/* istanbul ignore next */
|
|
689
|
+
this._dispatch = dispatch;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// TODO: drop when drop support for destructuring (0.14 and below)
|
|
693
|
+
get dispatch() {
|
|
694
|
+
return this._dispatch;
|
|
695
|
+
}
|
|
696
|
+
bindMiddleware({
|
|
697
|
+
dispatch,
|
|
698
|
+
getState
|
|
699
|
+
}) {
|
|
700
|
+
this._dispatch = dispatch;
|
|
701
|
+
this.getState = getState;
|
|
638
702
|
}
|
|
703
|
+
|
|
704
|
+
/*************** Action Dispatchers ***************/
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Fetches the endpoint with given args, updating the Reactive Data Client cache with the response or error upon completion.
|
|
708
|
+
* @see https://dataclient.io/docs/api/Controller#fetch
|
|
709
|
+
*/
|
|
710
|
+
fetch = (endpoint, ...args) => {
|
|
711
|
+
const action = createFetch(endpoint, {
|
|
712
|
+
args
|
|
713
|
+
});
|
|
714
|
+
this.dispatch(action);
|
|
715
|
+
if (endpoint.schema) {
|
|
716
|
+
return action.meta.promise.then(input => normalizr.denormalize(endpoint.schema, input, {}, args));
|
|
717
|
+
}
|
|
718
|
+
return action.meta.promise;
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Fetches only if endpoint is considered 'stale'; otherwise returns undefined
|
|
723
|
+
* @see https://dataclient.io/docs/api/Controller#fetchIfStale
|
|
724
|
+
*/
|
|
725
|
+
fetchIfStale = (endpoint, ...args) => {
|
|
726
|
+
const {
|
|
727
|
+
data,
|
|
728
|
+
expiresAt,
|
|
729
|
+
expiryStatus
|
|
730
|
+
} = this.getResponse(endpoint, ...args, this.getState());
|
|
731
|
+
if (expiryStatus !== normalizr.ExpiryStatus.Invalid && Date.now() <= expiresAt) return data;
|
|
732
|
+
return this.fetch(endpoint, ...args);
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Forces refetching and suspense on useSuspense with the same Endpoint and parameters.
|
|
737
|
+
* @see https://dataclient.io/docs/api/Controller#invalidate
|
|
738
|
+
*/
|
|
739
|
+
invalidate = (endpoint, ...args) => args[0] !== null ? this.dispatch(createInvalidate(endpoint, {
|
|
740
|
+
args: args
|
|
741
|
+
})) : Promise.resolve();
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Forces refetching and suspense on useSuspense on all matching endpoint result keys.
|
|
745
|
+
* @see https://dataclient.io/docs/api/Controller#invalidateAll
|
|
746
|
+
* @returns Promise that resolves when invalidation is commited.
|
|
747
|
+
*/
|
|
748
|
+
invalidateAll = options => this.dispatch(createInvalidateAll(key => options.testKey(key)));
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Sets all matching endpoint result keys to be STALE.
|
|
752
|
+
* @see https://dataclient.io/docs/api/Controller#expireAll
|
|
753
|
+
* @returns Promise that resolves when expiry is commited. *NOT* fetch promise
|
|
754
|
+
*/
|
|
755
|
+
expireAll = options => this.dispatch(createExpireAll(key => options.testKey(key)));
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Resets the entire Reactive Data Client cache. All inflight requests will not resolve.
|
|
759
|
+
* @see https://dataclient.io/docs/api/Controller#resetEntireStore
|
|
760
|
+
*/
|
|
761
|
+
resetEntireStore = () => this.dispatch(createReset());
|
|
762
|
+
|
|
639
763
|
/**
|
|
640
764
|
* Sets value for the Queryable and args.
|
|
641
765
|
* @see https://dataclient.io/docs/api/Controller#set
|
|
642
766
|
*/
|
|
767
|
+
|
|
643
768
|
set(schema, ...rest) {
|
|
644
769
|
const value = rest[rest.length - 1];
|
|
645
770
|
const action = createSet(schema, {
|
|
@@ -649,10 +774,80 @@ class Controller {
|
|
|
649
774
|
// TODO: reject with error if this fails in reducer
|
|
650
775
|
return this.dispatch(action);
|
|
651
776
|
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Sets response for the Endpoint and args.
|
|
780
|
+
* @see https://dataclient.io/docs/api/Controller#setResponse
|
|
781
|
+
*/
|
|
782
|
+
setResponse = (endpoint, ...rest) => {
|
|
783
|
+
const response = rest[rest.length - 1];
|
|
784
|
+
const action = createSetResponse(endpoint, {
|
|
785
|
+
args: rest.slice(0, rest.length - 1),
|
|
786
|
+
response
|
|
787
|
+
});
|
|
788
|
+
return this.dispatch(action);
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Sets an error response for the Endpoint and args.
|
|
793
|
+
* @see https://dataclient.io/docs/api/Controller#setError
|
|
794
|
+
*/
|
|
795
|
+
setError = (endpoint, ...rest) => {
|
|
796
|
+
const response = rest[rest.length - 1];
|
|
797
|
+
const action = createSetResponse(endpoint, {
|
|
798
|
+
args: rest.slice(0, rest.length - 1),
|
|
799
|
+
response,
|
|
800
|
+
error: true
|
|
801
|
+
});
|
|
802
|
+
return this.dispatch(action);
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Resolves an inflight fetch.
|
|
807
|
+
* @see https://dataclient.io/docs/api/Controller#resolve
|
|
808
|
+
*/
|
|
809
|
+
resolve = (endpoint, meta) => {
|
|
810
|
+
return this.dispatch(createSetResponse(endpoint, meta));
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Marks a new subscription to a given Endpoint.
|
|
815
|
+
* @see https://dataclient.io/docs/api/Controller#subscribe
|
|
816
|
+
*/
|
|
817
|
+
subscribe = (endpoint, ...args) => args[0] !== null ? this.dispatch(createSubscription(endpoint, {
|
|
818
|
+
args: args
|
|
819
|
+
})) : Promise.resolve();
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Marks completion of subscription to a given Endpoint.
|
|
823
|
+
* @see https://dataclient.io/docs/api/Controller#unsubscribe
|
|
824
|
+
*/
|
|
825
|
+
unsubscribe = (endpoint, ...args) => args[0] !== null ? this.dispatch(createUnsubscription(endpoint, {
|
|
826
|
+
args: args
|
|
827
|
+
})) : Promise.resolve();
|
|
828
|
+
|
|
829
|
+
/*************** More ***************/
|
|
830
|
+
|
|
831
|
+
/* TODO:
|
|
832
|
+
abort = <E extends EndpointInterface>(
|
|
833
|
+
endpoint: E,
|
|
834
|
+
...args: readonly [...Parameters<E>]
|
|
835
|
+
): Promise<void>
|
|
836
|
+
*/
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Gets a snapshot (https://dataclient.io/docs/api/Snapshot)
|
|
840
|
+
* @see https://dataclient.io/docs/api/Controller#snapshot
|
|
841
|
+
*/
|
|
842
|
+
snapshot = (state, fetchedAt) => {
|
|
843
|
+
return new Snapshot(this, state, fetchedAt);
|
|
844
|
+
};
|
|
845
|
+
|
|
652
846
|
/**
|
|
653
847
|
* Gets the error, if any, for a given endpoint. Returns undefined for no errors.
|
|
654
848
|
* @see https://dataclient.io/docs/api/Controller#getError
|
|
655
849
|
*/
|
|
850
|
+
|
|
656
851
|
getError(endpoint, ...rest) {
|
|
657
852
|
if (rest[0] === null) return;
|
|
658
853
|
const state = rest[rest.length - 1];
|
|
@@ -671,11 +866,21 @@ class Controller {
|
|
|
671
866
|
*/
|
|
672
867
|
|
|
673
868
|
getResponse(endpoint, ...rest) {
|
|
869
|
+
// TODO: breaking: only return data
|
|
870
|
+
return this.getResponseMeta(endpoint, ...rest);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Gets the (globally referentially stable) response for a given endpoint/args pair from state given.
|
|
875
|
+
* @see https://dataclient.io/docs/api/Controller#getResponseMeta
|
|
876
|
+
*/
|
|
877
|
+
|
|
878
|
+
getResponseMeta(endpoint, ...rest) {
|
|
674
879
|
const state = rest[rest.length - 1];
|
|
675
880
|
// this is typescript generics breaking
|
|
676
881
|
const args = rest.slice(0, rest.length - 1)
|
|
677
882
|
// handle FormData
|
|
678
|
-
.map(ensurePojo
|
|
883
|
+
.map(ensurePojo);
|
|
679
884
|
const isActive = args.length !== 1 || args[0] !== null;
|
|
680
885
|
const key = isActive ? endpoint.key(...args) : '';
|
|
681
886
|
const cacheEndpoints = isActive ? state.endpoints[key] : undefined;
|
|
@@ -692,17 +897,22 @@ class Controller {
|
|
|
692
897
|
return {
|
|
693
898
|
data: input,
|
|
694
899
|
expiryStatus: normalizr.ExpiryStatus.Valid,
|
|
695
|
-
expiresAt: Infinity
|
|
900
|
+
expiresAt: Infinity,
|
|
901
|
+
countRef: () => () => undefined
|
|
696
902
|
};
|
|
697
903
|
}
|
|
698
904
|
let isInvalid = false;
|
|
699
905
|
if (shouldQuery) {
|
|
700
906
|
isInvalid = !normalizr.validateQueryKey(input);
|
|
907
|
+
// endpoint without entities
|
|
701
908
|
} else if (!schema || !schemaHasEntity(schema)) {
|
|
702
909
|
return {
|
|
703
910
|
data: cacheEndpoints,
|
|
704
911
|
expiryStatus: meta != null && meta.invalidated ? normalizr.ExpiryStatus.Invalid : cacheEndpoints && !endpoint.invalidIfStale ? normalizr.ExpiryStatus.Valid : normalizr.ExpiryStatus.InvalidIfStale,
|
|
705
|
-
expiresAt: expiresAt || 0
|
|
912
|
+
expiresAt: expiresAt || 0,
|
|
913
|
+
countRef: this.gcPolicy.createCountRef({
|
|
914
|
+
key
|
|
915
|
+
})
|
|
706
916
|
};
|
|
707
917
|
}
|
|
708
918
|
|
|
@@ -715,7 +925,7 @@ class Controller {
|
|
|
715
925
|
|
|
716
926
|
// note: isInvalid can only be true if shouldQuery is true
|
|
717
927
|
if (!expiresAt && isInvalid) expiresAt = 1;
|
|
718
|
-
return this.getSchemaResponse(data, paths, state.entityMeta, expiresAt, endpoint.invalidIfStale || isInvalid, meta);
|
|
928
|
+
return this.getSchemaResponse(data, key, paths, state.entityMeta, expiresAt, endpoint.invalidIfStale || isInvalid, meta);
|
|
719
929
|
}
|
|
720
930
|
|
|
721
931
|
/**
|
|
@@ -725,10 +935,40 @@ class Controller {
|
|
|
725
935
|
get(schema, ...rest) {
|
|
726
936
|
const state = rest[rest.length - 1];
|
|
727
937
|
// this is typescript generics breaking
|
|
728
|
-
const args = rest.slice(0, rest.length - 1).map(ensurePojo
|
|
938
|
+
const args = rest.slice(0, rest.length - 1).map(ensurePojo);
|
|
729
939
|
return this.memo.query(schema, args, state.entities, state.indexes);
|
|
730
940
|
}
|
|
731
|
-
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Queries the store for a Querable schema; providing related metadata
|
|
944
|
+
* @see https://dataclient.io/docs/api/Controller#getQueryMeta
|
|
945
|
+
*/
|
|
946
|
+
getQueryMeta(schema, ...rest) {
|
|
947
|
+
const state = rest[rest.length - 1];
|
|
948
|
+
// this is typescript generics breaking
|
|
949
|
+
const args = rest.slice(0, rest.length - 1).map(ensurePojo);
|
|
950
|
+
|
|
951
|
+
// TODO: breaking: Switch back to this.memo.query(schema, args, state.entities as any, state.indexes) to do
|
|
952
|
+
// this logic
|
|
953
|
+
const input = this.memo.buildQueryKey(schema, args, state.entities, state.indexes, JSON.stringify(args));
|
|
954
|
+
if (!input) {
|
|
955
|
+
return {
|
|
956
|
+
data: undefined,
|
|
957
|
+
countRef: () => () => undefined
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
const {
|
|
961
|
+
data,
|
|
962
|
+
paths
|
|
963
|
+
} = this.memo.denormalize(schema, input, state.entities, args);
|
|
964
|
+
return {
|
|
965
|
+
data: typeof data === 'symbol' ? undefined : data,
|
|
966
|
+
countRef: this.gcPolicy.createCountRef({
|
|
967
|
+
paths
|
|
968
|
+
})
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
getSchemaResponse(data, key, paths, entityMeta, expiresAt, invalidIfStale, meta = {}) {
|
|
732
972
|
const invalidDenormalize = typeof data === 'symbol';
|
|
733
973
|
|
|
734
974
|
// fallback to entity expiry time
|
|
@@ -743,7 +983,11 @@ class Controller {
|
|
|
743
983
|
return {
|
|
744
984
|
data,
|
|
745
985
|
expiryStatus,
|
|
746
|
-
expiresAt
|
|
986
|
+
expiresAt,
|
|
987
|
+
countRef: this.gcPolicy.createCountRef({
|
|
988
|
+
key,
|
|
989
|
+
paths
|
|
990
|
+
})
|
|
747
991
|
};
|
|
748
992
|
}
|
|
749
993
|
}
|
|
@@ -781,11 +1025,12 @@ function schemaHasEntity(schema) {
|
|
|
781
1025
|
return false;
|
|
782
1026
|
}
|
|
783
1027
|
class Snapshot {
|
|
1028
|
+
static abort = new AbortOptimistic();
|
|
1029
|
+
state;
|
|
1030
|
+
controller;
|
|
1031
|
+
fetchedAt;
|
|
1032
|
+
abort = Snapshot.abort;
|
|
784
1033
|
constructor(controller, state, fetchedAt = 0) {
|
|
785
|
-
this.state = void 0;
|
|
786
|
-
this.controller = void 0;
|
|
787
|
-
this.fetchedAt = void 0;
|
|
788
|
-
this.abort = Snapshot.abort;
|
|
789
1034
|
this.state = state;
|
|
790
1035
|
this.controller = controller;
|
|
791
1036
|
this.fetchedAt = fetchedAt;
|
|
@@ -798,6 +1043,12 @@ class Snapshot {
|
|
|
798
1043
|
return this.controller.getResponse(endpoint, ...args, this.state);
|
|
799
1044
|
}
|
|
800
1045
|
|
|
1046
|
+
/** @see https://dataclient.io/docs/api/Snapshot#getResponseMeta */
|
|
1047
|
+
|
|
1048
|
+
getResponseMeta(endpoint, ...args) {
|
|
1049
|
+
return this.controller.getResponseMeta(endpoint, ...args, this.state);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
801
1052
|
/** @see https://dataclient.io/docs/api/Snapshot#getError */
|
|
802
1053
|
|
|
803
1054
|
getError(endpoint, ...args) {
|
|
@@ -811,13 +1062,20 @@ class Snapshot {
|
|
|
811
1062
|
get(schema, ...args) {
|
|
812
1063
|
return this.controller.get(schema, ...args, this.state);
|
|
813
1064
|
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Queries the store for a Querable schema; providing related metadata
|
|
1068
|
+
* @see https://dataclient.io/docs/api/Snapshot#getQueryMeta
|
|
1069
|
+
*/
|
|
1070
|
+
getQueryMeta(schema, ...args) {
|
|
1071
|
+
return this.controller.getQueryMeta(schema, ...args, this.state);
|
|
1072
|
+
}
|
|
814
1073
|
}
|
|
815
|
-
Snapshot.abort = new AbortOptimistic();
|
|
816
1074
|
|
|
817
1075
|
class ResetError extends Error {
|
|
1076
|
+
name = 'ResetError';
|
|
818
1077
|
constructor() {
|
|
819
1078
|
super('Aborted due to RESET');
|
|
820
|
-
this.name = 'ResetError';
|
|
821
1079
|
}
|
|
822
1080
|
}
|
|
823
1081
|
|
|
@@ -831,70 +1089,71 @@ class ResetError extends Error {
|
|
|
831
1089
|
* @see https://dataclient.io/docs/api/NetworkManager
|
|
832
1090
|
*/
|
|
833
1091
|
class NetworkManager {
|
|
1092
|
+
fetched = Object.create(null);
|
|
1093
|
+
resolvers = {};
|
|
1094
|
+
rejectors = {};
|
|
1095
|
+
fetchedAt = {};
|
|
1096
|
+
controller = new Controller();
|
|
834
1097
|
constructor({
|
|
835
1098
|
dataExpiryLength = 60000,
|
|
836
1099
|
errorExpiryLength = 1000
|
|
837
1100
|
} = {}) {
|
|
838
|
-
this.
|
|
839
|
-
this.
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
this.controller =
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
case
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1101
|
+
this.dataExpiryLength = dataExpiryLength;
|
|
1102
|
+
this.errorExpiryLength = errorExpiryLength;
|
|
1103
|
+
}
|
|
1104
|
+
middleware = controller => {
|
|
1105
|
+
this.controller = controller;
|
|
1106
|
+
return next => action => {
|
|
1107
|
+
switch (action.type) {
|
|
1108
|
+
case FETCH:
|
|
1109
|
+
this.handleFetch(action);
|
|
1110
|
+
// This is the only case that causes any state change
|
|
1111
|
+
// It's important to intercept other fetches as we don't want to trigger reducers during
|
|
1112
|
+
// render - so we need to stop 'readonly' fetches which can be triggered in render
|
|
1113
|
+
if (action.endpoint.getOptimisticResponse !== undefined && action.endpoint.sideEffect) {
|
|
1114
|
+
return next(action);
|
|
1115
|
+
}
|
|
1116
|
+
return Promise.resolve();
|
|
1117
|
+
case SET_RESPONSE:
|
|
1118
|
+
// only set after new state is computed
|
|
1119
|
+
return next(action).then(() => {
|
|
1120
|
+
if (action.key in this.fetched) {
|
|
1121
|
+
var _controller$getState$;
|
|
1122
|
+
// Note: meta *must* be set by reducer so this should be safe
|
|
1123
|
+
const error = (_controller$getState$ = controller.getState().meta[action.key]) == null ? void 0 : _controller$getState$.error;
|
|
1124
|
+
// processing errors result in state meta having error, so we should reject the promise
|
|
1125
|
+
if (error) {
|
|
1126
|
+
this.handleSet(createSetResponse(action.endpoint, {
|
|
1127
|
+
args: action.args,
|
|
1128
|
+
response: error,
|
|
1129
|
+
fetchedAt: action.meta.fetchedAt,
|
|
1130
|
+
error: true
|
|
1131
|
+
}));
|
|
1132
|
+
} else {
|
|
1133
|
+
this.handleSet(action);
|
|
1134
|
+
}
|
|
854
1135
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1136
|
+
});
|
|
1137
|
+
case RESET:
|
|
1138
|
+
{
|
|
1139
|
+
const rejectors = {
|
|
1140
|
+
...this.rejectors
|
|
1141
|
+
};
|
|
1142
|
+
this.clearAll();
|
|
858
1143
|
return next(action).then(() => {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
// processing errors result in state meta having error, so we should reject the promise
|
|
864
|
-
if (error) {
|
|
865
|
-
this.handleSet(createSetResponse(action.endpoint, {
|
|
866
|
-
args: action.args,
|
|
867
|
-
response: error,
|
|
868
|
-
fetchedAt: action.meta.fetchedAt,
|
|
869
|
-
error: true
|
|
870
|
-
}));
|
|
871
|
-
} else {
|
|
872
|
-
this.handleSet(action);
|
|
873
|
-
}
|
|
1144
|
+
// there could be external listeners to the promise
|
|
1145
|
+
// this must happen after commit so our own rejector knows not to dispatch an error based on this
|
|
1146
|
+
for (const k in rejectors) {
|
|
1147
|
+
rejectors[k](new ResetError());
|
|
874
1148
|
}
|
|
875
1149
|
});
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
};
|
|
881
|
-
this.clearAll();
|
|
882
|
-
return next(action).then(() => {
|
|
883
|
-
// there could be external listeners to the promise
|
|
884
|
-
// this must happen after commit so our own rejector knows not to dispatch an error based on this
|
|
885
|
-
for (const k in rejectors) {
|
|
886
|
-
rejectors[k](new ResetError());
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
default:
|
|
891
|
-
return next(action);
|
|
892
|
-
}
|
|
893
|
-
};
|
|
1150
|
+
}
|
|
1151
|
+
default:
|
|
1152
|
+
return next(action);
|
|
1153
|
+
}
|
|
894
1154
|
};
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
1155
|
+
};
|
|
1156
|
+
|
|
898
1157
|
/** On mount */
|
|
899
1158
|
init() {
|
|
900
1159
|
delete this.cleanupDate;
|
|
@@ -1076,13 +1335,9 @@ function applyManager(managers, controller) {
|
|
|
1076
1335
|
}
|
|
1077
1336
|
return managers.map((manager, i) => {
|
|
1078
1337
|
if (!manager.middleware) manager.middleware = manager.getMiddleware == null ? void 0 : manager.getMiddleware();
|
|
1079
|
-
return
|
|
1080
|
-
dispatch,
|
|
1081
|
-
getState
|
|
1082
|
-
}) => {
|
|
1338
|
+
return api => {
|
|
1083
1339
|
if (i === 0) {
|
|
1084
|
-
controller.
|
|
1085
|
-
controller.getState = getState;
|
|
1340
|
+
controller.bindMiddleware(api);
|
|
1086
1341
|
}
|
|
1087
1342
|
// controller is a superset of the middleware API
|
|
1088
1343
|
return manager.middleware(controller);
|
|
@@ -1094,6 +1349,21 @@ function applyManager(managers, controller) {
|
|
|
1094
1349
|
|
|
1095
1350
|
/* The next are types from React; but we don't want dependencies on it */
|
|
1096
1351
|
|
|
1352
|
+
function initManager(managers, controller, initialState) {
|
|
1353
|
+
return () => {
|
|
1354
|
+
managers.forEach(manager => {
|
|
1355
|
+
manager.init == null || manager.init(initialState);
|
|
1356
|
+
});
|
|
1357
|
+
controller.gcPolicy.init(controller);
|
|
1358
|
+
return () => {
|
|
1359
|
+
managers.forEach(manager => {
|
|
1360
|
+
manager.cleanup();
|
|
1361
|
+
});
|
|
1362
|
+
controller.gcPolicy.cleanup();
|
|
1363
|
+
};
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1097
1367
|
class BrowserConnectionListener {
|
|
1098
1368
|
isOnline() {
|
|
1099
1369
|
if (navigator.onLine !== undefined) {
|
|
@@ -1142,29 +1412,8 @@ var DefaultConnectionListener$1 = DefaultConnectionListener;
|
|
|
1142
1412
|
* @see https://dataclient.io/docs/api/PollingSubscription
|
|
1143
1413
|
*/
|
|
1144
1414
|
class PollingSubscription {
|
|
1415
|
+
frequencyHistogram = new Map();
|
|
1145
1416
|
constructor(action, controller, connectionListener) {
|
|
1146
|
-
this.frequencyHistogram = new Map();
|
|
1147
|
-
/** What happens when browser goes offline */
|
|
1148
|
-
this.offlineListener = () => {
|
|
1149
|
-
// this clears existing listeners, so no need to clear offline listener
|
|
1150
|
-
this.cleanup();
|
|
1151
|
-
this.connectionListener.addOnlineListener(this.onlineListener);
|
|
1152
|
-
};
|
|
1153
|
-
/** What happens when browser comes online */
|
|
1154
|
-
this.onlineListener = () => {
|
|
1155
|
-
this.connectionListener.removeOnlineListener(this.onlineListener);
|
|
1156
|
-
const now = Date.now();
|
|
1157
|
-
this.startId = setTimeout(() => {
|
|
1158
|
-
if (this.startId) {
|
|
1159
|
-
delete this.startId;
|
|
1160
|
-
this.update();
|
|
1161
|
-
this.run();
|
|
1162
|
-
} else if (process.env.NODE_ENV !== 'production') {
|
|
1163
|
-
console.warn(`Poll setTimeout for ${this.key} still running, but timeoutId deleted`);
|
|
1164
|
-
}
|
|
1165
|
-
}, Math.max(0, this.lastFetchTime() - now + this.frequency));
|
|
1166
|
-
this.connectionListener.addOfflineListener(this.offlineListener);
|
|
1167
|
-
};
|
|
1168
1417
|
if (action.endpoint.pollFrequency === undefined) throw new Error('frequency needed for polling subscription');
|
|
1169
1418
|
this.endpoint = action.endpoint;
|
|
1170
1419
|
this.frequency = action.endpoint.pollFrequency;
|
|
@@ -1256,6 +1505,30 @@ class PollingSubscription {
|
|
|
1256
1505
|
// stop any errors here from bubbling
|
|
1257
1506
|
this.controller.fetch(endpoint, ...this.args).catch(() => null);
|
|
1258
1507
|
}
|
|
1508
|
+
|
|
1509
|
+
/** What happens when browser goes offline */
|
|
1510
|
+
offlineListener = () => {
|
|
1511
|
+
// this clears existing listeners, so no need to clear offline listener
|
|
1512
|
+
this.cleanup();
|
|
1513
|
+
this.connectionListener.addOnlineListener(this.onlineListener);
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
/** What happens when browser comes online */
|
|
1517
|
+
onlineListener = () => {
|
|
1518
|
+
this.connectionListener.removeOnlineListener(this.onlineListener);
|
|
1519
|
+
const now = Date.now();
|
|
1520
|
+
this.startId = setTimeout(() => {
|
|
1521
|
+
if (this.startId) {
|
|
1522
|
+
delete this.startId;
|
|
1523
|
+
this.update();
|
|
1524
|
+
this.run();
|
|
1525
|
+
} else if (process.env.NODE_ENV !== 'production') {
|
|
1526
|
+
console.warn(`Poll setTimeout for ${this.key} still running, but timeoutId deleted`);
|
|
1527
|
+
}
|
|
1528
|
+
}, Math.max(0, this.lastFetchTime() - now + this.frequency));
|
|
1529
|
+
this.connectionListener.addOfflineListener(this.offlineListener);
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1259
1532
|
/** Run polling process with current frequency
|
|
1260
1533
|
*
|
|
1261
1534
|
* Will clean up old poll interval on next run
|
|
@@ -1295,30 +1568,31 @@ class PollingSubscription {
|
|
|
1295
1568
|
* @see https://dataclient.io/docs/api/SubscriptionManager
|
|
1296
1569
|
*/
|
|
1297
1570
|
class SubscriptionManager {
|
|
1571
|
+
subscriptions = {};
|
|
1572
|
+
controller = new Controller();
|
|
1298
1573
|
constructor(Subscription) {
|
|
1299
|
-
this.subscriptions = {};
|
|
1300
|
-
this.controller = new Controller();
|
|
1301
|
-
this.middleware = controller => {
|
|
1302
|
-
this.controller = controller;
|
|
1303
|
-
return next => action => {
|
|
1304
|
-
switch (action.type) {
|
|
1305
|
-
case SUBSCRIBE:
|
|
1306
|
-
try {
|
|
1307
|
-
this.handleSubscribe(action);
|
|
1308
|
-
} catch (e) {
|
|
1309
|
-
console.error(e);
|
|
1310
|
-
}
|
|
1311
|
-
return Promise.resolve();
|
|
1312
|
-
case UNSUBSCRIBE:
|
|
1313
|
-
this.handleUnsubscribe(action);
|
|
1314
|
-
return Promise.resolve();
|
|
1315
|
-
default:
|
|
1316
|
-
return next(action);
|
|
1317
|
-
}
|
|
1318
|
-
};
|
|
1319
|
-
};
|
|
1320
1574
|
this.Subscription = Subscription;
|
|
1321
1575
|
}
|
|
1576
|
+
middleware = controller => {
|
|
1577
|
+
this.controller = controller;
|
|
1578
|
+
return next => action => {
|
|
1579
|
+
switch (action.type) {
|
|
1580
|
+
case SUBSCRIBE:
|
|
1581
|
+
try {
|
|
1582
|
+
this.handleSubscribe(action);
|
|
1583
|
+
} catch (e) {
|
|
1584
|
+
console.error(e);
|
|
1585
|
+
}
|
|
1586
|
+
return Promise.resolve();
|
|
1587
|
+
case UNSUBSCRIBE:
|
|
1588
|
+
this.handleUnsubscribe(action);
|
|
1589
|
+
return Promise.resolve();
|
|
1590
|
+
default:
|
|
1591
|
+
return next(action);
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1322
1596
|
/** Ensures all subscriptions are cleaned up. */
|
|
1323
1597
|
cleanup() {
|
|
1324
1598
|
for (const key in this.subscriptions) {
|
|
@@ -1529,15 +1803,15 @@ class LogoutManager {
|
|
|
1529
1803
|
handleLogout,
|
|
1530
1804
|
shouldLogout
|
|
1531
1805
|
} = {}) {
|
|
1532
|
-
this.middleware = controller => next => async action => {
|
|
1533
|
-
await next(action);
|
|
1534
|
-
if (action.type === SET_RESPONSE && action.error && this.shouldLogout(action.response)) {
|
|
1535
|
-
this.handleLogout(controller);
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
1806
|
if (handleLogout) this.handleLogout = handleLogout;
|
|
1539
1807
|
if (shouldLogout) this.shouldLogout = shouldLogout;
|
|
1540
1808
|
}
|
|
1809
|
+
middleware = controller => next => async action => {
|
|
1810
|
+
await next(action);
|
|
1811
|
+
if (action.type === SET_RESPONSE && action.error && this.shouldLogout(action.response)) {
|
|
1812
|
+
this.handleLogout(controller);
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1541
1815
|
cleanup() {}
|
|
1542
1816
|
shouldLogout(error) {
|
|
1543
1817
|
// 401 indicates reauthorization is needed
|
|
@@ -1548,17 +1822,15 @@ class LogoutManager {
|
|
|
1548
1822
|
}
|
|
1549
1823
|
}
|
|
1550
1824
|
|
|
1551
|
-
Object.
|
|
1552
|
-
return Object.prototype.hasOwnProperty.call(it, key);
|
|
1553
|
-
};
|
|
1554
|
-
|
|
1555
|
-
Object.defineProperty(exports, 'ExpiryStatus', {
|
|
1825
|
+
Object.defineProperty(exports, "ExpiryStatus", {
|
|
1556
1826
|
enumerable: true,
|
|
1557
1827
|
get: function () { return normalizr.ExpiryStatus; }
|
|
1558
1828
|
});
|
|
1559
1829
|
exports.Controller = Controller;
|
|
1560
1830
|
exports.DefaultConnectionListener = DefaultConnectionListener$1;
|
|
1561
1831
|
exports.DevToolsManager = DevToolsManager;
|
|
1832
|
+
exports.GCPolicy = GCPolicy;
|
|
1833
|
+
exports.ImmortalGCPolicy = ImmortalGCPolicy;
|
|
1562
1834
|
exports.LogoutManager = LogoutManager;
|
|
1563
1835
|
exports.NetworkManager = NetworkManager;
|
|
1564
1836
|
exports.PollingSubscription = PollingSubscription;
|
|
@@ -1569,4 +1841,5 @@ exports.actionTypes = actionTypes;
|
|
|
1569
1841
|
exports.actions = index;
|
|
1570
1842
|
exports.applyManager = applyManager;
|
|
1571
1843
|
exports.createReducer = createReducer;
|
|
1844
|
+
exports.initManager = initManager;
|
|
1572
1845
|
exports.initialState = initialState;
|