@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.
- package/build/ComposedEntityCacheAdapter.d.ts +19 -0
- package/build/ComposedEntityCacheAdapter.js +66 -0
- package/build/ComposedEntityCacheAdapter.js.map +1 -0
- package/build/ComposedSecondaryEntityCache.d.ts +15 -0
- package/build/ComposedSecondaryEntityCache.js +37 -0
- package/build/ComposedSecondaryEntityCache.js.map +1 -0
- package/build/Entity.js +6 -6
- package/build/Entity.js.map +1 -1
- package/build/EntityAssociationLoader.js +4 -4
- package/build/EntityAssociationLoader.js.map +1 -1
- package/build/EntityFieldDefinition.d.ts +8 -0
- package/build/EntityFieldDefinition.js +5 -0
- package/build/EntityFieldDefinition.js.map +1 -1
- package/build/EntityFields.d.ts +38 -0
- package/build/EntityFields.js +38 -0
- package/build/EntityFields.js.map +1 -1
- package/build/EntityLoader.d.ts +3 -2
- package/build/EntityLoader.js +5 -4
- package/build/EntityLoader.js.map +1 -1
- package/build/EntityLoaderFactory.d.ts +2 -2
- package/build/EntityLoaderFactory.js +2 -2
- package/build/EntityLoaderFactory.js.map +1 -1
- package/build/EntityMutationInfo.d.ts +12 -3
- package/build/EntityMutator.d.ts +5 -4
- package/build/EntityMutator.js +31 -24
- package/build/EntityMutator.js.map +1 -1
- package/build/EntityMutatorFactory.d.ts +4 -4
- package/build/EntityMutatorFactory.js +6 -6
- package/build/EntityMutatorFactory.js.map +1 -1
- package/build/EntityPrivacyPolicy.d.ts +15 -4
- package/build/EntityPrivacyPolicy.js +14 -14
- package/build/EntityPrivacyPolicy.js.map +1 -1
- package/build/GenericSecondaryEntityCache.d.ts +19 -0
- package/build/GenericSecondaryEntityCache.js +74 -0
- package/build/GenericSecondaryEntityCache.js.map +1 -0
- package/build/IEntityGenericCacher.d.ts +11 -0
- package/build/IEntityGenericCacher.js +3 -0
- package/build/IEntityGenericCacher.js.map +1 -0
- package/build/ReadonlyEntity.js +1 -1
- package/build/ReadonlyEntity.js.map +1 -1
- package/build/ViewerScopedEntityLoaderFactory.d.ts +2 -2
- package/build/ViewerScopedEntityLoaderFactory.js +2 -2
- package/build/ViewerScopedEntityLoaderFactory.js.map +1 -1
- package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
- package/build/ViewerScopedEntityMutatorFactory.js +6 -6
- package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
- package/build/__tests__/ComposedCacheAdapter-test.d.ts +1 -0
- package/build/__tests__/ComposedCacheAdapter-test.js +198 -0
- package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -0
- package/build/__tests__/ComposedSecondaryEntityCache-test.d.ts +1 -0
- package/build/__tests__/ComposedSecondaryEntityCache-test.js +65 -0
- package/build/__tests__/ComposedSecondaryEntityCache-test.js.map +1 -0
- package/build/__tests__/EntityCommonUseCases-test.js +1 -1
- package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
- package/build/__tests__/EntityEdges-test.js +260 -37
- package/build/__tests__/EntityEdges-test.js.map +1 -1
- package/build/__tests__/EntityLoader-constructor-test.js +2 -1
- package/build/__tests__/EntityLoader-constructor-test.js.map +1 -1
- package/build/__tests__/EntityLoader-test.js +19 -11
- package/build/__tests__/EntityLoader-test.js.map +1 -1
- package/build/__tests__/EntityMutator-test.js +96 -43
- package/build/__tests__/EntityMutator-test.js.map +1 -1
- package/build/__tests__/EntityPrivacyPolicy-test.js +23 -12
- package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
- package/build/__tests__/EntitySecondaryCacheLoader-test.js +1 -1
- package/build/__tests__/EntitySecondaryCacheLoader-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js +3 -2
- package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +3 -2
- package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
- package/build/index.d.ts +4 -0
- package/build/index.js +7 -1
- package/build/index.js.map +1 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.d.ts +2 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js +1 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.d.ts +2 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js +1 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.d.ts +2 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js +1 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
- package/build/rules/PrivacyPolicyRule.d.ts +2 -1
- package/build/rules/PrivacyPolicyRule.js.map +1 -1
- package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js +1 -0
- package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
- package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js +1 -0
- package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
- package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js +1 -0
- package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
- package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +2 -0
- package/build/utils/testing/PrivacyPolicyRuleTestUtils.js +6 -6
- package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/ComposedEntityCacheAdapter.ts +86 -0
- package/src/ComposedSecondaryEntityCache.ts +63 -0
- package/src/Entity.ts +6 -4
- package/src/EntityAssociationLoader.ts +4 -4
- package/src/EntityFieldDefinition.ts +8 -0
- package/src/EntityFields.ts +45 -0
- package/src/EntityLoader.ts +5 -1
- package/src/EntityLoaderFactory.ts +4 -2
- package/src/EntityMutationInfo.ts +13 -3
- package/src/EntityMutator.ts +44 -21
- package/src/EntityMutatorFactory.ts +10 -4
- package/src/EntityPrivacyPolicy.ts +31 -1
- package/src/GenericSecondaryEntityCache.ts +98 -0
- package/src/IEntityGenericCacher.ts +15 -0
- package/src/ReadonlyEntity.ts +1 -1
- package/src/ViewerScopedEntityLoaderFactory.ts +8 -3
- package/src/ViewerScopedEntityMutatorFactory.ts +22 -7
- package/src/__tests__/ComposedCacheAdapter-test.ts +280 -0
- package/src/__tests__/ComposedSecondaryEntityCache-test.ts +101 -0
- package/src/__tests__/EntityCommonUseCases-test.ts +2 -1
- package/src/__tests__/EntityEdges-test.ts +286 -45
- package/src/__tests__/EntityLoader-constructor-test.ts +3 -1
- package/src/__tests__/EntityLoader-test.ts +26 -1
- package/src/__tests__/EntityMutator-test.ts +99 -37
- package/src/__tests__/EntityPrivacyPolicy-test.ts +66 -7
- package/src/__tests__/EntitySecondaryCacheLoader-test.ts +1 -0
- package/src/__tests__/ViewerScopedEntityLoaderFactory-test.ts +4 -2
- package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +6 -2
- package/src/index.ts +4 -0
- package/src/rules/AlwaysAllowPrivacyPolicyRule.ts +2 -0
- package/src/rules/AlwaysDenyPrivacyPolicyRule.ts +2 -0
- package/src/rules/AlwaysSkipPrivacyPolicyRule.ts +2 -0
- package/src/rules/PrivacyPolicyRule.ts +2 -0
- package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -0
- package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -0
- package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -0
- package/src/utils/testing/PrivacyPolicyRuleTestUtils.ts +14 -6
package/src/EntityMutator.ts
CHANGED
|
@@ -11,14 +11,14 @@ import {
|
|
|
11
11
|
EntityValidatorMutationInfo,
|
|
12
12
|
EntityMutationType,
|
|
13
13
|
EntityTriggerMutationInfo,
|
|
14
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
+
}
|
package/src/ReadonlyEntity.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
65
|
+
return this.entityMutatorFactory.forDelete(
|
|
66
|
+
existingEntity,
|
|
67
|
+
queryContext,
|
|
68
|
+
privacyPolicyEvaluationContext
|
|
69
|
+
);
|
|
55
70
|
}
|
|
56
71
|
}
|