Skip to content

Commit 547a1ef

Browse files
authored
fix: separate out EntityMutationValidator (#68)
1 parent fc4377d commit 547a1ef

File tree

10 files changed

+113
-46
lines changed

10 files changed

+113
-46
lines changed

packages/entity-database-adapter-knex/src/testfixtures/PostgresTriggerTestEntity.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
CacheAdapterFlavor,
1010
EntityCompanionDefinition,
1111
Entity,
12-
EntityMutationTrigger,
12+
EntityMutationExecutable,
1313
EntityQueryContext,
1414
} from '@expo/entity';
1515
import Knex from 'knex';
@@ -97,7 +97,7 @@ class PostgresTriggerTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
9797
];
9898
}
9999

100-
class ThrowConditionallyTrigger extends EntityMutationTrigger<
100+
class ThrowConditionallyTrigger extends EntityMutationExecutable<
101101
PostgresTriggerTestEntityFields,
102102
string,
103103
ViewerContext,

packages/entity-database-adapter-knex/src/testfixtures/PostgresValidatorTestEntity.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
CacheAdapterFlavor,
1010
EntityCompanionDefinition,
1111
Entity,
12-
EntityMutationTrigger,
12+
EntityMutationExecutable,
1313
EntityQueryContext,
1414
} from '@expo/entity';
1515
import Knex from 'knex';
@@ -97,7 +97,7 @@ class PostgresValidatorTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
9797
];
9898
}
9999

100-
class ThrowConditionallyTrigger extends EntityMutationTrigger<
100+
class ThrowConditionallyTrigger extends EntityMutationExecutable<
101101
PostgresValidatorTestEntityFields,
102102
string,
103103
ViewerContext,

packages/entity/src/EntityCompanion.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IEntityClass } from './Entity';
22
import EntityLoaderFactory from './EntityLoaderFactory';
3-
import { EntityMutationTriggerConfiguration, EntityMutationTrigger } from './EntityMutationTrigger';
3+
import EntityMutationTriggerConfiguration from './EntityMutationTriggerConfiguration';
4+
import EntityMutationValidator from './EntityMutationValidator';
45
import EntityMutatorFactory from './EntityMutatorFactory';
56
import EntityPrivacyPolicy from './EntityPrivacyPolicy';
67
import IEntityQueryContextProvider from './IEntityQueryContextProvider';
@@ -58,7 +59,7 @@ export default class EntityCompanion<
5859
>,
5960
private readonly tableDataCoordinator: EntityTableDataCoordinator<TFields>,
6061
PrivacyPolicyClass: IPrivacyPolicyClass<TPrivacyPolicy>,
61-
mutationValidators: EntityMutationTrigger<
62+
mutationValidators: EntityMutationValidator<
6263
TFields,
6364
TID,
6465
TViewerContext,

packages/entity/src/EntityCompanionProvider.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { IEntityClass } from './Entity';
22
import EntityCompanion, { IPrivacyPolicyClass } from './EntityCompanion';
33
import EntityConfiguration from './EntityConfiguration';
4-
import { EntityMutationTriggerConfiguration, EntityMutationTrigger } from './EntityMutationTrigger';
4+
import EntityMutationTriggerConfiguration from './EntityMutationTriggerConfiguration';
5+
import EntityMutationValidator from './EntityMutationValidator';
56
import EntityPrivacyPolicy from './EntityPrivacyPolicy';
67
import IEntityCacheAdapterProvider from './IEntityCacheAdapterProvider';
78
import IEntityDatabaseAdapterProvider from './IEntityDatabaseAdapterProvider';
@@ -81,7 +82,7 @@ export class EntityCompanionDefinition<
8182
>;
8283
readonly entityConfiguration: EntityConfiguration<TFields>;
8384
readonly privacyPolicyClass: IPrivacyPolicyClass<TPrivacyPolicy>;
84-
readonly mutationValidators: EntityMutationTrigger<
85+
readonly mutationValidators: EntityMutationValidator<
8586
TFields,
8687
TID,
8788
TViewerContext,
@@ -115,7 +116,7 @@ export class EntityCompanionDefinition<
115116
>;
116117
entityConfiguration: EntityConfiguration<TFields>;
117118
privacyPolicyClass: IPrivacyPolicyClass<TPrivacyPolicy>;
118-
mutationValidators?: EntityMutationTrigger<
119+
mutationValidators?: EntityMutationValidator<
119120
TFields,
120121
TID,
121122
TViewerContext,

