@snowtop/ent 0.1.0-alpha1 → 0.1.0-alpha13
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/action/action.d.ts +17 -13
- package/action/executor.d.ts +2 -2
- package/action/experimental_action.d.ts +4 -4
- package/action/experimental_action.js +1 -1
- package/action/orchestrator.d.ts +24 -9
- package/action/orchestrator.js +150 -39
- package/action/privacy.d.ts +2 -2
- package/core/base.d.ts +6 -2
- package/core/base.js +16 -0
- package/core/clause.d.ts +24 -3
- package/core/clause.js +246 -5
- package/core/config.d.ts +18 -0
- package/core/config.js +17 -0
- package/core/db.d.ts +3 -3
- package/core/db.js +2 -0
- package/core/ent.d.ts +2 -4
- package/core/ent.js +72 -25
- package/core/loaders/assoc_edge_loader.d.ts +1 -1
- package/core/loaders/assoc_edge_loader.js +5 -4
- package/core/loaders/index_loader.js +1 -0
- package/core/loaders/object_loader.d.ts +7 -2
- package/core/loaders/object_loader.js +59 -4
- package/core/privacy.d.ts +1 -1
- package/core/privacy.js +3 -0
- package/core/viewer.d.ts +1 -0
- package/core/viewer.js +4 -0
- package/graphql/builtins/connection.js +3 -3
- package/graphql/builtins/edge.js +2 -2
- package/graphql/builtins/node.js +1 -1
- package/graphql/graphql.d.ts +3 -2
- package/graphql/graphql.js +24 -23
- package/graphql/index.d.ts +1 -0
- package/graphql/index.js +3 -1
- package/graphql/mutations/union.d.ts +2 -0
- package/graphql/mutations/union.js +35 -0
- package/graphql/node_resolver.d.ts +0 -1
- package/graphql/query/connection_type.js +6 -6
- package/graphql/query/page_info.js +4 -4
- package/graphql/query/shared_assoc_test.js +2 -2
- package/graphql/scalars/time.d.ts +1 -1
- package/index.d.ts +16 -1
- package/index.js +18 -5
- package/package.json +3 -3
- package/parse_schema/parse.d.ts +21 -4
- package/parse_schema/parse.js +79 -8
- package/schema/base_schema.d.ts +36 -1
- package/schema/base_schema.js +48 -2
- package/schema/field.d.ts +1 -1
- package/schema/field.js +11 -3
- package/schema/index.d.ts +4 -2
- package/schema/index.js +10 -1
- package/schema/schema.d.ts +66 -4
- package/schema/schema.js +138 -5
- package/schema/struct_field.d.ts +17 -0
- package/schema/struct_field.js +102 -0
- package/schema/union_field.d.ts +23 -0
- package/schema/union_field.js +79 -0
- package/scripts/custom_graphql.js +122 -15
- package/scripts/move_generated.d.ts +1 -0
- package/scripts/move_generated.js +142 -0
- package/scripts/read_schema.js +15 -1
- package/scripts/transform_code.d.ts +1 -0
- package/scripts/transform_code.js +113 -0
- package/scripts/transform_schema.js +190 -123
- package/testutils/builder.d.ts +27 -18
- package/testutils/builder.js +82 -10
- package/testutils/context/test_context.d.ts +2 -2
- package/testutils/context/test_context.js +7 -1
- package/testutils/db/test_db.d.ts +2 -1
- package/testutils/db/test_db.js +13 -4
- package/testutils/ent-graphql-tests/index.d.ts +2 -0
- package/testutils/ent-graphql-tests/index.js +26 -17
- package/testutils/fake_data/fake_contact.d.ts +4 -8
- package/testutils/fake_data/fake_contact.js +15 -19
- package/testutils/fake_data/fake_event.d.ts +4 -8
- package/testutils/fake_data/fake_event.js +21 -25
- package/testutils/fake_data/fake_user.d.ts +5 -9
- package/testutils/fake_data/fake_user.js +23 -27
- package/testutils/fake_data/test_helpers.js +1 -1
- package/tsc/ast.d.ts +21 -0
- package/tsc/ast.js +154 -0
- package/tsc/compilerOptions.d.ts +6 -0
- package/tsc/compilerOptions.js +40 -1
package/action/action.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { Ent, EntConstructor, Viewer, ID, Data, PrivacyPolicy, Context } from "../core/base";
|
|
2
2
|
import { DataOperation, AssocEdgeInputOptions } from "../core/ent";
|
|
3
3
|
import { Queryer } from "../core/db";
|
|
4
|
+
import { TransformedUpdateOperation, UpdateOperation } from "../schema";
|
|
4
5
|
export declare enum WriteOperation {
|
|
5
6
|
Insert = "insert",
|
|
6
7
|
Edit = "edit",
|
|
7
8
|
Delete = "delete"
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
declare type MaybeNull<T extends Ent> = T | null;
|
|
11
|
+
declare type TMaybleNullableEnt<T extends Ent> = T | MaybeNull<T>;
|
|
12
|
+
export interface Builder<T extends Ent, TExistingEnt extends TMaybleNullableEnt<T> = MaybeNull<T>> {
|
|
13
|
+
existingEnt: TExistingEnt;
|
|
11
14
|
ent: EntConstructor<T>;
|
|
12
15
|
placeholderID: ID;
|
|
13
16
|
readonly viewer: Viewer;
|
|
@@ -32,24 +35,25 @@ export interface Changeset<T extends Ent> {
|
|
|
32
35
|
dependencies?: Map<ID, Builder<Ent>>;
|
|
33
36
|
}
|
|
34
37
|
export declare type TriggerReturn = void | Promise<Changeset<Ent> | void | (Changeset<Ent> | void)[]> | Promise<Changeset<Ent>>[];
|
|
35
|
-
export interface Trigger<TBuilder extends Builder<
|
|
36
|
-
changeset(builder: TBuilder, input:
|
|
38
|
+
export interface Trigger<TEnt extends Ent, TBuilder extends Builder<TEnt, TExistingEnt>, TInput extends Data = Data, TExistingEnt extends TMaybleNullableEnt<TEnt> = MaybeNull<TEnt>> {
|
|
39
|
+
changeset(builder: TBuilder, input: TInput): TriggerReturn;
|
|
37
40
|
}
|
|
38
|
-
export interface Observer<TBuilder extends Builder<
|
|
39
|
-
observe(builder: TBuilder, input:
|
|
41
|
+
export interface Observer<TEnt extends Ent, TBuilder extends Builder<TEnt, TExistingEnt>, TInput extends Data = Data, TExistingEnt extends TMaybleNullableEnt<TEnt> = MaybeNull<TEnt>> {
|
|
42
|
+
observe(builder: TBuilder, input: TInput): void | Promise<void>;
|
|
40
43
|
}
|
|
41
|
-
export interface Validator<TBuilder extends Builder<
|
|
42
|
-
validate(builder: TBuilder, input:
|
|
44
|
+
export interface Validator<TEnt extends Ent, TBuilder extends Builder<TEnt, TExistingEnt>, TInput extends Data, TExistingEnt extends TMaybleNullableEnt<TEnt> = MaybeNull<TEnt>> {
|
|
45
|
+
validate(builder: TBuilder, input: TInput): Promise<void> | void;
|
|
43
46
|
}
|
|
44
|
-
export interface Action<TEnt extends Ent, TBuilder extends Builder<TEnt>,
|
|
47
|
+
export interface Action<TEnt extends Ent, TBuilder extends Builder<TEnt, TExistingEnt>, TInput extends Data, TExistingEnt extends TMaybleNullableEnt<TEnt> = MaybeNull<TEnt>> {
|
|
45
48
|
readonly viewer: Viewer;
|
|
46
49
|
changeset(): Promise<Changeset<TEnt>>;
|
|
47
50
|
builder: TBuilder;
|
|
48
51
|
getPrivacyPolicy(): PrivacyPolicy<TEnt>;
|
|
49
|
-
triggers?: Trigger<TBuilder,
|
|
50
|
-
observers?: Observer<TBuilder,
|
|
51
|
-
validators?: Validator<TBuilder,
|
|
52
|
-
getInput():
|
|
52
|
+
triggers?: Trigger<TEnt, TBuilder, TInput, TExistingEnt>[];
|
|
53
|
+
observers?: Observer<TEnt, TBuilder, TInput, TExistingEnt>[];
|
|
54
|
+
validators?: Validator<TEnt, TBuilder, TInput, TExistingEnt>[];
|
|
55
|
+
getInput(): TInput;
|
|
56
|
+
transformWrite?: (stmt: UpdateOperation<TEnt>) => Promise<TransformedUpdateOperation<TEnt>> | TransformedUpdateOperation<TEnt> | null;
|
|
53
57
|
valid(): Promise<boolean>;
|
|
54
58
|
validX(): Promise<void>;
|
|
55
59
|
viewerForEntLoad?(data: Data): Viewer | Promise<Viewer>;
|
package/action/executor.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ID,
|
|
1
|
+
import { ID, Ent, Viewer, Context, Data } from "../core/base";
|
|
2
2
|
import { DataOperation } from "../core/ent";
|
|
3
3
|
import { Changeset, Executor } from "../action/action";
|
|
4
4
|
import { Builder } from "../action";
|
|
@@ -10,7 +10,7 @@ export declare class ListBasedExecutor<T extends Ent> implements Executor {
|
|
|
10
10
|
private operations;
|
|
11
11
|
private options?;
|
|
12
12
|
private idx;
|
|
13
|
-
constructor(viewer: Viewer, placeholderID: ID, operations: DataOperation<T>[], options?: OrchestratorOptions<T, Data> | undefined);
|
|
13
|
+
constructor(viewer: Viewer, placeholderID: ID, operations: DataOperation<T>[], options?: OrchestratorOptions<T, Data, T | null> | undefined);
|
|
14
14
|
private lastOp;
|
|
15
15
|
private createdEnt;
|
|
16
16
|
resolveValue(val: ID): Ent | null;
|
|
@@ -18,9 +18,9 @@ export declare class BaseAction<TEnt extends Ent, TData extends Data> implements
|
|
|
18
18
|
builderCtr: BuilderConstructor<TEnt, TData>;
|
|
19
19
|
builder: EntBuilder<TEnt>;
|
|
20
20
|
private input;
|
|
21
|
-
triggers: Trigger<EntBuilder<TEnt>, TData>[];
|
|
22
|
-
observers: Observer<EntBuilder<TEnt>, TData>[];
|
|
23
|
-
validators: Validator<EntBuilder<TEnt>, TData>[];
|
|
21
|
+
triggers: Trigger<TEnt, EntBuilder<TEnt>, TData>[];
|
|
22
|
+
observers: Observer<TEnt, EntBuilder<TEnt>, TData>[];
|
|
23
|
+
validators: Validator<TEnt, EntBuilder<TEnt>, TData>[];
|
|
24
24
|
getPrivacyPolicy(): import("../core/base").PrivacyPolicy<Ent>;
|
|
25
25
|
constructor(viewer: Viewer, builderCtr: BuilderConstructor<TEnt, TData>, options?: ActionOptions<TEnt, TData> | null);
|
|
26
26
|
static createBuilder<TEnt extends Ent, TData extends Data>(viewer: Viewer, builderCtr: BuilderConstructor<TEnt, TData>, options?: ActionOptions<TEnt, TData> | null): Builder<TEnt>;
|
|
@@ -33,7 +33,7 @@ export declare class BaseAction<TEnt extends Ent, TData extends Data> implements
|
|
|
33
33
|
getInput(): TData;
|
|
34
34
|
}
|
|
35
35
|
interface BuilderConstructor<TEnt extends Ent, TData extends Data> {
|
|
36
|
-
new (viewer: Viewer, operation: WriteOperation, action: Action<TEnt, EntBuilder<TEnt>, TData>, existingEnt
|
|
36
|
+
new (viewer: Viewer, operation: WriteOperation, action: Action<TEnt, EntBuilder<TEnt>, TData>, existingEnt: TEnt | null): EntBuilder<TEnt>;
|
|
37
37
|
}
|
|
38
38
|
export declare function updateRawObject<TEnt extends Ent, TInput extends Data>(viewer: Viewer, builderCtr: BuilderConstructor<TEnt, TInput>, existingEnt: TEnt, input: TInput): Promise<TEnt>;
|
|
39
39
|
export declare function getSimpleEditAction<TEnt extends Ent, TInput extends Data>(viewer: Viewer, builderCtr: BuilderConstructor<TEnt, TInput>, existingEnt: TEnt, input: TInput): Action<TEnt, Builder<TEnt>, TInput>;
|
|
@@ -20,7 +20,7 @@ class BaseAction {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
this.input = options?.input || {};
|
|
23
|
-
this.builder = new builderCtr(viewer, operation, this, options?.existingEnt ||
|
|
23
|
+
this.builder = new builderCtr(viewer, operation, this, options?.existingEnt || null);
|
|
24
24
|
}
|
|
25
25
|
getPrivacyPolicy() {
|
|
26
26
|
return privacy_1.AlwaysAllowPrivacyPolicy;
|
package/action/orchestrator.d.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { ID, Data, Ent, Viewer, EntConstructor, LoadEntOptions } from "../core/base";
|
|
2
2
|
import { AssocEdgeInputOptions, DataOperation } from "../core/ent";
|
|
3
|
-
import { SchemaInputType } from "../schema/schema";
|
|
3
|
+
import { SchemaInputType, FieldInfoMap } from "../schema/schema";
|
|
4
4
|
import { Changeset, Executor } from "../action/action";
|
|
5
5
|
import { WriteOperation, Builder, Action } from "../action";
|
|
6
|
-
|
|
6
|
+
declare type MaybeNull<T extends Ent> = T | null;
|
|
7
|
+
declare type TMaybleNullableEnt<T extends Ent> = T | MaybeNull<T>;
|
|
8
|
+
export interface OrchestratorOptions<TEnt extends Ent, TData extends Data, TExistingEnt extends TMaybleNullableEnt<TEnt> = MaybeNull<TEnt>> {
|
|
7
9
|
viewer: Viewer;
|
|
8
10
|
operation: WriteOperation;
|
|
9
11
|
tableName: string;
|
|
10
12
|
loaderOptions: LoadEntOptions<TEnt>;
|
|
11
13
|
key: string;
|
|
12
|
-
builder: Builder<TEnt>;
|
|
14
|
+
builder: Builder<TEnt, TExistingEnt>;
|
|
13
15
|
action?: Action<TEnt, Builder<TEnt>, TData>;
|
|
14
16
|
schema: SchemaInputType;
|
|
15
|
-
editedFields(): Map<string, any
|
|
17
|
+
editedFields(): Map<string, any> | Promise<Map<string, any>>;
|
|
16
18
|
updateInput?: (data: TData) => void;
|
|
19
|
+
fieldInfo: FieldInfoMap;
|
|
17
20
|
}
|
|
18
21
|
interface edgeInputDataOpts {
|
|
19
22
|
edgeType: string;
|
|
@@ -28,7 +31,7 @@ export declare enum edgeDirection {
|
|
|
28
31
|
inboundEdge = 0,
|
|
29
32
|
outboundEdge = 1
|
|
30
33
|
}
|
|
31
|
-
export declare class Orchestrator<TEnt extends Ent, TData extends Data> {
|
|
34
|
+
export declare class Orchestrator<TEnt extends Ent, TData extends Data, TExistingEnt extends TMaybleNullableEnt<TEnt> = MaybeNull<TEnt>> {
|
|
32
35
|
private options;
|
|
33
36
|
private edgeSet;
|
|
34
37
|
private edges;
|
|
@@ -41,8 +44,13 @@ export declare class Orchestrator<TEnt extends Ent, TData extends Data> {
|
|
|
41
44
|
viewer: Viewer;
|
|
42
45
|
private defaultFieldsByFieldName;
|
|
43
46
|
private defaultFieldsByTSName;
|
|
44
|
-
|
|
47
|
+
private actualOperation;
|
|
48
|
+
private existingEnt;
|
|
49
|
+
private disableTransformations;
|
|
50
|
+
private memoizedGetFields;
|
|
51
|
+
constructor(options: OrchestratorOptions<TEnt, TData, TExistingEnt>);
|
|
45
52
|
private addEdge;
|
|
53
|
+
setDisableTransformations(val: boolean): void;
|
|
46
54
|
addInboundEdge<T2 extends Ent>(id1: ID | Builder<T2>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
|
47
55
|
addOutboundEdge<T2 extends Ent>(id2: ID | Builder<T2>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
|
48
56
|
removeInboundEdge(id1: ID, edgeType: string): void;
|
|
@@ -53,11 +61,18 @@ export declare class Orchestrator<TEnt extends Ent, TData extends Data> {
|
|
|
53
61
|
private getEdgeOperation;
|
|
54
62
|
private buildEdgeOps;
|
|
55
63
|
private throwError;
|
|
56
|
-
private
|
|
64
|
+
private getEntForPrivacyPolicyImpl;
|
|
65
|
+
private getSQLStatementOperation;
|
|
66
|
+
private getWriteOpForSQLStamentOp;
|
|
67
|
+
getPossibleUnsafeEntForPrivacy(): Promise<TEnt>;
|
|
68
|
+
getEditedData(): Promise<Data>;
|
|
69
|
+
private getFieldsInfo;
|
|
57
70
|
private validate;
|
|
58
71
|
private triggers;
|
|
59
72
|
private validators;
|
|
60
73
|
private isBuilder;
|
|
74
|
+
private getInputKey;
|
|
75
|
+
private getStorageKey;
|
|
61
76
|
private getFieldsWithDefaultValues;
|
|
62
77
|
private hasData;
|
|
63
78
|
private transformFieldValue;
|
|
@@ -75,11 +90,11 @@ export declare class EntChangeset<T extends Ent> implements Changeset<T> {
|
|
|
75
90
|
readonly placeholderID: ID;
|
|
76
91
|
readonly ent: EntConstructor<T>;
|
|
77
92
|
operations: DataOperation[];
|
|
78
|
-
dependencies?: Map<ID, Builder<Ent>> | undefined;
|
|
93
|
+
dependencies?: Map<ID, Builder<Ent, Ent | null>> | undefined;
|
|
79
94
|
changesets?: Changeset<Ent>[] | undefined;
|
|
80
95
|
private options?;
|
|
81
96
|
private _executor;
|
|
82
|
-
constructor(viewer: Viewer, placeholderID: ID, ent: EntConstructor<T>, operations: DataOperation[], dependencies?: Map<ID, Builder<Ent>> | undefined, changesets?: Changeset<Ent>[] | undefined, options?: OrchestratorOptions<T, Data
|
|
97
|
+
constructor(viewer: Viewer, placeholderID: ID, ent: EntConstructor<T>, operations: DataOperation[], dependencies?: Map<ID, Builder<Ent, Ent | null>> | undefined, changesets?: Changeset<Ent>[] | undefined, options?: OrchestratorOptions<T, Data, MaybeNull<T>> | undefined);
|
|
83
98
|
executor(): Executor;
|
|
84
99
|
}
|
|
85
100
|
export {};
|
package/action/orchestrator.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.EntChangeset = exports.Orchestrator = exports.edgeDirection = void 0;
|
|
4
7
|
const ent_1 = require("../core/ent");
|
|
5
8
|
const schema_1 = require("../schema/schema");
|
|
6
9
|
const action_1 = require("../action");
|
|
7
|
-
const snake_case_1 = require("snake-case");
|
|
8
|
-
const camel_case_1 = require("camel-case");
|
|
9
10
|
const privacy_1 = require("../core/privacy");
|
|
10
11
|
const executor_1 = require("./executor");
|
|
11
12
|
const logger_1 = require("../core/logger");
|
|
13
|
+
const memoizee_1 = __importDefault(require("memoizee"));
|
|
12
14
|
var edgeDirection;
|
|
13
15
|
(function (edgeDirection) {
|
|
14
16
|
edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
|
|
@@ -62,6 +64,9 @@ class Orchestrator {
|
|
|
62
64
|
this.defaultFieldsByFieldName = {};
|
|
63
65
|
this.defaultFieldsByTSName = {};
|
|
64
66
|
this.viewer = options.viewer;
|
|
67
|
+
this.actualOperation = this.options.operation;
|
|
68
|
+
this.existingEnt = this.options.builder.existingEnt;
|
|
69
|
+
this.memoizedGetFields = (0, memoizee_1.default)(this.getFieldsInfo.bind(this));
|
|
65
70
|
}
|
|
66
71
|
addEdge(edge, op) {
|
|
67
72
|
this.edgeSet.add(edge.edgeType);
|
|
@@ -80,6 +85,9 @@ class Orchestrator {
|
|
|
80
85
|
m1.set(op, m2);
|
|
81
86
|
this.edges.set(edge.edgeType, m1);
|
|
82
87
|
}
|
|
88
|
+
setDisableTransformations(val) {
|
|
89
|
+
this.disableTransformations = val;
|
|
90
|
+
}
|
|
83
91
|
addInboundEdge(id1, edgeType, nodeType, options) {
|
|
84
92
|
this.addEdge(new edgeInputData({
|
|
85
93
|
id: id1,
|
|
@@ -135,24 +143,27 @@ class Orchestrator {
|
|
|
135
143
|
}
|
|
136
144
|
buildMainOp() {
|
|
137
145
|
// this assumes we have validated fields
|
|
138
|
-
switch (this.
|
|
146
|
+
switch (this.actualOperation) {
|
|
139
147
|
case action_1.WriteOperation.Delete:
|
|
140
|
-
return new ent_1.DeleteNodeOperation(this.
|
|
148
|
+
return new ent_1.DeleteNodeOperation(this.existingEnt.id, {
|
|
141
149
|
tableName: this.options.tableName,
|
|
142
150
|
});
|
|
143
151
|
default:
|
|
152
|
+
if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
|
|
153
|
+
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
154
|
+
}
|
|
144
155
|
const opts = {
|
|
145
156
|
fields: this.validatedFields,
|
|
146
157
|
tableName: this.options.tableName,
|
|
147
158
|
fieldsToResolve: this.fieldsToResolve,
|
|
148
159
|
key: this.options.key,
|
|
149
|
-
|
|
160
|
+
loadEntOptions: this.options.loaderOptions,
|
|
150
161
|
placeholderID: this.options.builder.placeholderID,
|
|
151
162
|
};
|
|
152
163
|
if (this.logValues) {
|
|
153
164
|
opts.fieldsToLog = this.logValues;
|
|
154
165
|
}
|
|
155
|
-
this.mainOp = new ent_1.EditNodeOperation(opts, this.
|
|
166
|
+
this.mainOp = new ent_1.EditNodeOperation(opts, this.existingEnt);
|
|
156
167
|
return this.mainOp;
|
|
157
168
|
}
|
|
158
169
|
}
|
|
@@ -219,35 +230,82 @@ class Orchestrator {
|
|
|
219
230
|
if (!privacyPolicy || !action) {
|
|
220
231
|
throw new Error(`shouldn't get here if no privacyPolicy for action`);
|
|
221
232
|
}
|
|
222
|
-
if (this.
|
|
233
|
+
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
223
234
|
return new EntCannotCreateEntError(privacyPolicy, action);
|
|
224
235
|
}
|
|
225
|
-
else if (this.
|
|
226
|
-
return new EntCannotEditEntError(privacyPolicy, action, this.
|
|
236
|
+
else if (this.actualOperation === action_1.WriteOperation.Edit) {
|
|
237
|
+
return new EntCannotEditEntError(privacyPolicy, action, this.existingEnt);
|
|
227
238
|
}
|
|
228
|
-
return new EntCannotDeleteEntError(privacyPolicy, action, this.
|
|
239
|
+
return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
|
|
229
240
|
}
|
|
230
|
-
|
|
231
|
-
if (this.
|
|
232
|
-
return this.
|
|
241
|
+
getEntForPrivacyPolicyImpl(editedData) {
|
|
242
|
+
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
243
|
+
return this.existingEnt;
|
|
233
244
|
}
|
|
234
245
|
// we create an unsafe ent to be used for privacy policies
|
|
235
246
|
return new this.options.builder.ent(this.options.builder.viewer, editedData);
|
|
236
247
|
}
|
|
248
|
+
getSQLStatementOperation() {
|
|
249
|
+
switch (this.actualOperation) {
|
|
250
|
+
case action_1.WriteOperation.Edit:
|
|
251
|
+
return schema_1.SQLStatementOperation.Update;
|
|
252
|
+
case action_1.WriteOperation.Insert:
|
|
253
|
+
return schema_1.SQLStatementOperation.Insert;
|
|
254
|
+
case action_1.WriteOperation.Delete:
|
|
255
|
+
return schema_1.SQLStatementOperation.Delete;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
getWriteOpForSQLStamentOp(op) {
|
|
259
|
+
switch (op) {
|
|
260
|
+
case schema_1.SQLStatementOperation.Update:
|
|
261
|
+
return action_1.WriteOperation.Edit;
|
|
262
|
+
case schema_1.SQLStatementOperation.Insert:
|
|
263
|
+
return action_1.WriteOperation.Insert;
|
|
264
|
+
case schema_1.SQLStatementOperation.Update:
|
|
265
|
+
return action_1.WriteOperation.Delete;
|
|
266
|
+
default:
|
|
267
|
+
throw new Error("invalid path");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// if you're doing custom privacy within an action and want to
|
|
271
|
+
// get either the unsafe ent or the existing ent that's being edited
|
|
272
|
+
async getPossibleUnsafeEntForPrivacy() {
|
|
273
|
+
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
274
|
+
return this.existingEnt;
|
|
275
|
+
}
|
|
276
|
+
const { editedData } = await this.memoizedGetFields();
|
|
277
|
+
return this.getEntForPrivacyPolicyImpl(editedData);
|
|
278
|
+
}
|
|
279
|
+
// this gets the fields that were explicitly set plus any default or transformed values
|
|
280
|
+
// mainly exists to get default fields e.g. default id to be used in triggers
|
|
281
|
+
// NOTE: this API may change in the future
|
|
282
|
+
// doesn't work to get ids for autoincrement keys
|
|
283
|
+
async getEditedData() {
|
|
284
|
+
const { editedData } = await this.memoizedGetFields();
|
|
285
|
+
return editedData;
|
|
286
|
+
}
|
|
287
|
+
// Note: this is memoized. call memoizedGetFields instead
|
|
288
|
+
async getFieldsInfo() {
|
|
289
|
+
const action = this.options.action;
|
|
290
|
+
const builder = this.options.builder;
|
|
291
|
+
// future optimization: can get schemaFields to memoize based on different values
|
|
292
|
+
const schemaFields = (0, schema_1.getFields)(this.options.schema);
|
|
293
|
+
const editedFields = await this.options.editedFields();
|
|
294
|
+
let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
|
|
295
|
+
return { editedData, editedFields, schemaFields };
|
|
296
|
+
}
|
|
237
297
|
async validate() {
|
|
238
298
|
// existing ent required for edit or delete operations
|
|
239
|
-
switch (this.
|
|
299
|
+
switch (this.actualOperation) {
|
|
240
300
|
case action_1.WriteOperation.Delete:
|
|
241
301
|
case action_1.WriteOperation.Edit:
|
|
242
|
-
if (!this.
|
|
243
|
-
throw new Error(
|
|
302
|
+
if (!this.existingEnt) {
|
|
303
|
+
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
244
304
|
}
|
|
245
305
|
}
|
|
306
|
+
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
246
307
|
const action = this.options.action;
|
|
247
308
|
const builder = this.options.builder;
|
|
248
|
-
// future optimization: can get schemaFields to memoize based on different values
|
|
249
|
-
const schemaFields = (0, schema_1.getFields)(this.options.schema);
|
|
250
|
-
let editedData = this.getFieldsWithDefaultValues(builder, schemaFields, action);
|
|
251
309
|
// this runs in following phases:
|
|
252
310
|
// * set default fields and pass to builder so the value can be checked by triggers/observers/validators
|
|
253
311
|
// * privacy policy (use unsafe ent if we have it)
|
|
@@ -255,7 +313,7 @@ class Orchestrator {
|
|
|
255
313
|
// * validators
|
|
256
314
|
let privacyPolicy = action?.getPrivacyPolicy();
|
|
257
315
|
if (privacyPolicy) {
|
|
258
|
-
await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.
|
|
316
|
+
await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicyImpl(editedData), this.throwError.bind(this));
|
|
259
317
|
}
|
|
260
318
|
// have to run triggers which update fields first before field and other validators
|
|
261
319
|
// so running this first to build things up
|
|
@@ -264,8 +322,11 @@ class Orchestrator {
|
|
|
264
322
|
await this.triggers(action, builder, triggers);
|
|
265
323
|
}
|
|
266
324
|
let validators = action?.validators || [];
|
|
325
|
+
// not ideal we're calling this twice. fix...
|
|
326
|
+
// needed for now. may need to rewrite some of this?
|
|
327
|
+
const editedFields2 = await this.options.editedFields();
|
|
267
328
|
await Promise.all([
|
|
268
|
-
this.formatAndValidateFields(schemaFields),
|
|
329
|
+
this.formatAndValidateFields(schemaFields, editedFields2),
|
|
269
330
|
this.validators(validators, action, builder),
|
|
270
331
|
]);
|
|
271
332
|
}
|
|
@@ -300,18 +361,71 @@ class Orchestrator {
|
|
|
300
361
|
isBuilder(val) {
|
|
301
362
|
return val.placeholderID !== undefined;
|
|
302
363
|
}
|
|
303
|
-
|
|
304
|
-
|
|
364
|
+
getInputKey(k) {
|
|
365
|
+
return this.options.fieldInfo[k].inputKey;
|
|
366
|
+
}
|
|
367
|
+
getStorageKey(k) {
|
|
368
|
+
return this.options.fieldInfo[k].dbCol;
|
|
369
|
+
}
|
|
370
|
+
async getFieldsWithDefaultValues(builder, schemaFields, editedFields, action) {
|
|
305
371
|
let data = {};
|
|
306
372
|
let defaultData = {};
|
|
307
373
|
let input = action?.getInput() || {};
|
|
308
374
|
let updateInput = false;
|
|
375
|
+
// transformations
|
|
376
|
+
// if action transformations. always do it
|
|
377
|
+
// if disable transformations set, don't do schema transform and just do the right thing
|
|
378
|
+
// else apply schema tranformation if it exists
|
|
379
|
+
let transformed = null;
|
|
380
|
+
if (action?.transformWrite) {
|
|
381
|
+
transformed = await action.transformWrite({
|
|
382
|
+
viewer: builder.viewer,
|
|
383
|
+
op: this.getSQLStatementOperation(),
|
|
384
|
+
data: editedFields,
|
|
385
|
+
existingEnt: this.existingEnt,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
else if (!this.disableTransformations) {
|
|
389
|
+
transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
|
|
390
|
+
viewer: builder.viewer,
|
|
391
|
+
op: this.getSQLStatementOperation(),
|
|
392
|
+
data: editedFields,
|
|
393
|
+
existingEnt: this.existingEnt,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (transformed) {
|
|
397
|
+
if (transformed.data) {
|
|
398
|
+
updateInput = true;
|
|
399
|
+
for (const k in transformed.data) {
|
|
400
|
+
let field = schemaFields.get(k);
|
|
401
|
+
if (!field) {
|
|
402
|
+
throw new Error(`tried to transform field with unknown field ${k}`);
|
|
403
|
+
}
|
|
404
|
+
let val = transformed.data[k];
|
|
405
|
+
if (field.format) {
|
|
406
|
+
val = field.format(transformed.data[k]);
|
|
407
|
+
}
|
|
408
|
+
data[this.getStorageKey(k)] = val;
|
|
409
|
+
this.defaultFieldsByTSName[this.getInputKey(k)] = val;
|
|
410
|
+
// hmm do we need this?
|
|
411
|
+
// TODO how to do this for local tests?
|
|
412
|
+
// this.defaultFieldsByFieldName[k] = val;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
|
|
416
|
+
if (transformed.existingEnt) {
|
|
417
|
+
// @ts-ignore
|
|
418
|
+
this.existingEnt = transformed.existingEnt;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// transforming before doing default fields so that we don't create a new id
|
|
422
|
+
// and anything that depends on the type of operations knows what it is
|
|
309
423
|
for (const [fieldName, field] of schemaFields) {
|
|
310
424
|
let value = editedFields.get(fieldName);
|
|
311
425
|
let defaultValue = undefined;
|
|
312
|
-
let dbKey =
|
|
426
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
313
427
|
if (value === undefined) {
|
|
314
|
-
if (this.
|
|
428
|
+
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
315
429
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
316
430
|
throw new Error(`cannot set both defaultToViewerOnCreate and defaultValueOnCreate`);
|
|
317
431
|
}
|
|
@@ -326,7 +440,7 @@ class Orchestrator {
|
|
|
326
440
|
}
|
|
327
441
|
}
|
|
328
442
|
if (field.defaultValueOnEdit &&
|
|
329
|
-
this.
|
|
443
|
+
this.actualOperation === action_1.WriteOperation.Edit) {
|
|
330
444
|
defaultValue = field.defaultValueOnEdit(builder, input);
|
|
331
445
|
}
|
|
332
446
|
}
|
|
@@ -337,8 +451,7 @@ class Orchestrator {
|
|
|
337
451
|
updateInput = true;
|
|
338
452
|
defaultData[dbKey] = defaultValue;
|
|
339
453
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
340
|
-
|
|
341
|
-
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
|
|
454
|
+
this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
|
|
342
455
|
}
|
|
343
456
|
}
|
|
344
457
|
// if there's data changing, add data
|
|
@@ -374,13 +487,13 @@ class Orchestrator {
|
|
|
374
487
|
// not setting server default as we're depending on the database handling that.
|
|
375
488
|
// server default allowed
|
|
376
489
|
field.serverDefault === undefined &&
|
|
377
|
-
this.
|
|
490
|
+
this.actualOperation === action_1.WriteOperation.Insert) {
|
|
378
491
|
throw new Error(`required field ${fieldName} not set`);
|
|
379
492
|
}
|
|
380
493
|
}
|
|
381
494
|
else if (this.isBuilder(value)) {
|
|
382
495
|
if (field.valid) {
|
|
383
|
-
const valid = await
|
|
496
|
+
const valid = await field.valid(value);
|
|
384
497
|
if (!valid) {
|
|
385
498
|
throw new Error(`invalid field ${fieldName} with value ${value}`);
|
|
386
499
|
}
|
|
@@ -393,24 +506,22 @@ class Orchestrator {
|
|
|
393
506
|
else {
|
|
394
507
|
if (field.valid) {
|
|
395
508
|
// TODO this could be async. handle this better
|
|
396
|
-
const valid = await
|
|
509
|
+
const valid = await field.valid(value);
|
|
397
510
|
if (!valid) {
|
|
398
511
|
throw new Error(`invalid field ${fieldName} with value ${value}`);
|
|
399
512
|
}
|
|
400
513
|
}
|
|
401
514
|
if (field.format) {
|
|
402
|
-
|
|
403
|
-
value = await Promise.resolve(field.format(value));
|
|
515
|
+
value = await field.format(value);
|
|
404
516
|
}
|
|
405
517
|
}
|
|
406
518
|
return value;
|
|
407
519
|
}
|
|
408
|
-
async formatAndValidateFields(schemaFields) {
|
|
409
|
-
const op = this.
|
|
520
|
+
async formatAndValidateFields(schemaFields, editedFields) {
|
|
521
|
+
const op = this.actualOperation;
|
|
410
522
|
if (op === action_1.WriteOperation.Delete) {
|
|
411
523
|
return;
|
|
412
524
|
}
|
|
413
|
-
const editedFields = this.options.editedFields();
|
|
414
525
|
// build up data to be saved...
|
|
415
526
|
let data = {};
|
|
416
527
|
let logValues = {};
|
|
@@ -420,7 +531,7 @@ class Orchestrator {
|
|
|
420
531
|
// null allowed
|
|
421
532
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
422
533
|
}
|
|
423
|
-
let dbKey =
|
|
534
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
424
535
|
value = await this.transformFieldValue(fieldName, field, dbKey, value);
|
|
425
536
|
if (value !== undefined) {
|
|
426
537
|
data[dbKey] = value;
|
|
@@ -433,7 +544,7 @@ class Orchestrator {
|
|
|
433
544
|
for (const fieldName in this.defaultFieldsByFieldName) {
|
|
434
545
|
const defaultValue = this.defaultFieldsByFieldName[fieldName];
|
|
435
546
|
let field = schemaFields.get(fieldName);
|
|
436
|
-
let dbKey =
|
|
547
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
437
548
|
// no value, let's just default
|
|
438
549
|
if (data[dbKey] === undefined) {
|
|
439
550
|
const value = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
|
|
@@ -494,7 +605,7 @@ class Orchestrator {
|
|
|
494
605
|
const viewer = await this.viewerForEntLoad(row);
|
|
495
606
|
const ent = await (0, ent_1.applyPrivacyPolicyForRow)(viewer, this.options.loaderOptions, row);
|
|
496
607
|
if (!ent) {
|
|
497
|
-
if (this.
|
|
608
|
+
if (this.actualOperation == action_1.WriteOperation.Insert) {
|
|
498
609
|
throw new Error(`was able to create ent but not load it`);
|
|
499
610
|
}
|
|
500
611
|
else {
|
package/action/privacy.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { Builder } from "./action";
|
|
|
2
2
|
import { Viewer, ID, Ent, PrivacyResult, PrivacyPolicyRule } from "../core/base";
|
|
3
3
|
export declare class DenyIfBuilder implements PrivacyPolicyRule {
|
|
4
4
|
private id?;
|
|
5
|
-
constructor(id?: ID | Builder<Ent> | undefined);
|
|
5
|
+
constructor(id?: ID | Builder<Ent, Ent | null> | undefined);
|
|
6
6
|
apply(_v: Viewer, _ent: Ent): Promise<PrivacyResult>;
|
|
7
7
|
}
|
|
8
8
|
export declare class AllowIfBuilder implements PrivacyPolicyRule {
|
|
9
9
|
private id?;
|
|
10
|
-
constructor(id?: ID | Builder<Ent> | undefined);
|
|
10
|
+
constructor(id?: ID | Builder<Ent, Ent | null> | undefined);
|
|
11
11
|
apply(_v: Viewer, _ent: Ent): Promise<PrivacyResult>;
|
|
12
12
|
}
|
package/core/base.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface Viewer {
|
|
|
43
43
|
export interface Ent {
|
|
44
44
|
id: ID;
|
|
45
45
|
viewer: Viewer;
|
|
46
|
-
|
|
46
|
+
getPrivacyPolicy(): PrivacyPolicy<this>;
|
|
47
47
|
nodeType: string;
|
|
48
48
|
}
|
|
49
49
|
export declare type Data = {
|
|
@@ -62,6 +62,7 @@ export interface SelectBaseDataOptions extends DataOptions {
|
|
|
62
62
|
}
|
|
63
63
|
export interface SelectDataOptions extends SelectBaseDataOptions {
|
|
64
64
|
key: string;
|
|
65
|
+
clause?: clause.Clause | (() => clause.Clause | undefined);
|
|
65
66
|
}
|
|
66
67
|
export interface QueryableDataOptions extends SelectBaseDataOptions, QueryDataOptions {
|
|
67
68
|
}
|
|
@@ -88,9 +89,11 @@ interface LoadableEntOptions<T extends Ent> {
|
|
|
88
89
|
ent: EntConstructor<T>;
|
|
89
90
|
}
|
|
90
91
|
export interface LoadEntOptions<T extends Ent> extends LoadableEntOptions<T>, SelectBaseDataOptions {
|
|
92
|
+
fieldPrivacy?: Map<string, PrivacyPolicy>;
|
|
91
93
|
}
|
|
92
94
|
export interface LoadCustomEntOptions<T extends Ent> extends SelectBaseDataOptions {
|
|
93
95
|
ent: EntConstructor<T>;
|
|
96
|
+
fieldPrivacy?: Map<string, PrivacyPolicy>;
|
|
94
97
|
}
|
|
95
98
|
export interface LoaderInfo {
|
|
96
99
|
tableName: string;
|
|
@@ -108,6 +111,7 @@ declare enum privacyResult {
|
|
|
108
111
|
export interface PrivacyResult {
|
|
109
112
|
result: privacyResult;
|
|
110
113
|
error?: PrivacyError;
|
|
114
|
+
getError?(policy: PrivacyPolicy, rule: PrivacyPolicyRule, ent?: Ent): PrivacyError;
|
|
111
115
|
}
|
|
112
116
|
export interface PrivacyError extends Error {
|
|
113
117
|
privacyPolicy: PrivacyPolicy<Ent>;
|
|
@@ -116,7 +120,7 @@ export interface PrivacyError extends Error {
|
|
|
116
120
|
export declare function Allow(): PrivacyResult;
|
|
117
121
|
export declare function Skip(): PrivacyResult;
|
|
118
122
|
export declare function Deny(): PrivacyResult;
|
|
119
|
-
export declare function DenyWithReason(e: PrivacyError): PrivacyResult;
|
|
123
|
+
export declare function DenyWithReason(e: PrivacyError | string): PrivacyResult;
|
|
120
124
|
export interface PrivacyPolicyRule<TEnt extends Ent = Ent> {
|
|
121
125
|
apply(v: Viewer, ent?: TEnt): Promise<PrivacyResult>;
|
|
122
126
|
}
|
package/core/base.js
CHANGED
|
@@ -30,7 +30,23 @@ function Deny() {
|
|
|
30
30
|
return deny;
|
|
31
31
|
}
|
|
32
32
|
exports.Deny = Deny;
|
|
33
|
+
class DenyWithReasonError extends Error {
|
|
34
|
+
constructor(privacyPolicy, rule, msg, ent) {
|
|
35
|
+
super(msg);
|
|
36
|
+
this.privacyPolicy = privacyPolicy;
|
|
37
|
+
this.privacyRule = rule;
|
|
38
|
+
this.ent = ent;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
33
41
|
function DenyWithReason(e) {
|
|
42
|
+
if (typeof e === "string") {
|
|
43
|
+
return {
|
|
44
|
+
result: privacyResult.Deny,
|
|
45
|
+
getError(policy, rule, ent) {
|
|
46
|
+
return new DenyWithReasonError(policy, rule, e, ent);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
34
50
|
return {
|
|
35
51
|
result: privacyResult.Deny,
|
|
36
52
|
error: e,
|