@expo/entity 0.35.0 → 0.36.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 (309) hide show
  1. package/build/AuthorizationResultBasedEntityLoader.d.ts +128 -0
  2. package/build/AuthorizationResultBasedEntityLoader.js +196 -0
  3. package/build/AuthorizationResultBasedEntityLoader.js.map +1 -0
  4. package/build/ComposedEntityCacheAdapter.js +1 -0
  5. package/build/ComposedEntityCacheAdapter.js.map +1 -1
  6. package/build/ComposedSecondaryEntityCache.js +1 -0
  7. package/build/ComposedSecondaryEntityCache.js.map +1 -1
  8. package/build/EnforcingEntityLoader.d.ts +5 -4
  9. package/build/EnforcingEntityLoader.js +4 -2
  10. package/build/EnforcingEntityLoader.js.map +1 -1
  11. package/build/Entity.d.ts +0 -27
  12. package/build/Entity.js +0 -50
  13. package/build/Entity.js.map +1 -1
  14. package/build/EntityAssociationLoader.d.ts +1 -1
  15. package/build/EntityAssociationLoader.js +17 -8
  16. package/build/EntityAssociationLoader.js.map +1 -1
  17. package/build/EntityCompanion.js +9 -1
  18. package/build/EntityCompanion.js.map +1 -1
  19. package/build/EntityCompanionProvider.d.ts +3 -1
  20. package/build/EntityCompanionProvider.js +10 -4
  21. package/build/EntityCompanionProvider.js.map +1 -1
  22. package/build/EntityConfiguration.d.ts +2 -1
  23. package/build/EntityConfiguration.js +19 -1
  24. package/build/EntityConfiguration.js.map +1 -1
  25. package/build/EntityDatabaseAdapter.d.ts +2 -2
  26. package/build/EntityDatabaseAdapter.js +5 -3
  27. package/build/EntityDatabaseAdapter.js.map +1 -1
  28. package/build/EntityFieldDefinition.d.ts +21 -10
  29. package/build/EntityFieldDefinition.js +8 -9
  30. package/build/EntityFieldDefinition.js.map +1 -1
  31. package/build/EntityFields.d.ts +10 -0
  32. package/build/EntityFields.js +15 -1
  33. package/build/EntityFields.js.map +1 -1
  34. package/build/EntityLoader.d.ts +12 -125
  35. package/build/EntityLoader.js +24 -239
  36. package/build/EntityLoader.js.map +1 -1
  37. package/build/EntityLoaderFactory.d.ts +1 -1
  38. package/build/EntityLoaderFactory.js +3 -0
  39. package/build/EntityLoaderFactory.js.map +1 -1
  40. package/build/EntityLoaderUtils.d.ts +58 -0
  41. package/build/EntityLoaderUtils.js +109 -0
  42. package/build/EntityLoaderUtils.js.map +1 -0
  43. package/build/EntityMutator.d.ts +1 -0
  44. package/build/EntityMutator.js +71 -56
  45. package/build/EntityMutator.js.map +1 -1
  46. package/build/EntityMutatorFactory.js +9 -0
  47. package/build/EntityMutatorFactory.js.map +1 -1
  48. package/build/EntityPrivacyPolicy.d.ts +11 -5
  49. package/build/EntityPrivacyPolicy.js +5 -7
  50. package/build/EntityPrivacyPolicy.js.map +1 -1
  51. package/build/EntityQueryContext.d.ts +2 -1
  52. package/build/EntityQueryContext.js +11 -6
  53. package/build/EntityQueryContext.js.map +1 -1
  54. package/build/EntitySecondaryCacheLoader.js +5 -1
  55. package/build/EntitySecondaryCacheLoader.js.map +1 -1
  56. package/build/GenericEntityCacheAdapter.js +1 -0
  57. package/build/GenericEntityCacheAdapter.js.map +1 -1
  58. package/build/GenericSecondaryEntityCache.js +2 -0
  59. package/build/GenericSecondaryEntityCache.js.map +1 -1
  60. package/build/IEntityCacheAdapterProvider.d.ts +1 -1
  61. package/build/IEntityDatabaseAdapterProvider.d.ts +1 -1
  62. package/build/ReadonlyEntity.js +5 -1
  63. package/build/ReadonlyEntity.js.map +1 -1
  64. package/build/ViewerContext.js +2 -0
  65. package/build/ViewerContext.js.map +1 -1
  66. package/build/ViewerScopedEntityCompanion.js +2 -0
  67. package/build/ViewerScopedEntityCompanion.js.map +1 -1
  68. package/build/ViewerScopedEntityCompanionProvider.d.ts +0 -1
  69. package/build/ViewerScopedEntityCompanionProvider.js +2 -1
  70. package/build/ViewerScopedEntityCompanionProvider.js.map +1 -1
  71. package/build/ViewerScopedEntityLoaderFactory.d.ts +1 -1
  72. package/build/ViewerScopedEntityLoaderFactory.js +2 -0
  73. package/build/ViewerScopedEntityLoaderFactory.js.map +1 -1
  74. package/build/ViewerScopedEntityMutatorFactory.js +2 -0
  75. package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
  76. package/build/__tests__/ComposedCacheAdapter-test.js +2 -0
  77. package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -1
  78. package/build/__tests__/ComposedSecondaryEntityCache-test.js +1 -0
  79. package/build/__tests__/ComposedSecondaryEntityCache-test.js.map +1 -1
  80. package/build/__tests__/EnforcingEntityLoader-test.js +101 -113
  81. package/build/__tests__/EnforcingEntityLoader-test.js.map +1 -1
  82. package/build/__tests__/Entity-test.js +0 -132
  83. package/build/__tests__/Entity-test.js.map +1 -1
  84. package/build/__tests__/EntityAssociationLoader-test.js +6 -2
  85. package/build/__tests__/EntityAssociationLoader-test.js.map +1 -1
  86. package/build/__tests__/EntityCommonUseCases-test.js +24 -22
  87. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  88. package/build/__tests__/EntityCompanion-test.js +26 -3
  89. package/build/__tests__/EntityCompanion-test.js.map +1 -1
  90. package/build/__tests__/EntityConfiguration-test.js +103 -0
  91. package/build/__tests__/EntityConfiguration-test.js.map +1 -0
  92. package/build/__tests__/EntityDatabaseAdapter-test.js +6 -0
  93. package/build/__tests__/EntityDatabaseAdapter-test.js.map +1 -1
  94. package/build/__tests__/EntityEdges-test.js +61 -20
  95. package/build/__tests__/EntityEdges-test.js.map +1 -1
  96. package/build/__tests__/EntityFields-test.js +6 -0
  97. package/build/__tests__/EntityFields-test.js.map +1 -1
  98. package/build/__tests__/EntityLoader-constructor-test.js +16 -17
  99. package/build/__tests__/EntityLoader-constructor-test.js.map +1 -1
  100. package/build/__tests__/EntityLoader-test.js +74 -22
  101. package/build/__tests__/EntityLoader-test.js.map +1 -1
  102. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js +12 -15
  103. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js.map +1 -1
  104. package/build/__tests__/EntityMutator-test.js +54 -9
  105. package/build/__tests__/EntityMutator-test.js.map +1 -1
  106. package/build/__tests__/EntityPrivacyPolicy-test.js +77 -59
  107. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  108. package/build/__tests__/EntityQueryContext-test.js +9 -0
  109. package/build/__tests__/EntityQueryContext-test.js.map +1 -1
  110. package/build/__tests__/EntitySelfReferentialEdges-test.js +42 -25
  111. package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
  112. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
  113. package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js +20 -18
  114. package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js.map +1 -1
  115. package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js +12 -15
  116. package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js.map +1 -1
  117. package/build/entityUtils.d.ts +1 -1
  118. package/build/entityUtils.js.map +1 -1
  119. package/build/errors/EntityCacheAdapterError.js +2 -5
  120. package/build/errors/EntityCacheAdapterError.js.map +1 -1
  121. package/build/errors/EntityDatabaseAdapterError.js +14 -35
  122. package/build/errors/EntityDatabaseAdapterError.js.map +1 -1
  123. package/build/errors/EntityError.js +1 -0
  124. package/build/errors/EntityError.js.map +1 -1
  125. package/build/errors/EntityInvalidFieldValueError.js +2 -2
  126. package/build/errors/EntityInvalidFieldValueError.js.map +1 -1
  127. package/build/errors/EntityNotAuthorizedError.js +3 -2
  128. package/build/errors/EntityNotAuthorizedError.js.map +1 -1
  129. package/build/errors/EntityNotFoundError.js +2 -2
  130. package/build/errors/EntityNotFoundError.js.map +1 -1
  131. package/build/index.d.ts +1 -0
  132. package/build/index.js +1 -0
  133. package/build/index.js.map +1 -1
  134. package/build/internal/EntityDataManager.d.ts +1 -1
  135. package/build/internal/EntityDataManager.js +6 -1
  136. package/build/internal/EntityDataManager.js.map +1 -1
  137. package/build/internal/EntityFieldTransformationUtils.d.ts +5 -5
  138. package/build/internal/EntityFieldTransformationUtils.js +5 -8
  139. package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
  140. package/build/internal/EntityTableDataCoordinator.d.ts +1 -1
  141. package/build/internal/EntityTableDataCoordinator.js +5 -0
  142. package/build/internal/EntityTableDataCoordinator.js.map +1 -1
  143. package/build/internal/ReadThroughEntityCache.d.ts +1 -1
  144. package/build/internal/ReadThroughEntityCache.js +2 -0
  145. package/build/internal/ReadThroughEntityCache.js.map +1 -1
  146. package/build/internal/__tests__/EntityFieldTransformationUtils-test.js +6 -2
  147. package/build/internal/__tests__/EntityFieldTransformationUtils-test.js.map +1 -1
  148. package/build/internal/__tests__/ReadThroughEntityCache-test.js +33 -0
  149. package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
  150. package/build/metrics/IEntityMetricsAdapter.d.ts +1 -1
  151. package/build/rules/AlwaysAllowPrivacyPolicyRule.d.ts +1 -1
  152. package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
  153. package/build/rules/AlwaysDenyPrivacyPolicyRule.d.ts +1 -1
  154. package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
  155. package/build/rules/AlwaysSkipPrivacyPolicyRule.d.ts +1 -1
  156. package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
  157. package/build/rules/PrivacyPolicyRule.d.ts +1 -1
  158. package/build/rules/PrivacyPolicyRule.js.map +1 -1
  159. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
  160. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
  161. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
  162. package/build/testfixtures/DateIDTestEntity.js +12 -15
  163. package/build/testfixtures/DateIDTestEntity.js.map +1 -1
  164. package/build/testfixtures/SimpleTestEntity.js +12 -15
  165. package/build/testfixtures/SimpleTestEntity.js.map +1 -1
  166. package/build/testfixtures/TestEntity.js +12 -15
  167. package/build/testfixtures/TestEntity.js.map +1 -1
  168. package/build/testfixtures/TestEntity2.js +12 -15
  169. package/build/testfixtures/TestEntity2.js.map +1 -1
  170. package/build/testfixtures/TestEntityNumberKey.js +12 -15
  171. package/build/testfixtures/TestEntityNumberKey.js.map +1 -1
  172. package/build/testfixtures/TestEntityWithMutationTriggers.d.ts +36 -0
  173. package/build/testfixtures/TestEntityWithMutationTriggers.js +82 -0
  174. package/build/testfixtures/TestEntityWithMutationTriggers.js.map +1 -0
  175. package/build/utils/EntityPrivacyUtils.d.ts +34 -0
  176. package/build/utils/EntityPrivacyUtils.js +160 -0
  177. package/build/utils/EntityPrivacyUtils.js.map +1 -0
  178. package/build/utils/__tests__/EntityPrivacyUtils-test.d.ts +1 -0
  179. package/build/utils/__tests__/EntityPrivacyUtils-test.js +395 -0
  180. package/build/utils/__tests__/EntityPrivacyUtils-test.js.map +1 -0
  181. package/build/utils/__tests__/mergeEntityMutationTriggerConfigurations-test.d.ts +1 -0
  182. package/build/utils/__tests__/mergeEntityMutationTriggerConfigurations-test.js +26 -0
  183. package/build/utils/__tests__/mergeEntityMutationTriggerConfigurations-test.js.map +1 -0
  184. package/build/utils/collections/maps.js.map +1 -1
  185. package/build/utils/mergeEntityMutationTriggerConfigurations.d.ts +4 -0
  186. package/build/utils/mergeEntityMutationTriggerConfigurations.js +28 -0
  187. package/build/utils/mergeEntityMutationTriggerConfigurations.js.map +1 -0
  188. package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +1 -1
  189. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
  190. package/build/utils/testing/StubCacheAdapter.d.ts +3 -3
  191. package/build/utils/testing/StubCacheAdapter.js +3 -3
  192. package/build/utils/testing/StubCacheAdapter.js.map +1 -1
  193. package/build/utils/testing/StubDatabaseAdapter.d.ts +2 -2
  194. package/build/utils/testing/StubDatabaseAdapter.js +4 -2
  195. package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
  196. package/build/utils/testing/StubDatabaseAdapterProvider.d.ts +1 -1
  197. package/build/utils/testing/StubDatabaseAdapterProvider.js +1 -3
  198. package/build/utils/testing/StubDatabaseAdapterProvider.js.map +1 -1
  199. package/build/utils/testing/__tests__/PrivacyPolicyRuleTestUtils-test.d.ts +1 -0
  200. package/build/utils/testing/__tests__/PrivacyPolicyRuleTestUtils-test.js +42 -0
  201. package/build/utils/testing/__tests__/PrivacyPolicyRuleTestUtils-test.js.map +1 -0
  202. package/build/utils/testing/__tests__/StubDatabaseAdapter-test.js +53 -0
  203. package/build/utils/testing/__tests__/StubDatabaseAdapter-test.js.map +1 -1
  204. package/build/utils/testing/describeFieldTestCase.js.map +1 -1
  205. package/package.json +4 -3
  206. package/src/AuthorizationResultBasedEntityLoader.ts +297 -0
  207. package/src/ComposedEntityCacheAdapter.ts +6 -6
  208. package/src/ComposedSecondaryEntityCache.ts +8 -8
  209. package/src/EnforcingEntityLoader.ts +20 -19
  210. package/src/Entity.ts +11 -126
  211. package/src/EntityAssociationLoader.ts +40 -41
  212. package/src/EntityCompanion.ts +8 -4
  213. package/src/EntityCompanionProvider.ts +24 -16
  214. package/src/EntityConfiguration.ts +18 -7
  215. package/src/EntityDatabaseAdapter.ts +41 -41
  216. package/src/EntityFieldDefinition.ts +28 -18
  217. package/src/EntityFields.ts +15 -0
  218. package/src/EntityLoader.ts +63 -357
  219. package/src/EntityLoaderFactory.ts +10 -4
  220. package/src/EntityLoaderUtils.ts +149 -0
  221. package/src/EntityMutationInfo.ts +2 -2
  222. package/src/EntityMutationTriggerConfiguration.ts +5 -5
  223. package/src/EntityMutationValidator.ts +2 -2
  224. package/src/EntityMutator.ts +146 -144
  225. package/src/EntityMutatorFactory.ts +8 -8
  226. package/src/EntityPrivacyPolicy.ts +78 -28
  227. package/src/EntityQueryContext.ts +14 -13
  228. package/src/EntityQueryContextProvider.ts +5 -5
  229. package/src/EntitySecondaryCacheLoader.ts +13 -11
  230. package/src/GenericEntityCacheAdapter.ts +10 -10
  231. package/src/GenericSecondaryEntityCache.ts +6 -6
  232. package/src/IEntityCacheAdapter.ts +4 -4
  233. package/src/IEntityCacheAdapterProvider.ts +2 -2
  234. package/src/IEntityDatabaseAdapterProvider.ts +2 -2
  235. package/src/ReadonlyEntity.ts +5 -5
  236. package/src/ViewerContext.ts +5 -5
  237. package/src/ViewerScopedEntityCompanion.ts +4 -4
  238. package/src/ViewerScopedEntityCompanionProvider.ts +4 -5
  239. package/src/ViewerScopedEntityLoaderFactory.ts +10 -4
  240. package/src/ViewerScopedEntityMutatorFactory.ts +5 -5
  241. package/src/__tests__/ComposedCacheAdapter-test.ts +12 -10
  242. package/src/__tests__/ComposedSecondaryEntityCache-test.ts +8 -8
  243. package/src/__tests__/EnforcingEntityLoader-test.ts +236 -159
  244. package/src/__tests__/Entity-test.ts +0 -202
  245. package/src/__tests__/EntityAssociationLoader-test.ts +29 -25
  246. package/src/__tests__/EntityCommonUseCases-test.ts +29 -13
  247. package/src/__tests__/EntityCompanion-test.ts +57 -5
  248. package/src/__tests__/EntityConfiguration-test.ts +118 -0
  249. package/src/__tests__/EntityDatabaseAdapter-test.ts +11 -11
  250. package/src/__tests__/EntityEdges-test.ts +108 -36
  251. package/src/__tests__/EntityFields-test.ts +14 -2
  252. package/src/__tests__/EntityLoader-constructor-test.ts +20 -7
  253. package/src/__tests__/EntityLoader-test.ts +214 -86
  254. package/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts +2 -2
  255. package/src/__tests__/EntityMutator-test.ts +281 -96
  256. package/src/__tests__/EntityPrivacyPolicy-test.ts +166 -53
  257. package/src/__tests__/EntityQueryContext-test.ts +30 -12
  258. package/src/__tests__/EntitySecondaryCacheLoader-test.ts +7 -7
  259. package/src/__tests__/EntitySelfReferentialEdges-test.ts +46 -26
  260. package/src/__tests__/GenericEntityCacheAdapter-test.ts +2 -2
  261. package/src/__tests__/ViewerContext-test.ts +1 -1
  262. package/src/__tests__/ViewerScopedEntityCompanion-test.ts +2 -2
  263. package/src/__tests__/ViewerScopedEntityCompanionProvider-test.ts +2 -2
  264. package/src/__tests__/ViewerScopedEntityLoaderFactory-test.ts +2 -1
  265. package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +19 -19
  266. package/src/__tests__/entityUtils-test.ts +2 -2
  267. package/src/entityUtils.ts +4 -4
  268. package/src/errors/EntityError.ts +4 -1
  269. package/src/errors/EntityInvalidFieldValueError.ts +2 -2
  270. package/src/errors/EntityNotAuthorizedError.ts +3 -3
  271. package/src/errors/EntityNotFoundError.ts +2 -2
  272. package/src/index.ts +1 -0
  273. package/src/internal/EntityDataManager.ts +24 -24
  274. package/src/internal/EntityFieldTransformationUtils.ts +39 -32
  275. package/src/internal/EntityTableDataCoordinator.ts +3 -3
  276. package/src/internal/ReadThroughEntityCache.ts +9 -9
  277. package/src/internal/__tests__/EntityDataManager-test.ts +51 -51
  278. package/src/internal/__tests__/EntityFieldTransformationUtils-test.ts +14 -10
  279. package/src/internal/__tests__/ReadThroughEntityCache-test.ts +74 -18
  280. package/src/metrics/EntityMetricsUtils.ts +4 -4
  281. package/src/metrics/IEntityMetricsAdapter.ts +1 -1
  282. package/src/rules/AlwaysAllowPrivacyPolicyRule.ts +9 -3
  283. package/src/rules/AlwaysDenyPrivacyPolicyRule.ts +9 -3
  284. package/src/rules/AlwaysSkipPrivacyPolicyRule.ts +9 -3
  285. package/src/rules/PrivacyPolicyRule.ts +9 -3
  286. package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -1
  287. package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -1
  288. package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -1
  289. package/src/testfixtures/TestEntity.ts +1 -1
  290. package/src/testfixtures/TestEntityWithMutationTriggers.ts +156 -0
  291. package/src/utils/EntityPrivacyUtils.ts +325 -0
  292. package/src/utils/__tests__/EntityPrivacyUtils-test.ts +570 -0
  293. package/src/utils/__tests__/mergeEntityMutationTriggerConfigurations-test.ts +29 -0
  294. package/src/utils/collections/__tests__/maps-test.ts +2 -2
  295. package/src/utils/collections/maps.ts +11 -11
  296. package/src/utils/mergeEntityMutationTriggerConfigurations.ts +44 -0
  297. package/src/utils/testing/PrivacyPolicyRuleTestUtils.ts +25 -22
  298. package/src/utils/testing/StubCacheAdapter.ts +17 -15
  299. package/src/utils/testing/StubDatabaseAdapter.ts +35 -30
  300. package/src/utils/testing/StubDatabaseAdapterProvider.ts +2 -2
  301. package/src/utils/testing/StubQueryContextProvider.ts +2 -2
  302. package/src/utils/testing/__tests__/PrivacyPolicyRuleTestUtils-test.ts +42 -0
  303. package/src/utils/testing/__tests__/StubDatabaseAdapter-test.ts +111 -29
  304. package/src/utils/testing/createUnitTestEntityCompanionProvider.ts +2 -2
  305. package/src/utils/testing/describeFieldTestCase.ts +1 -1
  306. package/build/__tests__/EntityDataConfiguration-test.js +0 -68
  307. package/build/__tests__/EntityDataConfiguration-test.js.map +0 -1
  308. package/src/__tests__/EntityDataConfiguration-test.ts +0 -77
  309. /package/build/__tests__/{EntityDataConfiguration-test.d.ts → EntityConfiguration-test.d.ts} +0 -0
