@expo/entity 0.38.0 → 0.40.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 (206) hide show
  1. package/build/AuthorizationResultBasedEntityAssociationLoader.d.ts +99 -0
  2. package/build/AuthorizationResultBasedEntityAssociationLoader.js +124 -0
  3. package/build/AuthorizationResultBasedEntityAssociationLoader.js.map +1 -0
  4. package/build/AuthorizationResultBasedEntityLoader.d.ts +1 -1
  5. package/build/AuthorizationResultBasedEntityLoader.js.map +1 -1
  6. package/build/{EntityMutator.d.ts → AuthorizationResultBasedEntityMutator.d.ts} +5 -17
  7. package/build/{EntityMutator.js → AuthorizationResultBasedEntityMutator.js} +22 -48
  8. package/build/AuthorizationResultBasedEntityMutator.js.map +1 -0
  9. package/build/EnforcingEntityAssociationLoader.d.ts +79 -0
  10. package/build/EnforcingEntityAssociationLoader.js +62 -0
  11. package/build/EnforcingEntityAssociationLoader.js.map +1 -0
  12. package/build/EnforcingEntityCreator.d.ts +24 -0
  13. package/build/EnforcingEntityCreator.js +32 -0
  14. package/build/EnforcingEntityCreator.js.map +1 -0
  15. package/build/EnforcingEntityDeleter.d.ts +17 -0
  16. package/build/EnforcingEntityDeleter.js +22 -0
  17. package/build/EnforcingEntityDeleter.js.map +1 -0
  18. package/build/EnforcingEntityUpdater.d.ts +24 -0
  19. package/build/EnforcingEntityUpdater.js +32 -0
  20. package/build/EnforcingEntityUpdater.js.map +1 -0
  21. package/build/Entity.d.ts +8 -12
  22. package/build/Entity.js +9 -34
  23. package/build/Entity.js.map +1 -1
  24. package/build/EntityAssociationLoader.d.ts +12 -91
  25. package/build/EntityAssociationLoader.js +20 -126
  26. package/build/EntityAssociationLoader.js.map +1 -1
  27. package/build/EntityCompanionProvider.d.ts +2 -2
  28. package/build/EntityCompanionProvider.js.map +1 -1
  29. package/build/EntityCreator.d.ts +27 -0
  30. package/build/EntityCreator.js +39 -0
  31. package/build/EntityCreator.js.map +1 -0
  32. package/build/EntityDatabaseAdapter.js +2 -2
  33. package/build/EntityDatabaseAdapter.js.map +1 -1
  34. package/build/EntityDeleter.d.ts +27 -0
  35. package/build/EntityDeleter.js +40 -0
  36. package/build/EntityDeleter.js.map +1 -0
  37. package/build/EntityLoader.d.ts +4 -14
  38. package/build/EntityLoader.js +7 -20
  39. package/build/EntityLoader.js.map +1 -1
  40. package/build/EntityLoaderFactory.d.ts +2 -2
  41. package/build/EntityLoaderFactory.js +4 -2
  42. package/build/EntityLoaderFactory.js.map +1 -1
  43. package/build/EntityMutatorFactory.d.ts +4 -4
  44. package/build/EntityMutatorFactory.js +4 -4
  45. package/build/EntityMutatorFactory.js.map +1 -1
  46. package/build/EntitySecondaryCacheLoader.d.ts +3 -3
  47. package/build/EntitySecondaryCacheLoader.js +1 -3
  48. package/build/EntitySecondaryCacheLoader.js.map +1 -1
  49. package/build/EntityUpdater.d.ts +27 -0
  50. package/build/EntityUpdater.js +40 -0
  51. package/build/EntityUpdater.js.map +1 -0
  52. package/build/ReadonlyEntity.d.ts +2 -2
  53. package/build/ReadonlyEntity.js +4 -6
  54. package/build/ReadonlyEntity.js.map +1 -1
  55. package/build/ViewerScopedEntityLoaderFactory.d.ts +2 -2
  56. package/build/ViewerScopedEntityLoaderFactory.js.map +1 -1
  57. package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
  58. package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
  59. package/build/__tests__/AuthorizationResultBasedEntityAssociationLoader-test.d.ts +1 -0
  60. package/build/__tests__/AuthorizationResultBasedEntityAssociationLoader-test.js +273 -0
  61. package/build/__tests__/AuthorizationResultBasedEntityAssociationLoader-test.js.map +1 -0
  62. package/build/__tests__/{EntityLoader-constructor-test.js → AuthorizationResultBasedEntityLoader-constructor-test.js} +11 -11
  63. package/build/__tests__/AuthorizationResultBasedEntityLoader-constructor-test.js.map +1 -0
  64. package/build/__tests__/AuthorizationResultBasedEntityLoader-test.d.ts +1 -0
  65. package/build/__tests__/AuthorizationResultBasedEntityLoader-test.js +401 -0
  66. package/build/__tests__/AuthorizationResultBasedEntityLoader-test.js.map +1 -0
  67. package/build/__tests__/EnforcingEntityAssociationLoader-test.d.ts +1 -0
  68. package/build/__tests__/EnforcingEntityAssociationLoader-test.js +115 -0
  69. package/build/__tests__/EnforcingEntityAssociationLoader-test.js.map +1 -0
  70. package/build/__tests__/Entity-test.js +23 -5
  71. package/build/__tests__/Entity-test.js.map +1 -1
  72. package/build/__tests__/EntityAssociationLoader-test.js +14 -184
  73. package/build/__tests__/EntityAssociationLoader-test.js.map +1 -1
  74. package/build/__tests__/EntityCommonUseCases-test.js +34 -12
  75. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  76. package/build/__tests__/EntityCompanion-test.js +17 -7
  77. package/build/__tests__/EntityCompanion-test.js.map +1 -1
  78. package/build/__tests__/EntityDatabaseAdapter-test.js.map +1 -1
  79. package/build/__tests__/EntityEdges-test.js +41 -23
  80. package/build/__tests__/EntityEdges-test.js.map +1 -1
  81. package/build/__tests__/EntityLoader-test.js +22 -386
  82. package/build/__tests__/EntityLoader-test.js.map +1 -1
  83. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js +4 -3
  84. package/build/__tests__/EntityMutator-MutationCacheConsistency-test.js.map +1 -1
  85. package/build/__tests__/EntityMutator-test.js +67 -70
  86. package/build/__tests__/EntityMutator-test.js.map +1 -1
  87. package/build/__tests__/EntityPrivacyPolicy-test.js +17 -7
  88. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  89. package/build/__tests__/EntitySecondaryCacheLoader-test.js +7 -7
  90. package/build/__tests__/EntitySecondaryCacheLoader-test.js.map +1 -1
  91. package/build/__tests__/EntitySelfReferentialEdges-test.js +36 -24
  92. package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
  93. package/build/__tests__/ReadonlyEntity-test.js +1 -1
  94. package/build/__tests__/ReadonlyEntity-test.js.map +1 -1
  95. package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js +4 -2
  96. package/build/__tests__/cases/TwoEntitySameTableDisjointRows-test.js.map +1 -1
  97. package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js +7 -4
  98. package/build/__tests__/cases/TwoEntitySameTableOverlappingRows-test.js.map +1 -1
  99. package/build/__tests__/entityUtils-test.js +8 -0
  100. package/build/__tests__/entityUtils-test.js.map +1 -1
  101. package/build/entityUtils.d.ts +7 -0
  102. package/build/entityUtils.js +20 -10
  103. package/build/entityUtils.js.map +1 -1
  104. package/build/errors/EntityCacheAdapterError.js +17 -7
  105. package/build/errors/EntityCacheAdapterError.js.map +1 -1
  106. package/build/errors/EntityDatabaseAdapterError.js +17 -7
  107. package/build/errors/EntityDatabaseAdapterError.js.map +1 -1
  108. package/build/errors/EntityInvalidFieldValueError.js +17 -7
  109. package/build/errors/EntityInvalidFieldValueError.js.map +1 -1
  110. package/build/errors/EntityNotAuthorizedError.js +17 -7
  111. package/build/errors/EntityNotAuthorizedError.js.map +1 -1
  112. package/build/errors/EntityNotFoundError.js +17 -7
  113. package/build/errors/EntityNotFoundError.js.map +1 -1
  114. package/build/index.d.ts +19 -11
  115. package/build/index.js +24 -7
  116. package/build/index.js.map +1 -1
  117. package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
  118. package/build/internal/__tests__/EntityDataManager-test.js +42 -32
  119. package/build/internal/__tests__/EntityDataManager-test.js.map +1 -1
  120. package/build/internal/__tests__/ReadThroughEntityCache-test.js +17 -7
  121. package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
  122. package/build/rules/AlwaysAllowPrivacyPolicyRule.js +17 -7
  123. package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
  124. package/build/rules/AlwaysDenyPrivacyPolicyRule.js +17 -7
  125. package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
  126. package/build/rules/AlwaysSkipPrivacyPolicyRule.js +17 -7
  127. package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
  128. package/build/utils/EntityPrivacyUtils.d.ts +32 -4
  129. package/build/utils/EntityPrivacyUtils.js +68 -24
  130. package/build/utils/EntityPrivacyUtils.js.map +1 -1
  131. package/build/utils/__tests__/EntityPrivacyUtils-test.js +148 -23
  132. package/build/utils/__tests__/EntityPrivacyUtils-test.js.map +1 -1
  133. package/build/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js +8 -5
  134. package/build/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js.map +1 -1
  135. package/build/utils/collections/__tests__/maps-test.js +1 -1
  136. package/build/utils/collections/__tests__/maps-test.js.map +1 -1
  137. package/build/utils/collections/maps.js +2 -2
  138. package/build/utils/collections/maps.js.map +1 -1
  139. package/build/utils/mergeEntityMutationTriggerConfigurations.js +1 -2
  140. package/build/utils/mergeEntityMutationTriggerConfigurations.js.map +1 -1
  141. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js +1 -1
  142. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
  143. package/build/utils/testing/StubDatabaseAdapter.js +17 -7
  144. package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
  145. package/build/utils/testing/StubQueryContextProvider.d.ts +1 -3
  146. package/build/utils/testing/StubQueryContextProvider.js +1 -3
  147. package/build/utils/testing/StubQueryContextProvider.js.map +1 -1
  148. package/build/utils/testing/createUnitTestEntityCompanionProvider.js +2 -1
  149. package/build/utils/testing/createUnitTestEntityCompanionProvider.js.map +1 -1
  150. package/build/utils/testing/describeFieldTestCase.js +1 -1
  151. package/build/utils/testing/describeFieldTestCase.js.map +1 -1
  152. package/package.json +19 -3
  153. package/src/AuthorizationResultBasedEntityAssociationLoader.ts +492 -0
  154. package/src/AuthorizationResultBasedEntityLoader.ts +2 -2
  155. package/src/{EntityMutator.ts → AuthorizationResultBasedEntityMutator.ts} +62 -58
  156. package/src/EnforcingEntityAssociationLoader.ts +390 -0
  157. package/src/EnforcingEntityCreator.ts +55 -0
  158. package/src/EnforcingEntityDeleter.ts +44 -0
  159. package/src/EnforcingEntityUpdater.ts +55 -0
  160. package/src/Entity.ts +20 -65
  161. package/src/EntityAssociationLoader.ts +38 -495
  162. package/src/EntityCompanionProvider.ts +5 -2
  163. package/src/EntityCreator.ts +73 -0
  164. package/src/EntityDeleter.ts +73 -0
  165. package/src/EntityLoader.ts +10 -49
  166. package/src/EntityLoaderFactory.ts +20 -3
  167. package/src/EntityMutatorFactory.ts +32 -7
  168. package/src/EntitySecondaryCacheLoader.ts +5 -7
  169. package/src/EntityUpdater.ts +73 -0
  170. package/src/ReadonlyEntity.ts +14 -13
  171. package/src/ViewerScopedEntityLoaderFactory.ts +9 -2
  172. package/src/ViewerScopedEntityMutatorFactory.ts +29 -4
  173. package/src/__tests__/AuthorizationResultBasedEntityAssociationLoader-test.ts +354 -0
  174. package/src/__tests__/{EntityLoader-constructor-test.ts → AuthorizationResultBasedEntityLoader-constructor-test.ts} +17 -10
  175. package/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts +730 -0
  176. package/src/__tests__/EnforcingEntityAssociationLoader-test.ts +253 -0
  177. package/src/__tests__/Entity-test.ts +24 -5
  178. package/src/__tests__/EntityAssociationLoader-test.ts +16 -259
  179. package/src/__tests__/EntityCommonUseCases-test.ts +20 -8
  180. package/src/__tests__/EntityCompanion-test.ts +1 -1
  181. package/src/__tests__/EntityDatabaseAdapter-test.ts +6 -6
  182. package/src/__tests__/EntityEdges-test.ts +24 -16
  183. package/src/__tests__/EntityLoader-test.ts +25 -675
  184. package/src/__tests__/EntityMutator-MutationCacheConsistency-test.ts +4 -3
  185. package/src/__tests__/EntityMutator-test.ts +116 -103
  186. package/src/__tests__/EntitySecondaryCacheLoader-test.ts +7 -7
  187. package/src/__tests__/EntitySelfReferentialEdges-test.ts +36 -24
  188. package/src/__tests__/ReadonlyEntity-test.ts +1 -1
  189. package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +4 -2
  190. package/src/__tests__/cases/TwoEntitySameTableOverlappingRows-test.ts +7 -4
  191. package/src/__tests__/entityUtils-test.ts +12 -0
  192. package/src/entityUtils.ts +24 -9
  193. package/src/index.ts +19 -11
  194. package/src/internal/EntityFieldTransformationUtils.ts +2 -2
  195. package/src/internal/__tests__/EntityDataManager-test.ts +29 -29
  196. package/src/utils/EntityPrivacyUtils.ts +188 -107
  197. package/src/utils/__tests__/EntityPrivacyUtils-test.ts +169 -29
  198. package/src/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.ts +8 -5
  199. package/src/utils/collections/__tests__/maps-test.ts +1 -1
  200. package/src/utils/testing/PrivacyPolicyRuleTestUtils.ts +1 -1
  201. package/src/utils/testing/StubDatabaseAdapter.ts +1 -1
  202. package/src/utils/testing/StubQueryContextProvider.ts +1 -3
  203. package/src/utils/testing/createUnitTestEntityCompanionProvider.ts +3 -1
  204. package/build/EntityMutator.js.map +0 -1
  205. package/build/__tests__/EntityLoader-constructor-test.js.map +0 -1
  206. /package/build/__tests__/{EntityLoader-constructor-test.d.ts → AuthorizationResultBasedEntityLoader-constructor-test.d.ts} +0 -0
