@expo/entity 0.54.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/AuthorizationResultBasedEntityAssociationLoader.d.ts +1 -1
- package/build/src/AuthorizationResultBasedEntityAssociationLoader.js.map +1 -1
- package/build/src/AuthorizationResultBasedEntityLoader.d.ts +18 -24
- package/build/src/AuthorizationResultBasedEntityLoader.js +37 -56
- package/build/src/AuthorizationResultBasedEntityLoader.js.map +1 -1
- package/build/src/AuthorizationResultBasedEntityMutator.js +26 -19
- package/build/src/AuthorizationResultBasedEntityMutator.js.map +1 -1
- package/build/src/EnforcingEntityCreator.d.ts +1 -1
- package/build/src/EnforcingEntityCreator.js +1 -1
- package/build/src/EnforcingEntityLoader.d.ts +1 -58
- package/build/src/EnforcingEntityLoader.js +0 -65
- package/build/src/EnforcingEntityLoader.js.map +1 -1
- package/build/src/Entity.d.ts +6 -0
- package/build/src/Entity.js +6 -0
- package/build/src/Entity.js.map +1 -1
- package/build/src/EntityCompanion.d.ts +2 -2
- package/build/src/EntityCompanion.js.map +1 -1
- package/build/src/EntityCompanionProvider.d.ts +1 -1
- package/build/src/EntityCompanionProvider.js +4 -4
- package/build/src/EntityConfiguration.d.ts +1 -1
- package/build/src/EntityConfiguration.js +1 -2
- package/build/src/EntityConfiguration.js.map +1 -1
- package/build/src/{EntityLoaderUtils.d.ts → EntityConstructionUtils.d.ts} +15 -29
- package/build/src/EntityConstructionUtils.js +118 -0
- package/build/src/EntityConstructionUtils.js.map +1 -0
- package/build/src/EntityDatabaseAdapter.d.ts +10 -108
- package/build/src/EntityDatabaseAdapter.js +14 -76
- package/build/src/EntityDatabaseAdapter.js.map +1 -1
- package/build/src/EntityFieldDefinition.d.ts +1 -1
- package/build/src/EntityInvalidationUtils.d.ts +41 -0
- package/build/src/EntityInvalidationUtils.js +71 -0
- package/build/src/EntityInvalidationUtils.js.map +1 -0
- package/build/src/EntityLoader.d.ts +0 -6
- package/build/src/EntityLoader.js +0 -7
- package/build/src/EntityLoader.js.map +1 -1
- package/build/src/EntityLoaderFactory.d.ts +4 -0
- package/build/src/EntityLoaderFactory.js +10 -3
- package/build/src/EntityLoaderFactory.js.map +1 -1
- package/build/src/EntityPrivacyPolicy.d.ts +27 -0
- package/build/src/EntityPrivacyPolicy.js +22 -1
- package/build/src/EntityPrivacyPolicy.js.map +1 -1
- package/build/src/EntitySecondaryCacheLoader.d.ts +14 -3
- package/build/src/EntitySecondaryCacheLoader.js +21 -4
- package/build/src/EntitySecondaryCacheLoader.js.map +1 -1
- package/build/src/ReadonlyEntity.d.ts +4 -5
- package/build/src/ReadonlyEntity.js +7 -8
- package/build/src/ReadonlyEntity.js.map +1 -1
- package/build/src/ViewerContext.d.ts +6 -6
- package/build/src/ViewerContext.js +8 -8
- package/build/src/ViewerScopedEntityCompanion.d.ts +1 -1
- package/build/src/ViewerScopedEntityCompanion.js.map +1 -1
- package/build/src/ViewerScopedEntityLoaderFactory.d.ts +4 -0
- package/build/src/ViewerScopedEntityLoaderFactory.js +6 -0
- package/build/src/ViewerScopedEntityLoaderFactory.js.map +1 -1
- package/build/src/errors/EntityDatabaseAdapterError.d.ts +4 -0
- package/build/src/errors/EntityDatabaseAdapterError.js +13 -1
- package/build/src/errors/EntityDatabaseAdapterError.js.map +1 -1
- package/build/src/errors/EntityError.d.ts +2 -1
- package/build/src/errors/EntityError.js +1 -0
- package/build/src/errors/EntityError.js.map +1 -1
- package/build/src/index.d.ts +6 -1
- package/build/src/index.js +6 -1
- package/build/src/index.js.map +1 -1
- package/build/src/internal/EntityDataManager.d.ts +8 -16
- package/build/src/internal/EntityDataManager.js +8 -18
- package/build/src/internal/EntityDataManager.js.map +1 -1
- package/build/src/internal/EntityFieldTransformationUtils.js.map +1 -1
- package/build/src/internal/EntityLoadInterfaces.d.ts +2 -0
- package/build/src/internal/EntityLoadInterfaces.js +2 -0
- package/build/src/internal/EntityLoadInterfaces.js.map +1 -1
- package/build/src/internal/EntityTableDataCoordinator.d.ts +2 -0
- package/build/src/internal/EntityTableDataCoordinator.js +4 -0
- package/build/src/internal/EntityTableDataCoordinator.js.map +1 -1
- package/build/src/metrics/EntityMetricsUtils.d.ts +1 -0
- package/build/src/metrics/EntityMetricsUtils.js +15 -1
- package/build/src/metrics/EntityMetricsUtils.js.map +1 -1
- package/build/src/metrics/IEntityMetricsAdapter.d.ts +4 -1
- package/build/src/metrics/IEntityMetricsAdapter.js +3 -0
- package/build/src/metrics/IEntityMetricsAdapter.js.map +1 -1
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.d.ts +10 -0
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js +19 -0
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.d.ts +10 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js +19 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.d.ts +66 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js +75 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.d.ts +12 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js +23 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/PrivacyPolicyRule.d.ts +2 -2
- package/build/src/utils/EntityPrivacyUtils.js +11 -20
- package/build/src/utils/EntityPrivacyUtils.js.map +1 -1
- package/build/src/utils/collections/maps.d.ts +2 -2
- package/build/src/utils/collections/maps.js +2 -2
- package/package.json +5 -5
- package/src/AuthorizationResultBasedEntityAssociationLoader.ts +4 -7
- package/src/AuthorizationResultBasedEntityLoader.ts +58 -88
- package/src/AuthorizationResultBasedEntityMutator.ts +35 -20
- package/src/EnforcingEntityCreator.ts +1 -1
- package/src/EnforcingEntityLoader.ts +1 -95
- package/src/Entity.ts +6 -0
- package/src/EntityCompanion.ts +2 -2
- package/src/EntityCompanionProvider.ts +4 -4
- package/src/EntityConfiguration.ts +8 -5
- package/src/EntityConstructionUtils.ts +168 -0
- package/src/EntityDatabaseAdapter.ts +32 -222
- package/src/EntityFieldDefinition.ts +1 -1
- package/src/{EntityLoaderUtils.ts → EntityInvalidationUtils.ts} +5 -96
- package/src/EntityLoader.ts +0 -16
- package/src/EntityLoaderFactory.ts +50 -10
- package/src/EntityPrivacyPolicy.ts +44 -1
- package/src/EntitySecondaryCacheLoader.ts +54 -3
- package/src/ReadonlyEntity.ts +9 -11
- package/src/ViewerContext.ts +10 -10
- package/src/ViewerScopedEntityCompanion.ts +1 -1
- package/src/ViewerScopedEntityLoaderFactory.ts +37 -0
- package/src/__tests__/AuthorizationResultBasedEntityLoader-constructor-test.ts +3 -5
- package/src/__tests__/AuthorizationResultBasedEntityLoader-test.ts +34 -419
- package/src/__tests__/ComposedCacheAdapter-test.ts +3 -3
- package/src/__tests__/EnforcingEntityLoader-test.ts +2 -134
- package/src/__tests__/EntityCompanion-test.ts +18 -0
- package/src/__tests__/EntityConfiguration-test.ts +4 -4
- package/src/__tests__/EntityDatabaseAdapter-test.ts +33 -68
- package/src/__tests__/EntityEdges-test.ts +10 -10
- package/src/__tests__/EntityLoader-test.ts +6 -4
- package/src/__tests__/EntityMutator-test.ts +27 -15
- package/src/__tests__/EntityPrivacyPolicy-test.ts +102 -0
- package/src/__tests__/EntityQueryContext-test.ts +11 -11
- package/src/__tests__/EntitySecondaryCacheLoader-test.ts +10 -5
- package/src/__tests__/EntitySelfReferentialEdges-test.ts +6 -6
- package/src/__tests__/GenericEntityCacheAdapter-test.ts +18 -15
- package/src/__tests__/GenericSecondaryEntityCache-test.ts +27 -5
- package/src/__tests__/ReadonlyEntity-test.ts +6 -4
- package/src/__tests__/ViewerContext-test.ts +4 -4
- package/src/__tests__/ViewerScopedEntityCompanion-test.ts +1 -0
- package/src/__tests__/cases/TwoEntitySameTableDisjointRows-test.ts +0 -17
- package/src/errors/EntityDatabaseAdapterError.ts +14 -0
- package/src/errors/EntityError.ts +1 -0
- package/src/errors/__tests__/EntityDatabaseAdapterError-test.ts +9 -0
- package/src/errors/__tests__/EntityError-test.ts +13 -5
- package/src/index.ts +6 -1
- package/src/internal/EntityDataManager.ts +19 -54
- package/src/internal/EntityFieldTransformationUtils.ts +5 -5
- package/src/internal/EntityLoadInterfaces.ts +2 -0
- package/src/internal/EntityTableDataCoordinator.ts +2 -2
- package/src/internal/__tests__/CompositeFieldHolder-test.ts +8 -2
- package/src/internal/__tests__/EntityDataManager-test.ts +71 -202
- package/src/internal/__tests__/ReadThroughEntityCache-test.ts +39 -24
- package/src/metrics/EntityMetricsUtils.ts +23 -0
- package/src/metrics/IEntityMetricsAdapter.ts +3 -0
- package/src/metrics/__tests__/EntityMetricsUtils-test.ts +120 -0
- package/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.ts +47 -0
- package/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.ts +47 -0
- package/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.ts +177 -0
- package/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.ts +46 -0
- package/src/rules/PrivacyPolicyRule.ts +2 -2
- package/src/rules/__tests__/AllowIfAllSubRulesAllowPrivacyPolicyRule-test.ts +64 -0
- package/src/rules/__tests__/AllowIfAnySubRuleAllowsPrivacyPolicyRule-test.ts +64 -0
- package/src/rules/__tests__/AllowIfInParentCascadeDeletionPrivacyPolicyRule-test.ts +268 -0
- package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -2
- package/src/rules/__tests__/EvaluateIfEntityFieldPredicatePrivacyPolicyRule-test.ts +47 -0
- package/src/utils/EntityPrivacyUtils.ts +18 -29
- package/src/utils/__testfixtures__/PrivacyPolicyRuleTestUtils.ts +2 -2
- package/src/utils/__testfixtures__/StubDatabaseAdapter.ts +13 -101
- package/src/utils/__tests__/EntityCreationUtils-test.ts +6 -6
- package/src/utils/__tests__/EntityPrivacyUtils-test.ts +2 -2
- package/src/utils/collections/maps.ts +2 -2
- package/build/src/EntityLoaderUtils.js +0 -147
- package/build/src/EntityLoaderUtils.js.map +0 -1
|
@@ -76,7 +76,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
76
76
|
|
|
77
77
|
when(
|
|
78
78
|
cacheAdapterMock.loadManyAsync(
|
|
79
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
79
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
80
80
|
deepEqualEntityAware([
|
|
81
81
|
new SingleFieldValueHolder('wat'),
|
|
82
82
|
new SingleFieldValueHolder('who'),
|
|
@@ -102,7 +102,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
102
102
|
|
|
103
103
|
verify(
|
|
104
104
|
cacheAdapterMock.loadManyAsync(
|
|
105
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
105
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
106
106
|
deepEqualEntityAware([
|
|
107
107
|
new SingleFieldValueHolder('wat'),
|
|
108
108
|
new SingleFieldValueHolder('who'),
|
|
@@ -111,7 +111,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
111
111
|
).once();
|
|
112
112
|
verify(
|
|
113
113
|
cacheAdapterMock.cacheManyAsync(
|
|
114
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
114
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
115
115
|
deepEqualEntityAware(
|
|
116
116
|
new SingleFieldValueHolderMap(
|
|
117
117
|
new Map([
|
|
@@ -146,7 +146,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
146
146
|
|
|
147
147
|
when(
|
|
148
148
|
cacheAdapterMock.loadManyAsync(
|
|
149
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
149
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
150
150
|
deepEqualEntityAware([
|
|
151
151
|
new SingleFieldValueHolder('wat'),
|
|
152
152
|
new SingleFieldValueHolder('who'),
|
|
@@ -170,7 +170,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
170
170
|
|
|
171
171
|
verify(
|
|
172
172
|
cacheAdapterMock.loadManyAsync(
|
|
173
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
173
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
174
174
|
deepEqualEntityAware([
|
|
175
175
|
new SingleFieldValueHolder('wat'),
|
|
176
176
|
new SingleFieldValueHolder('who'),
|
|
@@ -179,7 +179,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
179
179
|
).once();
|
|
180
180
|
verify(
|
|
181
181
|
cacheAdapterMock.cacheManyAsync(
|
|
182
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
182
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
183
183
|
deepEqualEntityAware(
|
|
184
184
|
new SingleFieldValueHolderMap(
|
|
185
185
|
new Map([
|
|
@@ -216,7 +216,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
216
216
|
|
|
217
217
|
when(
|
|
218
218
|
cacheAdapterMock.loadManyAsync(
|
|
219
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
219
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
220
220
|
deepEqualEntityAware([new SingleFieldValueHolder('why')]),
|
|
221
221
|
),
|
|
222
222
|
).thenResolve(
|
|
@@ -233,19 +233,19 @@ describe(ReadThroughEntityCache, () => {
|
|
|
233
233
|
|
|
234
234
|
verify(
|
|
235
235
|
cacheAdapterMock.loadManyAsync(
|
|
236
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
236
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
237
237
|
deepEqualEntityAware([new SingleFieldValueHolder('why')]),
|
|
238
238
|
),
|
|
239
239
|
).once();
|
|
240
240
|
verify(
|
|
241
241
|
cacheAdapterMock.cacheManyAsync(
|
|
242
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
242
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
243
243
|
deepEqualEntityAware(new SingleFieldValueHolderMap(new Map())),
|
|
244
244
|
),
|
|
245
245
|
).once();
|
|
246
246
|
verify(
|
|
247
247
|
cacheAdapterMock.cacheDBMissesAsync(
|
|
248
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
248
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
249
249
|
deepEqualEntityAware([new SingleFieldValueHolder('why')]),
|
|
250
250
|
),
|
|
251
251
|
).once();
|
|
@@ -260,7 +260,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
260
260
|
|
|
261
261
|
when(
|
|
262
262
|
cacheAdapterMock.loadManyAsync(
|
|
263
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
263
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
264
264
|
deepEqualEntityAware([new SingleFieldValueHolder('why')]),
|
|
265
265
|
),
|
|
266
266
|
).thenResolve(
|
|
@@ -276,12 +276,22 @@ describe(ReadThroughEntityCache, () => {
|
|
|
276
276
|
);
|
|
277
277
|
verify(
|
|
278
278
|
cacheAdapterMock.loadManyAsync(
|
|
279
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
279
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
280
280
|
deepEqualEntityAware([new SingleFieldValueHolder('why')]),
|
|
281
281
|
),
|
|
282
282
|
).once();
|
|
283
|
-
verify(
|
|
284
|
-
|
|
283
|
+
verify(
|
|
284
|
+
cacheAdapterMock.cacheManyAsync(
|
|
285
|
+
new SingleFieldHolder<BlahFields, 'id', 'id'>('id'),
|
|
286
|
+
anything(),
|
|
287
|
+
),
|
|
288
|
+
).never();
|
|
289
|
+
verify(
|
|
290
|
+
cacheAdapterMock.cacheDBMissesAsync(
|
|
291
|
+
new SingleFieldHolder<BlahFields, 'id', 'id'>('id'),
|
|
292
|
+
anything(),
|
|
293
|
+
),
|
|
294
|
+
).never();
|
|
285
295
|
expect(result).toEqual(new SingleFieldValueHolderMap(new Map()));
|
|
286
296
|
});
|
|
287
297
|
|
|
@@ -293,7 +303,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
293
303
|
|
|
294
304
|
when(
|
|
295
305
|
cacheAdapterMock.loadManyAsync(
|
|
296
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
306
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
297
307
|
deepEqualEntityAware([
|
|
298
308
|
new SingleFieldValueHolder('wat'),
|
|
299
309
|
new SingleFieldValueHolder('who'),
|
|
@@ -324,7 +334,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
324
334
|
);
|
|
325
335
|
verify(
|
|
326
336
|
cacheAdapterMock.loadManyAsync(
|
|
327
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
337
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
328
338
|
deepEqualEntityAware([
|
|
329
339
|
new SingleFieldValueHolder('wat'),
|
|
330
340
|
new SingleFieldValueHolder('who'),
|
|
@@ -335,7 +345,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
335
345
|
).once();
|
|
336
346
|
verify(
|
|
337
347
|
cacheAdapterMock.cacheManyAsync(
|
|
338
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
348
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
339
349
|
deepEqualEntityAware(
|
|
340
350
|
new SingleFieldValueHolderMap(
|
|
341
351
|
new Map([[new SingleFieldValueHolder('wat'), { id: 'wat' }]]),
|
|
@@ -345,7 +355,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
345
355
|
).once();
|
|
346
356
|
verify(
|
|
347
357
|
cacheAdapterMock.cacheDBMissesAsync(
|
|
348
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
358
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
349
359
|
deepEqualEntityAware([new SingleFieldValueHolder('how')]),
|
|
350
360
|
),
|
|
351
361
|
).once();
|
|
@@ -372,7 +382,12 @@ describe(ReadThroughEntityCache, () => {
|
|
|
372
382
|
[new SingleFieldValueHolder<BlahFields, 'id'>('wat')],
|
|
373
383
|
fetcher,
|
|
374
384
|
);
|
|
375
|
-
verify(
|
|
385
|
+
verify(
|
|
386
|
+
cacheAdapterMock.loadManyAsync(
|
|
387
|
+
new SingleFieldHolder<BlahFields, 'id', 'id'>('id'),
|
|
388
|
+
anything(),
|
|
389
|
+
),
|
|
390
|
+
).never();
|
|
376
391
|
expect(result).toEqual(
|
|
377
392
|
new SingleFieldValueHolderMap(
|
|
378
393
|
new Map([[new SingleFieldValueHolder('wat'), [{ id: 'wat' }]]]),
|
|
@@ -390,7 +405,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
390
405
|
|
|
391
406
|
when(
|
|
392
407
|
cacheAdapterMock.loadManyAsync(
|
|
393
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
408
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
394
409
|
deepEqualEntityAware([
|
|
395
410
|
new SingleFieldValueHolder('wat'),
|
|
396
411
|
new SingleFieldValueHolder('who'),
|
|
@@ -416,7 +431,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
416
431
|
|
|
417
432
|
verify(
|
|
418
433
|
cacheAdapterMock.loadManyAsync(
|
|
419
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
434
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
420
435
|
deepEqualEntityAware([
|
|
421
436
|
new SingleFieldValueHolder('wat'),
|
|
422
437
|
new SingleFieldValueHolder('who'),
|
|
@@ -425,7 +440,7 @@ describe(ReadThroughEntityCache, () => {
|
|
|
425
440
|
).once();
|
|
426
441
|
verify(
|
|
427
442
|
cacheAdapterMock.cacheManyAsync(
|
|
428
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
443
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
429
444
|
deepEqualEntityAware(
|
|
430
445
|
new Map([
|
|
431
446
|
[new SingleFieldValueHolder('wat'), { id: 'wat' }],
|
|
@@ -456,12 +471,12 @@ describe(ReadThroughEntityCache, () => {
|
|
|
456
471
|
const cacheAdapterMock = mock<IEntityCacheAdapter<BlahFields, 'id'>>();
|
|
457
472
|
const cacheAdapter = instance(cacheAdapterMock);
|
|
458
473
|
const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
|
|
459
|
-
await entityCache.invalidateManyAsync(new SingleFieldHolder('id'), [
|
|
474
|
+
await entityCache.invalidateManyAsync(new SingleFieldHolder<BlahFields, 'id', 'id'>('id'), [
|
|
460
475
|
new SingleFieldValueHolder('wat'),
|
|
461
476
|
]);
|
|
462
477
|
verify(
|
|
463
478
|
cacheAdapterMock.invalidateManyAsync(
|
|
464
|
-
deepEqualEntityAware(new SingleFieldHolder('id')),
|
|
479
|
+
deepEqualEntityAware(new SingleFieldHolder<BlahFields, 'id', 'id'>('id')),
|
|
465
480
|
deepEqualEntityAware([new SingleFieldValueHolder('wat')]),
|
|
466
481
|
),
|
|
467
482
|
).once();
|
|
@@ -30,6 +30,29 @@ export const timeAndLogLoadEventAsync =
|
|
|
30
30
|
return result;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
export const timeAndLogLoadOneEventAsync =
|
|
34
|
+
(
|
|
35
|
+
metricsAdapter: IEntityMetricsAdapter,
|
|
36
|
+
loadType: EntityMetricsLoadType,
|
|
37
|
+
entityClassName: string,
|
|
38
|
+
queryContext: EntityQueryContext,
|
|
39
|
+
) =>
|
|
40
|
+
async <TFields>(promise: Promise<Readonly<TFields> | null>) => {
|
|
41
|
+
const startTime = Date.now();
|
|
42
|
+
const result = await promise;
|
|
43
|
+
const endTime = Date.now();
|
|
44
|
+
|
|
45
|
+
metricsAdapter.logDataManagerLoadEvent({
|
|
46
|
+
type: loadType,
|
|
47
|
+
isInTransaction: queryContext.isInTransaction(),
|
|
48
|
+
entityClassName,
|
|
49
|
+
duration: endTime - startTime,
|
|
50
|
+
count: result ? 1 : 0,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
|
|
33
56
|
export const timeAndLogLoadMapEventAsync =
|
|
34
57
|
(
|
|
35
58
|
metricsAdapter: IEntityMetricsAdapter,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { anyNumber, deepEqual, instance, mock, verify, when } from 'ts-mockito';
|
|
3
|
+
|
|
4
|
+
import { EntityQueryContext } from '../../EntityQueryContext';
|
|
5
|
+
import {
|
|
6
|
+
timeAndLogLoadEventAsync,
|
|
7
|
+
timeAndLogLoadMapEventAsync,
|
|
8
|
+
timeAndLogMutationEventAsync,
|
|
9
|
+
} from '../EntityMetricsUtils';
|
|
10
|
+
import {
|
|
11
|
+
EntityMetricsLoadType,
|
|
12
|
+
EntityMetricsMutationType,
|
|
13
|
+
IEntityMetricsAdapter,
|
|
14
|
+
} from '../IEntityMetricsAdapter';
|
|
15
|
+
|
|
16
|
+
describe(timeAndLogLoadEventAsync, () => {
|
|
17
|
+
it('returns the result from the wrapped promise and logs', async () => {
|
|
18
|
+
const metricsAdapterMock = mock<IEntityMetricsAdapter>();
|
|
19
|
+
const metricsAdapter = instance(metricsAdapterMock);
|
|
20
|
+
|
|
21
|
+
const queryContextMock = mock<EntityQueryContext>();
|
|
22
|
+
when(queryContextMock.isInTransaction()).thenReturn(false);
|
|
23
|
+
const queryContext = instance(queryContextMock);
|
|
24
|
+
|
|
25
|
+
const expectedResult = [{ id: 1 }, { id: 2 }];
|
|
26
|
+
|
|
27
|
+
const result = await timeAndLogLoadEventAsync(
|
|
28
|
+
metricsAdapter,
|
|
29
|
+
EntityMetricsLoadType.LOAD_MANY,
|
|
30
|
+
'TestEntity',
|
|
31
|
+
queryContext,
|
|
32
|
+
)(Promise.resolve(expectedResult));
|
|
33
|
+
|
|
34
|
+
expect(result).toBe(expectedResult);
|
|
35
|
+
|
|
36
|
+
verify(
|
|
37
|
+
metricsAdapterMock.logDataManagerLoadEvent(
|
|
38
|
+
deepEqual({
|
|
39
|
+
type: EntityMetricsLoadType.LOAD_MANY,
|
|
40
|
+
isInTransaction: false,
|
|
41
|
+
entityClassName: 'TestEntity',
|
|
42
|
+
duration: anyNumber(),
|
|
43
|
+
count: expectedResult.length,
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
).once();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe(timeAndLogLoadMapEventAsync, () => {
|
|
51
|
+
it('returns the result from the wrapped promise and logs with count summed across map values', async () => {
|
|
52
|
+
const metricsAdapterMock = mock<IEntityMetricsAdapter>();
|
|
53
|
+
const metricsAdapter = instance(metricsAdapterMock);
|
|
54
|
+
|
|
55
|
+
const queryContextMock = mock<EntityQueryContext>();
|
|
56
|
+
when(queryContextMock.isInTransaction()).thenReturn(false);
|
|
57
|
+
const queryContext = instance(queryContextMock);
|
|
58
|
+
|
|
59
|
+
const key1 = { serialize: () => 'key1' };
|
|
60
|
+
const key2 = { serialize: () => 'key2' };
|
|
61
|
+
const expectedResult = new Map([
|
|
62
|
+
[key1, [{ id: 1 }, { id: 2 }]],
|
|
63
|
+
[key2, [{ id: 3 }]],
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const result = await timeAndLogLoadMapEventAsync(
|
|
67
|
+
metricsAdapter,
|
|
68
|
+
EntityMetricsLoadType.LOAD_MANY,
|
|
69
|
+
'TestEntity',
|
|
70
|
+
queryContext,
|
|
71
|
+
)(Promise.resolve(expectedResult));
|
|
72
|
+
|
|
73
|
+
expect(result).toBe(expectedResult);
|
|
74
|
+
|
|
75
|
+
verify(
|
|
76
|
+
metricsAdapterMock.logDataManagerLoadEvent(
|
|
77
|
+
deepEqual({
|
|
78
|
+
type: EntityMetricsLoadType.LOAD_MANY,
|
|
79
|
+
isInTransaction: false,
|
|
80
|
+
entityClassName: 'TestEntity',
|
|
81
|
+
duration: anyNumber(),
|
|
82
|
+
count: 3,
|
|
83
|
+
}),
|
|
84
|
+
),
|
|
85
|
+
).once();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe(timeAndLogMutationEventAsync, () => {
|
|
90
|
+
it('returns the result from the wrapped promise and logs', async () => {
|
|
91
|
+
const metricsAdapterMock = mock<IEntityMetricsAdapter>();
|
|
92
|
+
const metricsAdapter = instance(metricsAdapterMock);
|
|
93
|
+
|
|
94
|
+
const queryContextMock = mock<EntityQueryContext>();
|
|
95
|
+
when(queryContextMock.isInTransaction()).thenReturn(true);
|
|
96
|
+
const queryContext = instance(queryContextMock);
|
|
97
|
+
|
|
98
|
+
const expectedResult = { id: 1, name: 'created' };
|
|
99
|
+
|
|
100
|
+
const result = await timeAndLogMutationEventAsync(
|
|
101
|
+
metricsAdapter,
|
|
102
|
+
EntityMetricsMutationType.CREATE,
|
|
103
|
+
'TestEntity',
|
|
104
|
+
queryContext,
|
|
105
|
+
)(Promise.resolve(expectedResult));
|
|
106
|
+
|
|
107
|
+
expect(result).toBe(expectedResult);
|
|
108
|
+
|
|
109
|
+
verify(
|
|
110
|
+
metricsAdapterMock.logMutatorMutationEvent(
|
|
111
|
+
deepEqual({
|
|
112
|
+
type: EntityMetricsMutationType.CREATE,
|
|
113
|
+
isInTransaction: true,
|
|
114
|
+
entityClassName: 'TestEntity',
|
|
115
|
+
duration: anyNumber(),
|
|
116
|
+
}),
|
|
117
|
+
),
|
|
118
|
+
).once();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } from '../EntityPrivacyPolicy';
|
|
2
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
3
|
+
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
4
|
+
import { ViewerContext } from '../ViewerContext';
|
|
5
|
+
import { PrivacyPolicyRule, RuleEvaluationResult } from './PrivacyPolicyRule';
|
|
6
|
+
|
|
7
|
+
export class AllowIfAllSubRulesAllowPrivacyPolicyRule<
|
|
8
|
+
TFields extends object,
|
|
9
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
10
|
+
TViewerContext extends ViewerContext,
|
|
11
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
12
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
13
|
+
> extends PrivacyPolicyRule<TFields, TIDField, TViewerContext, TEntity, TSelectedFields> {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly subRules: PrivacyPolicyRule<
|
|
16
|
+
TFields,
|
|
17
|
+
TIDField,
|
|
18
|
+
TViewerContext,
|
|
19
|
+
TEntity,
|
|
20
|
+
TSelectedFields
|
|
21
|
+
>[],
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async evaluateAsync(
|
|
27
|
+
viewerContext: TViewerContext,
|
|
28
|
+
queryContext: EntityQueryContext,
|
|
29
|
+
evaluationContext: EntityPrivacyPolicyRuleEvaluationContext<
|
|
30
|
+
TFields,
|
|
31
|
+
TIDField,
|
|
32
|
+
TViewerContext,
|
|
33
|
+
TEntity,
|
|
34
|
+
TSelectedFields
|
|
35
|
+
>,
|
|
36
|
+
entity: TEntity,
|
|
37
|
+
): Promise<RuleEvaluationResult> {
|
|
38
|
+
const results = await Promise.all(
|
|
39
|
+
this.subRules.map((subRule) =>
|
|
40
|
+
subRule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
return results.every((result) => result === RuleEvaluationResult.ALLOW)
|
|
44
|
+
? RuleEvaluationResult.ALLOW
|
|
45
|
+
: RuleEvaluationResult.SKIP;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } from '../EntityPrivacyPolicy';
|
|
2
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
3
|
+
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
4
|
+
import { ViewerContext } from '../ViewerContext';
|
|
5
|
+
import { PrivacyPolicyRule, RuleEvaluationResult } from './PrivacyPolicyRule';
|
|
6
|
+
|
|
7
|
+
export class AllowIfAnySubRuleAllowsPrivacyPolicyRule<
|
|
8
|
+
TFields extends object,
|
|
9
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
10
|
+
TViewerContext extends ViewerContext,
|
|
11
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
12
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
13
|
+
> extends PrivacyPolicyRule<TFields, TIDField, TViewerContext, TEntity, TSelectedFields> {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly subRules: PrivacyPolicyRule<
|
|
16
|
+
TFields,
|
|
17
|
+
TIDField,
|
|
18
|
+
TViewerContext,
|
|
19
|
+
TEntity,
|
|
20
|
+
TSelectedFields
|
|
21
|
+
>[],
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async evaluateAsync(
|
|
27
|
+
viewerContext: TViewerContext,
|
|
28
|
+
queryContext: EntityQueryContext,
|
|
29
|
+
evaluationContext: EntityPrivacyPolicyRuleEvaluationContext<
|
|
30
|
+
TFields,
|
|
31
|
+
TIDField,
|
|
32
|
+
TViewerContext,
|
|
33
|
+
TEntity,
|
|
34
|
+
TSelectedFields
|
|
35
|
+
>,
|
|
36
|
+
entity: TEntity,
|
|
37
|
+
): Promise<RuleEvaluationResult> {
|
|
38
|
+
const results = await Promise.all(
|
|
39
|
+
this.subRules.map((subRule) =>
|
|
40
|
+
subRule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
return results.includes(RuleEvaluationResult.ALLOW)
|
|
44
|
+
? RuleEvaluationResult.ALLOW
|
|
45
|
+
: RuleEvaluationResult.SKIP;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { IEntityClass } from '../Entity';
|
|
2
|
+
import { EntityPrivacyPolicy, EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
|
|
3
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
4
|
+
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
5
|
+
import { ViewerContext } from '../ViewerContext';
|
|
6
|
+
import { PrivacyPolicyRule, RuleEvaluationResult } from './PrivacyPolicyRule';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Directive for specifying the parent relationship in AllowIfInParentCascadeDeletionPrivacyPolicyRule.
|
|
10
|
+
*/
|
|
11
|
+
export interface AllowIfInParentCascadeDeletionDirective<
|
|
12
|
+
TViewerContext extends ViewerContext,
|
|
13
|
+
TFields,
|
|
14
|
+
TParentFields extends object,
|
|
15
|
+
TParentIDField extends keyof NonNullable<Pick<TParentFields, TParentSelectedFields>>,
|
|
16
|
+
TParentEntity extends ReadonlyEntity<
|
|
17
|
+
TParentFields,
|
|
18
|
+
TParentIDField,
|
|
19
|
+
TViewerContext,
|
|
20
|
+
TParentSelectedFields
|
|
21
|
+
>,
|
|
22
|
+
TParentPrivacyPolicy extends EntityPrivacyPolicy<
|
|
23
|
+
TParentFields,
|
|
24
|
+
TParentIDField,
|
|
25
|
+
TViewerContext,
|
|
26
|
+
TParentEntity,
|
|
27
|
+
TParentSelectedFields
|
|
28
|
+
>,
|
|
29
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
30
|
+
TParentSelectedFields extends keyof TParentFields = keyof TParentFields,
|
|
31
|
+
> {
|
|
32
|
+
/**
|
|
33
|
+
* Class of parent entity that should trigger a cascade set null update to a field within
|
|
34
|
+
* the entity being authorized.
|
|
35
|
+
*/
|
|
36
|
+
parentEntityClass: IEntityClass<
|
|
37
|
+
TParentFields,
|
|
38
|
+
TParentIDField,
|
|
39
|
+
TViewerContext,
|
|
40
|
+
TParentEntity,
|
|
41
|
+
TParentPrivacyPolicy,
|
|
42
|
+
TParentSelectedFields
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Field of the current entity with references the deleting instace of parentEntityClass.
|
|
47
|
+
*/
|
|
48
|
+
fieldIdentifyingParentEntity: keyof Pick<TFields, TSelectedFields>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Field in parentEntityClass referenced by the value of fieldIdentifyingParentEntity.
|
|
52
|
+
* If not provided, ID is assumed.
|
|
53
|
+
*/
|
|
54
|
+
parentEntityLookupByField?: keyof Pick<TParentFields, TParentSelectedFields>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A generic privacy policy rule that allows when an entity is being authorized
|
|
59
|
+
* as part of a cascading delete from a parent entity. Handles two cases:
|
|
60
|
+
* - When the field has not yet been null'ed out due to a cascading set null. This is often
|
|
61
|
+
* required for read rules to authorize the initial re-read of the entity being update set null'ed.
|
|
62
|
+
* - When the field has been null'ed out due to a cascading set null. This is often required
|
|
63
|
+
* the update rules for the field nullification.
|
|
64
|
+
*
|
|
65
|
+
* These two cases could theoretically be handled by two separate (stricter) rules, but are combined
|
|
66
|
+
* to simplify configuration since practically there are few cases where having them be combined would
|
|
67
|
+
* preset an issue.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* Billing info owned by an account, but records who created the billing info in creating_user_id. User is a member of that account.
|
|
71
|
+
* User can delete themselves, and the billing info's creating_user_id field is cascade set null'ed when the user is deleted.
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* class BillingInfoEntityPrivacyPolicy extends EntityPrivacyPolicy<...> {
|
|
75
|
+
* protected override readonly readRules = [
|
|
76
|
+
* ...,
|
|
77
|
+
* new AllowIfInParentCascadeDeletionPrivacyPolicyRule<...>({
|
|
78
|
+
* fieldIdentifyingParentEntity: 'creating_user_id',
|
|
79
|
+
* parentEntityClass: UserEntity,
|
|
80
|
+
* }),
|
|
81
|
+
* ];
|
|
82
|
+
*
|
|
83
|
+
* protected override readonly updateRules = [
|
|
84
|
+
* ...,
|
|
85
|
+
* new AllowIfInParentCascadeDeletionPrivacyPolicyRule<...>({
|
|
86
|
+
* fieldIdentifyingParentEntity: 'creating_user_id',
|
|
87
|
+
* parentEntityClass: UserEntity,
|
|
88
|
+
* }),
|
|
89
|
+
* ];
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export class AllowIfInParentCascadeDeletionPrivacyPolicyRule<
|
|
94
|
+
TFields extends object,
|
|
95
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
96
|
+
TViewerContext extends ViewerContext,
|
|
97
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
98
|
+
TFields2 extends object,
|
|
99
|
+
TIDField2 extends keyof NonNullable<Pick<TFields2, TSelectedFields2>>,
|
|
100
|
+
TEntity2 extends ReadonlyEntity<TFields2, TIDField2, TViewerContext, TSelectedFields2>,
|
|
101
|
+
TPrivacyPolicy2 extends EntityPrivacyPolicy<
|
|
102
|
+
TFields2,
|
|
103
|
+
TIDField2,
|
|
104
|
+
TViewerContext,
|
|
105
|
+
TEntity2,
|
|
106
|
+
TSelectedFields2
|
|
107
|
+
>,
|
|
108
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
109
|
+
TSelectedFields2 extends keyof TFields2 = keyof TFields2,
|
|
110
|
+
> extends PrivacyPolicyRule<TFields, TIDField, TViewerContext, TEntity, TSelectedFields> {
|
|
111
|
+
constructor(
|
|
112
|
+
private readonly directive: AllowIfInParentCascadeDeletionDirective<
|
|
113
|
+
TViewerContext,
|
|
114
|
+
TFields,
|
|
115
|
+
TFields2,
|
|
116
|
+
TIDField2,
|
|
117
|
+
TEntity2,
|
|
118
|
+
TPrivacyPolicy2,
|
|
119
|
+
TSelectedFields,
|
|
120
|
+
TSelectedFields2
|
|
121
|
+
>,
|
|
122
|
+
) {
|
|
123
|
+
super();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async evaluateAsync(
|
|
127
|
+
_viewerContext: TViewerContext,
|
|
128
|
+
_queryContext: EntityQueryContext,
|
|
129
|
+
evaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
130
|
+
TFields,
|
|
131
|
+
TIDField,
|
|
132
|
+
TViewerContext,
|
|
133
|
+
TEntity,
|
|
134
|
+
TSelectedFields
|
|
135
|
+
>,
|
|
136
|
+
entity: TEntity,
|
|
137
|
+
): Promise<RuleEvaluationResult> {
|
|
138
|
+
const parentEntityClass = this.directive.parentEntityClass;
|
|
139
|
+
|
|
140
|
+
const deleteCause = evaluationContext.cascadingDeleteCause;
|
|
141
|
+
if (!deleteCause || !(deleteCause.entity instanceof parentEntityClass)) {
|
|
142
|
+
return RuleEvaluationResult.SKIP;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const entityBeingDeleted = deleteCause.entity;
|
|
146
|
+
|
|
147
|
+
// allow if parent foreign key field matches specified field in the entity being authorized
|
|
148
|
+
const valueInThisEntityReferencingParent = entity.getField(
|
|
149
|
+
this.directive.fieldIdentifyingParentEntity,
|
|
150
|
+
);
|
|
151
|
+
const valueInParent = this.directive.parentEntityLookupByField
|
|
152
|
+
? entityBeingDeleted.getField(this.directive.parentEntityLookupByField)
|
|
153
|
+
: entityBeingDeleted.getID();
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
valueInThisEntityReferencingParent &&
|
|
157
|
+
valueInThisEntityReferencingParent === valueInParent
|
|
158
|
+
) {
|
|
159
|
+
return RuleEvaluationResult.ALLOW;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// allow if parent foreign key field matches specified field in the entity being authorized, and the
|
|
163
|
+
// field in the entity being authorized has been null'ed out due to cascading set null
|
|
164
|
+
const valueInPreviousValueOfThisEntityReferencingParent =
|
|
165
|
+
evaluationContext.previousValue?.getField(this.directive.fieldIdentifyingParentEntity);
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
valueInPreviousValueOfThisEntityReferencingParent &&
|
|
169
|
+
valueInPreviousValueOfThisEntityReferencingParent === valueInParent &&
|
|
170
|
+
valueInThisEntityReferencingParent === null
|
|
171
|
+
) {
|
|
172
|
+
return RuleEvaluationResult.ALLOW;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return RuleEvaluationResult.SKIP;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { EntityPrivacyPolicyRuleEvaluationContext } from '../EntityPrivacyPolicy';
|
|
2
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
3
|
+
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
4
|
+
import { ViewerContext } from '../ViewerContext';
|
|
5
|
+
import { PrivacyPolicyRule, RuleEvaluationResult } from './PrivacyPolicyRule';
|
|
6
|
+
|
|
7
|
+
export class EvaluateIfEntityFieldPredicatePrivacyPolicyRule<
|
|
8
|
+
TFields extends object,
|
|
9
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
10
|
+
TViewerContext extends ViewerContext,
|
|
11
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
12
|
+
N extends TSelectedFields,
|
|
13
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
14
|
+
> extends PrivacyPolicyRule<TFields, TIDField, TViewerContext, TEntity, TSelectedFields> {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly fieldName: N,
|
|
17
|
+
private readonly shouldEvaluatePredicate: (fieldValue: TFields[N]) => boolean,
|
|
18
|
+
private readonly rule: PrivacyPolicyRule<
|
|
19
|
+
TFields,
|
|
20
|
+
TIDField,
|
|
21
|
+
TViewerContext,
|
|
22
|
+
TEntity,
|
|
23
|
+
TSelectedFields
|
|
24
|
+
>,
|
|
25
|
+
) {
|
|
26
|
+
super();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async evaluateAsync(
|
|
30
|
+
viewerContext: TViewerContext,
|
|
31
|
+
queryContext: EntityQueryContext,
|
|
32
|
+
evaluationContext: EntityPrivacyPolicyRuleEvaluationContext<
|
|
33
|
+
TFields,
|
|
34
|
+
TIDField,
|
|
35
|
+
TViewerContext,
|
|
36
|
+
TEntity,
|
|
37
|
+
TSelectedFields
|
|
38
|
+
>,
|
|
39
|
+
entity: TEntity,
|
|
40
|
+
): Promise<RuleEvaluationResult> {
|
|
41
|
+
const fieldValue = entity.getField(this.fieldName);
|
|
42
|
+
return this.shouldEvaluatePredicate(fieldValue)
|
|
43
|
+
? await this.rule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity)
|
|
44
|
+
: RuleEvaluationResult.SKIP;
|
|
45
|
+
}
|
|
46
|
+
}
|