@expo/entity 0.53.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/ComposedEntityCacheAdapter.js.map +1 -1
- package/build/src/ComposedSecondaryEntityCache.js.map +1 -1
- package/build/src/EntityDatabaseAdapter.js.map +1 -1
- package/build/src/EntityFieldDefinition.js.map +1 -1
- package/build/src/EntityLoaderUtils.js +14 -18
- package/build/src/EntityLoaderUtils.js.map +1 -1
- package/build/src/GenericEntityCacheAdapter.js.map +1 -1
- package/build/src/GenericSecondaryEntityCache.js.map +1 -1
- package/build/src/entityUtils.js +7 -2
- package/build/src/entityUtils.js.map +1 -1
- package/build/src/errors/EntityCacheAdapterError.d.ts +2 -2
- package/build/src/errors/EntityCacheAdapterError.js +12 -2
- package/build/src/errors/EntityCacheAdapterError.js.map +1 -1
- package/build/src/errors/EntityDatabaseAdapterError.d.ts +24 -24
- package/build/src/errors/EntityDatabaseAdapterError.js +111 -24
- package/build/src/errors/EntityDatabaseAdapterError.js.map +1 -1
- package/build/src/errors/EntityError.d.ts +2 -4
- package/build/src/errors/EntityError.js +5 -8
- package/build/src/errors/EntityError.js.map +1 -1
- package/build/src/errors/EntityInvalidFieldValueError.d.ts +2 -2
- package/build/src/errors/EntityInvalidFieldValueError.js +9 -2
- package/build/src/errors/EntityInvalidFieldValueError.js.map +1 -1
- package/build/src/errors/EntityNotAuthorizedError.d.ts +2 -2
- package/build/src/errors/EntityNotAuthorizedError.js +9 -2
- package/build/src/errors/EntityNotAuthorizedError.js.map +1 -1
- package/build/src/errors/EntityNotFoundError.d.ts +2 -2
- package/build/src/errors/EntityNotFoundError.js +9 -2
- package/build/src/errors/EntityNotFoundError.js.map +1 -1
- package/build/src/index.d.ts +4 -0
- package/build/src/index.js +4 -0
- package/build/src/index.js.map +1 -1
- package/build/src/internal/CompositeFieldHolder.js.map +1 -1
- package/build/src/internal/CompositeFieldValueMap.js.map +1 -1
- package/build/src/internal/SingleFieldHolder.js.map +1 -1
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.d.ts +10 -0
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js +19 -0
- package/build/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.d.ts +10 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js +19 -0
- package/build/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.d.ts +66 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js +75 -0
- package/build/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.js.map +1 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.d.ts +12 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js +23 -0
- package/build/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.js.map +1 -0
- package/build/src/utils/EntityPrivacyUtils.js +34 -15
- package/build/src/utils/EntityPrivacyUtils.js.map +1 -1
- package/package.json +10 -13
- package/src/ComposedEntityCacheAdapter.ts +1 -2
- package/src/ComposedSecondaryEntityCache.ts +4 -3
- package/src/EntityDatabaseAdapter.ts +3 -2
- package/src/EntityFieldDefinition.ts +1 -2
- package/src/EntityLoaderUtils.ts +17 -20
- package/src/GenericEntityCacheAdapter.ts +1 -2
- package/src/GenericSecondaryEntityCache.ts +1 -2
- package/src/__tests__/ComposedCacheAdapter-test.ts +4 -3
- package/src/__tests__/EntityFields-test.ts +2 -8
- package/src/entityUtils.ts +7 -4
- package/src/errors/EntityCacheAdapterError.ts +16 -3
- package/src/errors/EntityDatabaseAdapterError.ts +137 -25
- package/src/errors/EntityError.ts +7 -8
- package/src/errors/EntityInvalidFieldValueError.ts +11 -2
- package/src/errors/EntityNotAuthorizedError.ts +11 -2
- package/src/errors/EntityNotFoundError.ts +11 -2
- package/src/errors/__tests__/EntityDatabaseAdapterError-test.ts +68 -11
- package/src/errors/__tests__/EntityError-test.ts +36 -0
- package/src/index.ts +4 -0
- package/src/internal/CompositeFieldHolder.ts +7 -10
- package/src/internal/CompositeFieldValueMap.ts +1 -2
- package/src/internal/SingleFieldHolder.ts +10 -6
- package/src/rules/AllowIfAllSubRulesAllowPrivacyPolicyRule.ts +47 -0
- package/src/rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule.ts +47 -0
- package/src/rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule.ts +177 -0
- package/src/rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule.ts +46 -0
- package/src/rules/__tests__/AllowIfAllSubRulesAllowPrivacyPolicyRule-test.ts +64 -0
- package/src/rules/__tests__/AllowIfAnySubRuleAllowsPrivacyPolicyRule-test.ts +64 -0
- package/src/rules/__tests__/AllowIfInParentCascadeDeletionPrivacyPolicyRule-test.ts +258 -0
- package/src/rules/__tests__/EvaluateIfEntityFieldPredicatePrivacyPolicyRule-test.ts +47 -0
- package/src/utils/EntityPrivacyUtils.ts +61 -20
- package/src/utils/__testfixtures__/StubCacheAdapter.ts +2 -4
- package/src/utils/__testfixtures__/StubDatabaseAdapter.ts +1 -1
- package/src/utils/__tests__/EntityPrivacyUtils-test.ts +295 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import ES6Error from 'es6-error';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* The state of an entity error, indicating whether it may be transient/retryable.
|
|
5
3
|
*/
|
|
@@ -34,14 +32,15 @@ export enum EntityErrorCode {
|
|
|
34
32
|
/**
|
|
35
33
|
* Base class for all known errors thrown by the entity system.
|
|
36
34
|
*/
|
|
37
|
-
export abstract class EntityError extends
|
|
35
|
+
export abstract class EntityError extends Error {
|
|
36
|
+
static {
|
|
37
|
+
this.prototype.name = 'EntityError';
|
|
38
|
+
}
|
|
39
|
+
|
|
38
40
|
public abstract readonly state: EntityErrorState;
|
|
39
41
|
public abstract readonly code: EntityErrorCode;
|
|
40
42
|
|
|
41
|
-
constructor(
|
|
42
|
-
message
|
|
43
|
-
public override readonly cause?: Error,
|
|
44
|
-
) {
|
|
45
|
-
super(message);
|
|
43
|
+
constructor(message: string, cause?: Error) {
|
|
44
|
+
super(message, { cause });
|
|
46
45
|
}
|
|
47
46
|
}
|
|
@@ -22,8 +22,17 @@ export class EntityInvalidFieldValueError<
|
|
|
22
22
|
N extends keyof TFields,
|
|
23
23
|
TSelectedFields extends keyof TFields = keyof TFields,
|
|
24
24
|
> extends EntityError {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
static {
|
|
26
|
+
this.prototype.name = 'EntityInvalidFieldValueError';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get state(): EntityErrorState.PERMANENT {
|
|
30
|
+
return EntityErrorState.PERMANENT;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get code(): EntityErrorCode.ERR_ENTITY_INVALID_FIELD_VALUE {
|
|
34
|
+
return EntityErrorCode.ERR_ENTITY_INVALID_FIELD_VALUE;
|
|
35
|
+
}
|
|
27
36
|
|
|
28
37
|
constructor(
|
|
29
38
|
entityClass: IEntityClass<
|
|
@@ -13,8 +13,17 @@ export class EntityNotAuthorizedError<
|
|
|
13
13
|
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
14
14
|
TSelectedFields extends keyof TFields = keyof TFields,
|
|
15
15
|
> extends EntityError {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
static {
|
|
17
|
+
this.prototype.name = 'EntityNotAuthorizedError';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get state(): EntityErrorState.PERMANENT {
|
|
21
|
+
return EntityErrorState.PERMANENT;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get code(): EntityErrorCode.ERR_ENTITY_NOT_AUTHORIZED {
|
|
25
|
+
return EntityErrorCode.ERR_ENTITY_NOT_AUTHORIZED;
|
|
26
|
+
}
|
|
18
27
|
|
|
19
28
|
public readonly entityClassName: string;
|
|
20
29
|
|
|
@@ -49,8 +49,17 @@ export class EntityNotFoundError<
|
|
|
49
49
|
N extends keyof TFields,
|
|
50
50
|
TSelectedFields extends keyof TFields = keyof TFields,
|
|
51
51
|
> extends EntityError {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
static {
|
|
53
|
+
this.prototype.name = 'EntityNotFoundError';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get state(): EntityErrorState.PERMANENT {
|
|
57
|
+
return EntityErrorState.PERMANENT;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get code(): EntityErrorCode.ERR_ENTITY_NOT_FOUND {
|
|
61
|
+
return EntityErrorCode.ERR_ENTITY_NOT_FOUND;
|
|
62
|
+
}
|
|
54
63
|
|
|
55
64
|
constructor(message: string);
|
|
56
65
|
constructor(
|
|
@@ -2,7 +2,12 @@ import { describe, expect, it } from '@jest/globals';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
EntityDatabaseAdapterCheckConstraintError,
|
|
5
|
+
EntityDatabaseAdapterEmptyInsertResultError,
|
|
6
|
+
EntityDatabaseAdapterEmptyUpdateResultError,
|
|
5
7
|
EntityDatabaseAdapterError,
|
|
8
|
+
EntityDatabaseAdapterExcessiveDeleteResultError,
|
|
9
|
+
EntityDatabaseAdapterExcessiveInsertResultError,
|
|
10
|
+
EntityDatabaseAdapterExcessiveUpdateResultError,
|
|
6
11
|
EntityDatabaseAdapterExclusionConstraintError,
|
|
7
12
|
EntityDatabaseAdapterForeignKeyConstraintError,
|
|
8
13
|
EntityDatabaseAdapterNotNullConstraintError,
|
|
@@ -10,20 +15,72 @@ import {
|
|
|
10
15
|
EntityDatabaseAdapterUniqueConstraintError,
|
|
11
16
|
EntityDatabaseAdapterUnknownError,
|
|
12
17
|
} from '../EntityDatabaseAdapterError';
|
|
18
|
+
import { EntityErrorCode, EntityErrorState } from '../EntityError';
|
|
13
19
|
|
|
14
20
|
describe(EntityDatabaseAdapterError, () => {
|
|
15
21
|
// necessary for coverage within the entity package since these errors are
|
|
16
22
|
// currently only ever instantiated by database adapter implementations
|
|
17
|
-
it('instantiates all errors
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
expect(
|
|
23
|
+
it('instantiates all errors with correct state and code', () => {
|
|
24
|
+
const transientError = new EntityDatabaseAdapterTransientError('test');
|
|
25
|
+
expect(transientError.state).toBe(EntityErrorState.TRANSIENT);
|
|
26
|
+
expect(transientError.code).toBe(EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_TRANSIENT);
|
|
27
|
+
|
|
28
|
+
const unknownError = new EntityDatabaseAdapterUnknownError('test');
|
|
29
|
+
expect(unknownError.state).toBe(EntityErrorState.UNKNOWN);
|
|
30
|
+
expect(unknownError.code).toBe(EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_UNKNOWN);
|
|
31
|
+
|
|
32
|
+
const checkError = new EntityDatabaseAdapterCheckConstraintError('test');
|
|
33
|
+
expect(checkError.state).toBe(EntityErrorState.PERMANENT);
|
|
34
|
+
expect(checkError.code).toBe(EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_CHECK_CONSTRAINT);
|
|
35
|
+
|
|
36
|
+
const exclusionError = new EntityDatabaseAdapterExclusionConstraintError('test');
|
|
37
|
+
expect(exclusionError.state).toBe(EntityErrorState.PERMANENT);
|
|
38
|
+
expect(exclusionError.code).toBe(
|
|
39
|
+
EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_EXCLUSION_CONSTRAINT,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const foreignKeyError = new EntityDatabaseAdapterForeignKeyConstraintError('test');
|
|
43
|
+
expect(foreignKeyError.state).toBe(EntityErrorState.PERMANENT);
|
|
44
|
+
expect(foreignKeyError.code).toBe(
|
|
45
|
+
EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_FOREIGN_KEY_CONSTRAINT,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const notNullError = new EntityDatabaseAdapterNotNullConstraintError('test');
|
|
49
|
+
expect(notNullError.state).toBe(EntityErrorState.PERMANENT);
|
|
50
|
+
expect(notNullError.code).toBe(EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_NOT_NULL_CONSTRAINT);
|
|
51
|
+
|
|
52
|
+
const uniqueError = new EntityDatabaseAdapterUniqueConstraintError('test');
|
|
53
|
+
expect(uniqueError.state).toBe(EntityErrorState.PERMANENT);
|
|
54
|
+
expect(uniqueError.code).toBe(EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_UNIQUE_CONSTRAINT);
|
|
55
|
+
|
|
56
|
+
const excessiveInsertError = new EntityDatabaseAdapterExcessiveInsertResultError('test');
|
|
57
|
+
expect(excessiveInsertError.state).toBe(EntityErrorState.PERMANENT);
|
|
58
|
+
expect(excessiveInsertError.code).toBe(
|
|
59
|
+
EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_EXCESSIVE_INSERT_RESULT,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const emptyInsertError = new EntityDatabaseAdapterEmptyInsertResultError('test');
|
|
63
|
+
expect(emptyInsertError.state).toBe(EntityErrorState.PERMANENT);
|
|
64
|
+
expect(emptyInsertError.code).toBe(
|
|
65
|
+
EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_EMPTY_INSERT_RESULT,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const excessiveUpdateError = new EntityDatabaseAdapterExcessiveUpdateResultError('test');
|
|
69
|
+
expect(excessiveUpdateError.state).toBe(EntityErrorState.PERMANENT);
|
|
70
|
+
expect(excessiveUpdateError.code).toBe(
|
|
71
|
+
EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_EXCESSIVE_UPDATE_RESULT,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const emptyUpdateError = new EntityDatabaseAdapterEmptyUpdateResultError('test');
|
|
75
|
+
expect(emptyUpdateError.state).toBe(EntityErrorState.PERMANENT);
|
|
76
|
+
expect(emptyUpdateError.code).toBe(
|
|
77
|
+
EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_EMPTY_UPDATE_RESULT,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const excessiveDeleteError = new EntityDatabaseAdapterExcessiveDeleteResultError('test');
|
|
81
|
+
expect(excessiveDeleteError.state).toBe(EntityErrorState.PERMANENT);
|
|
82
|
+
expect(excessiveDeleteError.code).toBe(
|
|
83
|
+
EntityErrorCode.ERR_ENTITY_DATABASE_ADAPTER_EXCESSIVE_DELETE_RESULT,
|
|
84
|
+
);
|
|
28
85
|
});
|
|
29
86
|
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
import { EntityCacheAdapterTransientError } from '../EntityCacheAdapterError';
|
|
4
|
+
import { EntityErrorCode, EntityErrorState } from '../EntityError';
|
|
5
|
+
import { EntityInvalidFieldValueError } from '../EntityInvalidFieldValueError';
|
|
6
|
+
import { EntityNotAuthorizedError } from '../EntityNotAuthorizedError';
|
|
7
|
+
import { EntityNotFoundError } from '../EntityNotFoundError';
|
|
8
|
+
|
|
9
|
+
describe('EntityError subclasses', () => {
|
|
10
|
+
it('EntityNotFoundError has correct state and code', () => {
|
|
11
|
+
const error = new EntityNotFoundError('not found');
|
|
12
|
+
expect(error.state).toBe(EntityErrorState.PERMANENT);
|
|
13
|
+
expect(error.code).toBe(EntityErrorCode.ERR_ENTITY_NOT_FOUND);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('EntityNotAuthorizedError has correct state and code', () => {
|
|
17
|
+
const mockEntity = { constructor: { name: 'TestEntity' }, toString: () => 'TestEntity' } as any;
|
|
18
|
+
const mockViewerContext = { toString: () => 'TestViewer' } as any;
|
|
19
|
+
const error = new EntityNotAuthorizedError(mockEntity, mockViewerContext, 0, 0);
|
|
20
|
+
expect(error.state).toBe(EntityErrorState.PERMANENT);
|
|
21
|
+
expect(error.code).toBe(EntityErrorCode.ERR_ENTITY_NOT_AUTHORIZED);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('EntityInvalidFieldValueError has correct state and code', () => {
|
|
25
|
+
const mockEntityClass = { name: 'TestEntity' } as any;
|
|
26
|
+
const error = new EntityInvalidFieldValueError(mockEntityClass, 'testField', 'badValue');
|
|
27
|
+
expect(error.state).toBe(EntityErrorState.PERMANENT);
|
|
28
|
+
expect(error.code).toBe(EntityErrorCode.ERR_ENTITY_INVALID_FIELD_VALUE);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('EntityCacheAdapterTransientError has correct state and code', () => {
|
|
32
|
+
const error = new EntityCacheAdapterTransientError('cache error');
|
|
33
|
+
expect(error.state).toBe(EntityErrorState.TRANSIENT);
|
|
34
|
+
expect(error.code).toBe(EntityErrorCode.ERR_ENTITY_CACHE_ADAPTER_TRANSIENT);
|
|
35
|
+
});
|
|
36
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -66,9 +66,13 @@ export * from './internal/SingleFieldHolder';
|
|
|
66
66
|
export * from './metrics/EntityMetricsUtils';
|
|
67
67
|
export * from './metrics/IEntityMetricsAdapter';
|
|
68
68
|
export * from './metrics/NoOpEntityMetricsAdapter';
|
|
69
|
+
export * from './rules/AllowIfAllSubRulesAllowPrivacyPolicyRule';
|
|
70
|
+
export * from './rules/AllowIfAnySubRuleAllowsPrivacyPolicyRule';
|
|
71
|
+
export * from './rules/AllowIfInParentCascadeDeletionPrivacyPolicyRule';
|
|
69
72
|
export * from './rules/AlwaysAllowPrivacyPolicyRule';
|
|
70
73
|
export * from './rules/AlwaysDenyPrivacyPolicyRule';
|
|
71
74
|
export * from './rules/AlwaysSkipPrivacyPolicyRule';
|
|
75
|
+
export * from './rules/EvaluateIfEntityFieldPredicatePrivacyPolicyRule';
|
|
72
76
|
export * from './rules/PrivacyPolicyRule';
|
|
73
77
|
export * from './utils/EntityCreationUtils';
|
|
74
78
|
export * from './utils/EntityPrivacyUtils';
|
|
@@ -32,14 +32,12 @@ export type SerializedCompositeFieldHolder = string & {
|
|
|
32
32
|
export class CompositeFieldHolder<
|
|
33
33
|
TFields extends Record<string, any>,
|
|
34
34
|
TIDField extends keyof TFields,
|
|
35
|
-
> implements
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
>
|
|
42
|
-
{
|
|
35
|
+
> implements IEntityLoadKey<
|
|
36
|
+
TFields,
|
|
37
|
+
TIDField,
|
|
38
|
+
SerializedCompositeFieldValueHolder,
|
|
39
|
+
CompositeFieldValueHolder<TFields, EntityCompositeField<TFields>>
|
|
40
|
+
> {
|
|
43
41
|
public readonly compositeField: EntityCompositeField<TFields>;
|
|
44
42
|
|
|
45
43
|
constructor(compositeFieldInput: EntityCompositeField<TFields>) {
|
|
@@ -174,8 +172,7 @@ export type SerializedCompositeFieldValueHolder = string & {
|
|
|
174
172
|
export class CompositeFieldValueHolder<
|
|
175
173
|
TFields extends Record<string, any>,
|
|
176
174
|
TCompositeField extends EntityCompositeField<TFields>,
|
|
177
|
-
> implements IEntityLoadValue<SerializedCompositeFieldValueHolder>
|
|
178
|
-
{
|
|
175
|
+
> implements IEntityLoadValue<SerializedCompositeFieldValueHolder> {
|
|
179
176
|
constructor(
|
|
180
177
|
public readonly compositeFieldValue: EntityCompositeFieldValue<TFields, TCompositeField>,
|
|
181
178
|
) {}
|
|
@@ -11,8 +11,7 @@ export class CompositeFieldValueMap<
|
|
|
11
11
|
TFields extends Record<string, any>,
|
|
12
12
|
N extends EntityCompositeField<TFields>,
|
|
13
13
|
TOutput,
|
|
14
|
-
> implements ReadonlyMap<EntityCompositeFieldValue<TFields, N>, TOutput>
|
|
15
|
-
{
|
|
14
|
+
> implements ReadonlyMap<EntityCompositeFieldValue<TFields, N>, TOutput> {
|
|
16
15
|
private readonly map: Map<SerializedCompositeFieldValueHolder, TOutput>;
|
|
17
16
|
|
|
18
17
|
constructor(entries: [CompositeFieldValueHolder<TFields, N>, TOutput][]) {
|
|
@@ -18,9 +18,12 @@ export class SingleFieldHolder<
|
|
|
18
18
|
TFields extends Record<string, any>,
|
|
19
19
|
TIDField extends keyof TFields,
|
|
20
20
|
N extends keyof TFields,
|
|
21
|
-
> implements
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
> implements IEntityLoadKey<
|
|
22
|
+
TFields,
|
|
23
|
+
TIDField,
|
|
24
|
+
NonNullable<TFields[N]>,
|
|
25
|
+
SingleFieldValueHolder<TFields, N>
|
|
26
|
+
> {
|
|
24
27
|
constructor(public readonly fieldName: N) {}
|
|
25
28
|
|
|
26
29
|
toString(): string {
|
|
@@ -102,9 +105,10 @@ export class SingleFieldHolder<
|
|
|
102
105
|
*
|
|
103
106
|
* @internal
|
|
104
107
|
*/
|
|
105
|
-
export class SingleFieldValueHolder<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
export class SingleFieldValueHolder<
|
|
109
|
+
TFields extends Record<string, any>,
|
|
110
|
+
N extends keyof TFields,
|
|
111
|
+
> implements IEntityLoadValue<NonNullable<TFields[N]>> {
|
|
108
112
|
constructor(public readonly fieldValue: NonNullable<TFields[N]>) {}
|
|
109
113
|
|
|
110
114
|
toString(): string {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
|
|
2
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
3
|
+
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
4
|
+
import { ViewerContext } from '../ViewerContext';
|
|
5
|
+
import { PrivacyPolicyRule, RuleEvaluationResult } from './PrivacyPolicyRule';
|
|
6
|
+
|
|
7
|
+
export class AllowIfAllSubRulesAllowPrivacyPolicyRule<
|
|
8
|
+
TFields extends object,
|
|
9
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
10
|
+
TViewerContext extends ViewerContext,
|
|
11
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
12
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
13
|
+
> extends PrivacyPolicyRule<TFields, TIDField, TViewerContext, TEntity, TSelectedFields> {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly subRules: PrivacyPolicyRule<
|
|
16
|
+
TFields,
|
|
17
|
+
TIDField,
|
|
18
|
+
TViewerContext,
|
|
19
|
+
TEntity,
|
|
20
|
+
TSelectedFields
|
|
21
|
+
>[],
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async evaluateAsync(
|
|
27
|
+
viewerContext: TViewerContext,
|
|
28
|
+
queryContext: EntityQueryContext,
|
|
29
|
+
evaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
30
|
+
TFields,
|
|
31
|
+
TIDField,
|
|
32
|
+
TViewerContext,
|
|
33
|
+
TEntity,
|
|
34
|
+
TSelectedFields
|
|
35
|
+
>,
|
|
36
|
+
entity: TEntity,
|
|
37
|
+
): Promise<RuleEvaluationResult> {
|
|
38
|
+
const results = await Promise.all(
|
|
39
|
+
this.subRules.map((subRule) =>
|
|
40
|
+
subRule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
return results.every((result) => result === RuleEvaluationResult.ALLOW)
|
|
44
|
+
? RuleEvaluationResult.ALLOW
|
|
45
|
+
: RuleEvaluationResult.SKIP;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
|
|
2
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
3
|
+
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
4
|
+
import { ViewerContext } from '../ViewerContext';
|
|
5
|
+
import { PrivacyPolicyRule, RuleEvaluationResult } from './PrivacyPolicyRule';
|
|
6
|
+
|
|
7
|
+
export class AllowIfAnySubRuleAllowsPrivacyPolicyRule<
|
|
8
|
+
TFields extends object,
|
|
9
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
10
|
+
TViewerContext extends ViewerContext,
|
|
11
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
12
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
13
|
+
> extends PrivacyPolicyRule<TFields, TIDField, TViewerContext, TEntity, TSelectedFields> {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly subRules: PrivacyPolicyRule<
|
|
16
|
+
TFields,
|
|
17
|
+
TIDField,
|
|
18
|
+
TViewerContext,
|
|
19
|
+
TEntity,
|
|
20
|
+
TSelectedFields
|
|
21
|
+
>[],
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async evaluateAsync(
|
|
27
|
+
viewerContext: TViewerContext,
|
|
28
|
+
queryContext: EntityQueryContext,
|
|
29
|
+
evaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
30
|
+
TFields,
|
|
31
|
+
TIDField,
|
|
32
|
+
TViewerContext,
|
|
33
|
+
TEntity,
|
|
34
|
+
TSelectedFields
|
|
35
|
+
>,
|
|
36
|
+
entity: TEntity,
|
|
37
|
+
): Promise<RuleEvaluationResult> {
|
|
38
|
+
const results = await Promise.all(
|
|
39
|
+
this.subRules.map((subRule) =>
|
|
40
|
+
subRule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
return results.includes(RuleEvaluationResult.ALLOW)
|
|
44
|
+
? RuleEvaluationResult.ALLOW
|
|
45
|
+
: RuleEvaluationResult.SKIP;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { IEntityClass } from '../Entity';
|
|
2
|
+
import { EntityPrivacyPolicy, EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
|
|
3
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
4
|
+
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
5
|
+
import { ViewerContext } from '../ViewerContext';
|
|
6
|
+
import { PrivacyPolicyRule, RuleEvaluationResult } from './PrivacyPolicyRule';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Directive for specifying the parent relationship in AllowIfInParentCascadeDeletionPrivacyPolicyRule.
|
|
10
|
+
*/
|
|
11
|
+
export interface AllowIfInParentCascadeDeletionDirective<
|
|
12
|
+
TViewerContext extends ViewerContext,
|
|
13
|
+
TFields,
|
|
14
|
+
TParentFields extends object,
|
|
15
|
+
TParentIDField extends keyof NonNullable<Pick<TParentFields, TParentSelectedFields>>,
|
|
16
|
+
TParentEntity extends ReadonlyEntity<
|
|
17
|
+
TParentFields,
|
|
18
|
+
TParentIDField,
|
|
19
|
+
TViewerContext,
|
|
20
|
+
TParentSelectedFields
|
|
21
|
+
>,
|
|
22
|
+
TParentPrivacyPolicy extends EntityPrivacyPolicy<
|
|
23
|
+
TParentFields,
|
|
24
|
+
TParentIDField,
|
|
25
|
+
TViewerContext,
|
|
26
|
+
TParentEntity,
|
|
27
|
+
TParentSelectedFields
|
|
28
|
+
>,
|
|
29
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
30
|
+
TParentSelectedFields extends keyof TParentFields = keyof TParentFields,
|
|
31
|
+
> {
|
|
32
|
+
/**
|
|
33
|
+
* Class of parent entity that should trigger a cascade set null update to a field within
|
|
34
|
+
* the entity being authorized.
|
|
35
|
+
*/
|
|
36
|
+
parentEntityClass: IEntityClass<
|
|
37
|
+
TParentFields,
|
|
38
|
+
TParentIDField,
|
|
39
|
+
TViewerContext,
|
|
40
|
+
TParentEntity,
|
|
41
|
+
TParentPrivacyPolicy,
|
|
42
|
+
TParentSelectedFields
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Field of the current entity with references the deleting instace of parentEntityClass.
|
|
47
|
+
*/
|
|
48
|
+
fieldIdentifyingParentEntity: keyof Pick<TFields, TSelectedFields>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Field in parentEntityClass referenced by the value of fieldIdentifyingParentEntity.
|
|
52
|
+
* If not provided, ID is assumed.
|
|
53
|
+
*/
|
|
54
|
+
parentEntityLookupByField?: keyof Pick<TParentFields, TParentSelectedFields>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A generic privacy policy rule that allows when an entity is being authorized
|
|
59
|
+
* as part of a cascading delete from a parent entity. Handles two cases:
|
|
60
|
+
* - When the field has not yet been null'ed out due to a cascading set null. This is often
|
|
61
|
+
* required for read rules to authorize the initial re-read of the entity being update set null'ed.
|
|
62
|
+
* - When the field has been null'ed out due to a cascading set null. This is often required
|
|
63
|
+
* the update rules for the field nullification.
|
|
64
|
+
*
|
|
65
|
+
* These two cases could theoretically be handled by two separate (stricter) rules, but are combined
|
|
66
|
+
* to simplify configuration since practically there are few cases where having them be combined would
|
|
67
|
+
* preset an issue.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* Billing info owned by an account, but records who created the billing info in creating_user_id. User is a member of that account.
|
|
71
|
+
* User can delete themselves, and the billing info's creating_user_id field is cascade set null'ed when the user is deleted.
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* class BillingInfoEntityPrivacyPolicy extends EntityPrivacyPolicy<...> {
|
|
75
|
+
* protected override readonly readRules = [
|
|
76
|
+
* ...,
|
|
77
|
+
* new AllowIfInParentCascadeDeletionPrivacyPolicyRule<...>({
|
|
78
|
+
* fieldIdentifyingParentEntity: 'creating_user_id',
|
|
79
|
+
* parentEntityClass: UserEntity,
|
|
80
|
+
* }),
|
|
81
|
+
* ];
|
|
82
|
+
*
|
|
83
|
+
* protected override readonly updateRules = [
|
|
84
|
+
* ...,
|
|
85
|
+
* new AllowIfInParentCascadeDeletionPrivacyPolicyRule<...>({
|
|
86
|
+
* fieldIdentifyingParentEntity: 'creating_user_id',
|
|
87
|
+
* parentEntityClass: UserEntity,
|
|
88
|
+
* }),
|
|
89
|
+
* ];
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export class AllowIfInParentCascadeDeletionPrivacyPolicyRule<
|
|
94
|
+
TFields extends object,
|
|
95
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
96
|
+
TViewerContext extends ViewerContext,
|
|
97
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
98
|
+
TFields2 extends object,
|
|
99
|
+
TIDField2 extends keyof NonNullable<Pick<TFields2, TSelectedFields2>>,
|
|
100
|
+
TEntity2 extends ReadonlyEntity<TFields2, TIDField2, TViewerContext, TSelectedFields2>,
|
|
101
|
+
TPrivacyPolicy2 extends EntityPrivacyPolicy<
|
|
102
|
+
TFields2,
|
|
103
|
+
TIDField2,
|
|
104
|
+
TViewerContext,
|
|
105
|
+
TEntity2,
|
|
106
|
+
TSelectedFields2
|
|
107
|
+
>,
|
|
108
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
109
|
+
TSelectedFields2 extends keyof TFields2 = keyof TFields2,
|
|
110
|
+
> extends PrivacyPolicyRule<TFields, TIDField, TViewerContext, TEntity, TSelectedFields> {
|
|
111
|
+
constructor(
|
|
112
|
+
private readonly directive: AllowIfInParentCascadeDeletionDirective<
|
|
113
|
+
TViewerContext,
|
|
114
|
+
TFields,
|
|
115
|
+
TFields2,
|
|
116
|
+
TIDField2,
|
|
117
|
+
TEntity2,
|
|
118
|
+
TPrivacyPolicy2,
|
|
119
|
+
TSelectedFields,
|
|
120
|
+
TSelectedFields2
|
|
121
|
+
>,
|
|
122
|
+
) {
|
|
123
|
+
super();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async evaluateAsync(
|
|
127
|
+
_viewerContext: TViewerContext,
|
|
128
|
+
_queryContext: EntityQueryContext,
|
|
129
|
+
evaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
130
|
+
TFields,
|
|
131
|
+
TIDField,
|
|
132
|
+
TViewerContext,
|
|
133
|
+
TEntity,
|
|
134
|
+
TSelectedFields
|
|
135
|
+
>,
|
|
136
|
+
entity: TEntity,
|
|
137
|
+
): Promise<RuleEvaluationResult> {
|
|
138
|
+
const parentEntityClass = this.directive.parentEntityClass;
|
|
139
|
+
|
|
140
|
+
const deleteCause = evaluationContext.cascadingDeleteCause;
|
|
141
|
+
if (!deleteCause || !(deleteCause.entity instanceof parentEntityClass)) {
|
|
142
|
+
return RuleEvaluationResult.SKIP;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const entityBeingDeleted = deleteCause.entity;
|
|
146
|
+
|
|
147
|
+
// allow if parent foreign key field matches specified field in the entity being authorized
|
|
148
|
+
const valueInThisEntityReferencingParent = entity.getField(
|
|
149
|
+
this.directive.fieldIdentifyingParentEntity,
|
|
150
|
+
);
|
|
151
|
+
const valueInParent = this.directive.parentEntityLookupByField
|
|
152
|
+
? entityBeingDeleted.getField(this.directive.parentEntityLookupByField)
|
|
153
|
+
: entityBeingDeleted.getID();
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
valueInThisEntityReferencingParent &&
|
|
157
|
+
valueInThisEntityReferencingParent === valueInParent
|
|
158
|
+
) {
|
|
159
|
+
return RuleEvaluationResult.ALLOW;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// allow if parent foreign key field matches specified field in the entity being authorized, and the
|
|
163
|
+
// field in the entity being authorized has been null'ed out due to cascading set null
|
|
164
|
+
const valueInPreviousValueOfThisEntityReferencingParent =
|
|
165
|
+
evaluationContext.previousValue?.getField(this.directive.fieldIdentifyingParentEntity);
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
valueInPreviousValueOfThisEntityReferencingParent &&
|
|
169
|
+
valueInPreviousValueOfThisEntityReferencingParent === valueInParent &&
|
|
170
|
+
valueInThisEntityReferencingParent === null
|
|
171
|
+
) {
|
|
172
|
+
return RuleEvaluationResult.ALLOW;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return RuleEvaluationResult.SKIP;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { EntityPrivacyPolicyEvaluationContext } from '../EntityPrivacyPolicy';
|
|
2
|
+
import { EntityQueryContext } from '../EntityQueryContext';
|
|
3
|
+
import { ReadonlyEntity } from '../ReadonlyEntity';
|
|
4
|
+
import { ViewerContext } from '../ViewerContext';
|
|
5
|
+
import { PrivacyPolicyRule, RuleEvaluationResult } from './PrivacyPolicyRule';
|
|
6
|
+
|
|
7
|
+
export class EvaluateIfEntityFieldPredicatePrivacyPolicyRule<
|
|
8
|
+
TFields extends object,
|
|
9
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
10
|
+
TViewerContext extends ViewerContext,
|
|
11
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
12
|
+
N extends TSelectedFields,
|
|
13
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
14
|
+
> extends PrivacyPolicyRule<TFields, TIDField, TViewerContext, TEntity, TSelectedFields> {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly fieldName: N,
|
|
17
|
+
private readonly shouldEvaluatePredicate: (fieldValue: TFields[N]) => boolean,
|
|
18
|
+
private readonly rule: PrivacyPolicyRule<
|
|
19
|
+
TFields,
|
|
20
|
+
TIDField,
|
|
21
|
+
TViewerContext,
|
|
22
|
+
TEntity,
|
|
23
|
+
TSelectedFields
|
|
24
|
+
>,
|
|
25
|
+
) {
|
|
26
|
+
super();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async evaluateAsync(
|
|
30
|
+
viewerContext: TViewerContext,
|
|
31
|
+
queryContext: EntityQueryContext,
|
|
32
|
+
evaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
33
|
+
TFields,
|
|
34
|
+
TIDField,
|
|
35
|
+
TViewerContext,
|
|
36
|
+
TEntity,
|
|
37
|
+
TSelectedFields
|
|
38
|
+
>,
|
|
39
|
+
entity: TEntity,
|
|
40
|
+
): Promise<RuleEvaluationResult> {
|
|
41
|
+
const fieldValue = entity.getField(this.fieldName);
|
|
42
|
+
return this.shouldEvaluatePredicate(fieldValue)
|
|
43
|
+
? await this.rule.evaluateAsync(viewerContext, queryContext, evaluationContext, entity)
|
|
44
|
+
: RuleEvaluationResult.SKIP;
|
|
45
|
+
}
|
|
46
|
+
}
|