@snowtop/ent 0.1.0-alpha99 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/action/action.d.ts +8 -1
- package/action/executor.d.ts +16 -3
- package/action/executor.js +83 -27
- package/action/index.d.ts +2 -1
- package/action/operations.d.ts +126 -0
- package/action/operations.js +686 -0
- package/action/orchestrator.d.ts +22 -8
- package/action/orchestrator.js +278 -67
- package/core/base.d.ts +34 -24
- package/core/clause.d.ts +62 -79
- package/core/clause.js +77 -5
- package/core/config.d.ts +5 -1
- package/core/config.js +3 -0
- package/core/const.d.ts +3 -0
- package/core/const.js +6 -0
- package/core/context.d.ts +4 -3
- package/core/context.js +2 -1
- package/core/db.d.ts +1 -0
- package/core/db.js +7 -7
- package/core/ent.d.ts +53 -105
- package/core/ent.js +104 -599
- package/core/global_schema.d.ts +7 -0
- package/core/global_schema.js +51 -0
- package/core/loaders/assoc_count_loader.d.ts +4 -2
- package/core/loaders/assoc_count_loader.js +10 -2
- package/core/loaders/assoc_edge_loader.d.ts +2 -3
- package/core/loaders/assoc_edge_loader.js +16 -7
- package/core/loaders/index.d.ts +0 -1
- package/core/loaders/index.js +1 -3
- package/core/loaders/loader.d.ts +3 -3
- package/core/loaders/loader.js +3 -20
- package/core/loaders/object_loader.d.ts +30 -10
- package/core/loaders/object_loader.js +179 -40
- package/core/loaders/query_loader.d.ts +4 -4
- package/core/loaders/query_loader.js +14 -19
- package/core/loaders/raw_count_loader.d.ts +1 -0
- package/core/loaders/raw_count_loader.js +3 -2
- package/core/privacy.d.ts +19 -10
- package/core/privacy.js +47 -26
- package/core/query/assoc_query.js +1 -1
- package/core/query/custom_clause_query.d.ts +6 -3
- package/core/query/custom_clause_query.js +36 -9
- package/core/query/custom_query.d.ts +3 -1
- package/core/query/custom_query.js +29 -6
- package/core/query/query.d.ts +12 -2
- package/core/query/query.js +67 -38
- package/core/query/shared_assoc_test.js +151 -10
- package/core/query/shared_test.d.ts +2 -2
- package/core/query/shared_test.js +90 -30
- 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 +2 -0
- package/graphql/graphql.d.ts +103 -19
- package/graphql/graphql.js +169 -134
- package/graphql/graphql_field_helpers.d.ts +9 -3
- package/graphql/graphql_field_helpers.js +22 -2
- package/graphql/index.d.ts +2 -1
- package/graphql/index.js +5 -2
- 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 +1 -1
- package/imports/index.js +2 -2
- package/index.d.ts +12 -1
- package/index.js +18 -6
- package/package.json +20 -17
- package/parse_schema/parse.d.ts +10 -4
- package/parse_schema/parse.js +70 -24
- package/schema/base_schema.d.ts +8 -0
- package/schema/base_schema.js +11 -0
- package/schema/field.d.ts +6 -3
- package/schema/field.js +72 -17
- package/schema/index.d.ts +1 -1
- package/schema/index.js +2 -1
- package/schema/json_field.d.ts +3 -3
- package/schema/json_field.js +4 -1
- package/schema/schema.d.ts +42 -5
- package/schema/schema.js +35 -41
- package/schema/struct_field.d.ts +8 -6
- package/schema/struct_field.js +67 -8
- package/schema/union_field.d.ts +1 -1
- package/scripts/custom_compiler.js +4 -4
- package/scripts/custom_graphql.js +105 -75
- package/scripts/move_types.js +4 -1
- package/scripts/read_schema.js +2 -2
- package/testutils/action/complex_schemas.d.ts +1 -1
- package/testutils/action/complex_schemas.js +10 -3
- package/testutils/builder.d.ts +3 -0
- package/testutils/builder.js +6 -0
- package/testutils/db/temp_db.d.ts +9 -1
- package/testutils/db/temp_db.js +82 -14
- package/testutils/db_mock.js +1 -3
- package/testutils/ent-graphql-tests/index.d.ts +1 -1
- package/testutils/ent-graphql-tests/index.js +30 -19
- package/testutils/fake_comms.js +1 -1
- package/testutils/fake_data/fake_contact.d.ts +1 -1
- package/testutils/fake_data/fake_tag.d.ts +1 -1
- package/testutils/fake_data/fake_user.d.ts +3 -3
- package/testutils/fake_data/fake_user.js +15 -4
- package/testutils/fake_data/tag_query.js +8 -3
- package/testutils/fake_data/test_helpers.d.ts +3 -2
- package/testutils/fake_data/test_helpers.js +4 -4
- package/testutils/fake_data/user_query.d.ts +5 -2
- package/testutils/fake_data/user_query.js +19 -2
- package/testutils/fake_log.js +1 -1
- package/tsc/ast.js +2 -1
- package/tsc/move_generated.js +2 -2
- package/tsc/transform.d.ts +2 -2
- package/tsc/transform.js +4 -3
- package/tsc/transform_ent.js +2 -1
- package/tsc/transform_schema.js +4 -3
- package/core/loaders/index_loader.d.ts +0 -14
- package/core/loaders/index_loader.js +0 -27
package/action/orchestrator.js
CHANGED
|
@@ -29,12 +29,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.EntChangeset = exports.Orchestrator = exports.edgeDirection = void 0;
|
|
30
30
|
const ent_1 = require("../core/ent");
|
|
31
31
|
const schema_1 = require("../schema/schema");
|
|
32
|
+
const operations_1 = require("./operations");
|
|
32
33
|
const action_1 = require("../action");
|
|
33
34
|
const privacy_1 = require("../core/privacy");
|
|
34
35
|
const executor_1 = require("./executor");
|
|
35
36
|
const logger_1 = require("../core/logger");
|
|
36
37
|
const memoizee_1 = __importDefault(require("memoizee"));
|
|
37
38
|
const clause = __importStar(require("../core/clause"));
|
|
39
|
+
const types_1 = require("util/types");
|
|
40
|
+
const operations_2 = require("./operations");
|
|
38
41
|
var edgeDirection;
|
|
39
42
|
(function (edgeDirection) {
|
|
40
43
|
edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
|
|
@@ -48,31 +51,38 @@ class edgeInputData {
|
|
|
48
51
|
return id.placeholderID !== undefined;
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
|
-
function getViewer(
|
|
52
|
-
if (!
|
|
54
|
+
function getViewer(viewer) {
|
|
55
|
+
if (!viewer.viewerID) {
|
|
53
56
|
return "Logged out Viewer";
|
|
54
57
|
}
|
|
55
58
|
else {
|
|
56
|
-
return `Viewer with ID ${
|
|
59
|
+
return `Viewer with ID ${viewer.viewerID}`;
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
class EntCannotCreateEntError extends Error {
|
|
60
63
|
constructor(privacyPolicy, action) {
|
|
61
|
-
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}`;
|
|
62
65
|
super(msg);
|
|
63
66
|
this.privacyPolicy = privacyPolicy;
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
class EntCannotEditEntError extends Error {
|
|
67
70
|
constructor(privacyPolicy, action, ent) {
|
|
68
|
-
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}`;
|
|
69
79
|
super(msg);
|
|
70
80
|
this.privacyPolicy = privacyPolicy;
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
83
|
class EntCannotDeleteEntError extends Error {
|
|
74
84
|
constructor(privacyPolicy, action, ent) {
|
|
75
|
-
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}`;
|
|
76
86
|
super(msg);
|
|
77
87
|
this.privacyPolicy = privacyPolicy;
|
|
78
88
|
}
|
|
@@ -82,6 +92,7 @@ class Orchestrator {
|
|
|
82
92
|
this.options = options;
|
|
83
93
|
this.edgeSet = new Set();
|
|
84
94
|
this.edges = new Map();
|
|
95
|
+
this.conditionalEdges = new Map();
|
|
85
96
|
this.changesets = [];
|
|
86
97
|
this.dependencies = new Map();
|
|
87
98
|
this.fieldsToResolve = [];
|
|
@@ -96,7 +107,7 @@ class Orchestrator {
|
|
|
96
107
|
__getOptions() {
|
|
97
108
|
return this.options;
|
|
98
109
|
}
|
|
99
|
-
addEdge(edge, op) {
|
|
110
|
+
addEdge(edge, op, conditional) {
|
|
100
111
|
this.edgeSet.add(edge.edgeType);
|
|
101
112
|
let m1 = this.edges.get(edge.edgeType) || new Map();
|
|
102
113
|
let m2 = m1.get(op) || new Map();
|
|
@@ -111,11 +122,22 @@ class Orchestrator {
|
|
|
111
122
|
// set or overwrite the new edge data for said id
|
|
112
123
|
m2.set(id, edge);
|
|
113
124
|
m1.set(op, m2);
|
|
114
|
-
this.
|
|
125
|
+
if (conditional && this.onConflict) {
|
|
126
|
+
this.conditionalEdges.set(edge.edgeType, m1);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.edges.set(edge.edgeType, m1);
|
|
130
|
+
}
|
|
115
131
|
}
|
|
116
132
|
setDisableTransformations(val) {
|
|
117
133
|
this.disableTransformations = val;
|
|
118
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
|
+
}
|
|
119
141
|
addInboundEdge(id1, edgeType, nodeType, options) {
|
|
120
142
|
this.addEdge(new edgeInputData({
|
|
121
143
|
id: id1,
|
|
@@ -123,7 +145,7 @@ class Orchestrator {
|
|
|
123
145
|
nodeType,
|
|
124
146
|
options,
|
|
125
147
|
direction: edgeDirection.inboundEdge,
|
|
126
|
-
}), action_1.WriteOperation.Insert);
|
|
148
|
+
}), action_1.WriteOperation.Insert, options?.conditional);
|
|
127
149
|
}
|
|
128
150
|
addOutboundEdge(id2, edgeType, nodeType, options) {
|
|
129
151
|
this.addEdge(new edgeInputData({
|
|
@@ -132,21 +154,23 @@ class Orchestrator {
|
|
|
132
154
|
nodeType,
|
|
133
155
|
options,
|
|
134
156
|
direction: edgeDirection.outboundEdge,
|
|
135
|
-
}), action_1.WriteOperation.Insert);
|
|
157
|
+
}), action_1.WriteOperation.Insert, options?.conditional);
|
|
136
158
|
}
|
|
137
|
-
removeInboundEdge(id1, edgeType) {
|
|
159
|
+
removeInboundEdge(id1, edgeType, options) {
|
|
138
160
|
this.addEdge(new edgeInputData({
|
|
139
161
|
id: id1,
|
|
140
162
|
edgeType,
|
|
141
163
|
direction: edgeDirection.inboundEdge,
|
|
142
|
-
|
|
164
|
+
options,
|
|
165
|
+
}), action_1.WriteOperation.Delete, options?.conditional);
|
|
143
166
|
}
|
|
144
|
-
removeOutboundEdge(id2, edgeType) {
|
|
167
|
+
removeOutboundEdge(id2, edgeType, options) {
|
|
145
168
|
this.addEdge(new edgeInputData({
|
|
146
169
|
id: id2,
|
|
147
170
|
edgeType,
|
|
148
171
|
direction: edgeDirection.outboundEdge,
|
|
149
|
-
|
|
172
|
+
options,
|
|
173
|
+
}), action_1.WriteOperation.Delete, options?.conditional);
|
|
150
174
|
}
|
|
151
175
|
// this doesn't take a direction as that's an implementation detail
|
|
152
176
|
// it doesn't make any sense to use the same edgeType for inbound and outbound edges
|
|
@@ -169,11 +193,11 @@ class Orchestrator {
|
|
|
169
193
|
m.clear();
|
|
170
194
|
}
|
|
171
195
|
}
|
|
172
|
-
buildMainOp() {
|
|
196
|
+
buildMainOp(conditionalBuilder) {
|
|
173
197
|
// this assumes we have validated fields
|
|
174
198
|
switch (this.actualOperation) {
|
|
175
199
|
case action_1.WriteOperation.Delete:
|
|
176
|
-
return new
|
|
200
|
+
return new operations_1.DeleteNodeOperation(this.existingEnt.id, this.options.builder, {
|
|
177
201
|
tableName: this.options.tableName,
|
|
178
202
|
});
|
|
179
203
|
default:
|
|
@@ -190,14 +214,18 @@ class Orchestrator {
|
|
|
190
214
|
fieldsToResolve: this.fieldsToResolve,
|
|
191
215
|
key: this.options.key,
|
|
192
216
|
loadEntOptions: this.options.loaderOptions,
|
|
193
|
-
placeholderID: this.options.builder.placeholderID,
|
|
194
217
|
whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
|
|
195
218
|
expressions: this.options.expressions,
|
|
219
|
+
onConflict: this.onConflict,
|
|
220
|
+
builder: this.options.builder,
|
|
196
221
|
};
|
|
197
222
|
if (this.logValues) {
|
|
198
223
|
opts.fieldsToLog = this.logValues;
|
|
199
224
|
}
|
|
200
|
-
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
|
+
}
|
|
201
229
|
return this.mainOp;
|
|
202
230
|
}
|
|
203
231
|
}
|
|
@@ -217,10 +245,10 @@ class Orchestrator {
|
|
|
217
245
|
throw new Error(`no nodeType for edge when adding outboundEdge`);
|
|
218
246
|
}
|
|
219
247
|
if (edge.direction === edgeDirection.outboundEdge) {
|
|
220
|
-
return
|
|
248
|
+
return operations_1.EdgeOperation.outboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
|
|
221
249
|
}
|
|
222
250
|
else {
|
|
223
|
-
return
|
|
251
|
+
return operations_1.EdgeOperation.inboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
|
|
224
252
|
}
|
|
225
253
|
}
|
|
226
254
|
else if (op === action_1.WriteOperation.Delete) {
|
|
@@ -229,30 +257,55 @@ class Orchestrator {
|
|
|
229
257
|
}
|
|
230
258
|
let id2 = edge.id;
|
|
231
259
|
if (edge.direction === edgeDirection.outboundEdge) {
|
|
232
|
-
return
|
|
260
|
+
return operations_1.EdgeOperation.removeOutboundEdge(this.options.builder, edgeType, id2, edge.options);
|
|
233
261
|
}
|
|
234
262
|
else {
|
|
235
|
-
return
|
|
263
|
+
return operations_1.EdgeOperation.removeInboundEdge(this.options.builder, edgeType, id2, edge.options);
|
|
236
264
|
}
|
|
237
265
|
}
|
|
238
266
|
throw new Error("could not find an edge operation from the given parameters");
|
|
239
267
|
}
|
|
240
|
-
async buildEdgeOps(ops) {
|
|
268
|
+
async buildEdgeOps(ops, conditionalBuilder, conditionalOverride) {
|
|
241
269
|
const edgeDatas = await (0, ent_1.loadEdgeDatas)(...Array.from(this.edgeSet.values()));
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
+
}
|
|
256
309
|
}
|
|
257
310
|
}
|
|
258
311
|
}
|
|
@@ -272,12 +325,50 @@ class Orchestrator {
|
|
|
272
325
|
}
|
|
273
326
|
return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
|
|
274
327
|
}
|
|
275
|
-
|
|
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) {
|
|
276
364
|
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
277
365
|
return this.existingEnt;
|
|
278
366
|
}
|
|
367
|
+
if (!rowToUse) {
|
|
368
|
+
rowToUse = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
|
|
369
|
+
}
|
|
279
370
|
// we create an unsafe ent to be used for privacy policies
|
|
280
|
-
return new this.options.builder.ent(
|
|
371
|
+
return new this.options.builder.ent(viewerToUse, rowToUse);
|
|
281
372
|
}
|
|
282
373
|
getSQLStatementOperation() {
|
|
283
374
|
switch (this.actualOperation) {
|
|
@@ -307,8 +398,8 @@ class Orchestrator {
|
|
|
307
398
|
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
308
399
|
return this.existingEnt;
|
|
309
400
|
}
|
|
310
|
-
const { editedData } = await this.memoizedGetFields();
|
|
311
|
-
return this.getEntForPrivacyPolicyImpl(editedData);
|
|
401
|
+
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
402
|
+
return this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
|
|
312
403
|
}
|
|
313
404
|
// this gets the fields that were explicitly set plus any default or transformed values
|
|
314
405
|
// mainly exists to get default fields e.g. default id to be used in triggers
|
|
@@ -334,9 +425,25 @@ class Orchestrator {
|
|
|
334
425
|
const builder = this.options.builder;
|
|
335
426
|
// future optimization: can get schemaFields to memoize based on different values
|
|
336
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
|
+
}
|
|
337
438
|
const editedFields = await this.options.editedFields();
|
|
338
|
-
let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
|
|
339
|
-
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
|
+
};
|
|
340
447
|
}
|
|
341
448
|
async validate() {
|
|
342
449
|
// existing ent required for edit or delete operations
|
|
@@ -347,7 +454,7 @@ class Orchestrator {
|
|
|
347
454
|
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
348
455
|
}
|
|
349
456
|
}
|
|
350
|
-
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
457
|
+
const { schemaFields, editedData, userDefinedKeys, editPrivacyFields } = await this.memoizedGetFields();
|
|
351
458
|
const action = this.options.action;
|
|
352
459
|
const builder = this.options.builder;
|
|
353
460
|
// this runs in following phases:
|
|
@@ -356,18 +463,40 @@ class Orchestrator {
|
|
|
356
463
|
// * triggers
|
|
357
464
|
// * validators
|
|
358
465
|
let privacyPolicy = action?.getPrivacyPolicy();
|
|
359
|
-
|
|
466
|
+
const errors = [];
|
|
360
467
|
if (privacyPolicy) {
|
|
468
|
+
const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
|
|
361
469
|
try {
|
|
362
|
-
await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy,
|
|
470
|
+
await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, ent, () => this.throwError());
|
|
363
471
|
}
|
|
364
472
|
catch (err) {
|
|
365
|
-
|
|
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
|
+
})());
|
|
366
494
|
}
|
|
495
|
+
await Promise.all(promises);
|
|
367
496
|
}
|
|
368
|
-
//
|
|
369
|
-
if (
|
|
370
|
-
return
|
|
497
|
+
// privacy or field errors should return first so it's less confusing
|
|
498
|
+
if (errors.length) {
|
|
499
|
+
return errors;
|
|
371
500
|
}
|
|
372
501
|
// have to run triggers which update fields first before field and other validators
|
|
373
502
|
// so running this first to build things up
|
|
@@ -381,11 +510,12 @@ class Orchestrator {
|
|
|
381
510
|
// not ideal we're calling this twice. fix...
|
|
382
511
|
// needed for now. may need to rewrite some of this?
|
|
383
512
|
const editedFields2 = await this.options.editedFields();
|
|
384
|
-
const [
|
|
513
|
+
const [errs2, errs3] = await Promise.all([
|
|
385
514
|
this.formatAndValidateFields(schemaFields, editedFields2),
|
|
386
515
|
this.validators(validators, action, builder),
|
|
387
516
|
]);
|
|
388
517
|
errors.push(...errs2);
|
|
518
|
+
errors.push(...errs3);
|
|
389
519
|
return errors;
|
|
390
520
|
}
|
|
391
521
|
async triggers(action, builder, triggers) {
|
|
@@ -465,6 +595,8 @@ class Orchestrator {
|
|
|
465
595
|
// else apply schema tranformation if it exists
|
|
466
596
|
let transformed = null;
|
|
467
597
|
const sqlOp = this.getSQLStatementOperation();
|
|
598
|
+
// why is transform write technically different from upsert?
|
|
599
|
+
// it's create -> update just at the db level...
|
|
468
600
|
if (action?.transformWrite) {
|
|
469
601
|
transformed = await action.transformWrite({
|
|
470
602
|
builder,
|
|
@@ -506,8 +638,8 @@ class Orchestrator {
|
|
|
506
638
|
}
|
|
507
639
|
}
|
|
508
640
|
if (transformed.changeset) {
|
|
509
|
-
const
|
|
510
|
-
this.changesets.push(
|
|
641
|
+
const changeset = await transformed.changeset();
|
|
642
|
+
this.changesets.push(changeset);
|
|
511
643
|
}
|
|
512
644
|
this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
|
|
513
645
|
if (transformed.existingEnt) {
|
|
@@ -519,10 +651,15 @@ class Orchestrator {
|
|
|
519
651
|
}
|
|
520
652
|
// transforming before doing default fields so that we don't create a new id
|
|
521
653
|
// and anything that depends on the type of operations knows what it is
|
|
654
|
+
const userDefinedKeys = new Set();
|
|
522
655
|
for (const [fieldName, field] of schemaFields) {
|
|
523
656
|
let value = editedFields.get(fieldName);
|
|
524
657
|
let defaultValue = undefined;
|
|
525
658
|
let dbKey = this.getStorageKey(fieldName);
|
|
659
|
+
let updateOnlyIfOther = field.onlyUpdateIfOtherFieldsBeingSet_BETA;
|
|
660
|
+
if (value !== undefined) {
|
|
661
|
+
userDefinedKeys.add(dbKey);
|
|
662
|
+
}
|
|
526
663
|
if (value === undefined) {
|
|
527
664
|
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
528
665
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
@@ -536,11 +673,17 @@ class Orchestrator {
|
|
|
536
673
|
if (defaultValue === undefined) {
|
|
537
674
|
throw new Error(`defaultValueOnCreate() returned undefined for field ${fieldName}`);
|
|
538
675
|
}
|
|
676
|
+
if ((0, types_1.isPromise)(defaultValue)) {
|
|
677
|
+
defaultValue = await defaultValue;
|
|
678
|
+
}
|
|
539
679
|
}
|
|
540
680
|
}
|
|
541
681
|
if (field.defaultValueOnEdit &&
|
|
542
682
|
this.actualOperation === action_1.WriteOperation.Edit) {
|
|
543
683
|
defaultValue = field.defaultValueOnEdit(builder, input);
|
|
684
|
+
if ((0, types_1.isPromise)(defaultValue)) {
|
|
685
|
+
defaultValue = await defaultValue;
|
|
686
|
+
}
|
|
544
687
|
}
|
|
545
688
|
}
|
|
546
689
|
if (value !== undefined) {
|
|
@@ -548,7 +691,12 @@ class Orchestrator {
|
|
|
548
691
|
}
|
|
549
692
|
if (defaultValue !== undefined) {
|
|
550
693
|
updateInput = true;
|
|
551
|
-
|
|
694
|
+
if (updateOnlyIfOther) {
|
|
695
|
+
defaultData[dbKey] = defaultValue;
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
data[dbKey] = defaultValue;
|
|
699
|
+
}
|
|
552
700
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
553
701
|
this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
|
|
554
702
|
}
|
|
@@ -564,7 +712,7 @@ class Orchestrator {
|
|
|
564
712
|
this.options.updateInput(this.defaultFieldsByTSName);
|
|
565
713
|
}
|
|
566
714
|
}
|
|
567
|
-
return data;
|
|
715
|
+
return { data, userDefinedKeys };
|
|
568
716
|
}
|
|
569
717
|
hasData(data) {
|
|
570
718
|
for (const _k in data) {
|
|
@@ -592,7 +740,10 @@ class Orchestrator {
|
|
|
592
740
|
}
|
|
593
741
|
else if (this.isBuilder(value)) {
|
|
594
742
|
if (field.valid) {
|
|
595
|
-
|
|
743
|
+
let valid = field.valid(value);
|
|
744
|
+
if ((0, types_1.isPromise)(valid)) {
|
|
745
|
+
valid = await valid;
|
|
746
|
+
}
|
|
596
747
|
if (!valid) {
|
|
597
748
|
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
598
749
|
}
|
|
@@ -604,8 +755,10 @@ class Orchestrator {
|
|
|
604
755
|
}
|
|
605
756
|
else {
|
|
606
757
|
if (field.valid) {
|
|
607
|
-
|
|
608
|
-
|
|
758
|
+
let valid = field.valid(value);
|
|
759
|
+
if ((0, types_1.isPromise)(valid)) {
|
|
760
|
+
valid = await valid;
|
|
761
|
+
}
|
|
609
762
|
if (!valid) {
|
|
610
763
|
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
611
764
|
}
|
|
@@ -713,13 +866,25 @@ class Orchestrator {
|
|
|
713
866
|
async validWithErrors() {
|
|
714
867
|
return this.validate();
|
|
715
868
|
}
|
|
716
|
-
async
|
|
869
|
+
async buildPlusChangeset(conditionalBuilder, conditionalOverride) {
|
|
717
870
|
// validate everything first
|
|
718
871
|
await this.validX();
|
|
719
|
-
let ops = [
|
|
720
|
-
|
|
872
|
+
let ops = [
|
|
873
|
+
this.buildMainOp(conditionalOverride ? conditionalBuilder : undefined),
|
|
874
|
+
];
|
|
875
|
+
await this.buildEdgeOps(ops, conditionalBuilder, conditionalOverride);
|
|
721
876
|
// TODO throw if we try and create a new changeset after previously creating one
|
|
722
|
-
|
|
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);
|
|
723
888
|
}
|
|
724
889
|
async viewerForEntLoad(data) {
|
|
725
890
|
const action = this.options.action;
|
|
@@ -764,18 +929,58 @@ exports.Orchestrator = Orchestrator;
|
|
|
764
929
|
function randomNum() {
|
|
765
930
|
return Math.random().toString(10).substring(2);
|
|
766
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()
|
|
767
936
|
class EntChangeset {
|
|
768
|
-
constructor(viewer, placeholderID,
|
|
937
|
+
constructor(viewer, builder, placeholderID, conditionalOverride, operations, dependencies, changesets, options) {
|
|
769
938
|
this.viewer = viewer;
|
|
939
|
+
this.builder = builder;
|
|
770
940
|
this.placeholderID = placeholderID;
|
|
771
|
-
this.
|
|
941
|
+
this.conditionalOverride = conditionalOverride;
|
|
772
942
|
this.operations = operations;
|
|
773
943
|
this.dependencies = dependencies;
|
|
774
944
|
this.changesets = changesets;
|
|
775
945
|
this.options = options;
|
|
776
946
|
}
|
|
777
947
|
static changesetFrom(builder, ops) {
|
|
778
|
-
return new EntChangeset(builder.viewer,
|
|
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);
|
|
779
984
|
}
|
|
780
985
|
executor() {
|
|
781
986
|
if (this._executor) {
|
|
@@ -786,9 +991,15 @@ class EntChangeset {
|
|
|
786
991
|
// executor and depend on something else in the stack to handle this correctly
|
|
787
992
|
// ComplexExecutor which could be a parent of this should make sure the dependency
|
|
788
993
|
// is resolved beforehand
|
|
789
|
-
return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options
|
|
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
|
+
}));
|
|
790
998
|
}
|
|
791
|
-
return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options
|
|
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
|
+
}));
|
|
792
1003
|
}
|
|
793
1004
|
}
|
|
794
1005
|
exports.EntChangeset = EntChangeset;
|