@expo/entity 0.55.0 → 0.57.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/src/AuthorizationResultBasedEntityAssociationLoader.d.ts +1 -1
- package/build/src/AuthorizationResultBasedEntityAssociationLoader.js.map +1 -1
- package/build/src/AuthorizationResultBasedEntityLoader.d.ts +18 -24
- package/build/src/AuthorizationResultBasedEntityLoader.js +37 -56
- package/build/src/AuthorizationResultBasedEntityLoader.js.map +1 -1
- package/build/src/AuthorizationResultBasedEntityMutator.js +26 -19
- package/build/src/AuthorizationResultBasedEntityMutator.js.map +1 -1
- package/build/src/EnforcingEntityCreator.d.ts +1 -1
- package/build/src/EnforcingEntityCreator.js +1 -1
- package/build/src/EnforcingEntityLoader.d.ts +1 -58
- package/build/src/EnforcingEntityLoader.js +0 -65
- package/build/src/EnforcingEntityLoader.js.map +1 -1
- package/build/src/Entity.d.ts +6 -0
- package/build/src/Entity.js +6 -0
- package/build/src/Entity.js.map +1 -1
- package/build/src/EntityCompanion.d.ts +2 -2
- package/build/src/EntityCompanion.js.map +1 -1
- package/build/src/EntityCompanionProvider.d.ts +1 -1
- package/build/src/EntityCompanionProvider.js +4 -4
- package/build/src/EntityConfiguration.d.ts +1 -1
- package/build/src/EntityConfiguration.js +1 -2
- package/build/src/EntityConfiguration.js.map +1 -1
- package/build/src/{EntityLoaderUtils.d.ts → EntityConstructionUtils.d.ts} +15 -29
- package/build/src/EntityConstructionUtils.js +118 -0
- package/build/src/EntityConstructionUtils.js.map +1 -0
- package/build/src/EntityDatabaseAdapter.d.ts +10 -108
- package/build/src/EntityDatabaseAdapter.js +14 -76
- package/build/src/EntityDatabaseAdapter.js.map +1 -1
- package/build/src/EntityFieldDefinition.d.ts +1 -1
- package/build/src/EntityInvalidationUtils.d.ts +41 -0
- package/build/src/EntityInvalidationUtils.js +71 -0
- package/build/src/EntityInvalidationUtils.js.map +1 -0
- package/build/src/EntityLoader.d.ts +0 -6
- package/build/src/EntityLoader.js +0 -7
- package/build/src/EntityLoader.js.map +1 -1
- package/build/src/EntityLoaderFactory.d.ts +4 -0
- package/build/src/EntityLoaderFactory.js +10 -3
- package/build/src/EntityLoaderFactory.js.map +1 -1
- package/build/src/EntityPrivacyPolicy.d.ts +27 -0
- package/build/src/EntityPrivacyPolicy.js +22 -1
- package/build/src/EntityPrivacyPolicy.js.map +1 -1
- package/build/src/EntitySecondaryCacheLoader.d.ts +14 -3
- package/build/src/EntitySecondaryCacheLoader.js +21 -4
- package/build/src/EntitySecondaryCacheLoader.js.map +1 -1
- package/build/src/ReadonlyEntity.d.ts +4 -5
- package/build/src/ReadonlyEntity.js +7 -8
- package/build/src/ReadonlyEntity.js.map +1 -1
- package/build/src/ViewerContext.d.ts +6 -6
- package/build/src/ViewerContext.js +8 -8
- package/build/src/ViewerScopedEntityCompanion.d.ts +1 -1
- package/build/src/ViewerScopedEntityCompanion.js.map +1 -1
- package/build/src/ViewerScopedEntityLoaderFactory.d.ts +4 -0
- package/build/src/ViewerScopedEntityLoaderFactory.js +6 -0
- package/build/src/ViewerScopedEntityLoaderFactory.js.map +1 -1
- package/build/src/errors/EntityDatabaseAdapterError.d.ts +4 -0
- package/build/src/errors/EntityDatabaseAdapterError.js +13 -1
- package/build/src/errors/EntityDatabaseAdapterError.js.map +1 -1
- package/build/src/errors/EntityError.d.ts +2 -1
- package/build/src/errors/EntityError.js +1 -0
- package/build/src/errors/EntityError.js.map +1 -1
- package/build/src/index.d.ts +2 -1
- package/build/src/index.js +2 -1
- package/build/src/index.js.map +1 -1
- package/build/src/internal/EntityDataManager.d.ts +8 -16
- package/build/src/internal/EntityDataManager.js +8 -18
- package/build/src/internal/EntityDataManager.js.map +1 -1
- package/build/src/internal/EntityFieldTransformationUtils.js.map +1 -1
- package/build/src/internal/EntityLoadInterfaces.d.ts +2 -0
- package/build/src/internal/EntityLoadInterfaces.js +2 -0
- package/build/src/internal/EntityLoadInterfaces.js.map +1 -1
- package/build/src/internal/EntityTableDataCoordinator.d.ts +2 -0
- package/build/src/internal/EntityTableDataCoordinator.js +4 -0
- package/build/src/internal/EntityTableDataCoordinator.js.map +1 -1
- package/build/src/metrics/EntityMetricsUtils.d.ts +1 -0
- package/build/src/metrics/EntityMetricsUtils.js +15 -1
- package/build/src/metrics/EntityMetricsUtils.js.map +1 -1
- package/build/src/metrics/IEntityMetricsAdapter.d.ts +4 -1
- package/build/src/metrics/IEntityMetricsAdapter.js +3 -0
- package/build/src/metrics/IEntityMetricsAdapter.js.map +1 -1
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.d.ts +2 -2
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.d.ts +2 -2
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.d.ts +2 -2
- package/build/src/rules/PrivacyPolicyRule.d.ts +2 -2
- package/build/src/utils/EntityPrivacyUtils.js +11 -20
- package/build/src/utils/EntityPrivacyUtils.js.map +1 -1
- package/build/src/utils/collections/maps.d.ts +2 -2
- package/build/src/utils/collections/maps.js +2 -2
- package/package.json +4 -4
- package/src/AuthorizationResultBasedEntityAssociationLoader.ts +4 -7
- package/src/AuthorizationResultBasedEntityLoader.ts +58 -88
- package/src/AuthorizationResultBasedEntityMutator.ts +35 -20
- package/src/EnforcingEntityCreator.ts +1 -1
- package/src/EnforcingEntityLoader.ts +1 -95
- package/src/Entity.ts +6 -0
- package/src/EntityCompanion.ts +2 -2
- package/src/EntityCompanionProvider.ts +4 -4
- package/src/EntityConfiguration.ts +8 -5
- package/src/EntityConstructionUtils.ts +168 -0
- package/src/EntityDatabaseAdapter.ts +32 -222
- package/src/EntityFieldDefinition.ts +1 -1
- package/src/{EntityLoaderUtils.ts → EntityInvalidationUtils.ts} +5 -96
- package/src/EntityLoader.ts +0 -16
- package/src/EntityLoaderFactory.ts +50 -10
- package/src/EntityPrivacyPolicy.ts +44 -1
- package/src/EntitySecondaryCacheLoader.ts +54 -3
- package/src/ReadonlyEntity.ts +9 -11
- package/src/ViewerContext.ts +10 -10
- package/src/ViewerScopedEntityCompanion.ts +1 -1
- package/src/ViewerScopedEntityLoaderFactory.ts +37 -0
- package/src/__tests__/AuthorizationResultBasedEntityLoader-constructor-test.ts +3 -5
- package/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts +34 -419
- package/src/__tests__/ComposedCacheAdapter-test.ts +3 -3
- package/src/__tests__/EnforcingEntityLoader-test.ts +2 -134
- package/src/__tests__/EntityCompanion-test.ts +18 -0
- package/src/__tests__/EntityConfiguration-test.ts +4 -4
- package/src/__tests__/EntityDatabaseAdapter-test.ts +33 -68
- package/src/__tests__/EntityEdges-test.ts +10 -10
- package/src/__tests__/EntityLoader-test.ts +6 -4
- package/src/__tests__/EntityMutator-test.ts +27 -15
- package/src/__tests__/EntityPrivacyPolicy-test.ts +102 -0
- package/src/__tests__/EntityQueryContext-test.ts +11 -11
- package/src/__tests__/EntitySecondaryCacheLoader-test.ts +10 -5
- package/src/__tests__/EntitySelfReferentialEdges-test.ts +6 -6
- package/src/__tests__/GenericEntityCacheAdapter-test.ts +18 -15
- package/src/__tests__/GenericSecondaryEntityCache-test.ts +27 -5
- package/src/__tests__/ReadonlyEntity-test.ts +6 -4
- package/src/__tests__/ViewerContext-test.ts +4 -4
- package/src/__tests__/ViewerScopedEntityCompanion-test.ts +1 -0
- package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +0 -17
- package/src/errors/EntityDatabaseAdapterError.ts +14 -0
- package/src/errors/EntityError.ts +1 -0
- package/src/errors/__tests__/EntityDatabaseAdapterError-test.ts +9 -0
- package/src/errors/__tests__/EntityError-test.ts +13 -5
- package/src/index.ts +2 -1
- package/src/internal/EntityDataManager.ts +19 -54
- package/src/internal/EntityFieldTransformationUtils.ts +5 -5
- package/src/internal/EntityLoadInterfaces.ts +2 -0
- package/src/internal/EntityTableDataCoordinator.ts +2 -2
- package/src/internal/__tests__/CompositeFieldHolder-test.ts +8 -2
- package/src/internal/__tests__/EntityDataManager-test.ts +71 -202
- package/src/internal/__tests__/ReadThroughEntityCache-test.ts +39 -24
- package/src/metrics/EntityMetricsUtils.ts +23 -0
- package/src/metrics/IEntityMetricsAdapter.ts +3 -0
- package/src/metrics/__tests__/EntityMetricsUtils-test.ts +120 -0
- package/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.ts +2 -2
- package/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.ts +2 -2
- package/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.ts +2 -2
- package/src/rules/PrivacyPolicyRule.ts +2 -2
- package/src/rules/__tests__/AllowIfAllSubRulesAllowPrivacyPolicyRule-test.ts +4 -4
- package/src/rules/__tests__/AllowIfAnySubRuleAllowsPrivacyPolicyRule-test.ts +4 -4
- package/src/rules/__tests__/AllowIfInParentCascadeDeletionPrivacyPolicyRule-test.ts +11 -1
- package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/EvaluateIfEntityFieldPredicatePrivacyPolicyRule-test.ts +3 -3
- package/src/utils/EntityPrivacyUtils.ts +18 -29
- package/src/utils/__testfixtures__/PrivacyPolicyRuleTestUtils.ts +2 -2
- package/src/utils/__testfixtures__/StubDatabaseAdapter.ts +13 -101
- package/src/utils/__tests__/EntityCreationUtils-test.ts +6 -6
- package/src/utils/__tests__/EntityPrivacyUtils-test.ts +2 -2
- package/src/utils/collections/maps.ts +2 -2
- package/build/src/EntityLoaderUtils.js +0 -147
- package/build/src/EntityLoaderUtils.js.map +0 -1
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import { Result, asyncResult, result } from '@expo/results';
|
|
2
|
-
import nullthrows from 'nullthrows';
|
|
3
|
-
|
|
4
1
|
import { IEntityClass } from './Entity';
|
|
5
2
|
import { EntityConfiguration } from './EntityConfiguration';
|
|
6
|
-
import { EntityPrivacyPolicy
|
|
7
|
-
import {
|
|
3
|
+
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
|
|
4
|
+
import { EntityTransactionalQueryContext } from './EntityQueryContext';
|
|
8
5
|
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
9
6
|
import { ViewerContext } from './ViewerContext';
|
|
10
|
-
import { pick } from './entityUtils';
|
|
11
7
|
import { EntityDataManager } from './internal/EntityDataManager';
|
|
12
8
|
import { LoadPair } from './internal/EntityLoadInterfaces';
|
|
13
9
|
import { SingleFieldHolder, SingleFieldValueHolder } from './internal/SingleFieldHolder';
|
|
14
10
|
import { IEntityMetricsAdapter } from './metrics/IEntityMetricsAdapter';
|
|
15
|
-
import { mapMapAsync } from './utils/collections/maps';
|
|
16
11
|
|
|
17
12
|
/**
|
|
18
|
-
* Entity
|
|
13
|
+
* Entity invalidation utilities.
|
|
19
14
|
* Methods are exposed publicly since in rare cases they may need to be called manually.
|
|
20
15
|
*/
|
|
21
|
-
export class
|
|
16
|
+
export class EntityInvalidationUtils<
|
|
22
17
|
TFields extends Record<string, any>,
|
|
23
18
|
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
24
19
|
TViewerContext extends ViewerContext,
|
|
@@ -33,17 +28,8 @@ export class EntityLoaderUtils<
|
|
|
33
28
|
TSelectedFields extends keyof TFields,
|
|
34
29
|
> {
|
|
35
30
|
constructor(
|
|
36
|
-
private readonly viewerContext: TViewerContext,
|
|
37
|
-
private readonly queryContext: EntityQueryContext,
|
|
38
|
-
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
39
|
-
TFields,
|
|
40
|
-
TIDField,
|
|
41
|
-
TViewerContext,
|
|
42
|
-
TEntity,
|
|
43
|
-
TSelectedFields
|
|
44
|
-
>,
|
|
45
31
|
private readonly entityConfiguration: EntityConfiguration<TFields, TIDField>,
|
|
46
|
-
|
|
32
|
+
_entityClass: IEntityClass<
|
|
47
33
|
TFields,
|
|
48
34
|
TIDField,
|
|
49
35
|
TViewerContext,
|
|
@@ -51,8 +37,6 @@ export class EntityLoaderUtils<
|
|
|
51
37
|
TPrivacyPolicy,
|
|
52
38
|
TSelectedFields
|
|
53
39
|
>,
|
|
54
|
-
private readonly entitySelectedFields: TSelectedFields[] | undefined,
|
|
55
|
-
private readonly privacyPolicy: TPrivacyPolicy,
|
|
56
40
|
private readonly dataManager: EntityDataManager<TFields, TIDField>,
|
|
57
41
|
protected readonly metricsAdapter: IEntityMetricsAdapter,
|
|
58
42
|
) {}
|
|
@@ -128,79 +112,4 @@ export class EntityLoaderUtils<
|
|
|
128
112
|
): void {
|
|
129
113
|
this.invalidateFieldsForTransaction(queryContext, entity.getAllDatabaseFields());
|
|
130
114
|
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Construct an entity from a fields object (applying field selection if applicable),
|
|
134
|
-
* checking that the ID field is specified.
|
|
135
|
-
*
|
|
136
|
-
* @param fieldsObject - fields object
|
|
137
|
-
*/
|
|
138
|
-
public constructEntity(fieldsObject: TFields): TEntity {
|
|
139
|
-
const idField = this.entityConfiguration.idField;
|
|
140
|
-
const id = nullthrows(fieldsObject[idField], 'must provide ID to create an entity');
|
|
141
|
-
const entitySelectedFields =
|
|
142
|
-
this.entitySelectedFields ?? Array.from(this.entityConfiguration.schema.keys());
|
|
143
|
-
const selectedFields = pick(fieldsObject, entitySelectedFields);
|
|
144
|
-
return new this.entityClass({
|
|
145
|
-
viewerContext: this.viewerContext,
|
|
146
|
-
id: id as TFields[TIDField],
|
|
147
|
-
databaseFields: fieldsObject,
|
|
148
|
-
selectedFields,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Construct and authorize entities from fields map, returning error results for entities that fail
|
|
154
|
-
* to construct or fail to authorize.
|
|
155
|
-
*
|
|
156
|
-
* @param map - map from an arbitrary key type to an array of entity field objects
|
|
157
|
-
*/
|
|
158
|
-
public async constructAndAuthorizeEntitiesAsync<K>(
|
|
159
|
-
map: ReadonlyMap<K, readonly Readonly<TFields>[]>,
|
|
160
|
-
): Promise<ReadonlyMap<K, readonly Result<TEntity>[]>> {
|
|
161
|
-
return await mapMapAsync(map, async (fieldObjects) => {
|
|
162
|
-
return await this.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Construct and authorize entities from field objects array, returning error results for entities that fail
|
|
168
|
-
* to construct or fail to authorize.
|
|
169
|
-
*
|
|
170
|
-
* @param fieldObjects - array of field objects
|
|
171
|
-
*/
|
|
172
|
-
public async constructAndAuthorizeEntitiesArrayAsync(
|
|
173
|
-
fieldObjects: readonly Readonly<TFields>[],
|
|
174
|
-
): Promise<readonly Result<TEntity>[]> {
|
|
175
|
-
const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
|
|
176
|
-
return await Promise.all(
|
|
177
|
-
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
|
178
|
-
if (!uncheckedEntityResult.ok) {
|
|
179
|
-
return uncheckedEntityResult;
|
|
180
|
-
}
|
|
181
|
-
return await asyncResult(
|
|
182
|
-
this.privacyPolicy.authorizeReadAsync(
|
|
183
|
-
this.viewerContext,
|
|
184
|
-
this.queryContext,
|
|
185
|
-
this.privacyPolicyEvaluationContext,
|
|
186
|
-
uncheckedEntityResult.value,
|
|
187
|
-
this.metricsAdapter,
|
|
188
|
-
),
|
|
189
|
-
);
|
|
190
|
-
}),
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private tryConstructEntities(fieldsObjects: readonly TFields[]): readonly Result<TEntity>[] {
|
|
195
|
-
return fieldsObjects.map((fieldsObject) => {
|
|
196
|
-
try {
|
|
197
|
-
return result(this.constructEntity(fieldsObject));
|
|
198
|
-
} catch (e) {
|
|
199
|
-
if (!(e instanceof Error)) {
|
|
200
|
-
throw e;
|
|
201
|
-
}
|
|
202
|
-
return result(e);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
115
|
}
|
package/src/EntityLoader.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AuthorizationResultBasedEntityLoader } from './AuthorizationResultBasedEntityLoader';
|
|
2
2
|
import { EnforcingEntityLoader } from './EnforcingEntityLoader';
|
|
3
3
|
import { IEntityClass } from './Entity';
|
|
4
|
-
import { EntityLoaderUtils } from './EntityLoaderUtils';
|
|
5
4
|
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
|
|
6
5
|
import { EntityQueryContext } from './EntityQueryContext';
|
|
7
6
|
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
@@ -73,19 +72,4 @@ export class EntityLoader<
|
|
|
73
72
|
.getLoaderFactory()
|
|
74
73
|
.forLoad(this.queryContext, { previousValue: null, cascadingDeleteCause: null });
|
|
75
74
|
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Entity loader utilities for things like cache invalidation, entity construction, and authorization.
|
|
79
|
-
* Calling into these should only be necessary in rare cases.
|
|
80
|
-
*/
|
|
81
|
-
public utils(): EntityLoaderUtils<
|
|
82
|
-
TFields,
|
|
83
|
-
TIDField,
|
|
84
|
-
TViewerContext,
|
|
85
|
-
TEntity,
|
|
86
|
-
TPrivacyPolicy,
|
|
87
|
-
TSelectedFields
|
|
88
|
-
> {
|
|
89
|
-
return this.withAuthorizationResults().utils;
|
|
90
|
-
}
|
|
91
75
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AuthorizationResultBasedEntityLoader } from './AuthorizationResultBasedEntityLoader';
|
|
2
2
|
import { EntityCompanion } from './EntityCompanion';
|
|
3
|
-
import {
|
|
3
|
+
import { EntityConstructionUtils } from './EntityConstructionUtils';
|
|
4
|
+
import { EntityInvalidationUtils } from './EntityInvalidationUtils';
|
|
4
5
|
import { EntityPrivacyPolicy, EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
|
|
5
6
|
import { EntityQueryContext } from './EntityQueryContext';
|
|
6
7
|
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
@@ -38,6 +39,52 @@ export class EntityLoaderFactory<
|
|
|
38
39
|
protected readonly metricsAdapter: IEntityMetricsAdapter,
|
|
39
40
|
) {}
|
|
40
41
|
|
|
42
|
+
invalidationUtils(): EntityInvalidationUtils<
|
|
43
|
+
TFields,
|
|
44
|
+
TIDField,
|
|
45
|
+
TViewerContext,
|
|
46
|
+
TEntity,
|
|
47
|
+
TPrivacyPolicy,
|
|
48
|
+
TSelectedFields
|
|
49
|
+
> {
|
|
50
|
+
return new EntityInvalidationUtils(
|
|
51
|
+
this.entityCompanion.entityCompanionDefinition.entityConfiguration,
|
|
52
|
+
this.entityCompanion.entityCompanionDefinition.entityClass,
|
|
53
|
+
this.dataManager,
|
|
54
|
+
this.metricsAdapter,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
constructionUtils(
|
|
59
|
+
viewerContext: TViewerContext,
|
|
60
|
+
queryContext: EntityQueryContext,
|
|
61
|
+
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
62
|
+
TFields,
|
|
63
|
+
TIDField,
|
|
64
|
+
TViewerContext,
|
|
65
|
+
TEntity,
|
|
66
|
+
TSelectedFields
|
|
67
|
+
>,
|
|
68
|
+
): EntityConstructionUtils<
|
|
69
|
+
TFields,
|
|
70
|
+
TIDField,
|
|
71
|
+
TViewerContext,
|
|
72
|
+
TEntity,
|
|
73
|
+
TPrivacyPolicy,
|
|
74
|
+
TSelectedFields
|
|
75
|
+
> {
|
|
76
|
+
return new EntityConstructionUtils(
|
|
77
|
+
viewerContext,
|
|
78
|
+
queryContext,
|
|
79
|
+
privacyPolicyEvaluationContext,
|
|
80
|
+
this.entityCompanion.entityCompanionDefinition.entityConfiguration,
|
|
81
|
+
this.entityCompanion.entityCompanionDefinition.entityClass,
|
|
82
|
+
this.entityCompanion.entityCompanionDefinition.entitySelectedFields,
|
|
83
|
+
this.entityCompanion.privacyPolicy,
|
|
84
|
+
this.metricsAdapter,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
41
88
|
/**
|
|
42
89
|
* Vend loader for loading an entity in a given query context.
|
|
43
90
|
* @param viewerContext - viewer context of loading user
|
|
@@ -61,16 +108,10 @@ export class EntityLoaderFactory<
|
|
|
61
108
|
TPrivacyPolicy,
|
|
62
109
|
TSelectedFields
|
|
63
110
|
> {
|
|
64
|
-
const
|
|
111
|
+
const constructionUtils = this.constructionUtils(
|
|
65
112
|
viewerContext,
|
|
66
113
|
queryContext,
|
|
67
114
|
privacyPolicyEvaluationContext,
|
|
68
|
-
this.entityCompanion.entityCompanionDefinition.entityConfiguration,
|
|
69
|
-
this.entityCompanion.entityCompanionDefinition.entityClass,
|
|
70
|
-
this.entityCompanion.entityCompanionDefinition.entitySelectedFields,
|
|
71
|
-
this.entityCompanion.privacyPolicy,
|
|
72
|
-
this.dataManager,
|
|
73
|
-
this.metricsAdapter,
|
|
74
115
|
);
|
|
75
116
|
|
|
76
117
|
return new AuthorizationResultBasedEntityLoader(
|
|
@@ -78,8 +119,7 @@ export class EntityLoaderFactory<
|
|
|
78
119
|
this.entityCompanion.entityCompanionDefinition.entityConfiguration,
|
|
79
120
|
this.entityCompanion.entityCompanionDefinition.entityClass,
|
|
80
121
|
this.dataManager,
|
|
81
|
-
|
|
82
|
-
utils,
|
|
122
|
+
constructionUtils,
|
|
83
123
|
);
|
|
84
124
|
}
|
|
85
125
|
}
|
|
@@ -32,6 +32,25 @@ export type EntityPrivacyPolicyEvaluationContext<
|
|
|
32
32
|
cascadingDeleteCause: EntityCascadingDeletionInfo | null;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
export type EntityPrivacyPolicyRuleEvaluationContext<
|
|
36
|
+
TFields extends Record<string, any>,
|
|
37
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
38
|
+
TViewerContext extends ViewerContext,
|
|
39
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
40
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
41
|
+
> = EntityPrivacyPolicyEvaluationContext<
|
|
42
|
+
TFields,
|
|
43
|
+
TIDField,
|
|
44
|
+
TViewerContext,
|
|
45
|
+
TEntity,
|
|
46
|
+
TSelectedFields
|
|
47
|
+
> & {
|
|
48
|
+
/**
|
|
49
|
+
* The CRUD action for which the privacy policy rule is being evaluated.
|
|
50
|
+
*/
|
|
51
|
+
action: EntityAuthorizationAction;
|
|
52
|
+
};
|
|
53
|
+
|
|
35
54
|
/**
|
|
36
55
|
* Evaluation mode for a EntityPrivacyPolicy. Useful when transitioning to
|
|
37
56
|
* using Entity for privacy.
|
|
@@ -123,6 +142,9 @@ export abstract class EntityPrivacyPolicy<
|
|
|
123
142
|
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
124
143
|
TSelectedFields extends keyof TFields = keyof TFields,
|
|
125
144
|
> {
|
|
145
|
+
/**
|
|
146
|
+
* List of rules to evaluate for create authorization.
|
|
147
|
+
*/
|
|
126
148
|
protected readonly createRules: readonly PrivacyPolicyRule<
|
|
127
149
|
TFields,
|
|
128
150
|
TIDField,
|
|
@@ -130,6 +152,10 @@ export abstract class EntityPrivacyPolicy<
|
|
|
130
152
|
TEntity,
|
|
131
153
|
TSelectedFields
|
|
132
154
|
>[] = [];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* List of rules to evaluate for read authorization.
|
|
158
|
+
*/
|
|
133
159
|
protected readonly readRules: readonly PrivacyPolicyRule<
|
|
134
160
|
TFields,
|
|
135
161
|
TIDField,
|
|
@@ -137,6 +163,10 @@ export abstract class EntityPrivacyPolicy<
|
|
|
137
163
|
TEntity,
|
|
138
164
|
TSelectedFields
|
|
139
165
|
>[] = [];
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* List of rules to evaluate for update authorization.
|
|
169
|
+
*/
|
|
140
170
|
protected readonly updateRules: readonly PrivacyPolicyRule<
|
|
141
171
|
TFields,
|
|
142
172
|
TIDField,
|
|
@@ -144,6 +174,10 @@ export abstract class EntityPrivacyPolicy<
|
|
|
144
174
|
TEntity,
|
|
145
175
|
TSelectedFields
|
|
146
176
|
>[] = [];
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* List of rules to evaluate for delete authorization.
|
|
180
|
+
*/
|
|
147
181
|
protected readonly deleteRules: readonly PrivacyPolicyRule<
|
|
148
182
|
TFields,
|
|
149
183
|
TIDField,
|
|
@@ -156,6 +190,9 @@ export abstract class EntityPrivacyPolicy<
|
|
|
156
190
|
* Get the privacy policy evaluation mode and deny handler for this policy.
|
|
157
191
|
* Defaults to normal enforcing policy.
|
|
158
192
|
*
|
|
193
|
+
* DRY_RUN mode is useful for testing and logging the effects of a policy without actually enforcing it, such as when
|
|
194
|
+
* first rolling out a new policy. Entities that fail the policy will be allowed so caution should be take when using.
|
|
195
|
+
*
|
|
159
196
|
* @remarks
|
|
160
197
|
*
|
|
161
198
|
* Override to enable dry run evaluation of the policy.
|
|
@@ -204,7 +241,9 @@ export abstract class EntityPrivacyPolicy<
|
|
|
204
241
|
* Authorize an entity against read policy.
|
|
205
242
|
* @param viewerContext - viewer context of user reading the entity
|
|
206
243
|
* @param queryContext - query context in which to perform the read authorization
|
|
244
|
+
* @param evaluationContext - context about the reason for this privacy policy evaluation
|
|
207
245
|
* @param entity - entity to authorize
|
|
246
|
+
* @param metricsAdapter - adapter for logging metrics about this authorization
|
|
208
247
|
* @returns entity if authorized
|
|
209
248
|
* @throws EntityNotAuthorizedError when not authorized
|
|
210
249
|
*/
|
|
@@ -236,7 +275,9 @@ export abstract class EntityPrivacyPolicy<
|
|
|
236
275
|
* Authorize an entity against update policy.
|
|
237
276
|
* @param viewerContext - viewer context of user updating the entity
|
|
238
277
|
* @param queryContext - query context in which to perform the update authorization
|
|
278
|
+
* @param evaluationContext - context about the reason for this privacy policy evaluation
|
|
239
279
|
* @param entity - entity to authorize
|
|
280
|
+
* @param metricsAdapter - adapter for logging metrics about this authorization
|
|
240
281
|
* @returns entity if authorized
|
|
241
282
|
* @throws EntityNotAuthorizedError when not authorized
|
|
242
283
|
*/
|
|
@@ -268,7 +309,9 @@ export abstract class EntityPrivacyPolicy<
|
|
|
268
309
|
* Authorize an entity against deletion policy.
|
|
269
310
|
* @param viewerContext - viewer context of user deleting the entity
|
|
270
311
|
* @param queryContext - query context in which to perform the delete authorization
|
|
312
|
+
* @param evaluationContext - context about the reason for this privacy policy evaluation
|
|
271
313
|
* @param entity - entity to authorize
|
|
314
|
+
* @param metricsAdapter - adapter for logging metrics about this authorization
|
|
272
315
|
* @returns entity if authorized
|
|
273
316
|
* @throws EntityNotAuthorizedError when not authorized
|
|
274
317
|
*/
|
|
@@ -436,7 +479,7 @@ export abstract class EntityPrivacyPolicy<
|
|
|
436
479
|
const ruleEvaluationResult = await rule.evaluateAsync(
|
|
437
480
|
viewerContext,
|
|
438
481
|
queryContext,
|
|
439
|
-
evaluationContext,
|
|
482
|
+
{ ...evaluationContext, action },
|
|
440
483
|
entity,
|
|
441
484
|
);
|
|
442
485
|
switch (ruleEvaluationResult) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Result } from '@expo/results';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { IEntityClass } from './Entity';
|
|
4
|
+
import { EntityConstructionUtils } from './EntityConstructionUtils';
|
|
4
5
|
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
|
|
6
|
+
import { EntityQueryContext } from './EntityQueryContext';
|
|
5
7
|
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
6
8
|
import { ViewerContext } from './ViewerContext';
|
|
7
9
|
import { mapMap } from './utils/collections/maps';
|
|
@@ -60,7 +62,7 @@ export abstract class EntitySecondaryCacheLoader<
|
|
|
60
62
|
> {
|
|
61
63
|
constructor(
|
|
62
64
|
private readonly secondaryEntityCache: ISecondaryEntityCache<TFields, TLoadParams>,
|
|
63
|
-
|
|
65
|
+
private readonly constructionUtils: EntityConstructionUtils<
|
|
64
66
|
TFields,
|
|
65
67
|
TIDField,
|
|
66
68
|
TViewerContext,
|
|
@@ -84,7 +86,7 @@ export abstract class EntitySecondaryCacheLoader<
|
|
|
84
86
|
);
|
|
85
87
|
|
|
86
88
|
// convert value to and from array to reuse complex code
|
|
87
|
-
const entitiesMap = await this.
|
|
89
|
+
const entitiesMap = await this.constructionUtils.constructAndAuthorizeEntitiesAsync(
|
|
88
90
|
mapMap(loadParamsToFieldObjects, (fieldObject) => (fieldObject ? [fieldObject] : [])),
|
|
89
91
|
);
|
|
90
92
|
return mapMap(entitiesMap, (fieldObjects) => fieldObjects[0] ?? null);
|
|
@@ -109,4 +111,53 @@ export abstract class EntitySecondaryCacheLoader<
|
|
|
109
111
|
protected abstract fetchObjectsFromDatabaseAsync(
|
|
110
112
|
loadParamsArray: readonly Readonly<TLoadParams>[],
|
|
111
113
|
): Promise<ReadonlyMap<Readonly<Readonly<TLoadParams>>, Readonly<TFields> | null>>;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Helper to get construction utils for instantiating a EntitySecondaryCacheLoader.
|
|
117
|
+
*
|
|
118
|
+
* @param entityClass - the entity class for which to get construction utils for
|
|
119
|
+
* @param viewerContext - the viewer context to use for construction utils
|
|
120
|
+
* @param queryContext - query context to use for construction utils
|
|
121
|
+
* @returns construction utils for the given entity class and viewer context
|
|
122
|
+
*/
|
|
123
|
+
public static getConstructionUtilsForEntityClass<
|
|
124
|
+
TFields extends Record<string, any>,
|
|
125
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
126
|
+
TViewerContext extends ViewerContext,
|
|
127
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
128
|
+
TPrivacyPolicy extends EntityPrivacyPolicy<
|
|
129
|
+
TFields,
|
|
130
|
+
TIDField,
|
|
131
|
+
TViewerContext,
|
|
132
|
+
TEntity,
|
|
133
|
+
TSelectedFields
|
|
134
|
+
>,
|
|
135
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
136
|
+
>(
|
|
137
|
+
entityClass: IEntityClass<
|
|
138
|
+
TFields,
|
|
139
|
+
TIDField,
|
|
140
|
+
TViewerContext,
|
|
141
|
+
TEntity,
|
|
142
|
+
TPrivacyPolicy,
|
|
143
|
+
TSelectedFields
|
|
144
|
+
>,
|
|
145
|
+
viewerContext: TViewerContext,
|
|
146
|
+
queryContext: EntityQueryContext = viewerContext
|
|
147
|
+
.getViewerScopedEntityCompanionForClass(entityClass)
|
|
148
|
+
.getQueryContextProvider()
|
|
149
|
+
.getQueryContext(),
|
|
150
|
+
): EntityConstructionUtils<
|
|
151
|
+
TFields,
|
|
152
|
+
TIDField,
|
|
153
|
+
TViewerContext,
|
|
154
|
+
TEntity,
|
|
155
|
+
TPrivacyPolicy,
|
|
156
|
+
TSelectedFields
|
|
157
|
+
> {
|
|
158
|
+
return viewerContext
|
|
159
|
+
.getViewerScopedEntityCompanionForClass(entityClass)
|
|
160
|
+
.getLoaderFactory()
|
|
161
|
+
.constructionUtils(queryContext, { previousValue: null, cascadingDeleteCause: null });
|
|
162
|
+
}
|
|
112
163
|
}
|
package/src/ReadonlyEntity.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { EnforcingEntityAssociationLoader } from './EnforcingEntityAssociationLo
|
|
|
6
6
|
import { EnforcingEntityLoader } from './EnforcingEntityLoader';
|
|
7
7
|
import { IEntityClass } from './Entity';
|
|
8
8
|
import { EntityAssociationLoader } from './EntityAssociationLoader';
|
|
9
|
+
import { EntityInvalidationUtils } from './EntityInvalidationUtils';
|
|
9
10
|
import { EntityLoader } from './EntityLoader';
|
|
10
|
-
import { EntityLoaderUtils } from './EntityLoaderUtils';
|
|
11
11
|
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
|
|
12
12
|
import { EntityQueryContext } from './EntityQueryContext';
|
|
13
13
|
import { ViewerContext } from './ViewerContext';
|
|
@@ -230,11 +230,10 @@ export abstract class ReadonlyEntity<
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* @param queryContext - query context in which to perform the load
|
|
233
|
+
* Utilities for entity invalidation.
|
|
234
|
+
* Call these manually to keep entity cache consistent when performing operations outside of the entity framework.
|
|
236
235
|
*/
|
|
237
|
-
static
|
|
236
|
+
static invalidationUtils<
|
|
238
237
|
TMFields extends object,
|
|
239
238
|
TMIDField extends keyof NonNullable<Pick<TMFields, TMSelectedFields>>,
|
|
240
239
|
TMViewerContext extends ViewerContext,
|
|
@@ -258,11 +257,7 @@ export abstract class ReadonlyEntity<
|
|
|
258
257
|
TMSelectedFields
|
|
259
258
|
>,
|
|
260
259
|
viewerContext: TMViewerContext2,
|
|
261
|
-
|
|
262
|
-
.getViewerScopedEntityCompanionForClass(this)
|
|
263
|
-
.getQueryContextProvider()
|
|
264
|
-
.getQueryContext(),
|
|
265
|
-
): EntityLoaderUtils<
|
|
260
|
+
): EntityInvalidationUtils<
|
|
266
261
|
TMFields,
|
|
267
262
|
TMIDField,
|
|
268
263
|
TMViewerContext,
|
|
@@ -270,6 +265,9 @@ export abstract class ReadonlyEntity<
|
|
|
270
265
|
TMPrivacyPolicy,
|
|
271
266
|
TMSelectedFields
|
|
272
267
|
> {
|
|
273
|
-
return
|
|
268
|
+
return viewerContext
|
|
269
|
+
.getViewerScopedEntityCompanionForClass(this)
|
|
270
|
+
.getLoaderFactory()
|
|
271
|
+
.invalidationUtils();
|
|
274
272
|
}
|
|
275
273
|
}
|
package/src/ViewerContext.ts
CHANGED
|
@@ -64,30 +64,30 @@ export class ViewerContext {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Get the regular (non-transactional) query context for a database
|
|
68
|
-
* @param
|
|
67
|
+
* Get the regular (non-transactional) query context for a database adapter flavor.
|
|
68
|
+
* @param databaseAdapterFlavor - database adapter flavor
|
|
69
69
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
getQueryContextForDatabaseAdapterFlavor(
|
|
71
|
+
databaseAdapterFlavor: DatabaseAdapterFlavor,
|
|
72
72
|
): EntityQueryContext {
|
|
73
73
|
return this.entityCompanionProvider
|
|
74
|
-
.
|
|
74
|
+
.getQueryContextProviderForDatabaseAdapterFlavor(databaseAdapterFlavor)
|
|
75
75
|
.getQueryContext();
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Run a transaction of specified database
|
|
79
|
+
* Run a transaction of specified database adapter flavor and execute the provided
|
|
80
80
|
* transaction-scoped closure within the transaction.
|
|
81
|
-
* @param
|
|
81
|
+
* @param databaseAdapterFlavor - databaseAdapterFlavor
|
|
82
82
|
* @param transactionScope - async callback to execute within the transaction
|
|
83
83
|
*/
|
|
84
|
-
async
|
|
85
|
-
|
|
84
|
+
async runInTransactionForDatabaseAdapterFlavorAsync<TResult>(
|
|
85
|
+
databaseAdapterFlavor: DatabaseAdapterFlavor,
|
|
86
86
|
transactionScope: (queryContext: EntityTransactionalQueryContext) => Promise<TResult>,
|
|
87
87
|
transactionConfig?: TransactionConfig,
|
|
88
88
|
): Promise<TResult> {
|
|
89
89
|
return await this.entityCompanionProvider
|
|
90
|
-
.
|
|
90
|
+
.getQueryContextProviderForDatabaseAdapterFlavor(databaseAdapterFlavor)
|
|
91
91
|
.getQueryContext()
|
|
92
92
|
.runInTransactionIfNotInTransactionAsync(transactionScope, transactionConfig);
|
|
93
93
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AuthorizationResultBasedEntityLoader } from './AuthorizationResultBasedEntityLoader';
|
|
2
|
+
import { EntityConstructionUtils } from './EntityConstructionUtils';
|
|
3
|
+
import { EntityInvalidationUtils } from './EntityInvalidationUtils';
|
|
2
4
|
import { EntityLoaderFactory } from './EntityLoaderFactory';
|
|
3
5
|
import { EntityPrivacyPolicy, EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
|
|
4
6
|
import { EntityQueryContext } from './EntityQueryContext';
|
|
@@ -34,6 +36,41 @@ export class ViewerScopedEntityLoaderFactory<
|
|
|
34
36
|
private readonly viewerContext: TViewerContext,
|
|
35
37
|
) {}
|
|
36
38
|
|
|
39
|
+
invalidationUtils(): EntityInvalidationUtils<
|
|
40
|
+
TFields,
|
|
41
|
+
TIDField,
|
|
42
|
+
TViewerContext,
|
|
43
|
+
TEntity,
|
|
44
|
+
TPrivacyPolicy,
|
|
45
|
+
TSelectedFields
|
|
46
|
+
> {
|
|
47
|
+
return this.entityLoaderFactory.invalidationUtils();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
constructionUtils(
|
|
51
|
+
queryContext: EntityQueryContext,
|
|
52
|
+
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
53
|
+
TFields,
|
|
54
|
+
TIDField,
|
|
55
|
+
TViewerContext,
|
|
56
|
+
TEntity,
|
|
57
|
+
TSelectedFields
|
|
58
|
+
>,
|
|
59
|
+
): EntityConstructionUtils<
|
|
60
|
+
TFields,
|
|
61
|
+
TIDField,
|
|
62
|
+
TViewerContext,
|
|
63
|
+
TEntity,
|
|
64
|
+
TPrivacyPolicy,
|
|
65
|
+
TSelectedFields
|
|
66
|
+
> {
|
|
67
|
+
return this.entityLoaderFactory.constructionUtils(
|
|
68
|
+
this.viewerContext,
|
|
69
|
+
queryContext,
|
|
70
|
+
privacyPolicyEvaluationContext,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
37
74
|
forLoad(
|
|
38
75
|
queryContext: EntityQueryContext,
|
|
39
76
|
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
@@ -5,8 +5,8 @@ import { AuthorizationResultBasedEntityLoader } from '../AuthorizationResultBase
|
|
|
5
5
|
import { Entity } from '../Entity';
|
|
6
6
|
import { EntityCompanionDefinition } from '../EntityCompanionProvider';
|
|
7
7
|
import { EntityConfiguration } from '../EntityConfiguration';
|
|
8
|
+
import { EntityConstructionUtils } from '../EntityConstructionUtils';
|
|
8
9
|
import { StringField } from '../EntityFields';
|
|
9
|
-
import { EntityLoaderUtils } from '../EntityLoaderUtils';
|
|
10
10
|
import { EntityPrivacyPolicy, EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
|
|
11
11
|
import { ViewerContext } from '../ViewerContext';
|
|
12
12
|
import { EntityDataManager } from '../internal/EntityDataManager';
|
|
@@ -164,7 +164,7 @@ describe(AuthorizationResultBasedEntityLoader, () => {
|
|
|
164
164
|
metricsAdapter,
|
|
165
165
|
TestEntity.name,
|
|
166
166
|
);
|
|
167
|
-
const
|
|
167
|
+
const constructionUtils = new EntityConstructionUtils(
|
|
168
168
|
viewerContext,
|
|
169
169
|
queryContext,
|
|
170
170
|
privacyPolicyEvaluationContext,
|
|
@@ -172,7 +172,6 @@ describe(AuthorizationResultBasedEntityLoader, () => {
|
|
|
172
172
|
TestEntity,
|
|
173
173
|
/* entitySelectedFields */ undefined,
|
|
174
174
|
privacyPolicy,
|
|
175
|
-
dataManager,
|
|
176
175
|
metricsAdapter,
|
|
177
176
|
);
|
|
178
177
|
const entityLoader = new AuthorizationResultBasedEntityLoader(
|
|
@@ -180,8 +179,7 @@ describe(AuthorizationResultBasedEntityLoader, () => {
|
|
|
180
179
|
testEntityConfiguration,
|
|
181
180
|
TestEntity,
|
|
182
181
|
dataManager,
|
|
183
|
-
|
|
184
|
-
utils,
|
|
182
|
+
constructionUtils,
|
|
185
183
|
);
|
|
186
184
|
|
|
187
185
|
let capturedThrownThing1: any;
|