@snowtop/ent 0.1.0-alpha7 → 0.1.0-alpha75
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 +28 -24
- package/action/executor.d.ts +4 -4
- package/action/executor.js +2 -2
- package/action/experimental_action.d.ts +29 -22
- package/action/experimental_action.js +29 -6
- package/action/orchestrator.d.ts +44 -16
- package/action/orchestrator.js +287 -73
- package/action/privacy.d.ts +2 -2
- package/core/base.d.ts +43 -23
- package/core/base.js +16 -0
- package/core/clause.d.ts +82 -3
- package/core/clause.js +463 -27
- package/core/config.d.ts +26 -0
- package/core/config.js +17 -0
- package/core/context.d.ts +5 -3
- package/core/context.js +7 -2
- package/core/convert.d.ts +1 -1
- package/core/db.d.ts +3 -4
- package/core/db.js +2 -0
- package/core/ent.d.ts +71 -27
- package/core/ent.js +458 -97
- package/core/loaders/assoc_count_loader.d.ts +2 -2
- package/core/loaders/assoc_count_loader.js +6 -1
- package/core/loaders/assoc_edge_loader.d.ts +3 -3
- package/core/loaders/assoc_edge_loader.js +5 -4
- package/core/loaders/index_loader.js +1 -0
- package/core/loaders/loader.js +5 -5
- package/core/loaders/object_loader.d.ts +11 -5
- package/core/loaders/object_loader.js +70 -4
- package/core/loaders/query_loader.d.ts +2 -2
- package/core/loaders/raw_count_loader.d.ts +2 -2
- package/core/logger.d.ts +1 -1
- package/core/logger.js +1 -0
- package/core/privacy.d.ts +26 -25
- package/core/privacy.js +23 -24
- package/core/query/assoc_query.d.ts +6 -6
- package/core/query/custom_query.d.ts +5 -5
- package/core/query/query.d.ts +1 -1
- package/core/query/shared_assoc_test.d.ts +1 -1
- package/core/query/shared_assoc_test.js +17 -5
- package/core/query/shared_test.d.ts +3 -0
- package/core/query/shared_test.js +95 -17
- package/core/viewer.d.ts +4 -3
- package/core/viewer.js +5 -1
- package/graphql/builtins/connection.js +3 -3
- package/graphql/builtins/edge.js +2 -2
- package/graphql/builtins/node.js +1 -1
- package/graphql/graphql.d.ts +3 -2
- package/graphql/graphql.js +30 -23
- package/graphql/node_resolver.d.ts +0 -1
- package/graphql/query/connection_type.js +6 -6
- package/graphql/query/edge_connection.d.ts +9 -9
- package/graphql/query/page_info.d.ts +1 -1
- package/graphql/query/page_info.js +4 -4
- package/graphql/query/shared_assoc_test.js +2 -2
- package/graphql/scalars/time.d.ts +1 -1
- package/index.d.ts +21 -1
- package/index.js +24 -5
- package/package.json +3 -3
- package/parse_schema/parse.d.ts +24 -5
- package/parse_schema/parse.js +90 -8
- package/schema/base_schema.d.ts +36 -1
- package/schema/base_schema.js +51 -2
- package/schema/field.d.ts +34 -6
- package/schema/field.js +67 -2
- package/schema/index.d.ts +2 -2
- package/schema/index.js +8 -1
- package/schema/json_field.d.ts +13 -1
- package/schema/json_field.js +28 -1
- package/schema/schema.d.ts +101 -2
- package/schema/schema.js +127 -5
- package/schema/struct_field.d.ts +11 -1
- package/schema/struct_field.js +43 -4
- package/scripts/custom_graphql.js +127 -16
- package/scripts/{transform_schema.d.ts → migrate_v0.1.d.ts} +0 -0
- package/scripts/migrate_v0.1.js +36 -0
- package/scripts/read_schema.js +25 -2
- package/testutils/builder.d.ts +36 -22
- package/testutils/builder.js +110 -13
- package/testutils/context/test_context.d.ts +2 -2
- package/testutils/context/test_context.js +7 -1
- package/testutils/db/{test_db.d.ts → temp_db.d.ts} +17 -4
- package/testutils/db/{test_db.js → temp_db.js} +76 -19
- package/testutils/ent-graphql-tests/index.d.ts +2 -0
- package/testutils/ent-graphql-tests/index.js +26 -17
- package/testutils/fake_data/fake_contact.d.ts +5 -9
- package/testutils/fake_data/fake_contact.js +17 -21
- package/testutils/fake_data/fake_event.d.ts +5 -9
- package/testutils/fake_data/fake_event.js +24 -28
- package/testutils/fake_data/fake_user.d.ts +6 -10
- package/testutils/fake_data/fake_user.js +25 -29
- package/testutils/fake_data/test_helpers.d.ts +2 -2
- package/testutils/fake_data/test_helpers.js +6 -6
- package/testutils/fake_data/user_query.d.ts +2 -2
- package/testutils/fake_log.d.ts +3 -3
- 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 +58 -0
- package/testutils/write.d.ts +2 -2
- package/testutils/write.js +3 -3
- package/tsc/ast.d.ts +44 -0
- package/tsc/ast.js +267 -0
- package/tsc/compilerOptions.d.ts +6 -0
- package/tsc/compilerOptions.js +40 -1
- package/tsc/move_generated.d.ts +1 -0
- package/tsc/move_generated.js +160 -0
- package/tsc/transform.d.ts +21 -0
- package/tsc/transform.js +167 -0
- package/tsc/transform_action.d.ts +22 -0
- package/tsc/transform_action.js +179 -0
- package/tsc/transform_ent.d.ts +17 -0
- package/tsc/transform_ent.js +59 -0
- package/tsc/transform_schema.d.ts +27 -0
- package/tsc/transform_schema.js +379 -0
- package/scripts/transform_schema.js +0 -288
package/action/orchestrator.js
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
2
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
25
|
exports.EntChangeset = exports.Orchestrator = exports.edgeDirection = void 0;
|
|
4
26
|
const ent_1 = require("../core/ent");
|
|
5
27
|
const schema_1 = require("../schema/schema");
|
|
6
28
|
const action_1 = require("../action");
|
|
7
|
-
const snake_case_1 = require("snake-case");
|
|
8
|
-
const camel_case_1 = require("camel-case");
|
|
9
29
|
const privacy_1 = require("../core/privacy");
|
|
10
30
|
const executor_1 = require("./executor");
|
|
11
31
|
const logger_1 = require("../core/logger");
|
|
32
|
+
const memoizee_1 = __importDefault(require("memoizee"));
|
|
33
|
+
const clause = __importStar(require("../core/clause"));
|
|
12
34
|
var edgeDirection;
|
|
13
35
|
(function (edgeDirection) {
|
|
14
36
|
edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
|
|
@@ -62,6 +84,9 @@ class Orchestrator {
|
|
|
62
84
|
this.defaultFieldsByFieldName = {};
|
|
63
85
|
this.defaultFieldsByTSName = {};
|
|
64
86
|
this.viewer = options.viewer;
|
|
87
|
+
this.actualOperation = this.options.operation;
|
|
88
|
+
this.existingEnt = this.options.builder.existingEnt;
|
|
89
|
+
this.memoizedGetFields = (0, memoizee_1.default)(this.getFieldsInfo.bind(this));
|
|
65
90
|
}
|
|
66
91
|
addEdge(edge, op) {
|
|
67
92
|
this.edgeSet.add(edge.edgeType);
|
|
@@ -80,6 +105,9 @@ class Orchestrator {
|
|
|
80
105
|
m1.set(op, m2);
|
|
81
106
|
this.edges.set(edge.edgeType, m1);
|
|
82
107
|
}
|
|
108
|
+
setDisableTransformations(val) {
|
|
109
|
+
this.disableTransformations = val;
|
|
110
|
+
}
|
|
83
111
|
addInboundEdge(id1, edgeType, nodeType, options) {
|
|
84
112
|
this.addEdge(new edgeInputData({
|
|
85
113
|
id: id1,
|
|
@@ -135,24 +163,28 @@ class Orchestrator {
|
|
|
135
163
|
}
|
|
136
164
|
buildMainOp() {
|
|
137
165
|
// this assumes we have validated fields
|
|
138
|
-
switch (this.
|
|
166
|
+
switch (this.actualOperation) {
|
|
139
167
|
case action_1.WriteOperation.Delete:
|
|
140
|
-
return new ent_1.DeleteNodeOperation(this.
|
|
168
|
+
return new ent_1.DeleteNodeOperation(this.existingEnt.id, {
|
|
141
169
|
tableName: this.options.tableName,
|
|
142
170
|
});
|
|
143
171
|
default:
|
|
172
|
+
if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
|
|
173
|
+
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
174
|
+
}
|
|
144
175
|
const opts = {
|
|
145
176
|
fields: this.validatedFields,
|
|
146
177
|
tableName: this.options.tableName,
|
|
147
178
|
fieldsToResolve: this.fieldsToResolve,
|
|
148
179
|
key: this.options.key,
|
|
149
|
-
|
|
180
|
+
loadEntOptions: this.options.loaderOptions,
|
|
150
181
|
placeholderID: this.options.builder.placeholderID,
|
|
182
|
+
whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
|
|
151
183
|
};
|
|
152
184
|
if (this.logValues) {
|
|
153
185
|
opts.fieldsToLog = this.logValues;
|
|
154
186
|
}
|
|
155
|
-
this.mainOp = new ent_1.EditNodeOperation(opts, this.
|
|
187
|
+
this.mainOp = new ent_1.EditNodeOperation(opts, this.existingEnt);
|
|
156
188
|
return this.mainOp;
|
|
157
189
|
}
|
|
158
190
|
}
|
|
@@ -219,75 +251,173 @@ class Orchestrator {
|
|
|
219
251
|
if (!privacyPolicy || !action) {
|
|
220
252
|
throw new Error(`shouldn't get here if no privacyPolicy for action`);
|
|
221
253
|
}
|
|
222
|
-
if (this.
|
|
254
|
+
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
223
255
|
return new EntCannotCreateEntError(privacyPolicy, action);
|
|
224
256
|
}
|
|
225
|
-
else if (this.
|
|
226
|
-
return new EntCannotEditEntError(privacyPolicy, action, this.
|
|
257
|
+
else if (this.actualOperation === action_1.WriteOperation.Edit) {
|
|
258
|
+
return new EntCannotEditEntError(privacyPolicy, action, this.existingEnt);
|
|
227
259
|
}
|
|
228
|
-
return new EntCannotDeleteEntError(privacyPolicy, action, this.
|
|
260
|
+
return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
|
|
229
261
|
}
|
|
230
|
-
|
|
231
|
-
if (this.
|
|
232
|
-
return this.
|
|
262
|
+
getEntForPrivacyPolicyImpl(editedData) {
|
|
263
|
+
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
264
|
+
return this.existingEnt;
|
|
233
265
|
}
|
|
234
266
|
// we create an unsafe ent to be used for privacy policies
|
|
235
267
|
return new this.options.builder.ent(this.options.builder.viewer, editedData);
|
|
236
268
|
}
|
|
269
|
+
getSQLStatementOperation() {
|
|
270
|
+
switch (this.actualOperation) {
|
|
271
|
+
case action_1.WriteOperation.Edit:
|
|
272
|
+
return schema_1.SQLStatementOperation.Update;
|
|
273
|
+
case action_1.WriteOperation.Insert:
|
|
274
|
+
return schema_1.SQLStatementOperation.Insert;
|
|
275
|
+
case action_1.WriteOperation.Delete:
|
|
276
|
+
return schema_1.SQLStatementOperation.Delete;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
getWriteOpForSQLStamentOp(op) {
|
|
280
|
+
switch (op) {
|
|
281
|
+
case schema_1.SQLStatementOperation.Update:
|
|
282
|
+
return action_1.WriteOperation.Edit;
|
|
283
|
+
case schema_1.SQLStatementOperation.Insert:
|
|
284
|
+
return action_1.WriteOperation.Insert;
|
|
285
|
+
case schema_1.SQLStatementOperation.Update:
|
|
286
|
+
return action_1.WriteOperation.Delete;
|
|
287
|
+
default:
|
|
288
|
+
throw new Error("invalid path");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// if you're doing custom privacy within an action and want to
|
|
292
|
+
// get either the unsafe ent or the existing ent that's being edited
|
|
293
|
+
async getPossibleUnsafeEntForPrivacy() {
|
|
294
|
+
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
295
|
+
return this.existingEnt;
|
|
296
|
+
}
|
|
297
|
+
const { editedData } = await this.memoizedGetFields();
|
|
298
|
+
return this.getEntForPrivacyPolicyImpl(editedData);
|
|
299
|
+
}
|
|
300
|
+
// this gets the fields that were explicitly set plus any default or transformed values
|
|
301
|
+
// mainly exists to get default fields e.g. default id to be used in triggers
|
|
302
|
+
// NOTE: this API may change in the future
|
|
303
|
+
// doesn't work to get ids for autoincrement keys
|
|
304
|
+
async getEditedData() {
|
|
305
|
+
const { editedData } = await this.memoizedGetFields();
|
|
306
|
+
return editedData;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* @returns validated and formatted fields that would be written to the db
|
|
310
|
+
* throws an error if called before valid() or validX() has been called
|
|
311
|
+
*/
|
|
312
|
+
getValidatedFields() {
|
|
313
|
+
if (this.validatedFields === null) {
|
|
314
|
+
throw new Error(`trying to call getValidatedFields before validating fields`);
|
|
315
|
+
}
|
|
316
|
+
return this.validatedFields;
|
|
317
|
+
}
|
|
318
|
+
// Note: this is memoized. call memoizedGetFields instead
|
|
319
|
+
async getFieldsInfo() {
|
|
320
|
+
const action = this.options.action;
|
|
321
|
+
const builder = this.options.builder;
|
|
322
|
+
// future optimization: can get schemaFields to memoize based on different values
|
|
323
|
+
const schemaFields = (0, schema_1.getFields)(this.options.schema);
|
|
324
|
+
const editedFields = await this.options.editedFields();
|
|
325
|
+
let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
|
|
326
|
+
return { editedData, editedFields, schemaFields };
|
|
327
|
+
}
|
|
237
328
|
async validate() {
|
|
238
329
|
// existing ent required for edit or delete operations
|
|
239
|
-
switch (this.
|
|
330
|
+
switch (this.actualOperation) {
|
|
240
331
|
case action_1.WriteOperation.Delete:
|
|
241
332
|
case action_1.WriteOperation.Edit:
|
|
242
|
-
if (!this.
|
|
243
|
-
throw new Error(
|
|
333
|
+
if (!this.existingEnt) {
|
|
334
|
+
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
244
335
|
}
|
|
245
336
|
}
|
|
337
|
+
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
246
338
|
const action = this.options.action;
|
|
247
339
|
const builder = this.options.builder;
|
|
248
|
-
// future optimization: can get schemaFields to memoize based on different values
|
|
249
|
-
const schemaFields = (0, schema_1.getFields)(this.options.schema);
|
|
250
|
-
let editedData = this.getFieldsWithDefaultValues(builder, schemaFields, action);
|
|
251
340
|
// this runs in following phases:
|
|
252
341
|
// * set default fields and pass to builder so the value can be checked by triggers/observers/validators
|
|
253
342
|
// * privacy policy (use unsafe ent if we have it)
|
|
254
343
|
// * triggers
|
|
255
344
|
// * validators
|
|
256
345
|
let privacyPolicy = action?.getPrivacyPolicy();
|
|
346
|
+
let privacyError = null;
|
|
257
347
|
if (privacyPolicy) {
|
|
258
|
-
|
|
348
|
+
try {
|
|
349
|
+
await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicyImpl(editedData), this.throwError.bind(this));
|
|
350
|
+
}
|
|
351
|
+
catch (err) {
|
|
352
|
+
privacyError = err;
|
|
353
|
+
}
|
|
259
354
|
}
|
|
260
355
|
// have to run triggers which update fields first before field and other validators
|
|
261
356
|
// so running this first to build things up
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
357
|
+
if (action?.getTriggers) {
|
|
358
|
+
await this.triggers(action, builder, action.getTriggers());
|
|
359
|
+
}
|
|
360
|
+
let validators = [];
|
|
361
|
+
if (action?.getValidators) {
|
|
362
|
+
validators = action.getValidators();
|
|
363
|
+
}
|
|
364
|
+
// not ideal we're calling this twice. fix...
|
|
365
|
+
// needed for now. may need to rewrite some of this?
|
|
366
|
+
const editedFields2 = await this.options.editedFields();
|
|
367
|
+
const [errors, _] = await Promise.all([
|
|
368
|
+
this.formatAndValidateFields(schemaFields, editedFields2),
|
|
269
369
|
this.validators(validators, action, builder),
|
|
270
370
|
]);
|
|
371
|
+
if (privacyError !== null) {
|
|
372
|
+
errors.unshift(privacyError);
|
|
373
|
+
}
|
|
374
|
+
return errors;
|
|
271
375
|
}
|
|
272
376
|
async triggers(action, builder, triggers) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (Array.isArray(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
377
|
+
let groups = [];
|
|
378
|
+
let lastArray = 0;
|
|
379
|
+
let prevWasArray = false;
|
|
380
|
+
for (let i = 0; i < triggers.length; i++) {
|
|
381
|
+
let t = triggers[i];
|
|
382
|
+
if (Array.isArray(t)) {
|
|
383
|
+
if (!prevWasArray) {
|
|
384
|
+
// @ts-ignore
|
|
385
|
+
groups.push(triggers.slice(lastArray, i));
|
|
283
386
|
}
|
|
387
|
+
groups.push(t);
|
|
388
|
+
prevWasArray = true;
|
|
389
|
+
lastArray++;
|
|
284
390
|
}
|
|
285
|
-
else
|
|
286
|
-
|
|
391
|
+
else {
|
|
392
|
+
if (i === triggers.length - 1) {
|
|
393
|
+
// @ts-ignore
|
|
394
|
+
groups.push(triggers.slice(lastArray, i + 1));
|
|
395
|
+
}
|
|
396
|
+
prevWasArray = false;
|
|
287
397
|
}
|
|
288
|
-
}
|
|
398
|
+
}
|
|
399
|
+
for (const triggers of groups) {
|
|
400
|
+
await Promise.all(triggers.map(async (trigger) => {
|
|
401
|
+
let ret = await trigger.changeset(builder, action.getInput());
|
|
402
|
+
if (Array.isArray(ret)) {
|
|
403
|
+
ret = await Promise.all(ret);
|
|
404
|
+
}
|
|
405
|
+
if (Array.isArray(ret)) {
|
|
406
|
+
for (const v of ret) {
|
|
407
|
+
if (typeof v === "object") {
|
|
408
|
+
this.changesets.push(v);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else if (ret) {
|
|
413
|
+
this.changesets.push(ret);
|
|
414
|
+
}
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
289
417
|
}
|
|
290
418
|
async validators(validators, action, builder) {
|
|
419
|
+
// TODO need to catch errors and return it...
|
|
420
|
+
// don't need it initially since what we need this for doesn't have the errors
|
|
291
421
|
let promises = [];
|
|
292
422
|
validators.forEach((validator) => {
|
|
293
423
|
let res = validator.validate(builder, action.getInput());
|
|
@@ -300,18 +430,79 @@ class Orchestrator {
|
|
|
300
430
|
isBuilder(val) {
|
|
301
431
|
return val.placeholderID !== undefined;
|
|
302
432
|
}
|
|
303
|
-
|
|
304
|
-
|
|
433
|
+
getInputKey(k) {
|
|
434
|
+
return this.options.fieldInfo[k].inputKey;
|
|
435
|
+
}
|
|
436
|
+
getStorageKey(k) {
|
|
437
|
+
return this.options.fieldInfo[k].dbCol;
|
|
438
|
+
}
|
|
439
|
+
async getFieldsWithDefaultValues(builder, schemaFields, editedFields, action) {
|
|
305
440
|
let data = {};
|
|
306
441
|
let defaultData = {};
|
|
307
442
|
let input = action?.getInput() || {};
|
|
308
443
|
let updateInput = false;
|
|
444
|
+
// transformations
|
|
445
|
+
// if action transformations. always do it
|
|
446
|
+
// if disable transformations set, don't do schema transform and just do the right thing
|
|
447
|
+
// else apply schema tranformation if it exists
|
|
448
|
+
let transformed = null;
|
|
449
|
+
const sqlOp = this.getSQLStatementOperation();
|
|
450
|
+
if (action?.transformWrite) {
|
|
451
|
+
transformed = await action.transformWrite({
|
|
452
|
+
builder,
|
|
453
|
+
input,
|
|
454
|
+
op: sqlOp,
|
|
455
|
+
data: editedFields,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
else if (!this.disableTransformations) {
|
|
459
|
+
transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
|
|
460
|
+
builder,
|
|
461
|
+
input,
|
|
462
|
+
op: sqlOp,
|
|
463
|
+
data: editedFields,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
if (transformed) {
|
|
467
|
+
if (sqlOp === schema_1.SQLStatementOperation.Insert && sqlOp !== transformed.op) {
|
|
468
|
+
if (!transformed.existingEnt) {
|
|
469
|
+
throw new Error(`cannot transform an insert operation without providing an existing ent`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (transformed.data) {
|
|
473
|
+
updateInput = true;
|
|
474
|
+
for (const k in transformed.data) {
|
|
475
|
+
let field = schemaFields.get(k);
|
|
476
|
+
if (!field) {
|
|
477
|
+
throw new Error(`tried to transform field with unknown field ${k}`);
|
|
478
|
+
}
|
|
479
|
+
let val = transformed.data[k];
|
|
480
|
+
if (field.format) {
|
|
481
|
+
val = field.format(transformed.data[k]);
|
|
482
|
+
}
|
|
483
|
+
data[this.getStorageKey(k)] = val;
|
|
484
|
+
this.defaultFieldsByTSName[this.getInputKey(k)] = val;
|
|
485
|
+
// hmm do we need this?
|
|
486
|
+
// TODO how to do this for local tests?
|
|
487
|
+
// this.defaultFieldsByFieldName[k] = val;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
|
|
491
|
+
if (transformed.existingEnt) {
|
|
492
|
+
// @ts-ignore
|
|
493
|
+
this.existingEnt = transformed.existingEnt;
|
|
494
|
+
// modify existing ent in builder. it's readonly in generated ents but doesn't apply here
|
|
495
|
+
builder.existingEnt = transformed.existingEnt;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// transforming before doing default fields so that we don't create a new id
|
|
499
|
+
// and anything that depends on the type of operations knows what it is
|
|
309
500
|
for (const [fieldName, field] of schemaFields) {
|
|
310
501
|
let value = editedFields.get(fieldName);
|
|
311
502
|
let defaultValue = undefined;
|
|
312
|
-
let dbKey =
|
|
503
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
313
504
|
if (value === undefined) {
|
|
314
|
-
if (this.
|
|
505
|
+
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
315
506
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
316
507
|
throw new Error(`cannot set both defaultToViewerOnCreate and defaultValueOnCreate`);
|
|
317
508
|
}
|
|
@@ -326,7 +517,7 @@ class Orchestrator {
|
|
|
326
517
|
}
|
|
327
518
|
}
|
|
328
519
|
if (field.defaultValueOnEdit &&
|
|
329
|
-
this.
|
|
520
|
+
this.actualOperation === action_1.WriteOperation.Edit) {
|
|
330
521
|
defaultValue = field.defaultValueOnEdit(builder, input);
|
|
331
522
|
}
|
|
332
523
|
}
|
|
@@ -337,8 +528,7 @@ class Orchestrator {
|
|
|
337
528
|
updateInput = true;
|
|
338
529
|
defaultData[dbKey] = defaultValue;
|
|
339
530
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
340
|
-
|
|
341
|
-
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
|
|
531
|
+
this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
|
|
342
532
|
}
|
|
343
533
|
}
|
|
344
534
|
// if there's data changing, add data
|
|
@@ -364,7 +554,7 @@ class Orchestrator {
|
|
|
364
554
|
// now format and validate...
|
|
365
555
|
if (value === null) {
|
|
366
556
|
if (!field.nullable) {
|
|
367
|
-
|
|
557
|
+
return new Error(`field ${fieldName} set to null for non-nullable field`);
|
|
368
558
|
}
|
|
369
559
|
}
|
|
370
560
|
else if (value === undefined) {
|
|
@@ -374,15 +564,15 @@ class Orchestrator {
|
|
|
374
564
|
// not setting server default as we're depending on the database handling that.
|
|
375
565
|
// server default allowed
|
|
376
566
|
field.serverDefault === undefined &&
|
|
377
|
-
this.
|
|
378
|
-
|
|
567
|
+
this.actualOperation === action_1.WriteOperation.Insert) {
|
|
568
|
+
return new Error(`required field ${fieldName} not set`);
|
|
379
569
|
}
|
|
380
570
|
}
|
|
381
571
|
else if (this.isBuilder(value)) {
|
|
382
572
|
if (field.valid) {
|
|
383
|
-
const valid = await
|
|
573
|
+
const valid = await field.valid(value);
|
|
384
574
|
if (!valid) {
|
|
385
|
-
|
|
575
|
+
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
386
576
|
}
|
|
387
577
|
}
|
|
388
578
|
// keep track of dependencies to resolve
|
|
@@ -393,24 +583,23 @@ class Orchestrator {
|
|
|
393
583
|
else {
|
|
394
584
|
if (field.valid) {
|
|
395
585
|
// TODO this could be async. handle this better
|
|
396
|
-
const valid = await
|
|
586
|
+
const valid = await field.valid(value);
|
|
397
587
|
if (!valid) {
|
|
398
|
-
|
|
588
|
+
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
399
589
|
}
|
|
400
590
|
}
|
|
401
591
|
if (field.format) {
|
|
402
|
-
|
|
403
|
-
value = await Promise.resolve(field.format(value));
|
|
592
|
+
value = await field.format(value);
|
|
404
593
|
}
|
|
405
594
|
}
|
|
406
595
|
return value;
|
|
407
596
|
}
|
|
408
|
-
async formatAndValidateFields(schemaFields) {
|
|
409
|
-
const
|
|
597
|
+
async formatAndValidateFields(schemaFields, editedFields) {
|
|
598
|
+
const errors = [];
|
|
599
|
+
const op = this.actualOperation;
|
|
410
600
|
if (op === action_1.WriteOperation.Delete) {
|
|
411
|
-
return;
|
|
601
|
+
return [];
|
|
412
602
|
}
|
|
413
|
-
const editedFields = this.options.editedFields();
|
|
414
603
|
// build up data to be saved...
|
|
415
604
|
let data = {};
|
|
416
605
|
let logValues = {};
|
|
@@ -420,8 +609,14 @@ class Orchestrator {
|
|
|
420
609
|
// null allowed
|
|
421
610
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
422
611
|
}
|
|
423
|
-
let dbKey =
|
|
424
|
-
|
|
612
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
613
|
+
let ret = await this.transformFieldValue(fieldName, field, dbKey, value);
|
|
614
|
+
if (ret instanceof Error) {
|
|
615
|
+
errors.push(ret);
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
value = ret;
|
|
619
|
+
}
|
|
425
620
|
if (value !== undefined) {
|
|
426
621
|
data[dbKey] = value;
|
|
427
622
|
logValues[dbKey] = field.logValue(value);
|
|
@@ -433,29 +628,48 @@ class Orchestrator {
|
|
|
433
628
|
for (const fieldName in this.defaultFieldsByFieldName) {
|
|
434
629
|
const defaultValue = this.defaultFieldsByFieldName[fieldName];
|
|
435
630
|
let field = schemaFields.get(fieldName);
|
|
436
|
-
let dbKey =
|
|
631
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
437
632
|
// no value, let's just default
|
|
438
633
|
if (data[dbKey] === undefined) {
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
634
|
+
const ret = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
|
|
635
|
+
if (ret instanceof Error) {
|
|
636
|
+
errors.push(ret);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
data[dbKey] = ret;
|
|
640
|
+
logValues[dbKey] = field.logValue(ret);
|
|
641
|
+
}
|
|
442
642
|
}
|
|
443
643
|
}
|
|
444
644
|
}
|
|
445
645
|
this.validatedFields = data;
|
|
446
646
|
this.logValues = logValues;
|
|
647
|
+
return errors;
|
|
447
648
|
}
|
|
448
649
|
async valid() {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
catch (e) {
|
|
453
|
-
(0, logger_1.log)("error", e);
|
|
650
|
+
const errors = await this.validate();
|
|
651
|
+
if (errors.length) {
|
|
652
|
+
errors.map((err) => (0, logger_1.log)("error", err));
|
|
454
653
|
return false;
|
|
455
654
|
}
|
|
456
655
|
return true;
|
|
457
656
|
}
|
|
458
657
|
async validX() {
|
|
658
|
+
const errors = await this.validate();
|
|
659
|
+
if (errors.length) {
|
|
660
|
+
// just throw the first one...
|
|
661
|
+
// TODO we should ideally throw all of them
|
|
662
|
+
throw errors[0];
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* @experimental API that's not guaranteed to remain in the future which returns
|
|
667
|
+
* a list of errors encountered
|
|
668
|
+
* 0 errors indicates valid
|
|
669
|
+
* NOTE that this currently doesn't catch errors returned by validators().
|
|
670
|
+
* If those throws, this still throws and doesn't return them
|
|
671
|
+
*/
|
|
672
|
+
async validWithErrors() {
|
|
459
673
|
return this.validate();
|
|
460
674
|
}
|
|
461
675
|
async build() {
|
|
@@ -470,7 +684,7 @@ class Orchestrator {
|
|
|
470
684
|
if (!action || !action.viewerForEntLoad) {
|
|
471
685
|
return this.options.viewer;
|
|
472
686
|
}
|
|
473
|
-
return action.viewerForEntLoad(data);
|
|
687
|
+
return action.viewerForEntLoad(data, action.builder.viewer.context);
|
|
474
688
|
}
|
|
475
689
|
async returnedRow() {
|
|
476
690
|
if (this.mainOp && this.mainOp.returnedRow) {
|
|
@@ -494,7 +708,7 @@ class Orchestrator {
|
|
|
494
708
|
const viewer = await this.viewerForEntLoad(row);
|
|
495
709
|
const ent = await (0, ent_1.applyPrivacyPolicyForRow)(viewer, this.options.loaderOptions, row);
|
|
496
710
|
if (!ent) {
|
|
497
|
-
if (this.
|
|
711
|
+
if (this.actualOperation == action_1.WriteOperation.Insert) {
|
|
498
712
|
throw new Error(`was able to create ent but not load it`);
|
|
499
713
|
}
|
|
500
714
|
else {
|
package/action/privacy.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { Builder } from "./action";
|
|
|
2
2
|
import { Viewer, ID, Ent, PrivacyResult, PrivacyPolicyRule } from "../core/base";
|
|
3
3
|
export declare class DenyIfBuilder implements PrivacyPolicyRule {
|
|
4
4
|
private id?;
|
|
5
|
-
constructor(id?: ID | Builder<Ent> | undefined);
|
|
5
|
+
constructor(id?: ID | Builder<Ent<Viewer<Ent<any> | null, ID | null>>, any, Ent<Viewer<Ent<any> | null, ID | null>> | null> | undefined);
|
|
6
6
|
apply(_v: Viewer, _ent: Ent): Promise<PrivacyResult>;
|
|
7
7
|
}
|
|
8
8
|
export declare class AllowIfBuilder implements PrivacyPolicyRule {
|
|
9
9
|
private id?;
|
|
10
|
-
constructor(id?: ID | Builder<Ent> | undefined);
|
|
10
|
+
constructor(id?: ID | Builder<Ent<Viewer<Ent<any> | null, ID | null>>, any, Ent<Viewer<Ent<any> | null, ID | null>> | null> | undefined);
|
|
11
11
|
apply(_v: Viewer, _ent: Ent): Promise<PrivacyResult>;
|
|
12
12
|
}
|