@expo/entity 0.19.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/EntityCacheAdapter.d.ts +2 -9
- package/build/EntityCacheAdapter.js.map +1 -1
- package/build/EntityMutationInfo.d.ts +26 -0
- package/build/EntityMutationInfo.js +10 -0
- package/build/EntityMutationInfo.js.map +1 -0
- package/build/EntityMutationTriggerConfiguration.d.ts +3 -3
- package/build/EntityMutationValidator.d.ts +2 -2
- package/build/EntityMutationValidator.js.map +1 -1
- package/build/EntityMutator.d.ts +4 -15
- package/build/EntityMutator.js +45 -41
- package/build/EntityMutator.js.map +1 -1
- package/build/EntityQueryContext.d.ts +24 -0
- package/build/EntityQueryContext.js +43 -0
- package/build/EntityQueryContext.js.map +1 -1
- package/build/EntityQueryContextProvider.js +1 -0
- package/build/EntityQueryContextProvider.js.map +1 -1
- package/build/__tests__/EntityEdges-test.js +99 -3
- package/build/__tests__/EntityEdges-test.js.map +1 -1
- package/build/__tests__/EntityMutator-MutationCacheConsistency-test.d.ts +1 -0
- package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js +81 -0
- package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js.map +1 -0
- package/build/__tests__/EntityMutator-test.js +9 -7
- package/build/__tests__/EntityMutator-test.js.map +1 -1
- package/build/__tests__/EntityQueryContext-test.d.ts +1 -0
- package/build/__tests__/EntityQueryContext-test.js +56 -0
- package/build/__tests__/EntityQueryContext-test.js.map +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/internal/ReadThroughEntityCache.d.ts +2 -3
- package/build/internal/ReadThroughEntityCache.js +2 -6
- package/build/internal/ReadThroughEntityCache.js.map +1 -1
- package/build/internal/__tests__/ReadThroughEntityCache-test.js +0 -32
- package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
- package/build/utils/testing/StubCacheAdapter.d.ts +6 -9
- package/build/utils/testing/StubCacheAdapter.js +0 -6
- package/build/utils/testing/StubCacheAdapter.js.map +1 -1
- package/package.json +1 -1
- package/src/EntityCacheAdapter.ts +2 -10
- package/src/EntityMutationInfo.ts +47 -0
- package/src/EntityMutationTriggerConfiguration.ts +3 -3
- package/src/EntityMutationValidator.ts +8 -2
- package/src/EntityMutator.ts +81 -68
- package/src/EntityQueryContext.ts +54 -0
- package/src/EntityQueryContextProvider.ts +1 -0
- package/src/__tests__/EntityEdges-test.ts +163 -9
- package/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts +105 -0
- package/src/__tests__/EntityMutator-test.ts +24 -6
- package/src/__tests__/EntityQueryContext-test.ts +82 -0
- package/src/index.ts +1 -0
- package/src/internal/ReadThroughEntityCache.ts +6 -28
- package/src/internal/__tests__/ReadThroughEntityCache-test.ts +0 -44
- package/src/utils/testing/StubCacheAdapter.ts +11 -17
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Entity from '../Entity';
|
|
2
|
+
import { EntityCompanionDefinition } from '../EntityCompanionProvider';
|
|
3
|
+
import EntityConfiguration from '../EntityConfiguration';
|
|
4
|
+
import { UUIDField } from '../EntityFields';
|
|
5
|
+
import { EntityMutationType, EntityTriggerMutationInfo } from '../EntityMutationInfo';
|
|
6
|
+
import { EntityNonTransactionalMutationTrigger } from '../EntityMutationTriggerConfiguration';
|
|
7
|
+
import EntityPrivacyPolicy from '../EntityPrivacyPolicy';
|
|
8
|
+
import ViewerContext from '../ViewerContext';
|
|
9
|
+
import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule';
|
|
10
|
+
import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider';
|
|
11
|
+
|
|
12
|
+
type BlahFields = {
|
|
13
|
+
id: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
class BlahEntityPrivacyPolicy extends EntityPrivacyPolicy<
|
|
17
|
+
BlahFields,
|
|
18
|
+
string,
|
|
19
|
+
ViewerContext,
|
|
20
|
+
BlahEntity
|
|
21
|
+
> {
|
|
22
|
+
protected override readonly createRules = [
|
|
23
|
+
new AlwaysAllowPrivacyPolicyRule<BlahFields, string, ViewerContext, BlahEntity>(),
|
|
24
|
+
];
|
|
25
|
+
protected override readonly readRules = [
|
|
26
|
+
new AlwaysAllowPrivacyPolicyRule<BlahFields, string, ViewerContext, BlahEntity>(),
|
|
27
|
+
];
|
|
28
|
+
protected override readonly updateRules = [
|
|
29
|
+
new AlwaysAllowPrivacyPolicyRule<BlahFields, string, ViewerContext, BlahEntity>(),
|
|
30
|
+
];
|
|
31
|
+
protected override readonly deleteRules = [
|
|
32
|
+
new AlwaysAllowPrivacyPolicyRule<BlahFields, string, ViewerContext, BlahEntity>(),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class BlahEntity extends Entity<BlahFields, string, ViewerContext> {
|
|
37
|
+
static getCompanionDefinition(): EntityCompanionDefinition<
|
|
38
|
+
BlahFields,
|
|
39
|
+
string,
|
|
40
|
+
ViewerContext,
|
|
41
|
+
BlahEntity,
|
|
42
|
+
BlahEntityPrivacyPolicy
|
|
43
|
+
> {
|
|
44
|
+
return blahCompanion;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const blahCompanion = new EntityCompanionDefinition({
|
|
49
|
+
entityClass: BlahEntity,
|
|
50
|
+
entityConfiguration: new EntityConfiguration<BlahFields>({
|
|
51
|
+
idField: 'id',
|
|
52
|
+
tableName: 'blah_table',
|
|
53
|
+
schema: {
|
|
54
|
+
id: new UUIDField({
|
|
55
|
+
columnName: 'id',
|
|
56
|
+
cache: true,
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
databaseAdapterFlavor: 'postgres',
|
|
60
|
+
cacheAdapterFlavor: 'redis',
|
|
61
|
+
}),
|
|
62
|
+
privacyPolicyClass: BlahEntityPrivacyPolicy,
|
|
63
|
+
mutationTriggers: () => ({
|
|
64
|
+
afterCommit: [new TestNonTransactionalMutationTrigger()],
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
class TestNonTransactionalMutationTrigger extends EntityNonTransactionalMutationTrigger<
|
|
69
|
+
BlahFields,
|
|
70
|
+
string,
|
|
71
|
+
ViewerContext,
|
|
72
|
+
BlahEntity
|
|
73
|
+
> {
|
|
74
|
+
async executeAsync(
|
|
75
|
+
viewerContext: ViewerContext,
|
|
76
|
+
entity: BlahEntity,
|
|
77
|
+
mutationInfo: EntityTriggerMutationInfo<BlahFields, string, ViewerContext, BlahEntity>
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
if (mutationInfo.type === EntityMutationType.DELETE) {
|
|
80
|
+
const entityLoaded = await BlahEntity.loader(viewerContext)
|
|
81
|
+
.enforcing()
|
|
82
|
+
.loadByIDNullableAsync(entity.getID());
|
|
83
|
+
if (entityLoaded) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
'should not have been able to re-load the entity after delete. this means the cache has not been cleared'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe('EntityMutator', () => {
|
|
93
|
+
test('cache consistency with post-commit callbacks', async () => {
|
|
94
|
+
const companionProvider = createUnitTestEntityCompanionProvider();
|
|
95
|
+
const viewerContext = new ViewerContext(companionProvider);
|
|
96
|
+
|
|
97
|
+
// put it in cache
|
|
98
|
+
const entity = await BlahEntity.creator(viewerContext).enforceCreateAsync();
|
|
99
|
+
const entityLoaded = await BlahEntity.loader(viewerContext)
|
|
100
|
+
.enforcing()
|
|
101
|
+
.loadByIDAsync(entity.getID());
|
|
102
|
+
|
|
103
|
+
await BlahEntity.enforceDeleteAsync(entityLoaded);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -14,12 +14,16 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
14
14
|
|
|
15
15
|
import EntityDatabaseAdapter from '../EntityDatabaseAdapter';
|
|
16
16
|
import EntityLoaderFactory from '../EntityLoaderFactory';
|
|
17
|
+
import {
|
|
18
|
+
EntityMutationType,
|
|
19
|
+
EntityTriggerMutationInfo,
|
|
20
|
+
EntityValidatorMutationInfo,
|
|
21
|
+
} from '../EntityMutationInfo';
|
|
17
22
|
import EntityMutationTriggerConfiguration, {
|
|
18
23
|
EntityMutationTrigger,
|
|
19
24
|
EntityNonTransactionalMutationTrigger,
|
|
20
25
|
} from '../EntityMutationTriggerConfiguration';
|
|
21
26
|
import EntityMutationValidator from '../EntityMutationValidator';
|
|
22
|
-
import { EntityMutationInfo, EntityMutationType } from '../EntityMutator';
|
|
23
27
|
import EntityMutatorFactory from '../EntityMutatorFactory';
|
|
24
28
|
import { EntityTransactionalQueryContext, EntityQueryContext } from '../EntityQueryContext';
|
|
25
29
|
import ViewerContext from '../ViewerContext';
|
|
@@ -53,7 +57,7 @@ class TestMutationTrigger extends EntityMutationTrigger<
|
|
|
53
57
|
_viewerContext: ViewerContext,
|
|
54
58
|
_queryContext: EntityQueryContext,
|
|
55
59
|
_entity: TestEntity,
|
|
56
|
-
_mutationInfo:
|
|
60
|
+
_mutationInfo: EntityTriggerMutationInfo<
|
|
57
61
|
TestFields,
|
|
58
62
|
string,
|
|
59
63
|
ViewerContext,
|
|
@@ -95,7 +99,13 @@ const verifyValidatorCounts = (
|
|
|
95
99
|
keyof TestFields
|
|
96
100
|
>[],
|
|
97
101
|
expectedCalls: number,
|
|
98
|
-
mutationInfo:
|
|
102
|
+
mutationInfo: EntityValidatorMutationInfo<
|
|
103
|
+
TestFields,
|
|
104
|
+
string,
|
|
105
|
+
ViewerContext,
|
|
106
|
+
TestEntity,
|
|
107
|
+
keyof TestFields
|
|
108
|
+
>
|
|
99
109
|
): void => {
|
|
100
110
|
for (const validator of mutationValidatorSpies) {
|
|
101
111
|
verify(
|
|
@@ -164,7 +174,13 @@ const verifyTriggerCounts = (
|
|
|
164
174
|
>,
|
|
165
175
|
boolean
|
|
166
176
|
>,
|
|
167
|
-
mutationInfo:
|
|
177
|
+
mutationInfo: EntityTriggerMutationInfo<
|
|
178
|
+
TestFields,
|
|
179
|
+
string,
|
|
180
|
+
ViewerContext,
|
|
181
|
+
TestEntity,
|
|
182
|
+
keyof TestFields
|
|
183
|
+
>
|
|
168
184
|
): void => {
|
|
169
185
|
Object.keys(executed).forEach((s) => {
|
|
170
186
|
if ((executed as any)[s]) {
|
|
@@ -767,7 +783,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
767
783
|
beforeDelete: true,
|
|
768
784
|
afterDelete: true,
|
|
769
785
|
},
|
|
770
|
-
{ type: EntityMutationType.DELETE }
|
|
786
|
+
{ type: EntityMutationType.DELETE, cascadingDeleteCause: null }
|
|
771
787
|
);
|
|
772
788
|
});
|
|
773
789
|
|
|
@@ -796,7 +812,9 @@ describe(EntityMutatorFactory, () => {
|
|
|
796
812
|
|
|
797
813
|
await entityMutatorFactory.forDelete(existingEntity, queryContext).enforceDeleteAsync();
|
|
798
814
|
|
|
799
|
-
verifyValidatorCounts(viewerContext, validatorSpies, 0, {
|
|
815
|
+
verifyValidatorCounts(viewerContext, validatorSpies, 0, {
|
|
816
|
+
type: EntityMutationType.DELETE as any,
|
|
817
|
+
});
|
|
800
818
|
});
|
|
801
819
|
});
|
|
802
820
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import invariant from 'invariant';
|
|
2
|
+
|
|
3
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
4
|
+
import ViewerContext from '../ViewerContext';
|
|
5
|
+
import { createUnitTestEntityCompanionProvider } from '../utils/testing/createUnitTestEntityCompanionProvider';
|
|
6
|
+
|
|
7
|
+
describe(EntityQueryContext, () => {
|
|
8
|
+
describe('callbacks', () => {
|
|
9
|
+
it('calls all callbacks, and calls invalidation first', async () => {
|
|
10
|
+
const companionProvider = createUnitTestEntityCompanionProvider();
|
|
11
|
+
const viewerContext = new ViewerContext(companionProvider);
|
|
12
|
+
|
|
13
|
+
const preCommitFirstCallback = jest.fn(async (): Promise<void> => {});
|
|
14
|
+
const preCommitSecondCallback = jest.fn(async (): Promise<void> => {});
|
|
15
|
+
const postCommitInvalidationCallback = jest.fn(async (): Promise<void> => {
|
|
16
|
+
invariant(
|
|
17
|
+
preCommitFirstCallback.mock.calls.length === 1,
|
|
18
|
+
'preCommit should be called before postCommitInvalidation'
|
|
19
|
+
);
|
|
20
|
+
invariant(
|
|
21
|
+
preCommitSecondCallback.mock.calls.length === 1,
|
|
22
|
+
'preCommit should be called before postCommitInvalidation'
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
const postCommitCallback = jest.fn(async (): Promise<void> => {
|
|
26
|
+
invariant(
|
|
27
|
+
preCommitFirstCallback.mock.calls.length === 1,
|
|
28
|
+
'preCommit should be called before postCommit'
|
|
29
|
+
);
|
|
30
|
+
invariant(
|
|
31
|
+
preCommitSecondCallback.mock.calls.length === 1,
|
|
32
|
+
'preCommit should be called before postCommit'
|
|
33
|
+
);
|
|
34
|
+
invariant(
|
|
35
|
+
postCommitInvalidationCallback.mock.calls.length === 1,
|
|
36
|
+
'postCommitInvalidation should be called before postCommit'
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await viewerContext.runInTransactionForDatabaseAdaptorFlavorAsync(
|
|
41
|
+
'postgres',
|
|
42
|
+
async (queryContext) => {
|
|
43
|
+
queryContext.appendPostCommitCallback(postCommitCallback);
|
|
44
|
+
queryContext.appendPostCommitInvalidationCallback(postCommitInvalidationCallback);
|
|
45
|
+
queryContext.appendPreCommitCallback(preCommitSecondCallback, 2);
|
|
46
|
+
queryContext.appendPreCommitCallback(preCommitFirstCallback, 1);
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(preCommitFirstCallback).toHaveBeenCalledTimes(1);
|
|
51
|
+
expect(preCommitSecondCallback).toHaveBeenCalledTimes(1);
|
|
52
|
+
expect(postCommitCallback).toHaveBeenCalledTimes(1);
|
|
53
|
+
expect(postCommitInvalidationCallback).toHaveBeenCalledTimes(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('prevents transaction from finishing when precommit throws (post commit callbacks are not called)', async () => {
|
|
57
|
+
const companionProvider = createUnitTestEntityCompanionProvider();
|
|
58
|
+
const viewerContext = new ViewerContext(companionProvider);
|
|
59
|
+
|
|
60
|
+
const preCommitCallback = jest.fn(async (): Promise<void> => {
|
|
61
|
+
throw new Error('wat');
|
|
62
|
+
});
|
|
63
|
+
const postCommitInvalidationCallback = jest.fn(async (): Promise<void> => {});
|
|
64
|
+
const postCommitCallback = jest.fn(async (): Promise<void> => {});
|
|
65
|
+
|
|
66
|
+
await expect(
|
|
67
|
+
viewerContext.runInTransactionForDatabaseAdaptorFlavorAsync(
|
|
68
|
+
'postgres',
|
|
69
|
+
async (queryContext) => {
|
|
70
|
+
queryContext.appendPostCommitCallback(postCommitCallback);
|
|
71
|
+
queryContext.appendPostCommitInvalidationCallback(postCommitInvalidationCallback);
|
|
72
|
+
queryContext.appendPreCommitCallback(preCommitCallback, 0);
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
).rejects.toThrowError('wat');
|
|
76
|
+
|
|
77
|
+
expect(preCommitCallback).toHaveBeenCalledTimes(1);
|
|
78
|
+
expect(postCommitCallback).toHaveBeenCalledTimes(0);
|
|
79
|
+
expect(postCommitInvalidationCallback).toHaveBeenCalledTimes(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ export { default as EntitySecondaryCacheLoader } from './EntitySecondaryCacheLoa
|
|
|
33
33
|
export * from './EntitySecondaryCacheLoader';
|
|
34
34
|
export * from './EntityMutator';
|
|
35
35
|
export { default as EntityMutationValidator } from './EntityMutationValidator';
|
|
36
|
+
export * from './EntityMutationInfo';
|
|
36
37
|
export * from './EntityMutationTriggerConfiguration';
|
|
37
38
|
export { default as EntityMutationTriggerConfiguration } from './EntityMutationTriggerConfiguration';
|
|
38
39
|
export { default as EntityMutatorFactory } from './EntityMutatorFactory';
|
|
@@ -3,11 +3,6 @@ import invariant from 'invariant';
|
|
|
3
3
|
import EntityCacheAdapter from '../EntityCacheAdapter';
|
|
4
4
|
import EntityConfiguration from '../EntityConfiguration';
|
|
5
5
|
import { filterMap } from '../utils/collections/maps';
|
|
6
|
-
import {
|
|
7
|
-
FieldTransformerMap,
|
|
8
|
-
transformCacheObjectToFields,
|
|
9
|
-
transformFieldsToCacheObject,
|
|
10
|
-
} from './EntityFieldTransformationUtils';
|
|
11
6
|
|
|
12
7
|
export enum CacheStatus {
|
|
13
8
|
HIT,
|
|
@@ -15,10 +10,10 @@ export enum CacheStatus {
|
|
|
15
10
|
NEGATIVE,
|
|
16
11
|
}
|
|
17
12
|
|
|
18
|
-
export type CacheLoadResult =
|
|
13
|
+
export type CacheLoadResult<TFields> =
|
|
19
14
|
| {
|
|
20
15
|
status: CacheStatus.HIT;
|
|
21
|
-
item: Readonly<
|
|
16
|
+
item: Readonly<TFields>;
|
|
22
17
|
}
|
|
23
18
|
| {
|
|
24
19
|
status: CacheStatus.MISS;
|
|
@@ -32,14 +27,10 @@ export type CacheLoadResult =
|
|
|
32
27
|
* {@link EntityCacheAdapter} within the {@link EntityDataManager}.
|
|
33
28
|
*/
|
|
34
29
|
export default class ReadThroughEntityCache<TFields> {
|
|
35
|
-
private readonly fieldTransformerMap: FieldTransformerMap;
|
|
36
|
-
|
|
37
30
|
constructor(
|
|
38
31
|
private readonly entityConfiguration: EntityConfiguration<TFields>,
|
|
39
32
|
private readonly entityCacheAdapter: EntityCacheAdapter<TFields>
|
|
40
|
-
) {
|
|
41
|
-
this.fieldTransformerMap = entityCacheAdapter.getFieldTransformerMap();
|
|
42
|
-
}
|
|
33
|
+
) {}
|
|
43
34
|
|
|
44
35
|
private isFieldCacheable<N extends keyof TFields>(fieldName: N): boolean {
|
|
45
36
|
return this.entityConfiguration.cacheableKeys.has(fieldName);
|
|
@@ -91,13 +82,7 @@ export default class ReadThroughEntityCache<TFields> {
|
|
|
91
82
|
const results: Map<NonNullable<TFields[N]>, readonly Readonly<TFields>[]> = new Map();
|
|
92
83
|
cacheLoadResults.forEach((cacheLoadResult, fieldValue) => {
|
|
93
84
|
if (cacheLoadResult.status === CacheStatus.HIT) {
|
|
94
|
-
results.set(fieldValue, [
|
|
95
|
-
transformCacheObjectToFields(
|
|
96
|
-
this.entityConfiguration,
|
|
97
|
-
this.fieldTransformerMap,
|
|
98
|
-
cacheLoadResult.item
|
|
99
|
-
),
|
|
100
|
-
]);
|
|
85
|
+
results.set(fieldValue, [cacheLoadResult.item]);
|
|
101
86
|
}
|
|
102
87
|
});
|
|
103
88
|
|
|
@@ -110,7 +95,7 @@ export default class ReadThroughEntityCache<TFields> {
|
|
|
110
95
|
return !objectsFromFulfillerForFv || objectsFromFulfillerForFv.length === 0;
|
|
111
96
|
});
|
|
112
97
|
|
|
113
|
-
const objectsToCache: Map<NonNullable<TFields[N]>,
|
|
98
|
+
const objectsToCache: Map<NonNullable<TFields[N]>, Readonly<TFields>> = new Map();
|
|
114
99
|
for (const [fieldValue, objects] of dbFetchResults.entries()) {
|
|
115
100
|
if (objects.length > 1) {
|
|
116
101
|
// multiple objects received for what was supposed to be a unique query, don't add to return map nor cache
|
|
@@ -123,14 +108,7 @@ export default class ReadThroughEntityCache<TFields> {
|
|
|
123
108
|
}
|
|
124
109
|
const uniqueObject = objects[0];
|
|
125
110
|
if (uniqueObject) {
|
|
126
|
-
objectsToCache.set(
|
|
127
|
-
fieldValue,
|
|
128
|
-
transformFieldsToCacheObject(
|
|
129
|
-
this.entityConfiguration,
|
|
130
|
-
this.fieldTransformerMap,
|
|
131
|
-
uniqueObject
|
|
132
|
-
)
|
|
133
|
-
);
|
|
111
|
+
objectsToCache.set(fieldValue, uniqueObject);
|
|
134
112
|
results.set(fieldValue, [uniqueObject]);
|
|
135
113
|
}
|
|
136
114
|
}
|
|
@@ -40,7 +40,6 @@ describe(ReadThroughEntityCache, () => {
|
|
|
40
40
|
describe('readManyThroughAsync', () => {
|
|
41
41
|
it('fetches from DB upon cache miss and caches the result', async () => {
|
|
42
42
|
const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
|
|
43
|
-
when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
|
|
44
43
|
const cacheAdapter = instance(cacheAdapterMock);
|
|
45
44
|
const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
|
|
46
45
|
const fetcher = createIdFetcher(['wat', 'who']);
|
|
@@ -77,7 +76,6 @@ describe(ReadThroughEntityCache, () => {
|
|
|
77
76
|
|
|
78
77
|
it('does not fetch from the DB or cache results when all cache fetches are hits', async () => {
|
|
79
78
|
const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
|
|
80
|
-
when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
|
|
81
79
|
const cacheAdapter = instance(cacheAdapterMock);
|
|
82
80
|
const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
|
|
83
81
|
const fetcher = createIdFetcher(['wat', 'who']);
|
|
@@ -114,7 +112,6 @@ describe(ReadThroughEntityCache, () => {
|
|
|
114
112
|
|
|
115
113
|
it('negatively caches db misses', async () => {
|
|
116
114
|
const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
|
|
117
|
-
when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
|
|
118
115
|
const cacheAdapter = instance(cacheAdapterMock);
|
|
119
116
|
const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
|
|
120
117
|
|
|
@@ -135,7 +132,6 @@ describe(ReadThroughEntityCache, () => {
|
|
|
135
132
|
|
|
136
133
|
it('does not return or fetch negatively cached results from DB', async () => {
|
|
137
134
|
const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
|
|
138
|
-
when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
|
|
139
135
|
const cacheAdapter = instance(cacheAdapterMock);
|
|
140
136
|
const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
|
|
141
137
|
const fetcher = createIdFetcher([]);
|
|
@@ -153,7 +149,6 @@ describe(ReadThroughEntityCache, () => {
|
|
|
153
149
|
|
|
154
150
|
it('does a mix and match of hit, miss, and negative', async () => {
|
|
155
151
|
const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
|
|
156
|
-
when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
|
|
157
152
|
const cacheAdapter = instance(cacheAdapterMock);
|
|
158
153
|
const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
|
|
159
154
|
const fetcher = createIdFetcher(['wat', 'who', 'why']);
|
|
@@ -189,7 +184,6 @@ describe(ReadThroughEntityCache, () => {
|
|
|
189
184
|
|
|
190
185
|
it('does not call into cache for field that is not cacheable', async () => {
|
|
191
186
|
const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
|
|
192
|
-
when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
|
|
193
187
|
const cacheAdapter = instance(cacheAdapterMock);
|
|
194
188
|
const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(false), cacheAdapter);
|
|
195
189
|
const fetcher = createIdFetcher(['wat']);
|
|
@@ -197,44 +191,6 @@ describe(ReadThroughEntityCache, () => {
|
|
|
197
191
|
verify(cacheAdapterMock.loadManyAsync('id', anything())).never();
|
|
198
192
|
expect(result).toEqual(new Map([['wat', [{ id: 'wat' }]]]));
|
|
199
193
|
});
|
|
200
|
-
|
|
201
|
-
it('transforms fields for cache storage', async () => {
|
|
202
|
-
const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
|
|
203
|
-
when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(
|
|
204
|
-
new Map([
|
|
205
|
-
[
|
|
206
|
-
UUIDField.name,
|
|
207
|
-
{
|
|
208
|
-
read: (val) => val.split('-')[0],
|
|
209
|
-
write: (val) => `${val}-in-cache`,
|
|
210
|
-
},
|
|
211
|
-
],
|
|
212
|
-
])
|
|
213
|
-
);
|
|
214
|
-
const cacheAdapter = instance(cacheAdapterMock);
|
|
215
|
-
const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
|
|
216
|
-
const fetcher = createIdFetcher(['wat', 'who']);
|
|
217
|
-
|
|
218
|
-
when(cacheAdapterMock.loadManyAsync('id', deepEqual(['wat', 'who']))).thenResolve(
|
|
219
|
-
new Map([
|
|
220
|
-
['wat', { status: CacheStatus.MISS }],
|
|
221
|
-
['who', { status: CacheStatus.HIT, item: { id: 'who-in-cache' } }],
|
|
222
|
-
])
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
const result = await entityCache.readManyThroughAsync('id', ['wat', 'who'], fetcher);
|
|
226
|
-
|
|
227
|
-
verify(cacheAdapterMock.loadManyAsync('id', deepEqual(['wat', 'who']))).once();
|
|
228
|
-
verify(
|
|
229
|
-
cacheAdapterMock.cacheManyAsync('id', deepEqual(new Map([['wat', { id: 'wat-in-cache' }]])))
|
|
230
|
-
).once();
|
|
231
|
-
expect(result).toEqual(
|
|
232
|
-
new Map([
|
|
233
|
-
['wat', [{ id: 'wat' }]],
|
|
234
|
-
['who', [{ id: 'who' }]],
|
|
235
|
-
])
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
194
|
});
|
|
239
195
|
|
|
240
196
|
describe('invalidateManyAsync', () => {
|
|
@@ -3,7 +3,6 @@ import invariant from 'invariant';
|
|
|
3
3
|
import EntityCacheAdapter from '../../EntityCacheAdapter';
|
|
4
4
|
import EntityConfiguration from '../../EntityConfiguration';
|
|
5
5
|
import IEntityCacheAdapterProvider from '../../IEntityCacheAdapterProvider';
|
|
6
|
-
import { FieldTransformerMap } from '../../internal/EntityFieldTransformationUtils';
|
|
7
6
|
import { CacheStatus, CacheLoadResult } from '../../internal/ReadThroughEntityCache';
|
|
8
7
|
|
|
9
8
|
export class NoCacheStubCacheAdapterProvider implements IEntityCacheAdapterProvider {
|
|
@@ -15,15 +14,11 @@ export class NoCacheStubCacheAdapterProvider implements IEntityCacheAdapterProvi
|
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export class NoCacheStubCacheAdapter<TFields> extends EntityCacheAdapter<TFields> {
|
|
18
|
-
public getFieldTransformerMap(): FieldTransformerMap {
|
|
19
|
-
return new Map();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
public async loadManyAsync<N extends keyof TFields>(
|
|
23
18
|
_fieldName: N,
|
|
24
19
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
|
25
|
-
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult
|
|
26
|
-
return fieldValues.reduce((acc: Map<NonNullable<TFields[N]>, CacheLoadResult
|
|
20
|
+
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult<TFields>>> {
|
|
21
|
+
return fieldValues.reduce((acc: Map<NonNullable<TFields[N]>, CacheLoadResult<TFields>>, v) => {
|
|
27
22
|
acc.set(v, {
|
|
28
23
|
status: CacheStatus.MISS,
|
|
29
24
|
});
|
|
@@ -33,7 +28,7 @@ export class NoCacheStubCacheAdapter<TFields> extends EntityCacheAdapter<TFields
|
|
|
33
28
|
|
|
34
29
|
public async cacheManyAsync<N extends keyof TFields>(
|
|
35
30
|
_fieldName: N,
|
|
36
|
-
_objectMap: ReadonlyMap<NonNullable<TFields[N]>,
|
|
31
|
+
_objectMap: ReadonlyMap<NonNullable<TFields[N]>, Readonly<TFields>>
|
|
37
32
|
): Promise<void> {}
|
|
38
33
|
|
|
39
34
|
public async cacheDBMissesAsync<N extends keyof TFields>(
|
|
@@ -53,27 +48,26 @@ export class InMemoryFullCacheStubCacheAdapterProvider implements IEntityCacheAd
|
|
|
53
48
|
getCacheAdapter<TFields>(
|
|
54
49
|
entityConfiguration: EntityConfiguration<TFields>
|
|
55
50
|
): EntityCacheAdapter<TFields> {
|
|
56
|
-
return new InMemoryFullCacheStubCacheAdapter(
|
|
51
|
+
return new InMemoryFullCacheStubCacheAdapter(
|
|
52
|
+
entityConfiguration,
|
|
53
|
+
this.cache as Map<string, Readonly<TFields>>
|
|
54
|
+
);
|
|
57
55
|
}
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
export class InMemoryFullCacheStubCacheAdapter<TFields> extends EntityCacheAdapter<TFields> {
|
|
61
59
|
constructor(
|
|
62
60
|
entityConfiguration: EntityConfiguration<TFields>,
|
|
63
|
-
readonly cache: Map<string, Readonly<
|
|
61
|
+
readonly cache: Map<string, Readonly<TFields>>
|
|
64
62
|
) {
|
|
65
63
|
super(entityConfiguration);
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
public getFieldTransformerMap(): FieldTransformerMap {
|
|
69
|
-
return new Map();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
66
|
public async loadManyAsync<N extends keyof TFields>(
|
|
73
67
|
fieldName: N,
|
|
74
68
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
|
75
|
-
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult
|
|
76
|
-
const results = new Map<NonNullable<TFields[N]>, CacheLoadResult
|
|
69
|
+
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult<TFields>>> {
|
|
70
|
+
const results = new Map<NonNullable<TFields[N]>, CacheLoadResult<TFields>>();
|
|
77
71
|
fieldValues.forEach((fieldValue) => {
|
|
78
72
|
const cacheKey = this.createCacheKey(fieldName, fieldValue);
|
|
79
73
|
if (!this.cache.has(cacheKey)) {
|
|
@@ -94,7 +88,7 @@ export class InMemoryFullCacheStubCacheAdapter<TFields> extends EntityCacheAdapt
|
|
|
94
88
|
|
|
95
89
|
public async cacheManyAsync<N extends keyof TFields>(
|
|
96
90
|
fieldName: N,
|
|
97
|
-
objectMap: ReadonlyMap<NonNullable<TFields[N]>,
|
|
91
|
+
objectMap: ReadonlyMap<NonNullable<TFields[N]>, Readonly<TFields>>
|
|
98
92
|
): Promise<void> {
|
|
99
93
|
objectMap.forEach((obj, fieldValue) => {
|
|
100
94
|
const cacheKey = this.createCacheKey(fieldName, fieldValue);
|