Skip to content

Commit d9377a2

Browse files
authored
fix: Reorganize constructionUtils to make non-public (#454)
# Why In #410 we split out `EntityLoaderUtils` into two classes: - EntityInvalidationUtils - functions for invalidating entity caches from application code when underlying data is mutated outside of the entity framework - EntityConstructorUtils - functions for constructing and authorizing entities. Originally, these were both public due to them being the same class. Now, with them separated, we can better restrict access to entity construction which should only be done within the framework itself. # How Make constructor utils private everywhere except EntityLoaderFactory, which is deep enough in the abstraction that it's not immediately usable by top-level entity APIs (static methods on Entity/ReadonlyEntity, loader classes). # Test Plan `yarn tsc`, CI
1 parent 457f58a commit d9377a2

17 files changed

Lines changed: 307 additions & 336 deletions

packages/entity-secondary-cache-local-memory/src/__tests__/LocalMemorySecondaryEntityCache-test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {
22
AlwaysAllowPrivacyPolicyRule,
3+
AuthorizationResultBasedEntityLoader,
34
Entity,
45
EntityCompanionDefinition,
56
EntityCompanionProvider,
67
EntityConfiguration,
8+
EntityConstructionUtils,
79
EntityPrivacyPolicy,
810
EntitySecondaryCacheLoader,
911
IEntityMetricsAdapter,
@@ -161,6 +163,32 @@ class TestSecondaryLocalMemoryCacheLoader extends EntitySecondaryCacheLoader<
161163
> {
162164
public databaseLoadCount = 0;
163165

166+
constructor(
167+
secondaryEntityCache: LocalMemorySecondaryEntityCache<
168+
LocalMemoryTestEntityFields,
169+
'id',
170+
TestLoadParams
171+
>,
172+
constructionUtils: EntityConstructionUtils<
173+
LocalMemoryTestEntityFields,
174+
'id',
175+
TestViewerContext,
176+
LocalMemoryTestEntity,
177+
LocalMemoryTestEntityPrivacyPolicy,
178+
keyof LocalMemoryTestEntityFields
179+
>,
180+
private readonly entityLoader: AuthorizationResultBasedEntityLoader<
181+
LocalMemoryTestEntityFields,
182+
'id',
183+
TestViewerContext,
184+
LocalMemoryTestEntity,
185+
LocalMemoryTestEntityPrivacyPolicy,
186+
keyof LocalMemoryTestEntityFields
187+
>,
188+
) {
189+
super(secondaryEntityCache, constructionUtils);
190+
}
191+
164192
protected async fetchObjectsFromDatabaseAsync(
165193
loadParamsArray: readonly Readonly<TestLoadParams>[],
166194
): Promise<ReadonlyMap<Readonly<TestLoadParams>, Readonly<LocalMemoryTestEntityFields> | null>> {
@@ -193,6 +221,10 @@ describe(LocalMemorySecondaryEntityCache, () => {
193221
localMemoryTestEntityConfiguration,
194222
createTTLCache<LocalMemoryTestEntityFields>(),
195223
),
224+
EntitySecondaryCacheLoader.getConstructionUtilsForEntityClass(
225+
LocalMemoryTestEntity,
226+
viewerContext,
227+
),
196228
LocalMemoryTestEntity.loaderWithAuthorizationResults(viewerContext),
197229
);
198230

@@ -229,6 +261,10 @@ describe(LocalMemorySecondaryEntityCache, () => {
229261
localMemoryTestEntityConfiguration,
230262
createTTLCache<LocalMemoryTestEntityFields>(),
231263
),
264+
EntitySecondaryCacheLoader.getConstructionUtilsForEntityClass(
265+
LocalMemoryTestEntity,
266+
viewerContext,
267+
),
232268
LocalMemoryTestEntity.loaderWithAuthorizationResults(viewerContext),
233269
);
234270

packages/entity-secondary-cache-redis/src/__integration-tests__/RedisSecondaryEntityCache-integration-test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { EntitySecondaryCacheLoader, mapMapAsync, ViewerContext } from '@expo/entity';
1+
import {
2+
AuthorizationResultBasedEntityLoader,
3+
EntityConstructionUtils,
4+
EntitySecondaryCacheLoader,
5+
mapMapAsync,
6+
ViewerContext,
7+
} from '@expo/entity';
28
import {
39
GenericRedisCacheContext,
410
RedisCacheInvalidationStrategy,
@@ -33,6 +39,28 @@ class TestSecondaryRedisCacheLoader extends EntitySecondaryCacheLoader<
3339
> {
3440
public databaseLoadCount = 0;
3541

42+
constructor(
43+
secondaryEntityCache: RedisSecondaryEntityCache<RedisTestEntityFields, 'id', TestLoadParams>,
44+
constructionUtils: EntityConstructionUtils<
45+
RedisTestEntityFields,
46+
'id',
47+
TestViewerContext,
48+
RedisTestEntity,
49+
RedisTestEntityPrivacyPolicy,
50+
keyof RedisTestEntityFields
51+
>,
52+
private readonly entityLoader: AuthorizationResultBasedEntityLoader<
53+
RedisTestEntityFields,
54+
'id',
55+
TestViewerContext,
56+
RedisTestEntity,
57+
RedisTestEntityPrivacyPolicy,
58+
keyof RedisTestEntityFields
59+
>,
60+
) {
61+
super(secondaryEntityCache, constructionUtils);
62+
}
63+
3664
protected async fetchObjectsFromDatabaseAsync(
3765
loadParamsArray: readonly Readonly<TestLoadParams>[],
3866
): Promise<ReadonlyMap<Readonly<TestLoadParams>, Readonly<RedisTestEntityFields> | null>> {
@@ -93,6 +121,7 @@ describe(RedisSecondaryEntityCache, () => {
93121
genericRedisCacheContext,
94122
(loadParams) => `test-key-${loadParams.id}`,
95123
),
124+
EntitySecondaryCacheLoader.getConstructionUtilsForEntityClass(RedisTestEntity, viewerContext),
96125
RedisTestEntity.loaderWithAuthorizationResults(viewerContext),
97126
);
98127

@@ -132,6 +161,7 @@ describe(RedisSecondaryEntityCache, () => {
132161
genericRedisCacheContext,
133162
(loadParams) => `test-key-${loadParams.id}`,
134163
),
164+
EntitySecondaryCacheLoader.getConstructionUtilsForEntityClass(RedisTestEntity, viewerContext),
135165
RedisTestEntity.loaderWithAuthorizationResults(viewerContext),
136166
);
137167

packages/entity/src/AuthorizationResultBasedEntityLoader.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
EntityConfiguration,
1010
} from './EntityConfiguration';
1111
import { EntityConstructionUtils } from './EntityConstructionUtils';
12-
import { EntityInvalidationUtils } from './EntityInvalidationUtils';
1312
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
1413
import { EntityQueryContext } from './EntityQueryContext';
1514
import { ReadonlyEntity } from './ReadonlyEntity';
@@ -19,7 +18,6 @@ import { CompositeFieldHolder, CompositeFieldValueHolder } from './internal/Comp
1918
import { CompositeFieldValueMap } from './internal/CompositeFieldValueMap';
2019
import { EntityDataManager } from './internal/EntityDataManager';
2120
import { SingleFieldHolder, SingleFieldValueHolder } from './internal/SingleFieldHolder';
22-
import { IEntityMetricsAdapter } from './metrics/IEntityMetricsAdapter';
2321
import { mapKeys, mapMap } from './utils/collections/maps';
2422
import { areSetsEqual } from './utils/collections/sets';
2523

@@ -55,16 +53,7 @@ export class AuthorizationResultBasedEntityLoader<
5553
TSelectedFields
5654
>,
5755
private readonly dataManager: EntityDataManager<TFields, TIDField>,
58-
protected readonly metricsAdapter: IEntityMetricsAdapter,
59-
public readonly invalidationUtils: EntityInvalidationUtils<
60-
TFields,
61-
TIDField,
62-
TViewerContext,
63-
TEntity,
64-
TPrivacyPolicy,
65-
TSelectedFields
66-
>,
67-
public readonly constructionUtils: EntityConstructionUtils<
56+
private readonly constructionUtils: EntityConstructionUtils<
6857
TFields,
6958
TIDField,
7059
TViewerContext,

packages/entity/src/AuthorizationResultBasedEntityMutator.ts

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,17 @@ export class AuthorizationResultBasedCreateMutator<
333333
previousValue: null,
334334
cascadingDeleteCause: null,
335335
});
336+
const invalidationUtils = this.entityLoaderFactory.invalidationUtils();
337+
const constructionUtils = this.entityLoaderFactory.constructionUtils(
338+
this.viewerContext,
339+
queryContext,
340+
{
341+
previousValue: null,
342+
cascadingDeleteCause: null,
343+
},
344+
);
336345

337-
const temporaryEntityForPrivacyCheck = entityLoader.constructionUtils.constructEntity({
346+
const temporaryEntityForPrivacyCheck = constructionUtils.constructEntity({
338347
[this.entityConfiguration.idField]: '00000000-0000-0000-0000-000000000000', // zero UUID
339348
...this.fieldsForEntity,
340349
} as unknown as TFields);
@@ -376,14 +385,13 @@ export class AuthorizationResultBasedCreateMutator<
376385
// Invalidate all caches for the new entity so that any previously-negatively-cached loads
377386
// are removed from the caches.
378387
queryContext.appendPostCommitInvalidationCallback(async () => {
379-
entityLoader.invalidationUtils.invalidateFieldsForTransaction(queryContext, insertResult);
380-
await entityLoader.invalidationUtils.invalidateFieldsAsync(insertResult);
388+
invalidationUtils.invalidateFieldsForTransaction(queryContext, insertResult);
389+
await invalidationUtils.invalidateFieldsAsync(insertResult);
381390
});
382391

383-
entityLoader.invalidationUtils.invalidateFieldsForTransaction(queryContext, insertResult);
392+
invalidationUtils.invalidateFieldsForTransaction(queryContext, insertResult);
384393

385-
const unauthorizedEntityAfterInsert =
386-
entityLoader.constructionUtils.constructEntity(insertResult);
394+
const unauthorizedEntityAfterInsert = constructionUtils.constructEntity(insertResult);
387395
const newEntity = await enforceAsyncResult(
388396
entityLoader.loadByIDAsync(unauthorizedEntityAfterInsert.getID()),
389397
);
@@ -542,10 +550,17 @@ export class AuthorizationResultBasedUpdateMutator<
542550
previousValue: this.originalEntity,
543551
cascadingDeleteCause: this.cascadingDeleteCause,
544552
});
545-
546-
const entityAboutToBeUpdated = entityLoader.constructionUtils.constructEntity(
547-
this.fieldsForEntity,
553+
const invalidationUtils = this.entityLoaderFactory.invalidationUtils();
554+
const constructionUtils = this.entityLoaderFactory.constructionUtils(
555+
this.viewerContext,
556+
queryContext,
557+
{
558+
previousValue: this.originalEntity,
559+
cascadingDeleteCause: this.cascadingDeleteCause,
560+
},
548561
);
562+
563+
const entityAboutToBeUpdated = constructionUtils.constructEntity(this.fieldsForEntity);
549564
const authorizeUpdateResult = await asyncResult(
550565
this.privacyPolicy.authorizeUpdateAsync(
551566
this.viewerContext,
@@ -609,30 +624,22 @@ export class AuthorizationResultBasedUpdateMutator<
609624
// version of the entity.
610625

611626
queryContext.appendPostCommitInvalidationCallback(async () => {
612-
entityLoader.invalidationUtils.invalidateFieldsForTransaction(
627+
invalidationUtils.invalidateFieldsForTransaction(
613628
queryContext,
614629
this.originalEntity.getAllDatabaseFields(),
615630
);
616-
entityLoader.invalidationUtils.invalidateFieldsForTransaction(
617-
queryContext,
618-
this.fieldsForEntity,
619-
);
631+
invalidationUtils.invalidateFieldsForTransaction(queryContext, this.fieldsForEntity);
620632
await Promise.all([
621-
entityLoader.invalidationUtils.invalidateFieldsAsync(
622-
this.originalEntity.getAllDatabaseFields(),
623-
),
624-
entityLoader.invalidationUtils.invalidateFieldsAsync(this.fieldsForEntity),
633+
invalidationUtils.invalidateFieldsAsync(this.originalEntity.getAllDatabaseFields()),
634+
invalidationUtils.invalidateFieldsAsync(this.fieldsForEntity),
625635
]);
626636
});
627637

628-
entityLoader.invalidationUtils.invalidateFieldsForTransaction(
638+
invalidationUtils.invalidateFieldsForTransaction(
629639
queryContext,
630640
this.originalEntity.getAllDatabaseFields(),
631641
);
632-
entityLoader.invalidationUtils.invalidateFieldsForTransaction(
633-
queryContext,
634-
this.fieldsForEntity,
635-
);
642+
invalidationUtils.invalidateFieldsForTransaction(queryContext, this.fieldsForEntity);
636643

637644
const updatedEntity = await enforceAsyncResult(
638645
entityLoader.loadByIDAsync(entityAboutToBeUpdated.getID()),
@@ -850,23 +857,18 @@ export class AuthorizationResultBasedDeleteMutator<
850857
);
851858
}
852859

853-
const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext, {
854-
previousValue: null,
855-
cascadingDeleteCause: this.cascadingDeleteCause,
856-
});
860+
const invalidationUtils = this.entityLoaderFactory.invalidationUtils();
857861

858862
// Invalidate all caches for the entity so that any previously-cached loads
859863
// are removed from the caches.
860864
queryContext.appendPostCommitInvalidationCallback(async () => {
861-
entityLoader.invalidationUtils.invalidateFieldsForTransaction(
865+
invalidationUtils.invalidateFieldsForTransaction(
862866
queryContext,
863867
this.entity.getAllDatabaseFields(),
864868
);
865-
await entityLoader.invalidationUtils.invalidateFieldsAsync(
866-
this.entity.getAllDatabaseFields(),
867-
);
869+
await invalidationUtils.invalidateFieldsAsync(this.entity.getAllDatabaseFields());
868870
});
869-
entityLoader.invalidationUtils.invalidateFieldsForTransaction(
871+
invalidationUtils.invalidateFieldsForTransaction(
870872
queryContext,
871873
this.entity.getAllDatabaseFields(),
872874
);

packages/entity/src/EntityLoader.ts

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { AuthorizationResultBasedEntityLoader } from './AuthorizationResultBasedEntityLoader';
22
import { EnforcingEntityLoader } from './EnforcingEntityLoader';
33
import { IEntityClass } from './Entity';
4-
import { EntityConstructionUtils } from './EntityConstructionUtils';
5-
import { EntityInvalidationUtils } from './EntityInvalidationUtils';
64
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
75
import { EntityQueryContext } from './EntityQueryContext';
86
import { ReadonlyEntity } from './ReadonlyEntity';
@@ -74,34 +72,4 @@ export class EntityLoader<
7472
.getLoaderFactory()
7573
.forLoad(this.queryContext, { previousValue: null, cascadingDeleteCause: null });
7674
}
77-
78-
/**
79-
* Entity cache invalidation utilities.
80-
* Calling into these should only be necessary in rare cases.
81-
*/
82-
public invalidationUtils(): EntityInvalidationUtils<
83-
TFields,
84-
TIDField,
85-
TViewerContext,
86-
TEntity,
87-
TPrivacyPolicy,
88-
TSelectedFields
89-
> {
90-
return this.withAuthorizationResults().invalidationUtils;
91-
}
92-
93-
/**
94-
* Entity construction and validation utilities.
95-
* Calling into these should only be necessary in rare cases.
96-
*/
97-
public constructionUtils(): EntityConstructionUtils<
98-
TFields,
99-
TIDField,
100-
TViewerContext,
101-
TEntity,
102-
TPrivacyPolicy,
103-
TSelectedFields
104-
> {
105-
return this.withAuthorizationResults().constructionUtils;
106-
}
10775
}

0 commit comments

Comments
 (0)