@expo/entity 0.25.2 → 0.26.1

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 (117) hide show
  1. package/build/ComposedEntityCacheAdapter.d.ts +4 -2
  2. package/build/ComposedEntityCacheAdapter.js +39 -3
  3. package/build/ComposedEntityCacheAdapter.js.map +1 -1
  4. package/build/ComposedSecondaryEntityCache.d.ts +3 -2
  5. package/build/ComposedSecondaryEntityCache.js +3 -2
  6. package/build/ComposedSecondaryEntityCache.js.map +1 -1
  7. package/build/Entity.js +4 -4
  8. package/build/Entity.js.map +1 -1
  9. package/build/EntityAssociationLoader.d.ts +5 -0
  10. package/build/EntityAssociationLoader.js +5 -0
  11. package/build/EntityAssociationLoader.js.map +1 -1
  12. package/build/EntityFieldDefinition.d.ts +16 -8
  13. package/build/EntityFieldDefinition.js +12 -5
  14. package/build/EntityFieldDefinition.js.map +1 -1
  15. package/build/EntityLoader.js +2 -2
  16. package/build/EntityLoader.js.map +1 -1
  17. package/build/EntityMutationInfo.d.ts +2 -0
  18. package/build/EntityMutator.d.ts +6 -8
  19. package/build/EntityMutator.js +58 -46
  20. package/build/EntityMutator.js.map +1 -1
  21. package/build/EntityMutatorFactory.d.ts +4 -4
  22. package/build/EntityMutatorFactory.js +6 -6
  23. package/build/EntityMutatorFactory.js.map +1 -1
  24. package/build/EntityPrivacyPolicy.d.ts +13 -0
  25. package/build/EntityPrivacyPolicy.js +13 -0
  26. package/build/EntityPrivacyPolicy.js.map +1 -1
  27. package/build/EntityQueryContext.d.ts +11 -0
  28. package/build/EntityQueryContext.js +11 -0
  29. package/build/EntityQueryContext.js.map +1 -1
  30. package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
  31. package/build/ViewerScopedEntityMutatorFactory.js +6 -6
  32. package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
  33. package/build/__tests__/ComposedCacheAdapter-test.js +37 -4
  34. package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -1
  35. package/build/__tests__/EntityCommonUseCases-test.js +5 -1
  36. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  37. package/build/__tests__/EntityCompanion-test.js +5 -1
  38. package/build/__tests__/EntityCompanion-test.js.map +1 -1
  39. package/build/__tests__/EntityCompanionProvider-test.js +5 -1
  40. package/build/__tests__/EntityCompanionProvider-test.js.map +1 -1
  41. package/build/__tests__/EntityEdges-test.js +199 -33
  42. package/build/__tests__/EntityEdges-test.js.map +1 -1
  43. package/build/__tests__/EntityLoader-test.js +5 -1
  44. package/build/__tests__/EntityLoader-test.js.map +1 -1
  45. package/build/__tests__/EntityMutator-test.js +38 -53
  46. package/build/__tests__/EntityMutator-test.js.map +1 -1
  47. package/build/__tests__/EntityPrivacyPolicy-test.js +5 -1
  48. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  49. package/build/__tests__/EntitySelfReferentialEdges-test.js +2 -2
  50. package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
  51. package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js +5 -1
  52. package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js.map +1 -1
  53. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +2 -3
  54. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
  55. package/build/errors/EntityCacheAdapterError.js +5 -1
  56. package/build/errors/EntityCacheAdapterError.js.map +1 -1
  57. package/build/errors/EntityDatabaseAdapterError.js +5 -1
  58. package/build/errors/EntityDatabaseAdapterError.js.map +1 -1
  59. package/build/errors/EntityInvalidFieldValueError.js +6 -2
  60. package/build/errors/EntityInvalidFieldValueError.js.map +1 -1
  61. package/build/errors/EntityNotAuthorizedError.js +5 -1
  62. package/build/errors/EntityNotAuthorizedError.js.map +1 -1
  63. package/build/errors/EntityNotFoundError.js +6 -2
  64. package/build/errors/EntityNotFoundError.js.map +1 -1
  65. package/build/index.js +5 -1
  66. package/build/index.js.map +1 -1
  67. package/build/internal/EntityDataManager.js +7 -4
  68. package/build/internal/EntityDataManager.js.map +1 -1
  69. package/build/internal/EntityFieldTransformationUtils.js +1 -1
  70. package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
  71. package/build/internal/ReadThroughEntityCache.js +1 -1
  72. package/build/internal/ReadThroughEntityCache.js.map +1 -1
  73. package/build/internal/__tests__/EntityDataManager-test.js +12 -7
  74. package/build/internal/__tests__/EntityDataManager-test.js.map +1 -1
  75. package/build/internal/__tests__/ReadThroughEntityCache-test.js +5 -1
  76. package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
  77. package/build/metrics/IEntityMetricsAdapter.d.ts +62 -17
  78. package/build/metrics/IEntityMetricsAdapter.js +17 -1
  79. package/build/metrics/IEntityMetricsAdapter.js.map +1 -1
  80. package/build/metrics/NoOpEntityMetricsAdapter.d.ts +1 -3
  81. package/build/metrics/NoOpEntityMetricsAdapter.js +1 -3
  82. package/build/metrics/NoOpEntityMetricsAdapter.js.map +1 -1
  83. package/build/rules/AlwaysAllowPrivacyPolicyRule.js +5 -1
  84. package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
  85. package/build/rules/AlwaysDenyPrivacyPolicyRule.js +5 -1
  86. package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
  87. package/build/rules/AlwaysSkipPrivacyPolicyRule.js +5 -1
  88. package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
  89. package/build/utils/testing/StubDatabaseAdapter.js +6 -2
  90. package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
  91. package/package.json +1 -1
  92. package/src/ComposedEntityCacheAdapter.ts +44 -3
  93. package/src/ComposedSecondaryEntityCache.ts +3 -2
  94. package/src/Entity.ts +4 -4
  95. package/src/EntityAssociationLoader.ts +5 -0
  96. package/src/EntityFieldDefinition.ts +15 -6
  97. package/src/EntityLoader.ts +4 -2
  98. package/src/EntityMutationInfo.ts +2 -0
  99. package/src/EntityMutator.ts +98 -67
  100. package/src/EntityMutatorFactory.ts +4 -10
  101. package/src/EntityPrivacyPolicy.ts +15 -0
  102. package/src/EntityQueryContext.ts +11 -0
  103. package/src/ViewerScopedEntityMutatorFactory.ts +7 -22
  104. package/src/__tests__/ComposedCacheAdapter-test.ts +43 -4
  105. package/src/__tests__/EntityEdges-test.ts +287 -32
  106. package/src/__tests__/EntityMutator-test.ts +33 -54
  107. package/src/__tests__/EntitySelfReferentialEdges-test.ts +2 -2
  108. package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +2 -6
  109. package/src/errors/EntityInvalidFieldValueError.ts +1 -1
  110. package/src/errors/EntityNotFoundError.ts +1 -1
  111. package/src/internal/EntityDataManager.ts +13 -5
  112. package/src/internal/EntityFieldTransformationUtils.ts +1 -1
  113. package/src/internal/ReadThroughEntityCache.ts +3 -1
  114. package/src/internal/__tests__/EntityDataManager-test.ts +11 -8
  115. package/src/metrics/IEntityMetricsAdapter.ts +72 -19
  116. package/src/metrics/NoOpEntityMetricsAdapter.ts +1 -5
  117. package/src/utils/testing/StubDatabaseAdapter.ts +4 -1
