@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
@@ -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
- protected readonly metricsAdapter: IEntityMetricsAdapter,
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.utils.constructAndAuthorizeEntitiesAsync(fieldValuesToFieldObjects);
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.utils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects),
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 = entityLoader.utils.constructEntity({
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
- entityLoader.utils.invalidateFieldsForTransaction(queryContext, insertResult);
380
- await entityLoader.utils.invalidateFieldsAsync(insertResult);
388
+ invalidationUtils.invalidateFieldsForTransaction(queryContext, insertResult);
389
+ await invalidationUtils.invalidateFieldsAsync(insertResult);
381
390
  });
382
391
 
383
- entityLoader.utils.invalidateFieldsForTransaction(queryContext, insertResult);
392
+ invalidationUtils.invalidateFieldsForTransaction(queryContext, insertResult);
384
393
 
385
- const unauthorizedEntityAfterInsert = entityLoader.utils.constructEntity(insertResult);
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 = entityLoader.utils.constructEntity(this.fieldsForEntity);
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
- entityLoader.utils.invalidateFieldsForTransaction(
627
+ invalidationUtils.invalidateFieldsForTransaction(
610
628
  queryContext,
611
629
  this.originalEntity.getAllDatabaseFields(),
612
630
  );
613
- entityLoader.utils.invalidateFieldsForTransaction(queryContext, this.fieldsForEntity);
631
+ invalidationUtils.invalidateFieldsForTransaction(queryContext, this.fieldsForEntity);
614
632
  await Promise.all([
615
- entityLoader.utils.invalidateFieldsAsync(this.originalEntity.getAllDatabaseFields()),
616
- entityLoader.utils.invalidateFieldsAsync(this.fieldsForEntity),
633
+ invalidationUtils.invalidateFieldsAsync(this.originalEntity.getAllDatabaseFields()),
634
+ invalidationUtils.invalidateFieldsAsync(this.fieldsForEntity),
617
635
  ]);
618
636
  });
619
637
 
620
- entityLoader.utils.invalidateFieldsForTransaction(
638
+ invalidationUtils.invalidateFieldsForTransaction(
621
639
  queryContext,
622
640
  this.originalEntity.getAllDatabaseFields(),
623
641
  );
624
- entityLoader.utils.invalidateFieldsForTransaction(queryContext, this.fieldsForEntity);
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 entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext, {
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
- entityLoader.utils.invalidateFieldsForTransaction(
865
+ invalidationUtils.invalidateFieldsForTransaction(
851
866
  queryContext,
852
867
  this.entity.getAllDatabaseFields(),
853
868
  );
854
- await entityLoader.utils.invalidateFieldsAsync(this.entity.getAllDatabaseFields());
869
+ await invalidationUtils.invalidateFieldsAsync(this.entity.getAllDatabaseFields());
855
870
  });
856
- entityLoader.utils.invalidateFieldsForTransaction(
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 as any)
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 updates
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>,
@@ -59,8 +59,8 @@ export class EntityCompanion<
59
59
  TPrivacyPolicy,
60
60
  TSelectedFields
61
61
  >,
62
- private readonly tableDataCoordinator: EntityTableDataCoordinator<TFields, TIDField>,
63
- private readonly metricsAdapter: IEntityMetricsAdapter,
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
- getQueryContextProviderForDatabaseAdaptorFlavor(
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 adaptor configuration found for flavor: ${databaseAdapterFlavor}`,
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 adaptor configuration found for flavor: ${entityConfiguration.databaseAdapterFlavor}`,
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 adaptor configuration found for flavor: ${entityConfiguration.cacheAdapterFlavor}`,
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(keyDefinition.compositeField);
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(new CompositeFieldHolder(compositeField).serialize())
83
- ?.compositeFieldHolder;
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: keyof TFields;
113
+ readonly idField: TIDField;
111
114
  readonly tableName: string;
112
115
  readonly cacheableKeys: ReadonlySet<keyof TFields>;
113
116
  readonly compositeFieldInfo: CompositeFieldInfo<TFields, TIDField>;