@expo/entity 0.16.0 → 0.20.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.
Files changed (210) hide show
  1. package/build/EnforcingEntityLoader.js +2 -2
  2. package/build/EnforcingEntityLoader.js.map +1 -1
  3. package/build/Entity.js +8 -2
  4. package/build/Entity.js.map +1 -1
  5. package/build/EntityAssociationLoader.js +3 -3
  6. package/build/EntityAssociationLoader.js.map +1 -1
  7. package/build/EntityCompanion.d.ts +5 -0
  8. package/build/EntityCompanion.js +8 -1
  9. package/build/EntityCompanion.js.map +1 -1
  10. package/build/EntityCompanionProvider.d.ts +1 -1
  11. package/build/EntityCompanionProvider.js +5 -5
  12. package/build/EntityCompanionProvider.js.map +1 -1
  13. package/build/EntityConfiguration.d.ts +1 -1
  14. package/build/EntityConfiguration.js +3 -3
  15. package/build/EntityConfiguration.js.map +1 -1
  16. package/build/EntityDatabaseAdapter.d.ts +4 -4
  17. package/build/EntityDatabaseAdapter.js +13 -13
  18. package/build/EntityDatabaseAdapter.js.map +1 -1
  19. package/build/EntityFieldDefinition.d.ts +77 -0
  20. package/build/EntityFieldDefinition.js +53 -0
  21. package/build/EntityFieldDefinition.js.map +1 -0
  22. package/build/EntityFields.d.ts +5 -78
  23. package/build/EntityFields.js +19 -61
  24. package/build/EntityFields.js.map +1 -1
  25. package/build/EntityLoader.d.ts +3 -1
  26. package/build/EntityLoader.js +19 -15
  27. package/build/EntityLoader.js.map +1 -1
  28. package/build/EntityLoaderFactory.d.ts +3 -1
  29. package/build/EntityLoaderFactory.js +3 -2
  30. package/build/EntityLoaderFactory.js.map +1 -1
  31. package/build/EntityMutationInfo.d.ts +26 -0
  32. package/build/EntityMutationInfo.js +10 -0
  33. package/build/EntityMutationInfo.js.map +1 -0
  34. package/build/EntityMutationTriggerConfiguration.d.ts +4 -4
  35. package/build/EntityMutationValidator.d.ts +3 -3
  36. package/build/EntityMutationValidator.js.map +1 -1
  37. package/build/EntityMutator.d.ts +5 -16
  38. package/build/EntityMutator.js +62 -58
  39. package/build/EntityMutator.js.map +1 -1
  40. package/build/EntityPrivacyPolicy.d.ts +5 -4
  41. package/build/EntityPrivacyPolicy.js +60 -12
  42. package/build/EntityPrivacyPolicy.js.map +1 -1
  43. package/build/EntityQueryContext.d.ts +13 -0
  44. package/build/EntityQueryContext.js +18 -0
  45. package/build/EntityQueryContext.js.map +1 -1
  46. package/build/EntitySecondaryCacheLoader.js +2 -2
  47. package/build/EntitySecondaryCacheLoader.js.map +1 -1
  48. package/build/ReadonlyEntity.js +3 -4
  49. package/build/ReadonlyEntity.js.map +1 -1
  50. package/build/ViewerScopedEntityCompanion.d.ts +5 -0
  51. package/build/ViewerScopedEntityCompanion.js +6 -0
  52. package/build/ViewerScopedEntityCompanion.js.map +1 -1
  53. package/build/__tests__/EnforcingEntityLoader-test.js +82 -82
  54. package/build/__tests__/EnforcingEntityLoader-test.js.map +1 -1
  55. package/build/__tests__/Entity-test.js +6 -6
  56. package/build/__tests__/Entity-test.js.map +1 -1
  57. package/build/__tests__/EntityAssociationLoader-test.js +40 -40
  58. package/build/__tests__/EntityAssociationLoader-test.js.map +1 -1
  59. package/build/__tests__/EntityCommonUseCases-test.js +11 -11
  60. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  61. package/build/__tests__/EntityCompanion-test.js +3 -3
  62. package/build/__tests__/EntityCompanion-test.js.map +1 -1
  63. package/build/__tests__/EntityCompanionProvider-test.js +1 -1
  64. package/build/__tests__/EntityCompanionProvider-test.js.map +1 -1
  65. package/build/__tests__/EntityDatabaseAdapter-test.js +12 -12
  66. package/build/__tests__/EntityDatabaseAdapter-test.js.map +1 -1
  67. package/build/__tests__/EntityEdges-test.js +103 -6
  68. package/build/__tests__/EntityEdges-test.js.map +1 -1
  69. package/build/__tests__/EntityFields-test.js +18 -26
  70. package/build/__tests__/EntityFields-test.js.map +1 -1
  71. package/build/__tests__/EntityLoader-constructor-test.d.ts +22 -0
  72. package/build/__tests__/EntityLoader-constructor-test.js +111 -0
  73. package/build/__tests__/EntityLoader-constructor-test.js.map +1 -0
  74. package/build/__tests__/EntityLoader-test.js +81 -73
  75. package/build/__tests__/EntityLoader-test.js.map +1 -1
  76. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.d.ts +1 -0
  77. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js +81 -0
  78. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js.map +1 -0
  79. package/build/__tests__/EntityMutator-test.js +138 -136
  80. package/build/__tests__/EntityMutator-test.js.map +1 -1
  81. package/build/__tests__/EntityPrivacyPolicy-test.js +143 -67
  82. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  83. package/build/__tests__/EntitySecondaryCacheLoader-test.js +15 -15
  84. package/build/__tests__/EntitySecondaryCacheLoader-test.js.map +1 -1
  85. package/build/__tests__/EntitySelfReferentialEdges-test.js +13 -12
  86. package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
  87. package/build/__tests__/ReadonlyEntity-test.js +12 -12
  88. package/build/__tests__/ReadonlyEntity-test.js.map +1 -1
  89. package/build/__tests__/ViewerContext-test.js +2 -2
  90. package/build/__tests__/ViewerContext-test.js.map +1 -1
  91. package/build/__tests__/ViewerScopedEntityCompanion-test.js +2 -2
  92. package/build/__tests__/ViewerScopedEntityCompanion-test.js.map +1 -1
  93. package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js +2 -2
  94. package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js.map +1 -1
  95. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js +5 -5
  96. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
  97. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +5 -5
  98. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
  99. package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js +5 -5
  100. package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js.map +1 -1
  101. package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js +2 -2
  102. package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js.map +1 -1
  103. package/build/__tests__/entityUtils-test.js +21 -21
  104. package/build/__tests__/entityUtils-test.js.map +1 -1
  105. package/build/index.d.ts +3 -0
  106. package/build/index.js +5 -1
  107. package/build/index.js.map +1 -1
  108. package/build/internal/EntityDataManager.js +8 -7
  109. package/build/internal/EntityDataManager.js.map +1 -1
  110. package/build/internal/EntityFieldTransformationUtils.js +2 -2
  111. package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
  112. package/build/internal/ReadThroughEntityCache.js +4 -4
  113. package/build/internal/ReadThroughEntityCache.js.map +1 -1
  114. package/build/internal/__tests__/EntityDataManager-test.js +21 -21
  115. package/build/internal/__tests__/EntityDataManager-test.js.map +1 -1
  116. package/build/internal/__tests__/EntityFieldTransformationUtils-test.js +8 -8
  117. package/build/internal/__tests__/EntityFieldTransformationUtils-test.js.map +1 -1
  118. package/build/internal/__tests__/ReadThroughEntityCache-test.js +48 -48
  119. package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
  120. package/build/metrics/EntityMetricsUtils.js +1 -1
  121. package/build/metrics/EntityMetricsUtils.js.map +1 -1
  122. package/build/metrics/IEntityMetricsAdapter.d.ts +16 -0
  123. package/build/metrics/IEntityMetricsAdapter.js +6 -1
  124. package/build/metrics/IEntityMetricsAdapter.js.map +1 -1
  125. package/build/metrics/NoOpEntityMetricsAdapter.d.ts +2 -1
  126. package/build/metrics/NoOpEntityMetricsAdapter.js +1 -0
  127. package/build/metrics/NoOpEntityMetricsAdapter.js.map +1 -1
  128. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js +4 -4
  129. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
  130. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js +4 -4
  131. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
  132. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js +4 -4
  133. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
  134. package/build/testfixtures/DateIDTestEntity.js.map +1 -1
  135. package/build/testfixtures/SimpleTestEntity.js.map +1 -1
  136. package/build/testfixtures/TestEntity.d.ts +6 -6
  137. package/build/testfixtures/TestEntity.js +4 -4
  138. package/build/testfixtures/TestEntity.js.map +1 -1
  139. package/build/testfixtures/TestEntity2.d.ts +5 -5
  140. package/build/testfixtures/TestEntity2.js.map +1 -1
  141. package/build/testfixtures/TestEntityNumberKey.js +1 -1
  142. package/build/testfixtures/TestEntityNumberKey.js.map +1 -1
  143. package/build/utils/collections/__tests__/maps-test.js +13 -13
  144. package/build/utils/collections/__tests__/maps-test.js.map +1 -1
  145. package/build/utils/collections/maps.js +1 -1
  146. package/build/utils/collections/maps.js.map +1 -1
  147. package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +6 -6
  148. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js +1 -1
  149. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
  150. package/build/utils/testing/StubCacheAdapter.js +1 -1
  151. package/build/utils/testing/StubCacheAdapter.js.map +1 -1
  152. package/build/utils/testing/StubDatabaseAdapter.js +7 -7
  153. package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
  154. package/build/utils/testing/__tests__/StubDatabaseAdapter-test.js +26 -26
  155. package/build/utils/testing/__tests__/StubDatabaseAdapter-test.js.map +1 -1
  156. package/build/utils/testing/describeFieldTestCase.d.ts +2 -0
  157. package/build/utils/testing/describeFieldTestCase.js +18 -0
  158. package/build/utils/testing/describeFieldTestCase.js.map +1 -0
  159. package/package.json +2 -1
  160. package/src/Entity.ts +10 -2
  161. package/src/EntityAssociationLoader.ts +1 -1
  162. package/src/EntityCompanion.ts +10 -2
  163. package/src/EntityCompanionProvider.ts +5 -9
  164. package/src/EntityConfiguration.ts +1 -1
  165. package/src/EntityDatabaseAdapter.ts +10 -8
  166. package/src/EntityFieldDefinition.ts +124 -0
  167. package/src/EntityFields.ts +11 -126
  168. package/src/EntityLoader.ts +12 -4
  169. package/src/EntityLoaderFactory.ts +5 -2
  170. package/src/EntityMutationInfo.ts +47 -0
  171. package/src/EntityMutationTriggerConfiguration.ts +5 -5
  172. package/src/EntityMutationValidator.ts +10 -4
  173. package/src/EntityMutator.ts +98 -76
  174. package/src/EntityPrivacyPolicy.ts +77 -19
  175. package/src/EntityQueryContext.ts +20 -0
  176. package/src/ReadonlyEntity.ts +3 -2
  177. package/src/ViewerScopedEntityCompanion.ts +8 -0
  178. package/src/__tests__/Entity-test.ts +8 -8
  179. package/src/__tests__/EntityCommonUseCases-test.ts +4 -4
  180. package/src/__tests__/EntityEdges-test.ts +169 -14
  181. package/src/__tests__/EntityFields-test.ts +6 -23
  182. package/src/__tests__/EntityLoader-constructor-test.ts +177 -0
  183. package/src/__tests__/EntityLoader-test.ts +48 -20
  184. package/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts +105 -0
  185. package/src/__tests__/EntityMutator-test.ts +153 -146
  186. package/src/__tests__/EntityPrivacyPolicy-test.ts +215 -78
  187. package/src/__tests__/EntitySecondaryCacheLoader-test.ts +7 -9
  188. package/src/__tests__/EntitySelfReferentialEdges-test.ts +6 -5
  189. package/src/__tests__/ReadonlyEntity-test.ts +1 -1
  190. package/src/__tests__/ViewerContext-test.ts +7 -6
  191. package/src/__tests__/ViewerScopedEntityCompanion-test.ts +11 -10
  192. package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +4 -3
  193. package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +6 -6
  194. package/src/__tests__/cases/TwoEntitySameTableOverlappingRows-test.ts +4 -4
  195. package/src/index.ts +3 -0
  196. package/src/internal/EntityDataManager.ts +2 -1
  197. package/src/internal/__tests__/EntityDataManager-test.ts +6 -6
  198. package/src/internal/__tests__/ReadThroughEntityCache-test.ts +15 -13
  199. package/src/metrics/EntityMetricsUtils.ts +56 -50
  200. package/src/metrics/IEntityMetricsAdapter.ts +23 -0
  201. package/src/metrics/NoOpEntityMetricsAdapter.ts +2 -0
  202. package/src/testfixtures/DateIDTestEntity.ts +4 -4
  203. package/src/testfixtures/SimpleTestEntity.ts +4 -4
  204. package/src/testfixtures/TestEntity.ts +8 -8
  205. package/src/testfixtures/TestEntity2.ts +4 -4
  206. package/src/testfixtures/TestEntityNumberKey.ts +6 -6
  207. package/src/utils/testing/StubDatabaseAdapter.ts +4 -4
  208. package/src/utils/testing/__tests__/StubDatabaseAdapter-test.ts +18 -18
  209. package/src/utils/testing/describeFieldTestCase.ts +21 -0
  210. package/CHANGELOG.md +0 -241