@@ -1,26 +1,14 @@
1
- import { Result, asyncResult, result } from '@expo/results';
2
- import invariant from 'invariant';
3
- import nullthrows from 'nullthrows';
4
-
1
+ import AuthorizationResultBasedEntityLoader from './AuthorizationResultBasedEntityLoader';
5
2
  import EnforcingEntityLoader from './EnforcingEntityLoader';
6
3
  import { IEntityClass } from './Entity';
7
4
  import EntityConfiguration from './EntityConfiguration';
8
- import {
9
- FieldEqualityCondition,
10
- QuerySelectionModifiers,
11
- isSingleValueFieldEqualityCondition,
12
- QuerySelectionModifiersWithOrderByRaw,
13
- } from './EntityDatabaseAdapter';
5
+ import EntityLoaderUtils from './EntityLoaderUtils';
14
6
  import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
15
7
  import { EntityQueryContext } from './EntityQueryContext';
16
8
  import ReadonlyEntity from './ReadonlyEntity';
17
9
  import ViewerContext from './ViewerContext';
18
- import { pick } from './entityUtils';
19
- import EntityInvalidFieldValueError from './errors/EntityInvalidFieldValueError';
20
- import EntityNotFoundError from './errors/EntityNotFoundError';
21
10
  import EntityDataManager from './internal/EntityDataManager';