packages/entity/src/EntityMutationTrigger.ts renamed to packages/entity/src/EntityMutationTriggerConfiguration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import ViewerContext from './ViewerContext';
55
/**
66
* Interface to define trigger behavior for entities.
77
*/
8-
export interface EntityMutationTriggerConfiguration<
8+
export default interface EntityMutationTriggerConfiguration<
99
TFields,
1010
TID,
1111
TViewerContext extends ViewerContext,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { EntityQueryContext } from './EntityQueryContext';
2+
import ReadonlyEntity from './ReadonlyEntity';
3+
import ViewerContext from './ViewerContext';
4+
5+
/*
6+
* A validator is a way to specify entity mutation validation that runs within the
7+
* same transaction as the mutation itself before creating or updating an entity.
8+
*/
9+
export default abstract class EntityMutationValidator<
10+
TFields,
11+
TID,
12+
TViewerContext extends ViewerContext,
13+
TEntity extends ReadonlyEntity<TFields, TID, TViewerContext, TSelectedFields>,
14+
TSelectedFields extends keyof TFields = keyof TFields
15+
> {
16+
abstract async executeAsync(
17+
viewerContext: TViewerContext,
18+
queryContext: EntityQueryContext,
19+
entity: TEntity
20+
): Promise<void>;
21+
}

packages/entity/src/EntityMutator.ts

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import EntityConfiguration from './EntityConfiguration';
66
import EntityDatabaseAdapter from './EntityDatabaseAdapter';
77
import { EntityEdgeDeletionBehavior } from './EntityFields';
88
import EntityLoaderFactory from './EntityLoaderFactory';
9-
import { EntityMutationTrigger, EntityMutationTriggerConfiguration } from './EntityMutationTrigger';
9+
import EntityMutationTriggerConfiguration, {
10+
EntityMutationTrigger,
11+
} from './EntityMutationTriggerConfiguration';
12+
import EntityMutationValidator from './EntityMutationValidator';
1013
import EntityPrivacyPolicy from './EntityPrivacyPolicy';
1114
import { EntityQueryContext, EntityTransactionalQueryContext } from './EntityQueryContext';
1215
import ReadonlyEntity from './ReadonlyEntity';
@@ -42,7 +45,7 @@ abstract class BaseMutator<
4245
TSelectedFields
4346
>,
4447
protected readonly privacyPolicy: TPrivacyPolicy,
45-
protected readonly mutationValidators: EntityMutationTrigger<
48+
protected readonly mutationValidators: EntityMutationValidator<
4649
TFields,
4750
TID,
4851
TViewerContext,
@@ -68,18 +71,21 @@ abstract class BaseMutator<
6871
protected readonly metricsAdapter: IEntityMetricsAdapter
6972
) {}
7073