@@ -1,3 +1,5 @@
1
+ import invariant from 'invariant';
2
+
1
3
  import Entity from '../Entity';
2
4
  import { EntityCompanionDefinition } from '../EntityCompanionProvider';
3
5
  import EntityConfiguration from '../EntityConfiguration';
@@ -33,9 +35,13 @@ interface GrandChildFields {
33
35
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
34
36
  const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) => {
35
37
  const triggerExecutionCounts = {
36
- ParentEntity: 0,
37
- ChildEntity: 0,
38
- GrandChildEntity: 0,
38
+ ParentEntityDeletion: 0,
39
+ ChildEntityDeletion: 0,
40
+ GrandChildEntityDeletion: 0,
41
+
42
+ ParentEntityUpdate: 0,
43
+ ChildEntityUpdate: 0,
44
+ GrandChildEntityUpdate: 0,
39
45
  };
40
46
 
41
47
  const privacyPolicyEvaluationRecords = {
@@ -107,7 +113,7 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
107
113
  ];
108
114
  }
109
115
 
110
- class ParentCheckInfoTrigger extends EntityMutationTrigger<
116
+ class ParentCheckInfoDeletionTrigger extends EntityMutationTrigger<
111
117
  ParentFields,
112
118
  string,
113
119
  TestViewerContext,
