@expo/entity 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/Entity.js +4 -4
- package/build/Entity.js.map +1 -1
- package/build/EntityFieldDefinition.d.ts +16 -8
- package/build/EntityFieldDefinition.js +12 -5
- package/build/EntityFieldDefinition.js.map +1 -1
- package/build/EntityLoader.js +2 -2
- package/build/EntityLoader.js.map +1 -1
- package/build/EntityMutationInfo.d.ts +2 -0
- package/build/EntityMutator.d.ts +6 -8
- package/build/EntityMutator.js +58 -46
- 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 +13 -0
- package/build/EntityPrivacyPolicy.js +13 -0
- package/build/EntityPrivacyPolicy.js.map +1 -1
- package/build/EntityQueryContext.d.ts +11 -0
- package/build/EntityQueryContext.js +11 -0
- package/build/EntityQueryContext.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.js +1 -1
- package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -1
- package/build/__tests__/EntityCommonUseCases-test.js +5 -1
- package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
- package/build/__tests__/EntityCompanion-test.js +5 -1
- package/build/__tests__/EntityCompanion-test.js.map +1 -1
- package/build/__tests__/EntityCompanionProvider-test.js +5 -1
- package/build/__tests__/EntityCompanionProvider-test.js.map +1 -1
- package/build/__tests__/EntityEdges-test.js +199 -33
- package/build/__tests__/EntityEdges-test.js.map +1 -1
- package/build/__tests__/EntityLoader-test.js +5 -1
- package/build/__tests__/EntityLoader-test.js.map +1 -1
- package/build/__tests__/EntityMutator-test.js +38 -53
- package/build/__tests__/EntityMutator-test.js.map +1 -1
- package/build/__tests__/EntityPrivacyPolicy-test.js +5 -1
- package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
- package/build/__tests__/EntitySelfReferentialEdges-test.js +2 -2
- package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js +5 -1
- package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +2 -3
- package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
- package/build/errors/EntityCacheAdapterError.js +5 -1
- package/build/errors/EntityCacheAdapterError.js.map +1 -1
- package/build/errors/EntityDatabaseAdapterError.js +5 -1
- package/build/errors/EntityDatabaseAdapterError.js.map +1 -1
- package/build/errors/EntityInvalidFieldValueError.js +6 -2
- package/build/errors/EntityInvalidFieldValueError.js.map +1 -1
- package/build/errors/EntityNotAuthorizedError.js +5 -1
- package/build/errors/EntityNotAuthorizedError.js.map +1 -1
- package/build/errors/EntityNotFoundError.js +6 -2
- package/build/errors/EntityNotFoundError.js.map +1 -1
- package/build/index.js +5 -1
- package/build/index.js.map +1 -1
- package/build/internal/EntityDataManager.js +7 -4
- package/build/internal/EntityDataManager.js.map +1 -1
- package/build/internal/EntityFieldTransformationUtils.js +1 -1
- package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
- package/build/internal/ReadThroughEntityCache.js +1 -1
- package/build/internal/ReadThroughEntityCache.js.map +1 -1
- package/build/internal/__tests__/EntityDataManager-test.js +12 -7
- package/build/internal/__tests__/EntityDataManager-test.js.map +1 -1
- package/build/internal/__tests__/ReadThroughEntityCache-test.js +5 -1
- package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
- package/build/metrics/IEntityMetricsAdapter.d.ts +62 -17
- package/build/metrics/IEntityMetricsAdapter.js +17 -1
- package/build/metrics/IEntityMetricsAdapter.js.map +1 -1
- package/build/metrics/NoOpEntityMetricsAdapter.d.ts +1 -3
- package/build/metrics/NoOpEntityMetricsAdapter.js +1 -3
- package/build/metrics/NoOpEntityMetricsAdapter.js.map +1 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js +5 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js +5 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js +5 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
- package/build/utils/testing/StubDatabaseAdapter.js +6 -2
- package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
- package/package.json +1 -1
- package/src/Entity.ts +4 -4
- package/src/EntityFieldDefinition.ts +15 -6
- package/src/EntityLoader.ts +4 -2
- package/src/EntityMutationInfo.ts +2 -0
- package/src/EntityMutator.ts +98 -67
- package/src/EntityMutatorFactory.ts +4 -10
- package/src/EntityPrivacyPolicy.ts +15 -0
- package/src/EntityQueryContext.ts +11 -0
- package/src/ViewerScopedEntityMutatorFactory.ts +7 -22
- package/src/__tests__/ComposedCacheAdapter-test.ts +1 -1
- package/src/__tests__/EntityEdges-test.ts +287 -32
- package/src/__tests__/EntityMutator-test.ts +33 -54
- package/src/__tests__/EntitySelfReferentialEdges-test.ts +2 -2
- package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +2 -6
- package/src/errors/EntityInvalidFieldValueError.ts +1 -1
- package/src/errors/EntityNotFoundError.ts +1 -1
- package/src/internal/EntityDataManager.ts +13 -5
- package/src/internal/EntityFieldTransformationUtils.ts +1 -1
- package/src/internal/ReadThroughEntityCache.ts +3 -1
- package/src/internal/__tests__/EntityDataManager-test.ts +11 -8
- package/src/metrics/IEntityMetricsAdapter.ts +72 -19
- package/src/metrics/NoOpEntityMetricsAdapter.ts +1 -5
- package/src/utils/testing/StubDatabaseAdapter.ts +4 -1
|
@@ -341,7 +341,6 @@ describe(EntityMutatorFactory, () => {
|
|
|
341
341
|
describe('forCreate', () => {
|
|
342
342
|
it('creates entities', async () => {
|
|
343
343
|
const viewerContext = mock<ViewerContext>();
|
|
344
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
345
344
|
const queryContext = StubQueryContextProvider.getQueryContext();
|
|
346
345
|
|
|
347
346
|
const id1 = uuidv4();
|
|
@@ -365,7 +364,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
365
364
|
},
|
|
366
365
|
]);
|
|
367
366
|
const newEntity = await entityMutatorFactory
|
|
368
|
-
.forCreate(viewerContext, queryContext
|
|
367
|
+
.forCreate(viewerContext, queryContext)
|
|
369
368
|
.setField('stringField', 'huh')
|
|
370
369
|
.enforceCreateAsync();
|
|
371
370
|
expect(newEntity).toBeTruthy();
|
|
@@ -373,7 +372,6 @@ describe(EntityMutatorFactory, () => {
|
|
|
373
372
|
|
|
374
373
|
it('checks privacy', async () => {
|
|
375
374
|
const viewerContext = mock<ViewerContext>();
|
|
376
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
377
375
|
const queryContext = StubQueryContextProvider.getQueryContext();
|
|
378
376
|
|
|
379
377
|
const id1 = uuidv4();
|
|
@@ -400,7 +398,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
400
398
|
const spiedPrivacyPolicy = spy(privacyPolicy);
|
|
401
399
|
|
|
402
400
|
await entityMutatorFactory
|
|
403
|
-
.forCreate(viewerContext, queryContext
|
|
401
|
+
.forCreate(viewerContext, queryContext)
|
|
404
402
|
.setField('stringField', 'huh')
|
|
405
403
|
.enforceCreateAsync();
|
|
406
404
|
|
|
@@ -408,7 +406,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
408
406
|
spiedPrivacyPolicy.authorizeCreateAsync(
|
|
409
407
|
viewerContext,
|
|
410
408
|
anyOfClass(EntityTransactionalQueryContext),
|
|
411
|
-
|
|
409
|
+
deepEqual({ cascadingDeleteCause: null }),
|
|
412
410
|
anyOfClass(TestEntity),
|
|
413
411
|
anything()
|
|
414
412
|
)
|
|
@@ -417,7 +415,6 @@ describe(EntityMutatorFactory, () => {
|
|
|
417
415
|
|
|
418
416
|
it('executes triggers', async () => {
|
|
419
417
|
const viewerContext = mock<ViewerContext>();
|
|
420
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
421
418
|
const queryContext = StubQueryContextProvider.getQueryContext();
|
|
422
419
|
|
|
423
420
|
const id1 = uuidv4();
|
|
@@ -444,7 +441,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
444
441
|
const triggerSpies = setUpMutationTriggerSpies(mutationTriggers);
|
|
445
442
|
|
|
446
443
|
await entityMutatorFactory
|
|
447
|
-
.forCreate(viewerContext, queryContext
|
|
444
|
+
.forCreate(viewerContext, queryContext)
|
|
448
445
|
.setField('stringField', 'huh')
|
|
449
446
|
.enforceCreateAsync();
|
|
450
447
|
|
|
@@ -465,7 +462,6 @@ describe(EntityMutatorFactory, () => {
|
|
|
465
462
|
|
|
466
463
|
it('executes validators', async () => {
|
|
467
464
|
const viewerContext = mock<ViewerContext>();
|
|
468
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
469
465
|
const queryContext = StubQueryContextProvider.getQueryContext();
|
|
470
466
|
|
|
471
467
|
const id1 = uuidv4();
|
|
@@ -492,7 +488,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
492
488
|
const validatorSpies = setUpMutationValidatorSpies(mutationValidators);
|
|
493
489
|
|
|
494
490
|
await entityMutatorFactory
|
|
495
|
-
.forCreate(viewerContext, queryContext
|
|
491
|
+
.forCreate(viewerContext, queryContext)
|
|
496
492
|
.setField('stringField', 'huh')
|
|
497
493
|
.enforceCreateAsync();
|
|
498
494
|
|
|
@@ -534,7 +530,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
534
530
|
);
|
|
535
531
|
|
|
536
532
|
const updatedEntity = await entityMutatorFactory
|
|
537
|
-
.forUpdate(existingEntity, queryContext
|
|
533
|
+
.forUpdate(existingEntity, queryContext)
|
|
538
534
|
.setField('stringField', 'huh2')
|
|
539
535
|
.enforceUpdateAsync();
|
|
540
536
|
|
|
@@ -586,7 +582,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
586
582
|
);
|
|
587
583
|
|
|
588
584
|
await entityMutatorFactory
|
|
589
|
-
.forUpdate(existingEntity, queryContext
|
|
585
|
+
.forUpdate(existingEntity, queryContext)
|
|
590
586
|
.setField('stringField', 'huh2')
|
|
591
587
|
.enforceUpdateAsync();
|
|
592
588
|
|
|
@@ -594,7 +590,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
594
590
|
spiedPrivacyPolicy.authorizeUpdateAsync(
|
|
595
591
|
viewerContext,
|
|
596
592
|
anyOfClass(EntityTransactionalQueryContext),
|
|
597
|
-
|
|
593
|
+
deepEqual({ cascadingDeleteCause: null }),
|
|
598
594
|
anyOfClass(TestEntity),
|
|
599
595
|
anything()
|
|
600
596
|
)
|
|
@@ -637,7 +633,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
637
633
|
);
|
|
638
634
|
|
|
639
635
|
await entityMutatorFactory
|
|
640
|
-
.forUpdate(existingEntity, queryContext
|
|
636
|
+
.forUpdate(existingEntity, queryContext)
|
|
641
637
|
.setField('stringField', 'huh2')
|
|
642
638
|
.enforceUpdateAsync();
|
|
643
639
|
|
|
@@ -652,7 +648,11 @@ describe(EntityMutatorFactory, () => {
|
|
|
652
648
|
beforeDelete: false,
|
|
653
649
|
afterDelete: false,
|
|
654
650
|
},
|
|
655
|
-
{
|
|
651
|
+
{
|
|
652
|
+
type: EntityMutationType.UPDATE,
|
|
653
|
+
previousValue: existingEntity,
|
|
654
|
+
cascadingDeleteCause: null,
|
|
655
|
+
}
|
|
656
656
|
);
|
|
657
657
|
});
|
|
658
658
|
it('executes validators', async () => {
|
|
@@ -691,13 +691,14 @@ describe(EntityMutatorFactory, () => {
|
|
|
691
691
|
);
|
|
692
692
|
|
|
693
693
|
await entityMutatorFactory
|
|
694
|
-
.forUpdate(existingEntity, queryContext
|
|
694
|
+
.forUpdate(existingEntity, queryContext)
|
|
695
695
|
.setField('stringField', 'huh2')
|
|
696
696
|
.enforceUpdateAsync();
|
|
697
697
|
|
|
698
698
|
verifyValidatorCounts(viewerContext, validatorSpies, 1, {
|
|
699
699
|
type: EntityMutationType.UPDATE,
|
|
700
700
|
previousValue: existingEntity,
|
|
701
|
+
cascadingDeleteCause: null,
|
|
701
702
|
});
|
|
702
703
|
});
|
|
703
704
|
});
|
|
@@ -727,9 +728,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
727
728
|
);
|
|
728
729
|
expect(existingEntity).toBeTruthy();
|
|
729
730
|
|
|
730
|
-
await entityMutatorFactory
|
|
731
|
-
.forDelete(existingEntity, queryContext, privacyPolicyEvaluationContext)
|
|
732
|
-
.enforceDeleteAsync();
|
|
731
|
+
await entityMutatorFactory.forDelete(existingEntity, queryContext).enforceDeleteAsync();
|
|
733
732
|
|
|
734
733
|
await expect(
|
|
735
734
|
enforceAsyncResult(
|
|
@@ -766,9 +765,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
766
765
|
.loadByIDAsync(id1)
|
|
767
766
|
);
|
|
768
767
|
|
|
769
|
-
await entityMutatorFactory
|
|
770
|
-
.forDelete(existingEntity, queryContext, privacyPolicyEvaluationContext)
|
|
771
|
-
.enforceDeleteAsync();
|
|
768
|
+
await entityMutatorFactory.forDelete(existingEntity, queryContext).enforceDeleteAsync();
|
|
772
769
|
|
|
773
770
|
verify(
|
|
774
771
|
spiedPrivacyPolicy.authorizeDeleteAsync(
|
|
@@ -807,9 +804,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
807
804
|
.loadByIDAsync(id1)
|
|
808
805
|
);
|
|
809
806
|
|
|
810
|
-
await entityMutatorFactory
|
|
811
|
-
.forDelete(existingEntity, queryContext, privacyPolicyEvaluationContext)
|
|
812
|
-
.enforceDeleteAsync();
|
|
807
|
+
await entityMutatorFactory.forDelete(existingEntity, queryContext).enforceDeleteAsync();
|
|
813
808
|
|
|
814
809
|
verifyTriggerCounts(
|
|
815
810
|
viewerContext,
|
|
@@ -852,9 +847,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
852
847
|
.loadByIDAsync(id1)
|
|
853
848
|
);
|
|
854
849
|
|
|
855
|
-
await entityMutatorFactory
|
|
856
|
-
.forDelete(existingEntity, queryContext, privacyPolicyEvaluationContext)
|
|
857
|
-
.enforceDeleteAsync();
|
|
850
|
+
await entityMutatorFactory.forDelete(existingEntity, queryContext).enforceDeleteAsync();
|
|
858
851
|
|
|
859
852
|
verifyValidatorCounts(viewerContext, validatorSpies, 0, {
|
|
860
853
|
type: EntityMutationType.DELETE as any,
|
|
@@ -888,7 +881,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
888
881
|
|
|
889
882
|
await enforceAsyncResult(
|
|
890
883
|
entityMutatorFactory
|
|
891
|
-
.forCreate(viewerContext, queryContext
|
|
884
|
+
.forCreate(viewerContext, queryContext)
|
|
892
885
|
.setField('stringField', 'huh')
|
|
893
886
|
.createAsync()
|
|
894
887
|
);
|
|
@@ -903,7 +896,6 @@ describe(EntityMutatorFactory, () => {
|
|
|
903
896
|
|
|
904
897
|
it('throws error when field not valid', async () => {
|
|
905
898
|
const viewerContext = mock<ViewerContext>();
|
|
906
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
907
899
|
const queryContext = StubQueryContextProvider.getQueryContext();
|
|
908
900
|
const id1 = uuidv4();
|
|
909
901
|
const { entityMutatorFactory } = createEntityMutatorFactory([
|
|
@@ -919,19 +911,19 @@ describe(EntityMutatorFactory, () => {
|
|
|
919
911
|
|
|
920
912
|
await expect(
|
|
921
913
|
entityMutatorFactory
|
|
922
|
-
.forCreate(viewerContext, queryContext
|
|
914
|
+
.forCreate(viewerContext, queryContext)
|
|
923
915
|
.setField('stringField', 10 as any)
|
|
924
916
|
.createAsync()
|
|
925
917
|
).rejects.toThrowError('Entity field not valid: TestEntity (stringField = 10)');
|
|
926
918
|
|
|
927
919
|
const createdEntity = await entityMutatorFactory
|
|
928
|
-
.forCreate(viewerContext, queryContext
|
|
920
|
+
.forCreate(viewerContext, queryContext)
|
|
929
921
|
.setField('stringField', 'hello')
|
|
930
922
|
.enforceCreateAsync();
|
|
931
923
|
|
|
932
924
|
await expect(
|
|
933
925
|
entityMutatorFactory
|
|
934
|
-
.forUpdate(createdEntity, queryContext
|
|
926
|
+
.forUpdate(createdEntity, queryContext)
|
|
935
927
|
.setField('stringField', 10 as any)
|
|
936
928
|
.updateAsync()
|
|
937
929
|
).rejects.toThrowError('Entity field not valid: TestEntity (stringField = 10)');
|
|
@@ -939,7 +931,6 @@ describe(EntityMutatorFactory, () => {
|
|
|
939
931
|
|
|
940
932
|
it('returns error result when not authorized to create', async () => {
|
|
941
933
|
const viewerContext = instance(mock(ViewerContext));
|
|
942
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
943
934
|
const queryContext = StubQueryContextProvider.getQueryContext();
|
|
944
935
|
const privacyPolicyMock = mock(SimpleTestEntityPrivacyPolicy);
|
|
945
936
|
const databaseAdapter = instance(mock<EntityDatabaseAdapter<SimpleTestFields>>());
|
|
@@ -999,7 +990,7 @@ describe(EntityMutatorFactory, () => {
|
|
|
999
990
|
);
|
|
1000
991
|
|
|
1001
992
|
const entityCreateResult = await entityMutatorFactory
|
|
1002
|
-
.forCreate(viewerContext, queryContext
|
|
993
|
+
.forCreate(viewerContext, queryContext)
|
|
1003
994
|
.createAsync();
|
|
1004
995
|
expect(entityCreateResult.ok).toBe(false);
|
|
1005
996
|
expect(entityCreateResult.reason).toEqual(rejectionError);
|
|
@@ -1011,14 +1002,14 @@ describe(EntityMutatorFactory, () => {
|
|
|
1011
1002
|
});
|
|
1012
1003
|
|
|
1013
1004
|
const entityUpdateResult = await entityMutatorFactory
|
|
1014
|
-
.forUpdate(fakeEntity, queryContext
|
|
1005
|
+
.forUpdate(fakeEntity, queryContext)
|
|
1015
1006
|
.updateAsync();
|
|
1016
1007
|
expect(entityUpdateResult.ok).toBe(false);
|
|
1017
1008
|
expect(entityUpdateResult.reason).toEqual(rejectionError);
|
|
1018
1009
|
expect(entityUpdateResult.value).toBe(undefined);
|
|
1019
1010
|
|
|
1020
1011
|
const entityDeleteResult = await entityMutatorFactory
|
|
1021
|
-
.forDelete(fakeEntity, queryContext
|
|
1012
|
+
.forDelete(fakeEntity, queryContext)
|
|
1022
1013
|
.deleteAsync();
|
|
1023
1014
|
expect(entityDeleteResult.ok).toBe(false);
|
|
1024
1015
|
expect(entityDeleteResult.reason).toEqual(rejectionError);
|
|
@@ -1027,7 +1018,6 @@ describe(EntityMutatorFactory, () => {
|
|
|
1027
1018
|
|
|
1028
1019
|
it('throws error when db adapter throws', async () => {
|
|
1029
1020
|
const viewerContext = instance(mock(ViewerContext));
|
|
1030
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
1031
1021
|
const queryContext = StubQueryContextProvider.getQueryContext();
|
|
1032
1022
|
const privacyPolicy = instance(mock(SimpleTestEntityPrivacyPolicy));
|
|
1033
1023
|
const databaseAdapterMock = mock<EntityDatabaseAdapter<SimpleTestFields>>();
|
|
@@ -1083,48 +1073,37 @@ describe(EntityMutatorFactory, () => {
|
|
|
1083
1073
|
});
|
|
1084
1074
|
|
|
1085
1075
|
await expect(
|
|
1086
|
-
entityMutatorFactory
|
|
1087
|
-
.forCreate(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
|
1088
|
-
.createAsync()
|
|
1076
|
+
entityMutatorFactory.forCreate(viewerContext, queryContext).createAsync()
|
|
1089
1077
|
).rejects.toEqual(rejectionError);
|
|
1090
1078
|
await expect(
|
|
1091
|
-
entityMutatorFactory
|
|
1092
|
-
.forUpdate(fakeEntity, queryContext, privacyPolicyEvaluationContext)
|
|
1093
|
-
.updateAsync()
|
|
1079
|
+
entityMutatorFactory.forUpdate(fakeEntity, queryContext).updateAsync()
|
|
1094
1080
|
).rejects.toEqual(rejectionError);
|
|
1095
1081
|
await expect(
|
|
1096
|
-
entityMutatorFactory
|
|
1097
|
-
.forDelete(fakeEntity, queryContext, privacyPolicyEvaluationContext)
|
|
1098
|
-
.deleteAsync()
|
|
1082
|
+
entityMutatorFactory.forDelete(fakeEntity, queryContext).deleteAsync()
|
|
1099
1083
|
).rejects.toEqual(rejectionError);
|
|
1100
1084
|
});
|
|
1101
1085
|
|
|
1102
1086
|
it('records metrics appropriately', async () => {
|
|
1103
1087
|
const viewerContext = mock<ViewerContext>();
|
|
1104
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
1105
1088
|
const queryContext = StubQueryContextProvider.getQueryContext();
|
|
1106
1089
|
const { entityMutatorFactory, metricsAdapter } = createEntityMutatorFactory([]);
|
|
1107
1090
|
const spiedMetricsAdapter = spy(metricsAdapter);
|
|
1108
1091
|
|
|
1109
1092
|
const newEntity = await enforceAsyncResult(
|
|
1110
1093
|
entityMutatorFactory
|
|
1111
|
-
.forCreate(viewerContext, queryContext
|
|
1094
|
+
.forCreate(viewerContext, queryContext)
|
|
1112
1095
|
.setField('stringField', 'huh')
|
|
1113
1096
|
.createAsync()
|
|
1114
1097
|
);
|
|
1115
1098
|
|
|
1116
1099
|
await enforceAsyncResult(
|
|
1117
1100
|
entityMutatorFactory
|
|
1118
|
-
.forUpdate(newEntity, queryContext
|
|
1101
|
+
.forUpdate(newEntity, queryContext)
|
|
1119
1102
|
.setField('stringField', 'wat')
|
|
1120
1103
|
.updateAsync()
|
|
1121
1104
|
);
|
|
1122
1105
|
|
|
1123
|
-
await enforceAsyncResult(
|
|
1124
|
-
entityMutatorFactory
|
|
1125
|
-
.forDelete(newEntity, queryContext, privacyPolicyEvaluationContext)
|
|
1126
|
-
.deleteAsync()
|
|
1127
|
-
);
|
|
1106
|
+
await enforceAsyncResult(entityMutatorFactory.forDelete(newEntity, queryContext).deleteAsync());
|
|
1128
1107
|
|
|
1129
1108
|
verify(
|
|
1130
1109
|
spiedMetricsAdapter.logMutatorMutationEvent(
|
|
@@ -213,7 +213,7 @@ describe('EntityEdgeDeletionBehavior.SET_NULL', () => {
|
|
|
213
213
|
describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => {
|
|
214
214
|
it('invalidates the cache', async () => {
|
|
215
215
|
const { CategoryEntity } = makeEntityClass(
|
|
216
|
-
EntityEdgeDeletionBehavior.
|
|
216
|
+
EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY
|
|
217
217
|
);
|
|
218
218
|
|
|
219
219
|
const companionProvider = createUnitTestEntityCompanionProvider();
|
|
@@ -283,7 +283,7 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => {
|
|
|
283
283
|
|
|
284
284
|
it('handles cycles', async () => {
|
|
285
285
|
const { CategoryEntity } = makeEntityClass(
|
|
286
|
-
EntityEdgeDeletionBehavior.
|
|
286
|
+
EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY
|
|
287
287
|
);
|
|
288
288
|
|
|
289
289
|
const companionProvider = createUnitTestEntityCompanionProvider();
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { mock, instance, verify } from 'ts-mockito';
|
|
2
2
|
|
|
3
3
|
import EntityMutatorFactory from '../EntityMutatorFactory';
|
|
4
|
-
import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
|
|
5
4
|
import { EntityQueryContext } from '../EntityQueryContext';
|
|
6
5
|
import ViewerContext from '../ViewerContext';
|
|
7
6
|
import ViewerScopedEntityMutatorFactory from '../ViewerScopedEntityMutatorFactory';
|
|
@@ -10,7 +9,6 @@ import TestEntity, { TestFields, TestEntityPrivacyPolicy } from '../testfixtures
|
|
|
10
9
|
describe(ViewerScopedEntityMutatorFactory, () => {
|
|
11
10
|
it('correctly scopes viewer to entity mutations', async () => {
|
|
12
11
|
const viewerContext = instance(mock(ViewerContext));
|
|
13
|
-
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
|
|
14
12
|
const queryContext = instance(mock(EntityQueryContext));
|
|
15
13
|
const baseMutatorFactory =
|
|
16
14
|
mock<
|
|
@@ -27,10 +25,8 @@ describe(ViewerScopedEntityMutatorFactory, () => {
|
|
|
27
25
|
keyof TestFields
|
|
28
26
|
>(baseMutatorFactoryInstance, viewerContext);
|
|
29
27
|
|
|
30
|
-
viewerScopedEntityLoader.forCreate(queryContext
|
|
28
|
+
viewerScopedEntityLoader.forCreate(queryContext);
|
|
31
29
|
|
|
32
|
-
verify(
|
|
33
|
-
baseMutatorFactory.forCreate(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
|
34
|
-
).once();
|
|
30
|
+
verify(baseMutatorFactory.forCreate(viewerContext, queryContext)).once();
|
|
35
31
|
});
|
|
36
32
|
});
|
|
@@ -34,6 +34,6 @@ export default class EntityInvalidFieldValueError<
|
|
|
34
34
|
fieldName: N,
|
|
35
35
|
fieldValue?: TFields[N]
|
|
36
36
|
) {
|
|
37
|
-
super(`Entity field not valid: ${entityClass.name} (${fieldName} = ${fieldValue})`);
|
|
37
|
+
super(`Entity field not valid: ${entityClass.name} (${String(fieldName)} = ${fieldValue})`);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -34,6 +34,6 @@ export default class EntityNotFoundError<
|
|
|
34
34
|
fieldName: N,
|
|
35
35
|
fieldValue: TFields[N]
|
|
36
36
|
) {
|
|
37
|
-
super(`Entity not found: ${entityClass.name} (${fieldName} = ${fieldValue})`);
|
|
37
|
+
super(`Entity not found: ${entityClass.name} (${String(fieldName)} = ${fieldValue})`);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
timeAndLogLoadEventAsync,
|
|
12
12
|
timeAndLogLoadMapEventAsync,
|
|
13
13
|
} from '../metrics/EntityMetricsUtils';
|
|
14
|
-
import IEntityMetricsAdapter, {
|
|
14
|
+
import IEntityMetricsAdapter, {
|
|
15
|
+
EntityMetricsLoadType,
|
|
16
|
+
IncrementLoadCountEventType,
|
|
17
|
+
} from '../metrics/IEntityMetricsAdapter';
|
|
15
18
|
import { computeIfAbsent, zipToMap } from '../utils/collections/maps';
|
|
16
19
|
import ReadThroughEntityCache from './ReadThroughEntityCache';
|
|
17
20
|
|
|
@@ -57,7 +60,8 @@ export default class EntityDataManager<TFields> {
|
|
|
57
60
|
fieldName: N,
|
|
58
61
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
|
59
62
|
): Promise<ReadonlyMap<NonNullable<TFields[N]>, readonly Readonly<TFields>[]>> {
|
|
60
|
-
this.metricsAdapter.
|
|
63
|
+
this.metricsAdapter.incrementDataManagerLoadCount({
|
|
64
|
+
type: IncrementLoadCountEventType.CACHE,
|
|
61
65
|
fieldValueCount: fieldValues.length,
|
|
62
66
|
entityClassName: this.entityClassName,
|
|
63
67
|
});
|
|
@@ -65,7 +69,8 @@ export default class EntityDataManager<TFields> {
|
|
|
65
69
|
fieldName,
|
|
66
70
|
fieldValues,
|
|
67
71
|
async (fetcherValues) => {
|
|
68
|
-
this.metricsAdapter.
|
|
72
|
+
this.metricsAdapter.incrementDataManagerLoadCount({
|
|
73
|
+
type: IncrementLoadCountEventType.DATABASE,
|
|
69
74
|
fieldValueCount: fieldValues.length,
|
|
70
75
|
entityClassName: this.entityClassName,
|
|
71
76
|
});
|
|
@@ -108,7 +113,9 @@ export default class EntityDataManager<TFields> {
|
|
|
108
113
|
);
|
|
109
114
|
if (nullOrUndefinedValueIndex >= 0) {
|
|
110
115
|
throw new Error(
|
|
111
|
-
`Invalid load: ${this.entityClassName} (${fieldName} = ${
|
|
116
|
+
`Invalid load: ${this.entityClassName} (${String(fieldName)} = ${
|
|
117
|
+
fieldValues[nullOrUndefinedValueIndex]
|
|
118
|
+
})`
|
|
112
119
|
);
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -117,7 +124,8 @@ export default class EntityDataManager<TFields> {
|
|
|
117
124
|
return await this.databaseAdapter.fetchManyWhereAsync(queryContext, fieldName, fieldValues);
|
|
118
125
|
}
|
|
119
126
|
|
|
120
|
-
this.metricsAdapter.
|
|
127
|
+
this.metricsAdapter.incrementDataManagerLoadCount({
|
|
128
|
+
type: IncrementLoadCountEventType.DATALOADER,
|
|
121
129
|
fieldValueCount: fieldValues.length,
|
|
122
130
|
entityClassName: this.entityClassName,
|
|
123
131
|
});
|
|
@@ -24,7 +24,7 @@ export const getDatabaseFieldForEntityField = <TFields>(
|
|
|
24
24
|
entityField: keyof TFields
|
|
25
25
|
): string => {
|
|
26
26
|
const databaseField = entityConfiguration.entityToDBFieldsKeyMapping.get(entityField);
|
|
27
|
-
invariant(databaseField, `database field mapping missing for ${entityField}`);
|
|
27
|
+
invariant(databaseField, `database field mapping missing for ${String(entityField)}`);
|
|
28
28
|
return databaseField!;
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -102,7 +102,9 @@ export default class ReadThroughEntityCache<TFields> {
|
|
|
102
102
|
// TODO(wschurman): emit or throw here since console may not be available
|
|
103
103
|
// eslint-disable-next-line no-console
|
|
104
104
|
console.warn(
|
|
105
|
-
`unique key ${fieldName} in ${
|
|
105
|
+
`unique key ${String(fieldName)} in ${
|
|
106
|
+
this.entityConfiguration.tableName
|
|
107
|
+
} returned multiple rows for ${fieldValue}`
|
|
106
108
|
);
|
|
107
109
|
continue;
|
|
108
110
|
}
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
when,
|
|
5
5
|
anything,
|
|
6
6
|
verify,
|
|
7
|
-
anyNumber,
|
|
8
7
|
objectContaining,
|
|
9
8
|
spy,
|
|
10
9
|
anyString,
|
|
@@ -13,7 +12,10 @@ import {
|
|
|
13
12
|
} from 'ts-mockito';
|
|
14
13
|
|
|
15
14
|
import EntityDatabaseAdapter from '../../EntityDatabaseAdapter';
|
|
16
|
-
import IEntityMetricsAdapter, {
|
|
15
|
+
import IEntityMetricsAdapter, {
|
|
16
|
+
EntityMetricsLoadType,
|
|
17
|
+
IncrementLoadCountEventType,
|
|
18
|
+
} from '../../metrics/IEntityMetricsAdapter';
|
|
17
19
|
import NoOpEntityMetricsAdapter from '../../metrics/NoOpEntityMetricsAdapter';
|
|
18
20
|
import TestEntity, { testEntityConfiguration, TestFields } from '../../testfixtures/TestEntity';
|
|
19
21
|
import {
|
|
@@ -519,24 +521,27 @@ describe(EntityDataManager, () => {
|
|
|
519
521
|
).once();
|
|
520
522
|
|
|
521
523
|
verify(
|
|
522
|
-
metricsAdapterMock.
|
|
524
|
+
metricsAdapterMock.incrementDataManagerLoadCount(
|
|
523
525
|
deepEqual({
|
|
526
|
+
type: IncrementLoadCountEventType.DATALOADER,
|
|
524
527
|
fieldValueCount: 1,
|
|
525
528
|
entityClassName: TestEntity.name,
|
|
526
529
|
})
|
|
527
530
|
)
|
|
528
531
|
).once();
|
|
529
532
|
verify(
|
|
530
|
-
metricsAdapterMock.
|
|
533
|
+
metricsAdapterMock.incrementDataManagerLoadCount(
|
|
531
534
|
deepEqual({
|
|
535
|
+
type: IncrementLoadCountEventType.CACHE,
|
|
532
536
|
fieldValueCount: 1,
|
|
533
537
|
entityClassName: TestEntity.name,
|
|
534
538
|
})
|
|
535
539
|
)
|
|
536
540
|
).once();
|
|
537
541
|
verify(
|
|
538
|
-
metricsAdapterMock.
|
|
542
|
+
metricsAdapterMock.incrementDataManagerLoadCount(
|
|
539
543
|
deepEqual({
|
|
544
|
+
type: IncrementLoadCountEventType.DATABASE,
|
|
540
545
|
fieldValueCount: 1,
|
|
541
546
|
entityClassName: TestEntity.name,
|
|
542
547
|
})
|
|
@@ -565,9 +570,7 @@ describe(EntityDataManager, () => {
|
|
|
565
570
|
)
|
|
566
571
|
).once();
|
|
567
572
|
|
|
568
|
-
verify(metricsAdapterMock.
|
|
569
|
-
verify(metricsAdapterMock.incrementDataManagerCacheLoadCount(anyNumber())).never();
|
|
570
|
-
verify(metricsAdapterMock.incrementDataManagerDatabaseLoadCount(anyNumber())).never();
|
|
573
|
+
verify(metricsAdapterMock.incrementDataManagerLoadCount(anything())).never();
|
|
571
574
|
});
|
|
572
575
|
|
|
573
576
|
it('throws when a load-by value is null or undefined', async () => {
|
|
@@ -9,10 +9,28 @@ export enum EntityMetricsLoadType {
|
|
|
9
9
|
LOAD_MANY_RAW,
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Event about a single call to an {@link EntityLoader} method.
|
|
14
|
+
*/
|
|
12
15
|
export interface EntityMetricsLoadEvent {
|
|
16
|
+
/**
|
|
17
|
+
* {@link EntityMetricsLoadType} for this load.
|
|
18
|
+
*/
|
|
13
19
|
type: EntityMetricsLoadType;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Class name of the {@link Entity} being loaded.
|
|
23
|
+
*/
|
|
14
24
|
entityClassName: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Total duration of this load, including fetch and construction of entities.
|
|
28
|
+
*/
|
|
15
29
|
duration: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Number of entities returned for this load.
|
|
33
|
+
*/
|
|
16
34
|
count: number;
|
|
17
35
|
}
|
|
18
36
|
|
|
@@ -23,13 +41,57 @@ export enum EntityMetricsMutationType {
|
|
|
23
41
|
}
|
|
24
42
|
|
|
25
43
|
export interface EntityMetricsMutationEvent {
|
|
44
|
+
/**
|
|
45
|
+
* {@link EntityMetricsMutationType} for this mutation.
|
|
46
|
+
*/
|
|
26
47
|
type: EntityMetricsMutationType;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Class name of the {@link Entity} being mutated.
|
|
51
|
+
*/
|
|
27
52
|
entityClassName: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Total duration of this mutation.
|
|
56
|
+
*/
|
|
28
57
|
duration: number;
|
|
29
58
|
}
|
|
30
59
|
|
|
60
|
+
export enum IncrementLoadCountEventType {
|
|
61
|
+
/**
|
|
62
|
+
* Type for when a dataloader load is initiated via the standard load methods
|
|
63
|
+
* since all loads go through a dataloader.
|
|
64
|
+
*/
|
|
65
|
+
DATALOADER,
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Type for when a cache load is initiated due to a dataloader miss.
|
|
69
|
+
*/
|
|
70
|
+
CACHE,
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Type for when a database load is initiated due to a dataloader and cache miss, when an entity query doesn't support caching, or during a transaction.
|
|
74
|
+
*/
|
|
75
|
+
DATABASE,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Event used to record dataloader, cache, and database load counts in {@link EntityDataManager}.
|
|
80
|
+
*/
|
|
31
81
|
export interface IncrementLoadCountEvent {
|
|
82
|
+
/**
|
|
83
|
+
* Type of this event.
|
|
84
|
+
*/
|
|
85
|
+
type: IncrementLoadCountEventType;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Number of field values being loaded for this call.
|
|
89
|
+
*/
|
|
32
90
|
fieldValueCount: number;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Class name of the {@link Entity} being loaded.
|
|
94
|
+
*/
|
|
33
95
|
entityClassName: string;
|
|
34
96
|
}
|
|
35
97
|
|
|
@@ -38,7 +100,13 @@ export enum EntityMetricsAuthorizationResult {
|
|
|
38
100
|
ALLOW,
|
|
39
101
|
}
|
|
40
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Event used to record a singe {@link EntityPrivacyPolicy} authorization.
|
|
105
|
+
*/
|
|
41
106
|
export interface EntityMetricsAuthorizationEvent {
|
|
107
|
+
/**
|
|
108
|
+
* Class name of the {@link Entity} being authorized.
|
|
109
|
+
*/
|
|
42
110
|
entityClassName: string;
|
|
43
111
|
action: EntityAuthorizationAction;
|
|
44
112
|
evaluationResult: EntityMetricsAuthorizationResult;
|
|
@@ -69,25 +137,10 @@ export default interface IEntityMetricsAdapter {
|
|
|
69
137
|
logMutatorMutationEvent(mutationEvent: EntityMetricsMutationEvent): void;
|
|
70
138
|
|
|
71
139
|
/**
|
|
72
|
-
* Called when a dataloader load is initiated via the standard
|
|
73
|
-
* load methods (not equality conjunction or raw).
|
|
74
|
-
*
|
|
75
|
-
*/
|
|
76
|
-
incrementDataManagerDataloaderLoadCount(incrementLoadCountEvent: IncrementLoadCountEvent): void;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Called when a cache load is initiated via the standard
|
|
80
|
-
* load methods (not equality conjunction or raw). Occurs upon a dataloader
|
|
81
|
-
* miss.
|
|
82
|
-
* @param fieldValueCount - count of field values being loaded for a field
|
|
83
|
-
*/
|
|
84
|
-
incrementDataManagerCacheLoadCount(incrementLoadCountEvent: IncrementLoadCountEvent): void;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Called when a database load is initiated via the standard
|
|
88
|
-
* load methods (not equality conjunction or raw). Occurs upon a cache
|
|
89
|
-
* miss or when fetching an uncacheable field.
|
|
140
|
+
* Called when a dataloader, cache, or database load is initiated via the standard
|
|
141
|
+
* load methods (not equality conjunction or raw). Most commonly used for logging
|
|
142
|
+
* a waterfall to determine dataloader and cache hit rates and ratios.
|
|
90
143
|
* @param fieldValueCount - count of field values being loaded for a field
|
|
91
144
|
*/
|
|
92
|
-
|
|
145
|
+
incrementDataManagerLoadCount(incrementLoadCountEvent: IncrementLoadCountEvent): void;
|
|
93
146
|
}
|
|
@@ -9,9 +9,5 @@ export default class NoOpEntityMetricsAdapter implements IEntityMetricsAdapter {
|
|
|
9
9
|
logAuthorizationEvent(_authorizationEvent: EntityMetricsAuthorizationEvent): void {}
|
|
10
10
|
logDataManagerLoadEvent(_loadEvent: EntityMetricsLoadEvent): void {}
|
|
11
11
|
logMutatorMutationEvent(_mutationEvent: EntityMetricsMutationEvent): void {}
|
|
12
|
-
|
|
13
|
-
_incrementLoadCountEvent: IncrementLoadCountEvent
|
|
14
|
-
): void {}
|
|
15
|
-
incrementDataManagerCacheLoadCount(_incrementLoadCountEvent: IncrementLoadCountEvent): void {}
|
|
16
|
-
incrementDataManagerDatabaseLoadCount(_incrementLoadCountEvent: IncrementLoadCountEvent): void {}
|
|
12
|
+
incrementDataManagerLoadCount(_incrementLoadCountEvent: IncrementLoadCountEvent): void {}
|
|
17
13
|
}
|
|
@@ -158,7 +158,10 @@ export default class StubDatabaseAdapter<T> extends EntityDatabaseAdapter<T> {
|
|
|
158
158
|
|
|
159
159
|
private generateRandomID(): any {
|
|
160
160
|
const idSchemaField = this.entityConfiguration2.schema.get(this.entityConfiguration2.idField);
|
|
161
|
-
invariant(
|
|
161
|
+
invariant(
|
|
162
|
+
idSchemaField,
|
|
163
|
+
`No schema field found for ${String(this.entityConfiguration2.idField)}`
|
|
164
|
+
);
|
|
162
165
|
if (idSchemaField instanceof StringField) {
|
|
163
166
|
return uuidv4();
|
|
164
167
|
} else if (idSchemaField instanceof IntField) {
|