@snowtop/ent 0.1.0-alpha6 → 0.1.0-alpha60

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.
Files changed (111) hide show
  1. package/action/action.d.ts +28 -24
  2. package/action/executor.d.ts +4 -4
  3. package/action/executor.js +2 -2
  4. package/action/experimental_action.d.ts +29 -22
  5. package/action/experimental_action.js +29 -6
  6. package/action/orchestrator.d.ts +44 -16
  7. package/action/orchestrator.js +287 -73
  8. package/action/privacy.d.ts +2 -2
  9. package/core/base.d.ts +26 -22
  10. package/core/base.js +16 -0
  11. package/core/clause.d.ts +53 -3
  12. package/core/clause.js +347 -5
  13. package/core/config.d.ts +26 -0
  14. package/core/config.js +17 -0
  15. package/core/context.d.ts +2 -2
  16. package/core/context.js +2 -2
  17. package/core/convert.d.ts +1 -1
  18. package/core/db.d.ts +3 -4
  19. package/core/db.js +2 -0
  20. package/core/ent.d.ts +35 -24
  21. package/core/ent.js +223 -60
  22. package/core/loaders/assoc_count_loader.d.ts +2 -2
  23. package/core/loaders/assoc_count_loader.js +6 -1
  24. package/core/loaders/assoc_edge_loader.d.ts +3 -3
  25. package/core/loaders/assoc_edge_loader.js +5 -4
  26. package/core/loaders/index_loader.js +1 -0
  27. package/core/loaders/loader.js +5 -5
  28. package/core/loaders/object_loader.d.ts +10 -5
  29. package/core/loaders/object_loader.js +58 -4
  30. package/core/loaders/query_loader.d.ts +2 -2
  31. package/core/loaders/raw_count_loader.d.ts +2 -2
  32. package/core/logger.d.ts +1 -1
  33. package/core/logger.js +1 -0
  34. package/core/privacy.d.ts +25 -25
  35. package/core/privacy.js +3 -0
  36. package/core/query/assoc_query.d.ts +6 -6
  37. package/core/query/custom_query.d.ts +5 -5
  38. package/core/query/query.d.ts +1 -1
  39. package/core/query/shared_assoc_test.d.ts +1 -1
  40. package/core/query/shared_assoc_test.js +17 -5
  41. package/core/query/shared_test.d.ts +3 -0
  42. package/core/query/shared_test.js +95 -17
  43. package/core/viewer.d.ts +4 -3
  44. package/core/viewer.js +4 -0
  45. package/graphql/builtins/connection.js +3 -3
  46. package/graphql/builtins/edge.js +2 -2
  47. package/graphql/builtins/node.js +1 -1
  48. package/graphql/graphql.d.ts +3 -2
  49. package/graphql/graphql.js +30 -23
  50. package/graphql/node_resolver.d.ts +0 -1
  51. package/graphql/query/connection_type.js +6 -6
  52. package/graphql/query/edge_connection.d.ts +9 -9
  53. package/graphql/query/page_info.d.ts +1 -1
  54. package/graphql/query/page_info.js +4 -4
  55. package/graphql/query/shared_assoc_test.js +2 -2
  56. package/graphql/scalars/time.d.ts +1 -1
  57. package/index.d.ts +16 -1
  58. package/index.js +19 -5
  59. package/package.json +3 -3
  60. package/parse_schema/parse.d.ts +24 -5
  61. package/parse_schema/parse.js +90 -8
  62. package/schema/base_schema.d.ts +36 -1
  63. package/schema/base_schema.js +51 -2
  64. package/schema/field.d.ts +34 -6
  65. package/schema/field.js +68 -3
  66. package/schema/index.d.ts +2 -2
  67. package/schema/index.js +8 -1
  68. package/schema/schema.d.ts +87 -2
  69. package/schema/schema.js +127 -5
  70. package/scripts/custom_graphql.js +127 -16
  71. package/scripts/{transform_schema.d.ts → migrate_v0.1.d.ts} +0 -0
  72. package/scripts/migrate_v0.1.js +36 -0
  73. package/scripts/read_schema.js +25 -2
  74. package/testutils/builder.d.ts +36 -22
  75. package/testutils/builder.js +110 -13
  76. package/testutils/context/test_context.d.ts +2 -2
  77. package/testutils/context/test_context.js +7 -1
  78. package/testutils/db/{test_db.d.ts → temp_db.d.ts} +17 -4
  79. package/testutils/db/{test_db.js → temp_db.js} +75 -19
  80. package/testutils/ent-graphql-tests/index.d.ts +2 -0
  81. package/testutils/ent-graphql-tests/index.js +26 -17
  82. package/testutils/fake_data/fake_contact.d.ts +5 -9
  83. package/testutils/fake_data/fake_contact.js +17 -21
  84. package/testutils/fake_data/fake_event.d.ts +5 -9
  85. package/testutils/fake_data/fake_event.js +24 -28
  86. package/testutils/fake_data/fake_user.d.ts +6 -10
  87. package/testutils/fake_data/fake_user.js +25 -29
  88. package/testutils/fake_data/test_helpers.d.ts +2 -2
  89. package/testutils/fake_data/test_helpers.js +6 -6
  90. package/testutils/fake_data/user_query.d.ts +2 -2
  91. package/testutils/fake_log.d.ts +3 -3
  92. package/testutils/parse_sql.js +4 -0
  93. package/testutils/test_edge_global_schema.d.ts +15 -0
  94. package/testutils/test_edge_global_schema.js +58 -0
  95. package/testutils/write.d.ts +2 -2
  96. package/testutils/write.js +3 -3
  97. package/tsc/ast.d.ts +44 -0
  98. package/tsc/ast.js +267 -0
  99. package/tsc/compilerOptions.d.ts +6 -0
  100. package/tsc/compilerOptions.js +40 -1
  101. package/tsc/move_generated.d.ts +1 -0
  102. package/tsc/move_generated.js +160 -0
  103. package/tsc/transform.d.ts +21 -0
  104. package/tsc/transform.js +167 -0
  105. package/tsc/transform_action.d.ts +22 -0
  106. package/tsc/transform_action.js +179 -0
  107. package/tsc/transform_ent.d.ts +17 -0
  108. package/tsc/transform_ent.js +59 -0
  109. package/tsc/transform_schema.d.ts +27 -0
  110. package/tsc/transform_schema.js +379 -0
  111. package/scripts/transform_schema.js +0 -288
