@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.
Files changed (173) hide show
  1. package/build/src/AuthorizationResultBasedEntityAssociationLoader.d.ts +1 -1
  2. package/build/src/AuthorizationResultBasedEntityAssociationLoader.js.map +1 -1
  3. package/build/src/AuthorizationResultBasedEntityLoader.d.ts +18 -24
  4. package/build/src/AuthorizationResultBasedEntityLoader.js +37 -56
  5. package/build/src/AuthorizationResultBasedEntityLoader.js.map +1 -1
  6. package/build/src/AuthorizationResultBasedEntityMutator.js +26 -19
  7. package/build/src/AuthorizationResultBasedEntityMutator.js.map +1 -1
  8. package/build/src/EnforcingEntityCreator.d.ts +1 -1
  9. package/build/src/EnforcingEntityCreator.js +1 -1
  10. package/build/src/EnforcingEntityLoader.d.ts +1 -58
  11. package/build/src/EnforcingEntityLoader.js +0 -65
  12. package/build/src/EnforcingEntityLoader.js.map +1 -1
  13. package/build/src/Entity.d.ts +6 -0
  14. package/build/src/Entity.js +6 -0
  15. package/build/src/Entity.js.map +1 -1
  16. package/build/src/EntityCompanion.d.ts +2 -2
  17. package/build/src/EntityCompanion.js.map +1 -1
  18. package/build/src/EntityCompanionProvider.d.ts +1 -1
  19. package/build/src/EntityCompanionProvider.js +4 -4
  20. package/build/src/EntityConfiguration.d.ts +1 -1
  21. package/build/src/EntityConfiguration.js +1 -2
  22. package/build/src/EntityConfiguration.js.map +1 -1
  23. package/build/src/{EntityLoaderUtils.d.ts → EntityConstructionUtils.d.ts} +15 -29
  24. package/build/src/EntityConstructionUtils.js +118 -0
  25. package/build/src/EntityConstructionUtils.js.map +1 -0
  26. package/build/src/EntityDatabaseAdapter.d.ts +10 -108
  27. package/build/src/EntityDatabaseAdapter.js +14 -76
  28. package/build/src/EntityDatabaseAdapter.js.map +1 -1
  29. package/build/src/EntityFieldDefinition.d.ts +1 -1
  30. package/build/src/EntityInvalidationUtils.d.ts +41 -0
  31. package/build/src/EntityInvalidationUtils.js +71 -0
  32. package/build/src/EntityInvalidationUtils.js.map +1 -0
  33. package/build/src/EntityLoader.d.ts +0 -6
  34. package/build/src/EntityLoader.js +0 -7
  35. package/build/src/EntityLoader.js.map +1 -1
  36. package/build/src/EntityLoaderFactory.d.ts +4 -0
  37. package/build/src/EntityLoaderFactory.js +10 -3
  38. package/build/src/EntityLoaderFactory.js.map +1 -1
  39. package/build/src/EntityPrivacyPolicy.d.ts +27 -0
  40. package/build/src/EntityPrivacyPolicy.js +22 -1
  41. package/build/src/EntityPrivacyPolicy.js.map +1 -1
  42. package/build/src/EntitySecondaryCacheLoader.d.ts +14 -3
  43. package/build/src/EntitySecondaryCacheLoader.js +21 -4
  44. package/build/src/EntitySecondaryCacheLoader.js.map +1 -1
  45. package/build/src/ReadonlyEntity.d.ts +4 -5
  46. package/build/src/ReadonlyEntity.js +7 -8
  47. package/build/src/ReadonlyEntity.js.map +1 -1
  48. package/build/src/ViewerContext.d.ts +6 -6
  49. package/build/src/ViewerContext.js +8 -8
  50. package/build/src/ViewerScopedEntityCompanion.d.ts +1 -1
  51. package/build/src/ViewerScopedEntityCompanion.js.map +1 -1
  52. package/build/src/ViewerScopedEntityLoaderFactory.d.ts +4 -0
  53. package/build/src/ViewerScopedEntityLoaderFactory.js +6 -0
  54. package/build/src/ViewerScopedEntityLoaderFactory.js.map +1 -1
  55. package/build/src/errors/EntityDatabaseAdapterError.d.ts +4 -0
  56. package/build/src/errors/EntityDatabaseAdapterError.js +13 -1
  57. package/build/src/errors/EntityDatabaseAdapterError.js.map +1 -1
  58. package/build/src/errors/EntityError.d.ts +2 -1
  59. package/build/src/errors/EntityError.js +1 -0
  60. package/build/src/errors/EntityError.js.map +1 -1
  61. package/build/src/index.d.ts +6 -1
  62. package/build/src/index.js +6 -1
  63. package/build/src/index.js.map +1 -1
  64. package/build/src/internal/EntityDataManager.d.ts +8 -16
  65. package/build/src/internal/EntityDataManager.js +8 -18
  66. package/build/src/internal/EntityDataManager.js.map +1 -1
  67. package/build/src/internal/EntityFieldTransformationUtils.js.map +1 -1
  68. package/build/src/internal/EntityLoadInterfaces.d.ts +2 -0
  69. package/build/src/internal/EntityLoadInterfaces.js +2 -0
  70. package/build/src/internal/EntityLoadInterfaces.js.map +1 -1
  71. package/build/src/internal/EntityTableDataCoordinator.d.ts +2 -0
  72. package/build/src/internal/EntityTableDataCoordinator.js +4 -0
  73. package/build/src/internal/EntityTableDataCoordinator.js.map +1 -1
  74. package/build/src/metrics/EntityMetricsUtils.d.ts +1 -0
  75. package/build/src/metrics/EntityMetricsUtils.js +15 -1
  76. package/build/src/metrics/EntityMetricsUtils.js.map +1 -1
  77. package/build/src/metrics/IEntityMetricsAdapter.d.ts +4 -1
  78. package/build/src/metrics/IEntityMetricsAdapter.js +3 -0
  79. package/build/src/metrics/IEntityMetricsAdapter.js.map +1 -1
  80. package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.d.ts +10 -0
  81. package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js +19 -0
  82. package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js.map +1 -0
  83. package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.d.ts +10 -0
  84. package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js +19 -0
  85. package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js.map +1 -0
  86. package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.d.ts +66 -0
  87. package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js +75 -0
  88. package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js.map +1 -0
  89. package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.d.ts +12 -0
  90. package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js +23 -0
  91. package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js.map +1 -0
  92. package/build/src/rules/PrivacyPolicyRule.d.ts +2 -2
  93. package/build/src/utils/EntityPrivacyUtils.js +11 -20
  94. package/build/src/utils/EntityPrivacyUtils.js.map +1 -1
  95. package/build/src/utils/collections/maps.d.ts +2 -2
  96. package/build/src/utils/collections/maps.js +2 -2
  97. package/package.json +5 -5
  98. package/src/AuthorizationResultBasedEntityAssociationLoader.ts +4 -7
  99. package/src/AuthorizationResultBasedEntityLoader.ts +58 -88
  100. package/src/AuthorizationResultBasedEntityMutator.ts +35 -20
  101. package/src/EnforcingEntityCreator.ts +1 -1
  102. package/src/EnforcingEntityLoader.ts +1 -95
  103. package/src/Entity.ts +6 -0
  104. package/src/EntityCompanion.ts +2 -2
  105. package/src/EntityCompanionProvider.ts +4 -4
  106. package/src/EntityConfiguration.ts +8 -5
  107. package/src/EntityConstructionUtils.ts +168 -0
  108. package/src/EntityDatabaseAdapter.ts +32 -222
  109. package/src/EntityFieldDefinition.ts +1 -1
  110. package/src/{EntityLoaderUtils.ts → EntityInvalidationUtils.ts} +5 -96
  111. package/src/EntityLoader.ts +0 -16
  112. package/src/EntityLoaderFactory.ts +50 -10
  113. package/src/EntityPrivacyPolicy.ts +44 -1
  114. package/src/EntitySecondaryCacheLoader.ts +54 -3
  115. package/src/ReadonlyEntity.ts +9 -11
  116. package/src/ViewerContext.ts +10 -10
  117. package/src/ViewerScopedEntityCompanion.ts +1 -1
  118. package/src/ViewerScopedEntityLoaderFactory.ts +37 -0
  119. package/src/__tests__/AuthorizationResultBasedEntityLoader-constructor-test.ts +3 -5
  120. package/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts +34 -419
  121. package/src/__tests__/ComposedCacheAdapter-test.ts +3 -3
  122. package/src/__tests__/EnforcingEntityLoader-test.ts +2 -134
  123. package/src/__tests__/EntityCompanion-test.ts +18 -0
  124. package/src/__tests__/EntityConfiguration-test.ts +4 -4
  125. package/src/__tests__/EntityDatabaseAdapter-test.ts +33 -68
  126. package/src/__tests__/EntityEdges-test.ts +10 -10
  127. package/src/__tests__/EntityLoader-test.ts +6 -4
  128. package/src/__tests__/EntityMutator-test.ts +27 -15
  129. package/src/__tests__/EntityPrivacyPolicy-test.ts +102 -0
  130. package/src/__tests__/EntityQueryContext-test.ts +11 -11
  131. package/src/__tests__/EntitySecondaryCacheLoader-test.ts +10 -5
  132. package/src/__tests__/EntitySelfReferentialEdges-test.ts +6 -6
  133. package/src/__tests__/GenericEntityCacheAdapter-test.ts +18 -15
  134. package/src/__tests__/GenericSecondaryEntityCache-test.ts +27 -5
  135. package/src/__tests__/ReadonlyEntity-test.ts +6 -4
  136. package/src/__tests__/ViewerContext-test.ts +4 -4
  137. package/src/__tests__/ViewerScopedEntityCompanion-test.ts +1 -0
  138. package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +0 -17
  139. package/src/errors/EntityDatabaseAdapterError.ts +14 -0
  140. package/src/errors/EntityError.ts +1 -0
  141. package/src/errors/__tests__/EntityDatabaseAdapterError-test.ts +9 -0
  142. package/src/errors/__tests__/EntityError-test.ts +13 -5
  143. package/src/index.ts +6 -1
  144. package/src/internal/EntityDataManager.ts +19 -54
  145. package/src/internal/EntityFieldTransformationUtils.ts +5 -5
  146. package/src/internal/EntityLoadInterfaces.ts +2 -0
  147. package/src/internal/EntityTableDataCoordinator.ts +2 -2
  148. package/src/internal/__tests__/CompositeFieldHolder-test.ts +8 -2
  149. package/src/internal/__tests__/EntityDataManager-test.ts +71 -202
  150. package/src/internal/__tests__/ReadThroughEntityCache-test.ts +39 -24
  151. package/src/metrics/EntityMetricsUtils.ts +23 -0
  152. package/src/metrics/IEntityMetricsAdapter.ts +3 -0
  153. package/src/metrics/__tests__/EntityMetricsUtils-test.ts +120 -0
  154. package/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.ts +47 -0
  155. package/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.ts +47 -0
  156. package/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.ts +177 -0
  157. package/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.ts +46 -0
  158. package/src/rules/PrivacyPolicyRule.ts +2 -2
  159. package/src/rules/__tests__/AllowIfAllSubRulesAllowPrivacyPolicyRule-test.ts +64 -0
  160. package/src/rules/__tests__/AllowIfAnySubRuleAllowsPrivacyPolicyRule-test.ts +64 -0
  161. package/src/rules/__tests__/AllowIfInParentCascadeDeletionPrivacyPolicyRule-test.ts +268 -0
  162. package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -2
  163. package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -2
  164. package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -2
  165. package/src/rules/__tests__/EvaluateIfEntityFieldPredicatePrivacyPolicyRule-test.ts +47 -0
  166. package/src/utils/EntityPrivacyUtils.ts +18 -29
  167. package/src/utils/__testfixtures__/PrivacyPolicyRuleTestUtils.ts +2 -2
  168. package/src/utils/__testfixtures__/StubDatabaseAdapter.ts +13 -101
  169. package/src/utils/__tests__/EntityCreationUtils-test.ts +6 -6
  170. package/src/utils/__tests__/EntityPrivacyUtils-test.ts +2 -2
  171. package/src/utils/collections/maps.ts +2 -2
  172. package/build/src/EntityLoaderUtils.js +0 -147
  173. 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
