@expo/entity 0.23.0 → 0.25.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 (131) hide show
  1. package/build/ComposedEntityCacheAdapter.d.ts +19 -0
  2. package/build/ComposedEntityCacheAdapter.js +66 -0
  3. package/build/ComposedEntityCacheAdapter.js.map +1 -0
  4. package/build/ComposedSecondaryEntityCache.d.ts +15 -0
  5. package/build/ComposedSecondaryEntityCache.js +37 -0
  6. package/build/ComposedSecondaryEntityCache.js.map +1 -0
  7. package/build/Entity.js +6 -6
  8. package/build/Entity.js.map +1 -1
  9. package/build/EntityAssociationLoader.js +4 -4
  10. package/build/EntityAssociationLoader.js.map +1 -1
  11. package/build/EntityFieldDefinition.d.ts +8 -0
  12. package/build/EntityFieldDefinition.js +5 -0
  13. package/build/EntityFieldDefinition.js.map +1 -1
  14. package/build/EntityFields.d.ts +38 -0
  15. package/build/EntityFields.js +38 -0
  16. package/build/EntityFields.js.map +1 -1
  17. package/build/EntityLoader.d.ts +3 -2
  18. package/build/EntityLoader.js +5 -4
  19. package/build/EntityLoader.js.map +1 -1
  20. package/build/EntityLoaderFactory.d.ts +2 -2
  21. package/build/EntityLoaderFactory.js +2 -2
  22. package/build/EntityLoaderFactory.js.map +1 -1
  23. package/build/EntityMutationInfo.d.ts +12 -3
  24. package/build/EntityMutator.d.ts +5 -4
  25. package/build/EntityMutator.js +31 -24
  26. package/build/EntityMutator.js.map +1 -1
  27. package/build/EntityMutatorFactory.d.ts +4 -4
  28. package/build/EntityMutatorFactory.js +6 -6
  29. package/build/EntityMutatorFactory.js.map +1 -1
  30. package/build/EntityPrivacyPolicy.d.ts +15 -4
  31. package/build/EntityPrivacyPolicy.js +14 -14
  32. package/build/EntityPrivacyPolicy.js.map +1 -1
  33. package/build/GenericSecondaryEntityCache.d.ts +19 -0
  34. package/build/GenericSecondaryEntityCache.js +74 -0
  35. package/build/GenericSecondaryEntityCache.js.map +1 -0
  36. package/build/IEntityGenericCacher.d.ts +11 -0
  37. package/build/IEntityGenericCacher.js +3 -0
  38. package/build/IEntityGenericCacher.js.map +1 -0
  39. package/build/ReadonlyEntity.js +1 -1
  40. package/build/ReadonlyEntity.js.map +1 -1
  41. package/build/ViewerScopedEntityLoaderFactory.d.ts +2 -2
  42. package/build/ViewerScopedEntityLoaderFactory.js +2 -2
  43. package/build/ViewerScopedEntityLoaderFactory.js.map +1 -1
  44. package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
  45. package/build/ViewerScopedEntityMutatorFactory.js +6 -6
  46. package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
  47. package/build/__tests__/ComposedCacheAdapter-test.d.ts +1 -0
  48. package/build/__tests__/ComposedCacheAdapter-test.js +198 -0
  49. package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -0
  50. package/build/__tests__/ComposedSecondaryEntityCache-test.d.ts +1 -0
  51. package/build/__tests__/ComposedSecondaryEntityCache-test.js +65 -0
  52. package/build/__tests__/ComposedSecondaryEntityCache-test.js.map +1 -0
  53. package/build/__tests__/EntityCommonUseCases-test.js +1 -1
  54. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  55. package/build/__tests__/EntityEdges-test.js +260 -37
  56. package/build/__tests__/EntityEdges-test.js.map +1 -1
  57. package/build/__tests__/EntityLoader-constructor-test.js +2 -1
  58. package/build/__tests__/EntityLoader-constructor-test.js.map +1 -1
  59. package/build/__tests__/EntityLoader-test.js +19 -11
  60. package/build/__tests__/EntityLoader-test.js.map +1 -1
  61. package/build/__tests__/EntityMutator-test.js +96 -43
  62. package/build/__tests__/EntityMutator-test.js.map +1 -1
  63. package/build/__tests__/EntityPrivacyPolicy-test.js +23 -12
  64. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  65. package/build/__tests__/EntitySecondaryCacheLoader-test.js +1 -1
  66. package/build/__tests__/EntitySecondaryCacheLoader-test.js.map +1 -1
  67. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js +3 -2
  68. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
  69. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +3 -2
  70. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
  71. package/build/index.d.ts +4 -0
  72. package/build/index.js +7 -1
  73. package/build/index.js.map +1 -1
  74. package/build/rules/AlwaysAllowPrivacyPolicyRule.d.ts +2 -1
  75. package/build/rules/AlwaysAllowPrivacyPolicyRule.js +1 -1
  76. package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
  77. package/build/rules/AlwaysDenyPrivacyPolicyRule.d.ts +2 -1
  78. package/build/rules/AlwaysDenyPrivacyPolicyRule.js +1 -1
  79. package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
  80. package/build/rules/AlwaysSkipPrivacyPolicyRule.d.ts +2 -1
  81. package/build/rules/AlwaysSkipPrivacyPolicyRule.js +1 -1
  82. package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
  83. package/build/rules/PrivacyPolicyRule.d.ts +2 -1
  84. package/build/rules/PrivacyPolicyRule.js.map +1 -1
  85. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js +1 -0
  86. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
  87. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js +1 -0
  88. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
  89. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js +1 -0
  90. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
  91. package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +2 -0
  92. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js +6 -6
  93. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
  94. package/package.json +1 -1
  95. package/src/ComposedEntityCacheAdapter.ts +86 -0
  96. package/src/ComposedSecondaryEntityCache.ts +63 -0
  97. package/src/Entity.ts +6 -4
  98. package/src/EntityAssociationLoader.ts +4 -4
  99. package/src/EntityFieldDefinition.ts +8 -0
  100. package/src/EntityFields.ts +45 -0
  101. package/src/EntityLoader.ts +5 -1
  102. package/src/EntityLoaderFactory.ts +4 -2
  103. package/src/EntityMutationInfo.ts +13 -3
  104. package/src/EntityMutator.ts +44 -21
  105. package/src/EntityMutatorFactory.ts +10 -4
  106. package/src/EntityPrivacyPolicy.ts +31 -1
  107. package/src/GenericSecondaryEntityCache.ts +98 -0
  108. package/src/IEntityGenericCacher.ts +15 -0
  109. package/src/ReadonlyEntity.ts +1 -1
  110. package/src/ViewerScopedEntityLoaderFactory.ts +8 -3
  111. package/src/ViewerScopedEntityMutatorFactory.ts +22 -7
  112. package/src/__tests__/ComposedCacheAdapter-test.ts +280 -0
  113. package/src/__tests__/ComposedSecondaryEntityCache-test.ts +101 -0
  114. package/src/__tests__/EntityCommonUseCases-test.ts +2 -1
  115. package/src/__tests__/EntityEdges-test.ts +286 -45
  116. package/src/__tests__/EntityLoader-constructor-test.ts +3 -1
  117. package/src/__tests__/EntityLoader-test.ts +26 -1
  118. package/src/__tests__/EntityMutator-test.ts +99 -37
  119. package/src/__tests__/EntityPrivacyPolicy-test.ts +66 -7
  120. package/src/__tests__/EntitySecondaryCacheLoader-test.ts +1 -0
  121. package/src/__tests__/ViewerScopedEntityLoaderFactory-test.ts +4 -2
  122. package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +6 -2
  123. package/src/index.ts +4 -0
  124. package/src/rules/AlwaysAllowPrivacyPolicyRule.ts +2 -0
  125. package/src/rules/AlwaysDenyPrivacyPolicyRule.ts +2 -0
  126. package/src/rules/AlwaysSkipPrivacyPolicyRule.ts +2 -0
  127. package/src/rules/PrivacyPolicyRule.ts +2 -0
  128. package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -0
  129. package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -0
  130. package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -0
  131. package/src/utils/testing/PrivacyPolicyRuleTestUtils.ts +14 -6
