@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.
- package/build/ComposedEntityCacheAdapter.d.ts +4 -2
- package/build/ComposedEntityCacheAdapter.js +39 -3
- package/build/ComposedEntityCacheAdapter.js.map +1 -1
- package/build/ComposedSecondaryEntityCache.d.ts +3 -2
- package/build/ComposedSecondaryEntityCache.js +3 -2
- package/build/ComposedSecondaryEntityCache.js.map +1 -1
- package/build/Entity.js +4 -4
- package/build/Entity.js.map +1 -1
- package/build/EntityAssociationLoader.d.ts +5 -0
- package/build/EntityAssociationLoader.js +5 -0
- package/build/EntityAssociationLoader.js.map +1 -1
- package/build/EntityFieldDefinition.d.ts +16 -8
- package/build/EntityFieldDefinition.js +12 -5
- package/build/EntityFieldDefinition.js.map +1 -1
- package/build/EntityLoader.js +2 -2
- package/build/EntityLoader.js.map +1 -1
- package/build/EntityMutationInfo.d.ts +2 -0
- package/build/EntityMutator.d.ts +6 -8
- package/build/EntityMutator.js +58 -46
- package/build/EntityMutator.js.map +1 -1
- package/build/EntityMutatorFactory.d.ts +4 -4
- package/build/EntityMutatorFactory.js +6 -6
- package/build/EntityMutatorFactory.js.map +1 -1
- package/build/EntityPrivacyPolicy.d.ts +13 -0
- package/build/EntityPrivacyPolicy.js +13 -0
- package/build/EntityPrivacyPolicy.js.map +1 -1
- package/build/EntityQueryContext.d.ts +11 -0
- package/build/EntityQueryContext.js +11 -0
- package/build/EntityQueryContext.js.map +1 -1
- package/build/ViewerScopedEntityMutatorFactory.d.ts +4 -4
- package/build/ViewerScopedEntityMutatorFactory.js +6 -6
- package/build/ViewerScopedEntityMutatorFactory.js.map +1 -1
- package/build/__tests__/ComposedCacheAdapter-test.js +37 -4
- package/build/__tests__/ComposedCacheAdapter-test.js.map +1 -1
- package/build/__tests__/EntityCommonUseCases-test.js +5 -1
- package/build/__tests__/EntityCommonUseCases-test.js.map +1 -1
- package/build/__tests__/EntityCompanion-test.js +5 -1
- package/build/__tests__/EntityCompanion-test.js.map +1 -1
- package/build/__tests__/EntityCompanionProvider-test.js +5 -1
- package/build/__tests__/EntityCompanionProvider-test.js.map +1 -1
- package/build/__tests__/EntityEdges-test.js +199 -33
- package/build/__tests__/EntityEdges-test.js.map +1 -1
- package/build/__tests__/EntityLoader-test.js +5 -1
- package/build/__tests__/EntityLoader-test.js.map +1 -1
- package/build/__tests__/EntityMutator-test.js +38 -53
- package/build/__tests__/EntityMutator-test.js.map +1 -1
- package/build/__tests__/EntityPrivacyPolicy-test.js +5 -1
- package/build/__tests__/EntityPrivacyPolicy-test.js.map +1 -1
- package/build/__tests__/EntitySelfReferentialEdges-test.js +2 -2
- package/build/__tests__/EntitySelfReferentialEdges-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js +5 -1
- package/build/__tests__/ViewerScopedEntityCompanionProvider-test.js.map +1 -1
- package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js +2 -3
- package/build/__tests__/ViewerScopedEntityMutatorFactory-test.js.map +1 -1
- package/build/errors/EntityCacheAdapterError.js +5 -1
- package/build/errors/EntityCacheAdapterError.js.map +1 -1
- package/build/errors/EntityDatabaseAdapterError.js +5 -1
- package/build/errors/EntityDatabaseAdapterError.js.map +1 -1
- package/build/errors/EntityInvalidFieldValueError.js +6 -2
- package/build/errors/EntityInvalidFieldValueError.js.map +1 -1
- package/build/errors/EntityNotAuthorizedError.js +5 -1
- package/build/errors/EntityNotAuthorizedError.js.map +1 -1
- package/build/errors/EntityNotFoundError.js +6 -2
- package/build/errors/EntityNotFoundError.js.map +1 -1
- package/build/index.js +5 -1
- package/build/index.js.map +1 -1
- package/build/internal/EntityDataManager.js +7 -4
- package/build/internal/EntityDataManager.js.map +1 -1
- package/build/internal/EntityFieldTransformationUtils.js +1 -1
- package/build/internal/EntityFieldTransformationUtils.js.map +1 -1
- package/build/internal/ReadThroughEntityCache.js +1 -1
- package/build/internal/ReadThroughEntityCache.js.map +1 -1
- package/build/internal/__tests__/EntityDataManager-test.js +12 -7
- package/build/internal/__tests__/EntityDataManager-test.js.map +1 -1
- package/build/internal/__tests__/ReadThroughEntityCache-test.js +5 -1
- package/build/internal/__tests__/ReadThroughEntityCache-test.js.map +1 -1
- package/build/metrics/IEntityMetricsAdapter.d.ts +62 -17
- package/build/metrics/IEntityMetricsAdapter.js +17 -1
- package/build/metrics/IEntityMetricsAdapter.js.map +1 -1
- package/build/metrics/NoOpEntityMetricsAdapter.d.ts +1 -3
- package/build/metrics/NoOpEntityMetricsAdapter.js +1 -3
- package/build/metrics/NoOpEntityMetricsAdapter.js.map +1 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js +5 -1
- package/build/rules/AlwaysAllowPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js +5 -1
- package/build/rules/AlwaysDenyPrivacyPolicyRule.js.map +1 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js +5 -1
- package/build/rules/AlwaysSkipPrivacyPolicyRule.js.map +1 -1
- package/build/utils/testing/StubDatabaseAdapter.js +6 -2
- package/build/utils/testing/StubDatabaseAdapter.js.map +1 -1
- package/package.json +1 -1
- package/src/ComposedEntityCacheAdapter.ts +44 -3
- package/src/ComposedSecondaryEntityCache.ts +3 -2
- package/src/Entity.ts +4 -4
- package/src/EntityAssociationLoader.ts +5 -0
- package/src/EntityFieldDefinition.ts +15 -6
- package/src/EntityLoader.ts +4 -2
- package/src/EntityMutationInfo.ts +2 -0
- package/src/EntityMutator.ts +98 -67
- package/src/EntityMutatorFactory.ts +4 -10
- package/src/EntityPrivacyPolicy.ts +15 -0
- package/src/EntityQueryContext.ts +11 -0
- package/src/ViewerScopedEntityMutatorFactory.ts +7 -22
- package/src/__tests__/ComposedCacheAdapter-test.ts +43 -4
- package/src/__tests__/EntityEdges-test.ts +287 -32
- package/src/__tests__/EntityMutator-test.ts +33 -54
- package/src/__tests__/EntitySelfReferentialEdges-test.ts +2 -2
- package/src/__tests__/ViewerScopedEntityMutatorFactory-test.ts +2 -6
- package/src/errors/EntityInvalidFieldValueError.ts +1 -1
- package/src/errors/EntityNotFoundError.ts +1 -1
- package/src/internal/EntityDataManager.ts +13 -5
- package/src/internal/EntityFieldTransformationUtils.ts +1 -1
- package/src/internal/ReadThroughEntityCache.ts +3 -1
- package/src/internal/__tests__/EntityDataManager-test.ts +11 -8
- package/src/metrics/IEntityMetricsAdapter.ts +72 -19
- package/src/metrics/NoOpEntityMetricsAdapter.ts +1 -5
- package/src/utils/testing/StubDatabaseAdapter.ts +4 -1
package/src/EntityMutator.ts
CHANGED
|
@@ -18,7 +18,7 @@ import EntityMutationTriggerConfiguration, {
|
|
|
18
18
|
EntityNonTransactionalMutationTrigger,
|
|
19
19
|
} from './EntityMutationTriggerConfiguration';
|
|
20
20
|
import EntityMutationValidator from './EntityMutationValidator';
|
|
21
|
-
import 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
|
|
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
|
-
|
|
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
|
-
|
|
256
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
457
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
{
|
|
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
|
|
776
|
-
|
|
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
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
169
|
-
await
|
|
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', []);
|