@@ -9,9 +9,22 @@ import { EntityCascadingDeletionInfo } from '../EntityMutationInfo';
9
9
  import EntityPrivacyPolicy from '../EntityPrivacyPolicy';
10
10
  import { EntityQueryContext } from '../EntityQueryContext';
11
11
  import ViewerContext from '../ViewerContext';
12
- import { failedResults } from '../entityUtils';
12
+ import { failedResults, partitionArray } from '../entityUtils';
13
13
  import EntityNotAuthorizedError from '../errors/EntityNotAuthorizedError';
14
14
 
15
+ export type EntityPrivacyEvaluationResultSuccess = {
16
+ allowed: true;
17
+ };
18
+
19
+ export type EntityPrivacyEvaluationResultFailure = {
20
+ allowed: false;
21
+ authorizationErrors: EntityNotAuthorizedError<any, any, any, any, any>[];
22
+ };
23
+
24
+ export type EntityPrivacyEvaluationResult =
25
+ | EntityPrivacyEvaluationResultSuccess
26
+ | EntityPrivacyEvaluationResultFailure;
27
+
15
28
  /**
16
29
  * Check whether an entity loaded by a viewer can be updated by that same viewer.
17
30
  *
@@ -30,34 +43,67 @@ import EntityNotAuthorizedError from '../errors/EntityNotAuthorizedError';
30
43
  * @param queryContext - query context in which to perform the check
31
44
  */
