@expo/entity 0.35.0 → 0.36.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/AuthorizationResultBasedEntityLoader.d.ts +128 -0
- package/build/AuthorizationResultBasedEntityLoader.js +196 -0
- package/build/AuthorizationResultBasedEntityLoader.js.map +1 -0
- package/build/ComposedEntityCacheAdapter.js +1 -0
- package/build/ComposedEntityCacheAdapter.js.map +1 -1
- package/build/ComposedSecondaryEntityCache.js +1 -0
- package/build/ComposedSecondaryEntityCache.js.map +1 -1
- package/build/EnforcingEntityLoader.d.ts +5 -4
- package/build/EnforcingEntityLoader.js +4 -2
- package/build/EnforcingEntityLoader.js.map +1 -1
- package/build/Entity.d.ts +0 -27
- package/build/Entity.js +0 -50
- package/build/Entity.js.map +1 -1
- package/build/EntityAssociationLoader.d.ts +1 -1
- package/build/EntityAssociationLoader.js +17 -8
- package/build/EntityAssociationLoader.js.map +1 -1
- package/build/EntityCompanion.js +9 -1
- package/build/EntityCompanion.js.map +1 -1
- package/build/EntityCompanionProvider.d.ts +3 -1
- package/build/EntityCompanionProvider.js +10 -4
- package/build/EntityCompanionProvider.js.map +1 -1
- package/build/EntityConfiguration.d.ts +2 -1
- package/build/EntityConfiguration.js +19 -1
- package/build/EntityConfiguration.js.map +1 -1
- package/build/EntityDatabaseAdapter.d.ts +2 -2
- package/build/EntityDatabaseAdapter.js +5 -3
- package/build/EntityDatabaseAdapter.js.map +1 -1
- package/build/EntityFieldDefinition.d.ts +21 -10
- package/build/EntityFieldDefinition.js +8 -9
- package/build/EntityFieldDefinition.js.map +1 -1
- package/build/EntityFields.d.ts +10 -0
- package/build/EntityFields.js +15 -1
- package/build/EntityFields.js.map +1 -1
- package/build/EntityLoader.d.ts +12 -125
- package/build/EntityLoader.js +24 -239
- package/build/EntityLoader.js.map +1 -1
- package/build/EntityLoaderFactory.d.ts +1 -1
- package/build/EntityLoaderFactory.js +3 -0
- package/build/EntityLoaderFactory.js.map +1 -1
- package/build/EntityLoaderUtils.d.ts +58 -0
- package/build/EntityLoaderUtils.js +109 -0
- package/build/EntityLoaderUtils.js.map +1 -0
- package/build/EntityMutator.d.ts +1 -0
- package/build/EntityMutator.js +71 -56
- package/build/EntityMutator.js.map +1 -1
- package/build/EntityMutatorFactory.js +9 -0
- package/build/EntityMutatorFactory.js.map +1 -1
- package/build/EntityPrivacyPolicy.d.ts +11 -5
- package/build/EntityPrivacyPolicy.js +5 -7
- package/build/EntityPrivacyPolicy.js.map +1 -1
- package/build/EntityQueryContext.d.ts +2 -1
- package/build/EntityQueryContext.js +11 -6
- package/build/EntityQueryContext.js.map +1 -1
- package/build/EntitySecondaryCacheLoader.js +5 -1
- package/build/EntitySecondaryCacheLoader.js.map +1 -1
- package/build/GenericEntityCacheAdapter.js +1 -0
- package/build/GenericEntityCacheAdapter.js.map +1 -1
- package/build/GenericSecondaryEntityCache.js +2 -0
- package/build/GenericSecondaryEntityCache.js.map +1 -1
- package/build/IEntityCacheAdapterProvider.d.ts +1 -1
- package/build/IEntityDatabaseAdapterProvider.d.ts +1 -1
- package/build/ReadonlyEntity.js +5 -1
- package/build/ReadonlyEntity.js.map +1 -1
- package/build/ViewerContext.js +2 -0
- package/build/ViewerContext.js.map +1 -1
- package/build/ViewerScopedEntityCompanion.js +2 -0
- package/build/ViewerScopedEntityCompanion.js.map +1 -1
- package/build/ViewerScopedEntityCompanionProvider.d.ts +0 -1
- package/build/ViewerScopedEntityCompanionProvider.js +2 -1
- package/build/ViewerScopedEntityCompanionProvider.js.map +1 -1
- package/build/ViewerScopedEntityLoaderFactory.d.ts +1 -1
- package/build/ViewerScopedEntityLoaderFactory.js +2 -0
- package/build/ViewerScopedEntityLoaderFactory.js.map +1 -1
- package/build/ViewerScopedEntityMutatorFactory.js +2 -0
- package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
- package/build/__tests__/ComposedCacheAdapter-test.js +2 -0
- package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -1
- package/build/__tests__/ComposedSecondaryEntityCache-test.js +1 -0
- package/build/__tests__/ComposedSecondaryEntityCache-test.js.map +1 -1
- package/build/__tests__/EnforcingEntityLoader-test.js +101 -113
- package/build/__tests__/EnforcingEntityLoader-test.js.map +1 -1
- package/build/__tests__/Entity-test.js +0 -132
- package/build/__tests__/Entity-test.js.map +1 -1
- package/build/__tests__/EntityAssociationLoader-test.js +6 -2
- package/build/__tests__/EntityAssociationLoader-test.js.map +1 -1
- package/build/__tests__/EntityCommonUseCases-test.js +24 -22
- package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
- package/build/__tests__/EntityCompanion-test.js +26 -3
- package/build/__tests__/EntityCompanion-test.js.map +1 -1
- package/build/__tests__/EntityConfiguration-test.js +103 -0
- package/build/__tests__/EntityConfiguration-test.js.map +1 -0
- package/build/__tests__/EntityDatabaseAdapter-test.js +6 -0
- package/build/__tests__/EntityDatabaseAdapter-test.js.map +1 -1
- package/build/__tests__/EntityEdges-test.js +61 -20
- package/build/__tests__/EntityEdges-test.js.map +1 -1
- package/build/__tests__/EntityFields-test.js +6 -0
- package/build/__tests__/EntityFields-test.js.map +1 -1
- package/build/__tests__/EntityLoader-constructor-test.js +16 -17
- package/build/__tests__/EntityLoader-constructor-test.js.map +1 -1
- package/build/__tests__/EntityLoader-test.js +74 -22
- package/build/__tests__/EntityLoader-test.js.map +1 -1
- package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js +12 -15
- package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js.map +1 -1
- package/build/__tests__/EntityMutator-test.js +54 -9
- package/build/__tests__/EntityMutator-test.js.map +1 -1
- package/build/__tests__/EntityPrivacyPolicy-test.js +77 -59
- package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
- package/build/__tests__/EntityQueryContext-test.js +9 -0
- package/build/__tests__/EntityQueryContext-test.js.map +1 -1
- package/build/__tests__/EntitySelfReferentialEdges-test.js +42 -25
- package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
- package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js +20 -18
- package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js.map +1 -1
- package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js +12 -15
- package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js.map +1 -1
- package/build/entityUtils.d.ts +1 -1
- package/build/entityUtils.js.map +1 -1
- package/build/errors/EntityCacheAdapterError.js +2 -5
- package/build/errors/EntityCacheAdapterError.js.map +1 -1
- package/build/errors/EntityDatabaseAdapterError.js +14 -35
- package/build/errors/EntityDatabaseAdapterError.js.map +1 -1
- package/build/errors/EntityError.js +1 -0
- package/build/errors/EntityError.js.map +1 -1
- package/build/errors/EntityInvalidFieldValueError.js +2 -2
- package/build/errors/EntityInvalidFieldValueError.js.map +1 -1
- package/build/errors/EntityNotAuthorizedError.js +3 -2
- package/build/errors/EntityNotAuthorizedError.js.map +1 -1
- package/build/errors/EntityNotFoundError.js +2 -2
- package/build/errors/EntityNotFoundError.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/internal/EntityDataManager.d.ts +1 -1
- package/build/internal/EntityDataManager.js +6 -1
- package/build/internal/EntityDataManager.js.map +1 -1
- package/build/internal/EntityFieldTransformationUtils.d.ts +5 -5
- package/build/internal/EntityFieldTransformationUtils.js +5 -8
- package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
- package/build/internal/EntityTableDataCoordinator.d.ts +1 -1
- package/build/internal/EntityTableDataCoordinator.js +5 -0
- package/build/internal/EntityTableDataCoordinator.js.map +1 -1
- package/build/internal/ReadThroughEntityCache.d.ts +1 -1
- package/build/internal/ReadThroughEntityCache.js +2 -0
- package/build/internal/ReadThroughEntityCache.js.map +1 -1
- package/build/internal/__tests__/EntityFieldTransformationUtils-test.js +6 -2
- package/build/internal/__tests__/EntityFieldTransformationUtils-test.js.map +1 -1
- package/build/internal/__tests__/ReadThroughEntityCache-test.js +33 -0
- package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
- package/build/metrics/IEntityMetricsAdapter.d.ts +1 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.d.ts +1 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.d.ts +1 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.d.ts +1 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
- package/build/rules/PrivacyPolicyRule.d.ts +1 -1
- package/build/rules/PrivacyPolicyRule.js.map +1 -1
- package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
- package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
- package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
- package/build/testfixtures/DateIDTestEntity.js +12 -15
- package/build/testfixtures/DateIDTestEntity.js.map +1 -1
- package/build/testfixtures/SimpleTestEntity.js +12 -15
- package/build/testfixtures/SimpleTestEntity.js.map +1 -1
- package/build/testfixtures/TestEntity.js +12 -15
- package/build/testfixtures/TestEntity.js.map +1 -1
- package/build/testfixtures/TestEntity2.js +12 -15
- package/build/testfixtures/TestEntity2.js.map +1 -1
- package/build/testfixtures/TestEntityNumberKey.js +12 -15
- package/build/testfixtures/TestEntityNumberKey.js.map +1 -1
- package/build/testfixtures/TestEntityWithMutationTriggers.d.ts +36 -0
- package/build/testfixtures/TestEntityWithMutationTriggers.js +82 -0
- package/build/testfixtures/TestEntityWithMutationTriggers.js.map +1 -0
- package/build/utils/EntityPrivacyUtils.d.ts +34 -0
- package/build/utils/EntityPrivacyUtils.js +160 -0
- package/build/utils/EntityPrivacyUtils.js.map +1 -0
- package/build/utils/__tests__/EntityPrivacyUtils-test.d.ts +1 -0
- package/build/utils/__tests__/EntityPrivacyUtils-test.js +395 -0
- package/build/utils/__tests__/EntityPrivacyUtils-test.js.map +1 -0
- package/build/utils/__tests__/mergeEntityMutationTriggerConfigurations-test.d.ts +1 -0
- package/build/utils/__tests__/mergeEntityMutationTriggerConfigurations-test.js +26 -0
- package/build/utils/__tests__/mergeEntityMutationTriggerConfigurations-test.js.map +1 -0
- package/build/utils/collections/maps.js.map +1 -1
- package/build/utils/mergeEntityMutationTriggerConfigurations.d.ts +4 -0
- package/build/utils/mergeEntityMutationTriggerConfigurations.js +28 -0
- package/build/utils/mergeEntityMutationTriggerConfigurations.js.map +1 -0
- package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +1 -1
- package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
- package/build/utils/testing/StubCacheAdapter.d.ts +3 -3
- package/build/utils/testing/StubCacheAdapter.js +3 -3
- package/build/utils/testing/StubCacheAdapter.js.map +1 -1
- package/build/utils/testing/StubDatabaseAdapter.d.ts +2 -2
- package/build/utils/testing/StubDatabaseAdapter.js +4 -2
- package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
- package/build/utils/testing/StubDatabaseAdapterProvider.d.ts +1 -1
- package/build/utils/testing/StubDatabaseAdapterProvider.js +1 -3
- package/build/utils/testing/StubDatabaseAdapterProvider.js.map +1 -1
- package/build/utils/testing/__tests__/PrivacyPolicyRuleTestUtils-test.d.ts +1 -0
- package/build/utils/testing/__tests__/PrivacyPolicyRuleTestUtils-test.js +42 -0
- package/build/utils/testing/__tests__/PrivacyPolicyRuleTestUtils-test.js.map +1 -0
- package/build/utils/testing/__tests__/StubDatabaseAdapter-test.js +53 -0
- package/build/utils/testing/__tests__/StubDatabaseAdapter-test.js.map +1 -1
- package/build/utils/testing/describeFieldTestCase.js.map +1 -1
- package/package.json +4 -3
- package/src/AuthorizationResultBasedEntityLoader.ts +297 -0
- package/src/ComposedEntityCacheAdapter.ts +6 -6
- package/src/ComposedSecondaryEntityCache.ts +8 -8
- package/src/EnforcingEntityLoader.ts +20 -19
- package/src/Entity.ts +11 -126
- package/src/EntityAssociationLoader.ts +40 -41
- package/src/EntityCompanion.ts +8 -4
- package/src/EntityCompanionProvider.ts +24 -16
- package/src/EntityConfiguration.ts +18 -7
- package/src/EntityDatabaseAdapter.ts +41 -41
- package/src/EntityFieldDefinition.ts +28 -18
- package/src/EntityFields.ts +15 -0
- package/src/EntityLoader.ts +63 -357
- package/src/EntityLoaderFactory.ts +10 -4
- package/src/EntityLoaderUtils.ts +149 -0
- package/src/EntityMutationInfo.ts +2 -2
- package/src/EntityMutationTriggerConfiguration.ts +5 -5
- package/src/EntityMutationValidator.ts +2 -2
- package/src/EntityMutator.ts +146 -144
- package/src/EntityMutatorFactory.ts +8 -8
- package/src/EntityPrivacyPolicy.ts +78 -28
- package/src/EntityQueryContext.ts +14 -13
- package/src/EntityQueryContextProvider.ts +5 -5
- package/src/EntitySecondaryCacheLoader.ts +13 -11
- package/src/GenericEntityCacheAdapter.ts +10 -10
- package/src/GenericSecondaryEntityCache.ts +6 -6
- package/src/IEntityCacheAdapter.ts +4 -4
- package/src/IEntityCacheAdapterProvider.ts +2 -2
- package/src/IEntityDatabaseAdapterProvider.ts +2 -2
- package/src/ReadonlyEntity.ts +5 -5
- package/src/ViewerContext.ts +5 -5
- package/src/ViewerScopedEntityCompanion.ts +4 -4
- package/src/ViewerScopedEntityCompanionProvider.ts +4 -5
- package/src/ViewerScopedEntityLoaderFactory.ts +10 -4
- package/src/ViewerScopedEntityMutatorFactory.ts +5 -5
- package/src/__tests__/ComposedCacheAdapter-test.ts +12 -10
- package/src/__tests__/ComposedSecondaryEntityCache-test.ts +8 -8
- package/src/__tests__/EnforcingEntityLoader-test.ts +236 -159
- package/src/__tests__/Entity-test.ts +0 -202
- package/src/__tests__/EntityAssociationLoader-test.ts +29 -25
- package/src/__tests__/EntityCommonUseCases-test.ts +29 -13
- package/src/__tests__/EntityCompanion-test.ts +57 -5
- package/src/__tests__/EntityConfiguration-test.ts +118 -0
- package/src/__tests__/EntityDatabaseAdapter-test.ts +11 -11
- package/src/__tests__/EntityEdges-test.ts +108 -36
- package/src/__tests__/EntityFields-test.ts +14 -2
- package/src/__tests__/EntityLoader-constructor-test.ts +20 -7
- package/src/__tests__/EntityLoader-test.ts +214 -86
- package/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts +2 -2
- package/src/__tests__/EntityMutator-test.ts +281 -96
- package/src/__tests__/EntityPrivacyPolicy-test.ts +166 -53
- package/src/__tests__/EntityQueryContext-test.ts +30 -12
- package/src/__tests__/EntitySecondaryCacheLoader-test.ts +7 -7
- package/src/__tests__/EntitySelfReferentialEdges-test.ts +46 -26
- package/src/__tests__/GenericEntityCacheAdapter-test.ts +2 -2
- package/src/__tests__/ViewerContext-test.ts +1 -1
- package/src/__tests__/ViewerScopedEntityCompanion-test.ts +2 -2
- package/src/__tests__/ViewerScopedEntityCompanionProvider-test.ts +2 -2
- package/src/__tests__/ViewerScopedEntityLoaderFactory-test.ts +2 -1
- package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +19 -19
- package/src/__tests__/entityUtils-test.ts +2 -2
- package/src/entityUtils.ts +4 -4
- package/src/errors/EntityError.ts +4 -1
- package/src/errors/EntityInvalidFieldValueError.ts +2 -2
- package/src/errors/EntityNotAuthorizedError.ts +3 -3
- package/src/errors/EntityNotFoundError.ts +2 -2
- package/src/index.ts +1 -0
- package/src/internal/EntityDataManager.ts +24 -24
- package/src/internal/EntityFieldTransformationUtils.ts +39 -32
- package/src/internal/EntityTableDataCoordinator.ts +3 -3
- package/src/internal/ReadThroughEntityCache.ts +9 -9
- package/src/internal/__tests__/EntityDataManager-test.ts +51 -51
- package/src/internal/__tests__/EntityFieldTransformationUtils-test.ts +14 -10
- package/src/internal/__tests__/ReadThroughEntityCache-test.ts +74 -18
- package/src/metrics/EntityMetricsUtils.ts +4 -4
- package/src/metrics/IEntityMetricsAdapter.ts +1 -1
- package/src/rules/AlwaysAllowPrivacyPolicyRule.ts +9 -3
- package/src/rules/AlwaysDenyPrivacyPolicyRule.ts +9 -3
- package/src/rules/AlwaysSkipPrivacyPolicyRule.ts +9 -3
- package/src/rules/PrivacyPolicyRule.ts +9 -3
- package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -1
- package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -1
- package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -1
- package/src/testfixtures/TestEntity.ts +1 -1
- package/src/testfixtures/TestEntityWithMutationTriggers.ts +156 -0
- package/src/utils/EntityPrivacyUtils.ts +325 -0
- package/src/utils/__tests__/EntityPrivacyUtils-test.ts +570 -0
- package/src/utils/__tests__/mergeEntityMutationTriggerConfigurations-test.ts +29 -0
- package/src/utils/collections/__tests__/maps-test.ts +2 -2
- package/src/utils/collections/maps.ts +11 -11
- package/src/utils/mergeEntityMutationTriggerConfigurations.ts +44 -0
- package/src/utils/testing/PrivacyPolicyRuleTestUtils.ts +25 -22
- package/src/utils/testing/StubCacheAdapter.ts +17 -15
- package/src/utils/testing/StubDatabaseAdapter.ts +35 -30
- package/src/utils/testing/StubDatabaseAdapterProvider.ts +2 -2
- package/src/utils/testing/StubQueryContextProvider.ts +2 -2
- package/src/utils/testing/__tests__/PrivacyPolicyRuleTestUtils-test.ts +42 -0
- package/src/utils/testing/__tests__/StubDatabaseAdapter-test.ts +111 -29
- package/src/utils/testing/createUnitTestEntityCompanionProvider.ts +2 -2
- package/src/utils/testing/describeFieldTestCase.ts +1 -1
- package/build/__tests__/EntityDataConfiguration-test.js +0 -68
- package/build/__tests__/EntityDataConfiguration-test.js.map +0 -1
- package/src/__tests__/EntityDataConfiguration-test.ts +0 -77
- /package/build/__tests__/{EntityDataConfiguration-test.d.ts → EntityConfiguration-test.d.ts} +0 -0
package/src/EntityLoader.ts
CHANGED
|
@@ -1,26 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import invariant from 'invariant';
|
|
3
|
-
import nullthrows from 'nullthrows';
|
|
4
|
-
|
|
1
|
+
import AuthorizationResultBasedEntityLoader from './AuthorizationResultBasedEntityLoader';
|
|
5
2
|
import EnforcingEntityLoader from './EnforcingEntityLoader';
|
|
6
3
|
import { IEntityClass } from './Entity';
|
|
7
4
|
import EntityConfiguration from './EntityConfiguration';
|
|
8
|
-
import
|
|
9
|
-
FieldEqualityCondition,
|
|
10
|
-
QuerySelectionModifiers,
|
|
11
|
-
isSingleValueFieldEqualityCondition,
|
|
12
|
-
QuerySelectionModifiersWithOrderByRaw,
|
|
13
|
-
} from './EntityDatabaseAdapter';
|
|
5
|
+
import EntityLoaderUtils from './EntityLoaderUtils';
|
|
14
6
|
import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
|
|
15
7
|
import { EntityQueryContext } from './EntityQueryContext';
|
|
16
8
|
import ReadonlyEntity from './ReadonlyEntity';
|
|
17
9
|
import ViewerContext from './ViewerContext';
|
|
18
|
-
import { pick } from './entityUtils';
|
|
19
|
-
import EntityInvalidFieldValueError from './errors/EntityInvalidFieldValueError';
|
|
20
|
-
import EntityNotFoundError from './errors/EntityNotFoundError';
|
|
21
10
|
import EntityDataManager from './internal/EntityDataManager';
|
|
22
11
|
import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
|
|
23
|
-
import { mapMap, mapMapAsync } from './utils/collections/maps';
|
|
24
12
|
|
|
25
13
|
/**
|
|
26
14
|
* The primary interface for loading entities. All normal loads are batched,
|
|
@@ -38,12 +26,27 @@ export default class EntityLoader<
|
|
|
38
26
|
TEntity,
|
|
39
27
|
TSelectedFields
|
|
40
28
|
>,
|
|
41
|
-
TSelectedFields extends keyof TFields
|
|
29
|
+
TSelectedFields extends keyof TFields,
|
|
42
30
|
> {
|
|
31
|
+
private readonly utilsPrivate: EntityLoaderUtils<
|
|
32
|
+
TFields,
|
|
33
|
+
TID,
|
|
34
|
+
TViewerContext,
|
|
35
|
+
TEntity,
|
|
36
|
+
TPrivacyPolicy,
|
|
37
|
+
TSelectedFields
|
|
38
|
+
>;
|
|
39
|
+
|
|
43
40
|
constructor(
|
|
44
41
|
private readonly viewerContext: TViewerContext,
|
|
45
42
|
private readonly queryContext: EntityQueryContext,
|
|
46
|
-
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
|
|
43
|
+
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
44
|
+
TFields,
|
|
45
|
+
TID,
|
|
46
|
+
TViewerContext,
|
|
47
|
+
TEntity,
|
|
48
|
+
TSelectedFields
|
|
49
|
+
>,
|
|
47
50
|
private readonly entityConfiguration: EntityConfiguration<TFields>,
|
|
48
51
|
private readonly entityClass: IEntityClass<
|
|
49
52
|
TFields,
|
|
@@ -56,11 +59,23 @@ export default class EntityLoader<
|
|
|
56
59
|
private readonly entitySelectedFields: TSelectedFields[] | undefined,
|
|
57
60
|
private readonly privacyPolicy: TPrivacyPolicy,
|
|
58
61
|
private readonly dataManager: EntityDataManager<TFields>,
|
|
59
|
-
protected readonly metricsAdapter: IEntityMetricsAdapter
|
|
60
|
-
) {
|
|
62
|
+
protected readonly metricsAdapter: IEntityMetricsAdapter,
|
|
63
|
+
) {
|
|
64
|
+
this.utilsPrivate = new EntityLoaderUtils(
|
|
65
|
+
this.viewerContext,
|
|
66
|
+
this.queryContext,
|
|
67
|
+
this.privacyPolicyEvaluationContext,
|
|
68
|
+
this.entityConfiguration,
|
|
69
|
+
this.entityClass,
|
|
70
|
+
this.entitySelectedFields,
|
|
71
|
+
this.privacyPolicy,
|
|
72
|
+
this.dataManager,
|
|
73
|
+
this.metricsAdapter,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
61
76
|
|
|
62
77
|
/**
|
|
63
|
-
* Enforcing
|
|
78
|
+
* Enforcing entity loader. All loads through this loader are
|
|
64
79
|
* guaranteed to be the values of successful results (or null for some loader methods),
|
|
65
80
|
* and will throw otherwise.
|
|
66
81
|
*/
|
|
@@ -72,353 +87,44 @@ export default class EntityLoader<
|
|
|
72
87
|
TPrivacyPolicy,
|
|
73
88
|
TSelectedFields
|
|
74
89
|
> {
|
|
75
|
-
return new EnforcingEntityLoader(this);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Load many entities where fieldName is one of fieldValues.
|
|
80
|
-
* @param fieldName - entity field being queried
|
|
81
|
-
* @param fieldValues - fieldName field values being queried
|
|
82
|
-
* @returns map from fieldValue to entity results that match the query for that fieldValue,
|
|
83
|
-
* where result errors can be UnauthorizedError
|
|
84
|
-
*/
|
|
85
|
-
async loadManyByFieldEqualingManyAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
86
|
-
fieldName: N,
|
|
87
|
-
fieldValues: readonly NonNullable<TFields[N]>[]
|
|
88
|
-
): Promise<ReadonlyMap<NonNullable<TFields[N]>, readonly Result<TEntity>[]>> {
|
|
89
|
-
this.validateFieldValues(fieldName, fieldValues);
|
|
90
|
-
|
|
91
|
-
const fieldValuesToFieldObjects = await this.dataManager.loadManyByFieldEqualingAsync(
|
|
92
|
-
this.queryContext,
|
|
93
|
-
fieldName,
|
|
94
|
-
fieldValues
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
return await this.constructAndAuthorizeEntitiesAsync(fieldValuesToFieldObjects);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Load many entities where fieldName equals fieldValue.
|
|
102
|
-
* @param fieldName - entity field being queried
|
|
103
|
-
* @param fieldValue - fieldName field value being queried
|
|
104
|
-
* @returns array of entity results that match the query for fieldValue, where result error can be UnauthorizedError
|
|
105
|
-
*/
|
|
106
|
-
async loadManyByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
107
|
-
fieldName: N,
|
|
108
|
-
fieldValue: NonNullable<TFields[N]>
|
|
109
|
-
): Promise<readonly Result<TEntity>[]> {
|
|
110
|
-
const entityResults = await this.loadManyByFieldEqualingManyAsync(fieldName, [fieldValue]);
|
|
111
|
-
const entityResultsForFieldValue = entityResults.get(fieldValue);
|
|
112
|
-
invariant(
|
|
113
|
-
entityResultsForFieldValue !== undefined,
|
|
114
|
-
`${fieldValue} should be guaranteed to be present in returned map of entities`
|
|
115
|
-
);
|
|
116
|
-
return entityResultsForFieldValue!;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Load an entity where fieldName equals fieldValue, or null if no entity exists.
|
|
121
|
-
* @param uniqueFieldName - entity field being queried
|
|
122
|
-
* @param fieldValue - uniqueFieldName field value being queried
|
|
123
|
-
* @returns entity result where uniqueFieldName equals fieldValue, or null if no entity matches the condition.
|
|
124
|
-
* @throws when multiple entities match the condition
|
|
125
|
-
*/
|
|
126
|
-
async loadByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
127
|
-
uniqueFieldName: N,
|
|
128
|
-
fieldValue: NonNullable<TFields[N]>
|
|
129
|
-
): Promise<Result<TEntity> | null> {
|
|
130
|
-
const entityResults = await this.loadManyByFieldEqualingAsync(uniqueFieldName, fieldValue);
|
|
131
|
-
invariant(
|
|
132
|
-
entityResults.length <= 1,
|
|
133
|
-
`loadByFieldEqualing: Multiple entities of type ${this.entityClass.name} found for ${String(
|
|
134
|
-
uniqueFieldName
|
|
135
|
-
)}=${fieldValue}`
|
|
136
|
-
);
|
|
137
|
-
return entityResults[0] ?? null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Loads an entity by a specified ID.
|
|
142
|
-
* @param id - ID of the entity
|
|
143
|
-
* @returns entity result for matching ID, where result error can be UnauthorizedError or EntityNotFoundError.
|
|
144
|
-
*/
|
|
145
|
-
async loadByIDAsync(id: TID): Promise<Result<TEntity>> {
|
|
146
|
-
const entityResults = await this.loadManyByIDsAsync([id]);
|
|
147
|
-
const entityResult = entityResults.get(id);
|
|
148
|
-
if (entityResult === undefined) {
|
|
149
|
-
return result(
|
|
150
|
-
new EntityNotFoundError(this.entityClass, this.entityConfiguration.idField, id)
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
return entityResult;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Load an entity by a specified ID, or return null if non-existent.
|
|
158
|
-
* @param id - ID of the entity
|
|
159
|
-
* @returns entity result for matching ID, or null if no entity exists for ID.
|
|
160
|
-
*/
|
|
161
|
-
async loadByIDNullableAsync(id: TID): Promise<Result<TEntity> | null> {
|
|
162
|
-
return await this.loadByFieldEqualingAsync(
|
|
163
|
-
this.entityConfiguration.idField as TSelectedFields,
|
|
164
|
-
id
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Loads many entities for a list of IDs.
|
|
170
|
-
* @param viewerContext - viewer context of loading user
|
|
171
|
-
* @param ids - IDs of the entities to load
|
|
172
|
-
* @returns map from ID to corresponding entity result, where result error can be UnauthorizedError or EntityNotFoundError.
|
|
173
|
-
*/
|
|
174
|
-
async loadManyByIDsAsync(ids: readonly TID[]): Promise<ReadonlyMap<TID, Result<TEntity>>> {
|
|
175
|
-
const entityResults = (await this.loadManyByFieldEqualingManyAsync(
|
|
176
|
-
this.entityConfiguration.idField as TSelectedFields,
|
|
177
|
-
ids
|
|
178
|
-
)) as ReadonlyMap<TID, readonly Result<TEntity>[]>;
|
|
179
|
-
return mapMap(entityResults, (entityResultsForId, id) => {
|
|
180
|
-
const entityResult = entityResultsForId[0];
|
|
181
|
-
return (
|
|
182
|
-
entityResult ??
|
|
183
|
-
result(new EntityNotFoundError(this.entityClass, this.entityConfiguration.idField, id))
|
|
184
|
-
);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Loads many entities for a list of IDs, returning null for any IDs that are non-existent.
|
|
190
|
-
* @param ids - IDs of the entities to load
|
|
191
|
-
* @returns map from ID to nullable corresponding entity result, where result error can be UnauthorizedError or EntityNotFoundError.
|
|
192
|
-
*/
|
|
193
|
-
async loadManyByIDsNullableAsync(
|
|
194
|
-
ids: readonly TID[]
|
|
195
|
-
): Promise<ReadonlyMap<TID, Result<TEntity> | null>> {
|
|
196
|
-
const entityResults = (await this.loadManyByFieldEqualingManyAsync(
|
|
197
|
-
this.entityConfiguration.idField as TSelectedFields,
|
|
198
|
-
ids
|
|
199
|
-
)) as ReadonlyMap<TID, readonly Result<TEntity>[]>;
|
|
200
|
-
return mapMap(entityResults, (entityResultsForId) => {
|
|
201
|
-
return entityResultsForId[0] ?? null;
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Loads the first entity matching the selection constructed from the conjunction of specified
|
|
207
|
-
* operands, or null if no matching entity exists. Entities loaded using this method are not
|
|
208
|
-
* batched or cached.
|
|
209
|
-
*
|
|
210
|
-
* This is a convenience method for {@link loadManyByFieldEqualityConjunctionAsync}. However, the
|
|
211
|
-
* `orderBy` option must be specified to define what "first" means. If ordering doesn't matter,
|
|
212
|
-
* explicitly pass in an empty array.
|
|
213
|
-
*
|
|
214
|
-
* @param fieldEqualityOperands - list of field equality selection operand specifications
|
|
215
|
-
* @param querySelectionModifiers - orderBy and optional offset for the query
|
|
216
|
-
* @returns the first entity results that matches the query, where result error can be
|
|
217
|
-
* UnauthorizedError
|
|
218
|
-
*/
|
|
219
|
-
async loadFirstByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
220
|
-
fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
|
|
221
|
-
querySelectionModifiers: Omit<QuerySelectionModifiers<TFields>, 'limit'> &
|
|
222
|
-
Required<Pick<QuerySelectionModifiers<TFields>, 'orderBy'>>
|
|
223
|
-
): Promise<Result<TEntity> | null> {
|
|
224
|
-
const results = await this.loadManyByFieldEqualityConjunctionAsync(fieldEqualityOperands, {
|
|
225
|
-
...querySelectionModifiers,
|
|
226
|
-
limit: 1,
|
|
227
|
-
});
|
|
228
|
-
return results[0] ?? null;
|
|
90
|
+
return new EnforcingEntityLoader(this.withAuthorizationResults());
|
|
229
91
|
}
|
|
230
92
|
|
|
231
93
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* @example
|
|
236
|
-
* fieldEqualityOperands:
|
|
237
|
-
* `[{fieldName: 'hello', fieldValue: 1}, {fieldName: 'world', fieldValues: [2, 3]}]`
|
|
238
|
-
* Entities returned with a SQL EntityDatabaseAdapter:
|
|
239
|
-
* `WHERE hello = 1 AND world = ANY({2, 3})`
|
|
240
|
-
*
|
|
241
|
-
* @param fieldEqualityOperands - list of field equality selection operand specifications
|
|
242
|
-
* @param querySelectionModifiers - limit, offset, and orderBy for the query
|
|
243
|
-
* @returns array of entity results that match the query, where result error can be UnauthorizedError
|
|
94
|
+
* Authorization-result-based entity loader. All loads through this
|
|
95
|
+
* loader are are results (or null for some loader methods), where an unsuccessful result
|
|
96
|
+
* means an authorization error or entity construction error occurred. Other errors are thrown.
|
|
244
97
|
*/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const fieldObjects = await this.dataManager.loadManyByFieldEqualityConjunctionAsync(
|
|
98
|
+
withAuthorizationResults(): AuthorizationResultBasedEntityLoader<
|
|
99
|
+
TFields,
|
|
100
|
+
TID,
|
|
101
|
+
TViewerContext,
|
|
102
|
+
TEntity,
|
|
103
|
+
TPrivacyPolicy,
|
|
104
|
+
TSelectedFields
|
|
105
|
+
> {
|
|
106
|
+
return new AuthorizationResultBasedEntityLoader(
|
|
257
107
|
this.queryContext,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
|
264
|
-
if (!uncheckedEntityResult.ok) {
|
|
265
|
-
return uncheckedEntityResult;
|
|
266
|
-
}
|
|
267
|
-
return await asyncResult(
|
|
268
|
-
this.privacyPolicy.authorizeReadAsync(
|
|
269
|
-
this.viewerContext,
|
|
270
|
-
this.queryContext,
|
|
271
|
-
this.privacyPolicyEvaluationContext,
|
|
272
|
-
uncheckedEntityResult.value,
|
|
273
|
-
this.metricsAdapter
|
|
274
|
-
)
|
|
275
|
-
);
|
|
276
|
-
})
|
|
108
|
+
this.entityConfiguration,
|
|
109
|
+
this.entityClass,
|
|
110
|
+
this.dataManager,
|
|
111
|
+
this.metricsAdapter,
|
|
112
|
+
this.utilsPrivate,
|
|
277
113
|
);
|
|
278
114
|
}
|
|
279
115
|
|
|
280
116
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* @remarks
|
|
284
|
-
* Important notes:
|
|
285
|
-
* - Fields in clause are database column names instead of transformed entity field names.
|
|
286
|
-
* - Entities loaded using this method are not batched or cached.
|
|
287
|
-
* - Not all database adapters implement the ability to execute this method of fetching entities.
|
|
288
|
-
*
|
|
289
|
-
* @example
|
|
290
|
-
* rawWhereClause: `id = ?`
|
|
291
|
-
* bindings: `[1]`
|
|
292
|
-
* Entites returned `WHERE id = 1`
|
|
293
|
-
*
|
|
294
|
-
* http://knexjs.org/#Builder-whereRaw
|
|
295
|
-
* http://knexjs.org/#Raw-Bindings
|
|
296
|
-
*
|
|
297
|
-
* @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
|
|
298
|
-
* @param bindings - array of positional bindings or object of named bindings
|
|
299
|
-
* @param querySelectionModifiers - limit, offset, orderBy, and orderByRaw for the query
|
|
300
|
-
* @returns array of entity results that match the query, where result error can be UnauthorizedError
|
|
301
|
-
* @throws Error when rawWhereClause or bindings are invalid
|
|
302
|
-
*
|
|
303
|
-
* @deprecated prefer caching loaders
|
|
117
|
+
* Entity loader utilities for things like cache invalidation, entity construction, and authorization.
|
|
118
|
+
* Calling into these should only be necessary in rare cases.
|
|
304
119
|
*/
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
querySelectionModifiers
|
|
315
|
-
);
|
|
316
|
-
const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
|
|
317
|
-
return await Promise.all(
|
|
318
|
-
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
|
319
|
-
if (!uncheckedEntityResult.ok) {
|
|
320
|
-
return uncheckedEntityResult;
|
|
321
|
-
}
|
|
322
|
-
return await asyncResult(
|
|
323
|
-
this.privacyPolicy.authorizeReadAsync(
|
|
324
|
-
this.viewerContext,
|
|
325
|
-
this.queryContext,
|
|
326
|
-
this.privacyPolicyEvaluationContext,
|
|
327
|
-
uncheckedEntityResult.value,
|
|
328
|
-
this.metricsAdapter
|
|
329
|
-
)
|
|
330
|
-
);
|
|
331
|
-
})
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Invalidate all caches for an entity's fields. Exposed primarily for internal use by EntityMutator.
|
|
337
|
-
* @param objectFields - entity data object to be invalidated
|
|
338
|
-
*/
|
|
339
|
-
async invalidateFieldsAsync(objectFields: Readonly<TFields>): Promise<void> {
|
|
340
|
-
await this.dataManager.invalidateObjectFieldsAsync(objectFields);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Invalidate all caches for an entity. One potential use case would be to keep the entity
|
|
345
|
-
* framework in sync with changes made to data outside of the framework.
|
|
346
|
-
* @param entity - entity to be invalidated
|
|
347
|
-
*/
|
|
348
|
-
async invalidateEntityAsync(entity: TEntity): Promise<void> {
|
|
349
|
-
await this.invalidateFieldsAsync(entity.getAllDatabaseFields());
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
private tryConstructEntities(fieldsObjects: readonly TFields[]): readonly Result<TEntity>[] {
|
|
353
|
-
return fieldsObjects.map((fieldsObject) => {
|
|
354
|
-
try {
|
|
355
|
-
return result(this.constructEntity(fieldsObject));
|
|
356
|
-
} catch (e) {
|
|
357
|
-
if (!(e instanceof Error)) {
|
|
358
|
-
throw e;
|
|
359
|
-
}
|
|
360
|
-
return result(e);
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
public constructEntity(fieldsObject: TFields): TEntity {
|
|
366
|
-
const idField = this.entityConfiguration.idField;
|
|
367
|
-
const id = nullthrows(fieldsObject[idField], 'must provide ID to create an entity');
|
|
368
|
-
const entitySelectedFields =
|
|
369
|
-
this.entitySelectedFields ?? Array.from(this.entityConfiguration.schema.keys());
|
|
370
|
-
const selectedFields = pick(fieldsObject, entitySelectedFields);
|
|
371
|
-
return new this.entityClass({
|
|
372
|
-
viewerContext: this.viewerContext,
|
|
373
|
-
id: id as TID,
|
|
374
|
-
databaseFields: fieldsObject,
|
|
375
|
-
selectedFields,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Construct and authorize entities from fields map, returning error results for entities that fail
|
|
381
|
-
* to construct or fail to authorize.
|
|
382
|
-
*
|
|
383
|
-
* @param map - map from an arbitrary key type to an array of entity field objects
|
|
384
|
-
*/
|
|
385
|
-
public async constructAndAuthorizeEntitiesAsync<K>(
|
|
386
|
-
map: ReadonlyMap<K, readonly Readonly<TFields>[]>
|
|
387
|
-
): Promise<ReadonlyMap<K, readonly Result<TEntity>[]>> {
|
|
388
|
-
const uncheckedEntityResultsMap = mapMap(map, (fieldObjects) =>
|
|
389
|
-
this.tryConstructEntities(fieldObjects)
|
|
390
|
-
);
|
|
391
|
-
return await mapMapAsync(uncheckedEntityResultsMap, async (uncheckedEntityResults) => {
|
|
392
|
-
return await Promise.all(
|
|
393
|
-
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
|
394
|
-
if (!uncheckedEntityResult.ok) {
|
|
395
|
-
return uncheckedEntityResult;
|
|
396
|
-
}
|
|
397
|
-
return await asyncResult(
|
|
398
|
-
this.privacyPolicy.authorizeReadAsync(
|
|
399
|
-
this.viewerContext,
|
|
400
|
-
this.queryContext,
|
|
401
|
-
this.privacyPolicyEvaluationContext,
|
|
402
|
-
uncheckedEntityResult.value,
|
|
403
|
-
this.metricsAdapter
|
|
404
|
-
)
|
|
405
|
-
);
|
|
406
|
-
})
|
|
407
|
-
);
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private validateFieldValues<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
412
|
-
fieldName: N,
|
|
413
|
-
fieldValues: readonly TFields[N][]
|
|
414
|
-
): void {
|
|
415
|
-
const fieldDefinition = this.entityConfiguration.schema.get(fieldName);
|
|
416
|
-
invariant(fieldDefinition, `must have field definition for field = ${String(fieldName)}`);
|
|
417
|
-
for (const fieldValue of fieldValues) {
|
|
418
|
-
const isInputValid = fieldDefinition.validateInputValue(fieldValue);
|
|
419
|
-
if (!isInputValid) {
|
|
420
|
-
throw new EntityInvalidFieldValueError(this.entityClass, fieldName, fieldValue);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
120
|
+
public utils(): EntityLoaderUtils<
|
|
121
|
+
TFields,
|
|
122
|
+
TID,
|
|
123
|
+
TViewerContext,
|
|
124
|
+
TEntity,
|
|
125
|
+
TPrivacyPolicy,
|
|
126
|
+
TSelectedFields
|
|
127
|
+
> {
|
|
128
|
+
return this.utilsPrivate;
|
|
423
129
|
}
|
|
424
130
|
}
|
|
@@ -22,7 +22,7 @@ export default class EntityLoaderFactory<
|
|
|
22
22
|
TEntity,
|
|
23
23
|
TSelectedFields
|
|
24
24
|
>,
|
|
25
|
-
TSelectedFields extends keyof TFields
|
|
25
|
+
TSelectedFields extends keyof TFields,
|
|
26
26
|
> {
|
|
27
27
|
constructor(
|
|
28
28
|
private readonly entityCompanion: EntityCompanion<
|
|
@@ -34,7 +34,7 @@ export default class EntityLoaderFactory<
|
|
|
34
34
|
TSelectedFields
|
|
35
35
|
>,
|
|
36
36
|
private readonly dataManager: EntityDataManager<TFields>,
|
|
37
|
-
protected readonly metricsAdapter: IEntityMetricsAdapter
|
|
37
|
+
protected readonly metricsAdapter: IEntityMetricsAdapter,
|
|
38
38
|
) {}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -45,7 +45,13 @@ export default class EntityLoaderFactory<
|
|
|
45
45
|
forLoad(
|
|
46
46
|
viewerContext: TViewerContext,
|
|
47
47
|
queryContext: EntityQueryContext,
|
|
48
|
-
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
|
|
48
|
+
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
49
|
+
TFields,
|
|
50
|
+
TID,
|
|
51
|
+
TViewerContext,
|
|
52
|
+
TEntity,
|
|
53
|
+
TSelectedFields
|
|
54
|
+
>,
|
|
49
55
|
): EntityLoader<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
|
|
50
56
|
return new EntityLoader(
|
|
51
57
|
viewerContext,
|
|
@@ -56,7 +62,7 @@ export default class EntityLoaderFactory<
|
|
|
56
62
|
this.entityCompanion.entityCompanionDefinition.entitySelectedFields,
|
|
57
63
|
this.entityCompanion.privacyPolicy,
|
|
58
64
|
this.dataManager,
|
|
59
|
-
this.metricsAdapter
|
|
65
|
+
this.metricsAdapter,
|
|
60
66
|
);
|
|
61
67
|
}
|
|
62
68
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Result, asyncResult, result } from '@expo/results';
|
|
2
|
+
import nullthrows from 'nullthrows';
|
|
3
|
+
|
|
4
|
+
import { IEntityClass } from './Entity';
|
|
5
|
+
import EntityConfiguration from './EntityConfiguration';
|
|
6
|
+
import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
|
|
7
|
+
import { EntityQueryContext } from './EntityQueryContext';
|
|
8
|
+
import ReadonlyEntity from './ReadonlyEntity';
|
|
9
|
+
import ViewerContext from './ViewerContext';
|
|
10
|
+
import { pick } from './entityUtils';
|
|
11
|
+
import EntityDataManager from './internal/EntityDataManager';
|
|
12
|
+
import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
|
|
13
|
+
import { mapMapAsync } from './utils/collections/maps';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Entity loader utilities for things like invalidation, entity construction, and authorization.
|
|
17
|
+
* Methods are exposed publicly since in rare cases they may need to be called manually.
|
|
18
|
+
*/
|
|
19
|
+
export default class EntityLoaderUtils<
|
|
20
|
+
TFields extends object,
|
|
21
|
+
TID extends NonNullable<TFields[TSelectedFields]>,
|
|
22
|
+
TViewerContext extends ViewerContext,
|
|
23
|
+
TEntity extends ReadonlyEntity<TFields, TID, TViewerContext, TSelectedFields>,
|
|
24
|
+
TPrivacyPolicy extends EntityPrivacyPolicy<
|
|
25
|
+
TFields,
|
|
26
|
+
TID,
|
|
27
|
+
TViewerContext,
|
|
28
|
+
TEntity,
|
|
29
|
+
TSelectedFields
|
|
30
|
+
>,
|
|
31
|
+
TSelectedFields extends keyof TFields,
|
|
32
|
+
> {
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly viewerContext: TViewerContext,
|
|
35
|
+
private readonly queryContext: EntityQueryContext,
|
|
36
|
+
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
37
|
+
TFields,
|
|
38
|
+
TID,
|
|
39
|
+
TViewerContext,
|
|
40
|
+
TEntity,
|
|
41
|
+
TSelectedFields
|
|
42
|
+
>,
|
|
43
|
+
private readonly entityConfiguration: EntityConfiguration<TFields>,
|
|
44
|
+
private readonly entityClass: IEntityClass<
|
|
45
|
+
TFields,
|
|
46
|
+
TID,
|
|
47
|
+
TViewerContext,
|
|
48
|
+
TEntity,
|
|
49
|
+
TPrivacyPolicy,
|
|
50
|
+
TSelectedFields
|
|
51
|
+
>,
|
|
52
|
+
private readonly entitySelectedFields: TSelectedFields[] | undefined,
|
|
53
|
+
private readonly privacyPolicy: TPrivacyPolicy,
|
|
54
|
+
private readonly dataManager: EntityDataManager<TFields>,
|
|
55
|
+
protected readonly metricsAdapter: IEntityMetricsAdapter,
|
|
56
|
+
) {}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Invalidate all caches for an entity's fields. Exposed primarily for internal use by EntityMutator.
|
|
60
|
+
* @param objectFields - entity data object to be invalidated
|
|
61
|
+
*/
|
|
62
|
+
async invalidateFieldsAsync(objectFields: Readonly<TFields>): Promise<void> {
|
|
63
|
+
await this.dataManager.invalidateObjectFieldsAsync(objectFields);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Invalidate all caches for an entity. One potential use case would be to keep the entity
|
|
68
|
+
* framework in sync with changes made to data outside of the framework.
|
|
69
|
+
* @param entity - entity to be invalidated
|
|
70
|
+
*/
|
|
71
|
+
async invalidateEntityAsync(entity: TEntity): Promise<void> {
|
|
72
|
+
await this.invalidateFieldsAsync(entity.getAllDatabaseFields());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Construct an entity from a fields object (applying field selection if applicable),
|
|
77
|
+
* checking that the ID field is specified.
|
|
78
|
+
*
|
|
79
|
+
* @param fieldsObject - fields object
|
|
80
|
+
*/
|
|
81
|
+
public constructEntity(fieldsObject: TFields): TEntity {
|
|
82
|
+
const idField = this.entityConfiguration.idField;
|
|
83
|
+
const id = nullthrows(fieldsObject[idField], 'must provide ID to create an entity');
|
|
84
|
+
const entitySelectedFields =
|
|
85
|
+
this.entitySelectedFields ?? Array.from(this.entityConfiguration.schema.keys());
|
|
86
|
+
const selectedFields = pick(fieldsObject, entitySelectedFields);
|
|
87
|
+
return new this.entityClass({
|
|
88
|
+
viewerContext: this.viewerContext,
|
|
89
|
+
id: id as TID,
|
|
90
|
+
databaseFields: fieldsObject,
|
|
91
|
+
selectedFields,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Construct and authorize entities from fields map, returning error results for entities that fail
|
|
97
|
+
* to construct or fail to authorize.
|
|
98
|
+
*
|
|
99
|
+
* @param map - map from an arbitrary key type to an array of entity field objects
|
|
100
|
+
*/
|
|
101
|
+
public async constructAndAuthorizeEntitiesAsync<K>(
|
|
102
|
+
map: ReadonlyMap<K, readonly Readonly<TFields>[]>,
|
|
103
|
+
): Promise<ReadonlyMap<K, readonly Result<TEntity>[]>> {
|
|
104
|
+
return await mapMapAsync(map, async (fieldObjects) => {
|
|
105
|
+
return await this.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Construct and authorize entities from field objects array, returning error results for entities that fail
|
|
111
|
+
* to construct or fail to authorize.
|
|
112
|
+
*
|
|
113
|
+
* @param fieldObjects - array of field objects
|
|
114
|
+
*/
|
|
115
|
+
public async constructAndAuthorizeEntitiesArrayAsync(
|
|
116
|
+
fieldObjects: readonly Readonly<TFields>[],
|
|
117
|
+
): Promise<readonly Result<TEntity>[]> {
|
|
118
|
+
const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
|
|
119
|
+
return await Promise.all(
|
|
120
|
+
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
|
121
|
+
if (!uncheckedEntityResult.ok) {
|
|
122
|
+
return uncheckedEntityResult;
|
|
123
|
+
}
|
|
124
|
+
return await asyncResult(
|
|
125
|
+
this.privacyPolicy.authorizeReadAsync(
|
|
126
|
+
this.viewerContext,
|
|
127
|
+
this.queryContext,
|
|
128
|
+
this.privacyPolicyEvaluationContext,
|
|
129
|
+
uncheckedEntityResult.value,
|
|
130
|
+
this.metricsAdapter,
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private tryConstructEntities(fieldsObjects: readonly TFields[]): readonly Result<TEntity>[] {
|
|
138
|
+
return fieldsObjects.map((fieldsObject) => {
|
|
139
|
+
try {
|
|
140
|
+
return result(this.constructEntity(fieldsObject));
|
|
141
|
+
} catch (e) {
|
|
142
|
+
if (!(e instanceof Error)) {
|
|
143
|
+
throw e;
|
|
144
|
+
}
|
|
145
|
+
return result(e);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -12,7 +12,7 @@ export type EntityValidatorMutationInfo<
|
|
|
12
12
|
TID extends NonNullable<TFields[TSelectedFields]>,
|
|
13
13
|
TViewerContext extends ViewerContext,
|
|
14
14
|
TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
|
|
15
|
-
TSelectedFields extends keyof TFields = keyof TFields
|
|
15
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
16
16
|
> =
|
|
17
17
|
| {
|
|
18
18
|
type: EntityMutationType.CREATE;
|
|
@@ -43,7 +43,7 @@ export type EntityTriggerMutationInfo<
|
|
|
43
43
|
TID extends NonNullable<TFields[TSelectedFields]>,
|
|
44
44
|
TViewerContext extends ViewerContext,
|
|
45
45
|
TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
|
|
46
|
-
TSelectedFields extends keyof TFields = keyof TFields
|
|
46
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
47
47
|
> =
|
|
48
48
|
| {
|
|
49
49
|
type: EntityMutationType.CREATE;
|