@expo/entity 0.37.0 → 0.38.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.
@@ -31,6 +31,33 @@ export declare enum EntityEdgeDeletionBehavior {
31
31
  */
32
32
  SET_NULL = 3
33
33
  }
34
+ export declare enum EntityEdgeDeletionAuthorizationInferenceBehavior {
35
+ /**
36
+ * Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
37
+ * (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
38
+ * cannot be inferred from authorization of any single entity at the end of an edge of this type.
39
+ *
40
+ * To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must be called on all
41
+ * entities at the ends of all edges of this type.
42
+ */
43
+ NONE = 0,
44
+ /**
45
+ * Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
46
+ * (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
47
+ * may be inferred from authorization of any single entity at the end of an edge of this type.
48
+ *
49
+ * To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must only be called on
50
+ * a single entity at the end of one edge of this type chosen at random.
51
+ *
52
+ * This should only be the case when the entity at the other end of this edge can be implicitly
53
+ * deleted/updated by virtue of the source entity deletion being authorized and a single authorization check
54
+ * on one edge of this type.
55
+ *
56
+ * Note that this is not used during actual deletions, only as an optimistic optimization during execution
57
+ * of canViewerDeleteAsync. Each entity being deleted will still check deletion privacy during actual deletion.
58
+ */
59
+ ONE_IMPLIES_ALL = 1
60
+ }
34
61
  /**
35
62
  * Defines an association between entities. An association is primarily used to define cascading deletion behavior.
36
63
  */
@@ -45,7 +72,7 @@ export interface EntityAssociationDefinition<TViewerContext extends ViewerContex
45
72
  */
46
73
  associatedEntityLookupByField?: keyof TAssociatedFields;
47
74
  /**
48
- * What action to perform on the current entity when the entity on the referencing end of
75
+ * What action to perform on the entity at the other end of this edge when the entity on the source end of
49
76
  * this edge is deleted.
50
77
  *
51
78
  * @remarks
@@ -61,6 +88,13 @@ export interface EntityAssociationDefinition<TViewerContext extends ViewerContex
61
88
  * integrity is recommended.
62
89
  */
63
90
  edgeDeletionBehavior: EntityEdgeDeletionBehavior;
91
+ /**
92
+ * Optimization setting for evaluation of this edge in canViewerDeleteAsync. Can be used to optimize permission checks
93
+ * for edge cascading deletions based on application-specific design of the entity association. Not used during actual deletions.
94
+ *
95
+ * Defaults to EntityEdgeDeletionAuthorizationInferenceBehavior.NONE.
96
+ */
97
+ edgeDeletionAuthorizationInferenceBehavior?: EntityEdgeDeletionAuthorizationInferenceBehavior;
64
98
  }
65
99
  /**
66
100
  * Options for EntityFieldDefinition
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EntityFieldDefinition = exports.EntityEdgeDeletionBehavior = void 0;
3
+ exports.EntityFieldDefinition = exports.EntityEdgeDeletionAuthorizationInferenceBehavior = exports.EntityEdgeDeletionBehavior = void 0;
4
4
  var EntityEdgeDeletionBehavior;
5
5
  (function (EntityEdgeDeletionBehavior) {
6
6
  /**
@@ -31,6 +31,34 @@ var EntityEdgeDeletionBehavior;
31
31
  */
32
32
  EntityEdgeDeletionBehavior[EntityEdgeDeletionBehavior["SET_NULL"] = 3] = "SET_NULL";
33
33
  })(EntityEdgeDeletionBehavior || (exports.EntityEdgeDeletionBehavior = EntityEdgeDeletionBehavior = {}));