@@ -5,8 +5,14 @@ import Entity, { IEntityClass } from './Entity';
5
5
  import { EntityCompanionDefinition } from './EntityCompanionProvider';
6
6
  import EntityConfiguration from './EntityConfiguration';
7
7
  import EntityDatabaseAdapter from './EntityDatabaseAdapter';
8
- import { EntityEdgeDeletionBehavior } from './EntityFields';
8
+ import { EntityEdgeDeletionBehavior } from './EntityFieldDefinition';
9
9
  import EntityLoaderFactory from './EntityLoaderFactory';
10
+ import {
11
+ EntityValidatorMutationInfo,
12
+ EntityMutationType,
13
+ EntityTriggerMutationInfo,
14
+ EntityMutationTriggerDeleteCascadeInfo,
15
+ } from './EntityMutationInfo';
10
16
  import EntityMutationTriggerConfiguration, {
11
17
  EntityMutationTrigger,
12
18
  EntityNonTransactionalMutationTrigger,
@@ -21,30 +27,6 @@ import { timeAndLogMutationEventAsync } from './metrics/EntityMetricsUtils';
21
27
  import IEntityMetricsAdapter, { EntityMetricsMutationType } from './metrics/IEntityMetricsAdapter';
22
28
  import { mapMapAsync } from './utils/collections/maps';
23
29
 
24
- export enum EntityMutationType {
25
- CREATE,
26
- UPDATE,
27
- DELETE,
28
- }
29
-
30
- export type EntityMutationInfo<
31
- TFields,
32
- TID extends NonNullable<TFields[TSelectedFields]>,
33
- TViewerContext extends ViewerContext,
34
- TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
35
- TSelectedFields extends keyof TFields = keyof TFields
36
- > =
37
- | {
38
- type: EntityMutationType.CREATE;
39
- }
40
- | {
41
- type: EntityMutationType.UPDATE;
42
- previousValue: TEntity;
43
- }
44
- | {
45
- type: EntityMutationType.DELETE;
46
- };
47
-
48
30
  abstract class BaseMutator<
49
31
  TFields,
50
32
  TID extends NonNullable<TFields[TSelectedFields]>,
@@ -110,26 +92,44 @@ abstract class BaseMutator<
110
92
  }