@@ -11,14 +11,14 @@ import {
11
11
  EntityValidatorMutationInfo,
12
12
  EntityMutationType,
13
13
  EntityTriggerMutationInfo,
14
- EntityMutationTriggerDeleteCascadeInfo,
14
+ EntityCascadingDeletionInfo,
15
15
  } from './EntityMutationInfo';
16
16
  import EntityMutationTriggerConfiguration, {
17
17
  EntityMutationTrigger,
18
18
  EntityNonTransactionalMutationTrigger,
19
19
  } from './EntityMutationTriggerConfiguration';
20
20
  import EntityMutationValidator from './EntityMutationValidator';
21
- import EntityPrivacyPolicy from './EntityPrivacyPolicy';
21
+ import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
22
22
  import { EntityQueryContext, EntityTransactionalQueryContext } from './EntityQueryContext';
23
23
  import ReadonlyEntity from './ReadonlyEntity';
24
24
  import ViewerContext from './ViewerContext';
@@ -44,6 +44,7 @@ abstract class BaseMutator<
44
44
  constructor(
45
45
  protected readonly viewerContext: TViewerContext,
46
46
  protected readonly queryContext: EntityQueryContext,
47
+ protected readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
47
48
  protected readonly entityConfiguration: EntityConfiguration<TFields>,
48
49
  protected readonly entityClass: IEntityClass<
49
50
  TFields,
@@ -220,6 +221,7 @@ export class CreateMutator<
220
221
  this.privacyPolicy.authorizeCreateAsync(
221
222
  this.viewerContext,
222
223
  queryContext,
224
+ this.privacyPolicyEvaluationContext,
223
225
  temporaryEntityForPrivacyCheck,
224
226
  this.metricsAdapter
225
227
  )
@@ -249,7 +251,11 @@ export class CreateMutator<
249
251
 
250
252
  const insertResult = await this.databaseAdapter.insertAsync(queryContext, this.fieldsForEntity);
251
253
 
252
- const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext);
254
+ const entityLoader = this.entityLoaderFactory.forLoad(
255
+ this.viewerContext,
256
+ queryContext,
257
+ this.privacyPolicyEvaluationContext
258
+ );
253
259
  queryContext.appendPostCommitInvalidationCallback(
254
260
  entityLoader.invalidateFieldsAsync.bind(entityLoader, insertResult)
255
261
  );