34
+ var EntityEdgeDeletionAuthorizationInferenceBehavior;
35
+ (function (EntityEdgeDeletionAuthorizationInferenceBehavior) {
36
+ /**
37
+ * Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
38
+ * (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
39
+ * cannot be inferred from authorization of any single entity at the end of an edge of this type.
40
+ *
41
+ * To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must be called on all
42
+ * entities at the ends of all edges of this type.
43
+ */
44
+ EntityEdgeDeletionAuthorizationInferenceBehavior[EntityEdgeDeletionAuthorizationInferenceBehavior["NONE"] = 0] = "NONE";
45
+ /**
46
+ * Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
47
+ * (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
48
+ * may be inferred from authorization of any single entity at the end of an edge of this type.
49
+ *
50
+ * To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must only be called on
51
+ * a single entity at the end of one edge of this type chosen at random.
52
+ *
53
+ * This should only be the case when the entity at the other end of this edge can be implicitly
54
+ * deleted/updated by virtue of the source entity deletion being authorized and a single authorization check
55
+ * on one edge of this type.
56
+ *
57
+ * Note that this is not used during actual deletions, only as an optimistic optimization during execution
58
+ * of canViewerDeleteAsync. Each entity being deleted will still check deletion privacy during actual deletion.
59
+ */
60
+ EntityEdgeDeletionAuthorizationInferenceBehavior[EntityEdgeDeletionAuthorizationInferenceBehavior["ONE_IMPLIES_ALL"] = 1] = "ONE_IMPLIES_ALL";
61
+ })(EntityEdgeDeletionAuthorizationInferenceBehavior || (exports.EntityEdgeDeletionAuthorizationInferenceBehavior = EntityEdgeDeletionAuthorizationInferenceBehavior = {}));
34
62
  /**
35
63
  * Definition for a field referencing a column in the underlying database. Specifies things like
36
64
  * cache behavior and associations, and handles input validation.
@@ -1 +1 @@
1
- {"version":3,"file":"EntityFieldDefinition.js","sourceRoot":"","sources":["../src/EntityFieldDefinition.ts"],"names":[],"mappings":";;;AAKA,IAAY,0BA+BX;AA/BD,WAAY,0BAA0B;IACpC;;;;;;OAMG;IACH,2IAAoC,CAAA;IAEpC;;;;;OAKG;IACH,+HAA8B,CAAA;IAE9B;;;;OAIG;IACH,+FAAc,CAAA;IAEd;;;;OAIG;IACH,mFAAQ,CAAA;AACV,CAAC,EA/BW,0BAA0B,0CAA1B,0BAA0B,QA+BrC;AAmFD;;;GAGG;AACH,MAAsB,qBAAqB;IAChC,UAAU,CAAS;IACnB,KAAK,CAAU;IACf,WAAW,CAAwE;IAC5F;;;OAGG;IACH,YAAY,OAAqC;QAC/C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;QACpC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACI,kBAAkB,CAAC,KAA2B;QACnD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;CAEF;AA3BD,sDA2BC"}
1
+ {"version":3,"file":"EntityFieldDefinition.js","sourceRoot":"","sources":["../src/EntityFieldDefinition.ts"],"names":[],"mappings":";;;AAKA,IAAY,0BA+BX;AA/BD,WAAY,0BAA0B;IACpC;;;;;;OAMG;IACH,2IAAoC,CAAA;IAEpC;;;;;OAKG;IACH,+HAA8B,CAAA;IAE9B;;;;OAIG;IACH,+FAAc,CAAA;IAEd;;;;OAIG;IACH,mFAAQ,CAAA;AACV,CAAC,EA/BW,0BAA0B,0CAA1B,0BAA0B,QA+BrC;AAED,IAAY,gDA2BX;AA3BD,WAAY,gDAAgD;IAC1D;;;;;;;OAOG;IACH,uHAAI,CAAA;IAEJ;;;;;;;;;;;;;;OAcG;IACH,6IAAe,CAAA;AACjB,CAAC,EA3BW,gDAAgD,gEAAhD,gDAAgD,QA2B3D;AA2FD;;;GAGG;AACH,MAAsB,qBAAqB;IAChC,UAAU,CAAS;IACnB,KAAK,CAAU;IACf,WAAW,CAAwE;IAC5F;;;OAGG;IACH,YAAY,OAAqC;QAC/C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;QACpC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACI,kBAAkB,CAAC,KAA2B;QACnD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;CAEF;AA3BD,sDA2BC"}
@@ -23,8 +23,8 @@ export declare class TestEntityPrivacyPolicy extends EntityPrivacyPolicy<TestFie
23
23
  export default class TestEntity extends Entity<TestFields, string, ViewerContext> {
24
24
  static defineCompanionDefinition(): EntityCompanionDefinition<TestFields, string, ViewerContext, TestEntity, TestEntityPrivacyPolicy>;
25
25
  getBlah(): string;
26
- static hello(viewerContext: ViewerContext, testValue: string): Promise<Result<TestEntity>>;
27
- static returnError(_viewerContext: ViewerContext): Promise<Result<TestEntity>>;
28
- static throwError(_viewerContext: ViewerContext): Promise<Result<TestEntity>>;
29
- static nonResult(_viewerContext: ViewerContext, testValue: string): Promise<string>;
26
+ static helloAsync(viewerContext: ViewerContext, testValue: string): Promise<Result<TestEntity>>;
27
+ static returnErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>>;
28
+ static throwErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>>;
29
+ static nonResultAsync(_viewerContext: ViewerContext, testValue: string): Promise<string>;
30
30
  }
@@ -63,7 +63,7 @@ class TestEntity extends Entity_1.default {
63
63
  getBlah() {
64
64
  return 'Hello World!';
65
65
  }
66
- static async hello(viewerContext, testValue) {
66
+ static async helloAsync(viewerContext, testValue) {
67
67
  const fields = {
68
68
  customIdField: testValue,
69
69
  testIndexedField: 'hello',
@@ -79,13 +79,13 @@ class TestEntity extends Entity_1.default {
79
79
  selectedFields: fields,
80
80
  }));
81
81
  }
82
- static async returnError(_viewerContext) {
82
+ static async returnErrorAsync(_viewerContext) {
83
83
  return (0, results_1.result)(new Error('return entity'));
84
84
  }
85
- static async throwError(_viewerContext) {
85
+ static async throwErrorAsync(_viewerContext) {
86
86
  throw new Error('threw entity');
87
87
  }
88
- static async nonResult(_viewerContext, testValue) {
88
+ static async nonResultAsync(_viewerContext, testValue) {
89
89
  return testValue;
90
90
  }
91
91
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TestEntity.js","sourceRoot":"","sources":["../../src/testfixtures/TestEntity.ts"],"names":[],"mappings":";;;;;;AAAA,2CAA+C;AAE/C,uDAA+B;AAE/B,iFAAyD;AACzD,kDAA8E;AAC9E,iFAAyD;AAEzD,yGAAiF;AAWpE,QAAA,uBAAuB,GAAG,IAAI,6BAAmB,CAAa;IACzE,OAAO,EAAE,eAAe;IACxB,SAAS,EAAE,oCAAoC;IAC/C,MAAM,EAAE;QACN,aAAa,EAAE,IAAI,wBAAS,CAAC;YAC3B,UAAU,EAAE,WAAW;SACxB,CAAC;QACF,gBAAgB,EAAE,IAAI,0BAAW,CAAC;YAChC,UAAU,EAAE,YAAY;YACxB,KAAK,EAAE,IAAI;SACZ,CAAC;QACF,WAAW,EAAE,IAAI,0BAAW,CAAC;YAC3B,UAAU,EAAE,cAAc;SAC3B,CAAC;QACF,QAAQ,EAAE,IAAI,uBAAQ,CAAC;YACrB,UAAU,EAAE,cAAc;SAC3B,CAAC;QACF,SAAS,EAAE,IAAI,wBAAS,CAAC;YACvB,UAAU,EAAE,YAAY;SACzB,CAAC;QACF,aAAa,EAAE,IAAI,0BAAW,CAAC;YAC7B,UAAU,EAAE,gBAAgB;SAC7B,CAAC;KACH;IACD,qBAAqB,EAAE,UAAU;IACjC,kBAAkB,EAAE,OAAO;CAC5B,CAAC,CAAC;AAEH,MAAa,uBAAwB,SAAQ,6BAK5C;IAC6B,SAAS,GAAG;QACtC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;CACH;AAlBD,0DAkBC;AAED,MAAqB,UAAW,SAAQ,gBAAyC;IAC/E,MAAM,CAAC,yBAAyB;QAO9B,OAAO;YACL,WAAW,EAAE,UAAU;YACvB,mBAAmB,EAAE,+BAAuB;YAC5C,kBAAkB,EAAE,uBAAuB;SAC5C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,aAA4B,EAAE,SAAiB;QAChE,MAAM,MAAM,GAAG;YACb,aAAa,EAAE,SAAS;YACxB,gBAAgB,EAAE,OAAO;YACzB,WAAW,EAAE,OAAO;YACpB,QAAQ,EAAE,CAAC;YACX,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,aAAa,EAAE,IAAI;SACpB,CAAC;QACF,OAAO,IAAA,gBAAM,EACX,IAAI,UAAU,CAAC;YACb,aAAa;YACb,EAAE,EAAE,SAAS;YACb,cAAc,EAAE,MAAM;YACtB,cAAc,EAAE,MAAM;SACvB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,cAA6B;QACpD,OAAO,IAAA,gBAAM,EAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,cAA6B;QACnD,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,cAA6B,EAAE,SAAiB;QACrE,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAjDD,6BAiDC"}
1
+ {"version":3,"file":"TestEntity.js","sourceRoot":"","sources":["../../src/testfixtures/TestEntity.ts"],"names":[],"mappings":";;;;;;AAAA,2CAA+C;AAE/C,uDAA+B;AAE/B,iFAAyD;AACzD,kDAA8E;AAC9E,iFAAyD;AAEzD,yGAAiF;AAWpE,QAAA,uBAAuB,GAAG,IAAI,6BAAmB,CAAa;IACzE,OAAO,EAAE,eAAe;IACxB,SAAS,EAAE,oCAAoC;IAC/C,MAAM,EAAE;QACN,aAAa,EAAE,IAAI,wBAAS,CAAC;YAC3B,UAAU,EAAE,WAAW;SACxB,CAAC;QACF,gBAAgB,EAAE,IAAI,0BAAW,CAAC;YAChC,UAAU,EAAE,YAAY;YACxB,KAAK,EAAE,IAAI;SACZ,CAAC;QACF,WAAW,EAAE,IAAI,0BAAW,CAAC;YAC3B,UAAU,EAAE,cAAc;SAC3B,CAAC;QACF,QAAQ,EAAE,IAAI,uBAAQ,CAAC;YACrB,UAAU,EAAE,cAAc;SAC3B,CAAC;QACF,SAAS,EAAE,IAAI,wBAAS,CAAC;YACvB,UAAU,EAAE,YAAY;SACzB,CAAC;QACF,aAAa,EAAE,IAAI,0BAAW,CAAC;YAC7B,UAAU,EAAE,gBAAgB;SAC7B,CAAC;KACH;IACD,qBAAqB,EAAE,UAAU;IACjC,kBAAkB,EAAE,OAAO;CAC5B,CAAC,CAAC;AAEH,MAAa,uBAAwB,SAAQ,6BAK5C;IAC6B,SAAS,GAAG;QACtC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;CACH;AAlBD,0DAkBC;AAED,MAAqB,UAAW,SAAQ,gBAAyC;IAC/E,MAAM,CAAC,yBAAyB;QAO9B,OAAO;YACL,WAAW,EAAE,UAAU;YACvB,mBAAmB,EAAE,+BAAuB;YAC5C,kBAAkB,EAAE,uBAAuB;SAC5C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,UAAU,CACrB,aAA4B,EAC5B,SAAiB;QAEjB,MAAM,MAAM,GAAG;YACb,aAAa,EAAE,SAAS;YACxB,gBAAgB,EAAE,OAAO;YACzB,WAAW,EAAE,OAAO;YACpB,QAAQ,EAAE,CAAC;YACX,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,aAAa,EAAE,IAAI;SACpB,CAAC;QACF,OAAO,IAAA,gBAAM,EACX,IAAI,UAAU,CAAC;YACb,aAAa;YACb,EAAE,EAAE,SAAS;YACb,cAAc,EAAE,MAAM;YACtB,cAAc,EAAE,MAAM;SACvB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,cAA6B;QACzD,OAAO,IAAA,gBAAM,EAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,cAA6B;QACxD,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,cAA6B,EAAE,SAAiB;QAC1E,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AApDD,6BAoDC"}
@@ -119,12 +119,33 @@ async function canViewerDeleteInternalAsync(entityClass, sourceEntity, cascading
119
119
  if (associatedConfiguration !== entityConfiguration) {
120
120
  continue;
121
121
  }
122
- const entityResultsForInboundEdge = await loader
123
- .withAuthorizationResults()
124
- .loadManyByFieldEqualingAsync(fieldName, association.associatedEntityLookupByField
125
- ? sourceEntity.getField(association.associatedEntityLookupByField)
126
- : sourceEntity.getID());
127
- const failedEntityLoadResults = (0, entityUtils_1.failedResults)(entityResultsForInboundEdge);
122
+ const edgeDeletionPermissionInferenceBehavior = association.edgeDeletionAuthorizationInferenceBehavior;
123
+ let entityResultsToCheckForInboundEdge;
124
+ if (edgeDeletionPermissionInferenceBehavior ===
125
+ EntityFieldDefinition_1.EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL) {
126
+ const singleEntityToTestForInboundEdge = await loader
127
+ .withAuthorizationResults()
128
+ .loadFirstByFieldEqualityConjunctionAsync([
129
+ {
130
+ fieldName,
131
+ fieldValue: association.associatedEntityLookupByField
132
+ ? sourceEntity.getField(association.associatedEntityLookupByField)
133
+ : sourceEntity.getID(),
134
+ },
135
+ ], { orderBy: [] });
136
+ entityResultsToCheckForInboundEdge = singleEntityToTestForInboundEdge
137
+ ? [singleEntityToTestForInboundEdge]
138
+ : [];
139
+ }
140
+ else {
141
+ const entityResultsForInboundEdge = await loader
142
+ .withAuthorizationResults()
143
+ .loadManyByFieldEqualingAsync(fieldName, association.associatedEntityLookupByField
144
+ ? sourceEntity.getField(association.associatedEntityLookupByField)
145
+ : sourceEntity.getID());
146
+ entityResultsToCheckForInboundEdge = entityResultsForInboundEdge;
147
+ }
148
+ const failedEntityLoadResults = (0, entityUtils_1.failedResults)(entityResultsToCheckForInboundEdge);
128
149
  for (const failedResult of failedEntityLoadResults) {
129
150
  if (failedResult.reason instanceof EntityNotAuthorizedError_1.default) {
130
151
  return false;
@@ -134,7 +155,7 @@ async function canViewerDeleteInternalAsync(entityClass, sourceEntity, cascading
134
155
  }
135
156
  }
136
157
  // all results should be success at this point due to check above
137
- const entitiesForInboundEdge = entityResultsForInboundEdge.map((r) => r.enforceValue());
158
+ const entitiesForInboundEdge = entityResultsToCheckForInboundEdge.map((r) => r.enforceValue());
138
159
  switch (association.edgeDeletionBehavior) {
139
160
  case EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE:
140
161
  case EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY: {
@@ -1 +1 @@
1
- {"version":3,"file":"EntityPrivacyUtils.js","sourceRoot":"","sources":["../../src/utils/EntityPrivacyUtils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityPrivacyUtils.js","sourceRoot":"","sources":["../../src/utils/EntityPrivacyUtils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const Entity_1 = __importDefault(require("../../Entity"));
7
+ const EntityConfiguration_1 = __importDefault(require("../../EntityConfiguration"));
8
+ const EntityFieldDefinition_1 = require("../../EntityFieldDefinition");
9
+ const EntityFields_1 = require("../../EntityFields");
10
+ const EntityPrivacyPolicy_1 = __importDefault(require("../../EntityPrivacyPolicy"));
11
+ const ViewerContext_1 = __importDefault(require("../../ViewerContext"));
12
+ const AlwaysAllowPrivacyPolicyRule_1 = __importDefault(require("../../rules/AlwaysAllowPrivacyPolicyRule"));
13
+ const AlwaysDenyPrivacyPolicyRule_1 = __importDefault(require("../../rules/AlwaysDenyPrivacyPolicyRule"));
14
+ const EntityPrivacyUtils_1 = require("../EntityPrivacyUtils");
15
+ const createUnitTestEntityCompanionProvider_1 = require("../testing/createUnitTestEntityCompanionProvider");
16
+ describe(EntityPrivacyUtils_1.canViewerDeleteAsync, () => {
17
+ describe('edgeDeletionPermissionInferenceBehavior', () => {
18
+ it('optimizes when EntityEdgeDeletionPermissionInferenceBehavior.ONE_IMPLIES_ALL', async () => {
19
+ const companionProvider = (0, createUnitTestEntityCompanionProvider_1.createUnitTestEntityCompanionProvider)();
20
+ const viewerContext = new ViewerContext_1.default(companionProvider);
21
+ // create root
22
+ const testEntity = await TestEntity.creator(viewerContext).enforceCreateAsync();
23
+ // create a bunch of leaves referencing root with
24
+ // edgeDeletionPermissionInferenceBehavior = EntityEdgeDeletionPermissionInferenceBehavior.ONE_IMPLIES_ALL
25
+ for (let i = 0; i < 10; i++) {
26
+ await TestLeafEntity.creator(viewerContext)
27
+ .setField('test_entity_id', testEntity.getID())
28
+ .enforceCreateAsync();
29
+ }
30
+ for (let i = 0; i < 10; i++) {
31
+ await TestLeafLookupByFieldEntity.creator(viewerContext)
32
+ .setField('test_entity_id', testEntity.getID())
33
+ .enforceCreateAsync();
34
+ }
35
+ const testLeafEntityCompanion = viewerContext.getViewerScopedEntityCompanionForClass(TestLeafEntity);
36
+ const testLeafEntityAuthorizeDeleteSpy = jest.spyOn(testLeafEntityCompanion.entityCompanion.privacyPolicy, 'authorizeDeleteAsync');
37
+ const testLeafLookupByFieldEntityCompanion = viewerContext.getViewerScopedEntityCompanionForClass(TestLeafLookupByFieldEntity);
38
+ const testLeafLookupByFieldEntityAuthorizeDeleteSpy = jest.spyOn(testLeafLookupByFieldEntityCompanion.entityCompanion.privacyPolicy, 'authorizeDeleteAsync');
39
+ const canViewerDelete = await (0, EntityPrivacyUtils_1.canViewerDeleteAsync)(TestEntity, testEntity);
40
+ expect(canViewerDelete).toBe(true);
41
+ expect(testLeafEntityAuthorizeDeleteSpy).toHaveBeenCalledTimes(1);
42
+ expect(testLeafLookupByFieldEntityAuthorizeDeleteSpy).toHaveBeenCalledTimes(1);
43
+ });
44
+ it('does not optimize when undefined', async () => {
45
+ const companionProvider = (0, createUnitTestEntityCompanionProvider_1.createUnitTestEntityCompanionProvider)();
46
+ const viewerContext = new ViewerContext_1.default(companionProvider);
47
+ // create root
48
+ const testEntity = await TestEntity.creator(viewerContext).enforceCreateAsync();
49
+ // create a bunch of leaves with no edgeDeletionPermissionInferenceBehavior
50
+ for (let i = 0; i < 10; i++) {
51
+ await TestLeafNoInferenceEntity.creator(viewerContext)
52
+ .setField('test_entity_id', testEntity.getID())
53
+ .enforceCreateAsync();
54
+ }
55
+ const companion = viewerContext.getViewerScopedEntityCompanionForClass(TestLeafNoInferenceEntity);
56
+ const authorizeDeleteSpy = jest.spyOn(companion.entityCompanion.privacyPolicy, 'authorizeDeleteAsync');
57
+ const canViewerDelete = await (0, EntityPrivacyUtils_1.canViewerDeleteAsync)(TestEntity, testEntity);
58
+ expect(canViewerDelete).toBe(true);
59
+ expect(authorizeDeleteSpy).toHaveBeenCalledTimes(10);
60
+ });
61
+ });
62
+ });
63
+ class AlwaysAllowEntityPrivacyPolicy extends EntityPrivacyPolicy_1.default {
64
+ readRules = [
65
+ new AlwaysAllowPrivacyPolicyRule_1.default(),
66
+ ];
67
+ createRules = [
68
+ new AlwaysAllowPrivacyPolicyRule_1.default(),
69
+ ];
70
+ updateRules = [
71
+ new AlwaysDenyPrivacyPolicyRule_1.default(),
72
+ ];
73
+ deleteRules = [
74
+ new AlwaysAllowPrivacyPolicyRule_1.default(),
75
+ ];
76
+ }
77
+ class TestEntity extends Entity_1.default {
78
+ static defineCompanionDefinition() {
79
+ return {
80
+ entityClass: TestEntity,
81
+ entityConfiguration: new EntityConfiguration_1.default({
82
+ idField: 'id',
83
+ tableName: 'blah',
84
+ inboundEdges: [TestLeafEntity, TestLeafLookupByFieldEntity, TestLeafNoInferenceEntity],
85
+ schema: {
86
+ id: new EntityFields_1.UUIDField({
87
+ columnName: 'custom_id',
88
+ }),
89
+ },
90
+ databaseAdapterFlavor: 'postgres',
91
+ cacheAdapterFlavor: 'redis',
92
+ }),
93
+ privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
94
+ };
95
+ }
96
+ }
97
+ class TestLeafEntity extends Entity_1.default {
98
+ static defineCompanionDefinition() {
99
+ return {
100
+ entityClass: TestLeafEntity,
101
+ entityConfiguration: new EntityConfiguration_1.default({
102
+ idField: 'id',
103
+ tableName: 'blah_2',
104
+ schema: {
105
+ id: new EntityFields_1.UUIDField({
106
+ columnName: 'custom_id',
107
+ }),
108
+ test_entity_id: new EntityFields_1.UUIDField({
109
+ columnName: 'test_entity_id',
110
+ association: {
111
+ associatedEntityClass: TestEntity,
112
+ edgeDeletionBehavior: EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE,
113
+ edgeDeletionAuthorizationInferenceBehavior: EntityFieldDefinition_1.EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL,
114
+ },
115
+ }),
116
+ },
117
+ databaseAdapterFlavor: 'postgres',
118
+ cacheAdapterFlavor: 'redis',
119
+ }),
120
+ privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
121
+ };
122
+ }
123
+ }
124
+ class TestLeafLookupByFieldEntity extends Entity_1.default {
125
+ static defineCompanionDefinition() {
126
+ return {
127
+ entityClass: TestLeafEntity,
128
+ entityConfiguration: new EntityConfiguration_1.default({
129
+ idField: 'id',
130
+ tableName: 'blah_4',
131
+ schema: {
132
+ id: new EntityFields_1.UUIDField({
133
+ columnName: 'custom_id',
134
+ }),
135
+ test_entity_id: new EntityFields_1.UUIDField({
136
+ columnName: 'test_entity_id',
137
+ association: {
138
+ associatedEntityClass: TestEntity,
139
+ edgeDeletionBehavior: EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE,
140
+ associatedEntityLookupByField: 'id',
141
+ edgeDeletionAuthorizationInferenceBehavior: EntityFieldDefinition_1.EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL,
142
+ },
143
+ }),
144
+ },
145
+ databaseAdapterFlavor: 'postgres',
146
+ cacheAdapterFlavor: 'redis',
147
+ }),
148
+ privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
149
+ };
150
+ }
151
+ }
152
+ class TestLeafNoInferenceEntity extends Entity_1.default {
153
+ static defineCompanionDefinition() {
154
+ return {
155
+ entityClass: TestLeafNoInferenceEntity,
156
+ entityConfiguration: new EntityConfiguration_1.default({
157
+ idField: 'id',
158
+ tableName: 'blah_3',
159
+ schema: {
160
+ id: new EntityFields_1.UUIDField({
161
+ columnName: 'custom_id',
162
+ }),
163
+ test_entity_id: new EntityFields_1.UUIDField({
164
+ columnName: 'test_entity_id',
165
+ association: {
166
+ associatedEntityClass: TestEntity,
167
+ edgeDeletionBehavior: EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE,
168
+ },
169
+ }),
170
+ },
171
+ databaseAdapterFlavor: 'postgres',
172
+ cacheAdapterFlavor: 'redis',
173
+ }),
174
+ privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
175
+ };
176
+ }
177
+ }
178
+ //# sourceMappingURL=canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/entity",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "A privacy-first data model",
5
5
  "files": [
6
6
  "build",
@@ -34,5 +34,5 @@
34
34
  "uuid": "^8.3.0",
35
35
  "uuidv7": "^1.0.0"
36
36
  },
37
- "gitHead": "59fc77810ad169b1820f5c58a462d996f375e0cc"
37
+ "gitHead": "d7cc0c23b983eccca9ca4e7ca620a5aaef6846c4"
38
38
  }
@@ -36,6 +36,35 @@ export enum EntityEdgeDeletionBehavior {
36
36
  SET_NULL,
37
37
  }
38
38
 
39
+ export enum EntityEdgeDeletionAuthorizationInferenceBehavior {
40
+ /**
41
+ * Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
42
+ * (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
43
+ * cannot be inferred from authorization of any single entity at the end of an edge of this type.
44
+ *
45
+ * To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must be called on all
46
+ * entities at the ends of all edges of this type.
47
+ */
48
+ NONE,
49
+
50
+ /**
51
+ * Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
52
+ * (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
53
+ * may be inferred from authorization of any single entity at the end of an edge of this type.
54
+ *
55
+ * To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must only be called on
56
+ * a single entity at the end of one edge of this type chosen at random.
57
+ *
58
+ * This should only be the case when the entity at the other end of this edge can be implicitly
59
+ * deleted/updated by virtue of the source entity deletion being authorized and a single authorization check
60
+ * on one edge of this type.
61
+ *
62
+ * Note that this is not used during actual deletions, only as an optimistic optimization during execution
63
+ * of canViewerDeleteAsync. Each entity being deleted will still check deletion privacy during actual deletion.
64
+ */
65
+ ONE_IMPLIES_ALL,
66
+ }
67
+
39
68
  /**
40
69
  * Defines an association between entities. An association is primarily used to define cascading deletion behavior.
41
70
  */