111
93
  }
112
94
 
113
- protected async executeMutationTriggersOrValidatorsAsync(
114
- triggersOrValidators:
95
+ protected async executeMutationValidatorsAsync(
96
+ validators: EntityMutationValidator<TFields, TID, TViewerContext, TEntity, TSelectedFields>[],
97
+ queryContext: EntityTransactionalQueryContext,
98
+ entity: TEntity,
99
+ mutationInfo: EntityValidatorMutationInfo<
100
+ TFields,
101
+ TID,
102
+ TViewerContext,
103
+ TEntity,
104
+ TSelectedFields
105
+ >
106
+ ): Promise<void> {
107
+ await Promise.all(
108
+ validators.map((validator) =>
109
+ validator.executeAsync(this.viewerContext, queryContext, entity, mutationInfo)
110
+ )
111
+ );
112
+ }
113
+
114
+ protected async executeMutationTriggersAsync(
115
+ triggers:
115
116
  | EntityMutationTrigger<TFields, TID, TViewerContext, TEntity, TSelectedFields>[]
116
- | EntityMutationValidator<TFields, TID, TViewerContext, TEntity, TSelectedFields>[]
117
117
  | undefined,
118
- queryContext: EntityQueryContext,
118
+ queryContext: EntityTransactionalQueryContext,
119
119
  entity: TEntity,
120
- mutationInfo: EntityMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
120
+ mutationInfo: EntityTriggerMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
121
121
  ): Promise<void> {
122
- if (!triggersOrValidators) {
122
+ if (!triggers) {
123
123
  return;
124
124
  }
125
125
  await Promise.all(
126
- triggersOrValidators.map((triggerOrValidator) =>
127
- triggerOrValidator.executeAsync(this.viewerContext, queryContext, entity, mutationInfo)
126
+ triggers.map((trigger) =>
127
+ trigger.executeAsync(this.viewerContext, queryContext, entity, mutationInfo)
128
128
  )
129
129
  );
130
130
  }
131
131
 
132
- protected async executeNonTransactionalMutationTriggersOrValidatorsAsync(
132
+ protected async executeNonTransactionalMutationTriggersAsync(
133
133
  triggers:
134
134
  | EntityNonTransactionalMutationTrigger<
135
135
  TFields,
@@ -140,7 +140,7 @@ abstract class BaseMutator<
140
140
  >[]
141
141
  | undefined,
142
142
  entity: TEntity,
143
- mutationInfo: EntityMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
143
+ mutationInfo: EntityTriggerMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
144
144
  ): Promise<void> {
145
145
  if (!triggers) {
146
146
  return;
@@ -211,35 +211,36 @@ export class CreateMutator<
211
211
  ): Promise<Result<TEntity>> {
212
212
  this.validateFields(this.fieldsForEntity);
213
213
 
214
- const temporaryEntityForPrivacyCheck = new this.entityClass(this.viewerContext, ({
214
+ const temporaryEntityForPrivacyCheck = new this.entityClass(this.viewerContext, {
215
215
  [this.entityConfiguration.idField]: '00000000-0000-0000-0000-000000000000', // zero UUID
216
216
  ...this.fieldsForEntity,
217
- } as unknown) as TFields);
217
+ } as unknown as TFields);
218
218
 
219
219
  const authorizeCreateResult = await asyncResult(
220
220
  this.privacyPolicy.authorizeCreateAsync(
221
221
  this.viewerContext,
222
222
  queryContext,
223
- temporaryEntityForPrivacyCheck
223
+ temporaryEntityForPrivacyCheck,
224
+ this.metricsAdapter
224
225
  )
225
226
  );
226
227
  if (!authorizeCreateResult.ok) {
227
228
  return authorizeCreateResult;
228
229
  }
229
230
 
230
- await this.executeMutationTriggersOrValidatorsAsync(
231
+ await this.executeMutationValidatorsAsync(
231
232
  this.mutationValidators,
232
233
  queryContext,
233
234
  temporaryEntityForPrivacyCheck,
234
235
  { type: EntityMutationType.CREATE }
235
236
  );
236
- await this.executeMutationTriggersOrValidatorsAsync(
237
+ await this.executeMutationTriggersAsync(
237
238
  this.mutationTriggers.beforeAll,
238
239
  queryContext,
239
240
  temporaryEntityForPrivacyCheck,
240
241
  { type: EntityMutationType.CREATE }
241
242
  );
242
- await this.executeMutationTriggersOrValidatorsAsync(
243
+ await this.executeMutationTriggersAsync(
243
244
  this.mutationTriggers.beforeCreate,
244
245
  queryContext,
245
246
  temporaryEntityForPrivacyCheck,
@@ -249,7 +250,7 @@ export class CreateMutator<
249
250
  const insertResult = await this.databaseAdapter.insertAsync(queryContext, this.fieldsForEntity);
250
251
 
251
252
  const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext);
252
- queryContext.appendPostCommitCallback(
253
+ queryContext.appendPostCommitInvalidationCallback(
253
254
  entityLoader.invalidateFieldsAsync.bind(entityLoader, insertResult)
254
255
  );
255
256
 
@@ -258,13 +259,13 @@ export class CreateMutator<
258
259
  .enforcing()
259
260
  .loadByIDAsync(unauthorizedEntityAfterInsert.getID());
260
261
 
261
- await this.executeMutationTriggersOrValidatorsAsync(
262
+ await this.executeMutationTriggersAsync(
262
263
  this.mutationTriggers.afterCreate,
263
264
  queryContext,
264
265
  newEntity,
265
266
  { type: EntityMutationType.CREATE }
266
267
  );
267
- await this.executeMutationTriggersOrValidatorsAsync(
268
+ await this.executeMutationTriggersAsync(
268
269
  this.mutationTriggers.afterAll,
269
270
  queryContext,
270
271
  newEntity,
@@ -272,7 +273,7 @@ export class CreateMutator<
272
273
  );
273
274
 
274
275
  queryContext.appendPostCommitCallback(
275
- this.executeNonTransactionalMutationTriggersOrValidatorsAsync.bind(
276
+ this.executeNonTransactionalMutationTriggersAsync.bind(
276
277
  this,
277
278
  this.mutationTriggers.afterCommit,
278
279
  newEntity,
@@ -408,26 +409,27 @@ export class UpdateMutator<
408
409
  this.privacyPolicy.authorizeUpdateAsync(
409
410
  this.viewerContext,
410
411
  queryContext,
411
- entityAboutToBeUpdated
412
+ entityAboutToBeUpdated,
413
+ this.metricsAdapter
412
414
  )
413
415
  );
414
416
  if (!authorizeUpdateResult.ok) {
415
417
  return authorizeUpdateResult;
416
418
  }
417
419
 
418
- await this.executeMutationTriggersOrValidatorsAsync(
420
+ await this.executeMutationValidatorsAsync(
419
421
  this.mutationValidators,
420
422
  queryContext,
421
423
  entityAboutToBeUpdated,
422
424
  { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
423
425
  );
424
- await this.executeMutationTriggersOrValidatorsAsync(
426
+ await this.executeMutationTriggersAsync(
425
427
  this.mutationTriggers.beforeAll,
426
428
  queryContext,
427
429
  entityAboutToBeUpdated,
428
430
  { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
429
431
  );
430
- await this.executeMutationTriggersOrValidatorsAsync(
432
+ await this.executeMutationTriggersAsync(
431
433
  this.mutationTriggers.beforeUpdate,
432
434
  queryContext,
433
435
  entityAboutToBeUpdated,
@@ -443,13 +445,13 @@ export class UpdateMutator<
443
445
 
444
446
  const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext);
445
447
 
446
- queryContext.appendPostCommitCallback(
448
+ queryContext.appendPostCommitInvalidationCallback(
447
449
  entityLoader.invalidateFieldsAsync.bind(
448
450
  entityLoader,
449
451
  this.originalEntity.getAllDatabaseFields()
450
452
  )
451
453
  );
452
- queryContext.appendPostCommitCallback(
454
+ queryContext.appendPostCommitInvalidationCallback(
453
455
  entityLoader.invalidateFieldsAsync.bind(entityLoader, this.fieldsForEntity)
454
456
  );
455
457
 
@@ -458,13 +460,13 @@ export class UpdateMutator<
458
460
  .enforcing()
459
461
  .loadByIDAsync(unauthorizedEntityAfterUpdate.getID());
460
462
 
461
- await this.executeMutationTriggersOrValidatorsAsync(
463
+ await this.executeMutationTriggersAsync(
462
464
  this.mutationTriggers.afterUpdate,
463
465
  queryContext,
464
466
  updatedEntity,
465
467
  { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
466
468
  );
467
- await this.executeMutationTriggersOrValidatorsAsync(
469
+ await this.executeMutationTriggersAsync(
468
470
  this.mutationTriggers.afterAll,
469
471
  queryContext,
470
472
  updatedEntity,
@@ -472,7 +474,7 @@ export class UpdateMutator<
472
474
  );
473
475
 
474
476
  queryContext.appendPostCommitCallback(
475
- this.executeNonTransactionalMutationTriggersOrValidatorsAsync.bind(
477
+ this.executeNonTransactionalMutationTriggersAsync.bind(
476
478
  this,
477
479
  this.mutationTriggers.afterCommit,
478
480
  updatedEntity,
@@ -563,7 +565,7 @@ export class DeleteMutator<
563
565
  this.metricsAdapter,
564
566
  EntityMetricsMutationType.DELETE,
565
567
  this.entityClass.name
566
- )(this.deleteInTransactionAsync());
568
+ )(this.deleteInTransactionAsync(new Set(), false, null));
567
569
  }
568
570
 
569
571
  /**
@@ -574,14 +576,16 @@ export class DeleteMutator<
574
576
  }
575
577
 
576
578
  private async deleteInTransactionAsync(
577
- processedEntityIdentifiersFromTransitiveDeletions: Set<string> = new Set(),
578
- skipDatabaseDeletion: boolean = false
579
+ processedEntityIdentifiersFromTransitiveDeletions: Set<string>,
580
+ skipDatabaseDeletion: boolean,
581
+ cascadingDeleteCause: EntityMutationTriggerDeleteCascadeInfo | null
579
582
  ): Promise<Result<void>> {
580
583
  return await this.queryContext.runInTransactionIfNotInTransactionAsync((innerQueryContext) =>
581
584
  this.deleteInternalAsync(
582
585
  innerQueryContext,
583
586
  processedEntityIdentifiersFromTransitiveDeletions,
584
- skipDatabaseDeletion
587
+ skipDatabaseDeletion,
588
+ cascadingDeleteCause
585
589
  )
586
590
  );
587
591
  }
@@ -589,10 +593,16 @@ export class DeleteMutator<
589
593
  private async deleteInternalAsync(
590
594
  queryContext: EntityTransactionalQueryContext,
591
595
  processedEntityIdentifiersFromTransitiveDeletions: Set<string>,
592
- skipDatabaseDeletion: boolean
596
+ skipDatabaseDeletion: boolean,
597
+ cascadingDeleteCause: EntityMutationTriggerDeleteCascadeInfo | null
593
598
  ): Promise<Result<void>> {
594
599
  const authorizeDeleteResult = await asyncResult(
595
- this.privacyPolicy.authorizeDeleteAsync(this.viewerContext, queryContext, this.entity)
600
+ this.privacyPolicy.authorizeDeleteAsync(
601
+ this.viewerContext,
602
+ queryContext,
603
+ this.entity,
604
+ this.metricsAdapter
605
+ )
596
606
  );
597
607
  if (!authorizeDeleteResult.ok) {
598
608
  return authorizeDeleteResult;
@@ -601,20 +611,21 @@ export class DeleteMutator<
601
611
  await this.processEntityDeletionForInboundEdgesAsync(
602
612
  this.entity,
603
613
  queryContext,
604
- processedEntityIdentifiersFromTransitiveDeletions
614
+ processedEntityIdentifiersFromTransitiveDeletions,
615
+ cascadingDeleteCause
605
616
  );
606
617
 
607
- await this.executeMutationTriggersOrValidatorsAsync(
618
+ await this.executeMutationTriggersAsync(
608
619
  this.mutationTriggers.beforeAll,
609
620
  queryContext,
610
621
  this.entity,
611
- { type: EntityMutationType.DELETE }
622
+ { type: EntityMutationType.DELETE, cascadingDeleteCause }
612
623
  );
613
- await this.executeMutationTriggersOrValidatorsAsync(
624
+ await this.executeMutationTriggersAsync(
614
625
  this.mutationTriggers.beforeDelete,
615
626
  queryContext,
616
627
  this.entity,
617
- { type: EntityMutationType.DELETE }
628
+ { type: EntityMutationType.DELETE, cascadingDeleteCause }
618
629
  );
619
630
 
620
631
  if (!skipDatabaseDeletion) {
@@ -626,29 +637,29 @@ export class DeleteMutator<
626
637
  }
627
638
 
628
639
  const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext);
629
- queryContext.appendPostCommitCallback(
640
+ queryContext.appendPostCommitInvalidationCallback(
630
641
  entityLoader.invalidateFieldsAsync.bind(entityLoader, this.entity.getAllDatabaseFields())
631
642
  );
632
643
 
633
- await this.executeMutationTriggersOrValidatorsAsync(
644
+ await this.executeMutationTriggersAsync(
634
645
  this.mutationTriggers.afterDelete,
635
646
  queryContext,
636
647
  this.entity,
637
- { type: EntityMutationType.DELETE }
648
+ { type: EntityMutationType.DELETE, cascadingDeleteCause }
638
649
  );
639
- await this.executeMutationTriggersOrValidatorsAsync(
650
+ await this.executeMutationTriggersAsync(
640
651
  this.mutationTriggers.afterAll,
641
652
  queryContext,
642
653
  this.entity,
643
- { type: EntityMutationType.DELETE }
654
+ { type: EntityMutationType.DELETE, cascadingDeleteCause }
644
655
  );
645
656
 
646
657
  queryContext.appendPostCommitCallback(
647
- this.executeNonTransactionalMutationTriggersOrValidatorsAsync.bind(
658
+ this.executeNonTransactionalMutationTriggersAsync.bind(
648
659
  this,
649
660
  this.mutationTriggers.afterCommit,
650
661
  this.entity,
651
- { type: EntityMutationType.DELETE }
662
+ { type: EntityMutationType.DELETE, cascadingDeleteCause }
652
663
  )
653
664
  );
654
665
 
@@ -672,7 +683,8 @@ export class DeleteMutator<
672
683
  private async processEntityDeletionForInboundEdgesAsync(
673
684
  entity: TEntity,
674
685
  queryContext: EntityTransactionalQueryContext,
675
- processedEntityIdentifiers: Set<string>
686
+ processedEntityIdentifiers: Set<string>,
687
+ cascadingDeleteCause: EntityMutationTriggerDeleteCascadeInfo | null
676
688
  ): Promise<void> {
677
689
  // prevent infinite reference cycles by keeping track of entities already processed
678
690
  if (processedEntityIdentifiers.has(entity.getUniqueIdentifier())) {
@@ -680,7 +692,9 @@ export class DeleteMutator<
680
692
  }
681
693
  processedEntityIdentifiers.add(entity.getUniqueIdentifier());
682
694
 
683
- const companionDefinition = (entity.constructor as any).getCompanionDefinition() as EntityCompanionDefinition<
695
+ const companionDefinition = (
696
+ entity.constructor as any
697
+ ).getCompanionDefinition() as EntityCompanionDefinition<
684
698
  TFields,
685
699
  TID,
686
700
  TViewerContext,
@@ -743,7 +757,11 @@ export class DeleteMutator<
743
757
  .forDelete(inboundReferenceEntity, queryContext)
744
758
  .deleteInTransactionAsync(
745
759
  processedEntityIdentifiers,
746
- /* skipDatabaseDeletion */ true // deletion is handled by DB
760
+ /* skipDatabaseDeletion */ true, // deletion is handled by DB
761
+ {
762
+ entity,
763
+ cascadingDeleteCause,
764
+ }
747
765
  )
748
766
  )
749
767
  );
@@ -767,7 +785,11 @@ export class DeleteMutator<
767
785
  .forDelete(inboundReferenceEntity, queryContext)
768
786
  .deleteInTransactionAsync(
769
787
  processedEntityIdentifiers,
770
- /* skipDatabaseDeletion */ false
788
+ /* skipDatabaseDeletion */ false,
789
+ {
790
+ entity,
791
+ cascadingDeleteCause,
792
+ }
771
793
  )
772
794
  )
773
795
  );
@@ -2,6 +2,9 @@ import { EntityQueryContext } from './EntityQueryContext';
2
2
  import ReadonlyEntity from './ReadonlyEntity';
3
3
  import ViewerContext from './ViewerContext';
4
4
  import EntityNotAuthorizedError from './errors/EntityNotAuthorizedError';
5
+ import IEntityMetricsAdapter, {
6
+ EntityMetricsAuthorizationResult,
7
+ } from './metrics/IEntityMetricsAdapter';
5
8
  import PrivacyPolicyRule, { RuleEvaluationResult } from './rules/PrivacyPolicyRule';
6
9
 
7
10
  export enum EntityPrivacyPolicyEvaluationMode {
@@ -124,14 +127,16 @@ export default abstract class EntityPrivacyPolicy<
124
127
  async authorizeCreateAsync(
125
128
  viewerContext: TViewerContext,
126
129
  queryContext: EntityQueryContext,
127
- entity: TEntity
130
+ entity: TEntity,
131
+ metricsAdapter: IEntityMetricsAdapter
128
132
  ): Promise<TEntity> {
129
133
  return await this.authorizeForRulesetAsync(
130
134
  this.createRules,
131
135
  viewerContext,
132
136
  queryContext,
133
137
  entity,
134
- EntityAuthorizationAction.CREATE
138
+ EntityAuthorizationAction.CREATE,
139
+ metricsAdapter
135
140
  );
136
141
  }
137
142
 
@@ -146,14 +151,16 @@ export default abstract class EntityPrivacyPolicy<
146
151
  async authorizeReadAsync(
147
152
  viewerContext: TViewerContext,
148
153
  queryContext: EntityQueryContext,
149
- entity: TEntity
154
+ entity: TEntity,
155
+ metricsAdapter: IEntityMetricsAdapter
150
156
  ): Promise<TEntity> {
151
157
  return await this.authorizeForRulesetAsync(
152
158
  this.readRules,
153
159
  viewerContext,
154
160
  queryContext,
155
161
  entity,
156
- EntityAuthorizationAction.READ
162
+ EntityAuthorizationAction.READ,
163
+ metricsAdapter
157
164
  );
158
165
  }
159
166
 
@@ -168,14 +175,16 @@ export default abstract class EntityPrivacyPolicy<
168
175
  async authorizeUpdateAsync(
169
176
  viewerContext: TViewerContext,
170
177
  queryContext: EntityQueryContext,
171
- entity: TEntity
178
+ entity: TEntity,
179
+ metricsAdapter: IEntityMetricsAdapter
172
180
  ): Promise<TEntity> {
173
181
  return await this.authorizeForRulesetAsync(
174
182
  this.updateRules,
175
183
  viewerContext,
176
184
  queryContext,
177
185
  entity,
178
- EntityAuthorizationAction.UPDATE
186
+ EntityAuthorizationAction.UPDATE,
187
+ metricsAdapter
179
188
  );
180
189
  }
181
190
 
@@ -190,14 +199,16 @@ export default abstract class EntityPrivacyPolicy<
190
199
  async authorizeDeleteAsync(
191
200
  viewerContext: TViewerContext,
192
201
  queryContext: EntityQueryContext,
193
- entity: TEntity
202
+ entity: TEntity,
203
+ metricsAdapter: IEntityMetricsAdapter
194
204
  ): Promise<TEntity> {
195
205
  return await this.authorizeForRulesetAsync(
196
206
  this.deleteRules,
197
207
  viewerContext,
198
208
  queryContext,
199
209
  entity,
200
- EntityAuthorizationAction.DELETE
210
+ EntityAuthorizationAction.DELETE,
211
+ metricsAdapter
201
212
  );
202
213
  }
203
214
 
@@ -206,48 +217,95 @@ export default abstract class EntityPrivacyPolicy<
206
217
  viewerContext: TViewerContext,
207
218
  queryContext: EntityQueryContext,
208
219
  entity: TEntity,
209
- action: EntityAuthorizationAction
220
+ action: EntityAuthorizationAction,
221
+ metricsAdapter: IEntityMetricsAdapter
210
222
  ): Promise<TEntity> {
211
223
  const privacyPolicyEvaluator = this.getPrivacyPolicyEvaluator(viewerContext);
212
224
  switch (privacyPolicyEvaluator.mode) {
213
225
  case EntityPrivacyPolicyEvaluationMode.ENFORCE:
214
- return await this.authorizeForRulesetInnerAsync(
215
- ruleset,
216
- viewerContext,
217
- queryContext,
218
- entity,
219
- action
220
- );
226
+ try {
227
+ const result = await this.authorizeForRulesetInnerAsync(
228
+ ruleset,
229
+ viewerContext,
230
+ queryContext,
231
+ entity,
232
+ action
233
+ );
234
+ metricsAdapter.logAuthorizationEvent({
235
+ entityClassName: entity.constructor.name,
236
+ action,
237
+ evaluationResult: EntityMetricsAuthorizationResult.ALLOW,
238
+ privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
239
+ });
240
+ return result;
241
+ } catch (e) {
242
+ if (!(e instanceof EntityNotAuthorizedError)) {
243
+ throw e;
244
+ }
245
+ metricsAdapter.logAuthorizationEvent({
246
+ entityClassName: entity.constructor.name,
247
+ action,
248
+ evaluationResult: EntityMetricsAuthorizationResult.DENY,
249
+ privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
250
+ });
251
+ throw e;
252
+ }
221
253
  case EntityPrivacyPolicyEvaluationMode.ENFORCE_AND_LOG:
222
254
  try {
223
- return await this.authorizeForRulesetInnerAsync(
255
+ const result = await this.authorizeForRulesetInnerAsync(
224
256
  ruleset,
225
257
  viewerContext,
226
258
  queryContext,
227
259
  entity,
228
260
  action
229
261
  );
262
+ metricsAdapter.logAuthorizationEvent({
263
+ entityClassName: entity.constructor.name,
264
+ action,
265
+ evaluationResult: EntityMetricsAuthorizationResult.ALLOW,
266
+ privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
267
+ });
268
+ return result;
230
269
  } catch (e) {
231
270
  if (!(e instanceof EntityNotAuthorizedError)) {
232
271
  throw e;
233
272
  }
234
273
  privacyPolicyEvaluator.denyHandler(e);
274
+ metricsAdapter.logAuthorizationEvent({
275
+ entityClassName: entity.constructor.name,
276
+ action,
277
+ evaluationResult: EntityMetricsAuthorizationResult.DENY,
278
+ privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
279
+ });
235
280
  throw e;
236
281
  }
237
282
  case EntityPrivacyPolicyEvaluationMode.DRY_RUN:
238
283
  try {
239
- return await this.authorizeForRulesetInnerAsync(
284
+ const result = await this.authorizeForRulesetInnerAsync(
240
285
  ruleset,
241
286
  viewerContext,
242
287
  queryContext,
243
288
  entity,
244
289
  action
245
290
  );
291
+ metricsAdapter.logAuthorizationEvent({
292
+ entityClassName: entity.constructor.name,
293
+ action,
294
+ evaluationResult: EntityMetricsAuthorizationResult.ALLOW,
295
+ privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
296
+ });
297
+ return result;
246
298
  } catch (e) {
247
299
  if (!(e instanceof EntityNotAuthorizedError)) {
248
300
  throw e;
249
301
  }
250
302
  privacyPolicyEvaluator.denyHandler(e);
303
+ metricsAdapter.logAuthorizationEvent({
304
+ entityClassName: entity.constructor.name,
305
+ action,
306
+ evaluationResult: EntityMetricsAuthorizationResult.DENY,
307
+ privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
308
+ });
251
309
  return entity;
252
310
  }
253
311
  }
@@ -261,7 +319,7 @@ export default abstract class EntityPrivacyPolicy<
261
319
  action: EntityAuthorizationAction
262
320
  ): Promise<TEntity> {
263
321
  for (let i = 0; i < ruleset.length; i++) {
264
- const rule = ruleset[i];
322
+ const rule = ruleset[i]!;
265
323
  const ruleEvaluationResult = await rule.evaluateAsync(viewerContext, queryContext, entity);
266
324
  switch (ruleEvaluationResult) {
267
325
  case RuleEvaluationResult.DENY:
@@ -43,13 +43,33 @@ export class EntityNonTransactionalQueryContext extends EntityQueryContext {
43
43
  }
44
44
 
45
45
  export class EntityTransactionalQueryContext extends EntityQueryContext {
46
+ private readonly postCommitInvalidationCallbacks: PostCommitCallback[] = [];
46
47
  private readonly postCommitCallbacks: PostCommitCallback[] = [];
47
48
 
49
+ /**
50
+ * Schedule a post-commit cache invalidation callback. These are run before normal
51
+ * post-commit callbacks in order to have cache consistency in normal post-commit callbacks.
52
+ *
53
+ * @param callback - callback to schedule
54
+ */
55
+ public appendPostCommitInvalidationCallback(callback: PostCommitCallback): void {
56
+ this.postCommitInvalidationCallbacks.push(callback);
57
+ }
58
+
59
+ /**
60
+ * Schedule a post-commit callback. These will be run after the transaction has
61
+ * been committed.
62
+ * @param callback - callback to schedule
63
+ */
48
64
  public appendPostCommitCallback(callback: PostCommitCallback): void {
49
65
  this.postCommitCallbacks.push(callback);
50
66
  }
51
67
 
52
68
  public async runPostCommitCallbacksAsync(): Promise<void> {
69
+ const invalidationCallbacks = [...this.postCommitInvalidationCallbacks];
70
+ this.postCommitInvalidationCallbacks.length = 0;
71
+ await Promise.all(invalidationCallbacks.map((callback) => callback()));
72
+
53
73
  const callbacks = [...this.postCommitCallbacks];
54
74
  this.postCommitCallbacks.length = 0;
55
75
  await Promise.all(callbacks.map((callback) => callback()));
@@ -37,8 +37,9 @@ export default abstract class ReadonlyEntity<
37
37
  private readonly viewerContext: TViewerContext,
38
38
  private readonly databaseFields: Readonly<TFields>
39
39
  ) {
40
- const companionDefinition = (this
41
- .constructor as any).getCompanionDefinition() as EntityCompanionDefinition<
40
+ const companionDefinition = (
41
+ this.constructor as any
42
+ ).getCompanionDefinition() as EntityCompanionDefinition<
42
43
  TFields,
43
44
  TID,
44
45
  TViewerContext,
@@ -5,6 +5,7 @@ import ReadonlyEntity from './ReadonlyEntity';
5
5
  import ViewerContext from './ViewerContext';
6
6
  import ViewerScopedEntityLoaderFactory from './ViewerScopedEntityLoaderFactory';
7
7
  import ViewerScopedEntityMutatorFactory from './ViewerScopedEntityMutatorFactory';
8
+ import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
8
9
 
9
10
  /**
10
11
  * Provides a simpler API for loading and mutating entities by injecting the {@link ViewerContext}
@@ -76,4 +77,11 @@ export default class ViewerScopedEntityCompanion<
76
77
  getQueryContextProvider(): EntityQueryContextProvider {
77
78
  return this.entityCompanion.getQueryContextProvider();
78
79
  }
80
+
81
+ /**
82
+ * Get the {@link IEntityMetricsAdapter} for this companion.
83
+ */
84
+ getMetricsAdapter(): IEntityMetricsAdapter {
85
+ return this.entityCompanion.getMetricsAdapter();
86
+ }
79
87
  }