@@ -309,6 +315,7 @@ export class UpdateMutator<
309
315
  constructor(
310
316
  viewerContext: TViewerContext,
311
317
  queryContext: EntityQueryContext,
318
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
312
319
  entityConfiguration: EntityConfiguration<TFields>,
313
320
  entityClass: IEntityClass<
314
321
  TFields,
@@ -348,6 +355,7 @@ export class UpdateMutator<
348
355
  super(
349
356
  viewerContext,
350
357
  queryContext,
358
+ privacyPolicyEvaluationContext,
351
359
  entityConfiguration,
352
360
  entityClass,
353
361
  privacyPolicy,
@@ -409,6 +417,7 @@ export class UpdateMutator<
409
417
  this.privacyPolicy.authorizeUpdateAsync(
410
418
  this.viewerContext,
411
419
  queryContext,
420
+ this.privacyPolicyEvaluationContext,
412
421
  entityAboutToBeUpdated,
413
422
  this.metricsAdapter
414
423
  )
@@ -443,7 +452,11 @@ export class UpdateMutator<
443
452
  this.updatedFields
444
453
  );
445
454
 
446
- const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext);
455
+ const entityLoader = this.entityLoaderFactory.forLoad(
456
+ this.viewerContext,
457
+ queryContext,
458
+ this.privacyPolicyEvaluationContext
459
+ );
447
460
 