32
45
  export async function canViewerUpdateAsync<
33
- TMFields extends object,
34
- TMID extends NonNullable<TMFields[TMSelectedFields]>,
35
- TMViewerContext extends ViewerContext,
36
- TMEntity extends Entity<TMFields, TMID, TMViewerContext, TMSelectedFields>,
37
- TMPrivacyPolicy extends EntityPrivacyPolicy<
38
- TMFields,
39
- TMID,
40
- TMViewerContext,
41
- TMEntity,
42
- TMSelectedFields
46
+ TFields extends object,
47
+ TID extends NonNullable<TFields[TSelectedFields]>,
48
+ TViewerContext extends ViewerContext,
49
+ TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
50
+ TPrivacyPolicy extends EntityPrivacyPolicy<
51
+ TFields,
52
+ TID,
53
+ TViewerContext,
54
+ TEntity,
55
+ TSelectedFields
43
56
  >,
44
- TMSelectedFields extends keyof TMFields = keyof TMFields,
57
+ TSelectedFields extends keyof TFields = keyof TFields,
45
58
  >(
46
- entityClass: IEntityClass<
47
- TMFields,
48
- TMID,
49
- TMViewerContext,
50
- TMEntity,
51
- TMPrivacyPolicy,
52
- TMSelectedFields
53
- >,
54
- sourceEntity: TMEntity,
59
+ entityClass: IEntityClass<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields>,
60
+ sourceEntity: TEntity,
55
61
  queryContext: EntityQueryContext = sourceEntity
56
62
  .getViewerContext()
57
63
  .getViewerScopedEntityCompanionForClass(entityClass)
58
64
  .getQueryContextProvider()
59
65
  .getQueryContext(),
60
66
  ): Promise<boolean> {
67
+ const result = await canViewerUpdateInternalAsync(
68
+ entityClass,
69
+ sourceEntity,
70
+ /* cascadingDeleteCause */ null,
71
+ queryContext,
72
+ );
73
+ return result.allowed;
74
+ }
75
+
76
+ /**
77
+ * Check whether an entity loaded by a viewer can be updated by that same viewer and return the evaluation result.
78
+ *
79
+ * @see canViewerUpdateAsync
80
+ *
81
+ * @param entityClass - class of entity
82
+ * @param sourceEntity - entity loaded by viewer
83
+ * @param queryContext - query context in which to perform the check
84
+ */
85
+ export async function getCanViewerUpdateResultAsync<
86
+ TFields extends object,
87
+ TID extends NonNullable<TFields[TSelectedFields]>,
88
+ TViewerContext extends ViewerContext,
89
+ TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
90
+ TPrivacyPolicy extends EntityPrivacyPolicy<
91
+ TFields,
92
+ TID,
93
+ TViewerContext,
94
+ TEntity,
95
+ TSelectedFields
96
+ >,
97
+ TSelectedFields extends keyof TFields = keyof TFields,
98
+ >(
99
+ entityClass: IEntityClass<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields>,
100
+ sourceEntity: TEntity,
101
+ queryContext: EntityQueryContext = sourceEntity
102
+ .getViewerContext()
103
+ .getViewerScopedEntityCompanionForClass(entityClass)
104
+ .getQueryContextProvider()
105
+ .getQueryContext(),
106
+ ): Promise<EntityPrivacyEvaluationResult> {
61
107
  return await canViewerUpdateInternalAsync(
62
108
  entityClass,
63
109
  sourceEntity,
@@ -67,31 +113,24 @@ export async function canViewerUpdateAsync<
67
113
  }
68
114
 
69
115
  async function canViewerUpdateInternalAsync<
70
- TMFields extends object,
71
- TMID extends NonNullable<TMFields[TMSelectedFields]>,
72
- TMViewerContext extends ViewerContext,
73
- TMEntity extends Entity<TMFields, TMID, TMViewerContext, TMSelectedFields>,
74
- TMPrivacyPolicy extends EntityPrivacyPolicy<
75
- TMFields,
76
- TMID,
77
- TMViewerContext,
78
- TMEntity,
79
- TMSelectedFields
116
+ TFields extends object,
117
+ TID extends NonNullable<TFields[TSelectedFields]>,
118
+ TViewerContext extends ViewerContext,
119
+ TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
120
+ TPrivacyPolicy extends EntityPrivacyPolicy<
121
+ TFields,
122
+ TID,
123
+ TViewerContext,
124
+ TEntity,
125
+ TSelectedFields
80
126
  >,
81
- TMSelectedFields extends keyof TMFields = keyof TMFields,
127
+ TSelectedFields extends keyof TFields = keyof TFields,
82
128
  >(
83
- entityClass: IEntityClass<
84
- TMFields,
85
- TMID,
86
- TMViewerContext,
87
- TMEntity,
88
- TMPrivacyPolicy,
89
- TMSelectedFields
90
- >,
91
- sourceEntity: TMEntity,
129
+ entityClass: IEntityClass<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields>,
130
+ sourceEntity: TEntity,
92
131
  cascadingDeleteCause: EntityCascadingDeletionInfo | null,
93
132
  queryContext: EntityQueryContext,
94
- ): Promise<boolean> {
133
+ ): Promise<EntityPrivacyEvaluationResult> {
95
134
  const companion = sourceEntity
96
135
  .getViewerContext()
97
136
  .getViewerScopedEntityCompanionForClass(entityClass);
@@ -107,20 +146,19 @@ async function canViewerUpdateInternalAsync<
107
146
  );
108
147
  if (!evaluationResult.ok) {
109
148
  if (evaluationResult.reason instanceof EntityNotAuthorizedError) {
110
- return false;
149
+ return { allowed: false, authorizationErrors: [evaluationResult.reason] };
111
150
  } else {
112
151
  throw evaluationResult.reason;
113
152
  }
114
153
  }
115
- return evaluationResult.ok;
154
+ return { allowed: true };
116
155
  }