@@ -119,19 +125,37 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
119
125
  _entity: ParentEntity,
120
126
  mutationInfo: EntityTriggerMutationInfo<ParentFields, string, TestViewerContext, ParentEntity>
121
127
  ): Promise<void> {
122
- if (mutationInfo.type !== EntityMutationType.DELETE) {
123
- return;
128
+ invariant(mutationInfo.type === EntityMutationType.DELETE, 'invalid EntityMutationType');
129
+ if (mutationInfo.cascadingDeleteCause !== null) {
130
+ throw new Error('Parent entity should not have casade delete cause');
124
131
  }
125
132
 
133
+ triggerExecutionCounts.ParentEntityDeletion++;
134
+ }
135
+ }
136
+
137
+ class ParentCheckInfoUpdateTrigger extends EntityMutationTrigger<
138
+ ParentFields,
139
+ string,
140
+ TestViewerContext,
141
+ ParentEntity
142
+ > {
143
+ async executeAsync(
144
+ _viewerContext: TestViewerContext,
145
+ _queryContext: EntityTransactionalQueryContext,
146
+ _entity: ParentEntity,
147
+ mutationInfo: EntityTriggerMutationInfo<ParentFields, string, TestViewerContext, ParentEntity>
148
+ ): Promise<void> {
149
+ invariant(mutationInfo.type === EntityMutationType.UPDATE, 'invalid EntityMutationType');
126
150
  if (mutationInfo.cascadingDeleteCause !== null) {
127
151
  throw new Error('Parent entity should not have casade delete cause');
128
152
  }
129
153
 
130
- triggerExecutionCounts.ParentEntity++;
154
+ triggerExecutionCounts.ParentEntityUpdate++;
131
155
  }
132
156
  }
133
157
 
134
- class ChildCheckInfoTrigger extends EntityMutationTrigger<
158
+ class ChildCheckInfoDeletionTrigger extends EntityMutationTrigger<
135
159
  ChildFields,
136
160
  string,
137
161
  TestViewerContext,
@@ -143,10 +167,39 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
143
167
  _entity: ChildEntity,
144
168
  mutationInfo: EntityTriggerMutationInfo<ChildFields, string, TestViewerContext, ChildEntity>