22
11
  import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
23
- import { mapMap, mapMapAsync } from './utils/collections/maps';
24
12
 
25
13
  /**
26
14
  * The primary interface for loading entities. All normal loads are batched,
@@ -38,12 +26,27 @@ export default class EntityLoader<
38
26
  TEntity,
39
27
  TSelectedFields
40
28
  >,
41
- TSelectedFields extends keyof TFields
29
+ TSelectedFields extends keyof TFields,
42
30
  > {
31
+ private readonly utilsPrivate: EntityLoaderUtils<
32
+ TFields,
33
+ TID,
34
+ TViewerContext,
35
+ TEntity,
36
+ TPrivacyPolicy,
37
+ TSelectedFields
38
+ >;
39
+
43
40
  constructor(
44
41
  private readonly viewerContext: TViewerContext,
45
42
  private readonly queryContext: EntityQueryContext,
46
- private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
43
+ private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
44
+ TFields,
45
+ TID,
46
+ TViewerContext,
47
+ TEntity,
48
+ TSelectedFields
49
+ >,
47
50
  private readonly entityConfiguration: EntityConfiguration<TFields>,
48
51
  private readonly entityClass: IEntityClass<
49
52
  TFields,
@@ -56,11 +59,23 @@ export default class EntityLoader<
56
59
  private readonly entitySelectedFields: TSelectedFields[] | undefined,
57
60
  private readonly privacyPolicy: TPrivacyPolicy,
58
61
  private readonly dataManager: EntityDataManager<TFields>,
59
- protected readonly metricsAdapter: IEntityMetricsAdapter
60
- ) {}
62
+ protected readonly metricsAdapter: IEntityMetricsAdapter,
63
+ ) {
64
+ this.utilsPrivate = new EntityLoaderUtils(
65
+ this.viewerContext,
66
+ this.queryContext,
67
+ this.privacyPolicyEvaluationContext,
68
+ this.entityConfiguration,
69
+ this.entityClass,
70
+ this.entitySelectedFields,
71
+ this.privacyPolicy,
72
+ this.dataManager,
73
+ this.metricsAdapter,
74
+ );
75
+ }
61
76
 
62
77
  /**
63
- * Enforcing view on this entity loader. All loads through this view are
78
+ * Enforcing entity loader. All loads through this loader are
64
79
  * guaranteed to be the values of successful results (or null for some loader methods),
65
80
  * and will throw otherwise.
66
81
  */