117
156
 
118
157
  /**
119
158
  * Check whether a single entity loaded by a viewer can be deleted by that same viewer.
120
159
  * This recursively checks edge cascade permissions (EntityEdgeDeletionBehavior) as well.
121
160
  *
122
- * @remarks
123
- * See remarks for canViewerUpdate.
161
+ * @see canViewerUpdateAsync
124
162
  *
125
163
  * @param entityClass - class of entity
126
164
  * @param sourceEntity - entity loaded by viewer
@@ -129,32 +167,65 @@ async function canViewerUpdateInternalAsync<
129
167
  export async function canViewerDeleteAsync<
130
168
  TFields extends object,
131
169
  TID extends NonNullable<TFields[TSelectedFields]>,
132
- TMViewerContext extends ViewerContext,
133
- TEntity extends Entity<TFields, TID, TMViewerContext, TSelectedFields>,
170
+ TViewerContext extends ViewerContext,
171
+ TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
134
172
  TPrivacyPolicy extends EntityPrivacyPolicy<
135
173
  TFields,
136
174
  TID,
137
- TMViewerContext,
175
+ TViewerContext,
138
176
  TEntity,
139
177
  TSelectedFields
140
178
  >,
141
179
  TSelectedFields extends keyof TFields = keyof TFields,
142
180
  >(
143
- entityClass: IEntityClass<
181
+ entityClass: IEntityClass<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields>,
182
+ sourceEntity: TEntity,
183
+ queryContext: EntityQueryContext = sourceEntity
184
+ .getViewerContext()
185
+ .getViewerScopedEntityCompanionForClass(entityClass)
186
+ .getQueryContextProvider()
187
+ .getQueryContext(),
188
+ ): Promise<boolean> {
189
+ const result = await canViewerDeleteInternalAsync(
190
+ entityClass,
191
+ sourceEntity,
192
+ /* cascadingDeleteCause */ null,
193
+ queryContext,
194
+ );
195
+ return result.allowed;
196
+ }
197
+
198
+ /**
199
+ * Check whether a single entity loaded by a viewer can be deleted by that same viewer and return the evaluation result.
200
+ *
201
+ * @see canViewerDeleteAsync
202
+ *
203
+ * @param entityClass - class of entity
204
+ * @param sourceEntity - entity loaded by viewer
205
+ * @param queryContext - query context in which to perform the check
206
+ */
207
+ export async function getCanViewerDeleteResultAsync<
208
+ TFields extends object,
209
+ TID extends NonNullable<TFields[TSelectedFields]>,
210
+ TViewerContext extends ViewerContext,
211
+ TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
212
+ TPrivacyPolicy extends EntityPrivacyPolicy<
144
213
  TFields,