145
169
  ): Promise<void> {
146
- if (mutationInfo.type !== EntityMutationType.DELETE) {
147
- return;
170
+ invariant(mutationInfo.type === EntityMutationType.DELETE, 'invalid EntityMutationType');
171
+ if (mutationInfo.cascadingDeleteCause === null) {
172
+ throw new Error('Child entity should have casade delete cause');
173
+ }
174
+
175
+ const cascadingDeleteCauseEntity = mutationInfo.cascadingDeleteCause.entity;
176
+ if (!(cascadingDeleteCauseEntity instanceof ParentEntity)) {
177
+ throw new Error('Child entity should have casade delete cause entity of type ParentEntity');
148
178
  }
149
179
 
180
+ const secondLevelCascadingDeleteCause =
181
+ mutationInfo.cascadingDeleteCause.cascadingDeleteCause;
182
+ if (secondLevelCascadingDeleteCause) {
183
+ throw new Error('Child entity should not have two-level casade delete cause');
184
+ }
185
+
186
+ triggerExecutionCounts.ChildEntityDeletion++;
187
+ }
188
+ }
189
+
190
+ class ChildCheckInfoUpdateTrigger extends EntityMutationTrigger<
191
+ ChildFields,
192
+ string,
193
+ TestViewerContext,
194
+ ChildEntity
195
+ > {
196
+ async executeAsync(
197
+ _viewerContext: TestViewerContext,
198
+ _queryContext: EntityTransactionalQueryContext,
199
+ _entity: ChildEntity,
200
+ mutationInfo: EntityTriggerMutationInfo<ChildFields, string, TestViewerContext, ChildEntity>
201
+ ): Promise<void> {
202
+ invariant(mutationInfo.type === EntityMutationType.UPDATE, 'invalid EntityMutationType');
150
203
  if (mutationInfo.cascadingDeleteCause === null) {
151
204
  throw new Error('Child entity should have casade delete cause');
152
205
  }
@@ -162,11 +215,11 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
162
215
  throw new Error('Child entity should not have two-level casade delete cause');
163
216
  }
164
217
 
165
- triggerExecutionCounts.ChildEntity++;
218
+ triggerExecutionCounts.ChildEntityUpdate++;
166
219
  }
167
220
  }
168
221
 
169
- class GrandChildCheckInfoTrigger extends EntityMutationTrigger<
222
+ class GrandChildCheckInfoDeletionTrigger extends EntityMutationTrigger<
170
223
  GrandChildFields,
171
224
  string,
172
225
  TestViewerContext,
@@ -183,10 +236,58 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
183
236
  GrandChildEntity
184
237
  >
185
238
  ): Promise<void> {
186
- if (mutationInfo.type !== EntityMutationType.DELETE) {
187
- return;
239
+ invariant(mutationInfo.type === EntityMutationType.DELETE, 'invalid EntityMutationType');
240
+ if (mutationInfo.cascadingDeleteCause === null) {
241
+ throw new Error('GrandChild entity should have cascade delete cause');
242
+ }
243
+
244
+ const cascadingDeleteCauseEntity = mutationInfo.cascadingDeleteCause.entity;
245
+ if (!(cascadingDeleteCauseEntity instanceof ChildEntity)) {
246
+ throw new Error(
247
+ 'GrandChild entity should have cascade delete cause entity of type ChildEntity'
248
+ );
249
+ }
250
+
251
+ const secondLevelCascadingDeleteCause =
252
+ mutationInfo.cascadingDeleteCause.cascadingDeleteCause;
253
+ if (!secondLevelCascadingDeleteCause) {
254
+ throw new Error('GrandChild entity should have two-level casade delete cause');
255
+ }
256
+
257
+ const secondLevelCascadingDeleteCauseEntity = secondLevelCascadingDeleteCause.entity;
258
+ if (!(secondLevelCascadingDeleteCauseEntity instanceof ParentEntity)) {
259
+ throw new Error(
260
+ 'GrandChild entity should have second level casade delete cause entity of type ParentEntity'
261
+ );
262
+ }
263
+
264
+ const thirdLevelCascadingDeleteCause = secondLevelCascadingDeleteCause.cascadingDeleteCause;
265
+ if (thirdLevelCascadingDeleteCause) {
266
+ throw new Error('GrandChild entity should not have three-level casade delete cause');
188
267
  }
189
268
 
269
+ triggerExecutionCounts.GrandChildEntityDeletion++;
270
+ }
271
+ }
272
+
273
+ class GrandChildCheckInfoUpdateTrigger extends EntityMutationTrigger<
274
+ GrandChildFields,
275
+ string,
276
+ TestViewerContext,
277
+ GrandChildEntity
278
+ > {
279
+ async executeAsync(
280
+ _viewerContext: TestViewerContext,
281
+ _queryContext: EntityTransactionalQueryContext,
282
+ _entity: GrandChildEntity,
283
+ mutationInfo: EntityTriggerMutationInfo<
284
+ GrandChildFields,
285
+ string,
286
+ TestViewerContext,
287
+ GrandChildEntity
288
+ >
289
+ ): Promise<void> {
290
+ invariant(mutationInfo.type === EntityMutationType.UPDATE, 'invalid EntityMutationType');
190
291
  if (mutationInfo.cascadingDeleteCause === null) {
191
292
  throw new Error('GrandChild entity should have cascade delete cause');
192
293
  }
@@ -216,7 +317,7 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
216
317
  throw new Error('GrandChild entity should not have three-level casade delete cause');
217
318
  }
218
319
 
219
- triggerExecutionCounts.GrandChildEntity++;
320
+ triggerExecutionCounts.GrandChildEntityUpdate++;
220
321
  }