@@ -72,353 +87,44 @@ export default class EntityLoader<
72
87
  TPrivacyPolicy,
73
88
  TSelectedFields
74
89
  > {
75
- return new EnforcingEntityLoader(this);
76
- }
77
-
78
- /**
79
- * Load many entities where fieldName is one of fieldValues.
80
- * @param fieldName - entity field being queried
81
- * @param fieldValues - fieldName field values being queried
82
- * @returns map from fieldValue to entity results that match the query for that fieldValue,
83
- * where result errors can be UnauthorizedError
84
- */
85
- async loadManyByFieldEqualingManyAsync<N extends keyof Pick<TFields, TSelectedFields>>(
86
- fieldName: N,
87
- fieldValues: readonly NonNullable<TFields[N]>[]
88
- ): Promise<ReadonlyMap<NonNullable<TFields[N]>, readonly Result<TEntity>[]>> {
89
- this.validateFieldValues(fieldName, fieldValues);
90
-
91
- const fieldValuesToFieldObjects = await this.dataManager.loadManyByFieldEqualingAsync(
92
- this.queryContext,
93
- fieldName,
94
- fieldValues
95
- );
96
-
97
- return await this.constructAndAuthorizeEntitiesAsync(fieldValuesToFieldObjects);
98
- }
99
-
100
- /**
101
- * Load many entities where fieldName equals fieldValue.
102
- * @param fieldName - entity field being queried
103
- * @param fieldValue - fieldName field value being queried
104
- * @returns array of entity results that match the query for fieldValue, where result error can be UnauthorizedError
105
- */
106
- async loadManyByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
107
- fieldName: N,
108
- fieldValue: NonNullable<TFields[N]>
109
- ): Promise<readonly Result<TEntity>[]> {
110
- const entityResults = await this.loadManyByFieldEqualingManyAsync(fieldName, [fieldValue]);
111
- const entityResultsForFieldValue = entityResults.get(fieldValue);
112
- invariant(
113
- entityResultsForFieldValue !== undefined,
114
- `${fieldValue} should be guaranteed to be present in returned map of entities`
115
- );
116
- return entityResultsForFieldValue!;
117
- }
118
-
119
- /**
120
- * Load an entity where fieldName equals fieldValue, or null if no entity exists.
121
- * @param uniqueFieldName - entity field being queried
122
- * @param fieldValue - uniqueFieldName field value being queried
123
- * @returns entity result where uniqueFieldName equals fieldValue, or null if no entity matches the condition.
124
- * @throws when multiple entities match the condition
125
- */
126
- async loadByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
127
- uniqueFieldName: N,
128
- fieldValue: NonNullable<TFields[N]>
129
- ): Promise<Result<TEntity> | null> {
130
- const entityResults = await this.loadManyByFieldEqualingAsync(uniqueFieldName, fieldValue);
131
- invariant(
132
- entityResults.length <= 1,
133
- `loadByFieldEqualing: Multiple entities of type ${this.entityClass.name} found for ${String(
134
- uniqueFieldName
135
- )}=${fieldValue}`
136
- );
137
- return entityResults[0] ?? null;
138
- }
139
-
140
- /**
141
- * Loads an entity by a specified ID.
142
- * @param id - ID of the entity
143
- * @returns entity result for matching ID, where result error can be UnauthorizedError or EntityNotFoundError.
144
- */
145
- async loadByIDAsync(id: TID): Promise<Result<TEntity>> {
146
- const entityResults = await this.loadManyByIDsAsync([id]);
147
- const entityResult = entityResults.get(id);
148
- if (entityResult === undefined) {
149
- return result(
150
- new EntityNotFoundError(this.entityClass, this.entityConfiguration.idField, id)
151
- );
152
- }
153
- return entityResult;
154
- }
155
-
156
- /**
157
- * Load an entity by a specified ID, or return null if non-existent.
158
- * @param id - ID of the entity
159
- * @returns entity result for matching ID, or null if no entity exists for ID.
160
- */
161
- async loadByIDNullableAsync(id: TID): Promise<Result<TEntity> | null> {
162
- return await this.loadByFieldEqualingAsync(
163
- this.entityConfiguration.idField as TSelectedFields,
164
- id
165
- );
166
- }
167
-
168
- /**
169
- * Loads many entities for a list of IDs.
170
- * @param viewerContext - viewer context of loading user
171
- * @param ids - IDs of the entities to load
172
- * @returns map from ID to corresponding entity result, where result error can be UnauthorizedError or EntityNotFoundError.
173
- */
174
- async loadManyByIDsAsync(ids: readonly TID[]): Promise<ReadonlyMap<TID, Result<TEntity>>> {
175
- const entityResults = (await this.loadManyByFieldEqualingManyAsync(
176
- this.entityConfiguration.idField as TSelectedFields,
177
- ids
178
- )) as ReadonlyMap<TID, readonly Result<TEntity>[]>;
179
- return mapMap(entityResults, (entityResultsForId, id) => {
180
- const entityResult = entityResultsForId[0];
181
- return (
182
- entityResult ??
183
- result(new EntityNotFoundError(this.entityClass, this.entityConfiguration.idField, id))
184
- );
185
- });
186
- }
187
-
188
- /**
189
- * Loads many entities for a list of IDs, returning null for any IDs that are non-existent.
190
- * @param ids - IDs of the entities to load
191
- * @returns map from ID to nullable corresponding entity result, where result error can be UnauthorizedError or EntityNotFoundError.
192
- */
193
- async loadManyByIDsNullableAsync(
194
- ids: readonly TID[]
195
- ): Promise<ReadonlyMap<TID, Result<TEntity> | null>> {
196
- const entityResults = (await this.loadManyByFieldEqualingManyAsync(
197
- this.entityConfiguration.idField as TSelectedFields,
198
- ids
199
- )) as ReadonlyMap<TID, readonly Result<TEntity>[]>;
200
- return mapMap(entityResults, (entityResultsForId) => {
201
- return entityResultsForId[0] ?? null;
202
- });
203
- }
204
-
205
- /**
206
- * Loads the first entity matching the selection constructed from the conjunction of specified
207
- * operands, or null if no matching entity exists. Entities loaded using this method are not
208
- * batched or cached.
209
- *
210
- * This is a convenience method for {@link loadManyByFieldEqualityConjunctionAsync}. However, the
211
- * `orderBy` option must be specified to define what "first" means. If ordering doesn't matter,
212
- * explicitly pass in an empty array.
213
- *
214
- * @param fieldEqualityOperands - list of field equality selection operand specifications
215
- * @param querySelectionModifiers - orderBy and optional offset for the query
216
- * @returns the first entity results that matches the query, where result error can be
217
- * UnauthorizedError
218
- */
219
- async loadFirstByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
220
- fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
221
- querySelectionModifiers: Omit<QuerySelectionModifiers<TFields>, 'limit'> &
222
- Required<Pick<QuerySelectionModifiers<TFields>, 'orderBy'>>
223
- ): Promise<Result<TEntity> | null> {
224
- const results = await this.loadManyByFieldEqualityConjunctionAsync(fieldEqualityOperands, {
225
- ...querySelectionModifiers,
226
- limit: 1,
227
- });
228
- return results[0] ?? null;
90
+ return new EnforcingEntityLoader(this.withAuthorizationResults());
229
91
  }