@@ -77,7 +106,7 @@ export interface EntityAssociationDefinition<
77
106
  associatedEntityLookupByField?: keyof TAssociatedFields;
78
107
 
79
108
  /**
80
- * What action to perform on the current entity when the entity on the referencing end of
109
+ * What action to perform on the entity at the other end of this edge when the entity on the source end of
81
110
  * this edge is deleted.
82
111
  *
83
112
  * @remarks
@@ -93,6 +122,14 @@ export interface EntityAssociationDefinition<
93
122
  * integrity is recommended.
94
123
  */
95
124
  edgeDeletionBehavior: EntityEdgeDeletionBehavior;
125
+
126
+ /**
127
+ * Optimization setting for evaluation of this edge in canViewerDeleteAsync. Can be used to optimize permission checks
128
+ * for edge cascading deletions based on application-specific design of the entity association. Not used during actual deletions.
129
+ *
130
+ * Defaults to EntityEdgeDeletionAuthorizationInferenceBehavior.NONE.
131
+ */
132
+ edgeDeletionAuthorizationInferenceBehavior?: EntityEdgeDeletionAuthorizationInferenceBehavior;
96
133
  }
97
134
 
98
135
  /**
@@ -84,7 +84,10 @@ export default class TestEntity extends Entity<TestFields, string, ViewerContext
84
84
  return 'Hello World!';
85
85
  }
86
86
 
87
- static async hello(viewerContext: ViewerContext, testValue: string): Promise<Result<TestEntity>> {
87
+ static async helloAsync(
88
+ viewerContext: ViewerContext,
89
+ testValue: string,
90
+ ): Promise<Result<TestEntity>> {
88
91
  const fields = {
89
92
  customIdField: testValue,
90
93
  testIndexedField: 'hello',
@@ -103,15 +106,15 @@ export default class TestEntity extends Entity<TestFields, string, ViewerContext
103
106
  );
104
107
  }
105
108
 
106
- static async returnError(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
109
+ static async returnErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
107
110
  return result(new Error('return entity'));
108
111
  }
109
112
 
110
- static async throwError(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
113
+ static async throwErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
111
114
  throw new Error('threw entity');
112
115
  }
113
116
 
114
- static async nonResult(_viewerContext: ViewerContext, testValue: string): Promise<string> {
117
+ static async nonResultAsync(_viewerContext: ViewerContext, testValue: string): Promise<string> {
115
118
  return testValue;
116
119
  }
117
120
  }
@@ -1,7 +1,10 @@
1
- import { asyncResult } from '@expo/results';
1
+ import { Result, asyncResult } from '@expo/results';
2
2
 
3
3
  import Entity, { IEntityClass } from '../Entity';
4
- import { EntityEdgeDeletionBehavior } from '../EntityFieldDefinition';
4
+ import {
5
+ EntityEdgeDeletionBehavior,
6
+ EntityEdgeDeletionAuthorizationInferenceBehavior,
7
+ } from '../EntityFieldDefinition';
5
8
  import { EntityCascadingDeletionInfo } from '../EntityMutationInfo';
6
9
  import EntityPrivacyPolicy from '../EntityPrivacyPolicy';
7
10
  import { EntityQueryContext } from '../EntityQueryContext';
@@ -254,16 +257,44 @@ async function canViewerDeleteInternalAsync<
254
257
  continue;
255
258
  }
256
259
 
257
- const entityResultsForInboundEdge = await loader
258
- .withAuthorizationResults()
259
- .loadManyByFieldEqualingAsync(
260
- fieldName,
261
- association.associatedEntityLookupByField
262
- ? sourceEntity.getField(association.associatedEntityLookupByField as any)
263
- : sourceEntity.getID(),
264
- );
260
+ const edgeDeletionPermissionInferenceBehavior =
261
+ association.edgeDeletionAuthorizationInferenceBehavior;
265
262
 
266
- const failedEntityLoadResults = failedResults(entityResultsForInboundEdge);
263
+ let entityResultsToCheckForInboundEdge: readonly Result<any>[];
264
+
265
+ if (
266
+ edgeDeletionPermissionInferenceBehavior ===
267
+ EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL
268
+ ) {
269
+ const singleEntityToTestForInboundEdge = await loader
270
+ .withAuthorizationResults()
271
+ .loadFirstByFieldEqualityConjunctionAsync(
272
+ [
273
+ {
274
+ fieldName,
275
+ fieldValue: association.associatedEntityLookupByField
276
+ ? sourceEntity.getField(association.associatedEntityLookupByField as any)
277
+ : sourceEntity.getID(),
278
+ },
279
+ ],
280
+ { orderBy: [] },
281
+ );
282
+ entityResultsToCheckForInboundEdge = singleEntityToTestForInboundEdge
283
+ ? [singleEntityToTestForInboundEdge]
284
+ : [];
285
+ } else {
286
+ const entityResultsForInboundEdge = await loader
287
+ .withAuthorizationResults()
288
+ .loadManyByFieldEqualingAsync(
289
+ fieldName,
290
+ association.associatedEntityLookupByField
291
+ ? sourceEntity.getField(association.associatedEntityLookupByField as any)
292
+ : sourceEntity.getID(),
293
+ );
294
+ entityResultsToCheckForInboundEdge = entityResultsForInboundEdge;
295
+ }
296
+
297
+ const failedEntityLoadResults = failedResults(entityResultsToCheckForInboundEdge);
267
298
  for (const failedResult of failedEntityLoadResults) {
268
299
  if (failedResult.reason instanceof EntityNotAuthorizedError) {
269
300
  return false;
@@ -273,7 +304,9 @@ async function canViewerDeleteInternalAsync<
273
304
  }
274
305
 
275
306
  // all results should be success at this point due to check above
276
- const entitiesForInboundEdge = entityResultsForInboundEdge.map((r) => r.enforceValue());
307
+ const entitiesForInboundEdge = entityResultsToCheckForInboundEdge.map((r) =>
308
+ r.enforceValue(),
309
+ );
277
310
 
278
311
  switch (association.edgeDeletionBehavior) {
279
312
  case EntityEdgeDeletionBehavior.CASCADE_DELETE:
@@ -0,0 +1,254 @@
1
+ import Entity from '../../Entity';
2
+ import { EntityCompanionDefinition } from '../../EntityCompanionProvider';
3
+ import EntityConfiguration from '../../EntityConfiguration';
4
+ import {
5
+ EntityEdgeDeletionBehavior,
6
+ EntityEdgeDeletionAuthorizationInferenceBehavior,
7
+ } from '../../EntityFieldDefinition';
8
+ import { UUIDField } from '../../EntityFields';
9
+ import EntityPrivacyPolicy from '../../EntityPrivacyPolicy';
10
+ import ReadonlyEntity from '../../ReadonlyEntity';
11
+ import ViewerContext from '../../ViewerContext';
12
+ import AlwaysAllowPrivacyPolicyRule from '../../rules/AlwaysAllowPrivacyPolicyRule';
13
+ import AlwaysDenyPrivacyPolicyRule from '../../rules/AlwaysDenyPrivacyPolicyRule';
14
+ import { canViewerDeleteAsync } from '../EntityPrivacyUtils';
15
+ import { createUnitTestEntityCompanionProvider } from '../testing/createUnitTestEntityCompanionProvider';
16
+
17
+ describe(canViewerDeleteAsync, () => {
18
+ describe('edgeDeletionPermissionInferenceBehavior', () => {
19
+ it('optimizes when EntityEdgeDeletionPermissionInferenceBehavior.ONE_IMPLIES_ALL', async () => {
20
+ const companionProvider = createUnitTestEntityCompanionProvider();
21
+ const viewerContext = new ViewerContext(companionProvider);
22
+
23
+ // create root
24
+ const testEntity = await TestEntity.creator(viewerContext).enforceCreateAsync();
25
+
26
+ // create a bunch of leaves referencing root with
27
+ // edgeDeletionPermissionInferenceBehavior = EntityEdgeDeletionPermissionInferenceBehavior.ONE_IMPLIES_ALL
28
+ for (let i = 0; i < 10; i++) {
29
+ await TestLeafEntity.creator(viewerContext)
30
+ .setField('test_entity_id', testEntity.getID())
31
+ .enforceCreateAsync();
32
+ }
33
+
34
+ for (let i = 0; i < 10; i++) {
35
+ await TestLeafLookupByFieldEntity.creator(viewerContext)
36
+ .setField('test_entity_id', testEntity.getID())
37
+ .enforceCreateAsync();
38
+ }
39
+
40
+ const testLeafEntityCompanion =
41
+ viewerContext.getViewerScopedEntityCompanionForClass(TestLeafEntity);
42
+ const testLeafEntityAuthorizeDeleteSpy = jest.spyOn(
43
+ testLeafEntityCompanion.entityCompanion.privacyPolicy,
44
+ 'authorizeDeleteAsync',
45
+ );
46
+
47
+ const testLeafLookupByFieldEntityCompanion =
48
+ viewerContext.getViewerScopedEntityCompanionForClass(TestLeafLookupByFieldEntity);
49
+ const testLeafLookupByFieldEntityAuthorizeDeleteSpy = jest.spyOn(
50
+ testLeafLookupByFieldEntityCompanion.entityCompanion.privacyPolicy,
51
+ 'authorizeDeleteAsync',
52
+ );
53
+
54
+ const canViewerDelete = await canViewerDeleteAsync(TestEntity, testEntity);
55
+ expect(canViewerDelete).toBe(true);
56
+
57
+ expect(testLeafEntityAuthorizeDeleteSpy).toHaveBeenCalledTimes(1);
58
+ expect(testLeafLookupByFieldEntityAuthorizeDeleteSpy).toHaveBeenCalledTimes(1);
59
+ });
60
+
61
+ it('does not optimize when undefined', async () => {
62
+ const companionProvider = createUnitTestEntityCompanionProvider();
63
+ const viewerContext = new ViewerContext(companionProvider);
64
+
65
+ // create root
66
+ const testEntity = await TestEntity.creator(viewerContext).enforceCreateAsync();
67
+
68
+ // create a bunch of leaves with no edgeDeletionPermissionInferenceBehavior
69
+ for (let i = 0; i < 10; i++) {
70
+ await TestLeafNoInferenceEntity.creator(viewerContext)
71
+ .setField('test_entity_id', testEntity.getID())
72
+ .enforceCreateAsync();
73
+ }
74
+
75
+ const companion =
76
+ viewerContext.getViewerScopedEntityCompanionForClass(TestLeafNoInferenceEntity);
77
+ const authorizeDeleteSpy = jest.spyOn(
78
+ companion.entityCompanion.privacyPolicy,
79
+ 'authorizeDeleteAsync',
80
+ );
81
+
82
+ const canViewerDelete = await canViewerDeleteAsync(TestEntity, testEntity);
83
+ expect(canViewerDelete).toBe(true);
84
+
85
+ expect(authorizeDeleteSpy).toHaveBeenCalledTimes(10);
86
+ });
87
+ });
88
+ });
89
+
90
+ type TestEntityFields = {
91
+ id: string;
92
+ };
93
+
94
+ type TestLeafEntityFields = {
95
+ id: string;
96
+ test_entity_id: string | null;
97
+ };
98
+
99
+ class AlwaysAllowEntityPrivacyPolicy<
100
+ TFields extends object,
101
+ TID extends NonNullable<TFields[TSelectedFields]>,
102
+ TViewerContext extends ViewerContext,
103
+ TEntity extends ReadonlyEntity<TFields, TID, TViewerContext, TSelectedFields>,
104
+ TSelectedFields extends keyof TFields = keyof TFields,
105
+ > extends EntityPrivacyPolicy<TFields, TID, TViewerContext, TEntity, TSelectedFields> {
106
+ protected override readonly readRules = [
107
+ new AlwaysAllowPrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>(),
108
+ ];
109
+ protected override readonly createRules = [
110
+ new AlwaysAllowPrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>(),
111
+ ];
112
+ protected override readonly updateRules = [
113
+ new AlwaysDenyPrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>(),
114
+ ];
115
+ protected override readonly deleteRules = [
116
+ new AlwaysAllowPrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>(),
117
+ ];
118
+ }
119
+
120
+ class TestEntity extends Entity<TestEntityFields, string, ViewerContext> {
121
+ static defineCompanionDefinition(): EntityCompanionDefinition<
122
+ TestEntityFields,
123
+ string,
124
+ ViewerContext,
125
+ TestEntity,
126
+ AlwaysAllowEntityPrivacyPolicy<TestEntityFields, string, ViewerContext, TestEntity>
127
+ > {
128
+ return {
129
+ entityClass: TestEntity,
130
+ entityConfiguration: new EntityConfiguration<TestEntityFields>({
131
+ idField: 'id',
132
+ tableName: 'blah',
133
+ inboundEdges: [TestLeafEntity, TestLeafLookupByFieldEntity, TestLeafNoInferenceEntity],
134
+ schema: {
135
+ id: new UUIDField({
136
+ columnName: 'custom_id',
137
+ }),
138
+ },
139
+ databaseAdapterFlavor: 'postgres',
140
+ cacheAdapterFlavor: 'redis',
141
+ }),
142
+ privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
143
+ };
144
+ }
145
+ }
146
+
147
+ class TestLeafEntity extends Entity<TestLeafEntityFields, string, ViewerContext> {
148
+ static defineCompanionDefinition(): EntityCompanionDefinition<
149
+ TestLeafEntityFields,
150
+ string,
151
+ ViewerContext,
152
+ TestLeafEntity,
153
+ AlwaysAllowEntityPrivacyPolicy<TestLeafEntityFields, string, ViewerContext, TestLeafEntity>
154
+ > {
155
+ return {
156
+ entityClass: TestLeafEntity,
157
+ entityConfiguration: new EntityConfiguration<TestLeafEntityFields>({
158
+ idField: 'id',
159
+ tableName: 'blah_2',
160
+ schema: {
161
+ id: new UUIDField({
162
+ columnName: 'custom_id',
163
+ }),
164
+ test_entity_id: new UUIDField({
165
+ columnName: 'test_entity_id',
166
+ association: {
167
+ associatedEntityClass: TestEntity,
168
+ edgeDeletionBehavior: EntityEdgeDeletionBehavior.CASCADE_DELETE,
169
+ edgeDeletionAuthorizationInferenceBehavior:
170
+ EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL,
171
+ },
172
+ }),
173
+ },
174
+ databaseAdapterFlavor: 'postgres',
175
+ cacheAdapterFlavor: 'redis',
176
+ }),
177
+ privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
178
+ };
179
+ }
180
+ }
181
+
182
+ class TestLeafLookupByFieldEntity extends Entity<TestLeafEntityFields, string, ViewerContext> {
183
+ static defineCompanionDefinition(): EntityCompanionDefinition<
184
+ TestLeafEntityFields,
185
+ string,
186
+ ViewerContext,
187
+ TestLeafEntity,
188
+ AlwaysAllowEntityPrivacyPolicy<TestLeafEntityFields, string, ViewerContext, TestLeafEntity>
189
+ > {
190
+ return {
191
+ entityClass: TestLeafEntity,
192
+ entityConfiguration: new EntityConfiguration<TestLeafEntityFields>({
193
+ idField: 'id',
194
+ tableName: 'blah_4',
195
+ schema: {
196
+ id: new UUIDField({
197
+ columnName: 'custom_id',
198
+ }),
199
+ test_entity_id: new UUIDField({
200
+ columnName: 'test_entity_id',
201
+ association: {
202
+ associatedEntityClass: TestEntity,
203
+ edgeDeletionBehavior: EntityEdgeDeletionBehavior.CASCADE_DELETE,
204
+ associatedEntityLookupByField: 'id',
205
+ edgeDeletionAuthorizationInferenceBehavior:
206
+ EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL,
207
+ },
208
+ }),
209
+ },
210
+ databaseAdapterFlavor: 'postgres',
211
+ cacheAdapterFlavor: 'redis',
212
+ }),
213
+ privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
214
+ };
215
+ }
216
+ }
217
+
218
+ class TestLeafNoInferenceEntity extends Entity<TestLeafEntityFields, string, ViewerContext> {
219
+ static defineCompanionDefinition(): EntityCompanionDefinition<
220
+ TestLeafEntityFields,
221
+ string,
222
+ ViewerContext,
223
+ TestLeafNoInferenceEntity,
224
+ AlwaysAllowEntityPrivacyPolicy<
225
+ TestLeafEntityFields,
226
+ string,
227
+ ViewerContext,
228
+ TestLeafNoInferenceEntity
229
+ >
230
+ > {
231
+ return {
232
+ entityClass: TestLeafNoInferenceEntity,
233
+ entityConfiguration: new EntityConfiguration<TestLeafEntityFields>({
234
+ idField: 'id',
235
+ tableName: 'blah_3',
236
+ schema: {
237
+ id: new UUIDField({
238
+ columnName: 'custom_id',
239
+ }),
240
+ test_entity_id: new UUIDField({
241
+ columnName: 'test_entity_id',
242
+ association: {
243
+ associatedEntityClass: TestEntity,
244
+ edgeDeletionBehavior: EntityEdgeDeletionBehavior.CASCADE_DELETE,
245
+ },
246
+ }),
247
+ },
248
+ databaseAdapterFlavor: 'postgres',
249
+ cacheAdapterFlavor: 'redis',
250
+ }),
251
+ privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
252
+ };
253
+ }
254
+ }