@expo/entity 0.22.0 → 0.25.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 (145) hide show
  1. package/build/ComposedEntityCacheAdapter.d.ts +19 -0
  2. package/build/ComposedEntityCacheAdapter.js +66 -0
  3. package/build/ComposedEntityCacheAdapter.js.map +1 -0
  4. package/build/ComposedSecondaryEntityCache.d.ts +15 -0
  5. package/build/ComposedSecondaryEntityCache.js +37 -0
  6. package/build/ComposedSecondaryEntityCache.js.map +1 -0
  7. package/build/Entity.js +6 -6
  8. package/build/Entity.js.map +1 -1
  9. package/build/EntityAssociationLoader.js +4 -4
  10. package/build/EntityAssociationLoader.js.map +1 -1
  11. package/build/EntityCacheAdapter.d.ts +2 -9
  12. package/build/EntityCacheAdapter.js.map +1 -1
  13. package/build/EntityFieldDefinition.d.ts +8 -0
  14. package/build/EntityFieldDefinition.js +5 -0
  15. package/build/EntityFieldDefinition.js.map +1 -1
  16. package/build/EntityFields.d.ts +38 -0
  17. package/build/EntityFields.js +38 -0
  18. package/build/EntityFields.js.map +1 -1
  19. package/build/EntityLoader.d.ts +3 -2
  20. package/build/EntityLoader.js +5 -4
  21. package/build/EntityLoader.js.map +1 -1
  22. package/build/EntityLoaderFactory.d.ts +2 -2
  23. package/build/EntityLoaderFactory.js +2 -2
  24. package/build/EntityLoaderFactory.js.map +1 -1
  25. package/build/EntityMutationInfo.d.ts +12 -3
  26. package/build/EntityMutator.d.ts +5 -4
  27. package/build/EntityMutator.js +31 -24
  28. package/build/EntityMutator.js.map +1 -1
  29. package/build/EntityMutatorFactory.d.ts +4 -4
  30. package/build/EntityMutatorFactory.js +6 -6
  31. package/build/EntityMutatorFactory.js.map +1 -1
  32. package/build/EntityPrivacyPolicy.d.ts +15 -4
  33. package/build/EntityPrivacyPolicy.js +14 -14
  34. package/build/EntityPrivacyPolicy.js.map +1 -1
  35. package/build/GenericSecondaryEntityCache.d.ts +19 -0
  36. package/build/GenericSecondaryEntityCache.js +74 -0
  37. package/build/GenericSecondaryEntityCache.js.map +1 -0
  38. package/build/IEntityGenericCacher.d.ts +11 -0
  39. package/build/IEntityGenericCacher.js +3 -0
  40. package/build/IEntityGenericCacher.js.map +1 -0
  41. package/build/ReadonlyEntity.js +1 -1
  42. package/build/ReadonlyEntity.js.map +1 -1
  43. package/build/ViewerScopedEntityLoaderFactory.d.ts +2 -2
  44. package/build/ViewerScopedEntityLoaderFactory.js +2 -2
  45. package/build/ViewerScopedEntityLoaderFactory.js.map +1 -1
  46. package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
  47. package/build/ViewerScopedEntityMutatorFactory.js +6 -6
  48. package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
  49. package/build/__tests__/ComposedCacheAdapter-test.d.ts +1 -0
  50. package/build/__tests__/ComposedCacheAdapter-test.js +198 -0
  51. package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -0
  52. package/build/__tests__/ComposedSecondaryEntityCache-test.d.ts +1 -0
  53. package/build/__tests__/ComposedSecondaryEntityCache-test.js +65 -0
  54. package/build/__tests__/ComposedSecondaryEntityCache-test.js.map +1 -0
  55. package/build/__tests__/EntityCommonUseCases-test.js +1 -1
  56. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  57. package/build/__tests__/EntityEdges-test.js +260 -37
  58. package/build/__tests__/EntityEdges-test.js.map +1 -1
  59. package/build/__tests__/EntityLoader-constructor-test.js +2 -1
  60. package/build/__tests__/EntityLoader-constructor-test.js.map +1 -1
  61. package/build/__tests__/EntityLoader-test.js +19 -11
  62. package/build/__tests__/EntityLoader-test.js.map +1 -1
  63. package/build/__tests__/EntityMutator-test.js +96 -43
  64. package/build/__tests__/EntityMutator-test.js.map +1 -1
  65. package/build/__tests__/EntityPrivacyPolicy-test.js +23 -12
  66. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  67. package/build/__tests__/EntitySecondaryCacheLoader-test.js +1 -1
  68. package/build/__tests__/EntitySecondaryCacheLoader-test.js.map +1 -1
  69. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js +3 -2
  70. package/build/__tests__/ViewerScopedEntityLoaderFactory-test.js.map +1 -1
  71. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +3 -2
  72. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
  73. package/build/index.d.ts +2 -0
  74. package/build/index.js +3 -1
  75. package/build/index.js.map +1 -1
  76. package/build/internal/ReadThroughEntityCache.d.ts +2 -3
  77. package/build/internal/ReadThroughEntityCache.js +2 -6
  78. package/build/internal/ReadThroughEntityCache.js.map +1 -1
  79. package/build/internal/__tests__/ReadThroughEntityCache-test.js +0 -32
  80. package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
  81. package/build/rules/AlwaysAllowPrivacyPolicyRule.d.ts +2 -1
  82. package/build/rules/AlwaysAllowPrivacyPolicyRule.js +1 -1
  83. package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
  84. package/build/rules/AlwaysDenyPrivacyPolicyRule.d.ts +2 -1
  85. package/build/rules/AlwaysDenyPrivacyPolicyRule.js +1 -1
  86. package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
  87. package/build/rules/AlwaysSkipPrivacyPolicyRule.d.ts +2 -1
  88. package/build/rules/AlwaysSkipPrivacyPolicyRule.js +1 -1
  89. package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
  90. package/build/rules/PrivacyPolicyRule.d.ts +2 -1
  91. package/build/rules/PrivacyPolicyRule.js.map +1 -1
  92. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js +1 -0
  93. package/build/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.js.map +1 -1
  94. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js +1 -0
  95. package/build/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.js.map +1 -1
  96. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js +1 -0
  97. package/build/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.js.map +1 -1
  98. package/build/utils/testing/PrivacyPolicyRuleTestUtils.d.ts +2 -0
  99. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js +6 -6
  100. package/build/utils/testing/PrivacyPolicyRuleTestUtils.js.map +1 -1
  101. package/build/utils/testing/StubCacheAdapter.d.ts +6 -9
  102. package/build/utils/testing/StubCacheAdapter.js +0 -6
  103. package/build/utils/testing/StubCacheAdapter.js.map +1 -1
  104. package/package.json +1 -1
  105. package/src/ComposedEntityCacheAdapter.ts +86 -0
  106. package/src/ComposedSecondaryEntityCache.ts +63 -0
  107. package/src/Entity.ts +6 -4
  108. package/src/EntityAssociationLoader.ts +4 -4
  109. package/src/EntityCacheAdapter.ts +2 -10
  110. package/src/EntityFieldDefinition.ts +8 -0
  111. package/src/EntityFields.ts +45 -0
  112. package/src/EntityLoader.ts +5 -1
  113. package/src/EntityLoaderFactory.ts +4 -2
  114. package/src/EntityMutationInfo.ts +13 -3
  115. package/src/EntityMutator.ts +44 -21
  116. package/src/EntityMutatorFactory.ts +10 -4
  117. package/src/EntityPrivacyPolicy.ts +31 -1
  118. package/src/GenericSecondaryEntityCache.ts +98 -0
  119. package/src/IEntityGenericCacher.ts +15 -0
  120. package/src/ReadonlyEntity.ts +1 -1
  121. package/src/ViewerScopedEntityLoaderFactory.ts +8 -3
  122. package/src/ViewerScopedEntityMutatorFactory.ts +22 -7
  123. package/src/__tests__/ComposedCacheAdapter-test.ts +280 -0
  124. package/src/__tests__/ComposedSecondaryEntityCache-test.ts +101 -0
  125. package/src/__tests__/EntityCommonUseCases-test.ts +2 -1
  126. package/src/__tests__/EntityEdges-test.ts +286 -45
  127. package/src/__tests__/EntityLoader-constructor-test.ts +3 -1
  128. package/src/__tests__/EntityLoader-test.ts +26 -1
  129. package/src/__tests__/EntityMutator-test.ts +99 -37
  130. package/src/__tests__/EntityPrivacyPolicy-test.ts +66 -7
  131. package/src/__tests__/EntitySecondaryCacheLoader-test.ts +1 -0
  132. package/src/__tests__/ViewerScopedEntityLoaderFactory-test.ts +4 -2
  133. package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +6 -2
  134. package/src/index.ts +2 -0
  135. package/src/internal/ReadThroughEntityCache.ts +6 -28
  136. package/src/internal/__tests__/ReadThroughEntityCache-test.ts +0 -44
  137. package/src/rules/AlwaysAllowPrivacyPolicyRule.ts +2 -0
  138. package/src/rules/AlwaysDenyPrivacyPolicyRule.ts +2 -0
  139. package/src/rules/AlwaysSkipPrivacyPolicyRule.ts +2 -0
  140. package/src/rules/PrivacyPolicyRule.ts +2 -0
  141. package/src/rules/__tests__/AlwaysAllowPrivacyPolicyRule-test.ts +2 -0
  142. package/src/rules/__tests__/AlwaysDenyPrivacyPolicyRule-test.ts +2 -0
  143. package/src/rules/__tests__/AlwaysSkipPrivacyPolicyRule-test.ts +2 -0
  144. package/src/utils/testing/PrivacyPolicyRuleTestUtils.ts +14 -6
  145. package/src/utils/testing/StubCacheAdapter.ts +11 -17
