@expo/entity 0.25.2 → 0.26.1

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 (117) hide show
  1. package/build/ComposedEntityCacheAdapter.d.ts +4 -2
  2. package/build/ComposedEntityCacheAdapter.js +39 -3
  3. package/build/ComposedEntityCacheAdapter.js.map +1 -1
  4. package/build/ComposedSecondaryEntityCache.d.ts +3 -2
  5. package/build/ComposedSecondaryEntityCache.js +3 -2
  6. package/build/ComposedSecondaryEntityCache.js.map +1 -1
  7. package/build/Entity.js +4 -4
  8. package/build/Entity.js.map +1 -1
  9. package/build/EntityAssociationLoader.d.ts +5 -0
  10. package/build/EntityAssociationLoader.js +5 -0
  11. package/build/EntityAssociationLoader.js.map +1 -1
  12. package/build/EntityFieldDefinition.d.ts +16 -8
  13. package/build/EntityFieldDefinition.js +12 -5
  14. package/build/EntityFieldDefinition.js.map +1 -1
  15. package/build/EntityLoader.js +2 -2
  16. package/build/EntityLoader.js.map +1 -1
  17. package/build/EntityMutationInfo.d.ts +2 -0
  18. package/build/EntityMutator.d.ts +6 -8
  19. package/build/EntityMutator.js +58 -46
  20. package/build/EntityMutator.js.map +1 -1
  21. package/build/EntityMutatorFactory.d.ts +4 -4
  22. package/build/EntityMutatorFactory.js +6 -6
  23. package/build/EntityMutatorFactory.js.map +1 -1
  24. package/build/EntityPrivacyPolicy.d.ts +13 -0
  25. package/build/EntityPrivacyPolicy.js +13 -0
  26. package/build/EntityPrivacyPolicy.js.map +1 -1
  27. package/build/EntityQueryContext.d.ts +11 -0
  28. package/build/EntityQueryContext.js +11 -0
  29. package/build/EntityQueryContext.js.map +1 -1
  30. package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
  31. package/build/ViewerScopedEntityMutatorFactory.js +6 -6
  32. package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
  33. package/build/__tests__/ComposedCacheAdapter-test.js +37 -4
  34. package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -1
  35. package/build/__tests__/EntityCommonUseCases-test.js +5 -1
  36. package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
  37. package/build/__tests__/EntityCompanion-test.js +5 -1
  38. package/build/__tests__/EntityCompanion-test.js.map +1 -1
  39. package/build/__tests__/EntityCompanionProvider-test.js +5 -1
  40. package/build/__tests__/EntityCompanionProvider-test.js.map +1 -1
  41. package/build/__tests__/EntityEdges-test.js +199 -33
  42. package/build/__tests__/EntityEdges-test.js.map +1 -1
  43. package/build/__tests__/EntityLoader-test.js +5 -1
  44. package/build/__tests__/EntityLoader-test.js.map +1 -1
  45. package/build/__tests__/EntityMutator-test.js +38 -53
  46. package/build/__tests__/EntityMutator-test.js.map +1 -1
  47. package/build/__tests__/EntityPrivacyPolicy-test.js +5 -1
  48. package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
  49. package/build/__tests__/EntitySelfReferentialEdges-test.js +2 -2
  50. package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
  51. package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js +5 -1
  52. package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js.map +1 -1
  53. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +2 -3
  54. package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
  55. package/build/errors/EntityCacheAdapterError.js +5 -1
  56. package/build/errors/EntityCacheAdapterError.js.map +1 -1
  57. package/build/errors/EntityDatabaseAdapterError.js +5 -1
  58. package/build/errors/EntityDatabaseAdapterError.js.map +1 -1
  59. package/build/errors/EntityInvalidFieldValueError.js +6 -2
  60. package/build/errors/EntityInvalidFieldValueError.js.map +1 -1
  61. package/build/errors/EntityNotAuthorizedError.js +5 -1
  62. package/build/errors/EntityNotAuthorizedError.js.map +1 -1
  63. package/build/errors/EntityNotFoundError.js +6 -2
  64. package/build/errors/EntityNotFoundError.js.map +1 -1
  65. package/build/index.js +5 -1
  66. package/build/index.js.map +1 -1
  67. package/build/internal/EntityDataManager.js +7 -4
  68. package/build/internal/EntityDataManager.js.map +1 -1
  69. package/build/internal/EntityFieldTransformationUtils.js +1 -1
  70. package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
  71. package/build/internal/ReadThroughEntityCache.js +1 -1
  72. package/build/internal/ReadThroughEntityCache.js.map +1 -1
  73. package/build/internal/__tests__/EntityDataManager-test.js +12 -7
  74. package/build/internal/__tests__/EntityDataManager-test.js.map +1 -1
  75. package/build/internal/__tests__/ReadThroughEntityCache-test.js +5 -1
  76. package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
  77. package/build/metrics/IEntityMetricsAdapter.d.ts +62 -17
  78. package/build/metrics/IEntityMetricsAdapter.js +17 -1
  79. package/build/metrics/IEntityMetricsAdapter.js.map +1 -1
  80. package/build/metrics/NoOpEntityMetricsAdapter.d.ts +1 -3
  81. package/build/metrics/NoOpEntityMetricsAdapter.js +1 -3
  82. package/build/metrics/NoOpEntityMetricsAdapter.js.map +1 -1
  83. package/build/rules/AlwaysAllowPrivacyPolicyRule.js +5 -1
  84. package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
  85. package/build/rules/AlwaysDenyPrivacyPolicyRule.js +5 -1
  86. package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
  87. package/build/rules/AlwaysSkipPrivacyPolicyRule.js +5 -1
  88. package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
  89. package/build/utils/testing/StubDatabaseAdapter.js +6 -2
  90. package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
  91. package/package.json +1 -1
  92. package/src/ComposedEntityCacheAdapter.ts +44 -3
  93. package/src/ComposedSecondaryEntityCache.ts +3 -2
  94. package/src/Entity.ts +4 -4
  95. package/src/EntityAssociationLoader.ts +5 -0
  96. package/src/EntityFieldDefinition.ts +15 -6
  97. package/src/EntityLoader.ts +4 -2
  98. package/src/EntityMutationInfo.ts +2 -0
  99. package/src/EntityMutator.ts +98 -67
  100. package/src/EntityMutatorFactory.ts +4 -10
  101. package/src/EntityPrivacyPolicy.ts +15 -0
  102. package/src/EntityQueryContext.ts +11 -0
  103. package/src/ViewerScopedEntityMutatorFactory.ts +7 -22
  104. package/src/__tests__/ComposedCacheAdapter-test.ts +43 -4
  105. package/src/__tests__/EntityEdges-test.ts +287 -32
  106. package/src/__tests__/EntityMutator-test.ts +33 -54
  107. package/src/__tests__/EntitySelfReferentialEdges-test.ts +2 -2
  108. package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +2 -6
  109. package/src/errors/EntityInvalidFieldValueError.ts +1 -1
  110. package/src/errors/EntityNotFoundError.ts +1 -1
  111. package/src/internal/EntityDataManager.ts +13 -5
  112. package/src/internal/EntityFieldTransformationUtils.ts +1 -1
  113. package/src/internal/ReadThroughEntityCache.ts +3 -1
  114. package/src/internal/__tests__/EntityDataManager-test.ts +11 -8
  115. package/src/metrics/IEntityMetricsAdapter.ts +72 -19
  116. package/src/metrics/NoOpEntityMetricsAdapter.ts +1 -5
  117. package/src/utils/testing/StubDatabaseAdapter.ts +4 -1
