@expo/entity 0.50.0 → 0.51.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/src/AuthorizationResultBasedEntityMutator.d.ts +6 -6
- package/build/src/AuthorizationResultBasedEntityMutator.js +9 -2
- package/build/src/AuthorizationResultBasedEntityMutator.js.map +1 -1
- package/build/src/EntityCompanion.js +1 -1
- package/build/src/EntityCompanionProvider.d.ts +4 -4
- package/build/src/EntityDatabaseAdapter.d.ts +17 -0
- package/build/src/EntityDatabaseAdapter.js +11 -0
- package/build/src/EntityDatabaseAdapter.js.map +1 -1
- package/build/src/EntityMutationInfo.d.ts +4 -8
- package/build/src/EntityMutationValidatorConfiguration.d.ts +26 -0
- package/build/src/EntityMutationValidatorConfiguration.js +11 -0
- package/build/src/EntityMutationValidatorConfiguration.js.map +1 -0
- package/build/src/EntityMutatorFactory.d.ts +2 -2
- package/build/src/EntityMutatorFactory.js.map +1 -1
- package/build/src/index.d.ts +1 -1
- package/build/src/index.js +1 -1
- package/build/src/index.js.map +1 -1
- package/package.json +5 -5
- package/src/AuthorizationResultBasedEntityMutator.ts +28 -16
- package/src/EntityCompanion.ts +1 -1
- package/src/EntityCompanionProvider.ts +5 -5
- package/src/EntityDatabaseAdapter.ts +19 -0
- package/src/EntityMutationInfo.ts +29 -17
- package/src/EntityMutationValidatorConfiguration.ts +64 -0
- package/src/EntityMutatorFactory.ts +3 -3
- package/src/__tests__/EntityMutator-test.ts +161 -54
- package/src/index.ts +1 -1
- package/src/utils/__testfixtures__/PrivacyPolicyRuleTestUtils.ts +2 -2
- package/build/src/EntityMutationValidator.d.ts +0 -11
- package/build/src/EntityMutationValidator.js +0 -13
- package/build/src/EntityMutationValidator.js.map +0 -1
- package/src/EntityMutationValidator.ts +0 -33
|
@@ -67,8 +67,20 @@ export interface TableFieldMultiValueEqualityCondition {
|
|
|
67
67
|
tableValues: readonly any[];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Ordering options for `orderBy` clauses.
|
|
72
|
+
*/
|
|
70
73
|
export enum OrderByOrdering {
|
|
74
|
+
/**
|
|
75
|
+
* Ascending order (lowest to highest).
|
|
76
|
+
* Ascending order puts smaller values first, where "smaller" is defined in terms of the %3C operator.
|
|
77
|
+
*/
|
|
71
78
|
ASCENDING = 'asc',
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Descending order (highest to lowest).
|
|
82
|
+
* Descending order puts larger values first, where "larger" is defined in terms of the %3E operator.
|
|
83
|
+
*/
|
|
72
84
|
DESCENDING = 'desc',
|
|
73
85
|
}
|
|
74
86
|
|
|
@@ -80,7 +92,14 @@ export interface QuerySelectionModifiers<TFields extends Record<string, any>> {
|
|
|
80
92
|
* Order the entities by specified columns and orders.
|
|
81
93
|
*/
|
|
82
94
|
orderBy?: {
|
|
95
|
+
/**
|
|
96
|
+
* The field name to order by.
|
|
97
|
+
*/
|
|
83
98
|
fieldName: keyof TFields;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The OrderByOrdering to order by.
|
|
102
|
+
*/
|
|
84
103
|
order: OrderByOrdering;
|
|
85
104
|
}[];
|
|
86
105
|
|
|
@@ -7,22 +7,6 @@ export enum EntityMutationType {
|
|
|
7
7
|
DELETE,
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export type EntityValidatorMutationInfo<
|
|
11
|
-
TFields extends Record<string, any>,
|
|
12
|
-
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
13
|
-
TViewerContext extends ViewerContext,
|
|
14
|
-
TEntity extends Entity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
15
|
-
TSelectedFields extends keyof TFields = keyof TFields,
|
|
16
|
-
> =
|
|
17
|
-
| {
|
|
18
|
-
type: EntityMutationType.CREATE;
|
|
19
|
-
}
|
|
20
|
-
| {
|
|
21
|
-
type: EntityMutationType.UPDATE;
|
|
22
|
-
previousValue: TEntity;
|
|
23
|
-
cascadingDeleteCause: EntityCascadingDeletionInfo | null;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
10
|
/**
|
|
27
11
|
* Information about a cascading deletion.
|
|
28
12
|
*/
|
|
@@ -38,7 +22,7 @@ export type EntityCascadingDeletionInfo = {
|
|
|
38
22
|
cascadingDeleteCause: EntityCascadingDeletionInfo | null;
|
|
39
23
|
};
|
|
40
24
|
|
|
41
|
-
|
|
25
|
+
type EntityTriggerOrValidatorMutationInfo<
|
|
42
26
|
TFields extends Record<string, any>,
|
|
43
27
|
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
44
28
|
TViewerContext extends ViewerContext,
|
|
@@ -57,3 +41,31 @@ export type EntityTriggerMutationInfo<
|
|
|
57
41
|
type: EntityMutationType.DELETE;
|
|
58
42
|
cascadingDeleteCause: EntityCascadingDeletionInfo | null;
|
|
59
43
|
};
|
|
44
|
+
|
|
45
|
+
export type EntityValidatorMutationInfo<
|
|
46
|
+
TFields extends Record<string, any>,
|
|
47
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
48
|
+
TViewerContext extends ViewerContext,
|
|
49
|
+
TEntity extends Entity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
50
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
51
|
+
> = EntityTriggerOrValidatorMutationInfo<
|
|
52
|
+
TFields,
|
|
53
|
+
TIDField,
|
|
54
|
+
TViewerContext,
|
|
55
|
+
TEntity,
|
|
56
|
+
TSelectedFields
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
export type EntityTriggerMutationInfo<
|
|
60
|
+
TFields extends Record<string, any>,
|
|
61
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
62
|
+
TViewerContext extends ViewerContext,
|
|
63
|
+
TEntity extends Entity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
64
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
65
|
+
> = EntityTriggerOrValidatorMutationInfo<
|
|
66
|
+
TFields,
|
|
67
|
+
TIDField,
|
|
68
|
+
TViewerContext,
|
|
69
|
+
TEntity,
|
|
70
|
+
TSelectedFields
|
|
71
|
+
>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { EntityValidatorMutationInfo } from './EntityMutationInfo';
|
|
2
|
+
import { EntityTransactionalQueryContext } from './EntityQueryContext';
|
|
3
|
+
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
4
|
+
import { ViewerContext } from './ViewerContext';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interface to define validator behavior for entities.
|
|
8
|
+
*/
|
|
9
|
+
export interface EntityMutationValidatorConfiguration<
|
|
10
|
+
TFields extends Record<string, any>,
|
|
11
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
12
|
+
TViewerContext extends ViewerContext,
|
|
13
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
14
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
15
|
+
> {
|
|
16
|
+
/**
|
|
17
|
+
* Validator set that runs within the transaction but before the entity is created.
|
|
18
|
+
*/
|
|
19
|
+
beforeCreateAndUpdate?: EntityMutationValidator<
|
|
20
|
+
TFields,
|
|
21
|
+
TIDField,
|
|
22
|
+
TViewerContext,
|
|
23
|
+
TEntity,
|
|
24
|
+
TSelectedFields
|
|
25
|
+
>[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validator set that runs within the transaction but before the entity is deleted. This can be used
|
|
29
|
+
* to maintain constraints not expressable via other means, like checking that this entity is not referenced
|
|
30
|
+
* by other entities (in a non-foreign-key-manner) before proceeding with deletion.
|
|
31
|
+
*/
|
|
32
|
+
beforeDelete?: EntityMutationValidator<
|
|
33
|
+
TFields,
|
|
34
|
+
TIDField,
|
|
35
|
+
TViewerContext,
|
|
36
|
+
TEntity,
|
|
37
|
+
TSelectedFields
|
|
38
|
+
>[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A validator is a way to specify side-effect-free entity mutation validation that runs within the
|
|
43
|
+
* same transaction as the mutation itself.
|
|
44
|
+
*/
|
|
45
|
+
export abstract class EntityMutationValidator<
|
|
46
|
+
TFields extends Record<string, any>,
|
|
47
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
48
|
+
TViewerContext extends ViewerContext,
|
|
49
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
50
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
51
|
+
> {
|
|
52
|
+
abstract executeAsync(
|
|
53
|
+
viewerContext: TViewerContext,
|
|
54
|
+
queryContext: EntityTransactionalQueryContext,
|
|
55
|
+
entity: TEntity,
|
|
56
|
+
mutationInfo: EntityValidatorMutationInfo<
|
|
57
|
+
TFields,
|
|
58
|
+
TIDField,
|
|
59
|
+
TViewerContext,
|
|
60
|
+
TEntity,
|
|
61
|
+
TSelectedFields
|
|
62
|
+
>,
|
|
63
|
+
): Promise<void>;
|
|
64
|
+
}
|
|
@@ -10,7 +10,7 @@ import { EntityDatabaseAdapter } from './EntityDatabaseAdapter';
|
|
|
10
10
|
import { EntityLoaderFactory } from './EntityLoaderFactory';
|
|
11
11
|
import { EntityCascadingDeletionInfo } from './EntityMutationInfo';
|
|
12
12
|
import { EntityMutationTriggerConfiguration } from './EntityMutationTriggerConfiguration';
|
|
13
|
-
import {
|
|
13
|
+
import { EntityMutationValidatorConfiguration } from './EntityMutationValidatorConfiguration';
|
|
14
14
|
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
|
|
15
15
|
import { EntityQueryContext } from './EntityQueryContext';
|
|
16
16
|
import { ViewerContext } from './ViewerContext';
|
|
@@ -45,13 +45,13 @@ export class EntityMutatorFactory<
|
|
|
45
45
|
TSelectedFields
|
|
46
46
|
>,
|
|
47
47
|
private readonly privacyPolicy: TPrivacyPolicy,
|
|
48
|
-
private readonly mutationValidators:
|
|
48
|
+
private readonly mutationValidators: EntityMutationValidatorConfiguration<
|
|
49
49
|
TFields,
|
|
50
50
|
TIDField,
|
|
51
51
|
TViewerContext,
|
|
52
52
|
TEntity,
|
|
53
53
|
TSelectedFields
|
|
54
|
-
|
|
54
|
+
>,
|
|
55
55
|
private readonly mutationTriggers: EntityMutationTriggerConfiguration<
|
|
56
56
|
TFields,
|
|
57
57
|
TIDField,
|
|
@@ -30,7 +30,10 @@ import {
|
|
|
30
30
|
EntityMutationTriggerConfiguration,
|
|
31
31
|
EntityNonTransactionalMutationTrigger,
|
|
32
32
|
} from '../EntityMutationTriggerConfiguration';
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
EntityMutationValidator,
|
|
35
|
+
EntityMutationValidatorConfiguration,
|
|
36
|
+
} from '../EntityMutationValidatorConfiguration';
|
|
34
37
|
import { EntityMutatorFactory } from '../EntityMutatorFactory';
|
|
35
38
|
import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
|
|
36
39
|
import { EntityQueryContext, EntityTransactionalQueryContext } from '../EntityQueryContext';
|
|
@@ -57,6 +60,27 @@ import {
|
|
|
57
60
|
TestFields,
|
|
58
61
|
} from '../utils/__testfixtures__/TestEntity';
|
|
59
62
|
|
|
63
|
+
class TestMutationValidator extends EntityMutationValidator<
|
|
64
|
+
TestFields,
|
|
65
|
+
'customIdField',
|
|
66
|
+
ViewerContext,
|
|
67
|
+
TestEntity,
|
|
68
|
+
keyof TestFields
|
|
69
|
+
> {
|
|
70
|
+
async executeAsync(
|
|
71
|
+
_viewerContext: ViewerContext,
|
|
72
|
+
_queryContext: EntityQueryContext,
|
|
73
|
+
_entity: TestEntity,
|
|
74
|
+
_mutationInfo: EntityValidatorMutationInfo<
|
|
75
|
+
TestFields,
|
|
76
|
+
'customIdField',
|
|
77
|
+
ViewerContext,
|
|
78
|
+
TestEntity,
|
|
79
|
+
keyof TestFields
|
|
80
|
+
>,
|
|
81
|
+
): Promise<void> {}
|
|
82
|
+
}
|
|
83
|
+
|
|
60
84
|
class TestMutationTrigger extends EntityMutationTrigger<
|
|
61
85
|
TestFields,
|
|
62
86
|
'customIdField',
|
|
@@ -89,33 +113,48 @@ class TestNonTransactionalMutationTrigger extends EntityNonTransactionalMutation
|
|
|
89
113
|
}
|
|
90
114
|
|
|
91
115
|
const setUpMutationValidatorSpies = (
|
|
92
|
-
mutationValidators:
|
|
116
|
+
mutationValidators: EntityMutationValidatorConfiguration<
|
|
93
117
|
TestFields,
|
|
94
118
|
'customIdField',
|
|
95
119
|
ViewerContext,
|
|
96
120
|
TestEntity,
|
|
97
121
|
keyof TestFields
|
|
98
|
-
|
|
99
|
-
):
|
|
122
|
+
>,
|
|
123
|
+
): EntityMutationValidatorConfiguration<
|
|
100
124
|
TestFields,
|
|
101
125
|
'customIdField',
|
|
102
126
|
ViewerContext,
|
|
103
127
|
TestEntity,
|
|
104
128
|
keyof TestFields
|
|
105
|
-
>
|
|
106
|
-
return
|
|
129
|
+
> => {
|
|
130
|
+
return {
|
|
131
|
+
beforeCreateAndUpdate: [spy(mutationValidators.beforeCreateAndUpdate![0]!)],
|
|
132
|
+
beforeDelete: [spy(mutationValidators.beforeDelete![0]!)],
|
|
133
|
+
};
|
|
107
134
|
};
|
|
108
135
|
|
|
109
136
|
const verifyValidatorCounts = (
|
|
110
137
|
viewerContext: ViewerContext,
|
|
111
|
-
mutationValidatorSpies:
|
|
138
|
+
mutationValidatorSpies: EntityMutationValidatorConfiguration<
|
|
112
139
|
TestFields,
|
|
113
140
|
'customIdField',
|
|
114
141
|
ViewerContext,
|
|
115
142
|
TestEntity,
|
|
116
143
|
keyof TestFields
|
|
117
|
-
|
|
118
|
-
|
|
144
|
+
>,
|
|
145
|
+
executed: Record<
|
|
146
|
+
keyof Pick<
|
|
147
|
+
EntityMutationValidatorConfiguration<
|
|
148
|
+
TestFields,
|
|
149
|
+
'customIdField',
|
|
150
|
+
ViewerContext,
|
|
151
|
+
TestEntity,
|
|
152
|
+
keyof TestFields
|
|
153
|
+
>,
|
|
154
|
+
'beforeCreateAndUpdate' | 'beforeDelete'
|
|
155
|
+
>,
|
|
156
|
+
boolean
|
|
157
|
+
>,
|
|
119
158
|
mutationInfo: EntityValidatorMutationInfo<
|
|
120
159
|
TestFields,
|
|
121
160
|
'customIdField',
|
|
@@ -124,16 +163,28 @@ const verifyValidatorCounts = (
|
|
|
124
163
|
keyof TestFields
|
|
125
164
|
>,
|
|
126
165
|
): void => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
166
|
+
Object.keys(executed).forEach((s) => {
|
|
167
|
+
const sk = s as keyof typeof executed;
|
|
168
|
+
if (executed[sk]) {
|
|
169
|
+
verify(
|
|
170
|
+
mutationValidatorSpies[sk]![0]!.executeAsync(
|
|
171
|
+
viewerContext,
|
|
172
|
+
anyOfClass(EntityTransactionalQueryContext),
|
|
173
|
+
anyOfClass(TestEntity),
|
|
174
|
+
deepEqual(mutationInfo),
|
|
175
|
+
),
|
|
176
|
+
).once();
|
|
177
|
+
} else {
|
|
178
|
+
verify(
|
|
179
|
+
mutationValidatorSpies[sk]![0]!.executeAsync(
|
|
180
|
+
viewerContext,
|
|
181
|
+
anyOfClass(EntityTransactionalQueryContext),
|
|
182
|
+
anyOfClass(TestEntity),
|
|
183
|
+
deepEqual(mutationInfo),
|
|
184
|
+
),
|
|
185
|
+
).never();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
137
188
|
};
|
|
138
189
|
|
|
139
190
|
const setUpMutationTriggerSpies = (
|
|
@@ -200,9 +251,10 @@ const verifyTriggerCounts = (
|
|
|
200
251
|
>,
|
|
201
252
|
): void => {
|
|
202
253
|
Object.keys(executed).forEach((s) => {
|
|
203
|
-
|
|
254
|
+
const sk = s as keyof typeof executed;
|
|
255
|
+
if (executed[sk]) {
|
|
204
256
|
verify(
|
|
205
|
-
|
|
257
|
+
mutationTriggerSpies[sk]![0]!.executeAsync(
|
|
206
258
|
viewerContext,
|
|
207
259
|
anyOfClass(EntityTransactionalQueryContext),
|
|
208
260
|
anyOfClass(TestEntity),
|
|
@@ -211,7 +263,7 @@ const verifyTriggerCounts = (
|
|
|
211
263
|
).once();
|
|
212
264
|
} else {
|
|
213
265
|
verify(
|
|
214
|
-
|
|
266
|
+
mutationTriggerSpies[sk]![0]!.executeAsync(
|
|
215
267
|
viewerContext,
|
|
216
268
|
anyOfClass(EntityTransactionalQueryContext),
|
|
217
269
|
anyOfClass(TestEntity),
|
|
@@ -268,13 +320,13 @@ const createEntityMutatorFactory = (
|
|
|
268
320
|
TestEntityPrivacyPolicy
|
|
269
321
|
>;
|
|
270
322
|
metricsAdapter: IEntityMetricsAdapter;
|
|
271
|
-
mutationValidators:
|
|
323
|
+
mutationValidators: EntityMutationValidatorConfiguration<
|
|
272
324
|
TestFields,
|
|
273
325
|
'customIdField',
|
|
274
326
|
ViewerContext,
|
|
275
327
|
TestEntity,
|
|
276
328
|
keyof TestFields
|
|
277
|
-
|
|
329
|
+
>;
|
|
278
330
|
mutationTriggers: EntityMutationTriggerConfiguration<
|
|
279
331
|
TestFields,
|
|
280
332
|
'customIdField',
|
|
@@ -283,13 +335,16 @@ const createEntityMutatorFactory = (
|
|
|
283
335
|
keyof TestFields
|
|
284
336
|
>;
|
|
285
337
|
} => {
|
|
286
|
-
const mutationValidators:
|
|
338
|
+
const mutationValidators: EntityMutationValidatorConfiguration<
|
|
287
339
|
TestFields,
|
|
288
340
|
'customIdField',
|
|
289
341
|
ViewerContext,
|
|
290
342
|
TestEntity,
|
|
291
343
|
keyof TestFields
|
|
292
|
-
>
|
|
344
|
+
> = {
|
|
345
|
+
beforeCreateAndUpdate: [new TestMutationValidator()],
|
|
346
|
+
beforeDelete: [new TestMutationValidator()],
|
|
347
|
+
};
|
|
293
348
|
const mutationTriggers: EntityMutationTriggerConfiguration<
|
|
294
349
|
TestFields,
|
|
295
350
|
'customIdField',
|
|
@@ -547,7 +602,15 @@ describe(EntityMutatorFactory, () => {
|
|
|
547
602
|
.createAsync(),
|
|
548
603
|
);
|
|
549
604
|
|
|
550
|
-
verifyValidatorCounts(
|
|
605
|
+
verifyValidatorCounts(
|
|
606
|
+
viewerContext,
|
|
607
|
+
validatorSpies,
|
|
608
|
+
{
|
|
609
|
+
beforeCreateAndUpdate: true,
|
|
610
|
+
beforeDelete: false,
|
|
611
|
+
},
|
|
612
|
+
{ type: EntityMutationType.CREATE },
|
|
613
|
+
);
|
|
551
614
|
});
|
|
552
615
|
});
|
|
553
616
|
|
|
@@ -801,11 +864,19 @@ describe(EntityMutatorFactory, () => {
|
|
|
801
864
|
.updateAsync(),
|
|
802
865
|
);
|
|
803
866
|
|
|
804
|
-
verifyValidatorCounts(
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
867
|
+
verifyValidatorCounts(
|
|
868
|
+
viewerContext,
|
|
869
|
+
validatorSpies,
|
|
870
|
+
{
|
|
871
|
+
beforeCreateAndUpdate: true,
|
|
872
|
+
beforeDelete: false,
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
type: EntityMutationType.UPDATE,
|
|
876
|
+
previousValue: existingEntity,
|
|
877
|
+
cascadingDeleteCause: null,
|
|
878
|
+
},
|
|
879
|
+
);
|
|
809
880
|
});
|
|
810
881
|
|
|
811
882
|
it('passes manaully-specified cascading delete cause to privacy policy and validators and triggers', async () => {
|
|
@@ -893,11 +964,19 @@ describe(EntityMutatorFactory, () => {
|
|
|
893
964
|
),
|
|
894
965
|
).once();
|
|
895
966
|
|
|
896
|
-
verifyValidatorCounts(
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
967
|
+
verifyValidatorCounts(
|
|
968
|
+
viewerContext,
|
|
969
|
+
validatorSpies,
|
|
970
|
+
{
|
|
971
|
+
beforeCreateAndUpdate: true,
|
|
972
|
+
beforeDelete: false,
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
type: EntityMutationType.UPDATE,
|
|
976
|
+
previousValue: existingEntity,
|
|
977
|
+
cascadingDeleteCause,
|
|
978
|
+
},
|
|
979
|
+
);
|
|
901
980
|
|
|
902
981
|
verifyTriggerCounts(
|
|
903
982
|
viewerContext,
|
|
@@ -1133,7 +1212,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
1133
1212
|
);
|
|
1134
1213
|
});
|
|
1135
1214
|
|
|
1136
|
-
it('
|
|
1215
|
+
it('executes validators', async () => {
|
|
1137
1216
|
const viewerContext = mock<ViewerContext>();
|
|
1138
1217
|
const privacyPolicyEvaluationContext =
|
|
1139
1218
|
instance(
|
|
@@ -1176,9 +1255,18 @@ describe(EntityMutatorFactory, () => {
|
|
|
1176
1255
|
.deleteAsync(),
|
|
1177
1256
|
);
|
|
1178
1257
|
|
|
1179
|
-
verifyValidatorCounts(
|
|
1180
|
-
|
|
1181
|
-
|
|
1258
|
+
verifyValidatorCounts(
|
|
1259
|
+
viewerContext,
|
|
1260
|
+
validatorSpies,
|
|
1261
|
+
{
|
|
1262
|
+
beforeCreateAndUpdate: false,
|
|
1263
|
+
beforeDelete: true,
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
type: EntityMutationType.DELETE,
|
|
1267
|
+
cascadingDeleteCause: null,
|
|
1268
|
+
},
|
|
1269
|
+
);
|
|
1182
1270
|
});
|
|
1183
1271
|
|
|
1184
1272
|
it('passes manaully-specified cascading delete cause to privacy policy and triggers', async () => {
|
|
@@ -1198,20 +1286,26 @@ describe(EntityMutatorFactory, () => {
|
|
|
1198
1286
|
const queryContext = new StubQueryContextProvider().getQueryContext();
|
|
1199
1287
|
|
|
1200
1288
|
const id1 = uuidv4();
|
|
1201
|
-
const {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1289
|
+
const {
|
|
1290
|
+
mutationTriggers,
|
|
1291
|
+
mutationValidators,
|
|
1292
|
+
privacyPolicy,
|
|
1293
|
+
entityMutatorFactory,
|
|
1294
|
+
entityLoaderFactory,
|
|
1295
|
+
} = createEntityMutatorFactory([
|
|
1296
|
+
{
|
|
1297
|
+
customIdField: id1,
|
|
1298
|
+
stringField: 'huh',
|
|
1299
|
+
testIndexedField: '3',
|
|
1300
|
+
intField: 3,
|
|
1301
|
+
dateField: new Date(),
|
|
1302
|
+
nullableField: null,
|
|
1303
|
+
},
|
|
1304
|
+
]);
|
|
1212
1305
|
|
|
1213
1306
|
const spiedPrivacyPolicy = spy(privacyPolicy);
|
|
1214
1307
|
const triggerSpies = setUpMutationTriggerSpies(mutationTriggers);
|
|
1308
|
+
const validatorSpies = setUpMutationValidatorSpies(mutationValidators);
|
|
1215
1309
|
|
|
1216
1310
|
const existingEntity = await enforceAsyncResult(
|
|
1217
1311
|
entityLoaderFactory
|
|
@@ -1240,6 +1334,19 @@ describe(EntityMutatorFactory, () => {
|
|
|
1240
1334
|
),
|
|
1241
1335
|
).once();
|
|
1242
1336
|
|
|
1337
|
+
verifyValidatorCounts(
|
|
1338
|
+
viewerContext,
|
|
1339
|
+
validatorSpies,
|
|
1340
|
+
{
|
|
1341
|
+
beforeCreateAndUpdate: false,
|
|
1342
|
+
beforeDelete: true,
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
type: EntityMutationType.DELETE,
|
|
1346
|
+
cascadingDeleteCause,
|
|
1347
|
+
},
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1243
1350
|
verifyTriggerCounts(
|
|
1244
1351
|
viewerContext,
|
|
1245
1352
|
triggerSpies,
|
|
@@ -1462,7 +1569,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
1462
1569
|
simpleTestEntityConfiguration,
|
|
1463
1570
|
SimpleTestEntity,
|
|
1464
1571
|
instance(privacyPolicyMock),
|
|
1465
|
-
|
|
1572
|
+
{},
|
|
1466
1573
|
{},
|
|
1467
1574
|
entityLoaderFactory,
|
|
1468
1575
|
databaseAdapter,
|
|
@@ -1588,7 +1695,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
1588
1695
|
simpleTestEntityConfiguration,
|
|
1589
1696
|
SimpleTestEntity,
|
|
1590
1697
|
privacyPolicy,
|
|
1591
|
-
|
|
1698
|
+
{},
|
|
1592
1699
|
{},
|
|
1593
1700
|
entityLoaderFactory,
|
|
1594
1701
|
instance(databaseAdapterMock),
|
package/src/index.ts
CHANGED
|
@@ -29,7 +29,7 @@ export * from './EntityLoaderFactory';
|
|
|
29
29
|
export * from './EntityLoaderUtils';
|
|
30
30
|
export * from './EntityMutationInfo';
|
|
31
31
|
export * from './EntityMutationTriggerConfiguration';
|
|
32
|
-
export * from './
|
|
32
|
+
export * from './EntityMutationValidatorConfiguration';
|
|
33
33
|
export * from './EntityMutatorFactory';
|
|
34
34
|
export * from './EntityPrivacyPolicy';
|
|
35
35
|
export * from './EntityQueryContext';
|
|
@@ -11,7 +11,7 @@ export interface Case<
|
|
|
11
11
|
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
12
12
|
TViewerContext extends ViewerContext,
|
|
13
13
|
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
14
|
-
TSelectedFields extends keyof TFields,
|
|
14
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
15
15
|
> {
|
|
16
16
|
viewerContext: TViewerContext;
|
|
17
17
|
queryContext: EntityQueryContext;
|
|
@@ -30,7 +30,7 @@ export type CaseMap<
|
|
|
30
30
|
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
31
31
|
TViewerContext extends ViewerContext,
|
|
32
32
|
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
33
|
-
TSelectedFields extends keyof TFields,
|
|
33
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
34
34
|
> = Map<string, () => Promise<Case<TFields, TIDField, TViewerContext, TEntity, TSelectedFields>>>;
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { EntityValidatorMutationInfo } from './EntityMutationInfo';
|
|
2
|
-
import { EntityTransactionalQueryContext } from './EntityQueryContext';
|
|
3
|
-
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
4
|
-
import { ViewerContext } from './ViewerContext';
|
|
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 declare abstract class EntityMutationValidator<TFields extends Record<string, any>, TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>, TViewerContext extends ViewerContext, TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>, TSelectedFields extends keyof TFields = keyof TFields> {
|
|
10
|
-
abstract executeAsync(viewerContext: TViewerContext, queryContext: EntityTransactionalQueryContext, entity: TEntity, mutationInfo: EntityValidatorMutationInfo<TFields, TIDField, TViewerContext, TEntity, TSelectedFields>): Promise<void>;
|
|
11
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* c8 ignore start - abstract class only */
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.EntityMutationValidator = void 0;
|
|
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
|
-
class EntityMutationValidator {
|
|
10
|
-
}
|
|
11
|
-
exports.EntityMutationValidator = EntityMutationValidator;
|
|
12
|
-
/* c8 ignore stop - abstract class only */
|
|
13
|
-
//# sourceMappingURL=EntityMutationValidator.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EntityMutationValidator.js","sourceRoot":"","sources":["../../src/EntityMutationValidator.ts"],"names":[],"mappings":";AAAA,2CAA2C;;;AAO3C;;;GAGG;AACH,MAAsB,uBAAuB;CAmB5C;AAnBD,0DAmBC;AAED,0CAA0C"}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/* c8 ignore start - abstract class only */
|
|
2
|
-
|
|
3
|
-
import { EntityValidatorMutationInfo } from './EntityMutationInfo';
|
|
4
|
-
import { EntityTransactionalQueryContext } from './EntityQueryContext';
|
|
5
|
-
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
6
|
-
import { ViewerContext } from './ViewerContext';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A validator is a way to specify entity mutation validation that runs within the
|
|
10
|
-
* same transaction as the mutation itself before creating or updating an entity.
|
|
11
|
-
*/
|
|
12
|
-
export abstract class EntityMutationValidator<
|
|
13
|
-
TFields extends Record<string, any>,
|
|
14
|
-
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
15
|
-
TViewerContext extends ViewerContext,
|
|
16
|
-
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
17
|
-
TSelectedFields extends keyof TFields = keyof TFields,
|
|
18
|
-
> {
|
|
19
|
-
abstract executeAsync(
|
|
20
|
-
viewerContext: TViewerContext,
|
|
21
|
-
queryContext: EntityTransactionalQueryContext,
|
|
22
|
-
entity: TEntity,
|
|
23
|
-
mutationInfo: EntityValidatorMutationInfo<
|
|
24
|
-
TFields,
|
|
25
|
-
TIDField,
|
|
26
|
-
TViewerContext,
|
|
27
|
-
TEntity,
|
|
28
|
-
TSelectedFields
|
|
29
|
-
>,
|
|
30
|
-
): Promise<void>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/* c8 ignore stop - abstract class only */
|