230
92
 
231
93
  /**
232
- * Loads many entities matching the selection constructed from the conjunction of specified operands.
233
- * Entities loaded using this method are not batched or cached.
234
- *
235
- * @example
236
- * fieldEqualityOperands:
237
- * `[{fieldName: 'hello', fieldValue: 1}, {fieldName: 'world', fieldValues: [2, 3]}]`
238
- * Entities returned with a SQL EntityDatabaseAdapter:
239
- * `WHERE hello = 1 AND world = ANY({2, 3})`
240
- *
241
- * @param fieldEqualityOperands - list of field equality selection operand specifications
242
- * @param querySelectionModifiers - limit, offset, and orderBy for the query
243
- * @returns array of entity results that match the query, where result error can be UnauthorizedError
94
+ * Authorization-result-based entity loader. All loads through this
95
+ * loader are are results (or null for some loader methods), where an unsuccessful result
96
+ * means an authorization error or entity construction error occurred. Other errors are thrown.
244
97
  */
245
- async loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
246
- fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
247
- querySelectionModifiers: QuerySelectionModifiers<TFields> = {}
248
- ): Promise<readonly Result<TEntity>[]> {
249
- for (const fieldEqualityOperand of fieldEqualityOperands) {
250
- const fieldValues = isSingleValueFieldEqualityCondition(fieldEqualityOperand)
251
- ? [fieldEqualityOperand.fieldValue]
252
- : fieldEqualityOperand.fieldValues;
253
- this.validateFieldValues(fieldEqualityOperand.fieldName, fieldValues);
254
- }
255
-
256
- const fieldObjects = await this.dataManager.loadManyByFieldEqualityConjunctionAsync(
98
+ withAuthorizationResults(): AuthorizationResultBasedEntityLoader<
99
+ TFields,
100
+ TID,
101
+ TViewerContext,
102
+ TEntity,
103
+ TPrivacyPolicy,
104
+ TSelectedFields
105
+ > {
106
+ return new AuthorizationResultBasedEntityLoader(
257
107
  this.queryContext,
258
- fieldEqualityOperands,
259
- querySelectionModifiers
260
- );
261
- const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
262
- return await Promise.all(
263
- uncheckedEntityResults.map(async (uncheckedEntityResult) => {
264
- if (!uncheckedEntityResult.ok) {
265
- return uncheckedEntityResult;
266
- }
267
- return await asyncResult(
268
- this.privacyPolicy.authorizeReadAsync(
269
- this.viewerContext,
270
- this.queryContext,
271
- this.privacyPolicyEvaluationContext,
272
- uncheckedEntityResult.value,
273
- this.metricsAdapter
274
- )
275
- );
276
- })
108
+ this.entityConfiguration,
109
+ this.entityClass,
110
+ this.dataManager,
111
+ this.metricsAdapter,
112
+ this.utilsPrivate,
277
113
  );
278
114
  }
279
115
 
280
116
  /**
281
- * Loads many entities matching the raw WHERE clause. Corresponds to the knex `whereRaw` argument format.
282
- *
283
- * @remarks
284
- * Important notes:
285
- * - Fields in clause are database column names instead of transformed entity field names.
286
- * - Entities loaded using this method are not batched or cached.
287
- * - Not all database adapters implement the ability to execute this method of fetching entities.
288
- *
289
- * @example
290
- * rawWhereClause: `id = ?`
291
- * bindings: `[1]`
292
- * Entites returned `WHERE id = 1`
293
- *
294
- * http://knexjs.org/#Builder-whereRaw
295
- * http://knexjs.org/#Raw-Bindings
296
- *
297
- * @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
298
- * @param bindings - array of positional bindings or object of named bindings
299
- * @param querySelectionModifiers - limit, offset, orderBy, and orderByRaw for the query
300
- * @returns array of entity results that match the query, where result error can be UnauthorizedError
301
- * @throws Error when rawWhereClause or bindings are invalid
302
- *
303
- * @deprecated prefer caching loaders
117
+ * Entity loader utilities for things like cache invalidation, entity construction, and authorization.
118
+ * Calling into these should only be necessary in rare cases.
304
119
  */
305
- async loadManyByRawWhereClauseAsync(
306
- rawWhereClause: string,
307
- bindings: any[] | object,
308
- querySelectionModifiers: QuerySelectionModifiersWithOrderByRaw<TFields> = {}
309
- ): Promise<readonly Result<TEntity>[]> {
310
- const fieldObjects = await this.dataManager.loadManyByRawWhereClauseAsync(
311
- this.queryContext,
312
- rawWhereClause,
313
- bindings,
314
- querySelectionModifiers
315
- );
316
- const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
317
- return await Promise.all(
318
- uncheckedEntityResults.map(async (uncheckedEntityResult) => {
319
- if (!uncheckedEntityResult.ok) {
320
- return uncheckedEntityResult;
321
- }
322
- return await asyncResult(
323
- this.privacyPolicy.authorizeReadAsync(
324
- this.viewerContext,
325
- this.queryContext,
326
- this.privacyPolicyEvaluationContext,
327
- uncheckedEntityResult.value,
328
- this.metricsAdapter
329
- )
330
- );
331
- })
332
- );
333
- }
334
-
335
- /**
336
- * Invalidate all caches for an entity's fields. Exposed primarily for internal use by EntityMutator.
337
- * @param objectFields - entity data object to be invalidated
338
- */
339
- async invalidateFieldsAsync(objectFields: Readonly<TFields>): Promise<void> {
340
- await this.dataManager.invalidateObjectFieldsAsync(objectFields);
341
- }
342
-
343
- /**
344
- * Invalidate all caches for an entity. One potential use case would be to keep the entity
345
- * framework in sync with changes made to data outside of the framework.
346
- * @param entity - entity to be invalidated
347
- */
348
- async invalidateEntityAsync(entity: TEntity): Promise<void> {
349
- await this.invalidateFieldsAsync(entity.getAllDatabaseFields());
350
- }
351
-
352
- private tryConstructEntities(fieldsObjects: readonly TFields[]): readonly Result<TEntity>[] {
353
- return fieldsObjects.map((fieldsObject) => {
354
- try {
355
- return result(this.constructEntity(fieldsObject));
356
- } catch (e) {
357
- if (!(e instanceof Error)) {
358
- throw e;
359
- }
360
- return result(e);
361
- }
362
- });
363
- }
364
-
365
- public constructEntity(fieldsObject: TFields): TEntity {
366
- const idField = this.entityConfiguration.idField;
367
- const id = nullthrows(fieldsObject[idField], 'must provide ID to create an entity');
368
- const entitySelectedFields =
369
- this.entitySelectedFields ?? Array.from(this.entityConfiguration.schema.keys());
370
- const selectedFields = pick(fieldsObject, entitySelectedFields);
371
- return new this.entityClass({
372
- viewerContext: this.viewerContext,
373
- id: id as TID,
374
- databaseFields: fieldsObject,
375
- selectedFields,
376
- });
377
- }
378
-
379
- /**
380
- * Construct and authorize entities from fields map, returning error results for entities that fail
381
- * to construct or fail to authorize.
382
- *
383
- * @param map - map from an arbitrary key type to an array of entity field objects
384
- */
385
- public async constructAndAuthorizeEntitiesAsync<K>(
386
- map: ReadonlyMap<K, readonly Readonly<TFields>[]>
387
- ): Promise<ReadonlyMap<K, readonly Result<TEntity>[]>> {
388
- const uncheckedEntityResultsMap = mapMap(map, (fieldObjects) =>
389
- this.tryConstructEntities(fieldObjects)
390
- );
391
- return await mapMapAsync(uncheckedEntityResultsMap, async (uncheckedEntityResults) => {
392
- return await Promise.all(
393
- uncheckedEntityResults.map(async (uncheckedEntityResult) => {
394
- if (!uncheckedEntityResult.ok) {
395
- return uncheckedEntityResult;
396
- }
397
- return await asyncResult(
398
- this.privacyPolicy.authorizeReadAsync(
399
- this.viewerContext,
400
- this.queryContext,
401
- this.privacyPolicyEvaluationContext,
402
- uncheckedEntityResult.value,
403
- this.metricsAdapter
404
- )
405
- );
406
- })
407
- );
408
- });
409
- }
410
-
411
- private validateFieldValues<N extends keyof Pick<TFields, TSelectedFields>>(
412
- fieldName: N,
413
- fieldValues: readonly TFields[N][]
414
- ): void {
415
- const fieldDefinition = this.entityConfiguration.schema.get(fieldName);
416
- invariant(fieldDefinition, `must have field definition for field = ${String(fieldName)}`);
417
- for (const fieldValue of fieldValues) {
418
- const isInputValid = fieldDefinition.validateInputValue(fieldValue);
419
- if (!isInputValid) {
420
- throw new EntityInvalidFieldValueError(this.entityClass, fieldName, fieldValue);
421
- }
422
- }
120
+ public utils(): EntityLoaderUtils<
121
+ TFields,
122
+ TID,
123
+ TViewerContext,
124
+ TEntity,
125
+ TPrivacyPolicy,
126
+ TSelectedFields
127
+ > {
128
+ return this.utilsPrivate;
423
129
  }
424
130
  }
@@ -22,7 +22,7 @@ export default class EntityLoaderFactory<
22
22
  TEntity,
23
23
  TSelectedFields
24
24
  >,
25
- TSelectedFields extends keyof TFields
25
+ TSelectedFields extends keyof TFields,
26
26
  > {
27
27
  constructor(
28
28
  private readonly entityCompanion: EntityCompanion<
@@ -34,7 +34,7 @@ export default class EntityLoaderFactory<
34
34
  TSelectedFields
35
35
  >,
36
36
  private readonly dataManager: EntityDataManager<TFields>,
37
- protected readonly metricsAdapter: IEntityMetricsAdapter
37
+ protected readonly metricsAdapter: IEntityMetricsAdapter,
38
38
  ) {}
39
39
 
40
40
  /**
@@ -45,7 +45,13 @@ export default class EntityLoaderFactory<
45
45
  forLoad(
46
46
  viewerContext: TViewerContext,
47
47
  queryContext: EntityQueryContext,
48
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
48
+ privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
49
+ TFields,
50
+ TID,
51
+ TViewerContext,
52
+ TEntity,
53
+ TSelectedFields
54
+ >,
49
55
  ): EntityLoader<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
50
56
  return new EntityLoader(
51
57
  viewerContext,
@@ -56,7 +62,7 @@ export default class EntityLoaderFactory<
56
62
  this.entityCompanion.entityCompanionDefinition.entitySelectedFields,
57
63
  this.entityCompanion.privacyPolicy,
58
64
  this.dataManager,
59
- this.metricsAdapter
65
+ this.metricsAdapter,
60
66
  );
61
67
  }
62
68
  }
@@ -0,0 +1,149 @@
1
+ import { Result, asyncResult, result } from '@expo/results';
2
+ import nullthrows from 'nullthrows';
3
+
4
+ import { IEntityClass } from './Entity';
5
+ import EntityConfiguration from './EntityConfiguration';
6
+ import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
7
+ import { EntityQueryContext } from './EntityQueryContext';
8
+ import ReadonlyEntity from './ReadonlyEntity';
9
+ import ViewerContext from './ViewerContext';
10
+ import { pick } from './entityUtils';
11
+ import EntityDataManager from './internal/EntityDataManager';
12
+ import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
13
+ import { mapMapAsync } from './utils/collections/maps';
14
+
15
+ /**
16
+ * Entity loader utilities for things like invalidation, entity construction, and authorization.
17
+ * Methods are exposed publicly since in rare cases they may need to be called manually.
18
+ */
19
+ export default class EntityLoaderUtils<
20
+ TFields extends object,
21
+ TID extends NonNullable<TFields[TSelectedFields]>,
22
+ TViewerContext extends ViewerContext,
23
+ TEntity extends ReadonlyEntity<TFields, TID, TViewerContext, TSelectedFields>,
24
+ TPrivacyPolicy extends EntityPrivacyPolicy<
25
+ TFields,
26
+ TID,
27
+ TViewerContext,
28
+ TEntity,
29
+ TSelectedFields
30
+ >,
31
+ TSelectedFields extends keyof TFields,
32
+ > {
33
+ constructor(
34
+ private readonly viewerContext: TViewerContext,
35
+ private readonly queryContext: EntityQueryContext,
36
+ private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
37
+ TFields,
38
+ TID,
39
+ TViewerContext,
40
+ TEntity,
41
+ TSelectedFields
42
+ >,
43
+ private readonly entityConfiguration: EntityConfiguration<TFields>,
44
+ private readonly entityClass: IEntityClass<
45
+ TFields,
46
+ TID,
47
+ TViewerContext,
48
+ TEntity,
49
+ TPrivacyPolicy,
50
+ TSelectedFields
51
+ >,
52
+ private readonly entitySelectedFields: TSelectedFields[] | undefined,
53
+ private readonly privacyPolicy: TPrivacyPolicy,
54
+ private readonly dataManager: EntityDataManager<TFields>,
55
+ protected readonly metricsAdapter: IEntityMetricsAdapter,
56
+ ) {}
57
+
58
+ /**
59
+ * Invalidate all caches for an entity's fields. Exposed primarily for internal use by EntityMutator.
60
+ * @param objectFields - entity data object to be invalidated
61
+ */
62
+ async invalidateFieldsAsync(objectFields: Readonly<TFields>): Promise<void> {
63
+ await this.dataManager.invalidateObjectFieldsAsync(objectFields);
64
+ }
65
+
66
+ /**
67
+ * Invalidate all caches for an entity. One potential use case would be to keep the entity
68
+ * framework in sync with changes made to data outside of the framework.
69
+ * @param entity - entity to be invalidated
70
+ */
71
+ async invalidateEntityAsync(entity: TEntity): Promise<void> {
72
+ await this.invalidateFieldsAsync(entity.getAllDatabaseFields());
73
+ }
74
+
75
+ /**
76
+ * Construct an entity from a fields object (applying field selection if applicable),
77
+ * checking that the ID field is specified.
78
+ *
79
+ * @param fieldsObject - fields object
80
+ */
81
+ public constructEntity(fieldsObject: TFields): TEntity {
82
+ const idField = this.entityConfiguration.idField;
83
+ const id = nullthrows(fieldsObject[idField], 'must provide ID to create an entity');
84
+ const entitySelectedFields =
85
+ this.entitySelectedFields ?? Array.from(this.entityConfiguration.schema.keys());
86
+ const selectedFields = pick(fieldsObject, entitySelectedFields);
87
+ return new this.entityClass({
88
+ viewerContext: this.viewerContext,
89
+ id: id as TID,
90
+ databaseFields: fieldsObject,
91
+ selectedFields,
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Construct and authorize entities from fields map, returning error results for entities that fail
97
+ * to construct or fail to authorize.
98
+ *
99
+ * @param map - map from an arbitrary key type to an array of entity field objects
100
+ */
101
+ public async constructAndAuthorizeEntitiesAsync<K>(
102
+ map: ReadonlyMap<K, readonly Readonly<TFields>[]>,
103
+ ): Promise<ReadonlyMap<K, readonly Result<TEntity>[]>> {
104
+ return await mapMapAsync(map, async (fieldObjects) => {
105
+ return await this.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Construct and authorize entities from field objects array, returning error results for entities that fail
111
+ * to construct or fail to authorize.
112
+ *
113
+ * @param fieldObjects - array of field objects
114
+ */
115
+ public async constructAndAuthorizeEntitiesArrayAsync(
116
+ fieldObjects: readonly Readonly<TFields>[],
117
+ ): Promise<readonly Result<TEntity>[]> {
118
+ const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
119
+ return await Promise.all(
120
+ uncheckedEntityResults.map(async (uncheckedEntityResult) => {
121
+ if (!uncheckedEntityResult.ok) {
122
+ return uncheckedEntityResult;
123
+ }
124
+ return await asyncResult(
125
+ this.privacyPolicy.authorizeReadAsync(
126
+ this.viewerContext,
127
+ this.queryContext,
128
+ this.privacyPolicyEvaluationContext,
129
+ uncheckedEntityResult.value,
130
+ this.metricsAdapter,
131
+ ),
132
+ );
133
+ }),
134
+ );
135
+ }
136
+
137
+ private tryConstructEntities(fieldsObjects: readonly TFields[]): readonly Result<TEntity>[] {
138
+ return fieldsObjects.map((fieldsObject) => {
139
+ try {
140
+ return result(this.constructEntity(fieldsObject));
141
+ } catch (e) {
142
+ if (!(e instanceof Error)) {
143
+ throw e;
144
+ }
145
+ return result(e);
146
+ }
147
+ });
148
+ }
149
+ }
@@ -12,7 +12,7 @@ export type EntityValidatorMutationInfo<
12
12
  TID extends NonNullable<TFields[TSelectedFields]>,
13
13
  TViewerContext extends ViewerContext,
14
14
  TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
15
- TSelectedFields extends keyof TFields = keyof TFields
15
+ TSelectedFields extends keyof TFields = keyof TFields,
16
16
  > =
17
17
  | {
18
18
  type: EntityMutationType.CREATE;
@@ -43,7 +43,7 @@ export type EntityTriggerMutationInfo<
43
43
  TID extends NonNullable<TFields[TSelectedFields]>,
44
44
  TViewerContext extends ViewerContext,
45
45
  TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
46
- TSelectedFields extends keyof TFields = keyof TFields
46
+ TSelectedFields extends keyof TFields = keyof TFields,
47
47
  > =
48
48
  | {
49
49
  type: EntityMutationType.CREATE;