@data-client/core 0.14.16 → 0.14.19
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 +556 -280
- package/dist/index.umd.min.js +1 -1
- package/legacy/actions.js +1 -1
- package/legacy/controller/Controller.js +101 -12
- package/legacy/controller/actions/createFetch.js +3 -2
- package/legacy/controller/actions/createSubscription.js +3 -3
- package/legacy/controller/ensurePojo.js +3 -2
- package/legacy/index.js +3 -4
- package/legacy/manager/DevtoolsManager.js +14 -9
- package/legacy/manager/NetworkManager.js +8 -5
- package/legacy/manager/PollingSubscription.js +6 -3
- package/legacy/manager/SubscriptionManager.js +4 -3
- package/legacy/manager/applyManager.js +3 -7
- package/legacy/manager/initManager.js +15 -0
- package/legacy/state/GCPolicy.js +153 -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/actionTypes.d.ts.map +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 +97 -9
- package/lib/controller/actions/createSubscription.js +3 -3
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -4
- package/lib/manager/DevtoolsManager.d.ts.map +1 -1
- package/lib/manager/DevtoolsManager.js +7 -5
- package/lib/manager/NetworkManager.js +3 -2
- package/lib/manager/PollingSubscription.js +1 -1
- 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 +153 -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 +5 -4
- package/lib/state/reducer/setReducer.d.ts.map +1 -1
- package/lib/state/reducer/setResponseReducer.d.ts +6 -4
- 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 +12 -4
- package/src/actions.ts +2 -1
- package/src/controller/Controller.ts +206 -9
- 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/controller/actions/createSubscription.ts +2 -2
- package/src/index.ts +2 -6
- package/src/manager/DevtoolsManager.ts +2 -3
- package/src/manager/PollingSubscription.ts +9 -9
- package/src/manager/SubscriptionManager.ts +1 -2
- 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
|
@@ -35,17 +35,23 @@ import {
|
|
|
35
35
|
} from './actions/index.js';
|
|
36
36
|
import ensurePojo from './ensurePojo.js';
|
|
37
37
|
import type { EndpointUpdateFunction } from './types.js';
|
|
38
|
+
import { ReduxMiddlewareAPI } from '../manager/applyManager.js';
|
|
39
|
+
import type { GCInterface } from '../state/GCPolicy.js';
|
|
40
|
+
import { ImmortalGCPolicy } from '../state/GCPolicy.js';
|
|
38
41
|
import { initialState } from '../state/reducer/createReducer.js';
|
|
39
42
|
import selectMeta from '../state/selectMeta.js';
|
|
40
|
-
import type { ActionTypes, State } from '../types.js';
|
|
43
|
+
import type { ActionTypes, Dispatch, State } from '../types.js';
|
|
41
44
|
|
|
42
45
|
export type GenericDispatch = (value: any) => Promise<void>;
|
|
43
46
|
export type DataClientDispatch = (value: ActionTypes) => Promise<void>;
|
|
44
47
|
|
|
45
|
-
interface
|
|
48
|
+
export interface ControllerConstructorProps<
|
|
49
|
+
D extends GenericDispatch = DataClientDispatch,
|
|
50
|
+
> {
|
|
46
51
|
dispatch?: D;
|
|
47
52
|
getState?: () => State<unknown>;
|
|
48
53
|
memo?: Pick<MemoCache, 'denormalize' | 'query' | 'buildQueryKey'>;
|
|
54
|
+
gcPolicy?: GCInterface;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
const unsetDispatch = (action: unknown): Promise<void> => {
|
|
@@ -65,6 +71,7 @@ const unsetState = (): State<unknown> => {
|
|
|
65
71
|
* @see https://dataclient.io/docs/api/Controller
|
|
66
72
|
*/
|
|
67
73
|
export default class Controller<
|
|
74
|
+
// NOTE: We template on entire dispatch, so we can be contravariant on ActionTypes
|
|
68
75
|
D extends GenericDispatch = DataClientDispatch,
|
|
69
76
|
> {
|
|
70
77
|
/**
|
|
@@ -72,7 +79,7 @@ export default class Controller<
|
|
|
72
79
|
*
|
|
73
80
|
* @see https://dataclient.io/docs/api/Controller#dispatch
|
|
74
81
|
*/
|
|
75
|
-
declare
|
|
82
|
+
declare protected _dispatch: D;
|
|
76
83
|
/**
|
|
77
84
|
* Gets the latest state snapshot that is fully committed.
|
|
78
85
|
*
|
|
@@ -80,7 +87,7 @@ export default class Controller<
|
|
|
80
87
|
* This should *not* be used to render; instead useSuspense() or useCache()
|
|
81
88
|
* @see https://dataclient.io/docs/api/Controller#getState
|
|
82
89
|
*/
|
|
83
|
-
declare
|
|
90
|
+
declare getState: () => State<unknown>;
|
|
84
91
|
/**
|
|
85
92
|
* Singleton to maintain referential equality between calls
|
|
86
93
|
*/
|
|
@@ -89,14 +96,43 @@ export default class Controller<
|
|
|
89
96
|
'denormalize' | 'query' | 'buildQueryKey'
|
|
90
97
|
>;
|
|
91
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Handles garbage collection
|
|
101
|
+
*/
|
|
102
|
+
declare readonly gcPolicy: GCInterface;
|
|
103
|
+
|
|
92
104
|
constructor({
|
|
93
105
|
dispatch = unsetDispatch as any,
|
|
94
106
|
getState = unsetState,
|
|
95
107
|
memo = new MemoCache(),
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
gcPolicy = new ImmortalGCPolicy(),
|
|
109
|
+
}: ControllerConstructorProps<D> = {}) {
|
|
110
|
+
this._dispatch = dispatch;
|
|
98
111
|
this.getState = getState;
|
|
99
112
|
this.memo = memo;
|
|
113
|
+
this.gcPolicy = gcPolicy;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// TODO: drop when drop support for destructuring (0.14 and below)
|
|
117
|
+
set dispatch(dispatch: D) {
|
|
118
|
+
/* istanbul ignore next */
|
|
119
|
+
this._dispatch = dispatch;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// TODO: drop when drop support for destructuring (0.14 and below)
|
|
123
|
+
get dispatch(): D {
|
|
124
|
+
return this._dispatch;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
bindMiddleware({
|
|
128
|
+
dispatch,
|
|
129
|
+
getState,
|
|
130
|
+
}: {
|
|
131
|
+
dispatch: D;
|
|
132
|
+
getState: ReduxMiddlewareAPI['getState'];
|
|
133
|
+
}) {
|
|
134
|
+
this._dispatch = dispatch;
|
|
135
|
+
this.getState = getState;
|
|
100
136
|
}
|
|
101
137
|
|
|
102
138
|
/*************** Action Dispatchers ***************/
|
|
@@ -336,7 +372,7 @@ export default class Controller<
|
|
|
336
372
|
* Gets a snapshot (https://dataclient.io/docs/api/Snapshot)
|
|
337
373
|
* @see https://dataclient.io/docs/api/Controller#snapshot
|
|
338
374
|
*/
|
|
339
|
-
snapshot = (state: State<unknown>, fetchedAt?: number):
|
|
375
|
+
snapshot = (state: State<unknown>, fetchedAt?: number): Snapshot<unknown> => {
|
|
340
376
|
return new Snapshot(this, state, fetchedAt);
|
|
341
377
|
};
|
|
342
378
|
|
|
@@ -389,6 +425,7 @@ export default class Controller<
|
|
|
389
425
|
data: DenormalizeNullable<E['schema']>;
|
|
390
426
|
expiryStatus: ExpiryStatus;
|
|
391
427
|
expiresAt: number;
|
|
428
|
+
countRef: () => () => void;
|
|
392
429
|
};
|
|
393
430
|
|
|
394
431
|
getResponse<
|
|
@@ -403,6 +440,7 @@ export default class Controller<
|
|
|
403
440
|
data: DenormalizeNullable<E['schema']>;
|
|
404
441
|
expiryStatus: ExpiryStatus;
|
|
405
442
|
expiresAt: number;
|
|
443
|
+
countRef: () => () => void;
|
|
406
444
|
};
|
|
407
445
|
|
|
408
446
|
getResponse(
|
|
@@ -412,6 +450,51 @@ export default class Controller<
|
|
|
412
450
|
data: unknown;
|
|
413
451
|
expiryStatus: ExpiryStatus;
|
|
414
452
|
expiresAt: number;
|
|
453
|
+
countRef: () => () => void;
|
|
454
|
+
} {
|
|
455
|
+
// TODO: breaking: only return data
|
|
456
|
+
return this.getResponseMeta(endpoint, ...rest);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Gets the (globally referentially stable) response for a given endpoint/args pair from state given.
|
|
461
|
+
* @see https://dataclient.io/docs/api/Controller#getResponseMeta
|
|
462
|
+
*/
|
|
463
|
+
getResponseMeta<E extends EndpointInterface>(
|
|
464
|
+
endpoint: E,
|
|
465
|
+
...rest:
|
|
466
|
+
| readonly [null, State<unknown>]
|
|
467
|
+
| readonly [...Parameters<E>, State<unknown>]
|
|
468
|
+
): {
|
|
469
|
+
data: DenormalizeNullable<E['schema']>;
|
|
470
|
+
expiryStatus: ExpiryStatus;
|
|
471
|
+
expiresAt: number;
|
|
472
|
+
countRef: () => () => void;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
getResponseMeta<
|
|
476
|
+
E extends Pick<EndpointInterface, 'key' | 'schema' | 'invalidIfStale'>,
|
|
477
|
+
>(
|
|
478
|
+
endpoint: E,
|
|
479
|
+
...rest: readonly [
|
|
480
|
+
...(readonly [...Parameters<E['key']>] | readonly [null]),
|
|
481
|
+
State<unknown>,
|
|
482
|
+
]
|
|
483
|
+
): {
|
|
484
|
+
data: DenormalizeNullable<E['schema']>;
|
|
485
|
+
expiryStatus: ExpiryStatus;
|
|
486
|
+
expiresAt: number;
|
|
487
|
+
countRef: () => () => void;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
getResponseMeta(
|
|
491
|
+
endpoint: EndpointInterface,
|
|
492
|
+
...rest: readonly [...unknown[], State<unknown>]
|
|
493
|
+
): {
|
|
494
|
+
data: unknown;
|
|
495
|
+
expiryStatus: ExpiryStatus;
|
|
496
|
+
expiresAt: number;
|
|
497
|
+
countRef: () => () => void;
|
|
415
498
|
} {
|
|
416
499
|
const state = rest[rest.length - 1] as State<unknown>;
|
|
417
500
|
// this is typescript generics breaking
|
|
@@ -446,12 +529,14 @@ export default class Controller<
|
|
|
446
529
|
data: input as any,
|
|
447
530
|
expiryStatus: ExpiryStatus.Valid,
|
|
448
531
|
expiresAt: Infinity,
|
|
532
|
+
countRef: () => () => undefined,
|
|
449
533
|
};
|
|
450
534
|
}
|
|
451
535
|
|
|
452
536
|
let isInvalid = false;
|
|
453
537
|
if (shouldQuery) {
|
|
454
538
|
isInvalid = !validateQueryKey(input);
|
|
539
|
+
// endpoint without entities
|
|
455
540
|
} else if (!schema || !schemaHasEntity(schema)) {
|
|
456
541
|
return {
|
|
457
542
|
data: cacheEndpoints,
|
|
@@ -460,11 +545,12 @@ export default class Controller<
|
|
|
460
545
|
: cacheEndpoints && !endpoint.invalidIfStale ? ExpiryStatus.Valid
|
|
461
546
|
: ExpiryStatus.InvalidIfStale,
|
|
462
547
|
expiresAt: expiresAt || 0,
|
|
548
|
+
countRef: this.gcPolicy.createCountRef({ key }),
|
|
463
549
|
};
|
|
464
550
|
}
|
|
465
551
|
|
|
466
552
|
// second argument is false if any entities are missing
|
|
467
|
-
|
|
553
|
+
|
|
468
554
|
const { data, paths } = this.memo.denormalize(
|
|
469
555
|
schema,
|
|
470
556
|
input,
|
|
@@ -477,6 +563,7 @@ export default class Controller<
|
|
|
477
563
|
|
|
478
564
|
return this.getSchemaResponse(
|
|
479
565
|
data,
|
|
566
|
+
key,
|
|
480
567
|
paths,
|
|
481
568
|
state.entityMeta,
|
|
482
569
|
expiresAt,
|
|
@@ -505,8 +592,55 @@ export default class Controller<
|
|
|
505
592
|
return this.memo.query(schema, args, state.entities as any, state.indexes);
|
|
506
593
|
}
|
|
507
594
|
|
|
595
|
+
/**
|
|
596
|
+
* Queries the store for a Querable schema; providing related metadata
|
|
597
|
+
* @see https://dataclient.io/docs/api/Controller#getQueryMeta
|
|
598
|
+
*/
|
|
599
|
+
getQueryMeta<S extends Queryable>(
|
|
600
|
+
schema: S,
|
|
601
|
+
...rest: readonly [
|
|
602
|
+
...SchemaArgs<S>,
|
|
603
|
+
Pick<State<unknown>, 'entities' | 'entityMeta'>,
|
|
604
|
+
]
|
|
605
|
+
): {
|
|
606
|
+
data: DenormalizeNullable<S> | undefined;
|
|
607
|
+
countRef: () => () => void;
|
|
608
|
+
} {
|
|
609
|
+
const state = rest[rest.length - 1] as State<any>;
|
|
610
|
+
// this is typescript generics breaking
|
|
611
|
+
const args: any = rest
|
|
612
|
+
.slice(0, rest.length - 1)
|
|
613
|
+
.map(ensurePojo) as SchemaArgs<S>;
|
|
614
|
+
|
|
615
|
+
// TODO: breaking: Switch back to this.memo.query(schema, args, state.entities as any, state.indexes) to do
|
|
616
|
+
// this logic
|
|
617
|
+
const input = this.memo.buildQueryKey(
|
|
618
|
+
schema,
|
|
619
|
+
args,
|
|
620
|
+
state.entities as any,
|
|
621
|
+
state.indexes,
|
|
622
|
+
JSON.stringify(args),
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
if (!input) {
|
|
626
|
+
return { data: undefined, countRef: () => () => undefined };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const { data, paths } = this.memo.denormalize(
|
|
630
|
+
schema,
|
|
631
|
+
input,
|
|
632
|
+
state.entities,
|
|
633
|
+
args,
|
|
634
|
+
);
|
|
635
|
+
return {
|
|
636
|
+
data: typeof data === 'symbol' ? undefined : (data as any),
|
|
637
|
+
countRef: this.gcPolicy.createCountRef({ paths }),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
508
641
|
private getSchemaResponse<T>(
|
|
509
642
|
data: T,
|
|
643
|
+
key: string,
|
|
510
644
|
paths: EntityPath[],
|
|
511
645
|
entityMeta: State<unknown>['entityMeta'],
|
|
512
646
|
expiresAt: number,
|
|
@@ -516,6 +650,7 @@ export default class Controller<
|
|
|
516
650
|
data: T;
|
|
517
651
|
expiryStatus: ExpiryStatus;
|
|
518
652
|
expiresAt: number;
|
|
653
|
+
countRef: () => () => void;
|
|
519
654
|
} {
|
|
520
655
|
const invalidDenormalize = typeof data === 'symbol';
|
|
521
656
|
|
|
@@ -533,7 +668,12 @@ export default class Controller<
|
|
|
533
668
|
: invalidDenormalize || invalidIfStale ? ExpiryStatus.InvalidIfStale
|
|
534
669
|
: ExpiryStatus.Valid;
|
|
535
670
|
|
|
536
|
-
return {
|
|
671
|
+
return {
|
|
672
|
+
data,
|
|
673
|
+
expiryStatus,
|
|
674
|
+
expiresAt,
|
|
675
|
+
countRef: this.gcPolicy.createCountRef({ key, paths }),
|
|
676
|
+
};
|
|
537
677
|
}
|
|
538
678
|
}
|
|
539
679
|
|
|
@@ -639,6 +779,49 @@ class Snapshot<T = unknown> implements SnapshotInterface {
|
|
|
639
779
|
return this.controller.getResponse(endpoint, ...args, this.state);
|
|
640
780
|
}
|
|
641
781
|
|
|
782
|
+
/** @see https://dataclient.io/docs/api/Snapshot#getResponseMeta */
|
|
783
|
+
getResponseMeta<E extends EndpointInterface>(
|
|
784
|
+
endpoint: E,
|
|
785
|
+
...args: readonly [null]
|
|
786
|
+
): {
|
|
787
|
+
data: DenormalizeNullable<E['schema']>;
|
|
788
|
+
expiryStatus: ExpiryStatus;
|
|
789
|
+
expiresAt: number;
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
getResponseMeta<E extends EndpointInterface>(
|
|
793
|
+
endpoint: E,
|
|
794
|
+
...args: readonly [...Parameters<E>]
|
|
795
|
+
): {
|
|
796
|
+
data: DenormalizeNullable<E['schema']>;
|
|
797
|
+
expiryStatus: ExpiryStatus;
|
|
798
|
+
expiresAt: number;
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
getResponseMeta<
|
|
802
|
+
E extends Pick<EndpointInterface, 'key' | 'schema' | 'invalidIfStale'>,
|
|
803
|
+
>(
|
|
804
|
+
endpoint: E,
|
|
805
|
+
...args: readonly [...Parameters<E['key']>] | readonly [null]
|
|
806
|
+
): {
|
|
807
|
+
data: DenormalizeNullable<E['schema']>;
|
|
808
|
+
expiryStatus: ExpiryStatus;
|
|
809
|
+
expiresAt: number;
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
getResponseMeta<
|
|
813
|
+
E extends Pick<EndpointInterface, 'key' | 'schema' | 'invalidIfStale'>,
|
|
814
|
+
>(
|
|
815
|
+
endpoint: E,
|
|
816
|
+
...args: readonly [...Parameters<E['key']>] | readonly [null]
|
|
817
|
+
): {
|
|
818
|
+
data: DenormalizeNullable<E['schema']>;
|
|
819
|
+
expiryStatus: ExpiryStatus;
|
|
820
|
+
expiresAt: number;
|
|
821
|
+
} {
|
|
822
|
+
return this.controller.getResponseMeta(endpoint, ...args, this.state);
|
|
823
|
+
}
|
|
824
|
+
|
|
642
825
|
/** @see https://dataclient.io/docs/api/Snapshot#getError */
|
|
643
826
|
getError<E extends EndpointInterface>(
|
|
644
827
|
endpoint: E,
|
|
@@ -667,4 +850,18 @@ class Snapshot<T = unknown> implements SnapshotInterface {
|
|
|
667
850
|
): DenormalizeNullable<S> | undefined {
|
|
668
851
|
return this.controller.get(schema, ...args, this.state);
|
|
669
852
|
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Queries the store for a Querable schema; providing related metadata
|
|
856
|
+
* @see https://dataclient.io/docs/api/Snapshot#getQueryMeta
|
|
857
|
+
*/
|
|
858
|
+
getQueryMeta<S extends Queryable>(
|
|
859
|
+
schema: S,
|
|
860
|
+
...args: SchemaArgs<S>
|
|
861
|
+
): {
|
|
862
|
+
data: DenormalizeNullable<S> | undefined;
|
|
863
|
+
countRef: () => () => void;
|
|
864
|
+
} {
|
|
865
|
+
return this.controller.getQueryMeta(schema, ...args, this.state);
|
|
866
|
+
}
|
|
670
867
|
}
|
|
@@ -50,19 +50,21 @@ describe('Controller', () => {
|
|
|
50
50
|
meta: {
|
|
51
51
|
[fetchKey]: {
|
|
52
52
|
date: Date.now(),
|
|
53
|
+
fetchedAt: Date.now(),
|
|
53
54
|
expiresAt: Date.now() + 10000,
|
|
54
55
|
},
|
|
55
56
|
},
|
|
56
57
|
};
|
|
57
58
|
const getState = () => state;
|
|
59
|
+
const dispatch = jest.fn(() => Promise.resolve());
|
|
58
60
|
const controller = new Controller({
|
|
59
|
-
dispatch
|
|
61
|
+
dispatch,
|
|
60
62
|
getState,
|
|
61
63
|
});
|
|
62
64
|
const article = await controller.fetchIfStale(CoolerArticleResource.get, {
|
|
63
65
|
id: payload.id,
|
|
64
66
|
});
|
|
65
|
-
expect(
|
|
67
|
+
expect(dispatch.mock.calls.length).toBe(0);
|
|
66
68
|
expect(article.title).toBe(payload.title);
|
|
67
69
|
});
|
|
68
70
|
it('should fetch if result stale', () => {
|
|
@@ -87,20 +89,22 @@ describe('Controller', () => {
|
|
|
87
89
|
meta: {
|
|
88
90
|
[fetchKey]: {
|
|
89
91
|
date: 0,
|
|
92
|
+
fetchedAt: 0,
|
|
90
93
|
expiresAt: 0,
|
|
91
94
|
},
|
|
92
95
|
},
|
|
93
96
|
};
|
|
94
97
|
const getState = () => state;
|
|
98
|
+
const dispatch = jest.fn(() => Promise.resolve());
|
|
95
99
|
const controller = new Controller({
|
|
96
|
-
dispatch
|
|
100
|
+
dispatch,
|
|
97
101
|
getState,
|
|
98
102
|
});
|
|
99
103
|
controller.fetchIfStale(CoolerArticleResource.get, {
|
|
100
104
|
id: payload.id,
|
|
101
105
|
});
|
|
102
106
|
|
|
103
|
-
expect(
|
|
107
|
+
expect(dispatch.mock.calls.length).toBe(1);
|
|
104
108
|
});
|
|
105
109
|
});
|
|
106
110
|
});
|
|
@@ -33,3 +33,18 @@ exports[`Controller.getResponse() infers schema with extra members but not set 1
|
|
|
33
33
|
},
|
|
34
34
|
}
|
|
35
35
|
`;
|
|
36
|
+
|
|
37
|
+
exports[`Snapshot.getResponseMeta() denormalizes schema with extra members but not set 1`] = `
|
|
38
|
+
{
|
|
39
|
+
"data": [
|
|
40
|
+
Tacos {
|
|
41
|
+
"id": "1",
|
|
42
|
+
"type": "foo",
|
|
43
|
+
},
|
|
44
|
+
Tacos {
|
|
45
|
+
"id": "2",
|
|
46
|
+
"type": "bar",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
@@ -3,24 +3,24 @@ import { Entity, schema } from '@data-client/endpoint';
|
|
|
3
3
|
import { initialState } from '../../state/reducer/createReducer';
|
|
4
4
|
import Controller from '../Controller';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[TacoList.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
6
|
+
class Tacos extends Entity {
|
|
7
|
+
type = '';
|
|
8
|
+
id = '';
|
|
9
|
+
}
|
|
10
|
+
const TacoList = new schema.Collection([Tacos]);
|
|
11
|
+
const entities = {
|
|
12
|
+
Tacos: {
|
|
13
|
+
1: { id: '1', type: 'foo' },
|
|
14
|
+
2: { id: '2', type: 'bar' },
|
|
15
|
+
},
|
|
16
|
+
[TacoList.key]: {
|
|
17
|
+
[TacoList.pk(undefined, undefined, '', [{ type: 'foo' }])]: ['1'],
|
|
18
|
+
[TacoList.pk(undefined, undefined, '', [{ type: 'bar' }])]: ['2'],
|
|
19
|
+
[TacoList.pk(undefined, undefined, '', [])]: ['1', '2'],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
23
22
|
|
|
23
|
+
describe('Controller.get()', () => {
|
|
24
24
|
it('query Entity based on pk', () => {
|
|
25
25
|
const controller = new Controller();
|
|
26
26
|
const state = {
|
|
@@ -273,3 +273,31 @@ describe('Controller.get()', () => {
|
|
|
273
273
|
() => controller.get(queryPerson, { id: '1', doesnotexist: 5 }, state);
|
|
274
274
|
});
|
|
275
275
|
});
|
|
276
|
+
|
|
277
|
+
describe('Snapshot.getQueryMeta()', () => {
|
|
278
|
+
it('query Entity based on pk', () => {
|
|
279
|
+
const controller = new Controller();
|
|
280
|
+
const state = {
|
|
281
|
+
...initialState,
|
|
282
|
+
entities,
|
|
283
|
+
};
|
|
284
|
+
const snapshot = controller.snapshot(state);
|
|
285
|
+
const taco = snapshot.getQueryMeta(Tacos, { id: '1' }).data;
|
|
286
|
+
expect(taco).toBeDefined();
|
|
287
|
+
expect(taco).toBeInstanceOf(Tacos);
|
|
288
|
+
expect(taco).toMatchSnapshot();
|
|
289
|
+
const taco2 = snapshot.getQueryMeta(Tacos, { id: '2' }).data;
|
|
290
|
+
expect(taco2).toBeDefined();
|
|
291
|
+
expect(taco2).toBeInstanceOf(Tacos);
|
|
292
|
+
expect(taco2).not.toEqual(taco);
|
|
293
|
+
// should maintain referential equality
|
|
294
|
+
expect(taco).toBe(snapshot.getQueryMeta(Tacos, { id: '1' }).data);
|
|
295
|
+
|
|
296
|
+
// @ts-expect-error
|
|
297
|
+
() => snapshot.getQueryMeta(Tacos, { id: { bob: 5 } });
|
|
298
|
+
// @ts-expect-error
|
|
299
|
+
expect(snapshot.getQueryMeta(Tacos, 5).data).toBeUndefined();
|
|
300
|
+
// @ts-expect-error
|
|
301
|
+
() => snapshot.getQueryMeta(Tacos, { doesnotexist: 5 });
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -174,3 +174,49 @@ describe('Controller.getResponse()', () => {
|
|
|
174
174
|
expect(second.expiresAt).toBe(expiresAt);
|
|
175
175
|
});
|
|
176
176
|
});
|
|
177
|
+
|
|
178
|
+
describe('Snapshot.getResponseMeta()', () => {
|
|
179
|
+
it('denormalizes schema with extra members but not set', () => {
|
|
180
|
+
const controller = new Contoller();
|
|
181
|
+
class Tacos extends Entity {
|
|
182
|
+
type = '';
|
|
183
|
+
id = '';
|
|
184
|
+
}
|
|
185
|
+
const ep = new Endpoint(() => Promise.resolve(), {
|
|
186
|
+
key() {
|
|
187
|
+
return 'mytest';
|
|
188
|
+
},
|
|
189
|
+
schema: {
|
|
190
|
+
data: [Tacos],
|
|
191
|
+
extra: '',
|
|
192
|
+
page: {
|
|
193
|
+
first: null,
|
|
194
|
+
second: undefined,
|
|
195
|
+
third: 0,
|
|
196
|
+
complex: { complex: true, next: false },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
const entities = {
|
|
201
|
+
Tacos: {
|
|
202
|
+
1: { id: '1', type: 'foo' },
|
|
203
|
+
2: { id: '2', type: 'bar' },
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const state = {
|
|
208
|
+
...initialState,
|
|
209
|
+
entities,
|
|
210
|
+
endpoints: {
|
|
211
|
+
[ep.key()]: {
|
|
212
|
+
data: ['1', '2'],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
const { data, expiryStatus } = controller
|
|
217
|
+
.snapshot(state)
|
|
218
|
+
.getResponseMeta(ep);
|
|
219
|
+
expect(expiryStatus).toBe(ExpiryStatus.Valid);
|
|
220
|
+
expect(data).toMatchSnapshot();
|
|
221
|
+
});
|
|
222
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
Object.hasOwn =
|
|
2
|
-
Object.hasOwn ||
|
|
3
|
-
/* istanbul ignore next */ function hasOwn(it, key) {
|
|
4
|
-
return Object.prototype.hasOwnProperty.call(it, key);
|
|
5
|
-
};
|
|
6
|
-
|
|
7
1
|
export * as __INTERNAL__ from './internal.js';
|
|
8
2
|
export type {
|
|
9
3
|
NetworkError,
|
|
@@ -29,11 +23,13 @@ export {
|
|
|
29
23
|
default as NetworkManager,
|
|
30
24
|
ResetError,
|
|
31
25
|
} from './manager/NetworkManager.js';
|
|
26
|
+
export * from './state/GCPolicy.js';
|
|
32
27
|
export {
|
|
33
28
|
default as createReducer,
|
|
34
29
|
initialState,
|
|
35
30
|
} from './state/reducer/createReducer.js';
|
|
36
31
|
export { default as applyManager } from './manager/applyManager.js';
|
|
32
|
+
export { default as initManager } from './manager/initManager.js';
|
|
37
33
|
|
|
38
34
|
export { default as Controller } from './controller/Controller.js';
|
|
39
35
|
export type {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-inner-declarations */
|
|
2
1
|
import type { DevToolsConfig } from './devtoolsTypes.js';
|
|
3
2
|
import type { Controller, EndpointInterface } from '../index.js';
|
|
4
3
|
import type { Middleware } from '../middlewareTypes.js';
|
|
@@ -90,10 +89,10 @@ if (process.env.NODE_ENV !== 'production') {
|
|
|
90
89
|
*/
|
|
91
90
|
export default class DevToolsManager implements Manager {
|
|
92
91
|
declare middleware: Middleware;
|
|
93
|
-
protected
|
|
92
|
+
declare protected devTools: undefined | any;
|
|
94
93
|
protected started = false;
|
|
95
94
|
protected actions: [ActionTypes, State<unknown>][] = [];
|
|
96
|
-
protected
|
|
95
|
+
declare protected controller: Controller;
|
|
97
96
|
declare skipLogging?: (action: ActionTypes) => boolean;
|
|
98
97
|
maxBufferLength = 100;
|
|
99
98
|
|
|
@@ -14,16 +14,16 @@ import type { SubscribeAction } from '../types.js';
|
|
|
14
14
|
* @see https://dataclient.io/docs/api/PollingSubscription
|
|
15
15
|
*/
|
|
16
16
|
export default class PollingSubscription implements Subscription {
|
|
17
|
-
protected
|
|
18
|
-
protected
|
|
19
|
-
protected
|
|
20
|
-
protected
|
|
17
|
+
declare protected readonly endpoint: EndpointInterface;
|
|
18
|
+
declare protected readonly args: readonly any[];
|
|
19
|
+
declare protected readonly key: string;
|
|
20
|
+
declare protected frequency: number;
|
|
21
21
|
protected frequencyHistogram: Map<number, number> = new Map();
|
|
22
|
-
protected
|
|
23
|
-
protected
|
|
24
|
-
protected
|
|
25
|
-
protected
|
|
26
|
-
private
|
|
22
|
+
declare protected controller: Controller;
|
|
23
|
+
declare protected intervalId?: ReturnType<typeof setInterval>;
|
|
24
|
+
declare protected lastIntervalId?: ReturnType<typeof setInterval>;
|
|
25
|
+
declare protected startId?: ReturnType<typeof setTimeout>;
|
|
26
|
+
declare private connectionListener: ConnectionListener;
|
|
27
27
|
|
|
28
28
|
constructor(
|
|
29
29
|
action: Omit<SubscribeAction, 'type'>,
|
|
@@ -2,7 +2,6 @@ import { SUBSCRIBE, UNSUBSCRIBE } from '../actionTypes.js';
|
|
|
2
2
|
import Controller from '../controller/Controller.js';
|
|
3
3
|
import type {
|
|
4
4
|
Manager,
|
|
5
|
-
MiddlewareAPI,
|
|
6
5
|
Middleware,
|
|
7
6
|
UnsubscribeAction,
|
|
8
7
|
SubscribeAction,
|
|
@@ -40,7 +39,7 @@ export default class SubscriptionManager<
|
|
|
40
39
|
[key: string]: InstanceType<S>;
|
|
41
40
|
} = {};
|
|
42
41
|
|
|
43
|
-
protected
|
|
42
|
+
declare protected readonly Subscription: S;
|
|
44
43
|
protected controller: Controller = new Controller();
|
|
45
44
|
|
|
46
45
|
constructor(Subscription: S) {
|
|
@@ -18,14 +18,13 @@ export default function applyManager(
|
|
|
18
18
|
}
|
|
19
19
|
return managers.map((manager, i) => {
|
|
20
20
|
if (!manager.middleware) manager.middleware = manager.getMiddleware?.();
|
|
21
|
-
return (
|
|
21
|
+
return (api: ReduxMiddlewareAPI) => {
|
|
22
22
|
if (i === 0) {
|
|
23
|
-
(
|
|
24
|
-
(controller as any).getState = getState;
|
|
23
|
+
controller.bindMiddleware(api);
|
|
25
24
|
}
|
|
26
25
|
// controller is a superset of the middleware API
|
|
27
26
|
return (manager as Manager & { middleware: ReduxMiddleware }).middleware(
|
|
28
|
-
controller as
|
|
27
|
+
controller as any,
|
|
29
28
|
);
|
|
30
29
|
};
|
|
31
30
|
});
|