221
322
  }
222
323
 
@@ -318,8 +419,11 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
318
419
  entityConfiguration: parentEntityConfiguration,
319
420
  privacyPolicyClass: TestEntityPrivacyPolicy,
320
421
  mutationTriggers: () => ({
321
- beforeDelete: [new ParentCheckInfoTrigger()],
322
- afterDelete: [new ParentCheckInfoTrigger()],
422
+ beforeDelete: [new ParentCheckInfoDeletionTrigger()],
423
+ afterDelete: [new ParentCheckInfoDeletionTrigger()],
424
+
425
+ beforeUpdate: [new ParentCheckInfoUpdateTrigger()],
426
+ afterUpdate: [new ParentCheckInfoUpdateTrigger()],
323
427
  }),
324
428
  });
325
429
 
@@ -328,8 +432,11 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
328
432
  entityConfiguration: childEntityConfiguration,
329
433
  privacyPolicyClass: TestEntityPrivacyPolicy,
330
434
  mutationTriggers: () => ({
331
- beforeDelete: [new ChildCheckInfoTrigger()],
332
- afterDelete: [new ChildCheckInfoTrigger()],
435
+ beforeDelete: [new ChildCheckInfoDeletionTrigger()],
436
+ afterDelete: [new ChildCheckInfoDeletionTrigger()],
437
+
438
+ beforeUpdate: [new ChildCheckInfoUpdateTrigger()],
439
+ afterUpdate: [new ChildCheckInfoUpdateTrigger()],
333
440
  }),
334
441
  });
335
442
 
@@ -338,8 +445,11 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
338
445
  entityConfiguration: grandChildEntityConfiguration,
339
446
  privacyPolicyClass: TestEntityPrivacyPolicy,
340
447
  mutationTriggers: () => ({
341
- beforeDelete: [new GrandChildCheckInfoTrigger()],
342
- afterDelete: [new GrandChildCheckInfoTrigger()],
448
+ beforeDelete: [new GrandChildCheckInfoDeletionTrigger()],
449
+ afterDelete: [new GrandChildCheckInfoDeletionTrigger()],
450
+
451
+ beforeUpdate: [new GrandChildCheckInfoUpdateTrigger()],
452
+ afterUpdate: [new GrandChildCheckInfoUpdateTrigger()],
343
453
  }),
344
454
  });
345
455
 
@@ -399,9 +509,13 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
399
509
 
400
510
  // two calls for each trigger, one beforeDelete, one afterDelete
401
511
  expect(triggerExecutionCounts).toMatchObject({
402
- ParentEntity: 2,
403
- ChildEntity: 2,
404
- GrandChildEntity: 2,
512
+ ParentEntityDeletion: 2,
513
+ ChildEntityDeletion: 2,
514
+ GrandChildEntityDeletion: 2,
515
+
516
+ ParentEntityUpdate: 0,
517
+ ChildEntityUpdate: 0,
518
+ GrandChildEntityUpdate: 0,
405
519
  });
406
520
 
407
521
  expect(privacyPolicyEvaluationRecords).toMatchObject({
@@ -518,9 +632,13 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
518
632
  // two calls for only parent trigger, one beforeDelete, one afterDelete
519
633
  // when using set null the descendants aren't deleted
520
634
  expect(triggerExecutionCounts).toMatchObject({
521
- ParentEntity: 2,
522
- ChildEntity: 0,
523
- GrandChildEntity: 0,
635
+ ParentEntityDeletion: 2,
636
+ ChildEntityDeletion: 0,
637
+ GrandChildEntityDeletion: 0,
638
+
639
+ ParentEntityUpdate: 0,
640
+ ChildEntityUpdate: 2,
641
+ GrandChildEntityUpdate: 0,
524
642
  });
525
643
 
526
644
  expect(privacyPolicyEvaluationRecords).toMatchObject({
@@ -573,7 +691,7 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
573
691
  });
574
692
  });
