@snowtop/ent 0.1.0-alpha9 → 0.1.0-alpha90
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 +36 -31
- package/action/action.js +2 -6
- package/action/executor.d.ts +3 -3
- 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 +38 -16
- package/action/orchestrator.js +219 -61
- package/action/privacy.d.ts +2 -2
- package/core/base.d.ts +45 -24
- package/core/base.js +7 -1
- package/core/clause.d.ts +68 -7
- package/core/clause.js +291 -62
- package/core/config.d.ts +8 -0
- package/core/context.d.ts +5 -3
- package/core/context.js +20 -2
- package/core/convert.d.ts +1 -1
- package/core/db.d.ts +2 -2
- package/core/db.js +1 -1
- package/core/ent.d.ts +79 -24
- package/core/ent.js +520 -168
- 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 +2 -2
- package/core/loaders/index.d.ts +1 -1
- package/core/loaders/index.js +1 -3
- package/core/loaders/index_loader.d.ts +2 -2
- package/core/loaders/loader.js +5 -5
- package/core/loaders/object_loader.d.ts +6 -5
- package/core/loaders/object_loader.js +62 -58
- package/core/loaders/query_loader.d.ts +6 -12
- package/core/loaders/query_loader.js +47 -10
- 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 +21 -25
- package/core/query/assoc_query.d.ts +6 -6
- package/core/query/custom_clause_query.d.ts +24 -0
- package/core/query/custom_clause_query.js +72 -0
- package/core/query/custom_query.d.ts +20 -5
- package/core/query/custom_query.js +77 -10
- package/core/query/index.d.ts +1 -0
- package/core/query/index.js +3 -1
- package/core/query/query.d.ts +1 -1
- package/core/query/query.js +8 -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 +211 -30
- package/core/viewer.d.ts +3 -3
- package/core/viewer.js +1 -1
- package/graphql/graphql.js +6 -0
- package/graphql/query/edge_connection.d.ts +9 -9
- package/graphql/query/page_info.d.ts +1 -1
- package/index.d.ts +11 -5
- package/index.js +15 -6
- package/package.json +1 -1
- package/parse_schema/parse.d.ts +12 -3
- package/parse_schema/parse.js +70 -11
- package/schema/base_schema.js +3 -0
- package/schema/field.d.ts +44 -8
- package/schema/field.js +125 -9
- package/schema/index.d.ts +2 -2
- package/schema/json_field.d.ts +13 -1
- package/schema/json_field.js +28 -1
- package/schema/schema.d.ts +65 -11
- package/schema/schema.js +18 -4
- package/schema/struct_field.d.ts +11 -1
- package/schema/struct_field.js +44 -5
- package/scripts/custom_graphql.js +8 -3
- 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 +15 -4
- package/testutils/builder.d.ts +31 -21
- package/testutils/builder.js +83 -29
- 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} +15 -3
- package/testutils/db/{test_db.js → temp_db.js} +70 -16
- package/testutils/db/value.d.ts +6 -0
- package/testutils/db/value.js +251 -0
- package/testutils/db_time_zone.d.ts +4 -0
- package/testutils/db_time_zone.js +41 -0
- package/testutils/fake_data/fake_contact.d.ts +5 -4
- package/testutils/fake_data/fake_contact.js +14 -6
- package/testutils/fake_data/fake_event.d.ts +5 -3
- package/testutils/fake_data/fake_event.js +8 -5
- package/testutils/fake_data/fake_user.d.ts +4 -4
- package/testutils/fake_data/fake_user.js +16 -13
- package/testutils/fake_data/test_helpers.d.ts +3 -2
- package/testutils/fake_data/test_helpers.js +8 -6
- package/testutils/fake_data/user_query.d.ts +8 -6
- package/testutils/fake_data/user_query.js +28 -21
- 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 +29 -7
- 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 -445
package/action/orchestrator.js
CHANGED
|
@@ -1,13 +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 camel_case_1 = require("camel-case");
|
|
8
29
|
const privacy_1 = require("../core/privacy");
|
|
9
30
|
const executor_1 = require("./executor");
|
|
10
31
|
const logger_1 = require("../core/logger");
|
|
32
|
+
const memoizee_1 = __importDefault(require("memoizee"));
|
|
33
|
+
const clause = __importStar(require("../core/clause"));
|
|
11
34
|
var edgeDirection;
|
|
12
35
|
(function (edgeDirection) {
|
|
13
36
|
edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
|
|
@@ -63,6 +86,11 @@ class Orchestrator {
|
|
|
63
86
|
this.viewer = options.viewer;
|
|
64
87
|
this.actualOperation = this.options.operation;
|
|
65
88
|
this.existingEnt = this.options.builder.existingEnt;
|
|
89
|
+
this.memoizedGetFields = (0, memoizee_1.default)(this.getFieldsInfo.bind(this));
|
|
90
|
+
}
|
|
91
|
+
// don't type this because we don't care
|
|
92
|
+
__getOptions() {
|
|
93
|
+
return this.options;
|
|
66
94
|
}
|
|
67
95
|
addEdge(edge, op) {
|
|
68
96
|
this.edgeSet.add(edge.edgeType);
|
|
@@ -155,6 +183,7 @@ class Orchestrator {
|
|
|
155
183
|
key: this.options.key,
|
|
156
184
|
loadEntOptions: this.options.loaderOptions,
|
|
157
185
|
placeholderID: this.options.builder.placeholderID,
|
|
186
|
+
whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
|
|
158
187
|
};
|
|
159
188
|
if (this.logValues) {
|
|
160
189
|
opts.fieldsToLog = this.logValues;
|
|
@@ -269,9 +298,28 @@ class Orchestrator {
|
|
|
269
298
|
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
270
299
|
return this.existingEnt;
|
|
271
300
|
}
|
|
272
|
-
const { editedData } = await this.
|
|
301
|
+
const { editedData } = await this.memoizedGetFields();
|
|
273
302
|
return this.getEntForPrivacyPolicyImpl(editedData);
|
|
274
303
|
}
|
|
304
|
+
// this gets the fields that were explicitly set plus any default or transformed values
|
|
305
|
+
// mainly exists to get default fields e.g. default id to be used in triggers
|
|
306
|
+
// NOTE: this API may change in the future
|
|
307
|
+
// doesn't work to get ids for autoincrement keys
|
|
308
|
+
async getEditedData() {
|
|
309
|
+
const { editedData } = await this.memoizedGetFields();
|
|
310
|
+
return editedData;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* @returns validated and formatted fields that would be written to the db
|
|
314
|
+
* throws an error if called before valid() or validX() has been called
|
|
315
|
+
*/
|
|
316
|
+
getValidatedFields() {
|
|
317
|
+
if (this.validatedFields === null) {
|
|
318
|
+
throw new Error(`trying to call getValidatedFields before validating fields`);
|
|
319
|
+
}
|
|
320
|
+
return this.validatedFields;
|
|
321
|
+
}
|
|
322
|
+
// Note: this is memoized. call memoizedGetFields instead
|
|
275
323
|
async getFieldsInfo() {
|
|
276
324
|
const action = this.options.action;
|
|
277
325
|
const builder = this.options.builder;
|
|
@@ -290,7 +338,7 @@ class Orchestrator {
|
|
|
290
338
|
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
291
339
|
}
|
|
292
340
|
}
|
|
293
|
-
const { schemaFields, editedData } = await this.
|
|
341
|
+
const { schemaFields, editedData } = await this.memoizedGetFields();
|
|
294
342
|
const action = this.options.action;
|
|
295
343
|
const builder = this.options.builder;
|
|
296
344
|
// this runs in following phases:
|
|
@@ -299,55 +347,104 @@ class Orchestrator {
|
|
|
299
347
|
// * triggers
|
|
300
348
|
// * validators
|
|
301
349
|
let privacyPolicy = action?.getPrivacyPolicy();
|
|
350
|
+
let privacyError = null;
|
|
302
351
|
if (privacyPolicy) {
|
|
303
|
-
|
|
352
|
+
try {
|
|
353
|
+
await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicyImpl(editedData), this.throwError.bind(this));
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
privacyError = err;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// privacyError should return first since it's less confusing
|
|
360
|
+
if (privacyError !== null) {
|
|
361
|
+
return [privacyError];
|
|
304
362
|
}
|
|
305
363
|
// have to run triggers which update fields first before field and other validators
|
|
306
364
|
// so running this first to build things up
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
365
|
+
if (action?.getTriggers) {
|
|
366
|
+
await this.triggers(action, builder, action.getTriggers());
|
|
367
|
+
}
|
|
368
|
+
let validators = [];
|
|
369
|
+
if (action?.getValidators) {
|
|
370
|
+
validators = action.getValidators();
|
|
310
371
|
}
|
|
311
|
-
let validators = action?.validators || [];
|
|
312
372
|
// not ideal we're calling this twice. fix...
|
|
313
373
|
// needed for now. may need to rewrite some of this?
|
|
314
374
|
const editedFields2 = await this.options.editedFields();
|
|
315
|
-
await Promise.all([
|
|
375
|
+
const [errors, errs2] = await Promise.all([
|
|
316
376
|
this.formatAndValidateFields(schemaFields, editedFields2),
|
|
317
377
|
this.validators(validators, action, builder),
|
|
318
378
|
]);
|
|
379
|
+
errors.push(...errs2);
|
|
380
|
+
return errors;
|
|
319
381
|
}
|
|
320
382
|
async triggers(action, builder, triggers) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (Array.isArray(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
383
|
+
let groups = [];
|
|
384
|
+
let lastArray = 0;
|
|
385
|
+
let prevWasArray = false;
|
|
386
|
+
for (let i = 0; i < triggers.length; i++) {
|
|
387
|
+
let t = triggers[i];
|
|
388
|
+
if (Array.isArray(t)) {
|
|
389
|
+
if (!prevWasArray) {
|
|
390
|
+
// @ts-ignore
|
|
391
|
+
groups.push(triggers.slice(lastArray, i));
|
|
331
392
|
}
|
|
393
|
+
groups.push(t);
|
|
394
|
+
prevWasArray = true;
|
|
395
|
+
lastArray++;
|
|
332
396
|
}
|
|
333
|
-
else
|
|
334
|
-
|
|
397
|
+
else {
|
|
398
|
+
if (i === triggers.length - 1) {
|
|
399
|
+
// @ts-ignore
|
|
400
|
+
groups.push(triggers.slice(lastArray, i + 1));
|
|
401
|
+
}
|
|
402
|
+
prevWasArray = false;
|
|
335
403
|
}
|
|
336
|
-
}
|
|
404
|
+
}
|
|
405
|
+
for (const triggers of groups) {
|
|
406
|
+
await Promise.all(triggers.map(async (trigger) => {
|
|
407
|
+
let ret = await trigger.changeset(builder, action.getInput());
|
|
408
|
+
if (Array.isArray(ret)) {
|
|
409
|
+
ret = await Promise.all(ret);
|
|
410
|
+
}
|
|
411
|
+
if (Array.isArray(ret)) {
|
|
412
|
+
for (const v of ret) {
|
|
413
|
+
if (typeof v === "object") {
|
|
414
|
+
this.changesets.push(v);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else if (ret) {
|
|
419
|
+
this.changesets.push(ret);
|
|
420
|
+
}
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
337
423
|
}
|
|
338
424
|
async validators(validators, action, builder) {
|
|
339
|
-
|
|
340
|
-
validators.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
425
|
+
const errors = [];
|
|
426
|
+
await Promise.all(validators.map(async (v) => {
|
|
427
|
+
try {
|
|
428
|
+
const r = await v.validate(builder, action.getInput());
|
|
429
|
+
if (r instanceof Error) {
|
|
430
|
+
errors.push(r);
|
|
431
|
+
}
|
|
344
432
|
}
|
|
345
|
-
|
|
346
|
-
|
|
433
|
+
catch (err) {
|
|
434
|
+
errors.push(err);
|
|
435
|
+
}
|
|
436
|
+
}));
|
|
437
|
+
return errors;
|
|
347
438
|
}
|
|
348
439
|
isBuilder(val) {
|
|
349
440
|
return val.placeholderID !== undefined;
|
|
350
441
|
}
|
|
442
|
+
getInputKey(k) {
|
|
443
|
+
return this.options.fieldInfo[k].inputKey;
|
|
444
|
+
}
|
|
445
|
+
getStorageKey(k) {
|
|
446
|
+
return this.options.fieldInfo[k].dbCol;
|
|
447
|
+
}
|
|
351
448
|
async getFieldsWithDefaultValues(builder, schemaFields, editedFields, action) {
|
|
352
449
|
let data = {};
|
|
353
450
|
let defaultData = {};
|
|
@@ -357,24 +454,30 @@ class Orchestrator {
|
|
|
357
454
|
// if action transformations. always do it
|
|
358
455
|
// if disable transformations set, don't do schema transform and just do the right thing
|
|
359
456
|
// else apply schema tranformation if it exists
|
|
360
|
-
let transformed;
|
|
457
|
+
let transformed = null;
|
|
458
|
+
const sqlOp = this.getSQLStatementOperation();
|
|
361
459
|
if (action?.transformWrite) {
|
|
362
460
|
transformed = await action.transformWrite({
|
|
363
|
-
|
|
364
|
-
|
|
461
|
+
builder,
|
|
462
|
+
input,
|
|
463
|
+
op: sqlOp,
|
|
365
464
|
data: editedFields,
|
|
366
|
-
existingEnt: this.existingEnt,
|
|
367
465
|
});
|
|
368
466
|
}
|
|
369
467
|
else if (!this.disableTransformations) {
|
|
370
468
|
transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
|
|
371
|
-
|
|
372
|
-
|
|
469
|
+
builder,
|
|
470
|
+
input,
|
|
471
|
+
op: sqlOp,
|
|
373
472
|
data: editedFields,
|
|
374
|
-
existingEnt: this.existingEnt,
|
|
375
473
|
});
|
|
376
474
|
}
|
|
377
475
|
if (transformed) {
|
|
476
|
+
if (sqlOp === schema_1.SQLStatementOperation.Insert && sqlOp !== transformed.op) {
|
|
477
|
+
if (!transformed.existingEnt) {
|
|
478
|
+
throw new Error(`cannot transform an insert operation without providing an existing ent`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
378
481
|
if (transformed.data) {
|
|
379
482
|
updateInput = true;
|
|
380
483
|
for (const k in transformed.data) {
|
|
@@ -386,17 +489,23 @@ class Orchestrator {
|
|
|
386
489
|
if (field.format) {
|
|
387
490
|
val = field.format(transformed.data[k]);
|
|
388
491
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(k)] = val;
|
|
492
|
+
data[this.getStorageKey(k)] = val;
|
|
493
|
+
this.defaultFieldsByTSName[this.getInputKey(k)] = val;
|
|
392
494
|
// hmm do we need this?
|
|
393
495
|
// TODO how to do this for local tests?
|
|
394
496
|
// this.defaultFieldsByFieldName[k] = val;
|
|
395
497
|
}
|
|
396
498
|
}
|
|
499
|
+
if (transformed.changeset) {
|
|
500
|
+
const ct = await transformed.changeset();
|
|
501
|
+
this.changesets.push(ct);
|
|
502
|
+
}
|
|
397
503
|
this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
|
|
398
504
|
if (transformed.existingEnt) {
|
|
505
|
+
// @ts-ignore
|
|
399
506
|
this.existingEnt = transformed.existingEnt;
|
|
507
|
+
// modify existing ent in builder. it's readonly in generated ents but doesn't apply here
|
|
508
|
+
builder.existingEnt = transformed.existingEnt;
|
|
400
509
|
}
|
|
401
510
|
}
|
|
402
511
|
// transforming before doing default fields so that we don't create a new id
|
|
@@ -404,7 +513,7 @@ class Orchestrator {
|
|
|
404
513
|
for (const [fieldName, field] of schemaFields) {
|
|
405
514
|
let value = editedFields.get(fieldName);
|
|
406
515
|
let defaultValue = undefined;
|
|
407
|
-
let dbKey =
|
|
516
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
408
517
|
if (value === undefined) {
|
|
409
518
|
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
410
519
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
@@ -432,8 +541,7 @@ class Orchestrator {
|
|
|
432
541
|
updateInput = true;
|
|
433
542
|
defaultData[dbKey] = defaultValue;
|
|
434
543
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
435
|
-
|
|
436
|
-
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
|
|
544
|
+
this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
|
|
437
545
|
}
|
|
438
546
|
}
|
|
439
547
|
// if there's data changing, add data
|
|
@@ -459,7 +567,7 @@ class Orchestrator {
|
|
|
459
567
|
// now format and validate...
|
|
460
568
|
if (value === null) {
|
|
461
569
|
if (!field.nullable) {
|
|
462
|
-
|
|
570
|
+
return new Error(`field ${fieldName} set to null for non-nullable field`);
|
|
463
571
|
}
|
|
464
572
|
}
|
|
465
573
|
else if (value === undefined) {
|
|
@@ -470,14 +578,14 @@ class Orchestrator {
|
|
|
470
578
|
// server default allowed
|
|
471
579
|
field.serverDefault === undefined &&
|
|
472
580
|
this.actualOperation === action_1.WriteOperation.Insert) {
|
|
473
|
-
|
|
581
|
+
return new Error(`required field ${fieldName} not set`);
|
|
474
582
|
}
|
|
475
583
|
}
|
|
476
584
|
else if (this.isBuilder(value)) {
|
|
477
585
|
if (field.valid) {
|
|
478
586
|
const valid = await field.valid(value);
|
|
479
587
|
if (!valid) {
|
|
480
|
-
|
|
588
|
+
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
481
589
|
}
|
|
482
590
|
}
|
|
483
591
|
// keep track of dependencies to resolve
|
|
@@ -490,66 +598,110 @@ class Orchestrator {
|
|
|
490
598
|
// TODO this could be async. handle this better
|
|
491
599
|
const valid = await field.valid(value);
|
|
492
600
|
if (!valid) {
|
|
493
|
-
|
|
601
|
+
return new Error(`invalid field ${fieldName} with value ${value}`);
|
|
494
602
|
}
|
|
495
603
|
}
|
|
496
604
|
if (field.format) {
|
|
497
|
-
|
|
498
|
-
value = await Promise.resolve(field.format(value));
|
|
605
|
+
value = await field.format(value);
|
|
499
606
|
}
|
|
500
607
|
}
|
|
501
608
|
return value;
|
|
502
609
|
}
|
|
503
610
|
async formatAndValidateFields(schemaFields, editedFields) {
|
|
611
|
+
const errors = [];
|
|
504
612
|
const op = this.actualOperation;
|
|
505
613
|
if (op === action_1.WriteOperation.Delete) {
|
|
506
|
-
return;
|
|
614
|
+
return [];
|
|
507
615
|
}
|
|
508
616
|
// build up data to be saved...
|
|
509
617
|
let data = {};
|
|
510
618
|
let logValues = {};
|
|
619
|
+
let needsFullDataChecks = [];
|
|
511
620
|
for (const [fieldName, field] of schemaFields) {
|
|
512
621
|
let value = editedFields.get(fieldName);
|
|
622
|
+
if (field.validateWithFullData) {
|
|
623
|
+
needsFullDataChecks.push(fieldName);
|
|
624
|
+
}
|
|
513
625
|
if (value === undefined && op === action_1.WriteOperation.Insert) {
|
|
514
626
|
// null allowed
|
|
515
627
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
516
628
|
}
|
|
517
|
-
let dbKey =
|
|
518
|
-
|
|
629
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
630
|
+
let ret = await this.transformFieldValue(fieldName, field, dbKey, value);
|
|
631
|
+
if (ret instanceof Error) {
|
|
632
|
+
errors.push(ret);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
value = ret;
|
|
636
|
+
}
|
|
519
637
|
if (value !== undefined) {
|
|
520
638
|
data[dbKey] = value;
|
|
521
639
|
logValues[dbKey] = field.logValue(value);
|
|
522
640
|
}
|
|
523
641
|
}
|
|
642
|
+
for (const fieldName of needsFullDataChecks) {
|
|
643
|
+
const field = schemaFields.get(fieldName);
|
|
644
|
+
let value = editedFields.get(fieldName);
|
|
645
|
+
// @ts-ignore...
|
|
646
|
+
// type hackery because it's hard
|
|
647
|
+
const v = await field.validateWithFullData(value, this.options.builder);
|
|
648
|
+
if (!v) {
|
|
649
|
+
if (value === undefined) {
|
|
650
|
+
errors.push(new Error(`field ${fieldName} set to undefined when it can't be nullable`));
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
errors.push(new Error(`field ${fieldName} set to null when it can't be nullable`));
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
524
657
|
// we ignored default values while editing.
|
|
525
658
|
// if we're editing and there's data, add default values
|
|
526
659
|
if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
|
|
527
660
|
for (const fieldName in this.defaultFieldsByFieldName) {
|
|
528
661
|
const defaultValue = this.defaultFieldsByFieldName[fieldName];
|
|
529
662
|
let field = schemaFields.get(fieldName);
|
|
530
|
-
let dbKey =
|
|
663
|
+
let dbKey = this.getStorageKey(fieldName);
|
|
531
664
|
// no value, let's just default
|
|
532
665
|
if (data[dbKey] === undefined) {
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
666
|
+
const ret = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
|
|
667
|
+
if (ret instanceof Error) {
|
|
668
|
+
errors.push(ret);
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
data[dbKey] = ret;
|
|
672
|
+
logValues[dbKey] = field.logValue(ret);
|
|
673
|
+
}
|
|
536
674
|
}
|
|
537
675
|
}
|
|
538
676
|
}
|
|
539
677
|
this.validatedFields = data;
|
|
540
678
|
this.logValues = logValues;
|
|
679
|
+
return errors;
|
|
541
680
|
}
|
|
542
681
|
async valid() {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
catch (e) {
|
|
547
|
-
(0, logger_1.log)("error", e);
|
|
682
|
+
const errors = await this.validate();
|
|
683
|
+
if (errors.length) {
|
|
684
|
+
errors.map((err) => (0, logger_1.log)("error", err));
|
|
548
685
|
return false;
|
|
549
686
|
}
|
|
550
687
|
return true;
|
|
551
688
|
}
|
|
552
689
|
async validX() {
|
|
690
|
+
const errors = await this.validate();
|
|
691
|
+
if (errors.length) {
|
|
692
|
+
// just throw the first one...
|
|
693
|
+
// TODO we should ideally throw all of them
|
|
694
|
+
throw errors[0];
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* @experimental API that's not guaranteed to remain in the future which returns
|
|
699
|
+
* a list of errors encountered
|
|
700
|
+
* 0 errors indicates valid
|
|
701
|
+
* NOTE that this currently doesn't catch errors returned by validators().
|
|
702
|
+
* If those throws, this still throws and doesn't return them
|
|
703
|
+
*/
|
|
704
|
+
async validWithErrors() {
|
|
553
705
|
return this.validate();
|
|
554
706
|
}
|
|
555
707
|
async build() {
|
|
@@ -564,7 +716,7 @@ class Orchestrator {
|
|
|
564
716
|
if (!action || !action.viewerForEntLoad) {
|
|
565
717
|
return this.options.viewer;
|
|
566
718
|
}
|
|
567
|
-
return action.viewerForEntLoad(data);
|
|
719
|
+
return action.viewerForEntLoad(data, action.builder.viewer.context);
|
|
568
720
|
}
|
|
569
721
|
async returnedRow() {
|
|
570
722
|
if (this.mainOp && this.mainOp.returnedRow) {
|
|
@@ -599,6 +751,9 @@ class Orchestrator {
|
|
|
599
751
|
}
|
|
600
752
|
}
|
|
601
753
|
exports.Orchestrator = Orchestrator;
|
|
754
|
+
function randomNum() {
|
|
755
|
+
return Math.random().toString(10).substring(2);
|
|
756
|
+
}
|
|
602
757
|
class EntChangeset {
|
|
603
758
|
constructor(viewer, placeholderID, ent, operations, dependencies, changesets, options) {
|
|
604
759
|
this.viewer = viewer;
|
|
@@ -609,6 +764,9 @@ class EntChangeset {
|
|
|
609
764
|
this.changesets = changesets;
|
|
610
765
|
this.options = options;
|
|
611
766
|
}
|
|
767
|
+
static changesetFrom(builder, ops) {
|
|
768
|
+
return new EntChangeset(builder.viewer, `$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, builder.ent, ops);
|
|
769
|
+
}
|
|
612
770
|
executor() {
|
|
613
771
|
if (this._executor) {
|
|
614
772
|
return this._executor;
|
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
|
}
|
package/core/base.d.ts
CHANGED
|
@@ -5,9 +5,15 @@ export interface Loader<T, V> {
|
|
|
5
5
|
loadMany?(keys: T[]): Promise<(V | null)[]>;
|
|
6
6
|
clearAll(): any;
|
|
7
7
|
}
|
|
8
|
-
export interface
|
|
8
|
+
export interface LoaderWithLoadMany<T, V> extends Loader<T, V> {
|
|
9
|
+
loadMany(keys: T[]): Promise<V[]>;
|
|
10
|
+
}
|
|
11
|
+
export interface LoaderFactory<K, V> {
|
|
9
12
|
name: string;
|
|
10
|
-
createLoader(context?: Context): Loader<
|
|
13
|
+
createLoader(context?: Context): Loader<K, V>;
|
|
14
|
+
}
|
|
15
|
+
interface LoaderFactoryWithLoaderMany<T, V> extends LoaderFactory<T, V> {
|
|
16
|
+
createLoader(context?: Context): LoaderWithLoadMany<T, V>;
|
|
11
17
|
}
|
|
12
18
|
export interface ConfigurableLoaderFactory<T, V> extends LoaderFactory<T, V> {
|
|
13
19
|
createConfigurableLoader(options: EdgeQueryableDataOptions, context?: Context): Loader<T, V>;
|
|
@@ -15,9 +21,11 @@ export interface ConfigurableLoaderFactory<T, V> extends LoaderFactory<T, V> {
|
|
|
15
21
|
export declare type EdgeQueryableDataOptions = Partial<Pick<QueryableDataOptions, "limit" | "orderby" | "clause">>;
|
|
16
22
|
export interface PrimableLoader<T, V> extends Loader<T, V> {
|
|
17
23
|
prime(d: Data): void;
|
|
24
|
+
primeAll?(d: Data): void;
|
|
18
25
|
}
|
|
19
26
|
interface cache {
|
|
20
27
|
getLoader<T, V>(name: string, create: () => Loader<T, V>): Loader<T, V>;
|
|
28
|
+
getLoaderWithLoadMany<T, V>(name: string, create: () => LoaderWithLoadMany<T, V>): LoaderWithLoadMany<T, V>;
|
|
21
29
|
getCachedRows(options: queryOptions): Data[] | null;
|
|
22
30
|
getCachedRow(options: queryOptions): Data | null;
|
|
23
31
|
primeCache(options: queryOptions, rows: Data[]): void;
|
|
@@ -30,27 +38,27 @@ interface queryOptions {
|
|
|
30
38
|
clause: clause.Clause;
|
|
31
39
|
orderby?: string;
|
|
32
40
|
}
|
|
33
|
-
export interface Context {
|
|
34
|
-
getViewer():
|
|
41
|
+
export interface Context<TViewer extends Viewer = Viewer> {
|
|
42
|
+
getViewer(): TViewer;
|
|
35
43
|
cache?: cache;
|
|
36
44
|
}
|
|
37
|
-
export interface Viewer {
|
|
38
|
-
viewerID:
|
|
39
|
-
viewer: () => Promise<
|
|
45
|
+
export interface Viewer<TEnt extends any = Ent<any> | null, TID extends any = ID | null> {
|
|
46
|
+
viewerID: TID;
|
|
47
|
+
viewer: () => Promise<TEnt>;
|
|
40
48
|
instanceKey: () => string;
|
|
41
|
-
context?: Context
|
|
49
|
+
context?: Context<any>;
|
|
42
50
|
}
|
|
43
|
-
export interface Ent {
|
|
51
|
+
export interface Ent<TViewer extends Viewer = Viewer> {
|
|
44
52
|
id: ID;
|
|
45
|
-
viewer:
|
|
46
|
-
|
|
53
|
+
viewer: TViewer;
|
|
54
|
+
getPrivacyPolicy(): PrivacyPolicy<this, TViewer>;
|
|
47
55
|
nodeType: string;
|
|
48
56
|
}
|
|
49
57
|
export declare type Data = {
|
|
50
58
|
[key: string]: any;
|
|
51
59
|
};
|
|
52
|
-
export interface EntConstructor<
|
|
53
|
-
new (viewer:
|
|
60
|
+
export interface EntConstructor<TEnt extends Ent, TViewer extends Viewer = Viewer> {
|
|
61
|
+
new (viewer: TViewer, data: Data): TEnt;
|
|
54
62
|
}
|
|
55
63
|
export declare type ID = string | number;
|
|
56
64
|
export interface DataOptions {
|
|
@@ -72,6 +80,7 @@ export interface QueryDataOptions {
|
|
|
72
80
|
orderby?: string;
|
|
73
81
|
groupby?: string;
|
|
74
82
|
limit?: number;
|
|
83
|
+
disableTransformations?: boolean;
|
|
75
84
|
}
|
|
76
85
|
export interface LoadRowOptions extends QueryableDataOptions {
|
|
77
86
|
}
|
|
@@ -82,17 +91,24 @@ export interface CreateRowOptions extends DataOptions {
|
|
|
82
91
|
fieldsToLog?: Data;
|
|
83
92
|
}
|
|
84
93
|
export interface EditRowOptions extends CreateRowOptions {
|
|
85
|
-
|
|
94
|
+
whereClause: clause.Clause;
|
|
86
95
|
}
|
|
87
|
-
interface LoadableEntOptions<
|
|
88
|
-
loaderFactory:
|
|
89
|
-
ent: EntConstructor<
|
|
96
|
+
interface LoadableEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> {
|
|
97
|
+
loaderFactory: LoaderFactoryWithOptions;
|
|
98
|
+
ent: EntConstructor<TEnt, TViewer>;
|
|
99
|
+
}
|
|
100
|
+
export interface LoaderFactoryWithOptions extends LoaderFactoryWithLoaderMany<any, Data | null> {
|
|
101
|
+
options?: SelectDataOptions;
|
|
90
102
|
}
|
|
91
|
-
export interface LoadEntOptions<
|
|
103
|
+
export interface LoadEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends LoadableEntOptions<TEnt, TViewer>, SelectBaseDataOptions {
|
|
92
104
|
fieldPrivacy?: Map<string, PrivacyPolicy>;
|
|
93
105
|
}
|
|
94
|
-
export interface
|
|
95
|
-
|
|
106
|
+
export interface SelectCustomDataOptions extends SelectBaseDataOptions {
|
|
107
|
+
loaderFactory: LoaderFactoryWithOptions;
|
|
108
|
+
prime?: boolean;
|
|
109
|
+
}
|
|
110
|
+
export interface LoadCustomEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends SelectCustomDataOptions {
|
|
111
|
+
ent: EntConstructor<TEnt, TViewer>;
|
|
96
112
|
fieldPrivacy?: Map<string, PrivacyPolicy>;
|
|
97
113
|
}
|
|
98
114
|
export interface LoaderInfo {
|
|
@@ -121,10 +137,15 @@ export declare function Allow(): PrivacyResult;
|
|
|
121
137
|
export declare function Skip(): PrivacyResult;
|
|
122
138
|
export declare function Deny(): PrivacyResult;
|
|
123
139
|
export declare function DenyWithReason(e: PrivacyError | string): PrivacyResult;
|
|
124
|
-
export interface PrivacyPolicyRule<TEnt extends Ent = Ent> {
|
|
125
|
-
apply(v:
|
|
140
|
+
export interface PrivacyPolicyRule<TEnt extends Ent = Ent, TViewer = Viewer> {
|
|
141
|
+
apply(v: TViewer, ent?: TEnt): Promise<PrivacyResult>;
|
|
142
|
+
}
|
|
143
|
+
export interface PrivacyPolicy<TEnt extends Ent = Ent, TViewer = Viewer> {
|
|
144
|
+
rules: PrivacyPolicyRule<TEnt, TViewer>[];
|
|
126
145
|
}
|
|
127
|
-
export
|
|
128
|
-
|
|
146
|
+
export declare enum WriteOperation {
|
|
147
|
+
Insert = "insert",
|
|
148
|
+
Edit = "edit",
|
|
149
|
+
Delete = "delete"
|
|
129
150
|
}
|
|
130
151
|
export {};
|
package/core/base.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DenyWithReason = exports.Deny = exports.Skip = exports.Allow = void 0;
|
|
3
|
+
exports.WriteOperation = exports.DenyWithReason = exports.Deny = exports.Skip = exports.Allow = void 0;
|
|
4
4
|
// Privacy
|
|
5
5
|
var privacyResult;
|
|
6
6
|
(function (privacyResult) {
|
|
@@ -53,3 +53,9 @@ function DenyWithReason(e) {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
exports.DenyWithReason = DenyWithReason;
|
|
56
|
+
var WriteOperation;
|
|
57
|
+
(function (WriteOperation) {
|
|
58
|
+
WriteOperation["Insert"] = "insert";
|
|
59
|
+
WriteOperation["Edit"] = "edit";
|
|
60
|
+
WriteOperation["Delete"] = "delete";
|
|
61
|
+
})(WriteOperation = exports.WriteOperation || (exports.WriteOperation = {}));
|