@expo/entity 0.16.0 → 0.20.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 (210) hide show
  1. package/build/EnforcingEntityLoader.js +2 -2
  2. package/build/EnforcingEntityLoader.js.map +1 -1
  3. package/build/Entity.js +8 -2
  4. package/build/Entity.js.map +1 -1
  5. package/build/EntityAssociationLoader.js +3 -3
  6. package/build/EntityAssociationLoader.js.map +1 -1
  7. package/build/EntityCompanion.d.ts +5 -0
  8. package/build/EntityCompanion.js +8 -1
  9. package/build/EntityCompanion.js.map +1 -1
  10. package/build/EntityCompanionProvider.d.ts +1 -1
  11. package/build/EntityCompanionProvider.js +5 -5
  12. package/build/EntityCompanionProvider.js.map +1 -1
  13. package/build/EntityConfiguration.d.ts +1 -1
  14. package/build/EntityConfiguration.js +3 -3
  15. package/build/EntityConfiguration.js.map +1 -1
  16. package/build/EntityDatabaseAdapter.d.ts +4 -4
  17. package/build/EntityDatabaseAdapter.js +13 -13
  18. package/build/EntityDatabaseAdapter.js.map +1 -1
  19. package/build/EntityFieldDefinition.d.ts +77 -0
  20. package/build/EntityFieldDefinition.js +53 -0
  21. package/build/EntityFieldDefinition.js.map +1 -0
  22. package/build/EntityFields.d.ts +5 -78
  23. package/build/EntityFields.js +19 -61
  24. package/build/EntityFields.js.map +1 -1
  25. package/build/EntityLoader.d.ts +3 -1
  26. package/build/EntityLoader.js +19 -15
  27. package/build/EntityLoader.js.map +1 -1
  28. package/build/EntityLoaderFactory.d.ts +3 -1
  29. package/build/EntityLoaderFactory.js +3 -2
  30. package/build/EntityLoaderFactory.js.map +1 -1
  31. package/build/EntityMutationInfo.d.ts +26 -0
  32. package/build/EntityMutationInfo.js +10 -0
  33. package/build/EntityMutationInfo.js.map +1 -0
  34. package/build/EntityMutationTriggerConfiguration.d.ts +4 -4
  35. package/build/EntityMutationValidator.d.ts +3 -3
  36. package/build/EntityMutationValidator.js.map +1 -1
  37. package/build/EntityMutator.d.ts +5 -16
  38. package/build/EntityMutator.js +62 -58
  39. package/build/EntityMutator.js.map +1 -1
  40. package/build/EntityPrivacyPolicy.d.ts +5 -4
  41. package/build/EntityPrivacyPolicy.js +60 -12
  42. package/build/EntityPrivacyPolicy.js.map +1 -1
  43. package/build/EntityQueryContext.d.ts +13 -0
  44. package/build/EntityQueryContext.js +18 -0
  45. package/build/EntityQueryContext.js.map +1 -1
  46. package/build/EntitySecondaryCacheLoader.js +2 -2
  47. package/build/EntitySecondaryCacheLoader.js.map +1 -1
  48. package/build/ReadonlyEntity.js +3 -4
  49. package/build/ReadonlyEntity.js.map +1 -1
  50. package/build/ViewerScopedEntityCompanion.d.ts +5 -0
  51. package/build/ViewerScopedEntityCompanion.js +6 -0
  52. package/build/ViewerScopedEntityCompanion.js.map +1 -1
  53. package/build/__tests__/EnforcingEntityLoader-test.js +82 -82
  54. package/build/__tests__/EnforcingEntityLoader-test.js.map +1 -1
  55. package/build/__tests__/Entity-test.js +6 -6
  56. package/build/__tests__/Entity-test.js.map +1 -1
  57. package/build/__tests__/EntityAssociationLoader-test.js +40 -40
  58. package/build/__tests__/EntityAssociationLoader-test.js.map +1 -1
  59. package/build/__tests__/EntityCommonUseCases-test.js +11 -11
  60. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  61. package/build/__tests__/EntityCompanion-test.js +3 -3
  62. package/build/__tests__/EntityCompanion-test.js.map +1 -1
  63. package/build/__tests__/EntityCompanionProvider-test.js +1 -1
  64. package/build/__tests__/EntityCompanionProvider-test.js.map +1 -1
  65. package/build/__tests__/EntityDatabaseAdapter-test.js +12 -12
  66. package/build/__tests__/EntityDatabaseAdapter-test.js.map +1 -1
  67. package/build/__tests__/EntityEdges-test.js +103 -6
  68. package/build/__tests__/EntityEdges-test.js.map +1 -1
  69. package/build/__tests__/EntityFields-test.js +18 -26
  70. package/build/__tests__/EntityFields-test.js.map +1 -1
  71. package/build/__tests__/EntityLoader-constructor-test.d.ts +22 -0
  72. package/build/__tests__/EntityLoader-constructor-test.js +111 -0
  73. package/build/__tests__/EntityLoader-constructor-test.js.map +1 -0
  74. package/build/__tests__/EntityLoader-test.js +81 -73
  75. package/build/__tests__/EntityLoader-test.js.map +1 -1
  76. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.d.ts +1 -0
  77. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js +81 -0
  78. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js.map +1 -0
  79. package/build/__tests__/EntityMutator-test.js +138 -136
  80. package/build/__tests__/EntityMutator-test.js.map +1 -1
  81. package/build/__tests__/EntityPrivacyPolicy-test.js +143 -67
  82. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  83. package/build/__tests__/EntitySecondaryCacheLoader-test.js +15 -15
  84. package/build/__tests__/EntitySecondaryCacheLoader-test.js.map +1 -1
  85. package/build/__tests__/EntitySelfReferentialEdges-test.js +13 -12
  86. package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
  87. package/build/__tests__/ReadonlyEntity-test.js +12 -12
  88. package/build/__tests__/ReadonlyEntity-test.js.map +1 -1
  89. package/build/__tests__/ViewerContext-test.js +2 -2
  90. package/build/__tests__/ViewerContext-test.js.map +1 -1
  91. package/build/__tests__/ViewerScopedEntityCompanion-test.js +2 -2
  92. package/build/__tests__/ViewerScopedEntityCompanion-test.js.map +1 -1
  93. package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js +2 -2
  94. package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js.map +1 -1
  95. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js +5 -5
  96. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
  97. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +5 -5
  98. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
  99. package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js +5 -5
  100. package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js.map +1 -1
  101. package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js +2 -2
  102. package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js.map +1 -1
  103. package/build/__tests__/entityUtils-test.js +21 -21
  104. package/build/__tests__/entityUtils-test.js.map +1 -1
  105. package/build/index.d.ts +3 -0
  106. package/build/index.js +5 -1
  107. package/build/index.js.map +1 -1
  108. package/build/internal/EntityDataManager.js +8 -7
  109. package/build/internal/EntityDataManager.js.map +1 -1
  110. package/build/internal/EntityFieldTransformationUtils.js +2 -2
  111. package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
  112. package/build/internal/ReadThroughEntityCache.js +4 -4
  113. package/build/internal/ReadThroughEntityCache.js.map +1 -1
  114. package/build/internal/__tests__/EntityDataManager-test.js +21 -21
  115. package/build/internal/__tests__/EntityDataManager-test.js.map +1 -1
  116. package/build/internal/__tests__/EntityFieldTransformationUtils-test.js +8 -8
  117. package/build/internal/__tests__/EntityFieldTransformationUtils-test.js.map +1 -1
  118. package/build/internal/__tests__/ReadThroughEntityCache-test.js +48 -48
  119. package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
  120. package/build/metrics/EntityMetricsUtils.js +1 -1
  121. package/build/metrics/EntityMetricsUtils.js.map +1 -1
  122. package/build/metrics/IEntityMetricsAdapter.d.ts +16 -0
  123. package/build/metrics/IEntityMetricsAdapter.js +6 -1
  124. package/build/metrics/IEntityMetricsAdapter.js.map +1 -1
  125. package/build/metrics/NoOpEntityMetricsAdapter.d.ts +2 -1
  126. package/build/metrics/NoOpEntityMetricsAdapter.js +1 -0
  127. package/build/metrics/NoOpEntityMetricsAdapter.js.map +1 -1
  128. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js +4 -4
  129. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
  130. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js +4 -4
  131. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
  132. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js +4 -4
  133. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
  134. package/build/testfixtures/DateIDTestEntity.js.map +1 -1
  135. package/build/testfixtures/SimpleTestEntity.js.map +1 -1
  136. package/build/testfixtures/TestEntity.d.ts +6 -6
  137. package/build/testfixtures/TestEntity.js +4 -4
  138. package/build/testfixtures/TestEntity.js.map +1 -1
  139. package/build/testfixtures/TestEntity2.d.ts +5 -5
  140. package/build/testfixtures/TestEntity2.js.map +1 -1
  141. package/build/testfixtures/TestEntityNumberKey.js +1 -1
  142. package/build/testfixtures/TestEntityNumberKey.js.map +1 -1
  143. package/build/utils/collections/__tests__/maps-test.js +13 -13
  144. package/build/utils/collections/__tests__/maps-test.js.map +1 -1
  145. package/build/utils/collections/maps.js +1 -1
  146. package/build/utils/collections/maps.js.map +1 -1
  147. package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +6 -6
  148. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js +1 -1
  149. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
  150. package/build/utils/testing/StubCacheAdapter.js +1 -1
  151. package/build/utils/testing/StubCacheAdapter.js.map +1 -1
  152. package/build/utils/testing/StubDatabaseAdapter.js +7 -7
  153. package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
  154. package/build/utils/testing/__tests__/StubDatabaseAdapter-test.js +26 -26
  155. package/build/utils/testing/__tests__/StubDatabaseAdapter-test.js.map +1 -1
  156. package/build/utils/testing/describeFieldTestCase.d.ts +2 -0
  157. package/build/utils/testing/describeFieldTestCase.js +18 -0
  158. package/build/utils/testing/describeFieldTestCase.js.map +1 -0
  159. package/package.json +2 -1
  160. package/src/Entity.ts +10 -2
  161. package/src/EntityAssociationLoader.ts +1 -1
  162. package/src/EntityCompanion.ts +10 -2
  163. package/src/EntityCompanionProvider.ts +5 -9
  164. package/src/EntityConfiguration.ts +1 -1
  165. package/src/EntityDatabaseAdapter.ts +10 -8
  166. package/src/EntityFieldDefinition.ts +124 -0
  167. package/src/EntityFields.ts +11 -126
  168. package/src/EntityLoader.ts +12 -4
  169. package/src/EntityLoaderFactory.ts +5 -2
  170. package/src/EntityMutationInfo.ts +47 -0
  171. package/src/EntityMutationTriggerConfiguration.ts +5 -5
  172. package/src/EntityMutationValidator.ts +10 -4
  173. package/src/EntityMutator.ts +98 -76
  174. package/src/EntityPrivacyPolicy.ts +77 -19
  175. package/src/EntityQueryContext.ts +20 -0
  176. package/src/ReadonlyEntity.ts +3 -2
  177. package/src/ViewerScopedEntityCompanion.ts +8 -0
  178. package/src/__tests__/Entity-test.ts +8 -8
  179. package/src/__tests__/EntityCommonUseCases-test.ts +4 -4
  180. package/src/__tests__/EntityEdges-test.ts +169 -14
  181. package/src/__tests__/EntityFields-test.ts +6 -23
  182. package/src/__tests__/EntityLoader-constructor-test.ts +177 -0
  183. package/src/__tests__/EntityLoader-test.ts +48 -20
  184. package/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts +105 -0
  185. package/src/__tests__/EntityMutator-test.ts +153 -146
  186. package/src/__tests__/EntityPrivacyPolicy-test.ts +215 -78
  187. package/src/__tests__/EntitySecondaryCacheLoader-test.ts +7 -9
  188. package/src/__tests__/EntitySelfReferentialEdges-test.ts +6 -5
  189. package/src/__tests__/ReadonlyEntity-test.ts +1 -1
  190. package/src/__tests__/ViewerContext-test.ts +7 -6
  191. package/src/__tests__/ViewerScopedEntityCompanion-test.ts +11 -10
  192. package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +4 -3
  193. package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +6 -6
  194. package/src/__tests__/cases/TwoEntitySameTableOverlappingRows-test.ts +4 -4
  195. package/src/index.ts +3 -0
  196. package/src/internal/EntityDataManager.ts +2 -1
  197. package/src/internal/__tests__/EntityDataManager-test.ts +6 -6
  198. package/src/internal/__tests__/ReadThroughEntityCache-test.ts +15 -13
  199. package/src/metrics/EntityMetricsUtils.ts +56 -50
  200. package/src/metrics/IEntityMetricsAdapter.ts +23 -0
  201. package/src/metrics/NoOpEntityMetricsAdapter.ts +2 -0
  202. package/src/testfixtures/DateIDTestEntity.ts +4 -4
  203. package/src/testfixtures/SimpleTestEntity.ts +4 -4
  204. package/src/testfixtures/TestEntity.ts +8 -8
  205. package/src/testfixtures/TestEntity2.ts +4 -4
  206. package/src/testfixtures/TestEntityNumberKey.ts +6 -6
  207. package/src/utils/testing/StubDatabaseAdapter.ts +4 -4
  208. package/src/utils/testing/__tests__/StubDatabaseAdapter-test.ts +18 -18
  209. package/src/utils/testing/describeFieldTestCase.ts +21 -0
  210. package/CHANGELOG.md +0 -241
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/entity",
3
- "version": "0.16.0",
3
+ "version": "0.20.0",
4
4
  "description": "A privacy-first data model",