- private readonly fieldTransformerMap: FieldTransformerMap;
30
+ protected readonly fieldTransformerMap: FieldTransformerMap;
152
31
 
153
- constructor(private readonly entityConfiguration: EntityConfiguration<TFields, TIDField>) {
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 many objects matching the conjunction of where clauses constructed from
226
- * specified field equality operands.
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 fieldEqualityOperands - list of field equality where clause operand specifications
230
- * @param querySelectionModifiers - limit, offset, orderBy, and orderByRaw for the query
231
- * @returns array of objects matching the query
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 fetchManyByFieldEqualityConjunctionAsync<N extends keyof TFields>(
113
+ async fetchOneWhereAsync<
114
+ TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
115
+ TSerializedLoadValue,
116
+ TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
117
+ >(
234
118
  queryContext: EntityQueryContext,
235
- fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
236
- querySelectionModifiers: QuerySelectionModifiers<TFields>,
237
- ): Promise<readonly Readonly<TFields>[]> {
238
- const tableFieldSingleValueOperands: TableFieldSingleValueEqualityCondition[] = [];
239
- const tableFieldMultipleValueOperands: TableFieldMultiValueEqualityCondition[] = [];
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 results = await this.fetchManyByFieldEqualityConjunctionInternalAsync(
125
+ const result = await this.fetchOneWhereInternalAsync(
255
126
  queryContext.getQueryInterface(),
256
127
  this.entityConfiguration.tableName,
257
- tableFieldSingleValueOperands,
258
- tableFieldMultipleValueOperands,
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
- protected abstract fetchManyByFieldEqualityConjunctionInternalAsync(
268
- queryInterface: any,
269
- tableName: string,
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 results.map((result) =>
299
- transformDatabaseObjectToFields(this.entityConfiguration, this.fieldTransformerMap, result),
136
+ return transformDatabaseObjectToFields(
137
+ this.entityConfiguration,
138
+ this.fieldTransformerMap,
139
+ result,
300
140
  );
301
141
  }
302
142
 
303
- protected abstract fetchManyByRawWhereClauseInternalAsync(
143
+ protected abstract fetchOneWhereInternalAsync(
304
144
  queryInterface: any,
305
145
  tableName: string,
306
- rawWhereClause: string,
307
- bindings: any[] | object,
308
- querySelectionModifiers: TableQuerySelectionModifiersWithOrderByRaw,
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?: keyof TAssociatedFields;
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, EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
7
- import { EntityQueryContext, EntityTransactionalQueryContext } from './EntityQueryContext';
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 loader utilities for things like invalidation, entity construction, and authorization.
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 EntityLoaderUtils<
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
- private readonly entityClass: IEntityClass<
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
  }
@@ -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
  }