575
693
 
576
- describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => {
694
+ describe('EntityEdgeDeletionBehavior.SET_NULL_INVALIDATE_CACHE_ONLY', () => {
577
695
  it('invalidates the cache', async () => {
578
696
  const {
579
697
  ParentEntity,
@@ -581,7 +699,140 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
581
699
  GrandChildEntity,
582
700
  triggerExecutionCounts,
583
701
  privacyPolicyEvaluationRecords,
584
- } = makeEntityClasses(EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE);
702
+ } = makeEntityClasses(EntityEdgeDeletionBehavior.SET_NULL_INVALIDATE_CACHE_ONLY);
703
+
704
+ const companionProvider = createUnitTestEntityCompanionProvider();
705
+ const viewerContext = new TestViewerContext(companionProvider);
706
+
707
+ const parent = await ParentEntity.creator(viewerContext).enforceCreateAsync();
708
+ const child = await ChildEntity.creator(viewerContext)
709
+ .setField('parent_id', parent.getID())
710
+ .enforceCreateAsync();
711
+ const grandchild = await GrandChildEntity.creator(viewerContext)
712
+ .setField('parent_id', child.getID())
713
+ .enforceCreateAsync();
714
+
715
+ await expect(
716
+ ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
717
+ ).resolves.not.toBeNull();
718
+ await expect(
719
+ ChildEntity.loader(viewerContext)
720
+ .enforcing()
721
+ .loadByFieldEqualingAsync('parent_id', parent.getID())
722
+ ).resolves.not.toBeNull();
723
+ await expect(
724
+ GrandChildEntity.loader(viewerContext)
725
+ .enforcing()
726
+ .loadByFieldEqualingAsync('parent_id', child.getID())
727
+ ).resolves.not.toBeNull();
728
+
729
+ const childCacheAdapter = viewerContext.getViewerScopedEntityCompanionForClass(ChildEntity)[
730
+ 'entityCompanion'
731
+ ]['tableDataCoordinator']['cacheAdapter'] as InMemoryFullCacheStubCacheAdapter<ChildFields>;
732
+ const childCachedBefore = await childCacheAdapter.loadManyAsync('parent_id', [
733
+ parent.getID(),
734
+ ]);
735
+ expect(childCachedBefore.get(parent.getID())?.status).toEqual(CacheStatus.HIT);
736
+
737
+ const grandChildCacheAdapter = viewerContext.getViewerScopedEntityCompanionForClass(
738
+ GrandChildEntity
739
+ )['entityCompanion']['tableDataCoordinator'][
740
+ 'cacheAdapter'
741
+ ] as InMemoryFullCacheStubCacheAdapter<ChildFields>;
742
+ const grandChildCachedBefore = await grandChildCacheAdapter.loadManyAsync('parent_id', [
743
+ child.getID(),
744
+ ]);
745
+ expect(grandChildCachedBefore.get(child.getID())?.status).toEqual(CacheStatus.HIT);
746
+
747
+ privacyPolicyEvaluationRecords.shouldRecord = true;
748
+ await ParentEntity.enforceDeleteAsync(parent);
749
+ privacyPolicyEvaluationRecords.shouldRecord = false;
750
+
751
+ const childCachedAfter = await childCacheAdapter.loadManyAsync('parent_id', [parent.getID()]);
752
+ expect(childCachedAfter.get(parent.getID())?.status).toEqual(CacheStatus.MISS);
753
+
754
+ const grandChildCachedAfter = await grandChildCacheAdapter.loadManyAsync('parent_id', [
755
+ child.getID(),
756
+ ]);
757
+ expect(grandChildCachedAfter.get(child.getID())?.status).toEqual(CacheStatus.HIT);
758
+
759
+ await expect(
760
+ ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
761
+ ).resolves.toBeNull();
762
+
763
+ const loadedChild = await ChildEntity.loader(viewerContext)
764
+ .enforcing()
765
+ .loadByIDAsync(child.getID());
766
+ expect(loadedChild).not.toBeNull();
767
+
768
+ const loadedGrandchild = await GrandChildEntity.loader(viewerContext)
769
+ .enforcing()
770
+ .loadByIDAsync(grandchild.getID());
771
+ expect(loadedGrandchild.getField('parent_id')).toEqual(loadedChild.getID());
772
+
773
+ // two calls for only parent trigger, one beforeDelete, one afterDelete
774
+ // when using set null the descendants aren't deleted
775
+ expect(triggerExecutionCounts).toMatchObject({
776
+ ParentEntityDeletion: 2,
777
+ ChildEntityDeletion: 0,
778
+ GrandChildEntityDeletion: 0,
779
+
780
+ ParentEntityUpdate: 0,
781
+ ChildEntityUpdate: 2,
782
+ GrandChildEntityUpdate: 0,
783
+ });
784
+
785
+ expect(privacyPolicyEvaluationRecords).toMatchObject({
786
+ ParentEntity: {
787
+ [EntityAuthorizationAction.CREATE]: [],
788
+ [EntityAuthorizationAction.READ]: [],
789
+ [EntityAuthorizationAction.UPDATE]: [],
790
+ // one DELETE auth action for parent (since it's being deleted)
791
+ [EntityAuthorizationAction.DELETE]: [{ cascadingDeleteCause: null }],
792
+ },
793
+ ChildEntity: {
794
+ [EntityAuthorizationAction.CREATE]: [],
795
+
796
+ // one READ auth action for child in order to update via cascade
797
+ // no other entities are read since it is not cascaded past first entity
798
+ [EntityAuthorizationAction.READ]: [
799
+ {
800
+ cascadingDeleteCause: {
801
+ entity: expect.any(ParentEntity),
802
+ cascadingDeleteCause: null,
803
+ },
804
+ },
805
+ ],
806
+ // one UPDATE to set null
807
+ [EntityAuthorizationAction.UPDATE]: [
808
+ {
809
+ cascadingDeleteCause: {
810
+ entity: expect.any(ParentEntity),
811
+ cascadingDeleteCause: null,
812
+ },
813
+ },
814
+ ],
815
+ [EntityAuthorizationAction.DELETE]: [],
816
+ },
817
+ GrandChildEntity: {
818
+ [EntityAuthorizationAction.CREATE]: [],
819
+ [EntityAuthorizationAction.READ]: [],
820
+ [EntityAuthorizationAction.UPDATE]: [],
821
+ [EntityAuthorizationAction.DELETE]: [],
822
+ },
823
+ });
824
+ });
825
+ });
826
+
827
+ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY', () => {
828
+ it('invalidates the cache', async () => {
829
+ const {
830
+ ParentEntity,
831
+ ChildEntity,
832
+ GrandChildEntity,
833
+ triggerExecutionCounts,
834
+ privacyPolicyEvaluationRecords,
835
+ } = makeEntityClasses(EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY);
585
836
 
586
837
  const companionProvider = createUnitTestEntityCompanionProvider();
587
838
  const viewerContext = new TestViewerContext(companionProvider);
@@ -650,9 +901,13 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
650
901
 
651
902
  // two calls for each trigger, one beforeDelete, one afterDelete
652
903
  expect(triggerExecutionCounts).toMatchObject({
653
- ParentEntity: 2,
654
- ChildEntity: 2,
655
- GrandChildEntity: 2,
904
+ ParentEntityDeletion: 2,
905
+ ChildEntityDeletion: 2,
906
+ GrandChildEntityDeletion: 2,
907
+
908
+ ParentEntityUpdate: 0,
909
+ ChildEntityUpdate: 0,
910
+ GrandChildEntityUpdate: 0,
656
911
  });
657
912
 
658
913
  expect(privacyPolicyEvaluationRecords).toMatchObject({