@expo/entity 0.54.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 +6 -1
- package/build/src/index.js +6 -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 +10 -0
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js +19 -0
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.d.ts +10 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js +19 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.d.ts +66 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js +75 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.d.ts +12 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js +23 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js.map +1 -0
- 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 +5 -5
- 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 +6 -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 +47 -0
- package/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.ts +47 -0
- package/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.ts +177 -0
- package/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.ts +46 -0
- package/src/rules/PrivacyPolicyRule.ts +2 -2
- package/src/rules/__tests__/AllowIfAllSubRulesAllowPrivacyPolicyRule-test.ts +64 -0
- package/src/rules/__tests__/AllowIfAnySubRuleAllowsPrivacyPolicyRule-test.ts +64 -0
- package/src/rules/__tests__/AllowIfInParentCascadeDeletionPrivacyPolicyRule-test.ts +268 -0
- 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 +47 -0
- 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
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Result, asyncResult, result } from '@expo/results';
|
|
2
|
+
import invariant from 'invariant';
|
|
3
|
+
import nullthrows from 'nullthrows';
|
|
4
|
+
|
|
5
|
+
import { IEntityClass } from './Entity';
|
|
6
|
+
import { EntityConfiguration } from './EntityConfiguration';
|
|
7
|
+
import { EntityPrivacyPolicy, EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
|
|
8
|
+
import { EntityQueryContext } from './EntityQueryContext';
|
|
9
|
+
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
10
|
+
import { ViewerContext } from './ViewerContext';
|
|
11
|
+
import { pick } from './entityUtils';
|
|
12
|
+
import { EntityInvalidFieldValueError } from './errors/EntityInvalidFieldValueError';
|
|
13
|
+
import { IEntityMetricsAdapter } from './metrics/IEntityMetricsAdapter';
|
|
14
|
+
import { mapMapAsync } from './utils/collections/maps';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Common entity loader utilities for entity construction and authorization.
|
|
18
|
+
* Methods are exposed publicly since in rare cases they may need to be called manually.
|
|
19
|
+
*/
|
|
20
|
+
export class EntityConstructionUtils<
|
|
21
|
+
TFields extends Record<string, any>,
|
|
22
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
23
|
+
TViewerContext extends ViewerContext,
|
|
24
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
25
|
+
TPrivacyPolicy extends EntityPrivacyPolicy<
|
|
26
|
+
TFields,
|
|
27
|
+
TIDField,
|
|
28
|
+
TViewerContext,
|
|
29
|
+
TEntity,
|
|
30
|
+
TSelectedFields
|
|
31
|
+
>,
|
|
32
|
+
TSelectedFields extends keyof TFields,
|
|
33
|
+
> {
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly viewerContext: TViewerContext,
|
|
36
|
+
private readonly queryContext: EntityQueryContext,
|
|
37
|
+
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
38
|
+
TFields,
|
|
39
|
+
TIDField,
|
|
40
|
+
TViewerContext,
|
|
41
|
+
TEntity,
|
|
42
|
+
TSelectedFields
|
|
43
|
+
>,
|
|
44
|
+
private readonly entityConfiguration: EntityConfiguration<TFields, TIDField>,
|
|
45
|
+
private readonly entityClass: IEntityClass<
|
|
46
|
+
TFields,
|
|
47
|
+
TIDField,
|
|
48
|
+
TViewerContext,
|
|
49
|
+
TEntity,
|
|
50
|
+
TPrivacyPolicy,
|
|
51
|
+
TSelectedFields
|
|
52
|
+
>,
|
|
53
|
+
private readonly entitySelectedFields: TSelectedFields[] | undefined,
|
|
54
|
+
private readonly privacyPolicy: TPrivacyPolicy,
|
|
55
|
+
protected readonly metricsAdapter: IEntityMetricsAdapter,
|
|
56
|
+
) {}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Construct an entity from a fields object (applying field selection if applicable),
|
|
60
|
+
* checking that the ID field is specified.
|
|
61
|
+
*
|
|
62
|
+
* @param fieldsObject - fields object
|
|
63
|
+
*/
|
|
64
|
+
public constructEntity(fieldsObject: TFields): TEntity {
|
|
65
|
+
const idField = this.entityConfiguration.idField;
|
|
66
|
+
const id = nullthrows(fieldsObject[idField], 'must provide ID to create an entity');
|
|
67
|
+
const entitySelectedFields =
|
|
68
|
+
this.entitySelectedFields ?? Array.from(this.entityConfiguration.schema.keys());
|
|
69
|
+
const selectedFields = pick(fieldsObject, entitySelectedFields);
|
|
70
|
+
return new this.entityClass({
|
|
71
|
+
viewerContext: this.viewerContext,
|
|
72
|
+
id: id as TFields[TIDField],
|
|
73
|
+
databaseFields: fieldsObject,
|
|
74
|
+
selectedFields,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Construct and authorize entities from fields map, returning error results for entities that fail
|
|
80
|
+
* to construct or fail to authorize.
|
|
81
|
+
*
|
|
82
|
+
* @param map - map from an arbitrary key type to an array of entity field objects
|
|
83
|
+
*/
|
|
84
|
+
public async constructAndAuthorizeEntitiesAsync<K>(
|
|
85
|
+
map: ReadonlyMap<K, readonly Readonly<TFields>[]>,
|
|
86
|
+
): Promise<ReadonlyMap<K, readonly Result<TEntity>[]>> {
|
|
87
|
+
return await mapMapAsync(map, async (fieldObjects) => {
|
|
88
|
+
return await this.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Construct and authorize entities from field objects array, returning error results for entities that fail
|
|
94
|
+
* to construct or fail to authorize.
|
|
95
|
+
*
|
|
96
|
+
* @param fieldObjects - array of field objects
|
|
97
|
+
*/
|
|
98
|
+
public async constructAndAuthorizeEntitiesArrayAsync(
|
|
99
|
+
fieldObjects: readonly Readonly<TFields>[],
|
|
100
|
+
): Promise<readonly Result<TEntity>[]> {
|
|
101
|
+
const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
|
|
102
|
+
return await Promise.all(
|
|
103
|
+
uncheckedEntityResults.map((uncheckedEntityResult) =>
|
|
104
|
+
this.authorizeEntityResultAsync(uncheckedEntityResult),
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async authorizeEntityResultAsync(
|
|
110
|
+
uncheckedEntityResult: Result<TEntity>,
|
|
111
|
+
): Promise<Result<TEntity>> {
|
|
112
|
+
if (!uncheckedEntityResult.ok) {
|
|
113
|
+
return uncheckedEntityResult;
|
|
114
|
+
}
|
|
115
|
+
return await asyncResult(
|
|
116
|
+
this.privacyPolicy.authorizeReadAsync(
|
|
117
|
+
this.viewerContext,
|
|
118
|
+
this.queryContext,
|
|
119
|
+
this.privacyPolicyEvaluationContext,
|
|
120
|
+
uncheckedEntityResult.value,
|
|
121
|
+
this.metricsAdapter,
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public async constructAndAuthorizeEntityAsync(
|
|
127
|
+
fieldsObject: Readonly<TFields>,
|
|
128
|
+
): Promise<Result<TEntity>> {
|
|
129
|
+
const uncheckedEntityResult = this.tryConstructEntity(fieldsObject);
|
|
130
|
+
return await this.authorizeEntityResultAsync(uncheckedEntityResult);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Validate that field values are valid according to the field's validation function.
|
|
135
|
+
*
|
|
136
|
+
* @param fieldName - field name to validate
|
|
137
|
+
* @param fieldValues - field values to validate
|
|
138
|
+
* @throws EntityInvalidFieldValueError when a field value is invalid
|
|
139
|
+
*/
|
|
140
|
+
public validateFieldAndValues<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
141
|
+
fieldName: N,
|
|
142
|
+
fieldValues: readonly TFields[N][],
|
|
143
|
+
): void {
|
|
144
|
+
const fieldDefinition = this.entityConfiguration.schema.get(fieldName);
|
|
145
|
+
invariant(fieldDefinition, `must have field definition for field = ${String(fieldName)}`);
|
|
146
|
+
for (const fieldValue of fieldValues) {
|
|
147
|
+
const isInputValid = fieldDefinition.validateInputValue(fieldValue);
|
|
148
|
+
if (!isInputValid) {
|
|
149
|
+
throw new EntityInvalidFieldValueError(this.entityClass, fieldName, fieldValue);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private tryConstructEntities(fieldsObjects: readonly TFields[]): readonly Result<TEntity>[] {
|
|
155
|
+
return fieldsObjects.map((fieldsObject) => this.tryConstructEntity(fieldsObject));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private tryConstructEntity(fieldsObject: TFields): Result<TEntity> {
|
|
159
|
+
try {
|
|
160
|
+
return result(this.constructEntity(fieldsObject));
|
|
161
|
+
} catch (e) {
|
|
162
|
+
if (!(e instanceof Error)) {
|
|
163
|
+
throw e;
|
|
164
|
+
}
|
|
165
|
+
return result(e);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -17,127 +17,6 @@ import {
|
|
|
17
17
|
} from './internal/EntityFieldTransformationUtils';
|
|
18
18
|
import { IEntityLoadKey, IEntityLoadValue } from './internal/EntityLoadInterfaces';
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Equality operand that is used for selecting entities with a field with a single value.
|
|
22
|
-
*/
|
|
23
|
-
export interface SingleValueFieldEqualityCondition<
|
|
24
|
-
TFields extends Record<string, any>,
|
|
25
|
-
N extends keyof TFields = keyof TFields,
|
|
26
|
-
> {
|
|
27
|
-
fieldName: N;
|
|
28
|
-
fieldValue: TFields[N];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Equality operand that is used for selecting entities with a field matching one of multiple values.
|
|
33
|
-
*/
|
|
34
|
-
export interface MultiValueFieldEqualityCondition<
|
|
35
|
-
TFields extends Record<string, any>,
|
|
36
|
-
N extends keyof TFields = keyof TFields,
|
|
37
|
-
> {
|
|
38
|
-
fieldName: N;
|
|
39
|
-
fieldValues: readonly TFields[N][];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* A single equality operand for use in a selection clause.
|
|
44
|
-
* See EntityLoader.loadManyByFieldEqualityConjunctionAsync documentation for examples.
|
|
45
|
-
*/
|
|
46
|
-
export type FieldEqualityCondition<
|
|
47
|
-
TFields extends Record<string, any>,
|
|
48
|
-
N extends keyof TFields = keyof TFields,
|
|
49
|
-
> = SingleValueFieldEqualityCondition<TFields, N> | MultiValueFieldEqualityCondition<TFields, N>;
|
|
50
|
-
|
|
51
|
-
export function isSingleValueFieldEqualityCondition<
|
|
52
|
-
TFields extends Record<string, any>,
|
|
53
|
-
N extends keyof TFields = keyof TFields,
|
|
54
|
-
>(
|
|
55
|
-
condition: FieldEqualityCondition<TFields, N>,
|
|
56
|
-
): condition is SingleValueFieldEqualityCondition<TFields, N> {
|
|
57
|
-
return (condition as SingleValueFieldEqualityCondition<TFields, N>).fieldValue !== undefined;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface TableFieldSingleValueEqualityCondition {
|
|
61
|
-
tableField: string;
|
|
62
|
-
tableValue: any;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface TableFieldMultiValueEqualityCondition {
|
|
66
|
-
tableField: string;
|
|
67
|
-
tableValues: readonly any[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Ordering options for `orderBy` clauses.
|
|
72
|
-
*/
|
|
73
|
-
export enum OrderByOrdering {
|
|
74
|
-
/**
|
|
75
|
-
* Ascending order (lowest to highest).
|
|
76
|
-
* Ascending order puts smaller values first, where "smaller" is defined in terms of the %3C operator.
|
|
77
|
-
*/
|
|
78
|
-
ASCENDING = 'asc',
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Descending order (highest to lowest).
|
|
82
|
-
* Descending order puts larger values first, where "larger" is defined in terms of the %3E operator.
|
|
83
|
-
*/
|
|
84
|
-
DESCENDING = 'desc',
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* SQL modifiers that only affect the selection but not the projection.
|
|
89
|
-
*/
|
|
90
|
-
export interface QuerySelectionModifiers<TFields extends Record<string, any>> {
|
|
91
|
-
/**
|
|
92
|
-
* Order the entities by specified columns and orders.
|
|
93
|
-
*/
|
|
94
|
-
orderBy?: {
|
|
95
|
-
/**
|
|
96
|
-
* The field name to order by.
|
|
97
|
-
*/
|
|
98
|
-
fieldName: keyof TFields;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* The OrderByOrdering to order by.
|
|
102
|
-
*/
|
|
103
|
-
order: OrderByOrdering;
|
|
104
|
-
}[];
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Skip the specified number of entities queried before returning.
|
|
108
|
-
*/
|
|
109
|
-
offset?: number;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Limit the number of entities returned.
|
|
113
|
-
*/
|
|
114
|
-
limit?: number;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export interface QuerySelectionModifiersWithOrderByRaw<
|
|
118
|
-
TFields extends Record<string, any>,
|
|
119
|
-
> extends QuerySelectionModifiers<TFields> {
|
|
120
|
-
/**
|
|
121
|
-
* Order the entities by a raw SQL `ORDER BY` clause.
|
|
122
|
-
*/
|
|
123
|
-
orderByRaw?: string;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export interface TableQuerySelectionModifiers {
|
|
127
|
-
orderBy:
|
|
128
|
-
| {
|
|
129
|
-
columnName: string;
|
|
130
|
-
order: OrderByOrdering;
|
|
131
|
-
}[]
|
|
132
|
-
| undefined;
|
|
133
|
-
offset: number | undefined;
|
|
134
|
-
limit: number | undefined;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export interface TableQuerySelectionModifiersWithOrderByRaw extends TableQuerySelectionModifiers {
|
|
138
|
-
orderByRaw: string | undefined;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
20
|
/**
|
|
142
21
|
* A database adapter is an interface by which entity objects can be
|
|
143
22
|
* fetched, inserted, updated, and deleted from a database. This base class
|
|
@@ -148,9 +27,9 @@ export abstract class EntityDatabaseAdapter<
|
|
|
148
27
|
TFields extends Record<string, any>,
|
|
149
28
|
TIDField extends keyof TFields,
|
|
150
29
|
> {
|
|
151
|
-
|
|
30
|
+
protected readonly fieldTransformerMap: FieldTransformerMap;
|
|
152
31
|
|
|
153
|
-
constructor(
|
|
32
|
+
constructor(protected readonly entityConfiguration: EntityConfiguration<TFields, TIDField>) {
|
|
154
33
|
this.fieldTransformerMap = this.getFieldTransformerMap();
|
|
155
34
|
}
|
|
156
35
|
|
|
@@ -222,91 +101,51 @@ export abstract class EntityDatabaseAdapter<
|
|
|
222
101
|
): Promise<object[]>;
|
|
223
102
|
|
|
224
103
|
/**
|
|
225
|
-
* Fetch
|
|
226
|
-
*
|
|
104
|
+
* Fetch one objects where key is equal to value, null if no matching object exists.
|
|
105
|
+
* Returned object is not guaranteed to be deterministic. Most concrete implementations will implement this
|
|
106
|
+
* with a "first" or "limit 1" query.
|
|
227
107
|
*
|
|
228
108
|
* @param queryContext - query context with which to perform the fetch
|
|
229
|
-
* @param
|
|
230
|
-
* @param
|
|
231
|
-
* @returns
|
|
109
|
+
* @param key - load key being queried
|
|
110
|
+
* @param values - load value being queried
|
|
111
|
+
* @returns object that matches the query for the value
|
|
232
112
|
*/
|
|
233
|
-
async
|
|
113
|
+
async fetchOneWhereAsync<
|
|
114
|
+
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
|
|
115
|
+
TSerializedLoadValue,
|
|
116
|
+
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
|
|
117
|
+
>(
|
|
234
118
|
queryContext: EntityQueryContext,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
): Promise<
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
for (const operand of fieldEqualityOperands) {
|
|
241
|
-
if (isSingleValueFieldEqualityCondition(operand)) {
|
|
242
|
-
tableFieldSingleValueOperands.push({
|
|
243
|
-
tableField: getDatabaseFieldForEntityField(this.entityConfiguration, operand.fieldName),
|
|
244
|
-
tableValue: operand.fieldValue,
|
|
245
|
-
});
|
|
246
|
-
} else {
|
|
247
|
-
tableFieldMultipleValueOperands.push({
|
|
248
|
-
tableField: getDatabaseFieldForEntityField(this.entityConfiguration, operand.fieldName),
|
|
249
|
-
tableValues: operand.fieldValues,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
119
|
+
key: TLoadKey,
|
|
120
|
+
value: TLoadValue,
|
|
121
|
+
): Promise<Readonly<TFields> | null> {
|
|
122
|
+
const keyDatabaseColumns = key.getDatabaseColumns(this.entityConfiguration);
|
|
123
|
+
const valueDatabaseValue = key.getDatabaseValues(value);
|
|
253
124
|
|
|
254
|
-
const
|
|
125
|
+
const result = await this.fetchOneWhereInternalAsync(
|
|
255
126
|
queryContext.getQueryInterface(),
|
|
256
127
|
this.entityConfiguration.tableName,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.convertToTableQueryModifiers(querySelectionModifiers),
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
return results.map((result) =>
|
|
263
|
-
transformDatabaseObjectToFields(this.entityConfiguration, this.fieldTransformerMap, result),
|
|
128
|
+
keyDatabaseColumns,
|
|
129
|
+
valueDatabaseValue,
|
|
264
130
|
);
|
|
265
|
-
}
|
|
266
131
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[],
|
|
271
|
-
tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[],
|
|
272
|
-
querySelectionModifiers: TableQuerySelectionModifiers,
|
|
273
|
-
): Promise<object[]>;
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Fetch many objects matching the raw WHERE clause.
|
|
277
|
-
*
|
|
278
|
-
* @param queryContext - query context with which to perform the fetch
|
|
279
|
-
* @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
|
|
280
|
-
* @param bindings - array of positional bindings or object of named bindings
|
|
281
|
-
* @param querySelectionModifiers - limit, offset, and orderBy for the query
|
|
282
|
-
* @returns array of objects matching the query
|
|
283
|
-
*/
|
|
284
|
-
async fetchManyByRawWhereClauseAsync(
|
|
285
|
-
queryContext: EntityQueryContext,
|
|
286
|
-
rawWhereClause: string,
|
|
287
|
-
bindings: any[] | object,
|
|
288
|
-
querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw<TFields>,
|
|
289
|
-
): Promise<readonly Readonly<TFields>[]> {
|
|
290
|
-
const results = await this.fetchManyByRawWhereClauseInternalAsync(
|
|
291
|
-
queryContext.getQueryInterface(),
|
|
292
|
-
this.entityConfiguration.tableName,
|
|
293
|
-
rawWhereClause,
|
|
294
|
-
bindings,
|
|
295
|
-
this.convertToTableQueryModifiersWithOrderByRaw(querySelectionModifiers),
|
|
296
|
-
);
|
|
132
|
+
if (!result) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
297
135
|
|
|
298
|
-
return
|
|
299
|
-
|
|
136
|
+
return transformDatabaseObjectToFields(
|
|
137
|
+
this.entityConfiguration,
|
|
138
|
+
this.fieldTransformerMap,
|
|
139
|
+
result,
|
|
300
140
|
);
|
|
301
141
|
}
|
|
302
142
|
|
|
303
|
-
protected abstract
|
|
143
|
+
protected abstract fetchOneWhereInternalAsync(
|
|
304
144
|
queryInterface: any,
|
|
305
145
|
tableName: string,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
): Promise<object[]>;
|
|
146
|
+
tableColumns: readonly string[],
|
|
147
|
+
tableTuple: readonly any[],
|
|
148
|
+
): Promise<object | null>;
|
|
310
149
|
|
|
311
150
|
/**
|
|
312
151
|
* Insert an object.
|
|
@@ -446,33 +285,4 @@ export abstract class EntityDatabaseAdapter<
|
|
|
446
285
|
tableIdField: string,
|
|
447
286
|
id: any,
|
|
448
287
|
): Promise<number>;
|
|
449
|
-
|
|
450
|
-
private convertToTableQueryModifiersWithOrderByRaw(
|
|
451
|
-
querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw<TFields>,
|
|
452
|
-
): TableQuerySelectionModifiersWithOrderByRaw {
|
|
453
|
-
return {
|
|
454
|
-
...this.convertToTableQueryModifiers(querySelectionModifiers),
|
|
455
|
-
orderByRaw: querySelectionModifiers.orderByRaw,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
private convertToTableQueryModifiers(
|
|
460
|
-
querySelectionModifiers: QuerySelectionModifiers<TFields>,
|
|
461
|
-
): TableQuerySelectionModifiers {
|
|
462
|
-
const orderBy = querySelectionModifiers.orderBy;
|
|
463
|
-
return {
|
|
464
|
-
orderBy:
|
|
465
|
-
orderBy !== undefined
|
|
466
|
-
? orderBy.map((orderBySpecification) => ({
|
|
467
|
-
columnName: getDatabaseFieldForEntityField(
|
|
468
|
-
this.entityConfiguration,
|
|
469
|
-
orderBySpecification.fieldName,
|
|
470
|
-
),
|
|
471
|
-
order: orderBySpecification.order,
|
|
472
|
-
}))
|
|
473
|
-
: undefined,
|
|
474
|
-
offset: querySelectionModifiers.offset,
|
|
475
|
-
limit: querySelectionModifiers.limit,
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
288
|
}
|
|
@@ -103,7 +103,7 @@ export interface EntityAssociationDefinition<
|
|
|
103
103
|
* Field by which to load the instance of associatedEntityClass. If not provided, the
|
|
104
104
|
* associatedEntityClass instance is fetched by its ID.
|
|
105
105
|
*/
|
|
106
|
-
associatedEntityLookupByField?:
|
|
106
|
+
associatedEntityLookupByField?: TAssociatedSelectedFields;
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* What action to perform on the entity at the other end of this edge when the entity on the source end of
|
|
@@ -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
|
}
|