5
5
  "files": [
6
6
  "build",
@@ -12,6 +12,7 @@
12
12
  "tsc": "tsc",
13
13
  "clean": "rm -rf build coverage coverage-integration",
14
14
  "lint": "eslint src",
15
+ "lint-fix": "eslint src --fix",
15
16
  "test": "jest --rootDir . --config ../../resources/jest.config.js",
16
17
  "integration": "../../resources/run-with-docker yarn integration-no-setup",
17
18
  "integration-no-setup": "jest --config ../../resources/jest-integration.config.js --rootDir . --runInBand --passWithNoTests",
package/src/Entity.ts CHANGED
@@ -243,12 +243,16 @@ export default abstract class Entity<
243
243
  .getQueryContextProvider()
244
244
  .getQueryContext()
245
245
  ): Promise<boolean> {
246
+ const companion = existingEntity
247
+ .getViewerContext()
248
+ .getViewerScopedEntityCompanionForClass(this);
246
249
  const privacyPolicy = new (this.getCompanionDefinition().privacyPolicyClass)();
247
250
  const evaluationResult = await asyncResult(
248
251
  privacyPolicy.authorizeUpdateAsync(
249
252
  existingEntity.getViewerContext(),
250
253
  queryContext,
251
- existingEntity
254
+ existingEntity,
255
+ companion.getMetricsAdapter()
252
256
  )
253
257
  );
254
258
  return evaluationResult.ok;
@@ -292,12 +296,16 @@ export default abstract class Entity<
292
296
  .getQueryContextProvider()
293
297
  .getQueryContext()
294
298
  ): Promise<boolean> {
299
+ const companion = existingEntity
300
+ .getViewerContext()
301
+ .getViewerScopedEntityCompanionForClass(this);
295
302
  const privacyPolicy = new (this.getCompanionDefinition().privacyPolicyClass)();
296
303
  const evaluationResult = await asyncResult(
297
304
  privacyPolicy.authorizeDeleteAsync(
298
305
  existingEntity.getViewerContext(),
299
306
  queryContext,
300
- existingEntity
307
+ existingEntity,
308
+ companion.getMetricsAdapter()
301
309
  )
302
310
  );
303
311
  return evaluationResult.ok;
@@ -71,7 +71,7 @@ export default class EntityAssociationLoader<
71
71
  .getLoaderFactory()
72
72
  .forLoad(queryContext);
73
73
 
74
- return (await loader.loadByIDAsync((associatedEntityID as unknown) as TAssociatedID)) as Result<
74
+ return (await loader.loadByIDAsync(associatedEntityID as unknown as TAssociatedID)) as Result<
75
75
  null extends TFields[TIdentifyingField] ? TAssociatedEntity | null : TAssociatedEntity
76
76
  >;
77
77
  }
