@snowtop/ent 0.1.0-alpha16 → 0.1.0-alpha160-test2
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 +25 -14
- package/action/action.js +22 -7
- package/action/executor.d.ts +16 -3
- package/action/executor.js +89 -28
- package/action/experimental_action.d.ts +25 -16
- package/action/experimental_action.js +34 -14
- package/action/index.d.ts +4 -1
- package/action/index.js +7 -1
- package/action/operations.d.ts +126 -0
- package/action/operations.js +686 -0
- package/action/orchestrator.d.ts +43 -12
- package/action/orchestrator.js +461 -101
- package/action/relative_value.d.ts +47 -0
- package/action/relative_value.js +125 -0
- package/action/transaction.d.ts +10 -0
- package/action/transaction.js +23 -0
- package/auth/auth.d.ts +1 -1
- package/core/base.d.ts +56 -23
- package/core/base.js +7 -1
- package/core/clause.d.ts +103 -39
- package/core/clause.js +430 -66
- package/core/config.d.ts +13 -3
- package/core/config.js +10 -1
- package/core/const.d.ts +3 -0
- package/core/const.js +6 -0
- package/core/context.d.ts +6 -3
- package/core/context.js +22 -3
- package/core/convert.d.ts +1 -1
- package/core/date.js +1 -5
- package/core/db.d.ts +12 -8
- package/core/db.js +21 -9
- package/core/ent.d.ts +99 -95
- package/core/ent.js +550 -602
- package/core/global_schema.d.ts +7 -0
- package/core/global_schema.js +51 -0
- package/core/loaders/assoc_count_loader.d.ts +5 -2
- package/core/loaders/assoc_count_loader.js +19 -3
- package/core/loaders/assoc_edge_loader.d.ts +2 -3
- package/core/loaders/assoc_edge_loader.js +23 -17
- package/core/loaders/index.d.ts +1 -2
- package/core/loaders/index.js +1 -5
- package/core/loaders/loader.d.ts +3 -3
- package/core/loaders/loader.js +4 -21
- package/core/loaders/object_loader.d.ts +30 -9
- package/core/loaders/object_loader.js +226 -79
- package/core/loaders/query_loader.d.ts +7 -13
- package/core/loaders/query_loader.js +60 -24
- package/core/loaders/raw_count_loader.d.ts +1 -0
- package/core/loaders/raw_count_loader.js +8 -3
- package/core/logger.d.ts +1 -1
- package/core/logger.js +1 -0
- package/core/privacy.d.ts +26 -16
- package/core/privacy.js +68 -51
- package/core/query/assoc_query.d.ts +3 -2
- package/core/query/assoc_query.js +10 -2
- package/core/query/custom_clause_query.d.ts +29 -0
- package/core/query/custom_clause_query.js +105 -0
- package/core/query/custom_query.d.ts +19 -2
- package/core/query/custom_query.js +111 -13
- package/core/query/index.d.ts +1 -0
- package/core/query/index.js +3 -1
- package/core/query/query.d.ts +18 -4
- package/core/query/query.js +135 -58
- package/core/query/shared_assoc_test.d.ts +2 -1
- package/core/query/shared_assoc_test.js +186 -55
- package/core/query/shared_test.d.ts +9 -2
- package/core/query/shared_test.js +529 -236
- package/core/query_impl.d.ts +8 -0
- package/core/query_impl.js +28 -0
- package/core/viewer.d.ts +2 -0
- package/core/viewer.js +3 -1
- package/graphql/graphql.d.ts +108 -22
- package/graphql/graphql.js +183 -137
- package/graphql/graphql_field_helpers.d.ts +9 -3
- package/graphql/graphql_field_helpers.js +22 -2
- package/graphql/index.d.ts +2 -2
- package/graphql/index.js +5 -5
- package/graphql/query/connection_type.d.ts +9 -9
- package/graphql/query/shared_assoc_test.js +1 -1
- package/graphql/query/shared_edge_connection.js +1 -19
- package/graphql/scalars/orderby_direction.d.ts +2 -0
- package/graphql/scalars/orderby_direction.js +15 -0
- package/imports/dataz/example1/_auth.js +128 -47
- package/imports/dataz/example1/_viewer.js +87 -39
- package/imports/index.d.ts +7 -2
- package/imports/index.js +20 -5
- package/index.d.ts +23 -5
- package/index.js +35 -10
- package/package.json +20 -19
- package/parse_schema/parse.d.ts +33 -9
- package/parse_schema/parse.js +182 -33
- package/schema/base_schema.d.ts +13 -3
- package/schema/base_schema.js +13 -0
- package/schema/field.d.ts +78 -21
- package/schema/field.js +232 -72
- package/schema/index.d.ts +2 -2
- package/schema/index.js +7 -2
- package/schema/json_field.d.ts +16 -4
- package/schema/json_field.js +32 -2
- package/schema/schema.d.ts +109 -20
- package/schema/schema.js +42 -53
- package/schema/struct_field.d.ts +15 -3
- package/schema/struct_field.js +117 -22
- package/schema/union_field.d.ts +1 -1
- package/scripts/custom_compiler.js +12 -8
- package/scripts/custom_graphql.js +167 -64
- package/scripts/migrate_v0.1.js +36 -0
- package/scripts/move_types.js +120 -0
- package/scripts/read_schema.js +22 -7
- package/testutils/action/complex_schemas.d.ts +69 -0
- package/testutils/action/complex_schemas.js +405 -0
- package/testutils/builder.d.ts +37 -41
- package/testutils/builder.js +66 -46
- package/testutils/db/fixture.d.ts +10 -0
- package/testutils/db/fixture.js +26 -0
- package/testutils/db/{test_db.d.ts → temp_db.d.ts} +32 -8
- package/testutils/db/{test_db.js → temp_db.js} +251 -48
- package/testutils/db/value.d.ts +7 -0
- package/testutils/db/value.js +251 -0
- package/testutils/db_mock.d.ts +16 -4
- package/testutils/db_mock.js +52 -9
- package/testutils/db_time_zone.d.ts +4 -0
- package/testutils/db_time_zone.js +41 -0
- package/testutils/ent-graphql-tests/index.d.ts +7 -1
- package/testutils/ent-graphql-tests/index.js +56 -26
- package/testutils/fake_comms.js +1 -1
- package/testutils/fake_data/const.d.ts +2 -1
- package/testutils/fake_data/const.js +3 -0
- package/testutils/fake_data/fake_contact.d.ts +7 -3
- package/testutils/fake_data/fake_contact.js +13 -7
- package/testutils/fake_data/fake_event.d.ts +4 -1
- package/testutils/fake_data/fake_event.js +7 -6
- package/testutils/fake_data/fake_tag.d.ts +36 -0
- package/testutils/fake_data/fake_tag.js +89 -0
- package/testutils/fake_data/fake_user.d.ts +8 -5
- package/testutils/fake_data/fake_user.js +31 -19
- package/testutils/fake_data/index.js +5 -1
- package/testutils/fake_data/internal.d.ts +2 -0
- package/testutils/fake_data/internal.js +7 -1
- package/testutils/fake_data/tag_query.d.ts +13 -0
- package/testutils/fake_data/tag_query.js +48 -0
- package/testutils/fake_data/test_helpers.d.ts +14 -6
- package/testutils/fake_data/test_helpers.js +31 -15
- package/testutils/fake_data/user_query.d.ts +16 -6
- package/testutils/fake_data/user_query.js +72 -23
- package/testutils/fake_log.js +1 -1
- package/testutils/parse_sql.d.ts +6 -0
- package/testutils/parse_sql.js +16 -2
- package/testutils/test_edge_global_schema.d.ts +15 -0
- package/testutils/test_edge_global_schema.js +62 -0
- package/testutils/write.d.ts +2 -2
- package/testutils/write.js +33 -7
- package/tsc/ast.d.ts +15 -3
- package/tsc/ast.js +114 -23
- package/tsc/compilerOptions.js +5 -1
- package/tsc/move_generated.d.ts +1 -0
- package/tsc/move_generated.js +164 -0
- package/tsc/transform.d.ts +22 -0
- package/tsc/transform.js +182 -0
- package/tsc/transform_action.d.ts +22 -0
- package/tsc/transform_action.js +183 -0
- package/tsc/transform_ent.d.ts +17 -0
- package/tsc/transform_ent.js +60 -0
- package/tsc/transform_schema.d.ts +27 -0
- package/{scripts → tsc}/transform_schema.js +146 -117
- package/core/loaders/index_loader.d.ts +0 -14
- package/core/loaders/index_loader.js +0 -27
- package/graphql/enums.d.ts +0 -3
- package/graphql/enums.js +0 -25
- package/scripts/move_generated.js +0 -141
- package/scripts/transform_actions.js +0 -266
- package/scripts/transform_code.d.ts +0 -1
- package/scripts/transform_code.js +0 -111
- package/scripts/transform_schema.d.ts +0 -1
- /package/scripts/{move_generated.d.ts → migrate_v0.1.d.ts} +0 -0
- /package/scripts/{transform_actions.d.ts → move_types.d.ts} +0 -0
package/action/orchestrator.js
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
27
|
};
|
|
@@ -6,11 +29,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
29
|
exports.EntChangeset = exports.Orchestrator = exports.edgeDirection = void 0;
|
|
7
30
|
const ent_1 = require("../core/ent");
|
|
8
31
|
const schema_1 = require("../schema/schema");
|
|
32
|
+
const operations_1 = require("./operations");
|
|
9
33
|
const action_1 = require("../action");
|
|
10
34
|
const privacy_1 = require("../core/privacy");
|
|
11
35
|
const executor_1 = require("./executor");
|
|
12
36
|
const logger_1 = require("../core/logger");
|
|
13
37
|
const memoizee_1 = __importDefault(require("memoizee"));
|
|
38
|
+
const clause = __importStar(require("../core/clause"));
|
|
39
|
+
const types_1 = require("util/types");
|
|
40
|
+
const operations_2 = require("./operations");
|
|
14
41
|
var edgeDirection;
|
|
15
42
|
(function (edgeDirection) {
|
|
16
43
|
edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
|
|
@@ -24,31 +51,38 @@ class edgeInputData {
|
|
|
24
51
|
return id.placeholderID !== undefined;
|
|
25
52
|
}
|
|
26
53
|
}
|
|
27
|
-
function getViewer(
|
|
28
|
-
if (!
|
|
54
|
+
function getViewer(viewer) {
|
|
55
|
+
if (!viewer.viewerID) {
|
|
29
56
|
return "Logged out Viewer";
|
|
30
57
|
}
|
|
31
58
|
else {
|
|
32
|
-
return `Viewer with ID ${
|
|
59
|
+
return `Viewer with ID ${viewer.viewerID}`;
|
|
33
60
|
}
|
|
34
61
|
}
|
|
35
62
|
class EntCannotCreateEntError extends Error {
|
|
36
63
|
constructor(privacyPolicy, action) {
|
|
37
|
-
let msg = `${getViewer(action)} does not have permission to create ${action.builder.ent.name}`;
|
|
64
|
+
let msg = `${getViewer(action.viewer)} does not have permission to create ${action.builder.ent.name}`;
|
|
38
65
|
super(msg);
|
|
39
66
|
this.privacyPolicy = privacyPolicy;
|
|
40
67
|
}
|
|
41
68
|
}
|
|
42
69
|
class EntCannotEditEntError extends Error {
|
|
43
70
|
constructor(privacyPolicy, action, ent) {
|
|
44
|
-
let msg = `${getViewer(action)} does not have permission to edit ${ent.constructor.name}`;
|
|
71
|
+
let msg = `${getViewer(action.viewer)} does not have permission to edit ${ent.constructor.name}`;
|
|
72
|
+
super(msg);
|
|
73
|
+
this.privacyPolicy = privacyPolicy;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
class EntCannotEditEntFieldError extends Error {
|
|
77
|
+
constructor(privacyPolicy, viewer, field, ent) {
|
|
78
|
+
let msg = `${getViewer(viewer)} does not have permission to edit field ${field} in ${ent.constructor.name}`;
|
|
45
79
|
super(msg);
|
|
46
80
|
this.privacyPolicy = privacyPolicy;
|
|
47
81
|
}
|
|
48
82
|
}
|
|
49
83
|
class EntCannotDeleteEntError extends Error {
|
|
50
84
|
constructor(privacyPolicy, action, ent) {
|
|
51
|
-
let msg = `${getViewer(action)} does not have permission to delete ${ent.constructor.name}`;
|
|
85
|
+
let msg = `${getViewer(action.viewer)} does not have permission to delete ${ent.constructor.name}`;
|
|
52
86
|
super(msg);
|
|
53
87
|
this.privacyPolicy = privacyPolicy;
|
|
54
88
|
}
|
|
@@ -58,6 +92,7 @@ class Orchestrator {
|
|
|
58
92
|
this.options = options;
|
|
59
93
|
this.edgeSet = new Set();
|
|
60
94
|
this.edges = new Map();
|
|
95
|
+
this.conditionalEdges = new Map();
|
|
61
96
|
this.changesets = [];
|
|
62
97
|
this.dependencies = new Map();
|
|
63
98
|
this.fieldsToResolve = [];
|
|
@@ -68,7 +103,11 @@ class Orchestrator {
|
|
|
68
103
|
this.existingEnt = this.options.builder.existingEnt;
|
|
69
104
|
this.memoizedGetFields = (0, memoizee_1.default)(this.getFieldsInfo.bind(this));
|
|
70
105
|
}
|
|
71
|
-
|
|
106
|
+
// don't type this because we don't care
|
|
107
|
+
__getOptions() {
|
|
108
|
+
return this.options;
|
|
109
|
+
}
|
|
110
|
+
addEdge(edge, op, conditional) {
|
|
72
111
|
this.edgeSet.add(edge.edgeType);
|
|
73
112
|
let m1 = this.edges.get(edge.edgeType) || new Map();
|
|
74
113
|
let m2 = m1.get(op) || new Map();
|
|
@@ -83,11 +122,22 @@ class Orchestrator {
|
|
|
83
122
|
// set or overwrite the new edge data for said id
|
|
84
123
|
m2.set(id, edge);
|
|
85
124
|
m1.set(op, m2);
|
|
86
|
-
this.
|
|
125
|
+
if (conditional && this.onConflict) {
|
|
126
|
+
this.conditionalEdges.set(edge.edgeType, m1);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.edges.set(edge.edgeType, m1);
|
|
130
|
+
}
|
|
87
131
|
}
|
|
88
132
|
setDisableTransformations(val) {
|
|
89
133
|
this.disableTransformations = val;
|
|
90
134
|
}
|
|
135
|
+
setOnConflictOptions(onConflict) {
|
|
136
|
+
if (onConflict?.onConflictConstraint && !onConflict.updateCols) {
|
|
137
|
+
throw new Error(`cannot set onConflictConstraint without updateCols`);
|
|
138
|
+
}
|
|
139
|
+
this.onConflict = onConflict;
|
|
140
|
+
}
|
|
91
141
|
addInboundEdge(id1, edgeType, nodeType, options) {
|
|
92
142
|
this.addEdge(new edgeInputData({
|
|
93
143
|
id: id1,
|
|
@@ -95,7 +145,7 @@ class Orchestrator {
|
|
|
95
145
|
nodeType,
|
|
96
146
|
options,
|
|
97
147
|
direction: edgeDirection.inboundEdge,
|
|
98
|
-
}), action_1.WriteOperation.Insert);
|
|
148
|
+
}), action_1.WriteOperation.Insert, options?.conditional);
|
|
99
149
|
}
|
|
100
150
|
addOutboundEdge(id2, edgeType, nodeType, options) {
|
|
101
151
|
this.addEdge(new edgeInputData({
|
|
@@ -104,21 +154,23 @@ class Orchestrator {
|
|
|
104
154
|
nodeType,
|
|
105
155
|
options,
|
|
106
156
|
direction: edgeDirection.outboundEdge,
|
|
107
|
-
}), action_1.WriteOperation.Insert);
|
|
157
|
+
}), action_1.WriteOperation.Insert, options?.conditional);
|
|
108
158
|
}
|
|
109
|
-
removeInboundEdge(id1, edgeType) {
|
|
159
|
+
removeInboundEdge(id1, edgeType, options) {
|
|
110
160
|
this.addEdge(new edgeInputData({
|
|
111
161
|
id: id1,
|
|
112
162
|
edgeType,
|
|
113
163
|
direction: edgeDirection.inboundEdge,
|
|
114
|
-
|
|
164
|
+
options,
|
|
165
|
+
}), action_1.WriteOperation.Delete, options?.conditional);
|
|
115
166
|
}
|
|
116
|
-
removeOutboundEdge(id2, edgeType) {
|
|
167
|
+
removeOutboundEdge(id2, edgeType, options) {
|
|
117
168
|
this.addEdge(new edgeInputData({
|
|
118
169
|
id: id2,
|
|
119
170
|
edgeType,
|
|
120
171
|
direction: edgeDirection.outboundEdge,
|
|
121
|
-
|
|
172
|
+
options,
|
|
173
|
+
}), action_1.WriteOperation.Delete, options?.conditional);
|
|
122
174
|
}
|
|
123
175
|
// this doesn't take a direction as that's an implementation detail
|
|
124
176
|
// it doesn't make any sense to use the same edgeType for inbound and outbound edges
|
|
@@ -141,29 +193,39 @@ class Orchestrator {
|
|
|
141
193
|
m.clear();
|
|
142
194
|
}
|
|
143
195
|
}
|
|
144
|
-
buildMainOp() {
|
|
196
|
+
buildMainOp(conditionalBuilder) {
|
|
145
197
|
// this assumes we have validated fields
|
|
146
198
|
switch (this.actualOperation) {
|
|
147
199
|
case action_1.WriteOperation.Delete:
|
|
148
|
-
return new
|
|
200
|
+
return new operations_1.DeleteNodeOperation(this.existingEnt.id, this.options.builder, {
|
|
149
201
|
tableName: this.options.tableName,
|
|
150
202
|
});
|
|
151
203
|
default:
|
|
152
204
|
if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
|
|
153
205
|
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
154
206
|
}
|
|
207
|
+
if (this.options.expressions &&
|
|
208
|
+
this.actualOperation !== action_1.WriteOperation.Edit) {
|
|
209
|
+
throw new Error(`expressions are only supported in edit operations for now`);
|
|
210
|
+
}
|
|
155
211
|
const opts = {
|
|
156
212
|
fields: this.validatedFields,
|
|
157
213
|
tableName: this.options.tableName,
|
|
158
214
|
fieldsToResolve: this.fieldsToResolve,
|
|
159
215
|
key: this.options.key,
|
|
160
216
|
loadEntOptions: this.options.loaderOptions,
|
|
161
|
-
|
|
217
|
+
whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
|
|
218
|
+
expressions: this.options.expressions,
|
|
219
|
+
onConflict: this.onConflict,
|
|
220
|
+
builder: this.options.builder,
|
|
162
221
|
};
|
|
163
222
|
if (this.logValues) {
|
|
164
223
|
opts.fieldsToLog = this.logValues;
|
|
165
224
|
}
|
|
166
|
-
this.mainOp = new
|
|
225
|
+
this.mainOp = new operations_1.EditNodeOperation(opts, this.existingEnt);
|
|
226
|
+
if (conditionalBuilder) {
|
|
227
|
+
this.mainOp = new operations_1.ConditionalNodeOperation(this.mainOp, conditionalBuilder);
|
|
228
|
+
}
|
|
167
229
|
return this.mainOp;
|
|
168
230
|
}
|
|
169
231
|
}
|
|
@@ -183,10 +245,10 @@ class Orchestrator {
|
|
|
183
245
|
throw new Error(`no nodeType for edge when adding outboundEdge`);
|
|
184
246
|
}
|
|
185
247
|
if (edge.direction === edgeDirection.outboundEdge) {
|
|
186
|
-
return
|
|
248
|
+
return operations_1.EdgeOperation.outboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
|
|
187
249
|
}
|
|
188
250
|
else {
|
|
189
|
-
return
|
|
251
|
+
return operations_1.EdgeOperation.inboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
|
|
190
252
|
}
|
|
191
253
|
}
|
|
192
254
|
else if (op === action_1.WriteOperation.Delete) {
|
|
@@ -195,30 +257,55 @@ class Orchestrator {
|
|
|
195
257
|
}
|
|
196
258
|
let id2 = edge.id;
|
|
197
259
|
if (edge.direction === edgeDirection.outboundEdge) {
|
|
198
|
-
return
|
|
260
|
+
return operations_1.EdgeOperation.removeOutboundEdge(this.options.builder, edgeType, id2, edge.options);
|
|
199
261
|
}
|
|
200
262
|
else {
|
|
201
|
-
return
|
|
263
|
+
return operations_1.EdgeOperation.removeInboundEdge(this.options.builder, edgeType, id2, edge.options);
|
|
202
264
|
}
|
|
203
265
|
}
|
|
204
266
|
throw new Error("could not find an edge operation from the given parameters");
|
|
205
267
|
}
|
|
206
|
-
async buildEdgeOps(ops) {
|
|
268
|
+
async buildEdgeOps(ops, conditionalBuilder, conditionalOverride) {
|
|
207
269
|
const edgeDatas = await (0, ent_1.loadEdgeDatas)(...Array.from(this.edgeSet.values()));
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
270
|
+
const edges = [
|
|
271
|
+
[this.edges, false],
|
|
272
|
+
[this.conditionalEdges, true],
|
|
273
|
+
];
|
|
274
|
+
// conditional should only apply if onconflict...
|
|
275
|
+
// if no upsert and just create, nothing to do here
|
|
276
|
+
for (const edgeInfo of edges) {
|
|
277
|
+
const [edges, conditionalEdge] = edgeInfo;
|
|
278
|
+
const conditional = conditionalOverride || conditionalEdge;
|
|
279
|
+
for (const [edgeType, m] of edges) {
|
|
280
|
+
for (const [op, m2] of m) {
|
|
281
|
+
for (const [_, edge] of m2) {
|
|
282
|
+
let edgeOp = this.getEdgeOperation(edgeType, op, edge);
|
|
283
|
+
if (conditional) {
|
|
284
|
+
ops.push(new operations_1.ConditionalOperation(edgeOp, conditionalBuilder));
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
ops.push(edgeOp);
|
|
288
|
+
}
|
|
289
|
+
const edgeData = edgeDatas.get(edgeType);
|
|
290
|
+
if (!edgeData) {
|
|
291
|
+
throw new Error(`could not load edge data for '${edgeType}'`);
|
|
292
|
+
}
|
|
293
|
+
// similar logic in EntChangeset.changesetFromEdgeOp
|
|
294
|
+
// doesn't support conditional edges
|
|
295
|
+
if (edgeData.symmetricEdge) {
|
|
296
|
+
let symmetric = edgeOp.symmetricEdge();
|
|
297
|
+
if (conditional) {
|
|
298
|
+
symmetric = new operations_1.ConditionalOperation(symmetric, conditionalBuilder);
|
|
299
|
+
}
|
|
300
|
+
ops.push(symmetric);
|
|
301
|
+
}
|
|
302
|
+
if (edgeData.inverseEdgeType) {
|
|
303
|
+
let inverse = edgeOp.inverseEdge(edgeData);
|
|
304
|
+
if (conditional) {
|
|
305
|
+
inverse = new operations_1.ConditionalOperation(inverse, conditionalBuilder);
|
|
306
|
+
}
|
|
307
|
+
ops.push(inverse);
|
|
308
|
+
}
|
|
222
309
|
}
|
|
223
310
|
}
|
|
224
311
|
}
|
|
@@ -238,12 +325,50 @@ class Orchestrator {
|
|
|
238
325
|
}
|
|
239
326
|
return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
|
|
240
327
|
}
|
|
241
|
-
|
|
328
|
+
async getRowForPrivacyPolicyImpl(schemaFields, editedData) {
|
|
329
|
+
// need to format fields if possible because ent constructors expect data that's
|
|
330
|
+
// in the format that's coming from the db
|
|
331
|
+
// required for object fields...
|
|
332
|
+
const formatted = { ...editedData };
|
|
333
|
+
for (const [fieldName, field] of schemaFields) {
|
|
334
|
+
if (!field.format) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
338
|
+
let val = formatted[dbKey];
|
|
339
|
+
if (!val) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (field.valid) {
|
|
343
|
+
let valid = field.valid(val);
|
|
344
|
+
if ((0, types_1.isPromise)(valid)) {
|
|
345
|
+
valid = await valid;
|
|
346
|
+
}
|
|
347
|
+
// if not valid, don't format and don't pass to ent?
|
|
348
|
+
// or just early throw here
|
|
349
|
+
if (!valid) {
|
|
350
|
+
continue;
|
|
351
|
+
// throw new Error(`invalid field ${fieldName} with value ${val}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// nested so it's not JSON stringified or anything like that
|
|
355
|
+
val = field.format(formatted[dbKey], true);
|
|
356
|
+
if ((0, types_1.isPromise)(val)) {
|
|
357
|
+
val = await val;
|
|
358
|
+
}
|
|
359
|
+
formatted[dbKey] = val;
|
|
360
|
+
}
|
|
361
|
+
return formatted;
|
|
362
|
+
}
|
|
363
|
+
async getEntForPrivacyPolicyImpl(schemaFields, editedData, viewerToUse, rowToUse) {
|
|
242
364
|
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
243
365
|
return this.existingEnt;
|
|
244
366
|
}
|
|
367
|
+
if (!rowToUse) {
|
|
368
|
+
rowToUse = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
|
|
369
|
+
}
|
|
245
370
|
// we create an unsafe ent to be used for privacy policies
|
|
246
|
-
return new this.options.builder.ent(
|
|
371
|
+
return new this.options.builder.ent(viewerToUse, rowToUse);
|
|
247
372
|
}
|
|
248
373
|
getSQLStatementOperation() {
|
|
249
374
|
switch (this.actualOperation) {
|
|
@@ -273,8 +398,8 @@ class Orchestrator {
|
|
|
273
398
|
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
274
399
|
return this.existingEnt;
|
|
275
400
|
}
|
|
276
|
-
const { editedData } = await this.memoizedGetFields();
|
|
277
|
-
return this.getEntForPrivacyPolicyImpl(editedData);
|
|
401
|
+
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
402
|
+
return this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
|
|
278
403
|
}
|
|
279
404
|
// this gets the fields that were explicitly set plus any default or transformed values
|
|
280
405
|
// mainly exists to get default fields e.g. default id to be used in triggers
|
|
@@ -284,15 +409,41 @@ class Orchestrator {
|
|
|
284
409
|
const { editedData } = await this.memoizedGetFields();
|
|
285
410
|
return editedData;
|
|
286
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* @returns validated and formatted fields that would be written to the db
|
|
414
|
+
* throws an error if called before valid() or validX() has been called
|
|
415
|
+
*/
|
|
416
|
+
getValidatedFields() {
|
|
417
|
+
if (this.validatedFields === null) {
|
|
418
|
+
throw new Error(`trying to call getValidatedFields before validating fields`);
|
|
419
|
+
}
|
|
420
|
+
return this.validatedFields;
|
|
421
|
+
}
|
|
287
422
|
// Note: this is memoized. call memoizedGetFields instead
|
|
288
423
|
async getFieldsInfo() {
|
|
289
424
|
const action = this.options.action;
|
|
290
425
|
const builder = this.options.builder;
|
|
291
426
|
// future optimization: can get schemaFields to memoize based on different values
|
|
292
427
|
const schemaFields = (0, schema_1.getFields)(this.options.schema);
|
|
428
|
+
// also future optimization, no need to go through the list of fields multiple times
|
|
429
|
+
let editPrivacyFields = new Map();
|
|
430
|
+
switch (this.actualOperation) {
|
|
431
|
+
case action_1.WriteOperation.Edit:
|
|
432
|
+
editPrivacyFields = (0, schema_1.getFieldsWithEditPrivacy)(this.options.schema, this.options.fieldInfo);
|
|
433
|
+
break;
|
|
434
|
+
case action_1.WriteOperation.Insert:
|
|
435
|
+
editPrivacyFields = (0, schema_1.getFieldsForCreateAction)(this.options.schema, this.options.fieldInfo);
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
293
438
|
const editedFields = await this.options.editedFields();
|
|
294
|
-
let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
|
|
295
|
-
return {
|
|
439
|
+
let { data: editedData, userDefinedKeys } = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
|
|
440
|
+
return {
|
|
441
|
+
editedData,
|
|
442
|
+
editedFields,
|
|
443
|
+
schemaFields,
|
|
444
|
+
userDefinedKeys,
|
|
445
|
+
editPrivacyFields,
|
|
446
|
+
};
|
|
296
447
|
}
|
|
297
448
|
async validate() {
|
|
298
449
|
// existing ent required for edit or delete operations
|
|
@@ -303,7 +454,7 @@ class Orchestrator {
|
|
|
303
454
|
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
304
455
|
}
|
|
305
456
|
}
|
|
306
|
-
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
457
|
+
const { schemaFields, editedData, userDefinedKeys, editPrivacyFields } = await this.memoizedGetFields();
|
|
307
458
|
const action = this.options.action;
|
|
308
459
|
const builder = this.options.builder;
|
|
309
460
|
// this runs in following phases:
|
|
@@ -312,8 +463,40 @@ class Orchestrator {
|
|
|
312
463
|
// * triggers
|
|
313
464
|
// * validators
|
|
314
465
|
let privacyPolicy = action?.getPrivacyPolicy();
|
|
466
|
+
const errors = [];
|
|
315
467
|
if (privacyPolicy) {
|
|
316
|
-
|
|
468
|
+
const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
|
|
469
|
+
try {
|
|
470
|
+
await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, ent, () => this.throwError());
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
errors.push(err);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// we have edit privacy fields, so we need to apply privacy policy on those
|
|
477
|
+
const promises = [];
|
|
478
|
+
if (editPrivacyFields.size) {
|
|
479
|
+
// get row based on edited data
|
|
480
|
+
const row = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
|
|
481
|
+
// get viewer for ent load based on formatted row
|
|
482
|
+
const viewer = await this.viewerForEntLoad(row);
|
|
483
|
+
const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, viewer, row);
|
|
484
|
+
for (const [k, policy] of editPrivacyFields) {
|
|
485
|
+
if (editedData[k] === undefined || !userDefinedKeys.has(k)) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
promises.push((async () => {
|
|
489
|
+
const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
|
|
490
|
+
if (!r) {
|
|
491
|
+
errors.push(new EntCannotEditEntFieldError(policy, viewer, k, ent));
|
|
492
|
+
}
|
|
493
|
+
})());
|
|
494
|
+
}
|
|
495
|
+
await Promise.all(promises);
|
|
496
|
+
}
|
|
497
|
+
// privacy or field errors should return first so it's less confusing
|
|
498
|
+
if (errors.length) {
|
|
499
|
+
return errors;
|
|
317
500
|
}
|
|
318
501
|
// have to run triggers which update fields first before field and other validators
|
|
319
502
|
// so running this first to build things up
|
|
@@ -327,38 +510,70 @@ class Orchestrator {
|
|
|
327
510
|
// not ideal we're calling this twice. fix...
|
|
328
511
|
// needed for now. may need to rewrite some of this?
|
|
329
512
|
const editedFields2 = await this.options.editedFields();
|
|
330
|
-
await Promise.all([
|
|
513
|
+
const [errs2, errs3] = await Promise.all([
|
|
331
514
|
this.formatAndValidateFields(schemaFields, editedFields2),
|
|
332
515
|
this.validators(validators, action, builder),
|
|
333
516
|
]);
|
|
517
|
+
errors.push(...errs2);
|
|
518
|
+
errors.push(...errs3);
|
|
519
|
+
return errors;
|
|
334
520
|
}
|
|
335
521
|
async triggers(action, builder, triggers) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (Array.isArray(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
522
|
+
let groups = [];
|
|
523
|
+
let lastArray = 0;
|
|
524
|
+
let prevWasArray = false;
|
|
525
|
+
for (let i = 0; i < triggers.length; i++) {
|
|
526
|
+
let t = triggers[i];
|
|
527
|
+
if (Array.isArray(t)) {
|
|
528
|
+
if (!prevWasArray) {
|
|
529
|
+
// @ts-ignore
|
|
530
|
+
groups.push(triggers.slice(lastArray, i));
|
|
346
531
|
}
|
|
532
|
+
groups.push(t);
|
|
533
|
+
prevWasArray = true;
|
|
534
|
+
lastArray++;
|
|
347
535
|
}
|
|
348
|
-
else
|
|
349
|
-
|
|
536
|
+
else {
|
|
537
|
+
if (i === triggers.length - 1) {
|
|
538
|
+
// @ts-ignore
|
|
539
|
+
groups.push(triggers.slice(lastArray, i + 1));
|
|
540
|
+
}
|
|
541
|
+
prevWasArray = false;
|
|
350
542
|
}
|
|
351
|
-
}
|
|
543
|
+
}
|
|
544
|
+
for (const triggers of groups) {
|
|
545
|
+
await Promise.all(triggers.map(async (trigger) => {
|
|
546
|
+
let ret = await trigger.changeset(builder, action.getInput());
|
|
547
|
+
if (Array.isArray(ret)) {
|
|
548
|
+
ret = await Promise.all(ret);
|
|
549
|
+
}
|
|
550
|
+
if (Array.isArray(ret)) {
|
|
551
|
+
for (const v of ret) {
|
|
552
|
+
if (typeof v === "object") {
|
|
553
|
+
this.changesets.push(v);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
else if (ret) {
|
|
558
|
+
this.changesets.push(ret);
|
|
559
|
+
}
|
|
560
|
+
}));
|
|
561
|
+
}
|
|
352
562
|
}
|
|
353
563
|
async validators(validators, action, builder) {
|
|
354
|
-
|
|
355
|
-
validators.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
564
|
+
const errors = [];
|
|
565
|
+
await Promise.all(validators.map(async (v) => {
|
|
566
|
+
try {
|
|
567
|
+
const r = await v.validate(builder, action.getInput());
|
|
568
|
+
if (r instanceof Error) {
|
|
569
|
+
errors.push(r);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
errors.push(err);
|
|
359
574
|
}
|
|
360
|
-
});
|
|
361
|
-
|
|
575
|
+
}));
|
|
576
|
+
return errors;
|
|
362
577
|
}
|
|
363
578
|
isBuilder(val) {
|
|
364
579
|
return val.placeholderID !== undefined;
|
|
@@ -379,23 +594,31 @@ class Orchestrator {
|
|
|
379
594
|
// if disable transformations set, don't do schema transform and just do the right thing
|
|
380
595
|
// else apply schema tranformation if it exists
|
|
381
596
|
let transformed = null;
|
|
597
|
+
const sqlOp = this.getSQLStatementOperation();
|
|
598
|
+
// why is transform write technically different from upsert?
|
|
599
|
+
// it's create -> update just at the db level...
|
|
382
600
|
if (action?.transformWrite) {
|
|
383
601
|
transformed = await action.transformWrite({
|
|
384
|
-
|
|
385
|
-
|
|
602
|
+
builder,
|
|
603
|
+
input,
|
|
604
|
+
op: sqlOp,
|
|
386
605
|
data: editedFields,
|
|
387
|
-
existingEnt: this.existingEnt,
|
|
388
606
|
});
|
|
389
607
|
}
|
|
390
608
|
else if (!this.disableTransformations) {
|
|
391
609
|
transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
|
|
392
|
-
|
|
393
|
-
|
|
610
|
+
builder,
|
|
611
|
+
input,
|
|
612
|
+
op: sqlOp,
|
|
394
613
|
data: editedFields,
|
|
395
|
-
existingEnt: this.existingEnt,
|
|
396
614
|
});
|
|
397
615
|
}
|
|
398
616
|
if (transformed) {
|
|
617
|
+
if (sqlOp === schema_1.SQLStatementOperation.Insert && sqlOp !== transformed.op) {
|
|
618
|
+
if (!transformed.existingEnt) {
|
|
619
|
+
throw new Error(`cannot transform an insert operation without providing an existing ent`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
399
622
|
if (transformed.data) {
|
|
400
623
|
updateInput = true;
|
|
401
624
|
for (const k in transformed.data) {
|
|
@@ -414,18 +637,29 @@ class Orchestrator {
|
|
|
414
637
|
// this.defaultFieldsByFieldName[k] = val;
|
|
415
638
|
}
|
|
416
639
|
}
|
|
640
|
+
if (transformed.changeset) {
|
|
641
|
+
const changeset = await transformed.changeset();
|
|
642
|
+
this.changesets.push(changeset);
|
|
643
|
+
}
|
|
417
644
|
this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
|
|
418
645
|
if (transformed.existingEnt) {
|
|
419
646
|
// @ts-ignore
|
|
420
647
|
this.existingEnt = transformed.existingEnt;
|
|
648
|
+
// modify existing ent in builder. it's readonly in generated ents but doesn't apply here
|
|
649
|
+
builder.existingEnt = transformed.existingEnt;
|
|
421
650
|
}
|
|
422
651
|
}
|
|
423
652
|
// transforming before doing default fields so that we don't create a new id
|
|
424
653
|
// and anything that depends on the type of operations knows what it is
|
|
654
|
+
const userDefinedKeys = new Set();
|
|
425
655
|
for (const [fieldName, field] of schemaFields) {
|
|
426
656
|
let value = editedFields.get(fieldName);
|
|
427
657
|
let defaultValue = undefined;
|
|
428
658
|
let dbKey = this.getStorageKey(fieldName);
|
|
659
|
+
let updateOnlyIfOther = field.onlyUpdateIfOtherFieldsBeingSet_BETA;
|
|
660
|
+
if (value !== undefined) {
|
|
661
|
+
userDefinedKeys.add(dbKey);
|
|
662
|
+
}
|
|
429
663
|
if (value === undefined) {
|
|
430
664
|
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
431
665
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
@@ -439,11 +673,17 @@ class Orchestrator {
|
|
|
439
673
|
if (defaultValue === undefined) {
|
|
440
674
|
throw new Error(`defaultValueOnCreate() returned undefined for field ${fieldName}`);
|
|
441
675
|
}
|
|
676
|
+
if ((0, types_1.isPromise)(defaultValue)) {
|
|
677
|
+
defaultValue = await defaultValue;
|
|
678
|
+
}
|
|
442
679
|
}
|
|
443
680
|
}
|
|
444
681
|
if (field.defaultValueOnEdit &&
|
|
445
682
|
this.actualOperation === action_1.WriteOperation.Edit) {
|
|
446
683
|
defaultValue = field.defaultValueOnEdit(builder, input);
|
|
684
|
+
if ((0, types_1.isPromise)(defaultValue)) {
|
|
685
|
+
defaultValue = await defaultValue;
|
|
686
|
+
}
|
|
447
687
|
}
|
|
448
688
|
}
|
|
449
689
|
if (value !== undefined) {
|
|
@@ -451,7 +691,12 @@ class Orchestrator {
|
|
|
451
691
|
}
|
|
452
692
|
if (defaultValue !== undefined) {
|
|
453
693
|
updateInput = true;
|
|
454
|
-
|
|
694
|
+
if (updateOnlyIfOther) {
|
|
695
|
+
defaultData[dbKey] = defaultValue;
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
data[dbKey] = defaultValue;
|
|
699
|
+
}
|
|
455
700
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
456
701
|
this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
|
|
457
702
|
}
|
|
@@ -467,7 +712,7 @@ class Orchestrator {
|
|
|
467
712
|
this.options.updateInput(this.defaultFieldsByTSName);
|
|
468
713
|
}
|
|
469
714
|
}
|
|
470
|
-
return data;
|
|
715
|
+
return { data, userDefinedKeys };
|
|
471
716
|
}
|
|
472
717
|
hasData(data) {
|
|
473
718
|
for (const _k in data) {
|
|
@@ -479,7 +724,7 @@ class Orchestrator {
|
|
|
479
724
|
// now format and validate...
|
|
480
725
|
if (value === null) {
|
|
481
726
|
if (!field.nullable) {
|
|
482
|
-
|
|
727
|
+
return new Error(`field ${fieldName} set to null for non-nullable field`);
|
|
483
728
|
}
|
|
484
729
|
}
|
|
485
730
|
else if (value === undefined) {
|
|
@@ -490,14 +735,17 @@ class Orchestrator {
|
|
|
490
735
|
// server default allowed
|
|
491
736
|
field.serverDefault === undefined &&
|
|
492
737
|
this.actualOperation === action_1.WriteOperation.Insert) {
|
|
493
|
-
|
|
738
|
+
return new Error(`required field ${fieldName} not set`);
|
|
494
739
|
}
|
|
495
740
|
}
|
|
496
741
|
else if (this.isBuilder(value)) {
|
|
497
742
|
if (field.valid) {
|
|
498
|
-
|
|
743
|
+
let valid = field.valid(value);
|
|
744
|
+
if ((0, types_1.isPromise)(valid)) {
|
|
745
|
+
valid = await valid;
|
|
746
|
+
}
|
|
499
747
|
if (!valid) {
|
|
500
|
-
|
|
748
|
+
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
501
749
|
}
|
|
502
750
|
}
|
|
503
751
|
// keep track of dependencies to resolve
|
|
@@ -507,10 +755,12 @@ class Orchestrator {
|
|
|
507
755
|
}
|
|
508
756
|
else {
|
|
509
757
|
if (field.valid) {
|
|
510
|
-
|
|
511
|
-
|
|
758
|
+
let valid = field.valid(value);
|
|
759
|
+
if ((0, types_1.isPromise)(valid)) {
|
|
760
|
+
valid = await valid;
|
|
761
|
+
}
|
|
512
762
|
if (!valid) {
|
|
513
|
-
|
|
763
|
+
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
514
764
|
}
|
|
515
765
|
}
|
|
516
766
|
if (field.format) {
|
|
@@ -520,26 +770,52 @@ class Orchestrator {
|
|
|
520
770
|
return value;
|
|
521
771
|
}
|
|
522
772
|
async formatAndValidateFields(schemaFields, editedFields) {
|
|
773
|
+
const errors = [];
|
|
523
774
|
const op = this.actualOperation;
|
|
524
775
|
if (op === action_1.WriteOperation.Delete) {
|
|
525
|
-
return;
|
|
776
|
+
return [];
|
|
526
777
|
}
|
|
527
778
|
// build up data to be saved...
|
|
528
779
|
let data = {};
|
|
529
780
|
let logValues = {};
|
|
781
|
+
let needsFullDataChecks = [];
|
|
530
782
|
for (const [fieldName, field] of schemaFields) {
|
|
531
783
|
let value = editedFields.get(fieldName);
|
|
784
|
+
if (field.validateWithFullData) {
|
|
785
|
+
needsFullDataChecks.push(fieldName);
|
|
786
|
+
}
|
|
532
787
|
if (value === undefined && op === action_1.WriteOperation.Insert) {
|
|
533
788
|
// null allowed
|
|
534
789
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
535
790
|
}
|
|
536
791
|
let dbKey = this.getStorageKey(fieldName);
|
|
537
|
-
|
|
792
|
+
let ret = await this.transformFieldValue(fieldName, field, dbKey, value);
|
|
793
|
+
if (ret instanceof Error) {
|
|
794
|
+
errors.push(ret);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
value = ret;
|
|
798
|
+
}
|
|
538
799
|
if (value !== undefined) {
|
|
539
800
|
data[dbKey] = value;
|
|
540
801
|
logValues[dbKey] = field.logValue(value);
|
|
541
802
|
}
|
|
542
803
|
}
|
|
804
|
+
for (const fieldName of needsFullDataChecks) {
|
|
805
|
+
const field = schemaFields.get(fieldName);
|
|
806
|
+
let value = editedFields.get(fieldName);
|
|
807
|
+
// @ts-ignore...
|
|
808
|
+
// type hackery because it's hard
|
|
809
|
+
const v = await field.validateWithFullData(value, this.options.builder);
|
|
810
|
+
if (!v) {
|
|
811
|
+
if (value === undefined) {
|
|
812
|
+
errors.push(new Error(`field ${fieldName} set to undefined when it can't be nullable`));
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
errors.push(new Error(`field ${fieldName} set to null when it can't be nullable`));
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
543
819
|
// we ignored default values while editing.
|
|
544
820
|
// if we're editing and there's data, add default values
|
|
545
821
|
if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
|
|
@@ -549,41 +825,73 @@ class Orchestrator {
|
|
|
549
825
|
let dbKey = this.getStorageKey(fieldName);
|
|
550
826
|
// no value, let's just default
|
|
551
827
|
if (data[dbKey] === undefined) {
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
828
|
+
const ret = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
|
|
829
|
+
if (ret instanceof Error) {
|
|
830
|
+
errors.push(ret);
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
data[dbKey] = ret;
|
|
834
|
+
logValues[dbKey] = field.logValue(ret);
|
|
835
|
+
}
|
|
555
836
|
}
|
|
556
837
|
}
|
|
557
838
|
}
|
|
558
839
|
this.validatedFields = data;
|
|
559
840
|
this.logValues = logValues;
|
|
841
|
+
return errors;
|
|
560
842
|
}
|
|
561
843
|
async valid() {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
catch (e) {
|
|
566
|
-
(0, logger_1.log)("error", e);
|
|
844
|
+
const errors = await this.validate();
|
|
845
|
+
if (errors.length) {
|
|
846
|
+
errors.map((err) => (0, logger_1.log)("error", err));
|
|
567
847
|
return false;
|
|
568
848
|
}
|
|
569
849
|
return true;
|
|
570
850
|
}
|
|
571
851
|
async validX() {
|
|
852
|
+
const errors = await this.validate();
|
|
853
|
+
if (errors.length) {
|
|
854
|
+
// just throw the first one...
|
|
855
|
+
// TODO we should ideally throw all of them
|
|
856
|
+
throw errors[0];
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* @experimental API that's not guaranteed to remain in the future which returns
|
|
861
|
+
* a list of errors encountered
|
|
862
|
+
* 0 errors indicates valid
|
|
863
|
+
* NOTE that this currently doesn't catch errors returned by validators().
|
|
864
|
+
* If those throws, this still throws and doesn't return them
|
|
865
|
+
*/
|
|
866
|
+
async validWithErrors() {
|
|
572
867
|
return this.validate();
|
|
573
868
|
}
|
|
574
|
-
async
|
|
869
|
+
async buildPlusChangeset(conditionalBuilder, conditionalOverride) {
|
|
575
870
|
// validate everything first
|
|
576
871
|
await this.validX();
|
|
577
|
-
let ops = [
|
|
578
|
-
|
|
579
|
-
|
|
872
|
+
let ops = [
|
|
873
|
+
this.buildMainOp(conditionalOverride ? conditionalBuilder : undefined),
|
|
874
|
+
];
|
|
875
|
+
await this.buildEdgeOps(ops, conditionalBuilder, conditionalOverride);
|
|
876
|
+
// TODO throw if we try and create a new changeset after previously creating one
|
|
877
|
+
// TODO test actualOperation value
|
|
878
|
+
// observers is fine since they're run after and we have the actualOperation value...
|
|
879
|
+
return new EntChangeset(this.options.viewer, this.options.builder, this.options.builder.placeholderID, conditionalOverride, ops, this.dependencies, this.changesets, this.options);
|
|
880
|
+
}
|
|
881
|
+
async build() {
|
|
882
|
+
return this.buildPlusChangeset(this.options.builder, false);
|
|
883
|
+
}
|
|
884
|
+
async buildWithOptions_BETA(options) {
|
|
885
|
+
// set as dependency so that we do the right order of operations
|
|
886
|
+
this.dependencies.set(options.conditionalBuilder.placeholderID, options.conditionalBuilder);
|
|
887
|
+
return this.buildPlusChangeset(options.conditionalBuilder, true);
|
|
580
888
|
}
|
|
581
889
|
async viewerForEntLoad(data) {
|
|
582
890
|
const action = this.options.action;
|
|
583
891
|
if (!action || !action.viewerForEntLoad) {
|
|
584
892
|
return this.options.viewer;
|
|
585
893
|
}
|
|
586
|
-
return action.viewerForEntLoad(data);
|
|
894
|
+
return action.viewerForEntLoad(data, action.builder.viewer.context);
|
|
587
895
|
}
|
|
588
896
|
async returnedRow() {
|
|
589
897
|
if (this.mainOp && this.mainOp.returnedRow) {
|
|
@@ -618,16 +926,62 @@ class Orchestrator {
|
|
|
618
926
|
}
|
|
619
927
|
}
|
|
620
928
|
exports.Orchestrator = Orchestrator;
|
|
929
|
+
function randomNum() {
|
|
930
|
+
return Math.random().toString(10).substring(2);
|
|
931
|
+
}
|
|
932
|
+
// each changeset is required to have a unique placeholderID
|
|
933
|
+
// used in executor. if we end up creating multiple changesets from a builder, we need
|
|
934
|
+
// different placeholders
|
|
935
|
+
// in practice, only applies to Entchangeset::changesetFrom()
|
|
621
936
|
class EntChangeset {
|
|
622
|
-
constructor(viewer, placeholderID,
|
|
937
|
+
constructor(viewer, builder, placeholderID, conditionalOverride, operations, dependencies, changesets, options) {
|
|
623
938
|
this.viewer = viewer;
|
|
939
|
+
this.builder = builder;
|
|
624
940
|
this.placeholderID = placeholderID;
|
|
625
|
-
this.
|
|
941
|
+
this.conditionalOverride = conditionalOverride;
|
|
626
942
|
this.operations = operations;
|
|
627
943
|
this.dependencies = dependencies;
|
|
628
944
|
this.changesets = changesets;
|
|
629
945
|
this.options = options;
|
|
630
946
|
}
|
|
947
|
+
static changesetFrom(builder, ops) {
|
|
948
|
+
return new EntChangeset(builder.viewer, builder,
|
|
949
|
+
// need unique placeholderID different from the builder. see comment above EntChangeset
|
|
950
|
+
`$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, false, ops);
|
|
951
|
+
}
|
|
952
|
+
static changesetFromQueries(builder, queries) {
|
|
953
|
+
return EntChangeset.changesetFrom(builder, [
|
|
954
|
+
new operations_2.RawQueryOperation(builder, queries),
|
|
955
|
+
]);
|
|
956
|
+
}
|
|
957
|
+
static async changesetFromEdgeOp(builder, op, edgeType) {
|
|
958
|
+
const edgeData = await (0, ent_1.loadEdgeData)(edgeType);
|
|
959
|
+
const ops = [op];
|
|
960
|
+
if (!edgeData) {
|
|
961
|
+
throw new Error(`could not load edge data for '${edgeType}'`);
|
|
962
|
+
}
|
|
963
|
+
// similar logic in Orchestrator.buildEdgeOps
|
|
964
|
+
// doesn't support conditional edges
|
|
965
|
+
if (edgeData.symmetricEdge) {
|
|
966
|
+
ops.push(op.symmetricEdge());
|
|
967
|
+
}
|
|
968
|
+
if (edgeData.inverseEdgeType) {
|
|
969
|
+
ops.push(op.inverseEdge(edgeData));
|
|
970
|
+
}
|
|
971
|
+
return EntChangeset.changesetFrom(builder, ops);
|
|
972
|
+
}
|
|
973
|
+
static async changesetFromOutboundEdge(builder, edgeType, id2, nodeType, options) {
|
|
974
|
+
return EntChangeset.changesetFromEdgeOp(builder, operations_1.EdgeOperation.outboundEdge(builder, edgeType, id2, nodeType, options), edgeType);
|
|
975
|
+
}
|
|
976
|
+
static async changesetFromInboundEdge(builder, edgeType, id1, nodeType, options) {
|
|
977
|
+
return EntChangeset.changesetFromEdgeOp(builder, operations_1.EdgeOperation.inboundEdge(builder, edgeType, id1, nodeType, options), edgeType);
|
|
978
|
+
}
|
|
979
|
+
static changesetRemoveFromOutboundEdge(builder, edgeType, id2, options) {
|
|
980
|
+
return EntChangeset.changesetFromEdgeOp(builder, operations_1.EdgeOperation.removeOutboundEdge(builder, edgeType, id2, options), edgeType);
|
|
981
|
+
}
|
|
982
|
+
static changesetRemoveFromInboundEdge(builder, edgeType, id1, options) {
|
|
983
|
+
return EntChangeset.changesetFromEdgeOp(builder, operations_1.EdgeOperation.removeInboundEdge(builder, edgeType, id1, options), edgeType);
|
|
984
|
+
}
|
|
631
985
|
executor() {
|
|
632
986
|
if (this._executor) {
|
|
633
987
|
return this._executor;
|
|
@@ -637,9 +991,15 @@ class EntChangeset {
|
|
|
637
991
|
// executor and depend on something else in the stack to handle this correctly
|
|
638
992
|
// ComplexExecutor which could be a parent of this should make sure the dependency
|
|
639
993
|
// is resolved beforehand
|
|
640
|
-
return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options
|
|
641
|
-
|
|
642
|
-
|
|
994
|
+
return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options, {
|
|
995
|
+
conditionalOverride: this.conditionalOverride,
|
|
996
|
+
builder: this.builder,
|
|
997
|
+
}));
|
|
998
|
+
}
|
|
999
|
+
return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options, {
|
|
1000
|
+
conditionalOverride: this.conditionalOverride,
|
|
1001
|
+
builder: this.builder,
|
|
1002
|
+
}));
|
|
643
1003
|
}
|
|
644
1004
|
}
|
|
645
1005
|
exports.EntChangeset = EntChangeset;
|