145
214
  TID,
146
- TMViewerContext,
215
+ TViewerContext,
147
216
  TEntity,
148
- TPrivacyPolicy,
149
217
  TSelectedFields
150
218
  >,
219
+ TSelectedFields extends keyof TFields = keyof TFields,
220
+ >(
221
+ entityClass: IEntityClass<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields>,
151
222
  sourceEntity: TEntity,
152
223
  queryContext: EntityQueryContext = sourceEntity
153
224
  .getViewerContext()
154
225
  .getViewerScopedEntityCompanionForClass(entityClass)
155
226
  .getQueryContextProvider()
156
227
  .getQueryContext(),
157
- ): Promise<boolean> {
228
+ ): Promise<EntityPrivacyEvaluationResult> {
158
229
  return await canViewerDeleteInternalAsync(
159
230
  entityClass,
160
231
  sourceEntity,
@@ -166,29 +237,22 @@ export async function canViewerDeleteAsync<
166
237
  async function canViewerDeleteInternalAsync<
167
238
  TFields extends object,
168
239
  TID extends NonNullable<TFields[TSelectedFields]>,
169
- TMViewerContext extends ViewerContext,
170
- TEntity extends Entity<TFields, TID, TMViewerContext, TSelectedFields>,
240
+ TViewerContext extends ViewerContext,
241
+ TEntity extends Entity<TFields, TID, TViewerContext, TSelectedFields>,
171
242
  TPrivacyPolicy extends EntityPrivacyPolicy<
172
243
  TFields,
173
244
  TID,
174
- TMViewerContext,
245
+ TViewerContext,
175
246
  TEntity,
176
247
  TSelectedFields
177
248
  >,
178
249
  TSelectedFields extends keyof TFields = keyof TFields,
179
250
  >(
180
- entityClass: IEntityClass<
181
- TFields,
182
- TID,
183
- TMViewerContext,
184
- TEntity,
185
- TPrivacyPolicy,
186
- TSelectedFields
187
- >,
251
+ entityClass: IEntityClass<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields>,
188
252
  sourceEntity: TEntity,
189
253
  cascadingDeleteCause: EntityCascadingDeletionInfo | null,
190
254
  queryContext: EntityQueryContext,
191
- ): Promise<boolean> {
255
+ ): Promise<EntityPrivacyEvaluationResult> {
192
256
  const viewerContext = sourceEntity.getViewerContext();
193
257
  const entityCompanionProvider = viewerContext.entityCompanionProvider;
194
258
  const viewerScopedCompanion = sourceEntity
@@ -207,7 +271,7 @@ async function canViewerDeleteInternalAsync<
207
271
  );
208
272
  if (!evaluationResult.ok) {
209
273
  if (evaluationResult.reason instanceof EntityNotAuthorizedError) {
210
- return false;
274
+ return { allowed: false, authorizationErrors: [evaluationResult.reason] };
211
275
  } else {
212
276
  throw evaluationResult.reason;
213
277
  }
@@ -266,9 +330,8 @@ async function canViewerDeleteInternalAsync<
266
330
  edgeDeletionPermissionInferenceBehavior ===
267
331
  EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL
268
332
  ) {
269
- const singleEntityToTestForInboundEdge = await loader
270
- .withAuthorizationResults()
271
- .loadFirstByFieldEqualityConjunctionAsync(
333
+ const singleEntityResultToTestForInboundEdge =
334
+ await loader.loadFirstByFieldEqualityConjunctionAsync(
272
335
  [
273
336
  {
274
337
  fieldName,
@@ -279,25 +342,23 @@ async function canViewerDeleteInternalAsync<
279
342
  ],
280
343
  { orderBy: [] },
281
344
  );
282
- entityResultsToCheckForInboundEdge = singleEntityToTestForInboundEdge
283
- ? [singleEntityToTestForInboundEdge]
345
+ entityResultsToCheckForInboundEdge = singleEntityResultToTestForInboundEdge
346
+ ? [singleEntityResultToTestForInboundEdge]
284
347
  : [];
285
348
  } else {
286
- const entityResultsForInboundEdge = await loader
287
- .withAuthorizationResults()
288
- .loadManyByFieldEqualingAsync(
289
- fieldName,
290
- association.associatedEntityLookupByField
291
- ? sourceEntity.getField(association.associatedEntityLookupByField as any)
292
- : sourceEntity.getID(),
293
- );
349
+ const entityResultsForInboundEdge = await loader.loadManyByFieldEqualingAsync(
350
+ fieldName,
351
+ association.associatedEntityLookupByField
352
+ ? sourceEntity.getField(association.associatedEntityLookupByField as any)
353
+ : sourceEntity.getID(),
354
+ );
294
355
  entityResultsToCheckForInboundEdge = entityResultsForInboundEdge;
295
356
  }
296
357
 
297
358
  const failedEntityLoadResults = failedResults(entityResultsToCheckForInboundEdge);
298
359
  for (const failedResult of failedEntityLoadResults) {
299
360
  if (failedResult.reason instanceof EntityNotAuthorizedError) {
300
- return false;
361
+ return { allowed: false, authorizationErrors: [failedResult.reason] };
301
362
  } else {
302
363
  throw failedResult.reason;
303
364
  }
@@ -311,48 +372,68 @@ async function canViewerDeleteInternalAsync<
311
372
  switch (association.edgeDeletionBehavior) {
312
373
  case EntityEdgeDeletionBehavior.CASCADE_DELETE:
313
374
  case EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY: {
314
- const canDeleteAll = (
315
- await Promise.all(
316
- entitiesForInboundEdge.map((entity) =>
317
- canViewerDeleteInternalAsync(
318
- inboundEdge,
319
- entity,
320
- newCascadingDeleteCause,
321
- queryContext,
322
- ),
375
+ const canDeleteEvaluationResults = await Promise.all(
376
+ entitiesForInboundEdge.map((entity) =>
377
+ canViewerDeleteInternalAsync(
378
+ inboundEdge,
379
+ entity,
380
+ newCascadingDeleteCause,
381
+ queryContext,
323
382
  ),
324
- )
325
- ).every((b) => b);
383
+ ),
384
+ );
326
385
 
327
- if (!canDeleteAll) {
328
- return false;
386
+ const reducedEvaluationResult = reduceEvaluationResults(canDeleteEvaluationResults);
387
+ if (!reducedEvaluationResult.allowed) {
388
+ return reducedEvaluationResult;
329
389
  }
390
+
330
391
  break;
331
392
  }
332
393
 
333
394
  case EntityEdgeDeletionBehavior.SET_NULL:
334
395
  case EntityEdgeDeletionBehavior.SET_NULL_INVALIDATE_CACHE_ONLY: {
335
- const canUpdateAll = (
336
- await Promise.all(
337
- entitiesForInboundEdge.map((entity) =>
338
- canViewerUpdateInternalAsync(
339
- inboundEdge,
340
- entity,
341
- newCascadingDeleteCause,
342
- queryContext,
343
- ),
396
+ const canUpdateEvaluationResults = await Promise.all(
397
+ entitiesForInboundEdge.map((entity) =>
398
+ canViewerUpdateInternalAsync(
399
+ inboundEdge,
400
+ entity,
401
+ newCascadingDeleteCause,
402
+ queryContext,
344
403
  ),
345
- )
346
- ).every((b) => b);
404
+ ),
405
+ );
347
406
 
348
- if (!canUpdateAll) {
349
- return false;
407
+ const reducedEvaluationResult = reduceEvaluationResults(canUpdateEvaluationResults);
408
+ if (!reducedEvaluationResult.allowed) {
409
+ return reducedEvaluationResult;
350
410
  }
411
+
351
412
  break;
352
413
  }
353
414
  }
354
415
  }
355
416
  }
356
417
 
357
- return true;
418
+ return { allowed: true };
419
+ }
420
+
421
+ function reduceEvaluationResults(
422
+ evaluationResults: EntityPrivacyEvaluationResult[],
423
+ ): EntityPrivacyEvaluationResult {
424
+ const [successResults, failureResults] = partitionArray<
425
+ EntityPrivacyEvaluationResultSuccess,
426
+ EntityPrivacyEvaluationResultFailure
427
+ >(evaluationResults, (evaluationResult) => evaluationResult.allowed);
428
+
429
+ if (successResults.length === evaluationResults.length) {
430
+ return { allowed: true };
431
+ }
432
+
433
+ return {
434
+ allowed: false,
435
+ authorizationErrors: failureResults.flatMap(
436
+ (failureResult) => failureResult.authorizationErrors,
437
+ ),
438
+ };
358
439
  }