@snowtop/ent 0.1.0-alpha15 → 0.1.0-alpha151
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 +27 -16
- package/action/action.js +22 -7
- package/action/executor.d.ts +16 -3
- package/action/executor.js +90 -23
- package/action/experimental_action.d.ts +25 -16
- package/action/experimental_action.js +35 -9
- package/action/index.d.ts +3 -1
- package/action/index.js +7 -1
- package/action/operations.d.ts +125 -0
- package/action/operations.js +684 -0
- package/action/orchestrator.d.ts +38 -12
- package/action/orchestrator.js +427 -102
- 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 +52 -21
- package/core/base.js +7 -1
- package/core/clause.d.ts +95 -40
- package/core/clause.js +395 -64
- package/core/config.d.ts +15 -1
- package/core/config.js +10 -1
- package/core/const.d.ts +3 -0
- package/core/const.js +6 -0
- package/core/context.d.ts +4 -2
- package/core/context.js +20 -2
- package/core/convert.d.ts +1 -1
- package/core/date.js +1 -5
- package/core/db.d.ts +12 -8
- package/core/db.js +18 -8
- package/core/ent.d.ts +68 -94
- package/core/ent.js +538 -587
- package/core/global_schema.d.ts +7 -0
- package/core/global_schema.js +51 -0
- package/core/loaders/assoc_count_loader.d.ts +1 -0
- package/core/loaders/assoc_count_loader.js +10 -2
- package/core/loaders/assoc_edge_loader.d.ts +1 -1
- package/core/loaders/assoc_edge_loader.js +10 -13
- package/core/loaders/index.d.ts +1 -1
- package/core/loaders/index.js +1 -3
- package/core/loaders/index_loader.d.ts +3 -3
- package/core/loaders/loader.d.ts +2 -2
- package/core/loaders/loader.js +5 -5
- package/core/loaders/object_loader.d.ts +30 -9
- package/core/loaders/object_loader.js +225 -78
- package/core/loaders/query_loader.d.ts +6 -12
- package/core/loaders/query_loader.js +54 -13
- package/core/loaders/raw_count_loader.d.ts +1 -0
- package/core/loaders/raw_count_loader.js +7 -2
- package/core/logger.d.ts +1 -1
- package/core/logger.js +1 -0
- package/core/privacy.d.ts +7 -6
- package/core/privacy.js +21 -25
- package/core/query/assoc_query.d.ts +3 -2
- package/core/query/assoc_query.js +9 -1
- package/core/query/custom_clause_query.d.ts +27 -0
- package/core/query/custom_clause_query.js +88 -0
- package/core/query/custom_query.d.ts +17 -2
- package/core/query/custom_query.js +88 -13
- package/core/query/index.d.ts +1 -0
- package/core/query/index.js +3 -1
- package/core/query/query.d.ts +15 -3
- package/core/query/query.js +128 -53
- package/core/query/shared_assoc_test.d.ts +2 -1
- package/core/query/shared_assoc_test.js +44 -54
- package/core/query/shared_test.d.ts +8 -1
- package/core/query/shared_test.js +532 -236
- package/core/viewer.d.ts +2 -0
- package/core/viewer.js +3 -1
- package/graphql/graphql.d.ts +52 -19
- package/graphql/graphql.js +174 -136
- package/graphql/graphql_field_helpers.d.ts +7 -1
- package/graphql/graphql_field_helpers.js +21 -1
- package/graphql/index.d.ts +2 -2
- package/graphql/index.js +3 -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 +18 -5
- package/index.js +30 -10
- package/package.json +18 -17
- package/parse_schema/parse.d.ts +31 -9
- package/parse_schema/parse.js +179 -32
- 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 +231 -71
- 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 +145 -34
- 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 +39 -43
- package/testutils/builder.js +75 -49
- 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} +244 -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 +16 -15
- 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 +43 -0
- package/testutils/fake_data/test_helpers.d.ts +11 -4
- package/testutils/fake_data/test_helpers.js +29 -13
- package/testutils/fake_data/user_query.d.ts +11 -4
- package/testutils/fake_data/user_query.js +54 -22
- 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 +25 -2
- package/tsc/ast.js +141 -17
- 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/graphql/enums.d.ts +0 -3
- package/graphql/enums.js +0 -25
- package/scripts/move_generated.js +0 -142
- package/scripts/transform_code.js +0 -113
- package/scripts/transform_schema.d.ts +0 -1
- /package/scripts/{move_generated.d.ts → migrate_v0.1.d.ts} +0 -0
- /package/scripts/{transform_code.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 util_1 = require("util");
|
|
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,21 @@ 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
|
-
}), action_1.WriteOperation.Delete);
|
|
164
|
+
}), action_1.WriteOperation.Delete, options?.conditional);
|
|
115
165
|
}
|
|
116
|
-
removeOutboundEdge(id2, edgeType) {
|
|
166
|
+
removeOutboundEdge(id2, edgeType, options) {
|
|
117
167
|
this.addEdge(new edgeInputData({
|
|
118
168
|
id: id2,
|
|
119
169
|
edgeType,
|
|
120
170
|
direction: edgeDirection.outboundEdge,
|
|
121
|
-
}), action_1.WriteOperation.Delete);
|
|
171
|
+
}), action_1.WriteOperation.Delete, options?.conditional);
|
|
122
172
|
}
|
|
123
173
|
// this doesn't take a direction as that's an implementation detail
|
|
124
174
|
// it doesn't make any sense to use the same edgeType for inbound and outbound edges
|
|
@@ -141,29 +191,39 @@ class Orchestrator {
|
|
|
141
191
|
m.clear();
|
|
142
192
|
}
|
|
143
193
|
}
|
|
144
|
-
buildMainOp() {
|
|
194
|
+
buildMainOp(conditionalBuilder) {
|
|
145
195
|
// this assumes we have validated fields
|
|
146
196
|
switch (this.actualOperation) {
|
|
147
197
|
case action_1.WriteOperation.Delete:
|
|
148
|
-
return new
|
|
198
|
+
return new operations_1.DeleteNodeOperation(this.existingEnt.id, this.options.builder, {
|
|
149
199
|
tableName: this.options.tableName,
|
|
150
200
|
});
|
|
151
201
|
default:
|
|
152
202
|
if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
|
|
153
203
|
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
154
204
|
}
|
|
205
|
+
if (this.options.expressions &&
|
|
206
|
+
this.actualOperation !== action_1.WriteOperation.Edit) {
|
|
207
|
+
throw new Error(`expressions are only supported in edit operations for now`);
|
|
208
|
+
}
|
|
155
209
|
const opts = {
|
|
156
210
|
fields: this.validatedFields,
|
|
157
211
|
tableName: this.options.tableName,
|
|
158
212
|
fieldsToResolve: this.fieldsToResolve,
|
|
159
213
|
key: this.options.key,
|
|
160
214
|
loadEntOptions: this.options.loaderOptions,
|
|
161
|
-
|
|
215
|
+
whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
|
|
216
|
+
expressions: this.options.expressions,
|
|
217
|
+
onConflict: this.onConflict,
|
|
218
|
+
builder: this.options.builder,
|
|
162
219
|
};
|
|
163
220
|
if (this.logValues) {
|
|
164
221
|
opts.fieldsToLog = this.logValues;
|
|
165
222
|
}
|
|
166
|
-
this.mainOp = new
|
|
223
|
+
this.mainOp = new operations_1.EditNodeOperation(opts, this.existingEnt);
|
|
224
|
+
if (conditionalBuilder) {
|
|
225
|
+
this.mainOp = new operations_1.ConditionalNodeOperation(this.mainOp, conditionalBuilder);
|
|
226
|
+
}
|
|
167
227
|
return this.mainOp;
|
|
168
228
|
}
|
|
169
229
|
}
|
|
@@ -183,10 +243,10 @@ class Orchestrator {
|
|
|
183
243
|
throw new Error(`no nodeType for edge when adding outboundEdge`);
|
|
184
244
|
}
|
|
185
245
|
if (edge.direction === edgeDirection.outboundEdge) {
|
|
186
|
-
return
|
|
246
|
+
return operations_1.EdgeOperation.outboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
|
|
187
247
|
}
|
|
188
248
|
else {
|
|
189
|
-
return
|
|
249
|
+
return operations_1.EdgeOperation.inboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
|
|
190
250
|
}
|
|
191
251
|
}
|
|
192
252
|
else if (op === action_1.WriteOperation.Delete) {
|
|
@@ -195,30 +255,53 @@ class Orchestrator {
|
|
|
195
255
|
}
|
|
196
256
|
let id2 = edge.id;
|
|
197
257
|
if (edge.direction === edgeDirection.outboundEdge) {
|
|
198
|
-
return
|
|
258
|
+
return operations_1.EdgeOperation.removeOutboundEdge(this.options.builder, edgeType, id2);
|
|
199
259
|
}
|
|
200
260
|
else {
|
|
201
|
-
return
|
|
261
|
+
return operations_1.EdgeOperation.removeInboundEdge(this.options.builder, edgeType, id2);
|
|
202
262
|
}
|
|
203
263
|
}
|
|
204
264
|
throw new Error("could not find an edge operation from the given parameters");
|
|
205
265
|
}
|
|
206
|
-
async buildEdgeOps(ops) {
|
|
266
|
+
async buildEdgeOps(ops, conditionalBuilder, conditionalOverride) {
|
|
207
267
|
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
|
-
|
|
268
|
+
const edges = [
|
|
269
|
+
[this.edges, false],
|
|
270
|
+
[this.conditionalEdges, true],
|
|
271
|
+
];
|
|
272
|
+
// conditional should only apply if onconflict...
|
|
273
|
+
// if no upsert and just create, nothing to do here
|
|
274
|
+
for (const edgeInfo of edges) {
|
|
275
|
+
const [edges, conditionalEdge] = edgeInfo;
|
|
276
|
+
const conditional = conditionalOverride || conditionalEdge;
|
|
277
|
+
for (const [edgeType, m] of edges) {
|
|
278
|
+
for (const [op, m2] of m) {
|
|
279
|
+
for (const [_, edge] of m2) {
|
|
280
|
+
let edgeOp = this.getEdgeOperation(edgeType, op, edge);
|
|
281
|
+
if (conditional) {
|
|
282
|
+
ops.push(new operations_1.ConditionalOperation(edgeOp, conditionalBuilder));
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
ops.push(edgeOp);
|
|
286
|
+
}
|
|
287
|
+
const edgeData = edgeDatas.get(edgeType);
|
|
288
|
+
if (!edgeData) {
|
|
289
|
+
throw new Error(`could not load edge data for '${edgeType}'`);
|
|
290
|
+
}
|
|
291
|
+
if (edgeData.symmetricEdge) {
|
|
292
|
+
let symmetric = edgeOp.symmetricEdge();
|
|
293
|
+
if (conditional) {
|
|
294
|
+
symmetric = new operations_1.ConditionalOperation(symmetric, conditionalBuilder);
|
|
295
|
+
}
|
|
296
|
+
ops.push(symmetric);
|
|
297
|
+
}
|
|
298
|
+
if (edgeData.inverseEdgeType) {
|
|
299
|
+
let inverse = edgeOp.inverseEdge(edgeData);
|
|
300
|
+
if (conditional) {
|
|
301
|
+
inverse = new operations_1.ConditionalOperation(inverse, conditionalBuilder);
|
|
302
|
+
}
|
|
303
|
+
ops.push(inverse);
|
|
304
|
+
}
|
|
222
305
|
}
|
|
223
306
|
}
|
|
224
307
|
}
|
|
@@ -238,12 +321,50 @@ class Orchestrator {
|
|
|
238
321
|
}
|
|
239
322
|
return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
|
|
240
323
|
}
|
|
241
|
-
|
|
324
|
+
async getRowForPrivacyPolicyImpl(schemaFields, editedData) {
|
|
325
|
+
// need to format fields if possible because ent constructors expect data that's
|
|
326
|
+
// in the format that's coming from the db
|
|
327
|
+
// required for object fields...
|
|
328
|
+
const formatted = { ...editedData };
|
|
329
|
+
for (const [fieldName, field] of schemaFields) {
|
|
330
|
+
if (!field.format) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
334
|
+
let val = formatted[dbKey];
|
|
335
|
+
if (!val) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (field.valid) {
|
|
339
|
+
let valid = field.valid(val);
|
|
340
|
+
if (util_1.types.isPromise(valid)) {
|
|
341
|
+
valid = await valid;
|
|
342
|
+
}
|
|
343
|
+
// if not valid, don't format and don't pass to ent?
|
|
344
|
+
// or just early throw here
|
|
345
|
+
if (!valid) {
|
|
346
|
+
continue;
|
|
347
|
+
// throw new Error(`invalid field ${fieldName} with value ${val}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// nested so it's not JSON stringified or anything like that
|
|
351
|
+
val = field.format(formatted[dbKey], true);
|
|
352
|
+
if (util_1.types.isPromise(val)) {
|
|
353
|
+
val = await val;
|
|
354
|
+
}
|
|
355
|
+
formatted[dbKey] = val;
|
|
356
|
+
}
|
|
357
|
+
return formatted;
|
|
358
|
+
}
|
|
359
|
+
async getEntForPrivacyPolicyImpl(schemaFields, editedData, viewerToUse, rowToUse) {
|
|
242
360
|
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
243
361
|
return this.existingEnt;
|
|
244
362
|
}
|
|
363
|
+
if (!rowToUse) {
|
|
364
|
+
rowToUse = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
|
|
365
|
+
}
|
|
245
366
|
// we create an unsafe ent to be used for privacy policies
|
|
246
|
-
return new this.options.builder.ent(
|
|
367
|
+
return new this.options.builder.ent(viewerToUse, rowToUse);
|
|
247
368
|
}
|
|
248
369
|
getSQLStatementOperation() {
|
|
249
370
|
switch (this.actualOperation) {
|
|
@@ -273,8 +394,8 @@ class Orchestrator {
|
|
|
273
394
|
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
274
395
|
return this.existingEnt;
|
|
275
396
|
}
|
|
276
|
-
const { editedData } = await this.memoizedGetFields();
|
|
277
|
-
return this.getEntForPrivacyPolicyImpl(editedData);
|
|
397
|
+
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
398
|
+
return this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
|
|
278
399
|
}
|
|
279
400
|
// this gets the fields that were explicitly set plus any default or transformed values
|
|
280
401
|
// mainly exists to get default fields e.g. default id to be used in triggers
|
|
@@ -284,15 +405,41 @@ class Orchestrator {
|
|
|
284
405
|
const { editedData } = await this.memoizedGetFields();
|
|
285
406
|
return editedData;
|
|
286
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* @returns validated and formatted fields that would be written to the db
|
|
410
|
+
* throws an error if called before valid() or validX() has been called
|
|
411
|
+
*/
|
|
412
|
+
getValidatedFields() {
|
|
413
|
+
if (this.validatedFields === null) {
|
|
414
|
+
throw new Error(`trying to call getValidatedFields before validating fields`);
|
|
415
|
+
}
|
|
416
|
+
return this.validatedFields;
|
|
417
|
+
}
|
|
287
418
|
// Note: this is memoized. call memoizedGetFields instead
|
|
288
419
|
async getFieldsInfo() {
|
|
289
420
|
const action = this.options.action;
|
|
290
421
|
const builder = this.options.builder;
|
|
291
422
|
// future optimization: can get schemaFields to memoize based on different values
|
|
292
423
|
const schemaFields = (0, schema_1.getFields)(this.options.schema);
|
|
424
|
+
// also future optimization, no need to go through the list of fields multiple times
|
|
425
|
+
let editPrivacyFields = new Map();
|
|
426
|
+
switch (this.actualOperation) {
|
|
427
|
+
case action_1.WriteOperation.Edit:
|
|
428
|
+
editPrivacyFields = (0, schema_1.getFieldsWithEditPrivacy)(this.options.schema, this.options.fieldInfo);
|
|
429
|
+
break;
|
|
430
|
+
case action_1.WriteOperation.Insert:
|
|
431
|
+
editPrivacyFields = (0, schema_1.getFieldsForCreateAction)(this.options.schema, this.options.fieldInfo);
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
293
434
|
const editedFields = await this.options.editedFields();
|
|
294
|
-
let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
|
|
295
|
-
return {
|
|
435
|
+
let { data: editedData, userDefinedKeys } = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
|
|
436
|
+
return {
|
|
437
|
+
editedData,
|
|
438
|
+
editedFields,
|
|
439
|
+
schemaFields,
|
|
440
|
+
userDefinedKeys,
|
|
441
|
+
editPrivacyFields,
|
|
442
|
+
};
|
|
296
443
|
}
|
|
297
444
|
async validate() {
|
|
298
445
|
// existing ent required for edit or delete operations
|
|
@@ -303,7 +450,7 @@ class Orchestrator {
|
|
|
303
450
|
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
304
451
|
}
|
|
305
452
|
}
|
|
306
|
-
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
453
|
+
const { schemaFields, editedData, userDefinedKeys, editPrivacyFields } = await this.memoizedGetFields();
|
|
307
454
|
const action = this.options.action;
|
|
308
455
|
const builder = this.options.builder;
|
|
309
456
|
// this runs in following phases:
|
|
@@ -312,51 +459,117 @@ class Orchestrator {
|
|
|
312
459
|
// * triggers
|
|
313
460
|
// * validators
|
|
314
461
|
let privacyPolicy = action?.getPrivacyPolicy();
|
|
462
|
+
const errors = [];
|
|
315
463
|
if (privacyPolicy) {
|
|
316
|
-
|
|
464
|
+
const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
|
|
465
|
+
try {
|
|
466
|
+
await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, ent, () => this.throwError());
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
errors.push(err);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// we have edit privacy fields, so we need to apply privacy policy on those
|
|
473
|
+
const promises = [];
|
|
474
|
+
if (editPrivacyFields.size) {
|
|
475
|
+
// get row based on edited data
|
|
476
|
+
const row = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
|
|
477
|
+
// get viewer for ent load based on formatted row
|
|
478
|
+
const viewer = await this.viewerForEntLoad(row);
|
|
479
|
+
const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, viewer, row);
|
|
480
|
+
for (const [k, policy] of editPrivacyFields) {
|
|
481
|
+
if (editedData[k] === undefined || !userDefinedKeys.has(k)) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
promises.push((async () => {
|
|
485
|
+
const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
|
|
486
|
+
if (!r) {
|
|
487
|
+
errors.push(new EntCannotEditEntFieldError(policy, viewer, k, ent));
|
|
488
|
+
}
|
|
489
|
+
})());
|
|
490
|
+
}
|
|
491
|
+
await Promise.all(promises);
|
|
492
|
+
}
|
|
493
|
+
// privacy or field errors should return first so it's less confusing
|
|
494
|
+
if (errors.length) {
|
|
495
|
+
return errors;
|
|
317
496
|
}
|
|
318
497
|
// have to run triggers which update fields first before field and other validators
|
|
319
498
|
// so running this first to build things up
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
499
|
+
if (action?.getTriggers) {
|
|
500
|
+
await this.triggers(action, builder, action.getTriggers());
|
|
501
|
+
}
|
|
502
|
+
let validators = [];
|
|
503
|
+
if (action?.getValidators) {
|
|
504
|
+
validators = action.getValidators();
|
|
323
505
|
}
|
|
324
|
-
let validators = action?.validators || [];
|
|
325
506
|
// not ideal we're calling this twice. fix...
|
|
326
507
|
// needed for now. may need to rewrite some of this?
|
|
327
508
|
const editedFields2 = await this.options.editedFields();
|
|
328
|
-
await Promise.all([
|
|
509
|
+
const [errs2, errs3] = await Promise.all([
|
|
329
510
|
this.formatAndValidateFields(schemaFields, editedFields2),
|
|
330
511
|
this.validators(validators, action, builder),
|
|
331
512
|
]);
|
|
513
|
+
errors.push(...errs2);
|
|
514
|
+
errors.push(...errs3);
|
|
515
|
+
return errors;
|
|
332
516
|
}
|
|
333
517
|
async triggers(action, builder, triggers) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (Array.isArray(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
518
|
+
let groups = [];
|
|
519
|
+
let lastArray = 0;
|
|
520
|
+
let prevWasArray = false;
|
|
521
|
+
for (let i = 0; i < triggers.length; i++) {
|
|
522
|
+
let t = triggers[i];
|
|
523
|
+
if (Array.isArray(t)) {
|
|
524
|
+
if (!prevWasArray) {
|
|
525
|
+
// @ts-ignore
|
|
526
|
+
groups.push(triggers.slice(lastArray, i));
|
|
344
527
|
}
|
|
528
|
+
groups.push(t);
|
|
529
|
+
prevWasArray = true;
|
|
530
|
+
lastArray++;
|
|
345
531
|
}
|
|
346
|
-
else
|
|
347
|
-
|
|
532
|
+
else {
|
|
533
|
+
if (i === triggers.length - 1) {
|
|
534
|
+
// @ts-ignore
|
|
535
|
+
groups.push(triggers.slice(lastArray, i + 1));
|
|
536
|
+
}
|
|
537
|
+
prevWasArray = false;
|
|
348
538
|
}
|
|
349
|
-
}
|
|
539
|
+
}
|
|
540
|
+
for (const triggers of groups) {
|
|
541
|
+
await Promise.all(triggers.map(async (trigger) => {
|
|
542
|
+
let ret = await trigger.changeset(builder, action.getInput());
|
|
543
|
+
if (Array.isArray(ret)) {
|
|
544
|
+
ret = await Promise.all(ret);
|
|
545
|
+
}
|
|
546
|
+
if (Array.isArray(ret)) {
|
|
547
|
+
for (const v of ret) {
|
|
548
|
+
if (typeof v === "object") {
|
|
549
|
+
this.changesets.push(v);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else if (ret) {
|
|
554
|
+
this.changesets.push(ret);
|
|
555
|
+
}
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
350
558
|
}
|
|
351
559
|
async validators(validators, action, builder) {
|
|
352
|
-
|
|
353
|
-
validators.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
560
|
+
const errors = [];
|
|
561
|
+
await Promise.all(validators.map(async (v) => {
|
|
562
|
+
try {
|
|
563
|
+
const r = await v.validate(builder, action.getInput());
|
|
564
|
+
if (r instanceof Error) {
|
|
565
|
+
errors.push(r);
|
|
566
|
+
}
|
|
357
567
|
}
|
|
358
|
-
|
|
359
|
-
|
|
568
|
+
catch (err) {
|
|
569
|
+
errors.push(err);
|
|
570
|
+
}
|
|
571
|
+
}));
|
|
572
|
+
return errors;
|
|
360
573
|
}
|
|
361
574
|
isBuilder(val) {
|
|
362
575
|
return val.placeholderID !== undefined;
|
|
@@ -377,23 +590,31 @@ class Orchestrator {
|
|
|
377
590
|
// if disable transformations set, don't do schema transform and just do the right thing
|
|
378
591
|
// else apply schema tranformation if it exists
|
|
379
592
|
let transformed = null;
|
|
593
|
+
const sqlOp = this.getSQLStatementOperation();
|
|
594
|
+
// why is transform write technically different from upsert?
|
|
595
|
+
// it's create -> update just at the db level...
|
|
380
596
|
if (action?.transformWrite) {
|
|
381
597
|
transformed = await action.transformWrite({
|
|
382
|
-
|
|
383
|
-
|
|
598
|
+
builder,
|
|
599
|
+
input,
|
|
600
|
+
op: sqlOp,
|
|
384
601
|
data: editedFields,
|
|
385
|
-
existingEnt: this.existingEnt,
|
|
386
602
|
});
|
|
387
603
|
}
|
|
388
604
|
else if (!this.disableTransformations) {
|
|
389
605
|
transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
|
|
390
|
-
|
|
391
|
-
|
|
606
|
+
builder,
|
|
607
|
+
input,
|
|
608
|
+
op: sqlOp,
|
|
392
609
|
data: editedFields,
|
|
393
|
-
existingEnt: this.existingEnt,
|
|
394
610
|
});
|
|
395
611
|
}
|
|
396
612
|
if (transformed) {
|
|
613
|
+
if (sqlOp === schema_1.SQLStatementOperation.Insert && sqlOp !== transformed.op) {
|
|
614
|
+
if (!transformed.existingEnt) {
|
|
615
|
+
throw new Error(`cannot transform an insert operation without providing an existing ent`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
397
618
|
if (transformed.data) {
|
|
398
619
|
updateInput = true;
|
|
399
620
|
for (const k in transformed.data) {
|
|
@@ -412,18 +633,29 @@ class Orchestrator {
|
|
|
412
633
|
// this.defaultFieldsByFieldName[k] = val;
|
|
413
634
|
}
|
|
414
635
|
}
|
|
636
|
+
if (transformed.changeset) {
|
|
637
|
+
const changeset = await transformed.changeset();
|
|
638
|
+
this.changesets.push(changeset);
|
|
639
|
+
}
|
|
415
640
|
this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
|
|
416
641
|
if (transformed.existingEnt) {
|
|
417
642
|
// @ts-ignore
|
|
418
643
|
this.existingEnt = transformed.existingEnt;
|
|
644
|
+
// modify existing ent in builder. it's readonly in generated ents but doesn't apply here
|
|
645
|
+
builder.existingEnt = transformed.existingEnt;
|
|
419
646
|
}
|
|
420
647
|
}
|
|
421
648
|
// transforming before doing default fields so that we don't create a new id
|
|
422
649
|
// and anything that depends on the type of operations knows what it is
|
|
650
|
+
const userDefinedKeys = new Set();
|
|
423
651
|
for (const [fieldName, field] of schemaFields) {
|
|
424
652
|
let value = editedFields.get(fieldName);
|
|
425
653
|
let defaultValue = undefined;
|
|
426
654
|
let dbKey = this.getStorageKey(fieldName);
|
|
655
|
+
let updateOnlyIfOther = field.onlyUpdateIfOtherFieldsBeingSet_BETA;
|
|
656
|
+
if (value !== undefined) {
|
|
657
|
+
userDefinedKeys.add(dbKey);
|
|
658
|
+
}
|
|
427
659
|
if (value === undefined) {
|
|
428
660
|
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
429
661
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
@@ -437,11 +669,17 @@ class Orchestrator {
|
|
|
437
669
|
if (defaultValue === undefined) {
|
|
438
670
|
throw new Error(`defaultValueOnCreate() returned undefined for field ${fieldName}`);
|
|
439
671
|
}
|
|
672
|
+
if (util_1.types.isPromise(defaultValue)) {
|
|
673
|
+
defaultValue = await defaultValue;
|
|
674
|
+
}
|
|
440
675
|
}
|
|
441
676
|
}
|
|
442
677
|
if (field.defaultValueOnEdit &&
|
|
443
678
|
this.actualOperation === action_1.WriteOperation.Edit) {
|
|
444
679
|
defaultValue = field.defaultValueOnEdit(builder, input);
|
|
680
|
+
if (util_1.types.isPromise(defaultValue)) {
|
|
681
|
+
defaultValue = await defaultValue;
|
|
682
|
+
}
|
|
445
683
|
}
|
|
446
684
|
}
|
|
447
685
|
if (value !== undefined) {
|
|
@@ -449,7 +687,12 @@ class Orchestrator {
|
|
|
449
687
|
}
|
|
450
688
|
if (defaultValue !== undefined) {
|
|
451
689
|
updateInput = true;
|
|
452
|
-
|
|
690
|
+
if (updateOnlyIfOther) {
|
|
691
|
+
defaultData[dbKey] = defaultValue;
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
data[dbKey] = defaultValue;
|
|
695
|
+
}
|
|
453
696
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
454
697
|
this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
|
|
455
698
|
}
|
|
@@ -465,7 +708,7 @@ class Orchestrator {
|
|
|
465
708
|
this.options.updateInput(this.defaultFieldsByTSName);
|
|
466
709
|
}
|
|
467
710
|
}
|
|
468
|
-
return data;
|
|
711
|
+
return { data, userDefinedKeys };
|
|
469
712
|
}
|
|
470
713
|
hasData(data) {
|
|
471
714
|
for (const _k in data) {
|
|
@@ -477,7 +720,7 @@ class Orchestrator {
|
|
|
477
720
|
// now format and validate...
|
|
478
721
|
if (value === null) {
|
|
479
722
|
if (!field.nullable) {
|
|
480
|
-
|
|
723
|
+
return new Error(`field ${fieldName} set to null for non-nullable field`);
|
|
481
724
|
}
|
|
482
725
|
}
|
|
483
726
|
else if (value === undefined) {
|
|
@@ -488,14 +731,14 @@ class Orchestrator {
|
|
|
488
731
|
// server default allowed
|
|
489
732
|
field.serverDefault === undefined &&
|
|
490
733
|
this.actualOperation === action_1.WriteOperation.Insert) {
|
|
491
|
-
|
|
734
|
+
return new Error(`required field ${fieldName} not set`);
|
|
492
735
|
}
|
|
493
736
|
}
|
|
494
737
|
else if (this.isBuilder(value)) {
|
|
495
738
|
if (field.valid) {
|
|
496
739
|
const valid = await field.valid(value);
|
|
497
740
|
if (!valid) {
|
|
498
|
-
|
|
741
|
+
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
499
742
|
}
|
|
500
743
|
}
|
|
501
744
|
// keep track of dependencies to resolve
|
|
@@ -508,7 +751,7 @@ class Orchestrator {
|
|
|
508
751
|
// TODO this could be async. handle this better
|
|
509
752
|
const valid = await field.valid(value);
|
|
510
753
|
if (!valid) {
|
|
511
|
-
|
|
754
|
+
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
512
755
|
}
|
|
513
756
|
}
|
|
514
757
|
if (field.format) {
|
|
@@ -518,26 +761,52 @@ class Orchestrator {
|
|
|
518
761
|
return value;
|
|
519
762
|
}
|
|
520
763
|
async formatAndValidateFields(schemaFields, editedFields) {
|
|
764
|
+
const errors = [];
|
|
521
765
|
const op = this.actualOperation;
|
|
522
766
|
if (op === action_1.WriteOperation.Delete) {
|
|
523
|
-
return;
|
|
767
|
+
return [];
|
|
524
768
|
}
|
|
525
769
|
// build up data to be saved...
|
|
526
770
|
let data = {};
|
|
527
771
|
let logValues = {};
|
|
772
|
+
let needsFullDataChecks = [];
|
|
528
773
|
for (const [fieldName, field] of schemaFields) {
|
|
529
774
|
let value = editedFields.get(fieldName);
|
|
775
|
+
if (field.validateWithFullData) {
|
|
776
|
+
needsFullDataChecks.push(fieldName);
|
|
777
|
+
}
|
|
530
778
|
if (value === undefined && op === action_1.WriteOperation.Insert) {
|
|
531
779
|
// null allowed
|
|
532
780
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
533
781
|
}
|
|
534
782
|
let dbKey = this.getStorageKey(fieldName);
|
|
535
|
-
|
|
783
|
+
let ret = await this.transformFieldValue(fieldName, field, dbKey, value);
|
|
784
|
+
if (ret instanceof Error) {
|
|
785
|
+
errors.push(ret);
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
value = ret;
|
|
789
|
+
}
|
|
536
790
|
if (value !== undefined) {
|
|
537
791
|
data[dbKey] = value;
|
|
538
792
|
logValues[dbKey] = field.logValue(value);
|
|
539
793
|
}
|
|
540
794
|
}
|
|
795
|
+
for (const fieldName of needsFullDataChecks) {
|
|
796
|
+
const field = schemaFields.get(fieldName);
|
|
797
|
+
let value = editedFields.get(fieldName);
|
|
798
|
+
// @ts-ignore...
|
|
799
|
+
// type hackery because it's hard
|
|
800
|
+
const v = await field.validateWithFullData(value, this.options.builder);
|
|
801
|
+
if (!v) {
|
|
802
|
+
if (value === undefined) {
|
|
803
|
+
errors.push(new Error(`field ${fieldName} set to undefined when it can't be nullable`));
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
errors.push(new Error(`field ${fieldName} set to null when it can't be nullable`));
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
541
810
|
// we ignored default values while editing.
|
|
542
811
|
// if we're editing and there's data, add default values
|
|
543
812
|
if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
|
|
@@ -547,41 +816,73 @@ class Orchestrator {
|
|
|
547
816
|
let dbKey = this.getStorageKey(fieldName);
|
|
548
817
|
// no value, let's just default
|
|
549
818
|
if (data[dbKey] === undefined) {
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
819
|
+
const ret = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
|
|
820
|
+
if (ret instanceof Error) {
|
|
821
|
+
errors.push(ret);
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
data[dbKey] = ret;
|
|
825
|
+
logValues[dbKey] = field.logValue(ret);
|
|
826
|
+
}
|
|
553
827
|
}
|
|
554
828
|
}
|
|
555
829
|
}
|
|
556
830
|
this.validatedFields = data;
|
|
557
831
|
this.logValues = logValues;
|
|
832
|
+
return errors;
|
|
558
833
|
}
|
|
559
834
|
async valid() {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
catch (e) {
|
|
564
|
-
(0, logger_1.log)("error", e);
|
|
835
|
+
const errors = await this.validate();
|
|
836
|
+
if (errors.length) {
|
|
837
|
+
errors.map((err) => (0, logger_1.log)("error", err));
|
|
565
838
|
return false;
|
|
566
839
|
}
|
|
567
840
|
return true;
|
|
568
841
|
}
|
|
569
842
|
async validX() {
|
|
843
|
+
const errors = await this.validate();
|
|
844
|
+
if (errors.length) {
|
|
845
|
+
// just throw the first one...
|
|
846
|
+
// TODO we should ideally throw all of them
|
|
847
|
+
throw errors[0];
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* @experimental API that's not guaranteed to remain in the future which returns
|
|
852
|
+
* a list of errors encountered
|
|
853
|
+
* 0 errors indicates valid
|
|
854
|
+
* NOTE that this currently doesn't catch errors returned by validators().
|
|
855
|
+
* If those throws, this still throws and doesn't return them
|
|
856
|
+
*/
|
|
857
|
+
async validWithErrors() {
|
|
570
858
|
return this.validate();
|
|
571
859
|
}
|
|
572
|
-
async
|
|
860
|
+
async buildPlusChangeset(conditionalBuilder, conditionalOverride) {
|
|
573
861
|
// validate everything first
|
|
574
862
|
await this.validX();
|
|
575
|
-
let ops = [
|
|
576
|
-
|
|
577
|
-
|
|
863
|
+
let ops = [
|
|
864
|
+
this.buildMainOp(conditionalOverride ? conditionalBuilder : undefined),
|
|
865
|
+
];
|
|
866
|
+
await this.buildEdgeOps(ops, conditionalBuilder, conditionalOverride);
|
|
867
|
+
// TODO throw if we try and create a new changeset after previously creating one
|
|
868
|
+
// TODO test actualOperation value
|
|
869
|
+
// observers is fine since they're run after and we have the actualOperation value...
|
|
870
|
+
return new EntChangeset(this.options.viewer, this.options.builder, this.options.builder.placeholderID, conditionalOverride, ops, this.dependencies, this.changesets, this.options);
|
|
871
|
+
}
|
|
872
|
+
async build() {
|
|
873
|
+
return this.buildPlusChangeset(this.options.builder, false);
|
|
874
|
+
}
|
|
875
|
+
async buildWithOptions_BETA(options) {
|
|
876
|
+
// set as dependency so that we do the right order of operations
|
|
877
|
+
this.dependencies.set(options.conditionalBuilder.placeholderID, options.conditionalBuilder);
|
|
878
|
+
return this.buildPlusChangeset(options.conditionalBuilder, true);
|
|
578
879
|
}
|
|
579
880
|
async viewerForEntLoad(data) {
|
|
580
881
|
const action = this.options.action;
|
|
581
882
|
if (!action || !action.viewerForEntLoad) {
|
|
582
883
|
return this.options.viewer;
|
|
583
884
|
}
|
|
584
|
-
return action.viewerForEntLoad(data);
|
|
885
|
+
return action.viewerForEntLoad(data, action.builder.viewer.context);
|
|
585
886
|
}
|
|
586
887
|
async returnedRow() {
|
|
587
888
|
if (this.mainOp && this.mainOp.returnedRow) {
|
|
@@ -616,16 +917,34 @@ class Orchestrator {
|
|
|
616
917
|
}
|
|
617
918
|
}
|
|
618
919
|
exports.Orchestrator = Orchestrator;
|
|
920
|
+
function randomNum() {
|
|
921
|
+
return Math.random().toString(10).substring(2);
|
|
922
|
+
}
|
|
923
|
+
// each changeset is required to have a unique placeholderID
|
|
924
|
+
// used in executor. if we end up creating multiple changesets from a builder, we need
|
|
925
|
+
// different placeholders
|
|
926
|
+
// in practice, only applies to Entchangeset::changesetFrom()
|
|
619
927
|
class EntChangeset {
|
|
620
|
-
constructor(viewer, placeholderID,
|
|
928
|
+
constructor(viewer, builder, placeholderID, conditionalOverride, operations, dependencies, changesets, options) {
|
|
621
929
|
this.viewer = viewer;
|
|
930
|
+
this.builder = builder;
|
|
622
931
|
this.placeholderID = placeholderID;
|
|
623
|
-
this.
|
|
932
|
+
this.conditionalOverride = conditionalOverride;
|
|
624
933
|
this.operations = operations;
|
|
625
934
|
this.dependencies = dependencies;
|
|
626
935
|
this.changesets = changesets;
|
|
627
936
|
this.options = options;
|
|
628
937
|
}
|
|
938
|
+
static changesetFrom(builder, ops) {
|
|
939
|
+
return new EntChangeset(builder.viewer, builder,
|
|
940
|
+
// need unique placeholderID different from the builder. see comment above EntChangeset
|
|
941
|
+
`$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, false, ops);
|
|
942
|
+
}
|
|
943
|
+
static changesetFromQueries(builder, queries) {
|
|
944
|
+
return new EntChangeset(builder.viewer, builder,
|
|
945
|
+
// need unique placeholderID different from the builder. see comment above EntChangeset
|
|
946
|
+
`$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, false, [new operations_2.RawQueryOperation(builder, queries)]);
|
|
947
|
+
}
|
|
629
948
|
executor() {
|
|
630
949
|
if (this._executor) {
|
|
631
950
|
return this._executor;
|
|
@@ -635,9 +954,15 @@ class EntChangeset {
|
|
|
635
954
|
// executor and depend on something else in the stack to handle this correctly
|
|
636
955
|
// ComplexExecutor which could be a parent of this should make sure the dependency
|
|
637
956
|
// is resolved beforehand
|
|
638
|
-
return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options
|
|
639
|
-
|
|
640
|
-
|
|
957
|
+
return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options, {
|
|
958
|
+
conditionalOverride: this.conditionalOverride,
|
|
959
|
+
builder: this.builder,
|
|
960
|
+
}));
|
|
961
|
+
}
|
|
962
|
+
return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options, {
|
|
963
|
+
conditionalOverride: this.conditionalOverride,
|
|
964
|
+
builder: this.builder,
|
|
965
|
+
}));
|
|
641
966
|
}
|
|
642
967
|
}
|
|
643
968
|
exports.EntChangeset = EntChangeset;
|