@@ -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.options.operation) {
166
+ switch (this.actualOperation) {
139
167
  case action_1.WriteOperation.Delete:
140
- return new ent_1.DeleteNodeOperation(this.options.builder.existingEnt.id, {
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
- ent: this.options.loaderOptions.ent,
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.options.builder.existingEnt);
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.options.operation === action_1.WriteOperation.Insert) {
254
+ if (this.actualOperation === action_1.WriteOperation.Insert) {
223
255
  return new EntCannotCreateEntError(privacyPolicy, action);
224
256
  }
225
- else if (this.options.operation === action_1.WriteOperation.Edit) {
226
- return new EntCannotEditEntError(privacyPolicy, action, this.options.builder.existingEnt);
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.options.builder.existingEnt);
260
+ return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
229
261
  }
230
- getEntForPrivacyPolicy(editedData) {
231
- if (this.options.operation !== action_1.WriteOperation.Insert) {
232
- return this.options.builder.existingEnt;
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.options.operation) {
330
+ switch (this.actualOperation) {
240
331
  case action_1.WriteOperation.Delete:
241
332
  case action_1.WriteOperation.Edit:
242
- if (!this.options.builder.existingEnt) {
243
- throw new Error("existing ent required with operation");
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
- await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicy(editedData), this.throwError.bind(this));
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
- let triggers = action?.triggers;
263
- if (triggers) {
264
- await this.triggers(action, builder, triggers);
265
- }
266
- let validators = action?.validators || [];
267
- await Promise.all([
268
- this.formatAndValidateFields(schemaFields),
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
- await Promise.all(triggers.map(async (trigger) => {
274
- let ret = await trigger.changeset(builder, action.getInput());
275
- if (Array.isArray(ret)) {
276
- ret = await Promise.all(ret);
277
- }
278
- if (Array.isArray(ret)) {
279
- for (const v of ret) {
280
- if (typeof v === "object") {
281
- this.changesets.push(v);
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 if (ret) {
286
- this.changesets.push(ret);
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
- getFieldsWithDefaultValues(builder, schemaFields, action) {
304
- const editedFields = this.options.editedFields();
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 = field.storageKey || (0, snake_case_1.snakeCase)(fieldName);
503
+ let dbKey = this.getStorageKey(fieldName);
313
504
  if (value === undefined) {
314
- if (this.options.operation === action_1.WriteOperation.Insert) {
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.options.operation === action_1.WriteOperation.Edit) {
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
- // TODO related to #510. we need this logic to be consistent so do this all in TypeScript or get it from go somehow
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
- throw new Error(`field ${fieldName} set to null for non-nullable field`);
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.options.operation === action_1.WriteOperation.Insert) {
378
- throw new Error(`required field ${fieldName} not set`);
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 Promise.resolve(field.valid(value));
573
+ const valid = await field.valid(value);
384
574
  if (!valid) {
385
- throw new Error(`invalid field ${fieldName} with value ${value}`);
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 Promise.resolve(field.valid(value));
586
+ const valid = await field.valid(value);
397
587
  if (!valid) {
398
- throw new Error(`invalid field ${fieldName} with value ${value}`);
588
+ return new Error(`invalid field ${fieldName} with value ${value}`);
399
589
  }
400
590
  }
401
591
  if (field.format) {
402
- // TODO this could be async e.g. password. handle this better
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 op = this.options.operation;
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 = field.storageKey || (0, snake_case_1.snakeCase)(fieldName);
424
- value = await this.transformFieldValue(fieldName, field, dbKey, value);
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 = field.storageKey || (0, snake_case_1.snakeCase)(fieldName);
631
+ let dbKey = this.getStorageKey(fieldName);
437
632
  // no value, let's just default
438
633
  if (data[dbKey] === undefined) {
439
- const value = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
440
- data[dbKey] = value;
441
- logValues[dbKey] = field.logValue(value);
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
- try {
450
- await this.validate();
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.options.operation == action_1.WriteOperation.Insert) {
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 {
@@ -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
@@ -30,27 +30,27 @@ interface queryOptions {
30
30
  clause: clause.Clause;
31
31
  orderby?: string;
32
32
  }
33
- export interface Context {
34
- getViewer(): Viewer;
33
+ export interface Context<TViewer extends Viewer = Viewer> {
34
+ getViewer(): TViewer;
35
35
  cache?: cache;
36
36
  }
37
- export interface Viewer {
38
- viewerID: ID | null;
39
- viewer: () => Promise<Ent | null>;
37
+ export interface Viewer<TEnt extends any = Ent<any> | null, TID extends any = ID | null> {
38
+ viewerID: TID;
39
+ viewer: () => Promise<TEnt>;
40
40
  instanceKey: () => string;
41
- context?: Context;
41
+ context?: Context<any>;
42
42
  }
43
- export interface Ent {
43
+ export interface Ent<TViewer extends Viewer = Viewer> {
44
44
  id: ID;
45
- viewer: Viewer;
46
- privacyPolicy: PrivacyPolicy<this>;
45
+ viewer: TViewer;
46
+ getPrivacyPolicy(): PrivacyPolicy<this, TViewer>;
47
47
  nodeType: string;
48
48
  }
49
49
  export declare type Data = {
50
50
  [key: string]: any;
51
51
  };
52
- export interface EntConstructor<T extends Ent> {
53
- new (viewer: Viewer, data: Data): T;
52
+ export interface EntConstructor<TEnt extends Ent, TViewer extends Viewer = Viewer> {
53
+ new (viewer: TViewer, data: Data): TEnt;
54
54
  }
55
55
  export declare type ID = string | number;
56
56
  export interface DataOptions {
@@ -62,6 +62,7 @@ export interface SelectBaseDataOptions extends DataOptions {
62
62
  }
63
63
  export interface SelectDataOptions extends SelectBaseDataOptions {
64
64
  key: string;
65
+ clause?: clause.Clause | (() => clause.Clause | undefined);
65
66
  }
66
67
  export interface QueryableDataOptions extends SelectBaseDataOptions, QueryDataOptions {
67
68
  }
@@ -81,16 +82,18 @@ export interface CreateRowOptions extends DataOptions {
81
82
  fieldsToLog?: Data;
82
83
  }
83
84
  export interface EditRowOptions extends CreateRowOptions {
84
- key: string;
85
+ whereClause: clause.Clause;
85
86
  }
86
- interface LoadableEntOptions<T extends Ent> {
87
+ interface LoadableEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> {
87
88
  loaderFactory: LoaderFactory<any, Data | null>;
88
- ent: EntConstructor<T>;
89
+ ent: EntConstructor<TEnt, TViewer>;
89
90
  }
90
- export interface LoadEntOptions<T extends Ent> extends LoadableEntOptions<T>, SelectBaseDataOptions {
91
+ export interface LoadEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends LoadableEntOptions<TEnt, TViewer>, SelectBaseDataOptions {
92
+ fieldPrivacy?: Map<string, PrivacyPolicy>;
91
93
  }
92
- export interface LoadCustomEntOptions<T extends Ent> extends SelectBaseDataOptions {
93
- ent: EntConstructor<T>;
94
+ export interface LoadCustomEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends SelectBaseDataOptions {
95
+ ent: EntConstructor<TEnt, TViewer>;
96
+ fieldPrivacy?: Map<string, PrivacyPolicy>;
94
97
  }
95
98
  export interface LoaderInfo {
96
99
  tableName: string;
@@ -108,6 +111,7 @@ declare enum privacyResult {
108
111
  export interface PrivacyResult {
109
112
  result: privacyResult;
110
113
  error?: PrivacyError;
114
+ getError?(policy: PrivacyPolicy, rule: PrivacyPolicyRule, ent?: Ent): PrivacyError;
111
115
  }
112
116
  export interface PrivacyError extends Error {
113
117
  privacyPolicy: PrivacyPolicy<Ent>;
@@ -116,11 +120,11 @@ export interface PrivacyError extends Error {
116
120
  export declare function Allow(): PrivacyResult;
117
121
  export declare function Skip(): PrivacyResult;
118
122
  export declare function Deny(): PrivacyResult;
119
- export declare function DenyWithReason(e: PrivacyError): PrivacyResult;
120
- export interface PrivacyPolicyRule<TEnt extends Ent = Ent> {
121
- apply(v: Viewer, ent?: TEnt): Promise<PrivacyResult>;
123
+ export declare function DenyWithReason(e: PrivacyError | string): PrivacyResult;
124
+ export interface PrivacyPolicyRule<TEnt extends Ent = Ent, TViewer = Viewer> {
125
+ apply(v: TViewer, ent?: TEnt): Promise<PrivacyResult>;
122
126
  }
123
- export interface PrivacyPolicy<TEnt extends Ent = Ent> {
124
- rules: PrivacyPolicyRule<TEnt>[];
127
+ export interface PrivacyPolicy<TEnt extends Ent = Ent, TViewer = Viewer> {
128
+ rules: PrivacyPolicyRule<TEnt, TViewer>[];
125
129
  }
126
130
  export {};