@@ -73,7 +73,7 @@ export default class EntityCompanion<
73
73
  TEntity,
74
74
  TSelectedFields
75
75
  >,
76
- metricsAdapter: IEntityMetricsAdapter
76
+ private readonly metricsAdapter: IEntityMetricsAdapter
77
77
  ) {
78
78
  const privacyPolicy = new PrivacyPolicyClass();
79
79
  this.entityLoaderFactory = new EntityLoaderFactory<
@@ -87,7 +87,8 @@ export default class EntityCompanion<
87
87
  tableDataCoordinator.entityConfiguration,
88
88
  entityClass,
89
89
  privacyPolicy,
90
- tableDataCoordinator.dataManager
90
+ tableDataCoordinator.dataManager,
91
+ metricsAdapter
91
92
  );
92
93
  this.entityMutatorFactory = new EntityMutatorFactory(
93
94
  tableDataCoordinator.entityConfiguration,
@@ -129,4 +130,11 @@ export default class EntityCompanion<
129
130
  getQueryContextProvider(): EntityQueryContextProvider {
130
131
  return this.tableDataCoordinator.getQueryContextProvider();
131
132
  }
133
+
134
+ /**
135
+ * Get the {@link IEntityMetricsAdapter} for this companion.
136
+ */
137
+ getMetricsAdapter(): IEntityMetricsAdapter {
138
+ return this.metricsAdapter;
139
+ }
132
140
  }