@@ -8,6 +8,7 @@ import EntityPrivacyPolicy, {
8
8
  EntityPrivacyPolicyEvaluator,
9
9
  EntityAuthorizationAction,
10
10
  EntityPrivacyPolicyEvaluationMode,
11
+ EntityPrivacyPolicyEvaluationContext,
11
12
  } from '../EntityPrivacyPolicy';
12
13
  import { EntityQueryContext } from '../EntityQueryContext';
13
14
  import ViewerContext from '../ViewerContext';
@@ -166,6 +167,7 @@ class AlwaysThrowPrivacyPolicyRule extends PrivacyPolicyRule<
166
167
  evaluateAsync(
167
168
  _viewerContext: ViewerContext,
168
169
  _queryContext: EntityQueryContext,
170
+ _evaluationContext: EntityPrivacyPolicyEvaluationContext,
169
171
  _entity: BlahEntity
170
172
  ): Promise<RuleEvaluationResult> {
171
173
  throw new Error('WooHoo!');
@@ -245,12 +247,19 @@ describe(EntityPrivacyPolicy, () => {
245
247
  it('throws EntityNotAuthorizedError when deny', async () => {
246
248
  const viewerContext = instance(mock(ViewerContext));
247
249
  const queryContext = instance(mock(EntityQueryContext));
250
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
248
251
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
249
252
  const metricsAdapter = instance(metricsAdapterMock);
250
253
  const entity = new BlahEntity(viewerContext, { id: '1' });
251
254
  const policy = new AlwaysDenyPolicy();
252
255
  await expect(
253
- policy.authorizeCreateAsync(viewerContext, queryContext, entity, metricsAdapter)
256
+ policy.authorizeCreateAsync(
257
+ viewerContext,
258
+ queryContext,
259
+ privacyPolicyEvaluationContext,
260
+ entity,
261
+ metricsAdapter
262
+ )
254
263
  ).rejects.toBeInstanceOf(EntityNotAuthorizedError);
255
264
  verify(
256
265
  metricsAdapterMock.logAuthorizationEvent(
@@ -267,6 +276,7 @@ describe(EntityPrivacyPolicy, () => {
267
276
  it('returns entity when allowed', async () => {
268
277
  const viewerContext = instance(mock(ViewerContext));
269
278
  const queryContext = instance(mock(EntityQueryContext));
279
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
270
280
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
271
281
  const metricsAdapter = instance(metricsAdapterMock);
272
282
  const entity = new BlahEntity(viewerContext, { id: '1' });
@@ -274,6 +284,7 @@ describe(EntityPrivacyPolicy, () => {
274
284
  const approvedEntity = await policy.authorizeCreateAsync(
275
285
  viewerContext,
276
286
  queryContext,
287
+ privacyPolicyEvaluationContext,
277
288
  entity,
278
289
  metricsAdapter
279
290
  );
@@ -293,12 +304,19 @@ describe(EntityPrivacyPolicy, () => {
293
304
  it('throws EntityNotAuthorizedError when all skipped', async () => {
294
305
  const viewerContext = instance(mock(ViewerContext));
295
306
  const queryContext = instance(mock(EntityQueryContext));
307
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
296
308
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
297
309
  const metricsAdapter = instance(metricsAdapterMock);
298
310
  const entity = new BlahEntity(viewerContext, { id: '1' });
299
311
  const policy = new SkipAllPolicy();
300
312
  await expect(
301
- policy.authorizeCreateAsync(viewerContext, queryContext, entity, metricsAdapter)
313
+ policy.authorizeCreateAsync(
314
+ viewerContext,
315
+ queryContext,
316
+ privacyPolicyEvaluationContext,
317
+ entity,
318
+ metricsAdapter
319
+ )
302
320
  ).rejects.toBeInstanceOf(EntityNotAuthorizedError);
303
321
  verify(
304
322
  metricsAdapterMock.logAuthorizationEvent(
@@ -315,12 +333,19 @@ describe(EntityPrivacyPolicy, () => {
315
333
  it('throws EntityNotAuthorizedError when empty policy', async () => {
316
334
  const viewerContext = instance(mock(ViewerContext));
317
335
  const queryContext = instance(mock(EntityQueryContext));
336
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
318
337
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
319
338
  const metricsAdapter = instance(metricsAdapterMock);
320
339
  const entity = new BlahEntity(viewerContext, { id: '1' });
321
340
  const policy = new EmptyPolicy();
322
341
  await expect(
323
- policy.authorizeCreateAsync(viewerContext, queryContext, entity, metricsAdapter)
342
+ policy.authorizeCreateAsync(
343
+ viewerContext,
344
+ queryContext,
345
+ privacyPolicyEvaluationContext,
346
+ entity,
347
+ metricsAdapter
348
+ )
324
349
  ).rejects.toBeInstanceOf(EntityNotAuthorizedError);
325
350
  verify(
326
351
  metricsAdapterMock.logAuthorizationEvent(
@@ -337,12 +362,19 @@ describe(EntityPrivacyPolicy, () => {
337
362
  it('throws when rule throws', async () => {
338
363
  const viewerContext = instance(mock(ViewerContext));
339
364
  const queryContext = instance(mock(EntityQueryContext));
365
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
340
366
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
341
367
  const metricsAdapter = instance(metricsAdapterMock);
342
368
  const entity = new BlahEntity(viewerContext, { id: '1' });
343
369
  const policy = new ThrowAllPolicy();
344
370
  await expect(
345
- policy.authorizeCreateAsync(viewerContext, queryContext, entity, metricsAdapter)
371
+ policy.authorizeCreateAsync(
372
+ viewerContext,
373
+ queryContext,
374
+ privacyPolicyEvaluationContext,
375
+ entity,
376
+ metricsAdapter
377
+ )
346
378
  ).rejects.toThrowError('WooHoo!');
347
379
  verify(metricsAdapterMock.logAuthorizationEvent(anything())).never();
348
380
  });
@@ -352,6 +384,7 @@ describe(EntityPrivacyPolicy, () => {
352
384
  it('returns entity when denied but calls denialHandler', async () => {
353
385
  const viewerContext = instance(mock(ViewerContext));
354
386
  const queryContext = instance(mock(EntityQueryContext));
387
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
355
388
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
356
389
  const metricsAdapter = instance(metricsAdapterMock);
357
390
  const entity = new BlahEntity(viewerContext, { id: '1' });
@@ -362,6 +395,7 @@ describe(EntityPrivacyPolicy, () => {
362
395
  const approvedEntity = await policy.authorizeCreateAsync(
363
396
  viewerContext,
364
397
  queryContext,
398
+ privacyPolicyEvaluationContext,
365
399
  entity,
366
400
  metricsAdapter
367
401
  );
@@ -384,6 +418,7 @@ describe(EntityPrivacyPolicy, () => {
384
418
  it('does not log when not denied', async () => {
385
419
  const viewerContext = instance(mock(ViewerContext));
386
420
  const queryContext = instance(mock(EntityQueryContext));
421
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
387
422
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
388
423
  const metricsAdapter = instance(metricsAdapterMock);
389
424
  const entity = new BlahEntity(viewerContext, { id: '1' });
@@ -394,6 +429,7 @@ describe(EntityPrivacyPolicy, () => {
394
429
  const approvedEntity = await policy.authorizeCreateAsync(
395
430
  viewerContext,
396
431
  queryContext,
432
+ privacyPolicyEvaluationContext,
397
433
  entity,
398
434
  metricsAdapter
399
435
  );
@@ -416,6 +452,7 @@ describe(EntityPrivacyPolicy, () => {
416
452
  it('passes through other errors', async () => {
417
453
  const viewerContext = instance(mock(ViewerContext));
418
454
  const queryContext = instance(mock(EntityQueryContext));
455
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
419
456
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
420
457
  const metricsAdapter = instance(metricsAdapterMock);
421
458
  const entity = new BlahEntity(viewerContext, { id: '1' });
@@ -424,7 +461,13 @@ describe(EntityPrivacyPolicy, () => {
424
461
  const policySpy = spy(policy);
425
462
 
426
463
  await expect(
427
- policy.authorizeCreateAsync(viewerContext, queryContext, entity, metricsAdapter)
464
+ policy.authorizeCreateAsync(
465
+ viewerContext,
466
+ queryContext,
467
+ privacyPolicyEvaluationContext,
468
+ entity,
469
+ metricsAdapter
470
+ )
428
471
  ).rejects.toThrowError('WooHoo!');
429
472
 
430
473
  verify(policySpy.denyHandler(anyOfClass(EntityNotAuthorizedError))).never();
@@ -437,6 +480,7 @@ describe(EntityPrivacyPolicy, () => {
437
480
  it('denies when denied but calls denialHandler', async () => {
438
481
  const viewerContext = instance(mock(ViewerContext));
439
482
  const queryContext = instance(mock(EntityQueryContext));
483
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
440
484
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
441
485
  const metricsAdapter = instance(metricsAdapterMock);
442
486
  const entity = new BlahEntity(viewerContext, { id: '1' });
@@ -445,7 +489,13 @@ describe(EntityPrivacyPolicy, () => {
445
489
  const policySpy = spy(policy);
446
490
 
447
491
  await expect(
448
- policy.authorizeCreateAsync(viewerContext, queryContext, entity, metricsAdapter)
492
+ policy.authorizeCreateAsync(
493
+ viewerContext,
494
+ queryContext,
495
+ privacyPolicyEvaluationContext,
496
+ entity,
497
+ metricsAdapter
498
+ )
449
499
  ).rejects.toBeInstanceOf(EntityNotAuthorizedError);
450
500
 
451
501
  verify(policySpy.denyHandler(anyOfClass(EntityNotAuthorizedError))).once();
@@ -465,6 +515,7 @@ describe(EntityPrivacyPolicy, () => {
465
515
  it('does not log when not denied', async () => {
466
516
  const viewerContext = instance(mock(ViewerContext));
467
517
  const queryContext = instance(mock(EntityQueryContext));
518
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
468
519
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
469
520
  const metricsAdapter = instance(metricsAdapterMock);
470
521
  const entity = new BlahEntity(viewerContext, { id: '1' });
@@ -475,6 +526,7 @@ describe(EntityPrivacyPolicy, () => {
475
526
  const approvedEntity = await policy.authorizeCreateAsync(
476
527
  viewerContext,
477
528
  queryContext,
529
+ privacyPolicyEvaluationContext,
478
530
  entity,
479
531
  metricsAdapter
480
532
  );
@@ -497,6 +549,7 @@ describe(EntityPrivacyPolicy, () => {
497
549
  it('passes through other errors', async () => {
498
550
  const viewerContext = instance(mock(ViewerContext));
499
551
  const queryContext = instance(mock(EntityQueryContext));
552
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
500
553
  const metricsAdapterMock = mock<IEntityMetricsAdapter>();
501
554
  const metricsAdapter = instance(metricsAdapterMock);
502
555
  const entity = new BlahEntity(viewerContext, { id: '1' });
@@ -505,7 +558,13 @@ describe(EntityPrivacyPolicy, () => {
505
558
  const policySpy = spy(policy);
506
559
 
507
560
  await expect(
508
- policy.authorizeCreateAsync(viewerContext, queryContext, entity, metricsAdapter)
561
+ policy.authorizeCreateAsync(
562
+ viewerContext,
563
+ queryContext,
564
+ privacyPolicyEvaluationContext,
565
+ entity,
566
+ metricsAdapter
567
+ )
509
568
  ).rejects.toThrowError('WooHoo!');
510
569
 
511
570
  verify(policySpy.denyHandler(anyOfClass(EntityNotAuthorizedError))).never();
@@ -79,6 +79,7 @@ describe(EntitySecondaryCacheLoader, () => {
79
79
  vc1,
80
80
  anyOfClass(EntityNonTransactionalQueryContext),
81
81
  anything(),
82
+ anything(),
82
83
  anything()
83
84
  )
84
85
  ).once();
@@ -1,6 +1,7 @@
1
1
  import { mock, verify, instance } from 'ts-mockito';
2
2
 
3
3
  import EntityLoaderFactory from '../EntityLoaderFactory';
4
+ import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
4
5
  import { EntityQueryContext } from '../EntityQueryContext';
5
6
  import ViewerContext from '../ViewerContext';
6
7
  import ViewerScopedEntityLoaderFactory from '../ViewerScopedEntityLoaderFactory';
@@ -8,6 +9,7 @@ import ViewerScopedEntityLoaderFactory from '../ViewerScopedEntityLoaderFactory'
8
9
  describe(ViewerScopedEntityLoaderFactory, () => {
9
10
  it('correctly scopes viewer to entity loads', async () => {
10
11
  const viewerContext = instance(mock(ViewerContext));
12
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
11
13
  const queryContext = instance(mock(EntityQueryContext));
12
14
  const baseLoader = mock<EntityLoaderFactory<any, any, any, any, any, any>>(EntityLoaderFactory);
13
15
  const baseLoaderInstance = instance(baseLoader);
@@ -21,8 +23,8 @@ describe(ViewerScopedEntityLoaderFactory, () => {
21
23
  any
22
24
  >(baseLoaderInstance, viewerContext);
23
25
 
24
- viewerScopedEntityLoader.forLoad(queryContext);
26
+ viewerScopedEntityLoader.forLoad(queryContext, privacyPolicyEvaluationContext);
25
27
 
26
- verify(baseLoader.forLoad(viewerContext, queryContext)).once();
28
+ verify(baseLoader.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)).once();
27
29
  });
28
30
  });
@@ -1,6 +1,7 @@
1
1
  import { mock, instance, verify } from 'ts-mockito';
2
2
 
3
3
  import EntityMutatorFactory from '../EntityMutatorFactory';
4
+ import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
4
5
  import { EntityQueryContext } from '../EntityQueryContext';
5
6
  import ViewerContext from '../ViewerContext';
6
7
  import ViewerScopedEntityMutatorFactory from '../ViewerScopedEntityMutatorFactory';
@@ -9,6 +10,7 @@ import TestEntity, { TestFields, TestEntityPrivacyPolicy } from '../testfixtures
9
10
  describe(ViewerScopedEntityMutatorFactory, () => {
10
11
  it('correctly scopes viewer to entity mutations', async () => {
11
12
  const viewerContext = instance(mock(ViewerContext));
13
+ const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
12
14
  const queryContext = instance(mock(EntityQueryContext));
13
15
  const baseMutatorFactory =
14
16
  mock<
@@ -25,8 +27,10 @@ describe(ViewerScopedEntityMutatorFactory, () => {
25
27
  keyof TestFields
26
28
  >(baseMutatorFactoryInstance, viewerContext);
27
29
 
28
- viewerScopedEntityLoader.forCreate(queryContext);
30
+ viewerScopedEntityLoader.forCreate(queryContext, privacyPolicyEvaluationContext);
29
31
 
30
- verify(baseMutatorFactory.forCreate(viewerContext, queryContext)).once();
32
+ verify(
33
+ baseMutatorFactory.forCreate(viewerContext, queryContext, privacyPolicyEvaluationContext)
34
+ ).once();
31
35
  });
32
36
  });
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * @module @expo/entity
5
5
  */
6
6
 
7
+ export { default as GenericSecondaryEntityCache } from './GenericSecondaryEntityCache';
7
8
  export { default as EnforcingEntityLoader } from './EnforcingEntityLoader';
8
9
  export { default as Entity } from './Entity';
9
10
  export * from './Entity';
@@ -43,6 +44,7 @@ export * from './EntityQueryContext';
43
44
  export { default as IEntityCacheAdapterProvider } from './IEntityCacheAdapterProvider';
44
45
  export { default as IEntityDatabaseAdapterProvider } from './IEntityDatabaseAdapterProvider';
45
46
  export { default as EntityQueryContextProvider } from './EntityQueryContextProvider';
47
+ export { default as IEntityGenericCacher } from './IEntityGenericCacher';
46
48
  export { default as ReadonlyEntity } from './ReadonlyEntity';
47
49
  export { default as ViewerContext } from './ViewerContext';
48
50
  export { default as ViewerScopedEntityCompanion } from './ViewerScopedEntityCompanion';
@@ -3,11 +3,6 @@ import invariant from 'invariant';
3
3
  import EntityCacheAdapter from '../EntityCacheAdapter';
4
4
  import EntityConfiguration from '../EntityConfiguration';
5
5
  import { filterMap } from '../utils/collections/maps';
6
- import {
7
- FieldTransformerMap,
8
- transformCacheObjectToFields,
9
- transformFieldsToCacheObject,
10
- } from './EntityFieldTransformationUtils';
11
6
 
12
7
  export enum CacheStatus {
13
8
  HIT,
@@ -15,10 +10,10 @@ export enum CacheStatus {
15
10
  NEGATIVE,
16
11
  }
17
12
 
18
- export type CacheLoadResult =
13
+ export type CacheLoadResult<TFields> =
19
14
  | {
20
15
  status: CacheStatus.HIT;
21
- item: Readonly<object>;
16
+ item: Readonly<TFields>;
22
17
  }
23
18
  | {
24
19
  status: CacheStatus.MISS;
@@ -32,14 +27,10 @@ export type CacheLoadResult =
32
27
  * {@link EntityCacheAdapter} within the {@link EntityDataManager}.
33
28
  */
34
29
  export default class ReadThroughEntityCache<TFields> {
35
- private readonly fieldTransformerMap: FieldTransformerMap;
36
-
37
30
  constructor(
38
31
  private readonly entityConfiguration: EntityConfiguration<TFields>,
39
32
  private readonly entityCacheAdapter: EntityCacheAdapter<TFields>
40
- ) {
41
- this.fieldTransformerMap = entityCacheAdapter.getFieldTransformerMap();
42
- }
33
+ ) {}
43
34
 
44
35
  private isFieldCacheable<N extends keyof TFields>(fieldName: N): boolean {
45
36
  return this.entityConfiguration.cacheableKeys.has(fieldName);
@@ -91,13 +82,7 @@ export default class ReadThroughEntityCache<TFields> {
91
82
  const results: Map<NonNullable<TFields[N]>, readonly Readonly<TFields>[]> = new Map();
92
83
  cacheLoadResults.forEach((cacheLoadResult, fieldValue) => {
93
84
  if (cacheLoadResult.status === CacheStatus.HIT) {
94
- results.set(fieldValue, [
95
- transformCacheObjectToFields(
96
- this.entityConfiguration,
97
- this.fieldTransformerMap,
98
- cacheLoadResult.item
99
- ),
100
- ]);
85
+ results.set(fieldValue, [cacheLoadResult.item]);
101
86
  }
102
87
  });
103
88
 
@@ -110,7 +95,7 @@ export default class ReadThroughEntityCache<TFields> {
110
95
  return !objectsFromFulfillerForFv || objectsFromFulfillerForFv.length === 0;
111
96
  });
112
97
 
113
- const objectsToCache: Map<NonNullable<TFields[N]>, object> = new Map();
98
+ const objectsToCache: Map<NonNullable<TFields[N]>, Readonly<TFields>> = new Map();
114
99
  for (const [fieldValue, objects] of dbFetchResults.entries()) {
115
100
  if (objects.length > 1) {
116
101
  // multiple objects received for what was supposed to be a unique query, don't add to return map nor cache
@@ -123,14 +108,7 @@ export default class ReadThroughEntityCache<TFields> {
123
108
  }
124
109
  const uniqueObject = objects[0];
125
110
  if (uniqueObject) {
126
- objectsToCache.set(
127
- fieldValue,
128
- transformFieldsToCacheObject(
129
- this.entityConfiguration,
130
- this.fieldTransformerMap,
131
- uniqueObject
132
- )
133
- );
111
+ objectsToCache.set(fieldValue, uniqueObject);
134
112
  results.set(fieldValue, [uniqueObject]);
135
113
  }
136
114
  }
@@ -40,7 +40,6 @@ describe(ReadThroughEntityCache, () => {
40
40
  describe('readManyThroughAsync', () => {
41
41
  it('fetches from DB upon cache miss and caches the result', async () => {
42
42
  const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
43
- when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
44
43
  const cacheAdapter = instance(cacheAdapterMock);
45
44
  const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
46
45
  const fetcher = createIdFetcher(['wat', 'who']);
@@ -77,7 +76,6 @@ describe(ReadThroughEntityCache, () => {
77
76
 
78
77
  it('does not fetch from the DB or cache results when all cache fetches are hits', async () => {
79
78
  const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
80
- when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
81
79
  const cacheAdapter = instance(cacheAdapterMock);
82
80
  const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
83
81
  const fetcher = createIdFetcher(['wat', 'who']);
@@ -114,7 +112,6 @@ describe(ReadThroughEntityCache, () => {
114
112
 
115
113
  it('negatively caches db misses', async () => {
116
114
  const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
117
- when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
118
115
  const cacheAdapter = instance(cacheAdapterMock);
119
116
  const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
120
117
 
@@ -135,7 +132,6 @@ describe(ReadThroughEntityCache, () => {
135
132
 
136
133
  it('does not return or fetch negatively cached results from DB', async () => {
137
134
  const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
138
- when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
139
135
  const cacheAdapter = instance(cacheAdapterMock);
140
136
  const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
141
137
  const fetcher = createIdFetcher([]);
@@ -153,7 +149,6 @@ describe(ReadThroughEntityCache, () => {
153
149
 
154
150
  it('does a mix and match of hit, miss, and negative', async () => {
155
151
  const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
156
- when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
157
152
  const cacheAdapter = instance(cacheAdapterMock);
158
153
  const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
159
154
  const fetcher = createIdFetcher(['wat', 'who', 'why']);
@@ -189,7 +184,6 @@ describe(ReadThroughEntityCache, () => {
189
184
 
190
185
  it('does not call into cache for field that is not cacheable', async () => {
191
186
  const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
192
- when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(new Map());
193
187
  const cacheAdapter = instance(cacheAdapterMock);
194
188
  const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(false), cacheAdapter);
195
189
  const fetcher = createIdFetcher(['wat']);
@@ -197,44 +191,6 @@ describe(ReadThroughEntityCache, () => {
197
191
  verify(cacheAdapterMock.loadManyAsync('id', anything())).never();
198
192
  expect(result).toEqual(new Map([['wat', [{ id: 'wat' }]]]));
199
193
  });
200
-
201
- it('transforms fields for cache storage', async () => {
202
- const cacheAdapterMock = mock<EntityCacheAdapter<BlahFields>>();
203
- when(cacheAdapterMock.getFieldTransformerMap()).thenReturn(
204
- new Map([
205
- [
206
- UUIDField.name,
207
- {
208
- read: (val) => val.split('-')[0],
209
- write: (val) => `${val}-in-cache`,
210
- },
211
- ],
212
- ])
213
- );
214
- const cacheAdapter = instance(cacheAdapterMock);
215
- const entityCache = new ReadThroughEntityCache(makeEntityConfiguration(true), cacheAdapter);
216
- const fetcher = createIdFetcher(['wat', 'who']);
217
-
218
- when(cacheAdapterMock.loadManyAsync('id', deepEqual(['wat', 'who']))).thenResolve(
219
- new Map([
220
- ['wat', { status: CacheStatus.MISS }],
221
- ['who', { status: CacheStatus.HIT, item: { id: 'who-in-cache' } }],
222
- ])
223
- );
224
-
225
- const result = await entityCache.readManyThroughAsync('id', ['wat', 'who'], fetcher);
226
-
227
- verify(cacheAdapterMock.loadManyAsync('id', deepEqual(['wat', 'who']))).once();
228
- verify(
229
- cacheAdapterMock.cacheManyAsync('id', deepEqual(new Map([['wat', { id: 'wat-in-cache' }]])))
230
- ).once();
231
- expect(result).toEqual(
232
- new Map([
233
- ['wat', [{ id: 'wat' }]],
234
- ['who', [{ id: 'who' }]],
235
- ])
236
- );
237
- });
238
194
  });
239
195
 
240
196
  describe('invalidateManyAsync', () => {
@@ -1,3 +1,4 @@
1
+ import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
1
2
  import { EntityQueryContext } from '../EntityQueryContext';
2
3
  import ReadonlyEntity from '../ReadonlyEntity';
3
4
  import ViewerContext from '../ViewerContext';
@@ -16,6 +17,7 @@ export default class AlwaysAllowPrivacyPolicyRule<
16
17
  async evaluateAsync(
17
18
  _viewerContext: TViewerContext,
18
19
  _queryContext: EntityQueryContext,
20
+ _evaluationContext: EntityPrivacyPolicyEvaluationContext,
19
21
  _entity: TEntity
20
22
  ): Promise<RuleEvaluationResult> {
21
23
  return RuleEvaluationResult.ALLOW;
@@ -1,3 +1,4 @@
1
+ import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
1
2
  import { EntityQueryContext } from '../EntityQueryContext';
2
3
  import ReadonlyEntity from '../ReadonlyEntity';
3
4
  import ViewerContext from '../ViewerContext';
@@ -16,6 +17,7 @@ export default class AlwaysDenyPrivacyPolicyRule<
16
17
  async evaluateAsync(
17
18
  _viewerContext: TViewerContext,
18
19
  _queryContext: EntityQueryContext,
20
+ _evaluationContext: EntityPrivacyPolicyEvaluationContext,
19
21
  _entity: TEntity
20
22
  ): Promise<RuleEvaluationResult> {
21
23
  return RuleEvaluationResult.DENY;
@@ -1,3 +1,4 @@
1
+ import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
1
2
  import { EntityQueryContext } from '../EntityQueryContext';
2
3
  import ReadonlyEntity from '../ReadonlyEntity';
3
4
  import ViewerContext from '../ViewerContext';
@@ -16,6 +17,7 @@ export default class AlwaysSkipPrivacyPolicyRule<
16
17
  async evaluateAsync(
17
18
  _viewerContext: TViewerContext,
18
19
  _queryContext: EntityQueryContext,
20
+ _evaluationContext: EntityPrivacyPolicyEvaluationContext,
19
21
  _entity: TEntity
20
22
  ): Promise<RuleEvaluationResult> {
21
23
  return RuleEvaluationResult.SKIP;
@@ -1,3 +1,4 @@
1
+ import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
1
2
  import { EntityQueryContext } from '../EntityQueryContext';
2
3
  import ReadonlyEntity from '../ReadonlyEntity';
3
4
  import ViewerContext from '../ViewerContext';
@@ -45,6 +46,7 @@ export default abstract class PrivacyPolicyRule<
45
46
  abstract evaluateAsync(
46
47
  viewerContext: TViewerContext,
47
48
  queryContext: EntityQueryContext,
49
+ evaluationContext: EntityPrivacyPolicyEvaluationContext,
48
50
  entity: TEntity
49
51
  ): Promise<RuleEvaluationResult>;
50
52
  }
@@ -1,5 +1,6 @@
1
1
  import { mock, instance, anything } from 'ts-mockito';
2
2
 
3
+ import { EntityPrivacyPolicyEvaluationContext } from '../../EntityPrivacyPolicy';
3
4
  import { EntityQueryContext } from '../../EntityQueryContext';
4
5
  import ViewerContext from '../../ViewerContext';
5
6
  import { describePrivacyPolicyRule } from '../../utils/testing/PrivacyPolicyRuleTestUtils';
@@ -10,6 +11,7 @@ describePrivacyPolicyRule(new AlwaysAllowPrivacyPolicyRule(), {
10
11
  {
11
12
  viewerContext: instance(mock(ViewerContext)),
12
13
  queryContext: instance(mock(EntityQueryContext)),
14
+ evaluationContext: instance(mock<EntityPrivacyPolicyEvaluationContext>()),
13
15
  entity: anything(),
14
16
  },
15
17
  ],
@@ -1,5 +1,6 @@
1
1
  import { mock, instance, anything } from 'ts-mockito';
2
2
 
3
+ import { EntityPrivacyPolicyEvaluationContext } from '../../EntityPrivacyPolicy';
3
4
  import { EntityQueryContext } from '../../EntityQueryContext';
4
5
  import ViewerContext from '../../ViewerContext';
5
6
  import { describePrivacyPolicyRule } from '../../utils/testing/PrivacyPolicyRuleTestUtils';
@@ -10,6 +11,7 @@ describePrivacyPolicyRule(new AlwaysDenyPrivacyPolicyRule(), {
10
11
  {
11
12
  viewerContext: instance(mock(ViewerContext)),
12
13
  queryContext: instance(mock(EntityQueryContext)),
14
+ evaluationContext: instance(mock<EntityPrivacyPolicyEvaluationContext>()),
13
15
  entity: anything(),
14
16
  },
15
17
  ],
@@ -1,5 +1,6 @@
1
1
  import { mock, instance, anything } from 'ts-mockito';
2
2
 
3
+ import { EntityPrivacyPolicyEvaluationContext } from '../../EntityPrivacyPolicy';
3
4
  import { EntityQueryContext } from '../../EntityQueryContext';
4
5
  import ViewerContext from '../../ViewerContext';
5
6
  import { describePrivacyPolicyRule } from '../../utils/testing/PrivacyPolicyRuleTestUtils';
@@ -10,6 +11,7 @@ describePrivacyPolicyRule(new AlwaysSkipPrivacyPolicyRule(), {
10
11
  {
11
12
  viewerContext: instance(mock(ViewerContext)),
12
13
  queryContext: instance(mock(EntityQueryContext)),
14
+ evaluationContext: instance(mock<EntityPrivacyPolicyEvaluationContext>()),
13
15
  entity: anything(),
14
16
  },
15
17
  ],
@@ -1,3 +1,4 @@
1
+ import { EntityPrivacyPolicyEvaluationContext } from '../../EntityPrivacyPolicy';
1
2
  import { EntityQueryContext } from '../../EntityQueryContext';
2
3
  import ReadonlyEntity from '../../ReadonlyEntity';
3
4
  import ViewerContext from '../../ViewerContext';
@@ -12,6 +13,7 @@ export interface Case<
12
13
  > {
13
14
  viewerContext: TViewerContext;
14
15
  queryContext: EntityQueryContext;
16
+ evaluationContext: EntityPrivacyPolicyEvaluationContext;
15
17
  entity: TEntity;
16
18
  }
17
19
 
@@ -48,9 +50,11 @@ export const describePrivacyPolicyRuleWithAsyncTestCase = <
48
50
  if (allowCases && allowCases.size > 0) {
49
51
  describe('allow cases', () => {
50
52
  test.each(Array.from(allowCases.keys()))('%p', async (caseKey) => {
51
- const { viewerContext, queryContext, entity } = await allowCases.get(caseKey)!();
53
+ const { viewerContext, queryContext, evaluationContext, entity } = await allowCases.get(
54
+ caseKey
55
+ )!();
52
56
  await expect(
53
- privacyPolicyRule.evaluateAsync(viewerContext, queryContext, entity)
57
+ privacyPolicyRule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity)
54
58
  ).resolves.toEqual(RuleEvaluationResult.ALLOW);
55
59
  });
56
60
  });
@@ -59,9 +63,11 @@ export const describePrivacyPolicyRuleWithAsyncTestCase = <
59
63
  if (skipCases && skipCases.size > 0) {
60
64
  describe('skip cases', () => {
61
65
  test.each(Array.from(skipCases.keys()))('%p', async (caseKey) => {
62
- const { viewerContext, queryContext, entity } = await skipCases.get(caseKey)!();
66
+ const { viewerContext, queryContext, evaluationContext, entity } = await skipCases.get(
67
+ caseKey
68
+ )!();
63
69
  await expect(
64
- privacyPolicyRule.evaluateAsync(viewerContext, queryContext, entity)
70
+ privacyPolicyRule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity)
65
71
  ).resolves.toEqual(RuleEvaluationResult.SKIP);
66
72
  });
67
73
  });
@@ -70,9 +76,11 @@ export const describePrivacyPolicyRuleWithAsyncTestCase = <
70
76
  if (denyCases && denyCases.size > 0) {
71
77
  describe('deny cases', () => {
72
78
  test.each(Array.from(denyCases.keys()))('%p', async (caseKey) => {
73
- const { viewerContext, queryContext, entity } = await denyCases.get(caseKey)!();
79
+ const { viewerContext, queryContext, evaluationContext, entity } = await denyCases.get(
80
+ caseKey
81
+ )!();
74
82
  await expect(
75
- privacyPolicyRule.evaluateAsync(viewerContext, queryContext, entity)
83
+ privacyPolicyRule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity)
76
84
  ).resolves.toEqual(RuleEvaluationResult.DENY);
77
85
  });
78
86
  });