71-
protected async executeTriggers(
72-
triggers:
74+
protected async executeMutationTriggersOrValidatorsAsync(
75+
triggersOrValidators:
7376
| EntityMutationTrigger<TFields, TID, TViewerContext, TEntity, TSelectedFields>[]
77+
| EntityMutationValidator<TFields, TID, TViewerContext, TEntity, TSelectedFields>[]
7478
| undefined,
7579
queryContext: EntityQueryContext,
7680
entity: TEntity
7781
): Promise<void> {
78-
if (!triggers) {
82+
if (!triggersOrValidators) {
7983
return;
8084
}
8185
await Promise.all(
82-
triggers.map((trigger) => trigger.executeAsync(this.viewerContext, queryContext, entity))
86+
triggersOrValidators.map((triggerOrValidator) =>
87+
triggerOrValidator.executeAsync(this.viewerContext, queryContext, entity)
88+
)
8389
);
8490
}
8591
}
@@ -137,7 +143,7 @@ export class CreateMutator<
137143
(innerQueryContext) => this.createInternalAsync(innerQueryContext)
138144
);
139145
if (internalResult.ok) {
140-
await this.executeTriggers(
146+
await this.executeMutationTriggersOrValidatorsAsync(
141147
this.mutationTriggers.afterCommit,
142148
this.queryContext,
143149
internalResult.value
@@ -165,17 +171,17 @@ export class CreateMutator<
165171
return authorizeCreateResult;
166172
}
167173

168-
await this.executeTriggers(
174+
await this.executeMutationTriggersOrValidatorsAsync(
169175
this.mutationValidators,
170176
queryContext,
171177
temporaryEntityForPrivacyCheck
172178
);
173-
await this.executeTriggers(
179+
await this.executeMutationTriggersOrValidatorsAsync(
174180
this.mutationTriggers.beforeAll,
175181
queryContext,
176182
temporaryEntityForPrivacyCheck
177183
);
178-
await this.executeTriggers(
184+
await this.executeMutationTriggersOrValidatorsAsync(
179185
this.mutationTriggers.beforeCreate,
180186
queryContext,
181187
temporaryEntityForPrivacyCheck
@@ -191,8 +197,16 @@ export class CreateMutator<
191197
.enforcing()
192198
.loadByIDAsync(unauthorizedEntityAfterInsert.getID());
193199

194-
await this.executeTriggers(this.mutationTriggers.afterCreate, queryContext, newEntity);
195-
await this.executeTriggers(this.mutationTriggers.afterAll, queryContext, newEntity);
200+
await this.executeMutationTriggersOrValidatorsAsync(
201+
this.mutationTriggers.afterCreate,
202+
queryContext,
203+
newEntity
204+
);
205+
await this.executeMutationTriggersOrValidatorsAsync(
206+
this.mutationTriggers.afterAll,
207+
queryContext,
208+
newEntity
209+
);
196210

197211
return result(newEntity);
198212
}
@@ -232,7 +246,7 @@ export class UpdateMutator<
232246
TSelectedFields
233247
>,
234248
privacyPolicy: TPrivacyPolicy,
235-
mutationValidators: EntityMutationTrigger<
249+
mutationValidators: EntityMutationValidator<
236250
TFields,
237251
TID,
238252
TViewerContext,
@@ -310,7 +324,7 @@ export class UpdateMutator<
310324
(innerQueryContext) => this.updateInternalAsync(innerQueryContext)
311325
);
312326
if (internalResult.ok) {
313-
await this.executeTriggers(
327+
await this.executeMutationTriggersOrValidatorsAsync(
314328
this.mutationTriggers.afterCommit,
315329
this.queryContext,
316330
internalResult.value
@@ -334,13 +348,17 @@ export class UpdateMutator<
334348
return authorizeUpdateResult;
335349
}
336350

337-
await this.executeTriggers(this.mutationValidators, queryContext, entityAboutToBeUpdated);
338-
await this.executeTriggers(
351+
await this.executeMutationTriggersOrValidatorsAsync(
352+
this.mutationValidators,
353+
queryContext,
354+
entityAboutToBeUpdated
355+
);
356+
await this.executeMutationTriggersOrValidatorsAsync(
339357
this.mutationTriggers.beforeAll,
340358
queryContext,
341359
entityAboutToBeUpdated
342360
);
343-
await this.executeTriggers(
361+
await this.executeMutationTriggersOrValidatorsAsync(
344362
this.mutationTriggers.beforeUpdate,
345363
queryContext,
346364
entityAboutToBeUpdated
@@ -363,8 +381,16 @@ export class UpdateMutator<
363381
.enforcing()
364382
.loadByIDAsync(unauthorizedEntityAfterUpdate.getID());
365383

366-
await this.executeTriggers(this.mutationTriggers.afterUpdate, queryContext, updatedEntity);
367-
await this.executeTriggers(this.mutationTriggers.afterAll, queryContext, updatedEntity);
384+
await this.executeMutationTriggersOrValidatorsAsync(
385+
this.mutationTriggers.afterUpdate,
386+
queryContext,
387+
updatedEntity
388+
);
389+
await this.executeMutationTriggersOrValidatorsAsync(
390+
this.mutationTriggers.afterAll,
391+
queryContext,
392+
updatedEntity
393+
);
368394

369395
return result(updatedEntity);
370396
}
@@ -400,7 +426,7 @@ export class DeleteMutator<
400426
TSelectedFields
401427
>,
402428
privacyPolicy: TPrivacyPolicy,
403-
mutationValidators: EntityMutationTrigger<
429+
mutationValidators: EntityMutationValidator<
404430
TFields,
405431
TID,
406432
TViewerContext,
@@ -471,7 +497,7 @@ export class DeleteMutator<
471497
)
472498
);
473499
if (internalResult.ok) {
474-
await this.executeTriggers(
500+
await this.executeMutationTriggersOrValidatorsAsync(
475501
this.mutationTriggers.afterCommit,
476502
this.queryContext,
477503
internalResult.value
@@ -498,8 +524,16 @@ export class DeleteMutator<
498524
processedEntityIdentifiersFromTransitiveDeletions
499525
);
500526

501-
await this.executeTriggers(this.mutationTriggers.beforeAll, queryContext, this.entity);
502-
await this.executeTriggers(this.mutationTriggers.beforeDelete, queryContext, this.entity);
527+
await this.executeMutationTriggersOrValidatorsAsync(
528+
this.mutationTriggers.beforeAll,
529+
queryContext,
530+
this.entity
531+
);
532+
await this.executeMutationTriggersOrValidatorsAsync(
533+
this.mutationTriggers.beforeDelete,
534+
queryContext,
535+
this.entity
536+
);
503537

504538
if (!skipDatabaseDeletion) {
505539
await this.databaseAdapter.deleteAsync(
@@ -512,8 +546,16 @@ export class DeleteMutator<
512546
const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext);
513547
await entityLoader.invalidateFieldsAsync(this.entity.getAllDatabaseFields());
514548

515-
await this.executeTriggers(this.mutationTriggers.afterDelete, queryContext, this.entity);
516-
await this.executeTriggers(this.mutationTriggers.afterAll, queryContext, this.entity);
549+
await this.executeMutationTriggersOrValidatorsAsync(
550+
this.mutationTriggers.afterDelete,
551+
queryContext,
552+
this.entity
553+
);
554+
await this.executeMutationTriggersOrValidatorsAsync(
555+
this.mutationTriggers.afterAll,
556+
queryContext,
557+
this.entity
558+
);
517559

518560
return result(this.entity);
519561
}

packages/entity/src/EntityMutatorFactory.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import Entity, { IEntityClass } from './Entity';
22
import EntityConfiguration from './EntityConfiguration';
33
import EntityDatabaseAdapter from './EntityDatabaseAdapter';
44
import EntityLoaderFactory from './EntityLoaderFactory';
5-
import { EntityMutationTriggerConfiguration, EntityMutationTrigger } from './EntityMutationTrigger';
5+
import EntityMutationTriggerConfiguration from './EntityMutationTriggerConfiguration';
6+
import EntityMutationValidator from './EntityMutationValidator';
67
import { CreateMutator, UpdateMutator, DeleteMutator } from './EntityMutator';
78
import EntityPrivacyPolicy from './EntityPrivacyPolicy';
89
import { EntityQueryContext } from './EntityQueryContext';
@@ -37,7 +38,7 @@ export default class EntityMutatorFactory<
3738
TSelectedFields
3839
>,
3940
private readonly privacyPolicy: TPrivacyPolicy,
40-
private readonly mutationValidators: EntityMutationTrigger<
41+
private readonly mutationValidators: EntityMutationValidator<
4142
TFields,
4243
TID,
4344
TViewerContext,

packages/entity/src/__tests__/EntityMutator-test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import {
1212

1313
import EntityDatabaseAdapter from '../EntityDatabaseAdapter';
1414
import EntityLoaderFactory from '../EntityLoaderFactory';
15-
import {
16-
EntityMutationTriggerConfiguration,
15+
import EntityMutationTriggerConfiguration, {
1716
EntityMutationTrigger,
18-
} from '../EntityMutationTrigger';
17+
} from '../EntityMutationTriggerConfiguration';
18+
import EntityMutationValidator from '../EntityMutationValidator';
1919
import EntityMutatorFactory from '../EntityMutatorFactory';
2020
import {
2121
EntityTransactionalQueryContext,
@@ -57,20 +57,20 @@ class TestMutationTrigger extends EntityMutationTrigger<
5757
}
5858

5959
const setUpMutationValidatorSpies = (
60-
mutationValidators: EntityMutationTrigger<
60+
mutationValidators: EntityMutationValidator<
6161
TestFields,
6262
string,
6363
ViewerContext,
6464
TestEntity,
6565
keyof TestFields
6666
>[]
67-
): EntityMutationTrigger<TestFields, string, ViewerContext, TestEntity, keyof TestFields>[] => {
67+
): EntityMutationValidator<TestFields, string, ViewerContext, TestEntity, keyof TestFields>[] => {
6868
return mutationValidators.map((validator) => spy(validator));
6969
};
7070

7171
const verifyValidatorCounts = (
7272
viewerContext: ViewerContext,
73-
mutationValidatorSpies: EntityMutationTrigger<
73+
mutationValidatorSpies: EntityMutationValidator<
7474
TestFields,
7575
string,
7676
ViewerContext,
@@ -211,7 +211,7 @@ const createEntityMutatorFactory = (
211211
TestEntityPrivacyPolicy
212212
>;
213213
metricsAdapter: IEntityMetricsAdapter;
214-
mutationValidators: EntityMutationTrigger<
214+
mutationValidators: EntityMutationValidator<
215215
TestFields,
216216
string,
217217
ViewerContext,
@@ -226,7 +226,7 @@ const createEntityMutatorFactory = (
226226
keyof TestFields
227227
>;
228228
} => {
229-
const mutationValidators: EntityMutationTrigger<
229+
const mutationValidators: EntityMutationValidator<
230230
TestFields,
231231
string,
232232
ViewerContext,

0 commit comments

Comments
 (0)