@@ -162,14 +162,10 @@ export class EntityCompanionDefinition<
162
162
  * {@link EntityCompanion} for each type of {@link Entity}.
163
163
  */
164
164
  export default class EntityCompanionProvider {
165
- private readonly companionMap: Map<
166
- string,
167
- EntityCompanion<any, any, any, any, any, any>
168
- > = new Map();
169
- private readonly tableDataCoordinatorMap: Map<
170
- string,
171
- EntityTableDataCoordinator<any>
172
- > = new Map();
165
+ private readonly companionMap: Map<string, EntityCompanion<any, any, any, any, any, any>> =
166
+ new Map();
167
+ private readonly tableDataCoordinatorMap: Map<string, EntityTableDataCoordinator<any>> =
168
+ new Map();
173
169
 
174
170
  /**
175
171
  * Instantiate an Entity framework.
@@ -178,7 +174,7 @@ export default class EntityCompanionProvider {
178
174
  * @param cacheAdapterFlavors - Cache adapter configurations for this instance
179
175
  */
180
176
  constructor(
181
- private metricsAdapter: IEntityMetricsAdapter,
177
+ public readonly metricsAdapter: IEntityMetricsAdapter,
182
178
  private databaseAdapterFlavors: ReadonlyMap<
183
179
  DatabaseAdapterFlavor,
184
180
  DatabaseAdapterFlavorDefinition
@@ -1,6 +1,6 @@
1
1
  import { IEntityClass } from './Entity';
2
2
  import { DatabaseAdapterFlavor, CacheAdapterFlavor } from './EntityCompanionProvider';
3
- import { EntityFieldDefinition } from './EntityFields';
3
+ import { EntityFieldDefinition } from './EntityFieldDefinition';
4
4
  import { mapMap, invertMap, reduceMap } from './utils/collections/maps';
5
5
 
6
6
  /**
@@ -69,12 +69,14 @@ export interface QuerySelectionModifiers<TFields> {
69
69
  }
70
70
 
71
71
  export interface TableQuerySelectionModifiers {
72
- orderBy?: {
73
- columnName: string;
74
- order: OrderByOrdering;
75
- }[];
76
- offset?: number;
77
- limit?: number;
72
+ orderBy:
73
+ | {
74
+ columnName: string;
75
+ order: OrderByOrdering;
76
+ }[]
77
+ | undefined;
78
+ offset: number | undefined;
79
+ limit: number | undefined;
78
80
  }
79
81
 
80
82
  /**
@@ -263,7 +265,7 @@ export default abstract class EntityDatabaseAdapter<TFields> {
263
265
  return transformDatabaseObjectToFields(
264
266
  this.entityConfiguration,
265
267
  this.fieldTransformerMap,
266
- results[0]
268
+ results[0]!
267
269
  );
268
270
  }
269
271
 
@@ -315,7 +317,7 @@ export default abstract class EntityDatabaseAdapter<TFields> {
315
317
  return transformDatabaseObjectToFields(
316
318
  this.entityConfiguration,
317
319
  this.fieldTransformerMap,
318
- results[0]
320
+ results[0]!
319
321
  );
320
322
  }
321
323
 
@@ -0,0 +1,124 @@
1
+ import { IEntityClass } from './Entity';
2
+ import EntityPrivacyPolicy from './EntityPrivacyPolicy';
3
+ import ReadonlyEntity from './ReadonlyEntity';
4
+ import ViewerContext from './ViewerContext';
5
+
6
+ export enum EntityEdgeDeletionBehavior {
7
+ /**
8
+ * Default. Invalidate the cache for all entities that reference the entity
9
+ * being deleted through this field, and transitively run deletions on those entities.
10
+ * This is most useful when the database itself expresses foreign
11
+ * keys and cascading deletes or set nulls and the entity framework just needs to
12
+ * be kept consistent with the state of the database.
13
+ */
14
+ CASCADE_DELETE_INVALIDATE_CACHE,
15
+
16
+ /**
17
+ * Delete all entities that reference the entity being deleted through this field. This is very similar
18
+ * to SQL `ON DELETE CASCADE` but is done in the Entity framework instead of at the underlying level.
19
+ * This will also invalidate the cached referencing entities.
20
+ */
21
+ CASCADE_DELETE,
22
+
23
+ /**
24
+ * Set this field to null when the referenced entity is deleted. This is very similar
25
+ * to SQL `ON DELETE SET NULL` but is done in the Entity framework instead of at the underlying level.
26
+ * This will also invalidate the cached referencing entities.
27
+ */
28
+ SET_NULL,
29
+ }
30
+
31
+ export interface EntityAssociationDefinition<
32
+ TViewerContext extends ViewerContext,
33
+ TAssociatedFields,
34
+ TAssociatedID extends NonNullable<TAssociatedFields[TAssociatedSelectedFields]>,
35
+ TAssociatedEntity extends ReadonlyEntity<
36
+ TAssociatedFields,
37
+ TAssociatedID,
38
+ TViewerContext,
39
+ TAssociatedSelectedFields
40
+ >,
41
+ TAssociatedPrivacyPolicy extends EntityPrivacyPolicy<
42
+ TAssociatedFields,
43
+ TAssociatedID,
44
+ TViewerContext,
45
+ TAssociatedEntity,
46
+ TAssociatedSelectedFields
47
+ >,
48
+ TAssociatedSelectedFields extends keyof TAssociatedFields = keyof TAssociatedFields
49
+ > {
50
+ /**
51
+ * Class of entity on the other end of this edge.
52
+ */
53
+ getAssociatedEntityClass: () => IEntityClass<
54
+ TAssociatedFields,
55
+ TAssociatedID,
56
+ TViewerContext,
57
+ TAssociatedEntity,
58
+ TAssociatedPrivacyPolicy,
59
+ TAssociatedSelectedFields
60
+ >;
61
+
62
+ /**
63
+ * Field by which to load the instance of associatedEntityClass. If not provided, the
64
+ * associatedEntityClass instance is fetched by its ID.
65
+ */
66
+ associatedEntityLookupByField?: keyof TAssociatedFields;
67
+
68
+ /**
69
+ * What action to perform on the current entity when the entity on the referencing end of
70
+ * this edge is deleted.
71
+ *
72
+ * @remarks
73
+ * The entity framework doesn't prescribe a one-size-fits-all solution for referential
74
+ * integrity; instead it exposes mechanisms that supports both database foreign key constraints
75
+ * and implicit entity-specified foreign keys. Choosing which approach to use often depends on
76
+ * application requirements, and sometimes even a mix-and-match is the right choice.
77
+ *
78
+ * - If referential integrity is critical to your application, database foreign key constraints
79
+ * combined with {@link EntityEdgeDeletionBehavior.CASACDE_DELETE_INVALIDATE_CACHE} are recommended.
80
+ * - If the database being used doesn't support foreign keys, then using the entity framework for referential
81
+ * integrity is recommended.
82
+ */
83
+ edgeDeletionBehavior?: EntityEdgeDeletionBehavior;
84
+ }
85
+
86
+ export abstract class EntityFieldDefinition<T> {
87
+ readonly columnName: string;
88
+ readonly cache: boolean;
89
+ readonly association: EntityAssociationDefinition<any, any, any, any, any, any> | undefined;
90
+ /**
91
+ *
92
+ * @param columnName - Column name in the database.
93
+ * @param cache - Whether or not to cache loaded instances of the entity by this field. The column name is
94
+ * used to derive a cache key for the cache entry. If true, this column must be able uniquely
95
+ * identify the entity.
96
+ */
97
+ constructor({
98
+ columnName,
99
+ cache = false,
100
+ association,
101
+ }: {
102
+ columnName: string;
103
+ cache?: boolean;
104
+ association?: EntityAssociationDefinition<any, any, any, any, any, any>;
105
+ }) {
106
+ this.columnName = columnName;
107
+ this.cache = cache;
108
+ this.association = association;
109
+ }
110
+
111
+ /**
112
+ * Validates input value for a field of this type. Null and undefined are considered valid by default. This is used for things like:
113
+ * - EntityLoader.loadByFieldValue - to ensure the value being loaded by is a valid value
114
+ * - EntityMutator.setField - to ensure the value being set is a valid value
115
+ */
116
+ public validateInputValue(value: T | null | undefined): boolean {
117
+ if (value === null || value === undefined) {
118
+ return true;
119
+ }
120
+
121
+ return this.validateInputValueInternal(value);
122
+ }
123
+ protected abstract validateInputValueInternal(value: T): boolean;
124
+ }
@@ -1,129 +1,6 @@
1
1
  import { validate as validateUUID } from 'uuid';
2
2
 
3
- import { IEntityClass } from './Entity';
4
- import EntityPrivacyPolicy from './EntityPrivacyPolicy';
5
- import ReadonlyEntity from './ReadonlyEntity';
6
- import ViewerContext from './ViewerContext';
7
-
8
- export enum EntityEdgeDeletionBehavior {
9
- /**
10
- * Default. Invalidate the cache for all entities that reference the entity
11
- * being deleted through this field, and transitively run deletions on those entities.
12
- * This is most useful when the database itself expresses foreign
13
- * keys and cascading deletes or set nulls and the entity framework just needs to
14
- * be kept consistent with the state of the database.
15
- */
16
- CASCADE_DELETE_INVALIDATE_CACHE,
17
-
18
- /**
19
- * Delete all entities that reference the entity being deleted through this field. This is very similar
20
- * to SQL `ON DELETE CASCADE` but is done in the Entity framework instead of at the underlying level.
21
- * This will also invalidate the cached referencing entities.
22
- */
23
- CASCADE_DELETE,
24
-
25
- /**
26
- * Set this field to null when the referenced entity is deleted. This is very similar
27
- * to SQL `ON DELETE SET NULL` but is done in the Entity framework instead of at the underlying level.
28
- * This will also invalidate the cached referencing entities.
29
- */
30
- SET_NULL,
31
- }
32
-
33
- export interface EntityAssociationDefinition<
34
- TViewerContext extends ViewerContext,
35
- TAssociatedFields,
36
- TAssociatedID extends NonNullable<TAssociatedFields[TAssociatedSelectedFields]>,
37
- TAssociatedEntity extends ReadonlyEntity<
38
- TAssociatedFields,
39
- TAssociatedID,
40
- TViewerContext,
41
- TAssociatedSelectedFields
42
- >,
43
- TAssociatedPrivacyPolicy extends EntityPrivacyPolicy<
44
- TAssociatedFields,
45
- TAssociatedID,
46
- TViewerContext,
47
- TAssociatedEntity,
48
- TAssociatedSelectedFields
49
- >,
50
- TAssociatedSelectedFields extends keyof TAssociatedFields = keyof TAssociatedFields
51
- > {
52
- /**
53
- * Class of entity on the other end of this edge.
54
- */
55
- getAssociatedEntityClass: () => IEntityClass<
56
- TAssociatedFields,
57
- TAssociatedID,
58
- TViewerContext,
59
- TAssociatedEntity,
60
- TAssociatedPrivacyPolicy,
61
- TAssociatedSelectedFields
62
- >;
63
-
64
- /**
65
- * Field by which to load the instance of associatedEntityClass. If not provided, the
66
- * associatedEntityClass instance is fetched by its ID.
67
- */
68
- associatedEntityLookupByField?: keyof TAssociatedFields;
69
-
70
- /**
71
- * What action to perform on the current entity when the entity on the referencing end of
72
- * this edge is deleted.
73
- *
74
- * @remarks
75
- * The entity framework doesn't prescribe a one-size-fits-all solution for referential
76
- * integrity; instead it exposes mechanisms that supports both database foreign key constraints
77
- * and implicit entity-specified foreign keys. Choosing which approach to use often depends on
78
- * application requirements, and sometimes even a mix-and-match is the right choice.
79
- *
80
- * - If referential integrity is critical to your application, database foreign key constraints
81
- * combined with {@link EntityEdgeDeletionBehavior.CASACDE_DELETE_INVALIDATE_CACHE} are recommended.
82
- * - If the database being used doesn't support foreign keys, then using the entity framework for referential
83
- * integrity is recommended.
84
- */
85
- edgeDeletionBehavior?: EntityEdgeDeletionBehavior;
86
- }
87
-
88
- export abstract class EntityFieldDefinition<T> {
89
- readonly columnName: string;
90
- readonly cache: boolean;
91
- readonly association?: EntityAssociationDefinition<any, any, any, any, any, any>;
92
- /**
93
- *
94
- * @param columnName - Column name in the database.
95
- * @param cache - Whether or not to cache loaded instances of the entity by this field. The column name is
96
- * used to derive a cache key for the cache entry. If true, this column must be able uniquely
97
- * identify the entity.
98
- */
99
- constructor({
100
- columnName,
101
- cache = false,
102
- association,
103
- }: {
104
- columnName: string;
105
- cache?: boolean;
106
- association?: EntityAssociationDefinition<any, any, any, any, any, any>;
107
- }) {
108
- this.columnName = columnName;
109
- this.cache = cache;
110
- this.association = association;
111
- }
112
-
113
- /**
114
- * Validates input value for a field of this type. Null and undefined are considered valid by default. This is used for things like:
115
- * - EntityLoader.loadByFieldValue - to ensure the value being loaded by is a valid value
116
- * - EntityMutator.setField - to ensure the value being set is a valid value
117
- */
118
- public validateInputValue(value: T | null | undefined): boolean {
119
- if (value === null || value === undefined) {
120
- return true;
121
- }
122
-
123
- return this.validateInputValueInternal(value);
124
- }
125
- protected abstract validateInputValueInternal(value: T): boolean;
126
- }
3
+ import { EntityFieldDefinition } from './EntityFieldDefinition';
127
4
 
128
5
  export class StringField extends EntityFieldDefinition<string> {
129
6
  protected validateInputValueInternal(value: string): boolean {
@@ -131,7 +8,7 @@ export class StringField extends EntityFieldDefinition<string> {
131
8
  }
132
9
  }
133
10
  export class UUIDField extends StringField {
134
- protected validateInputValueInternal(value: string): boolean {
11
+ protected override validateInputValueInternal(value: string): boolean {
135
12
  return validateUUID(value);
136
13
  }
137
14
  }
@@ -145,11 +22,19 @@ export class BooleanField extends EntityFieldDefinition<boolean> {
145
22
  return typeof value === 'boolean';
146
23
  }
147
24
  }
148
- export class NumberField extends EntityFieldDefinition<number> {
25
+
26
+ export class IntField extends EntityFieldDefinition<number> {
27
+ protected validateInputValueInternal(value: number): boolean {
28
+ return typeof value === 'number' && Number.isInteger(value);
29
+ }
30
+ }
31
+
32
+ export class FloatField extends EntityFieldDefinition<number> {
149
33
  protected validateInputValueInternal(value: number): boolean {
150
34
  return typeof value === 'number';
151
35
  }
152
36
  }
37
+
153
38
  export class StringArrayField extends EntityFieldDefinition<string[]> {
154
39
  protected validateInputValueInternal(value: string[]): boolean {
155
40
  return Array.isArray(value) && value.every((subValue) => typeof subValue === 'string');
@@ -16,6 +16,7 @@ import ViewerContext from './ViewerContext';
16
16
  import EntityInvalidFieldValueError from './errors/EntityInvalidFieldValueError';
17
17
  import EntityNotFoundError from './errors/EntityNotFoundError';
18
18
  import EntityDataManager from './internal/EntityDataManager';
19
+ import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
19
20
  import { mapMap, mapMapAsync } from './utils/collections/maps';
20
21
 
21
22
  /**
@@ -49,7 +50,8 @@ export default class EntityLoader<
49
50
  TSelectedFields
50
51
  >,
51
52
  private readonly privacyPolicy: TPrivacyPolicy,
52
- private readonly dataManager: EntityDataManager<TFields>
53
+ private readonly dataManager: EntityDataManager<TFields>,
54
+ protected readonly metricsAdapter: IEntityMetricsAdapter
53
55
  ) {}
54
56
 
55
57
  /**
@@ -216,7 +218,8 @@ export default class EntityLoader<
216
218
  this.privacyPolicy.authorizeReadAsync(
217
219
  this.viewerContext,
218
220
  this.queryContext,
219
- uncheckedEntityResult.value
221
+ uncheckedEntityResult.value,
222
+ this.metricsAdapter
220
223
  )
221
224
  );
222
225
  })
@@ -269,7 +272,8 @@ export default class EntityLoader<
269
272
  this.privacyPolicy.authorizeReadAsync(
270
273
  this.viewerContext,
271
274
  this.queryContext,
272
- uncheckedEntityResult.value
275
+ uncheckedEntityResult.value,
276
+ this.metricsAdapter
273
277
  )
274
278
  );
275
279
  })
@@ -298,6 +302,9 @@ export default class EntityLoader<
298
302
  try {
299
303
  return result(new this.entityClass(this.viewerContext, fieldsObject));
300
304
  } catch (e) {
305
+ if (!(e instanceof Error)) {
306
+ throw e;
307
+ }
301
308
  return result(e);
302
309
  }
303
310
  });
@@ -325,7 +332,8 @@ export default class EntityLoader<
325
332
  this.privacyPolicy.authorizeReadAsync(
326
333
  this.viewerContext,
327
334
  this.queryContext,
328
- uncheckedEntityResult.value
335
+ uncheckedEntityResult.value,
336
+ this.metricsAdapter
329
337
  )
330
338
  );
331
339
  })
@@ -6,6 +6,7 @@ import { EntityQueryContext } from './EntityQueryContext';
6
6
  import ReadonlyEntity from './ReadonlyEntity';
7
7
  import ViewerContext from './ViewerContext';
8
8
  import EntityDataManager from './internal/EntityDataManager';
9
+ import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
9
10
 
10
11
  /**
11
12
  * The primary entry point for loading entities.
@@ -35,7 +36,8 @@ export default class EntityLoaderFactory<
35
36
  TSelectedFields
36
37
  >,
37
38
  private readonly privacyPolicyClass: TPrivacyPolicy,
38
- private readonly dataManager: EntityDataManager<TFields>
39
+ private readonly dataManager: EntityDataManager<TFields>,
40
+ protected readonly metricsAdapter: IEntityMetricsAdapter
39
41
  ) {}
40
42
 
41
43
  /**
@@ -53,7 +55,8 @@ export default class EntityLoaderFactory<
53
55
  this.entityConfiguration,
54
56
  this.entityClass,
55
57
  this.privacyPolicyClass,
56
- this.dataManager
58
+ this.dataManager,
59
+ this.metricsAdapter
57
60
  );
58
61
  }
59
62
  }
@@ -0,0 +1,47 @@
1
+ import Entity from './Entity';
2
+ import ViewerContext from './ViewerContext';
3
+
4
+ export enum EntityMutationType {
5
+ CREATE,
6
+ UPDATE,
7
+ DELETE,
8
+ }
9
+
10
+ export type EntityValidatorMutationInfo<
11
+ TFields,
12
+ TID extends NonNullable<TFields[TSelectedFields]>,
13
+ TViewerContext extends ViewerContext,
14
+ TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
15
+ TSelectedFields extends keyof TFields = keyof TFields
16
+ > =
17
+ | {
18
+ type: EntityMutationType.CREATE;
19
+ }
20
+ | {
21
+ type: EntityMutationType.UPDATE;
22
+ previousValue: TEntity;
23
+ };
24
+
25
+ export type EntityMutationTriggerDeleteCascadeInfo<> = {
26
+ entity: Entity<any, any, any, any>;
27
+ cascadingDeleteCause: EntityMutationTriggerDeleteCascadeInfo | null;
28
+ };
29
+
30
+ export type EntityTriggerMutationInfo<
31
+ TFields,
32
+ TID extends NonNullable<TFields[TSelectedFields]>,
33
+ TViewerContext extends ViewerContext,
34
+ TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
35
+ TSelectedFields extends keyof TFields = keyof TFields
36
+ > =
37
+ | {
38
+ type: EntityMutationType.CREATE;
39
+ }
40
+ | {
41
+ type: EntityMutationType.UPDATE;
42
+ previousValue: TEntity;
43
+ }
44
+ | {
45
+ type: EntityMutationType.DELETE;
46
+ cascadingDeleteCause: EntityMutationTriggerDeleteCascadeInfo | null;
47
+ };
@@ -1,5 +1,5 @@
1
- import { EntityMutationInfo } from './EntityMutator';
2
- import { EntityQueryContext } from './EntityQueryContext';
1
+ import { EntityTriggerMutationInfo } from './EntityMutationInfo';
2
+ import { EntityTransactionalQueryContext } from './EntityQueryContext';
3
3
  import ReadonlyEntity from './ReadonlyEntity';
4
4
  import ViewerContext from './ViewerContext';
5
5
 
@@ -78,9 +78,9 @@ export abstract class EntityMutationTrigger<
78
78
  > {
79
79
  abstract executeAsync(
80
80
  viewerContext: TViewerContext,
81
- queryContext: EntityQueryContext,
81
+ queryContext: EntityTransactionalQueryContext,
82
82
  entity: TEntity,
83
- mutationInfo: EntityMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
83
+ mutationInfo: EntityTriggerMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
84
84
  ): Promise<void>;
85
85
  }
86
86
 
@@ -98,6 +98,6 @@ export abstract class EntityNonTransactionalMutationTrigger<
98
98
  abstract executeAsync(
99
99
  viewerContext: TViewerContext,
100
100
  entity: TEntity,
101
- mutationInfo: EntityMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
101
+ mutationInfo: EntityTriggerMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
102
102
  ): Promise<void>;
103
103
  }
@@ -1,5 +1,5 @@
1
- import { EntityMutationInfo } from './EntityMutator';
2
- import { EntityQueryContext } from './EntityQueryContext';
1
+ import { EntityValidatorMutationInfo } from './EntityMutationInfo';
2
+ import { EntityTransactionalQueryContext } from './EntityQueryContext';
3
3
  import ReadonlyEntity from './ReadonlyEntity';
4
4
  import ViewerContext from './ViewerContext';
5
5
 
@@ -16,8 +16,14 @@ export default abstract class EntityMutationValidator<
16
16
  > {
17
17
  abstract executeAsync(
18
18
  viewerContext: TViewerContext,
19
- queryContext: EntityQueryContext,
19
+ queryContext: EntityTransactionalQueryContext,
20
20
  entity: TEntity,
21
- mutationInfo: EntityMutationInfo<TFields, TID, TViewerContext, TEntity, TSelectedFields>
21
+ mutationInfo: EntityValidatorMutationInfo<
22
+ TFields,
23
+ TID,
24
+ TViewerContext,
25
+ TEntity,
26
+ TSelectedFields
27
+ >
22
28
  ): Promise<void>;
23
29
  }