@@ -18,7 +18,7 @@ import EntityMutationTriggerConfiguration, {
18
18
  EntityNonTransactionalMutationTrigger,
19
19
  } from './EntityMutationTriggerConfiguration';
20
20
  import EntityMutationValidator from './EntityMutationValidator';
21
- import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
21
+ import EntityPrivacyPolicy from './EntityPrivacyPolicy';
22
22
  import { EntityQueryContext, EntityTransactionalQueryContext } from './EntityQueryContext';
23
23
  import ReadonlyEntity from './ReadonlyEntity';
24
24
  import ViewerContext from './ViewerContext';
@@ -44,7 +44,6 @@ abstract class BaseMutator<
44
44
  constructor(
45
45
  protected readonly viewerContext: TViewerContext,
46
46
  protected readonly queryContext: EntityQueryContext,
47
- protected readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
48
47
  protected readonly entityConfiguration: EntityConfiguration<TFields>,
49
48
  protected readonly entityClass: IEntityClass<
50
49
  TFields,
@@ -183,7 +182,7 @@ export class CreateMutator<
183
182
 
184
183
  /**
185
184
  * Commit the new entity after authorizing against creation privacy rules. Invalidates all caches for
186
- * queries that would return new entity and caches the new entity if not in a transactional query context.
185
+ * queries that would return new entity.
187
186
  * @returns authorized, cached, newly-created entity result, where result error can be UnauthorizedError
188
187
  */
189
188
  async createAsync(): Promise<Result<TEntity>> {
@@ -221,7 +220,7 @@ export class CreateMutator<
221
220
  this.privacyPolicy.authorizeCreateAsync(
222
221
  this.viewerContext,
223
222
  queryContext,
224
- this.privacyPolicyEvaluationContext,
223
+ { cascadingDeleteCause: null },
225
224
  temporaryEntityForPrivacyCheck,
226
225
  this.metricsAdapter
227
226
  )
@@ -251,11 +250,9 @@ export class CreateMutator<
251
250
 
252
251
  const insertResult = await this.databaseAdapter.insertAsync(queryContext, this.fieldsForEntity);
253
252
 
254
- const entityLoader = this.entityLoaderFactory.forLoad(
255
- this.viewerContext,
256
- queryContext,
257
- this.privacyPolicyEvaluationContext
258
- );
253
+ const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext, {
254
+ cascadingDeleteCause: null,
255
+ });
259
256
  queryContext.appendPostCommitInvalidationCallback(
260
257
  entityLoader.invalidateFieldsAsync.bind(entityLoader, insertResult)
261
258
  );
@@ -315,7 +312,6 @@ export class UpdateMutator<
315
312
  constructor(
316
313
  viewerContext: TViewerContext,
317
314
  queryContext: EntityQueryContext,
318
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
319
315
  entityConfiguration: EntityConfiguration<TFields>,
320
316
  entityClass: IEntityClass<
321
317
  TFields,
@@ -355,7 +351,6 @@ export class UpdateMutator<
355
351
  super(
356
352
  viewerContext,
357
353
  queryContext,
358
- privacyPolicyEvaluationContext,
359
354
  entityConfiguration,
360
355
  entityClass,
361
356
  privacyPolicy,
@@ -382,8 +377,7 @@ export class UpdateMutator<
382
377
 
383
378
  /**
384
379
  * Commit the changes to the entity after authorizing against update privacy rules.
385
- * Invalidates all caches for pre-update entity and caches the updated entity if not in a
386
- * transactional query context.
380
+ * Invalidates all caches for pre-update entity.
387
381
  * @returns authorized updated entity result, where result error can be UnauthorizedError
388
382
  */
389
383
  async updateAsync(): Promise<Result<TEntity>> {
@@ -391,7 +385,7 @@ export class UpdateMutator<
391
385
  this.metricsAdapter,
392
386
  EntityMetricsMutationType.UPDATE,
393
387
  this.entityClass.name
394
- )(this.updateInTransactionAsync());
388
+ )(this.updateInTransactionAsync(false, null));
395
389
  }
396
390
 
397
391
  /**
@@ -401,14 +395,27 @@ export class UpdateMutator<
401
395
  return await enforceAsyncResult(this.updateAsync());
402
396
  }
403
397
 
404
- private async updateInTransactionAsync(): Promise<Result<TEntity>> {
398
+ private async updateInTransactionAsync(
399
+ skipDatabaseUpdate: true,
400
+ cascadingDeleteCause: EntityCascadingDeletionInfo
401
+ ): Promise<Result<TEntity>>;
402
+ private async updateInTransactionAsync(
403
+ skipDatabaseUpdate: false,
404
+ cascadingDeleteCause: EntityCascadingDeletionInfo | null
405
+ ): Promise<Result<TEntity>>;
406
+ private async updateInTransactionAsync(
407
+ skipDatabaseUpdate: boolean,
408
+ cascadingDeleteCause: EntityCascadingDeletionInfo | null
409
+ ): Promise<Result<TEntity>> {
405
410
  return await this.queryContext.runInTransactionIfNotInTransactionAsync((innerQueryContext) =>
406
- this.updateInternalAsync(innerQueryContext)
411
+ this.updateInternalAsync(innerQueryContext, skipDatabaseUpdate, cascadingDeleteCause)
407
412
  );
408
413
  }
409
414
 
410
415
  private async updateInternalAsync(
411
- queryContext: EntityTransactionalQueryContext
416
+ queryContext: EntityTransactionalQueryContext,
417
+ skipDatabaseUpdate: boolean,
418
+ cascadingDeleteCause: EntityCascadingDeletionInfo | null
412
419
  ): Promise<Result<TEntity>> {
413
420
  this.validateFields(this.updatedFields);
414
421
 
@@ -417,7 +424,7 @@ export class UpdateMutator<
417
424
  this.privacyPolicy.authorizeUpdateAsync(
418
425
  this.viewerContext,
419
426
  queryContext,
420
- this.privacyPolicyEvaluationContext,
427
+ { cascadingDeleteCause },
421
428
  entityAboutToBeUpdated,
422
429
  this.metricsAdapter
423
430
  )
@@ -430,33 +437,34 @@ export class UpdateMutator<
430
437
  this.mutationValidators,
431
438
  queryContext,
432
439
  entityAboutToBeUpdated,
433
- { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
440
+ { type: EntityMutationType.UPDATE, previousValue: this.originalEntity, cascadingDeleteCause }
434
441
  );
435
442
  await this.executeMutationTriggersAsync(
436
443
  this.mutationTriggers.beforeAll,
437
444
  queryContext,
438
445
  entityAboutToBeUpdated,
439
- { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
446
+ { type: EntityMutationType.UPDATE, previousValue: this.originalEntity, cascadingDeleteCause }
440
447
  );
441
448
  await this.executeMutationTriggersAsync(
442
449
  this.mutationTriggers.beforeUpdate,
443
450
  queryContext,
444
451
  entityAboutToBeUpdated,
445
- { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
452
+ { type: EntityMutationType.UPDATE, previousValue: this.originalEntity, cascadingDeleteCause }
446
453
  );
447
454
 
448
- const updateResult = await this.databaseAdapter.updateAsync(
449
- queryContext,
450
- this.entityConfiguration.idField,
451
- entityAboutToBeUpdated.getID(),
452
- this.updatedFields
453
- );
455
+ // skip the database update when specified
456
+ const updateResult = skipDatabaseUpdate
457
+ ? null
458
+ : await this.databaseAdapter.updateAsync(
459
+ queryContext,
460
+ this.entityConfiguration.idField,
461
+ entityAboutToBeUpdated.getID(),
462
+ this.updatedFields
463
+ );
454
464
 
455
- const entityLoader = this.entityLoaderFactory.forLoad(
456
- this.viewerContext,
457
- queryContext,
458
- this.privacyPolicyEvaluationContext
459
- );
465
+ const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext, {
466
+ cascadingDeleteCause,
467
+ });
460
468
 
461
469
  queryContext.appendPostCommitInvalidationCallback(
462
470
  entityLoader.invalidateFieldsAsync.bind(
@@ -468,22 +476,25 @@ export class UpdateMutator<
468
476
  entityLoader.invalidateFieldsAsync.bind(entityLoader, this.fieldsForEntity)
469
477
  );
470
478
 
471
- const unauthorizedEntityAfterUpdate = new this.entityClass(this.viewerContext, updateResult);
472
- const updatedEntity = await entityLoader
473
- .enforcing()
474
- .loadByIDAsync(unauthorizedEntityAfterUpdate.getID());
479
+ // when the database update was skipped, assume it succeeded and use the optimistic
480
+ // entity to execute triggers
481
+ const updatedEntity = updateResult
482
+ ? await entityLoader
483
+ .enforcing()
484
+ .loadByIDAsync(new this.entityClass(this.viewerContext, updateResult).getID())
485
+ : entityAboutToBeUpdated;
475
486
 
476
487
  await this.executeMutationTriggersAsync(
477
488
  this.mutationTriggers.afterUpdate,
478
489
  queryContext,
479
490
  updatedEntity,
480
- { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
491
+ { type: EntityMutationType.UPDATE, previousValue: this.originalEntity, cascadingDeleteCause }
481
492
  );
482
493
  await this.executeMutationTriggersAsync(
483
494
  this.mutationTriggers.afterAll,
484
495
  queryContext,
485
496
  updatedEntity,
486
- { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
497
+ { type: EntityMutationType.UPDATE, previousValue: this.originalEntity, cascadingDeleteCause }
487
498
  );
488
499
 
489
500
  queryContext.appendPostCommitCallback(
@@ -491,7 +502,11 @@ export class UpdateMutator<
491
502
  this,
492
503
  this.mutationTriggers.afterCommit,
493
504
  updatedEntity,
494
- { type: EntityMutationType.UPDATE, previousValue: this.originalEntity }
505
+ {
506
+ type: EntityMutationType.UPDATE,
507
+ previousValue: this.originalEntity,
508
+ cascadingDeleteCause,
509
+ }
495
510
  )
496
511
  );
497
512
 
@@ -519,7 +534,6 @@ export class DeleteMutator<
519
534
  constructor(
520
535
  viewerContext: TViewerContext,
521
536
  queryContext: EntityQueryContext,
522
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
523
537
  entityConfiguration: EntityConfiguration<TFields>,
524
538
  entityClass: IEntityClass<
525
539
  TFields,
@@ -559,7 +573,6 @@ export class DeleteMutator<
559
573
  super(
560
574
  viewerContext,
561
575
  queryContext,
562
- privacyPolicyEvaluationContext,
563
576
  entityConfiguration,
564
577
  entityClass,
565
578
  privacyPolicy,
@@ -772,19 +785,34 @@ export class DeleteMutator<
772
785
  }
773
786
 
774
787
  switch (association.edgeDeletionBehavior) {
775
- case undefined:
776
- case EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE: {
788
+ case EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY: {
789
+ await Promise.all(
790
+ inboundReferenceEntities.map((inboundReferenceEntity) =>
791
+ enforceAsyncResult(
792
+ mutatorFactory
793
+ .forDelete(inboundReferenceEntity, queryContext)
794
+ .deleteInTransactionAsync(
795
+ processedEntityIdentifiers,
796
+ /* skipDatabaseDeletion */ true, // deletion is handled by DB
797
+ newCascadingDeleteCause
798
+ )
799
+ )
800
+ )
801
+ );
802
+ break;
803
+ }
804
+ case EntityEdgeDeletionBehavior.SET_NULL_INVALIDATE_CACHE_ONLY: {
777
805
  await Promise.all(
778
806
  inboundReferenceEntities.map((inboundReferenceEntity) =>
779
- mutatorFactory
780
- .forDelete(inboundReferenceEntity, queryContext, {
781
- cascadingDeleteCause: newCascadingDeleteCause,
782
- })
783
- .deleteInTransactionAsync(
784
- processedEntityIdentifiers,
785
- /* skipDatabaseDeletion */ true, // deletion is handled by DB
786
- newCascadingDeleteCause
787
- )
807
+ enforceAsyncResult(
808
+ mutatorFactory
809
+ .forUpdate(inboundReferenceEntity, queryContext)
810
+ .setField(fieldName, null)
811
+ ['updateInTransactionAsync'](
812
+ /* skipDatabaseUpdate */ true,
813
+ newCascadingDeleteCause
814
+ )
815
+ )
788
816
  )
789
817
  );
790
818
  break;
@@ -792,12 +820,15 @@ export class DeleteMutator<
792
820
  case EntityEdgeDeletionBehavior.SET_NULL: {
793
821
  await Promise.all(
794
822
  inboundReferenceEntities.map((inboundReferenceEntity) =>
795
- mutatorFactory
796
- .forUpdate(inboundReferenceEntity, queryContext, {
797
- cascadingDeleteCause: newCascadingDeleteCause,
798
- })
799
- .setField(fieldName, null)
800
- .enforceUpdateAsync()
823
+ enforceAsyncResult(
824
+ mutatorFactory
825
+ .forUpdate(inboundReferenceEntity, queryContext)
826
+ .setField(fieldName, null)
827
+ ['updateInTransactionAsync'](
828
+ /* skipDatabaseUpdate */ false,
829
+ newCascadingDeleteCause
830
+ )
831
+ )
801
832
  )
802
833
  );
803
834
  break;
@@ -805,15 +836,15 @@ export class DeleteMutator<
805
836
  case EntityEdgeDeletionBehavior.CASCADE_DELETE: {
806
837
  await Promise.all(
807
838
  inboundReferenceEntities.map((inboundReferenceEntity) =>
808
- mutatorFactory
809
- .forDelete(inboundReferenceEntity, queryContext, {
810
- cascadingDeleteCause: newCascadingDeleteCause,
811
- })
812
- .deleteInTransactionAsync(
813
- processedEntityIdentifiers,
814
- /* skipDatabaseDeletion */ false,
815
- newCascadingDeleteCause
816
- )
839
+ enforceAsyncResult(
840
+ mutatorFactory
841
+ .forDelete(inboundReferenceEntity, queryContext)
842
+ .deleteInTransactionAsync(
843
+ processedEntityIdentifiers,
844
+ /* skipDatabaseDeletion */ false,
845
+ newCascadingDeleteCause
846
+ )
847
+ )
817
848
  )
818
849
  );
819
850
  }
@@ -5,7 +5,7 @@ import EntityLoaderFactory from './EntityLoaderFactory';
5
5
  import EntityMutationTriggerConfiguration from './EntityMutationTriggerConfiguration';
6
6
  import EntityMutationValidator from './EntityMutationValidator';
7
7
  import { CreateMutator, UpdateMutator, DeleteMutator } from './EntityMutator';
8
- import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
8
+ import EntityPrivacyPolicy from './EntityPrivacyPolicy';
9
9
  import { EntityQueryContext } from './EntityQueryContext';
10
10
  import ViewerContext from './ViewerContext';
11
11
  import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
@@ -72,13 +72,11 @@ export default class EntityMutatorFactory<
72
72
  */
73
73
  forCreate(
74
74
  viewerContext: TViewerContext,
75
- queryContext: EntityQueryContext,
76
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
75
+ queryContext: EntityQueryContext
77
76
  ): CreateMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
78
77
  return new CreateMutator(
79
78
  viewerContext,
80
79
  queryContext,
81
- privacyPolicyEvaluationContext,
82
80
  this.entityConfiguration,
83
81
  this.entityClass,
84
82
  this.privacyPolicy,
@@ -98,13 +96,11 @@ export default class EntityMutatorFactory<
98
96
  */
99
97
  forUpdate(
100
98
  existingEntity: TEntity,
101
- queryContext: EntityQueryContext,
102
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
99
+ queryContext: EntityQueryContext
103
100
  ): UpdateMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
104
101
  return new UpdateMutator(
105
102
  existingEntity.getViewerContext(),
106
103
  queryContext,
107
- privacyPolicyEvaluationContext,
108
104
  this.entityConfiguration,
109
105
  this.entityClass,
110
106
  this.privacyPolicy,
@@ -124,13 +120,11 @@ export default class EntityMutatorFactory<
124
120
  */
125
121
  forDelete(
126
122
  existingEntity: TEntity,
127
- queryContext: EntityQueryContext,
128
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
123
+ queryContext: EntityQueryContext
129
124
  ): DeleteMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
130
125
  return new DeleteMutator(
131
126
  existingEntity.getViewerContext(),
132
127
  queryContext,
133
- privacyPolicyEvaluationContext,
134
128
  this.entityConfiguration,
135
129
  this.entityClass,
136
130
  this.privacyPolicy,
@@ -19,9 +19,24 @@ export type EntityPrivacyPolicyEvaluationContext = {
19
19
  cascadingDeleteCause: EntityCascadingDeletionInfo | null;
20
20
  };
21
21
 
22
+ /**
23
+ * Evaluation mode for a {@link EntityPrivacyPolicy}. Useful when transitioning to
24
+ * using Entity for privacy.
25
+ */
22
26
  export enum EntityPrivacyPolicyEvaluationMode {
27
+ /**
28
+ * Enforce this privacy policy. Throw upon denial.
29
+ */
23
30
  ENFORCE,
31
+
32
+ /**
33
+ * Do not enforce this privacy policy. Always allow but log when it would have denied.
34
+ */
24
35
  DRY_RUN,
36
+
37
+ /**
38
+ * Enforce this privacy policy. Throw and log upon denial.
39
+ */
25
40
  ENFORCE_AND_LOG,
26
41
  }
27
42
 
@@ -29,6 +29,12 @@ export abstract class EntityQueryContext {
29
29
  ): Promise<T>;
30
30
  }
31
31
 
32
+ /**
33
+ * Entity framework representation of a non-transactional query execution unit.
34
+ * When supplied to {@link EntityMutator} and {@link EntityLoader} methods, they will be
35
+ * run independently of any running transaction (though mutations start their own
36
+ * independent transactions internally when not being run in a transaction).
37
+ */
32
38
  export class EntityNonTransactionalQueryContext extends EntityQueryContext {
33
39
  constructor(
34
40
  queryInterface: any,
@@ -48,6 +54,11 @@ export class EntityNonTransactionalQueryContext extends EntityQueryContext {
48
54
  }
49
55
  }
50
56
 
57
+ /**
58
+ * Entity framework representation of a transactional query execution unit. When supplied
59
+ * to {@link EntityMutator} and {@link EntityLoader} methods, those methods and their
60
+ * dependent triggers and validators will run within the transaction.
61
+ */
51
62
  export class EntityTransactionalQueryContext extends EntityQueryContext {
52
63
  private readonly postCommitInvalidationCallbacks: PostCommitCallback[] = [];
53
64
  private readonly postCommitCallbacks: PostCommitCallback[] = [];
@@ -1,6 +1,6 @@
1
1
  import { CreateMutator, UpdateMutator, DeleteMutator } from './EntityMutator';
2
2
  import EntityMutatorFactory from './EntityMutatorFactory';
3
- import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
3
+ import EntityPrivacyPolicy from './EntityPrivacyPolicy';
4
4
  import { EntityQueryContext } from './EntityQueryContext';
5
5
  import ReadonlyEntity from './ReadonlyEntity';
6
6
  import ViewerContext from './ViewerContext';
@@ -35,37 +35,22 @@ export default class ViewerScopedEntityMutatorFactory<
35
35
  ) {}
36
36
 
37
37
  forCreate(
38
- queryContext: EntityQueryContext,
39
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
38
+ queryContext: EntityQueryContext
40
39
  ): CreateMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
41
- return this.entityMutatorFactory.forCreate(
42
- this.viewerContext,
43
- queryContext,
44
- privacyPolicyEvaluationContext
45
- );
40
+ return this.entityMutatorFactory.forCreate(this.viewerContext, queryContext);
46
41
  }
47
42
 
48
43
  forUpdate(
49
44
  existingEntity: TEntity,
50
- queryContext: EntityQueryContext,
51
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
45
+ queryContext: EntityQueryContext
52
46
  ): UpdateMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
53
- return this.entityMutatorFactory.forUpdate(
54
- existingEntity,
55
- queryContext,
56
- privacyPolicyEvaluationContext
57
- );
47
+ return this.entityMutatorFactory.forUpdate(existingEntity, queryContext);
58
48
  }
59
49
 
60
50
  forDelete(
61
51
  existingEntity: TEntity,
62
- queryContext: EntityQueryContext,
63
- privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
52
+ queryContext: EntityQueryContext
64
53
  ): DeleteMutator<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
65
- return this.entityMutatorFactory.forDelete(
66
- existingEntity,
67
- queryContext,
68
- privacyPolicyEvaluationContext
69
- );
54
+ return this.entityMutatorFactory.forDelete(existingEntity, queryContext);
70
55
  }
71
56
  }
@@ -95,7 +95,7 @@ class TestLocalCacheAdapter<TFields> extends EntityCacheAdapter<TFields> {
95
95
  fieldValue: NonNullable<TFields[N]>
96
96
  ): string {
97
97
  const columnName = this.entityConfiguration.entityToDBFieldsKeyMapping.get(fieldName);
98
- invariant(columnName, `database field mapping missing for ${fieldName}`);
98
+ invariant(columnName, `database field mapping missing for ${String(fieldName)}`);
99
99
  const parts = [
100
100
  this.entityConfiguration.tableName,
101
101
  `${this.entityConfiguration.cacheKeyVersion}`,
@@ -162,11 +162,11 @@ describe(ComposedEntityCacheAdapter, () => {
162
162
  });
163
163
 
164
164
  it('returns fallback adapter results primary is empty', async () => {
165
- const { primaryCacheAdapter, cacheAdapter } = makeTestCacheAdapters();
165
+ const { fallbackCacheAdapter, cacheAdapter } = makeTestCacheAdapters();
166
166
 
167
167
  const cacheHits = new Map<string, Readonly<BlahFields>>([['test-id-1', { id: 'test-id-1' }]]);
168
- await primaryCacheAdapter.cacheManyAsync('id', cacheHits);
169
- await primaryCacheAdapter.cacheDBMissesAsync('id', ['test-id-2']);
168
+ await fallbackCacheAdapter.cacheManyAsync('id', cacheHits);
169
+ await fallbackCacheAdapter.cacheDBMissesAsync('id', ['test-id-2']);
170
170
 
171
171
  const results = await cacheAdapter.loadManyAsync('id', [
172
172
  'test-id-1',
@@ -183,6 +183,45 @@ describe(ComposedEntityCacheAdapter, () => {
183
183
  expect(results.size).toBe(3);
184
184
  });
185
185
 
186
+ it('populates primary adapter with fallback adapter results', async () => {
187
+ const { primaryCacheAdapter, fallbackCacheAdapter, cacheAdapter } = makeTestCacheAdapters();
188
+
189
+ const cacheHits = new Map<string, Readonly<BlahFields>>([['test-id-1', { id: 'test-id-1' }]]);
190
+ await fallbackCacheAdapter.cacheManyAsync('id', cacheHits);
191
+ await fallbackCacheAdapter.cacheDBMissesAsync('id', ['test-id-2']);
192
+
193
+ // should populate primary cache with fallback cache results
194
+ await cacheAdapter.loadManyAsync('id', ['test-id-1', 'test-id-2', 'test-id-3']);
195
+
196
+ const primaryResults = await primaryCacheAdapter.loadManyAsync('id', [
197
+ 'test-id-1',
198
+ 'test-id-2',
199
+ 'test-id-3',
200
+ ]);
201
+
202
+ expect(primaryResults.get('test-id-1')).toMatchObject({
203
+ status: CacheStatus.HIT,
204
+ item: { id: 'test-id-1' },
205
+ });
206
+ expect(primaryResults.get('test-id-2')).toMatchObject({ status: CacheStatus.NEGATIVE });
207
+ expect(primaryResults.get('test-id-3')).toMatchObject({ status: CacheStatus.MISS });
208
+ expect(primaryResults.size).toBe(3);
209
+
210
+ // ensure that populating the primary cache doesn't change the output
211
+ const composedResults = await cacheAdapter.loadManyAsync('id', [
212
+ 'test-id-1',
213
+ 'test-id-2',
214
+ 'test-id-3',
215
+ ]);
216
+ expect(composedResults.get('test-id-1')).toMatchObject({
217
+ status: CacheStatus.HIT,
218
+ item: { id: 'test-id-1' },
219
+ });
220
+ expect(composedResults.get('test-id-2')).toMatchObject({ status: CacheStatus.NEGATIVE });
221
+ expect(composedResults.get('test-id-3')).toMatchObject({ status: CacheStatus.MISS });
222
+ expect(composedResults.size).toBe(3);
223
+ });
224
+
186
225
  it('returns empty map when passed empty array of fieldValues', async () => {
187
226
  const { cacheAdapter } = makeTestCacheAdapters();
188
227
  const results = await cacheAdapter.loadManyAsync('id', []);