448
461
  queryContext.appendPostCommitInvalidationCallback(
449
462
  entityLoader.invalidateFieldsAsync.bind(
@@ -506,6 +519,7 @@ export class DeleteMutator<
506
519
  constructor(
507
520
  viewerContext: TViewerContext,
508
521
  queryContext: EntityQueryContext,
522
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
509
523
  entityConfiguration: EntityConfiguration<TFields>,
510
524
  entityClass: IEntityClass<
511
525
  TFields,
@@ -545,6 +559,7 @@ export class DeleteMutator<
545
559
  super(
546
560
  viewerContext,
547
561
  queryContext,
562
+ privacyPolicyEvaluationContext,
548
563
  entityConfiguration,
549
564
  entityClass,
550
565
  privacyPolicy,
@@ -578,7 +593,7 @@ export class DeleteMutator<
578
593
  private async deleteInTransactionAsync(
579
594
  processedEntityIdentifiersFromTransitiveDeletions: Set<string>,
580
595
  skipDatabaseDeletion: boolean,
581
- cascadingDeleteCause: EntityMutationTriggerDeleteCascadeInfo | null
596
+ cascadingDeleteCause: EntityCascadingDeletionInfo | null
582
597
  ): Promise<Result<void>> {
583
598
  return await this.queryContext.runInTransactionIfNotInTransactionAsync((innerQueryContext) =>
584
599
  this.deleteInternalAsync(
@@ -594,12 +609,13 @@ export class DeleteMutator<
594
609
  queryContext: EntityTransactionalQueryContext,
595
610
  processedEntityIdentifiersFromTransitiveDeletions: Set<string>,
596
611
  skipDatabaseDeletion: boolean,
597
- cascadingDeleteCause: EntityMutationTriggerDeleteCascadeInfo | null
612
+ cascadingDeleteCause: EntityCascadingDeletionInfo | null
598
613
  ): Promise<Result<void>> {
599
614
  const authorizeDeleteResult = await asyncResult(
600
615
  this.privacyPolicy.authorizeDeleteAsync(
601
616
  this.viewerContext,
602
617
  queryContext,
618
+ { cascadingDeleteCause },
603
619
  this.entity,
604
620
  this.metricsAdapter
605
621
  )
@@ -636,7 +652,9 @@ export class DeleteMutator<
636
652
  );
637
653
  }
638
654
 
639
- const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext);
655
+ const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext, {
656
+ cascadingDeleteCause,
657
+ });
640
658
  queryContext.appendPostCommitInvalidationCallback(
641
659
  entityLoader.invalidateFieldsAsync.bind(entityLoader, this.entity.getAllDatabaseFields())
642
660
  );
@@ -684,7 +702,7 @@ export class DeleteMutator<
684
702
  entity: TEntity,
685
703
  queryContext: EntityTransactionalQueryContext,
686
704
  processedEntityIdentifiers: Set<string>,
687
- cascadingDeleteCause: EntityMutationTriggerDeleteCascadeInfo | null
705
+ cascadingDeleteCause: EntityCascadingDeletionInfo | null
688
706
  ): Promise<void> {
689
707
  // prevent infinite reference cycles by keeping track of entities already processed
690
708
  if (processedEntityIdentifiers.has(entity.getUniqueIdentifier())) {
@@ -732,10 +750,15 @@ export class DeleteMutator<
732
750
  .getViewerScopedEntityCompanionForClass(entityClass)
733
751
  .getMutatorFactory();
734
752
 
753
+ const newCascadingDeleteCause = {
754
+ entity,
755
+ cascadingDeleteCause,
756
+ };
757
+
735
758
  let inboundReferenceEntities: readonly ReadonlyEntity<any, any, any, any>[];
736
759
  if (associatedEntityLookupByField) {
737
760
  inboundReferenceEntities = await loaderFactory
738
- .forLoad(queryContext)
761
+ .forLoad(queryContext, { cascadingDeleteCause: newCascadingDeleteCause })
739
762
  .enforcing()
740
763
  .loadManyByFieldEqualingAsync(
741
764
  fieldName,
@@ -743,7 +766,7 @@ export class DeleteMutator<
743
766
  );
744
767
  } else {
745
768
  inboundReferenceEntities = await loaderFactory
746
- .forLoad(queryContext)
769
+ .forLoad(queryContext, { cascadingDeleteCause: newCascadingDeleteCause })
747
770
  .enforcing()
748
771
  .loadManyByFieldEqualingAsync(fieldName, entity.getID());
749
772
  }
@@ -754,14 +777,13 @@ export class DeleteMutator<
754
777
  await Promise.all(
755
778
  inboundReferenceEntities.map((inboundReferenceEntity) =>
756
779
  mutatorFactory
757
- .forDelete(inboundReferenceEntity, queryContext)
780
+ .forDelete(inboundReferenceEntity, queryContext, {
781
+ cascadingDeleteCause: newCascadingDeleteCause,
782
+ })
758
783
  .deleteInTransactionAsync(
759
784
  processedEntityIdentifiers,
760
785
  /* skipDatabaseDeletion */ true, // deletion is handled by DB
761
- {
762
- entity,
763
- cascadingDeleteCause,
764
- }
786
+ newCascadingDeleteCause
765
787
  )
766
788
  )
767
789
  );
@@ -771,7 +793,9 @@ export class DeleteMutator<
771
793
  await Promise.all(
772
794
  inboundReferenceEntities.map((inboundReferenceEntity) =>
773
795
  mutatorFactory
774
- .forUpdate(inboundReferenceEntity, queryContext)
796
+ .forUpdate(inboundReferenceEntity, queryContext, {
797
+ cascadingDeleteCause: newCascadingDeleteCause,
798
+ })
775
799
  .setField(fieldName, null)
776
800
  .enforceUpdateAsync()
777
801
  )
@@ -782,14 +806,13 @@ export class DeleteMutator<
782
806
  await Promise.all(
783
807
  inboundReferenceEntities.map((inboundReferenceEntity) =>
784
808
  mutatorFactory
785
- .forDelete(inboundReferenceEntity, queryContext)
809
+ .forDelete(inboundReferenceEntity, queryContext, {
810
+ cascadingDeleteCause: newCascadingDeleteCause,
811
+ })
786
812
  .deleteInTransactionAsync(
787
813
  processedEntityIdentifiers,
788
814
  /* skipDatabaseDeletion */ false,
789
- {
790
- entity,
791
- cascadingDeleteCause,
792
- }
815
+ newCascadingDeleteCause
793
816
  )
794
817
  )
795
818
  );
@@ -5,7 +5,7 @@ import EntityLoaderFactory from './EntityLoaderFactory';
5
5
  import EntityMutationTriggerConfiguration from './EntityMutationTriggerConfiguration';
6
6
  import EntityMutationValidator from './EntityMutationValidator';
7
7
  import { CreateMutator, UpdateMutator, DeleteMutator } from './EntityMutator';
8
- import EntityPrivacyPolicy from './EntityPrivacyPolicy';
8
+ import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
9
9
  import { EntityQueryContext } from './EntityQueryContext';
10
10
  import ViewerContext from './ViewerContext';
11
11
  import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
@@ -72,11 +72,13 @@ export default class EntityMutatorFactory<
72
72
  */
73
73
  forCreate(
74
74
  viewerContext: TViewerContext,
75
- queryContext: EntityQueryContext
75
+ queryContext: EntityQueryContext,
76
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
76
77
  ): CreateMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
77
78
  return new CreateMutator(
78
79
  viewerContext,
79
80
  queryContext,
81
+ privacyPolicyEvaluationContext,
80
82
  this.entityConfiguration,
81
83
  this.entityClass,
82
84
  this.privacyPolicy,
@@ -96,11 +98,13 @@ export default class EntityMutatorFactory<
96
98
  */
97
99
  forUpdate(
98
100
  existingEntity: TEntity,
99
- queryContext: EntityQueryContext
101
+ queryContext: EntityQueryContext,
102
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
100
103
  ): UpdateMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
101
104
  return new UpdateMutator(
102
105
  existingEntity.getViewerContext(),
103
106
  queryContext,
107
+ privacyPolicyEvaluationContext,
104
108
  this.entityConfiguration,
105
109
  this.entityClass,
106
110
  this.privacyPolicy,
@@ -120,11 +124,13 @@ export default class EntityMutatorFactory<
120
124
  */
121
125
  forDelete(
122
126
  existingEntity: TEntity,
123
- queryContext: EntityQueryContext
127
+ queryContext: EntityQueryContext,
128
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
124
129
  ): DeleteMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
125
130
  return new DeleteMutator(
126
131
  existingEntity.getViewerContext(),
127
132
  queryContext,
133
+ privacyPolicyEvaluationContext,
128
134
  this.entityConfiguration,
129
135
  this.entityClass,
130
136
  this.privacyPolicy,
@@ -1,3 +1,4 @@
1
+ import { EntityCascadingDeletionInfo } from './EntityMutationInfo';
1
2
  import { EntityQueryContext } from './EntityQueryContext';
2
3
  import ReadonlyEntity from './ReadonlyEntity';
3
4
  import ViewerContext from './ViewerContext';
@@ -7,6 +8,17 @@ import IEntityMetricsAdapter, {
7
8
  } from './metrics/IEntityMetricsAdapter';
8
9
  import PrivacyPolicyRule, { RuleEvaluationResult } from './rules/PrivacyPolicyRule';
9
10
 
11
+ /**
12
+ * Information about the reason this privacy policy is being evaluated.
13
+ */
14
+ export type EntityPrivacyPolicyEvaluationContext = {
15
+ /**
16
+ * When this privacy policy is being evaluated as a result of a cascading deletion, this will be populated
17
+ * with information on the cascading delete.
18
+ */
19
+ cascadingDeleteCause: EntityCascadingDeletionInfo | null;
20
+ };
21
+
10
22
  export enum EntityPrivacyPolicyEvaluationMode {
11
23
  ENFORCE,
12
24
  DRY_RUN,
@@ -127,6 +139,7 @@ export default abstract class EntityPrivacyPolicy<
127
139
  async authorizeCreateAsync(
128
140
  viewerContext: TViewerContext,
129
141
  queryContext: EntityQueryContext,
142
+ evaluationContext: EntityPrivacyPolicyEvaluationContext,
130
143
  entity: TEntity,
131
144
  metricsAdapter: IEntityMetricsAdapter
132
145
  ): Promise<TEntity> {
@@ -134,6 +147,7 @@ export default abstract class EntityPrivacyPolicy<
134
147
  this.createRules,
135
148
  viewerContext,
136
149
  queryContext,
150
+ evaluationContext,
137
151
  entity,
138
152
  EntityAuthorizationAction.CREATE,
139
153
  metricsAdapter
@@ -151,6 +165,7 @@ export default abstract class EntityPrivacyPolicy<
151
165
  async authorizeReadAsync(
152
166
  viewerContext: TViewerContext,
153
167
  queryContext: EntityQueryContext,
168
+ evaluationContext: EntityPrivacyPolicyEvaluationContext,
154
169
  entity: TEntity,
155
170
  metricsAdapter: IEntityMetricsAdapter
156
171
  ): Promise<TEntity> {
@@ -158,6 +173,7 @@ export default abstract class EntityPrivacyPolicy<
158
173
  this.readRules,
159
174
  viewerContext,
160
175
  queryContext,
176
+ evaluationContext,
161
177
  entity,
162
178
  EntityAuthorizationAction.READ,
163
179
  metricsAdapter
@@ -175,6 +191,7 @@ export default abstract class EntityPrivacyPolicy<
175
191
  async authorizeUpdateAsync(
176
192
  viewerContext: TViewerContext,
177
193
  queryContext: EntityQueryContext,
194
+ evaluationContext: EntityPrivacyPolicyEvaluationContext,
178
195
  entity: TEntity,
179
196
  metricsAdapter: IEntityMetricsAdapter
180
197
  ): Promise<TEntity> {
@@ -182,6 +199,7 @@ export default abstract class EntityPrivacyPolicy<
182
199
  this.updateRules,
183
200
  viewerContext,
184
201
  queryContext,
202
+ evaluationContext,
185
203
  entity,
186
204
  EntityAuthorizationAction.UPDATE,
187
205
  metricsAdapter
@@ -199,6 +217,7 @@ export default abstract class EntityPrivacyPolicy<
199
217
  async authorizeDeleteAsync(
200
218
  viewerContext: TViewerContext,
201
219
  queryContext: EntityQueryContext,
220
+ evaluationContext: EntityPrivacyPolicyEvaluationContext,
202
221
  entity: TEntity,
203
222
  metricsAdapter: IEntityMetricsAdapter
204
223
  ): Promise<TEntity> {
@@ -206,6 +225,7 @@ export default abstract class EntityPrivacyPolicy<
206
225
  this.deleteRules,
207
226
  viewerContext,
208
227
  queryContext,
228
+ evaluationContext,
209
229
  entity,
210
230
  EntityAuthorizationAction.DELETE,
211
231
  metricsAdapter
@@ -216,6 +236,7 @@ export default abstract class EntityPrivacyPolicy<
216
236
  ruleset: readonly PrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>[],
217
237
  viewerContext: TViewerContext,
218
238
  queryContext: EntityQueryContext,
239
+ evaluationContext: EntityPrivacyPolicyEvaluationContext,
219
240
  entity: TEntity,
220
241
  action: EntityAuthorizationAction,
221
242
  metricsAdapter: IEntityMetricsAdapter
@@ -228,6 +249,7 @@ export default abstract class EntityPrivacyPolicy<
228
249
  ruleset,
229
250
  viewerContext,
230
251
  queryContext,
252
+ evaluationContext,
231
253
  entity,
232
254
  action
233
255
  );
@@ -256,6 +278,7 @@ export default abstract class EntityPrivacyPolicy<
256
278
  ruleset,
257
279
  viewerContext,
258
280
  queryContext,
281
+ evaluationContext,
259
282
  entity,
260
283
  action
261
284
  );
@@ -285,6 +308,7 @@ export default abstract class EntityPrivacyPolicy<
285
308
  ruleset,
286
309
  viewerContext,
287
310
  queryContext,
311
+ evaluationContext,
288
312
  entity,
289
313
  action
290
314
  );
@@ -315,12 +339,18 @@ export default abstract class EntityPrivacyPolicy<
315
339
  ruleset: readonly PrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>[],
316
340
  viewerContext: TViewerContext,
317
341
  queryContext: EntityQueryContext,
342
+ evaluationContext: EntityPrivacyPolicyEvaluationContext,
318
343
  entity: TEntity,
319
344
  action: EntityAuthorizationAction
320
345
  ): Promise<TEntity> {
321
346
  for (let i = 0; i < ruleset.length; i++) {
322
347
  const rule = ruleset[i]!;
323
- const ruleEvaluationResult = await rule.evaluateAsync(viewerContext, queryContext, entity);
348
+ const ruleEvaluationResult = await rule.evaluateAsync(
349
+ viewerContext,
350
+ queryContext,
351
+ evaluationContext,
352
+ entity
353
+ );
324
354
  switch (ruleEvaluationResult) {
325
355
  case RuleEvaluationResult.DENY:
326
356
  throw new EntityNotAuthorizedError<
@@ -0,0 +1,98 @@
1
+ import invariant from 'invariant';
2
+
3
+ import { ISecondaryEntityCache } from './EntitySecondaryCacheLoader';
4
+ import IEntityGenericCacher from './IEntityGenericCacher';
5
+ import { CacheStatus } from './internal/ReadThroughEntityCache';
6
+ import { filterMap, zipToMap } from './utils/collections/maps';
7
+
8
+ /**
9
+ * A custom secondary read-through entity cache is a way to add a custom second layer of caching for a particular
10
+ * single entity load. One common way this may be used is to add a second layer of caching in a hot path that makes
11
+ * a call to {@link EntityLoader.loadManyByFieldEqualityConjunctionAsync} is guaranteed to return at most one entity.
12
+ */
13
+ export default abstract class GenericSecondaryEntityCache<TFields, TLoadParams>
14
+ implements ISecondaryEntityCache<TFields, TLoadParams>
15
+ {
16
+ constructor(
17
+ protected readonly cacher: IEntityGenericCacher<TFields>,
18
+ protected readonly constructCacheKey: (params: Readonly<TLoadParams>) => string
19
+ ) {}
20
+
21
+ public async loadManyThroughAsync(
22
+ loadParamsArray: readonly Readonly<TLoadParams>[],
23
+ fetcher: (
24
+ fetcherLoadParamsArray: readonly Readonly<TLoadParams>[]
25
+ ) => Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>>
26
+ ): Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>> {
27
+ const cacheKeys = loadParamsArray.map(this.constructCacheKey);
28
+ const cacheKeyToLoadParamsMap = zipToMap(cacheKeys, loadParamsArray);
29
+
30
+ const cacheLoadResults = await this.cacher.loadManyAsync(cacheKeys);
31
+
32
+ invariant(
33
+ cacheLoadResults.size === loadParamsArray.length,
34
+ `${this.constructor.name} loadMany should return a result for each key`
35
+ );
36
+
37
+ const cacheKeysToFetch = Array.from(
38
+ filterMap(
39
+ cacheLoadResults,
40
+ (cacheLoadResult) => cacheLoadResult.status === CacheStatus.MISS
41
+ ).keys()
42
+ );
43
+
44
+ // put cache hits in result map
45
+ const results: Map<Readonly<TLoadParams>, Readonly<TFields> | null> = new Map();
46
+ cacheLoadResults.forEach((cacheLoadResult, cacheKey) => {
47
+ if (cacheLoadResult.status === CacheStatus.HIT) {
48
+ const loadParams = cacheKeyToLoadParamsMap.get(cacheKey);
49
+ invariant(loadParams !== undefined, 'load params should be in cache key map');
50
+ results.set(loadParams, cacheLoadResult.item);
51
+ }
52
+ });
53
+
54
+ // fetch any misses from DB, add DB objects to results, cache DB results, inform cache of any missing DB results
55
+ if (cacheKeysToFetch.length > 0) {
56
+ const loadParamsToFetch = cacheKeysToFetch.map((cacheKey) => {
57
+ const loadParams = cacheKeyToLoadParamsMap.get(cacheKey);
58
+ invariant(loadParams !== undefined, 'load params should be in cache key map');
59
+ return loadParams;
60
+ });
61
+ const fetchResults = await fetcher(loadParamsToFetch);
62
+
63
+ const fetchMisses = loadParamsToFetch.filter((loadParams) => {
64
+ // all values of fetchResults should be field objects or undefined
65
+ return !fetchResults.get(loadParams);
66
+ });
67
+
68
+ for (const fetchMiss of fetchMisses) {
69
+ results.set(fetchMiss, null);
70
+ }
71
+
72
+ const objectsToCache: Map<string, Readonly<TFields>> = new Map();
73
+ for (const [loadParams, object] of fetchResults.entries()) {
74
+ if (object) {
75
+ objectsToCache.set(this.constructCacheKey(loadParams), object);
76
+ results.set(loadParams, object);
77
+ }
78
+ }
79
+
80
+ await Promise.all([
81
+ this.cacher.cacheManyAsync(objectsToCache),
82
+ this.cacher.cacheDBMissesAsync(fetchMisses.map(this.constructCacheKey)),
83
+ ]);
84
+ }
85
+
86
+ return results;
87
+ }
88
+
89
+ /**
90
+ * Invalidate the cache for objects cached by constructCacheKey(loadParams).
91
+ *
92
+ * @param loadParamsArray - load params to invalidate
93
+ */
94
+ public invalidateManyAsync(loadParamsArray: readonly Readonly<TLoadParams>[]): Promise<void> {
95
+ const cacheKeys = loadParamsArray.map(this.constructCacheKey);
96
+ return this.cacher.invalidateManyAsync(cacheKeys);
97
+ }
98
+ }
@@ -0,0 +1,15 @@
1
+ import { CacheLoadResult } from './internal/ReadThroughEntityCache';
2
+
3
+ /**
4
+ * A cacher stores and loads key-value pairs. It also supports negative caching - it stores the absence
5
+ * of keys that don't exist in the backing datastore.
6
+ */
7
+ export default interface IEntityGenericCacher<TFields> {
8
+ loadManyAsync(keys: readonly string[]): Promise<ReadonlyMap<string, CacheLoadResult<TFields>>>;
9
+
10
+ cacheManyAsync(objectMap: ReadonlyMap<string, Readonly<TFields>>): Promise<void>;
11
+
12
+ cacheDBMissesAsync(keys: readonly string[]): Promise<void>;
13
+
14
+ invalidateManyAsync(keys: readonly string[]): Promise<void>;
15
+ }
@@ -156,6 +156,6 @@ export default abstract class ReadonlyEntity<
156
156
  return viewerContext
157
157
  .getViewerScopedEntityCompanionForClass(this)
158
158
  .getLoaderFactory()
159
- .forLoad(queryContext);
159
+ .forLoad(queryContext, { cascadingDeleteCause: null });
160
160
  }
161
161
  }
@@ -1,6 +1,6 @@
1
1
  import EntityLoader from './EntityLoader';
2
2
  import EntityLoaderFactory from './EntityLoaderFactory';
3
- import EntityPrivacyPolicy from './EntityPrivacyPolicy';
3
+ import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
4
4
  import { EntityQueryContext } from './EntityQueryContext';
5
5
  import ReadonlyEntity from './ReadonlyEntity';
6
6
  import ViewerContext from './ViewerContext';
@@ -35,8 +35,13 @@ export default class ViewerScopedEntityLoaderFactory<
35
35
  ) {}
36
36
 
37
37
  forLoad(
38
- queryContext: EntityQueryContext
38
+ queryContext: EntityQueryContext,
39
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
39
40
  ): EntityLoader<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
40
- return this.entityLoaderFactory.forLoad(this.viewerContext, queryContext);
41
+ return this.entityLoaderFactory.forLoad(
42
+ this.viewerContext,
43
+ queryContext,
44
+ privacyPolicyEvaluationContext
45
+ );
41
46
  }
42
47
  }
@@ -1,6 +1,6 @@
1
1
  import { CreateMutator, UpdateMutator, DeleteMutator } from './EntityMutator';
2
2
  import EntityMutatorFactory from './EntityMutatorFactory';
3
- import EntityPrivacyPolicy from './EntityPrivacyPolicy';
3
+ import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
4
4
  import { EntityQueryContext } from './EntityQueryContext';
5
5
  import ReadonlyEntity from './ReadonlyEntity';
6
6
  import ViewerContext from './ViewerContext';
@@ -35,22 +35,37 @@ export default class ViewerScopedEntityMutatorFactory<
35
35
  ) {}
36
36
 
37
37
  forCreate(
38
- queryContext: EntityQueryContext
38
+ queryContext: EntityQueryContext,
39
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
39
40
  ): CreateMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
40
- return this.entityMutatorFactory.forCreate(this.viewerContext, queryContext);
41
+ return this.entityMutatorFactory.forCreate(
42
+ this.viewerContext,
43
+ queryContext,
44
+ privacyPolicyEvaluationContext
45
+ );
41
46
  }
42
47
 
43
48
  forUpdate(
44
49
  existingEntity: TEntity,
45
- queryContext: EntityQueryContext
50
+ queryContext: EntityQueryContext,
51
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
46
52
  ): UpdateMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
47
- return this.entityMutatorFactory.forUpdate(existingEntity, queryContext);
53
+ return this.entityMutatorFactory.forUpdate(
54
+ existingEntity,
55
+ queryContext,
56
+ privacyPolicyEvaluationContext
57
+ );
48
58
  }
49
59
 
50
60
  forDelete(
51
61
  existingEntity: TEntity,
52
- queryContext: EntityQueryContext
62
+ queryContext: EntityQueryContext,
63
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
53
64
  ): DeleteMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
54
- return this.entityMutatorFactory.forDelete(existingEntity, queryContext);
65
+ return this.entityMutatorFactory.forDelete(
66
+ existingEntity,
67
+ queryContext,
68
+ privacyPolicyEvaluationContext
69
+ );
55
70
  }
56
71
  }