@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
|
@@ -8,24 +8,16 @@ import {
|
|
|
8
8
|
EntityCompositeFieldValue,
|
|
9
9
|
EntityConfiguration,
|
|
10
10
|
} from './EntityConfiguration';
|
|
11
|
-
import {
|
|
12
|
-
FieldEqualityCondition,
|
|
13
|
-
isSingleValueFieldEqualityCondition,
|
|
14
|
-
QuerySelectionModifiers,
|
|
15
|
-
QuerySelectionModifiersWithOrderByRaw,
|
|
16
|
-
} from './EntityDatabaseAdapter';
|
|
17
|
-
import { EntityLoaderUtils } from './EntityLoaderUtils';
|
|
11
|
+
import { EntityConstructionUtils } from './EntityConstructionUtils';
|
|
18
12
|
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
|
|
19
13
|
import { EntityQueryContext } from './EntityQueryContext';
|
|
20
14
|
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
21
15
|
import { ViewerContext } from './ViewerContext';
|
|
22
|
-
import { EntityInvalidFieldValueError } from './errors/EntityInvalidFieldValueError';
|
|
23
16
|
import { EntityNotFoundError } from './errors/EntityNotFoundError';
|
|
24
17
|
import { CompositeFieldHolder, CompositeFieldValueHolder } from './internal/CompositeFieldHolder';
|
|
25
18
|
import { CompositeFieldValueMap } from './internal/CompositeFieldValueMap';
|
|
26
19
|
import { EntityDataManager } from './internal/EntityDataManager';
|
|
27
20
|
import { SingleFieldHolder, SingleFieldValueHolder } from './internal/SingleFieldHolder';
|
|
28
|
-
import { IEntityMetricsAdapter } from './metrics/IEntityMetricsAdapter';
|
|
29
21
|
import { mapKeys, mapMap } from './utils/collections/maps';
|
|
30
22
|
import { areSetsEqual } from './utils/collections/sets';
|
|
31
23
|
|
|
@@ -61,8 +53,7 @@ export class AuthorizationResultBasedEntityLoader<
|
|
|
61
53
|
TSelectedFields
|
|
62
54
|
>,
|
|
63
55
|
private readonly dataManager: EntityDataManager<TFields, TIDField>,
|
|
64
|
-
|
|
65
|
-
public readonly utils: EntityLoaderUtils<
|
|
56
|
+
private readonly constructionUtils: EntityConstructionUtils<
|
|
66
57
|
TFields,
|
|
67
58
|
TIDField,
|
|
68
59
|
TViewerContext,
|
|
@@ -96,7 +87,9 @@ export class AuthorizationResultBasedEntityLoader<
|
|
|
96
87
|
loadValuesToFieldObjects,
|
|
97
88
|
(loadValue) => loadValue.fieldValue,
|
|
98
89
|
);
|
|
99
|
-
return await this.
|
|
90
|
+
return await this.constructionUtils.constructAndAuthorizeEntitiesAsync(
|
|
91
|
+
fieldValuesToFieldObjects,
|
|
92
|
+
);
|
|
100
93
|
}
|
|
101
94
|
|
|
102
95
|
/**
|
|
@@ -141,6 +134,41 @@ export class AuthorizationResultBasedEntityLoader<
|
|
|
141
134
|
return entityResultsForFieldValue;
|
|
142
135
|
}
|
|
143
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Load one entity where fieldName equals fieldValue, or null if no entity exists matching the condition.
|
|
139
|
+
* Not cached or coalesced, and not guaranteed to be deterministic if multiple entities match the condition.
|
|
140
|
+
*
|
|
141
|
+
* Only used when evaluating EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL.
|
|
142
|
+
*
|
|
143
|
+
* @param fieldName - entity field being queried
|
|
144
|
+
* @param fieldValue - fieldName field value being queried
|
|
145
|
+
* @returns entity result matching the query for fieldValue. Returns null if no entity matches the query.
|
|
146
|
+
* @throws EntityNotAuthorizedError when viewer is not authorized to view the returned entity
|
|
147
|
+
*
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
151
|
+
// @ts-ignore -- this method is used in EntityPrivacyUtils, but is not intended to be part of the public API of this class, so it is marked as private.
|
|
152
|
+
private async loadOneByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
153
|
+
fieldName: N,
|
|
154
|
+
fieldValue: NonNullable<TFields[N]>,
|
|
155
|
+
): Promise<Result<TEntity> | null> {
|
|
156
|
+
const { loadKey, loadValue } = this.validateFieldAndValueAndConvertToHolders(
|
|
157
|
+
fieldName,
|
|
158
|
+
fieldValue,
|
|
159
|
+
);
|
|
160
|
+
const result = await this.dataManager.loadOneEqualingAsync(
|
|
161
|
+
this.queryContext,
|
|
162
|
+
loadKey,
|
|
163
|
+
loadValue,
|
|
164
|
+
);
|
|
165
|
+
if (!result) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return await this.constructionUtils.constructAndAuthorizeEntityAsync(result);
|
|
170
|
+
}
|
|
171
|
+
|
|
144
172
|
/**
|
|
145
173
|
* Authorization-result-based version of the EnforcingEntityLoader method by the same name.
|
|
146
174
|
* @returns array of entity results that match the query for compositeFieldValue, where result error can be UnauthorizedError
|
|
@@ -265,79 +293,6 @@ export class AuthorizationResultBasedEntityLoader<
|
|
|
265
293
|
});
|
|
266
294
|
}
|
|
267
295
|
|
|
268
|
-
/**
|
|
269
|
-
* Authorization-result-based version of the EnforcingEntityLoader method by the same name.
|
|
270
|
-
* @returns the first entity results that matches the query, where result error can be
|
|
271
|
-
* UnauthorizedError
|
|
272
|
-
*/
|
|
273
|
-
async loadFirstByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
274
|
-
fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
|
|
275
|
-
querySelectionModifiers: Omit<QuerySelectionModifiers<TFields>, 'limit'> &
|
|
276
|
-
Required<Pick<QuerySelectionModifiers<TFields>, 'orderBy'>>,
|
|
277
|
-
): Promise<Result<TEntity> | null> {
|
|
278
|
-
const results = await this.loadManyByFieldEqualityConjunctionAsync(fieldEqualityOperands, {
|
|
279
|
-
...querySelectionModifiers,
|
|
280
|
-
limit: 1,
|
|
281
|
-
});
|
|
282
|
-
return results[0] ?? null;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Authorization-result-based version of the EnforcingEntityLoader method by the same name.
|
|
287
|
-
* @returns array of entity results that match the query, where result error can be UnauthorizedError
|
|
288
|
-
*/
|
|
289
|
-
async loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
290
|
-
fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
|
|
291
|
-
querySelectionModifiers: QuerySelectionModifiers<TFields> = {},
|
|
292
|
-
): Promise<readonly Result<TEntity>[]> {
|
|
293
|
-
for (const fieldEqualityOperand of fieldEqualityOperands) {
|
|
294
|
-
const fieldValues = isSingleValueFieldEqualityCondition(fieldEqualityOperand)
|
|
295
|
-
? [fieldEqualityOperand.fieldValue]
|
|
296
|
-
: fieldEqualityOperand.fieldValues;
|
|
297
|
-
this.validateFieldAndValues(fieldEqualityOperand.fieldName, fieldValues);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const fieldObjects = await this.dataManager.loadManyByFieldEqualityConjunctionAsync(
|
|
301
|
-
this.queryContext,
|
|
302
|
-
fieldEqualityOperands,
|
|
303
|
-
querySelectionModifiers,
|
|
304
|
-
);
|
|
305
|
-
return await this.utils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Authorization-result-based version of the EnforcingEntityLoader method by the same name.
|
|
310
|
-
* @returns array of entity results that match the query, where result error can be UnauthorizedError
|
|
311
|
-
* @throws Error when rawWhereClause or bindings are invalid
|
|
312
|
-
*/
|
|
313
|
-
async loadManyByRawWhereClauseAsync(
|
|
314
|
-
rawWhereClause: string,
|
|
315
|
-
bindings: any[] | object,
|
|
316
|
-
querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw<TFields> = {},
|
|
317
|
-
): Promise<readonly Result<TEntity>[]> {
|
|
318
|
-
const fieldObjects = await this.dataManager.loadManyByRawWhereClauseAsync(
|
|
319
|
-
this.queryContext,
|
|
320
|
-
rawWhereClause,
|
|
321
|
-
bindings,
|
|
322
|
-
querySelectionModifiers,
|
|
323
|
-
);
|
|
324
|
-
return await this.utils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
private validateFieldAndValues<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
328
|
-
fieldName: N,
|
|
329
|
-
fieldValues: readonly TFields[N][],
|
|
330
|
-
): void {
|
|
331
|
-
const fieldDefinition = this.entityConfiguration.schema.get(fieldName);
|
|
332
|
-
invariant(fieldDefinition, `must have field definition for field = ${String(fieldName)}`);
|
|
333
|
-
for (const fieldValue of fieldValues) {
|
|
334
|
-
const isInputValid = fieldDefinition.validateInputValue(fieldValue);
|
|
335
|
-
if (!isInputValid) {
|
|
336
|
-
throw new EntityInvalidFieldValueError(this.entityClass, fieldName, fieldValue);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
296
|
private validateFieldAndValuesAndConvertToHolders<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
342
297
|
fieldName: N,
|
|
343
298
|
fieldValues: readonly NonNullable<TFields[N]>[],
|
|
@@ -345,7 +300,7 @@ export class AuthorizationResultBasedEntityLoader<
|
|
|
345
300
|
loadKey: SingleFieldHolder<TFields, TIDField, N>;
|
|
346
301
|
loadValues: readonly SingleFieldValueHolder<TFields, N>[];
|
|
347
302
|
} {
|
|
348
|
-
this.validateFieldAndValues(fieldName, fieldValues);
|
|
303
|
+
this.constructionUtils.validateFieldAndValues(fieldName, fieldValues);
|
|
349
304
|
|
|
350
305
|
return {
|
|
351
306
|
loadKey: new SingleFieldHolder<TFields, TIDField, N>(fieldName),
|
|
@@ -355,6 +310,21 @@ export class AuthorizationResultBasedEntityLoader<
|
|
|
355
310
|
};
|
|
356
311
|
}
|
|
357
312
|
|
|
313
|
+
private validateFieldAndValueAndConvertToHolders<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
314
|
+
fieldName: N,
|
|
315
|
+
fieldValue: NonNullable<TFields[N]>,
|
|
316
|
+
): {
|
|
317
|
+
loadKey: SingleFieldHolder<TFields, TIDField, N>;
|
|
318
|
+
loadValue: SingleFieldValueHolder<TFields, N>;
|
|
319
|
+
} {
|
|
320
|
+
this.constructionUtils.validateFieldAndValues(fieldName, [fieldValue]);
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
loadKey: new SingleFieldHolder<TFields, TIDField, N>(fieldName),
|
|
324
|
+
loadValue: new SingleFieldValueHolder<TFields, N>(fieldValue),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
358
328
|
private validateCompositeFieldAndValuesAndConvertToHolders<
|
|
359
329
|
N extends EntityCompositeField<Pick<TFields, TSelectedFields>>,
|
|
360
330
|
>(
|
|
@@ -388,7 +358,7 @@ export class AuthorizationResultBasedEntityLoader<
|
|
|
388
358
|
);
|
|
389
359
|
for (const field of compositeField) {
|
|
390
360
|
const fieldValue = compositeFieldValueHolder.compositeFieldValue[field];
|
|
391
|
-
this.validateFieldAndValues(field, [fieldValue]);
|
|
361
|
+
this.constructionUtils.validateFieldAndValues(field, [fieldValue]);
|
|
392
362
|
}
|
|
393
363
|
}
|
|
394
364
|
|
|
@@ -408,7 +378,7 @@ export class AuthorizationResultBasedEntityLoader<
|
|
|
408
378
|
Array.from(map.entries()).map(async ([compositeFieldValueHolder, fieldObjects]) => {
|
|
409
379
|
return [
|
|
410
380
|
compositeFieldValueHolder,
|
|
411
|
-
await this.
|
|
381
|
+
await this.constructionUtils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects),
|
|
412
382
|
];
|
|
413
383
|
}),
|
|
414
384
|
),
|
|
@@ -333,8 +333,17 @@ export class AuthorizationResultBasedCreateMutator<
|
|
|
333
333
|
previousValue: null,
|
|
334
334
|
cascadingDeleteCause: null,
|
|
335
335
|
});
|
|
336
|
+
const invalidationUtils = this.entityLoaderFactory.invalidationUtils();
|
|
337
|
+
const constructionUtils = this.entityLoaderFactory.constructionUtils(
|
|
338
|
+
this.viewerContext,
|
|
339
|
+
queryContext,
|
|
340
|
+
{
|
|
341
|
+
previousValue: null,
|
|
342
|
+
cascadingDeleteCause: null,
|
|
343
|
+
},
|
|
344
|
+
);
|
|
336
345
|
|
|
337
|
-
const temporaryEntityForPrivacyCheck =
|
|
346
|
+
const temporaryEntityForPrivacyCheck = constructionUtils.constructEntity({
|
|
338
347
|
[this.entityConfiguration.idField]: '00000000-0000-0000-0000-000000000000', // zero UUID
|
|
339
348
|
...this.fieldsForEntity,
|
|
340
349
|
} as unknown as TFields);
|
|
@@ -376,13 +385,13 @@ export class AuthorizationResultBasedCreateMutator<
|
|
|
376
385
|
// Invalidate all caches for the new entity so that any previously-negatively-cached loads
|
|
377
386
|
// are removed from the caches.
|
|
378
387
|
queryContext.appendPostCommitInvalidationCallback(async () => {
|
|
379
|
-
|
|
380
|
-
await
|
|
388
|
+
invalidationUtils.invalidateFieldsForTransaction(queryContext, insertResult);
|
|
389
|
+
await invalidationUtils.invalidateFieldsAsync(insertResult);
|
|
381
390
|
});
|
|
382
391
|
|
|
383
|
-
|
|
392
|
+
invalidationUtils.invalidateFieldsForTransaction(queryContext, insertResult);
|
|
384
393
|
|
|
385
|
-
const unauthorizedEntityAfterInsert =
|
|
394
|
+
const unauthorizedEntityAfterInsert = constructionUtils.constructEntity(insertResult);
|
|
386
395
|
const newEntity = await enforceAsyncResult(
|
|
387
396
|
entityLoader.loadByIDAsync(unauthorizedEntityAfterInsert.getID()),
|
|
388
397
|
);
|
|
@@ -541,8 +550,17 @@ export class AuthorizationResultBasedUpdateMutator<
|
|
|
541
550
|
previousValue: this.originalEntity,
|
|
542
551
|
cascadingDeleteCause: this.cascadingDeleteCause,
|
|
543
552
|
});
|
|
553
|
+
const invalidationUtils = this.entityLoaderFactory.invalidationUtils();
|
|
554
|
+
const constructionUtils = this.entityLoaderFactory.constructionUtils(
|
|
555
|
+
this.viewerContext,
|
|
556
|
+
queryContext,
|
|
557
|
+
{
|
|
558
|
+
previousValue: this.originalEntity,
|
|
559
|
+
cascadingDeleteCause: this.cascadingDeleteCause,
|
|
560
|
+
},
|
|
561
|
+
);
|
|
544
562
|
|
|
545
|
-
const entityAboutToBeUpdated =
|
|
563
|
+
const entityAboutToBeUpdated = constructionUtils.constructEntity(this.fieldsForEntity);
|
|
546
564
|
const authorizeUpdateResult = await asyncResult(
|
|
547
565
|
this.privacyPolicy.authorizeUpdateAsync(
|
|
548
566
|
this.viewerContext,
|
|
@@ -606,22 +624,22 @@ export class AuthorizationResultBasedUpdateMutator<
|
|
|
606
624
|
// version of the entity.
|
|
607
625
|
|
|
608
626
|
queryContext.appendPostCommitInvalidationCallback(async () => {
|
|
609
|
-
|
|
627
|
+
invalidationUtils.invalidateFieldsForTransaction(
|
|
610
628
|
queryContext,
|
|
611
629
|
this.originalEntity.getAllDatabaseFields(),
|
|
612
630
|
);
|
|
613
|
-
|
|
631
|
+
invalidationUtils.invalidateFieldsForTransaction(queryContext, this.fieldsForEntity);
|
|
614
632
|
await Promise.all([
|
|
615
|
-
|
|
616
|
-
|
|
633
|
+
invalidationUtils.invalidateFieldsAsync(this.originalEntity.getAllDatabaseFields()),
|
|
634
|
+
invalidationUtils.invalidateFieldsAsync(this.fieldsForEntity),
|
|
617
635
|
]);
|
|
618
636
|
});
|
|
619
637
|
|
|
620
|
-
|
|
638
|
+
invalidationUtils.invalidateFieldsForTransaction(
|
|
621
639
|
queryContext,
|
|
622
640
|
this.originalEntity.getAllDatabaseFields(),
|
|
623
641
|
);
|
|
624
|
-
|
|
642
|
+
invalidationUtils.invalidateFieldsForTransaction(queryContext, this.fieldsForEntity);
|
|
625
643
|
|
|
626
644
|
const updatedEntity = await enforceAsyncResult(
|
|
627
645
|
entityLoader.loadByIDAsync(entityAboutToBeUpdated.getID()),
|
|
@@ -839,21 +857,18 @@ export class AuthorizationResultBasedDeleteMutator<
|
|
|
839
857
|
);
|
|
840
858
|
}
|
|
841
859
|
|
|
842
|
-
const
|
|
843
|
-
previousValue: null,
|
|
844
|
-
cascadingDeleteCause: this.cascadingDeleteCause,
|
|
845
|
-
});
|
|
860
|
+
const invalidationUtils = this.entityLoaderFactory.invalidationUtils();
|
|
846
861
|
|
|
847
862
|
// Invalidate all caches for the entity so that any previously-cached loads
|
|
848
863
|
// are removed from the caches.
|
|
849
864
|
queryContext.appendPostCommitInvalidationCallback(async () => {
|
|
850
|
-
|
|
865
|
+
invalidationUtils.invalidateFieldsForTransaction(
|
|
851
866
|
queryContext,
|
|
852
867
|
this.entity.getAllDatabaseFields(),
|
|
853
868
|
);
|
|
854
|
-
await
|
|
869
|
+
await invalidationUtils.invalidateFieldsAsync(this.entity.getAllDatabaseFields());
|
|
855
870
|
});
|
|
856
|
-
|
|
871
|
+
invalidationUtils.invalidateFieldsForTransaction(
|
|
857
872
|
queryContext,
|
|
858
873
|
this.entity.getAllDatabaseFields(),
|
|
859
874
|
);
|
|
@@ -971,7 +986,7 @@ export class AuthorizationResultBasedDeleteMutator<
|
|
|
971
986
|
.loadManyByFieldEqualingAsync(
|
|
972
987
|
fieldName,
|
|
973
988
|
association.associatedEntityLookupByField
|
|
974
|
-
? entity.getField(association.associatedEntityLookupByField
|
|
989
|
+
? entity.getField(association.associatedEntityLookupByField)
|
|
975
990
|
: entity.getID(),
|
|
976
991
|
),
|
|
977
992
|
);
|
|
@@ -6,7 +6,7 @@ import { ReadonlyEntity } from './ReadonlyEntity';
|
|
|
6
6
|
import { ViewerContext } from './ViewerContext';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Enforcing entity creator. All
|
|
9
|
+
* Enforcing entity creator. All creates
|
|
10
10
|
* through this creator will throw if authorization is not successful.
|
|
11
11
|
*/
|
|
12
12
|
export class EnforcingEntityCreator<
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { AuthorizationResultBasedEntityLoader } from './AuthorizationResultBasedEntityLoader';
|
|
2
2
|
import { EntityCompositeField, EntityCompositeFieldValue } from './EntityConfiguration';
|
|
3
|
-
import {
|
|
4
|
-
FieldEqualityCondition,
|
|
5
|
-
QuerySelectionModifiers,
|
|
6
|
-
QuerySelectionModifiersWithOrderByRaw,
|
|
7
|
-
} from './EntityDatabaseAdapter';
|
|
8
3
|
import { EntityPrivacyPolicy } from './EntityPrivacyPolicy';
|
|
9
4
|
import { ReadonlyEntity } from './ReadonlyEntity';
|
|
10
5
|
import { ViewerContext } from './ViewerContext';
|
|
@@ -29,7 +24,7 @@ export class EnforcingEntityLoader<
|
|
|
29
24
|
TEntity,
|
|
30
25
|
TSelectedFields
|
|
31
26
|
>,
|
|
32
|
-
TSelectedFields extends keyof TFields,
|
|
27
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
33
28
|
> {
|
|
34
29
|
constructor(
|
|
35
30
|
private readonly entityLoader: AuthorizationResultBasedEntityLoader<
|
|
@@ -219,93 +214,4 @@ export class EnforcingEntityLoader<
|
|
|
219
214
|
const entityResults = await this.entityLoader.loadManyByIDsNullableAsync(ids);
|
|
220
215
|
return mapMap(entityResults, (result) => result?.enforceValue() ?? null);
|
|
221
216
|
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Loads the first entity matching the selection constructed from the conjunction of specified
|
|
225
|
-
* operands, or null if no matching entity exists. Entities loaded using this method are not
|
|
226
|
-
* batched or cached.
|
|
227
|
-
*
|
|
228
|
-
* This is a convenience method for {@link loadManyByFieldEqualityConjunctionAsync}. However, the
|
|
229
|
-
* `orderBy` option must be specified to define what "first" means. If ordering doesn't matter,
|
|
230
|
-
* explicitly pass in an empty array.
|
|
231
|
-
*
|
|
232
|
-
* @param fieldEqualityOperands - list of field equality selection operand specifications
|
|
233
|
-
* @param querySelectionModifiers - orderBy and optional offset for the query
|
|
234
|
-
* @returns the first entity that matches the query or null if no entity matches the query
|
|
235
|
-
* @throws EntityNotAuthorizedError when viewer is not authorized to view the returned entity
|
|
236
|
-
*/
|
|
237
|
-
async loadFirstByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
238
|
-
fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
|
|
239
|
-
querySelectionModifiers: Omit<QuerySelectionModifiers<TFields>, 'limit'> &
|
|
240
|
-
Required<Pick<QuerySelectionModifiers<TFields>, 'orderBy'>>,
|
|
241
|
-
): Promise<TEntity | null> {
|
|
242
|
-
const entityResult = await this.entityLoader.loadFirstByFieldEqualityConjunctionAsync(
|
|
243
|
-
fieldEqualityOperands,
|
|
244
|
-
querySelectionModifiers,
|
|
245
|
-
);
|
|
246
|
-
return entityResult ? entityResult.enforceValue() : null;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Loads many entities matching the selection constructed from the conjunction of specified operands.
|
|
251
|
-
* Entities loaded using this method are not batched or cached.
|
|
252
|
-
*
|
|
253
|
-
* @example
|
|
254
|
-
* fieldEqualityOperands:
|
|
255
|
-
* `[{fieldName: 'hello', fieldValue: 1}, {fieldName: 'world', fieldValues: [2, 3]}]`
|
|
256
|
-
* Entities returned with a SQL EntityDatabaseAdapter:
|
|
257
|
-
* `WHERE hello = 1 AND world = ANY({2, 3})`
|
|
258
|
-
*
|
|
259
|
-
* @param fieldEqualityOperands - list of field equality selection operand specifications
|
|
260
|
-
* @param querySelectionModifiers - limit, offset, and orderBy for the query
|
|
261
|
-
* @returns array of entities that match the query
|
|
262
|
-
* @throws EntityNotAuthorizedError when viewer is not authorized to view one or more of the returned entities
|
|
263
|
-
*/
|
|
264
|
-
async loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
|
265
|
-
fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
|
|
266
|
-
querySelectionModifiers: QuerySelectionModifiers<TFields> = {},
|
|
267
|
-
): Promise<readonly TEntity[]> {
|
|
268
|
-
const entityResults = await this.entityLoader.loadManyByFieldEqualityConjunctionAsync(
|
|
269
|
-
fieldEqualityOperands,
|
|
270
|
-
querySelectionModifiers,
|
|
271
|
-
);
|
|
272
|
-
return entityResults.map((result) => result.enforceValue());
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Loads many entities matching the raw WHERE clause. Corresponds to the knex `whereRaw` argument format.
|
|
277
|
-
*
|
|
278
|
-
* @remarks
|
|
279
|
-
* Important notes:
|
|
280
|
-
* - Fields in clause are database column names instead of transformed entity field names.
|
|
281
|
-
* - Entities loaded using this method are not batched or cached.
|
|
282
|
-
* - Not all database adapters implement the ability to execute this method of fetching entities.
|
|
283
|
-
*
|
|
284
|
-
* @example
|
|
285
|
-
* rawWhereClause: `id = ?`
|
|
286
|
-
* bindings: `[1]`
|
|
287
|
-
* Entites returned `WHERE id = 1`
|
|
288
|
-
*
|
|
289
|
-
* http://knexjs.org/#Builder-whereRaw
|
|
290
|
-
* http://knexjs.org/#Raw-Bindings
|
|
291
|
-
*
|
|
292
|
-
* @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
|
|
293
|
-
* @param bindings - array of positional bindings or object of named bindings
|
|
294
|
-
* @param querySelectionModifiers - limit, offset, orderBy, and orderByRaw for the query
|
|
295
|
-
* @returns array of entities that match the query
|
|
296
|
-
* @throws EntityNotAuthorizedError when viewer is not authorized to view one or more of the returned entities
|
|
297
|
-
* @throws Error when rawWhereClause or bindings are invalid
|
|
298
|
-
*/
|
|
299
|
-
async loadManyByRawWhereClauseAsync(
|
|
300
|
-
rawWhereClause: string,
|
|
301
|
-
bindings: any[] | object,
|
|
302
|
-
querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw<TFields> = {},
|
|
303
|
-
): Promise<readonly TEntity[]> {
|
|
304
|
-
const entityResults = await this.entityLoader.loadManyByRawWhereClauseAsync(
|
|
305
|
-
rawWhereClause,
|
|
306
|
-
bindings,
|
|
307
|
-
querySelectionModifiers,
|
|
308
|
-
);
|
|
309
|
-
return entityResults.map((result) => result.enforceValue());
|
|
310
|
-
}
|
|
311
217
|
}
|
package/src/Entity.ts
CHANGED
|
@@ -32,6 +32,12 @@ import { ViewerContext } from './ViewerContext';
|
|
|
32
32
|
*
|
|
33
33
|
* All concrete entity implementations should extend this class and provide their
|
|
34
34
|
* own EntityCompanionDefinition.
|
|
35
|
+
*
|
|
36
|
+
* Generic type parameters:
|
|
37
|
+
* TFields - the shape of the underlying data for this entity, typically corresponding to a database table schema. The mapping from TFields to the actual database schema is defined in the EntityCompanionDefinition for this entity.
|
|
38
|
+
* TIDField - the key of the ID field in TFields, which must be non-nullable and is used to uniquely identify individual entities
|
|
39
|
+
* TViewerContext - the type of ViewerContext that can be used with this entity
|
|
40
|
+
* TSelectedFields - the keys of fields in TFields that belong to this entity; used when there are multiple entities backed by the same underlying table with different field subsets
|
|
35
41
|
*/
|
|
36
42
|
export abstract class Entity<
|
|
37
43
|
TFields extends Record<string, any>,
|
package/src/EntityCompanion.ts
CHANGED
|
@@ -59,8 +59,8 @@ export class EntityCompanion<
|
|
|
59
59
|
TPrivacyPolicy,
|
|
60
60
|
TSelectedFields
|
|
61
61
|
>,
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
public readonly tableDataCoordinator: EntityTableDataCoordinator<TFields, TIDField>,
|
|
63
|
+
public readonly metricsAdapter: IEntityMetricsAdapter,
|
|
64
64
|
) {
|
|
65
65
|
this.privacyPolicy = new entityCompanionDefinition.privacyPolicyClass();
|
|
66
66
|
this.entityLoaderFactory = new EntityLoaderFactory<
|
|
@@ -208,13 +208,13 @@ export class EntityCompanionProvider {
|
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
getQueryContextProviderForDatabaseAdapterFlavor(
|
|
212
212
|
databaseAdapterFlavor: DatabaseAdapterFlavor,
|
|
213
213
|
): EntityQueryContextProvider {
|
|
214
214
|
const entityDatabaseAdapterFlavor = this.databaseAdapterFlavors.get(databaseAdapterFlavor);
|
|
215
215
|
invariant(
|
|
216
216
|
entityDatabaseAdapterFlavor,
|
|
217
|
-
`No database
|
|
217
|
+
`No database adapter configuration found for flavor: ${databaseAdapterFlavor}`,
|
|
218
218
|
);
|
|
219
219
|
|
|
220
220
|
return entityDatabaseAdapterFlavor.queryContextProvider;
|
|
@@ -233,7 +233,7 @@ export class EntityCompanionProvider {
|
|
|
233
233
|
);
|
|
234
234
|
invariant(
|
|
235
235
|
entityDatabaseAdapterFlavor,
|
|
236
|
-
`No database
|
|
236
|
+
`No database adapter configuration found for flavor: ${entityConfiguration.databaseAdapterFlavor}`,
|
|
237
237
|
);
|
|
238
238
|
|
|
239
239
|
const entityCacheAdapterFlavor = this.cacheAdapterFlavors.get(
|
|
@@ -241,7 +241,7 @@ export class EntityCompanionProvider {
|
|
|
241
241
|
);
|
|
242
242
|
invariant(
|
|
243
243
|
entityCacheAdapterFlavor,
|
|
244
|
-
`No cache
|
|
244
|
+
`No cache adapter configuration found for flavor: ${entityConfiguration.cacheAdapterFlavor}`,
|
|
245
245
|
);
|
|
246
246
|
|
|
247
247
|
return new EntityTableDataCoordinator(
|
|
@@ -67,7 +67,9 @@ export class CompositeFieldInfo<
|
|
|
67
67
|
keyDefinition.compositeField.length === new Set(keyDefinition.compositeField).size,
|
|
68
68
|
'Composite field must have unique sub-fields',
|
|
69
69
|
);
|
|
70
|
-
const compositeFieldHolder = new CompositeFieldHolder(
|
|
70
|
+
const compositeFieldHolder = new CompositeFieldHolder<TFields, TIDField>(
|
|
71
|
+
keyDefinition.compositeField,
|
|
72
|
+
);
|
|
71
73
|
return [
|
|
72
74
|
compositeFieldHolder.serialize(),
|
|
73
75
|
{ compositeFieldHolder, cache: keyDefinition.cache ?? false },
|
|
@@ -79,8 +81,9 @@ export class CompositeFieldInfo<
|
|
|
79
81
|
public getCompositeFieldHolderForCompositeField(
|
|
80
82
|
compositeField: EntityCompositeField<TFields>,
|
|
81
83
|
): CompositeFieldHolder<TFields, TIDField> | undefined {
|
|
82
|
-
return this.compositeFieldInfoMap.get(
|
|
83
|
-
|
|
84
|
+
return this.compositeFieldInfoMap.get(
|
|
85
|
+
new CompositeFieldHolder<TFields, TIDField>(compositeField).serialize(),
|
|
86
|
+
)?.compositeFieldHolder;
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
public getAllCompositeFieldHolders(): readonly CompositeFieldHolder<TFields, TIDField>[] {
|
|
@@ -89,7 +92,7 @@ export class CompositeFieldInfo<
|
|
|
89
92
|
|
|
90
93
|
public canCacheCompositeField(compositeField: EntityCompositeField<TFields>): boolean {
|
|
91
94
|
const compositeFieldInfo = this.compositeFieldInfoMap.get(
|
|
92
|
-
new CompositeFieldHolder(compositeField).serialize(),
|
|
95
|
+
new CompositeFieldHolder<TFields, TIDField>(compositeField).serialize(),
|
|
93
96
|
);
|
|
94
97
|
invariant(
|
|
95
98
|
compositeFieldInfo,
|
|
@@ -107,7 +110,7 @@ export class EntityConfiguration<
|
|
|
107
110
|
TFields extends Record<string, any>,
|
|
108
111
|
TIDField extends keyof TFields,
|
|
109
112
|
> {
|
|
110
|
-
readonly idField:
|
|
113
|
+
readonly idField: TIDField;
|
|
111
114
|
readonly tableName: string;
|
|
112
115
|
readonly cacheableKeys: ReadonlySet<keyof TFields>;
|
|
113
116
|
readonly compositeFieldInfo: CompositeFieldInfo<TFields, TIDField>;
|