@snowtop/ent 0.1.0-alpha10 → 0.1.0-alpha101

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 (158) hide show
  1. package/action/action.d.ts +37 -31
  2. package/action/action.js +22 -7
  3. package/action/executor.d.ts +3 -3
  4. package/action/executor.js +8 -3
  5. package/action/experimental_action.d.ts +32 -22
  6. package/action/experimental_action.js +35 -9
  7. package/action/index.d.ts +2 -0
  8. package/action/index.js +7 -1
  9. package/action/orchestrator.d.ts +40 -16
  10. package/action/orchestrator.js +230 -62
  11. package/action/privacy.d.ts +2 -2
  12. package/action/relative_value.d.ts +47 -0
  13. package/action/relative_value.js +125 -0
  14. package/action/transaction.d.ts +10 -0
  15. package/action/transaction.js +23 -0
  16. package/auth/auth.d.ts +1 -1
  17. package/core/base.d.ts +49 -26
  18. package/core/base.js +7 -1
  19. package/core/clause.d.ts +88 -7
  20. package/core/clause.js +355 -63
  21. package/core/config.d.ts +12 -1
  22. package/core/config.js +7 -1
  23. package/core/context.d.ts +5 -3
  24. package/core/context.js +20 -2
  25. package/core/convert.d.ts +1 -1
  26. package/core/date.js +1 -5
  27. package/core/db.d.ts +11 -8
  28. package/core/db.js +20 -8
  29. package/core/ent.d.ts +81 -25
  30. package/core/ent.js +636 -193
  31. package/core/loaders/assoc_count_loader.d.ts +3 -2
  32. package/core/loaders/assoc_count_loader.js +10 -2
  33. package/core/loaders/assoc_edge_loader.d.ts +2 -2
  34. package/core/loaders/assoc_edge_loader.js +8 -11
  35. package/core/loaders/index.d.ts +1 -1
  36. package/core/loaders/index.js +1 -3
  37. package/core/loaders/index_loader.d.ts +2 -2
  38. package/core/loaders/loader.js +5 -5
  39. package/core/loaders/object_loader.d.ts +6 -5
  40. package/core/loaders/object_loader.js +67 -59
  41. package/core/loaders/query_loader.d.ts +6 -12
  42. package/core/loaders/query_loader.js +52 -11
  43. package/core/loaders/raw_count_loader.d.ts +2 -2
  44. package/core/loaders/raw_count_loader.js +5 -1
  45. package/core/logger.d.ts +1 -1
  46. package/core/logger.js +1 -0
  47. package/core/privacy.d.ts +25 -24
  48. package/core/privacy.js +21 -25
  49. package/core/query/assoc_query.d.ts +7 -6
  50. package/core/query/assoc_query.js +9 -1
  51. package/core/query/custom_clause_query.d.ts +26 -0
  52. package/core/query/custom_clause_query.js +78 -0
  53. package/core/query/custom_query.d.ts +20 -5
  54. package/core/query/custom_query.js +87 -12
  55. package/core/query/index.d.ts +1 -0
  56. package/core/query/index.js +3 -1
  57. package/core/query/query.d.ts +8 -4
  58. package/core/query/query.js +101 -53
  59. package/core/query/shared_assoc_test.d.ts +2 -1
  60. package/core/query/shared_assoc_test.js +35 -45
  61. package/core/query/shared_test.d.ts +8 -1
  62. package/core/query/shared_test.js +469 -236
  63. package/core/viewer.d.ts +3 -3
  64. package/core/viewer.js +1 -1
  65. package/graphql/graphql.d.ts +14 -7
  66. package/graphql/graphql.js +23 -7
  67. package/graphql/index.d.ts +0 -1
  68. package/graphql/index.js +1 -4
  69. package/graphql/query/connection_type.d.ts +9 -9
  70. package/graphql/query/edge_connection.d.ts +9 -9
  71. package/graphql/query/page_info.d.ts +1 -1
  72. package/graphql/query/shared_assoc_test.js +1 -1
  73. package/graphql/query/shared_edge_connection.js +1 -19
  74. package/imports/index.d.ts +6 -1
  75. package/imports/index.js +19 -4
  76. package/index.d.ts +12 -5
  77. package/index.js +20 -7
  78. package/package.json +17 -16
  79. package/parse_schema/parse.d.ts +29 -9
  80. package/parse_schema/parse.js +118 -11
  81. package/schema/base_schema.d.ts +5 -3
  82. package/schema/base_schema.js +5 -0
  83. package/schema/field.d.ts +74 -20
  84. package/schema/field.js +174 -69
  85. package/schema/index.d.ts +2 -2
  86. package/schema/index.js +5 -1
  87. package/schema/json_field.d.ts +13 -1
  88. package/schema/json_field.js +28 -1
  89. package/schema/schema.d.ts +81 -18
  90. package/schema/schema.js +24 -17
  91. package/schema/struct_field.d.ts +11 -1
  92. package/schema/struct_field.js +57 -21
  93. package/scripts/custom_compiler.js +10 -6
  94. package/scripts/custom_graphql.js +117 -30
  95. package/scripts/{transform_code.d.ts → migrate_v0.1.d.ts} +0 -0
  96. package/scripts/migrate_v0.1.js +36 -0
  97. package/scripts/{transform_schema.d.ts → move_types.d.ts} +0 -0
  98. package/scripts/move_types.js +117 -0
  99. package/scripts/read_schema.js +20 -5
  100. package/testutils/action/complex_schemas.d.ts +69 -0
  101. package/testutils/action/complex_schemas.js +398 -0
  102. package/testutils/builder.d.ts +46 -47
  103. package/testutils/builder.js +108 -65
  104. package/testutils/db/fixture.d.ts +10 -0
  105. package/testutils/db/fixture.js +26 -0
  106. package/testutils/db/{test_db.d.ts → temp_db.d.ts} +24 -8
  107. package/testutils/db/{test_db.js → temp_db.js} +179 -44
  108. package/testutils/db/value.d.ts +7 -0
  109. package/testutils/db/value.js +251 -0
  110. package/testutils/db_mock.d.ts +16 -4
  111. package/testutils/db_mock.js +51 -6
  112. package/testutils/db_time_zone.d.ts +4 -0
  113. package/testutils/db_time_zone.js +41 -0
  114. package/testutils/ent-graphql-tests/index.d.ts +7 -1
  115. package/testutils/ent-graphql-tests/index.js +27 -8
  116. package/testutils/fake_data/const.d.ts +2 -1
  117. package/testutils/fake_data/const.js +3 -0
  118. package/testutils/fake_data/fake_contact.d.ts +7 -3
  119. package/testutils/fake_data/fake_contact.js +15 -8
  120. package/testutils/fake_data/fake_event.d.ts +5 -2
  121. package/testutils/fake_data/fake_event.js +9 -7
  122. package/testutils/fake_data/fake_tag.d.ts +36 -0
  123. package/testutils/fake_data/fake_tag.js +89 -0
  124. package/testutils/fake_data/fake_user.d.ts +7 -4
  125. package/testutils/fake_data/fake_user.js +18 -16
  126. package/testutils/fake_data/index.js +5 -1
  127. package/testutils/fake_data/internal.d.ts +2 -0
  128. package/testutils/fake_data/internal.js +7 -1
  129. package/testutils/fake_data/tag_query.d.ts +13 -0
  130. package/testutils/fake_data/tag_query.js +43 -0
  131. package/testutils/fake_data/test_helpers.d.ts +11 -4
  132. package/testutils/fake_data/test_helpers.js +28 -12
  133. package/testutils/fake_data/user_query.d.ts +13 -6
  134. package/testutils/fake_data/user_query.js +54 -22
  135. package/testutils/fake_log.d.ts +3 -3
  136. package/testutils/parse_sql.d.ts +6 -0
  137. package/testutils/parse_sql.js +16 -2
  138. package/testutils/test_edge_global_schema.d.ts +15 -0
  139. package/testutils/test_edge_global_schema.js +62 -0
  140. package/testutils/write.d.ts +2 -2
  141. package/testutils/write.js +33 -7
  142. package/tsc/ast.d.ts +26 -2
  143. package/tsc/ast.js +163 -17
  144. package/tsc/compilerOptions.d.ts +2 -1
  145. package/tsc/compilerOptions.js +11 -2
  146. package/tsc/move_generated.d.ts +1 -0
  147. package/tsc/move_generated.js +164 -0
  148. package/tsc/transform.d.ts +22 -0
  149. package/tsc/transform.js +181 -0
  150. package/tsc/transform_action.d.ts +22 -0
  151. package/tsc/transform_action.js +183 -0
  152. package/tsc/transform_ent.d.ts +17 -0
  153. package/tsc/transform_ent.js +59 -0
  154. package/tsc/transform_schema.d.ts +27 -0
  155. package/{scripts → tsc}/transform_schema.js +145 -119
  156. package/graphql/enums.d.ts +0 -3
  157. package/graphql/enums.js +0 -25
  158. package/scripts/transform_code.js +0 -114
@@ -1,13 +1,40 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
2
28
  Object.defineProperty(exports, "__esModule", { value: true });
3
29
  exports.EntChangeset = exports.Orchestrator = exports.edgeDirection = void 0;
4
30
  const ent_1 = require("../core/ent");
5
31
  const schema_1 = require("../schema/schema");
6
32
  const action_1 = require("../action");
7
- const camel_case_1 = require("camel-case");
8
33
  const privacy_1 = require("../core/privacy");
9
34
  const executor_1 = require("./executor");
10
35
  const logger_1 = require("../core/logger");
36
+ const memoizee_1 = __importDefault(require("memoizee"));
37
+ const clause = __importStar(require("../core/clause"));
11
38
  var edgeDirection;
12
39
  (function (edgeDirection) {
13
40
  edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
@@ -63,6 +90,11 @@ class Orchestrator {
63
90
  this.viewer = options.viewer;
64
91
  this.actualOperation = this.options.operation;
65
92
  this.existingEnt = this.options.builder.existingEnt;
93
+ this.memoizedGetFields = (0, memoizee_1.default)(this.getFieldsInfo.bind(this));
94
+ }
95
+ // don't type this because we don't care
96
+ __getOptions() {
97
+ return this.options;
66
98
  }
67
99
  addEdge(edge, op) {
68
100
  this.edgeSet.add(edge.edgeType);
@@ -148,6 +180,10 @@ class Orchestrator {
148
180
  if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
149
181
  throw new Error(`existing ent required with operation ${this.actualOperation}`);
150
182
  }
183
+ if (this.options.expressions &&
184
+ this.actualOperation !== action_1.WriteOperation.Edit) {
185
+ throw new Error(`expressions are only supported in edit operations for now`);
186
+ }
151
187
  const opts = {
152
188
  fields: this.validatedFields,
153
189
  tableName: this.options.tableName,
@@ -155,6 +191,8 @@ class Orchestrator {
155
191
  key: this.options.key,
156
192
  loadEntOptions: this.options.loaderOptions,
157
193
  placeholderID: this.options.builder.placeholderID,
194
+ whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
195
+ expressions: this.options.expressions,
158
196
  };
159
197
  if (this.logValues) {
160
198
  opts.fieldsToLog = this.logValues;
@@ -208,7 +246,7 @@ class Orchestrator {
208
246
  ops.push(edgeOp);
209
247
  const edgeData = edgeDatas.get(edgeType);
210
248
  if (!edgeData) {
211
- throw new Error(`could not load edge data for ${edgeType}`);
249
+ throw new Error(`could not load edge data for '${edgeType}'`);
212
250
  }
213
251
  if (edgeData.symmetricEdge) {
214
252
  ops.push(edgeOp.symmetricEdge());
@@ -269,9 +307,28 @@ class Orchestrator {
269
307
  if (this.actualOperation !== action_1.WriteOperation.Insert) {
270
308
  return this.existingEnt;
271
309
  }
272
- const { editedData } = await this.getFieldsInfo();
310
+ const { editedData } = await this.memoizedGetFields();
273
311
  return this.getEntForPrivacyPolicyImpl(editedData);
274
312
  }
313
+ // this gets the fields that were explicitly set plus any default or transformed values
314
+ // mainly exists to get default fields e.g. default id to be used in triggers
315
+ // NOTE: this API may change in the future
316
+ // doesn't work to get ids for autoincrement keys
317
+ async getEditedData() {
318
+ const { editedData } = await this.memoizedGetFields();
319
+ return editedData;
320
+ }
321
+ /**
322
+ * @returns validated and formatted fields that would be written to the db
323
+ * throws an error if called before valid() or validX() has been called
324
+ */
325
+ getValidatedFields() {
326
+ if (this.validatedFields === null) {
327
+ throw new Error(`trying to call getValidatedFields before validating fields`);
328
+ }
329
+ return this.validatedFields;
330
+ }
331
+ // Note: this is memoized. call memoizedGetFields instead
275
332
  async getFieldsInfo() {
276
333
  const action = this.options.action;
277
334
  const builder = this.options.builder;
@@ -290,7 +347,7 @@ class Orchestrator {
290
347
  throw new Error(`existing ent required with operation ${this.actualOperation}`);
291
348
  }
292
349
  }
293
- const { schemaFields, editedData } = await this.getFieldsInfo();
350
+ const { schemaFields, editedData } = await this.memoizedGetFields();
294
351
  const action = this.options.action;
295
352
  const builder = this.options.builder;
296
353
  // this runs in following phases:
@@ -299,55 +356,104 @@ class Orchestrator {
299
356
  // * triggers
300
357
  // * validators
301
358
  let privacyPolicy = action?.getPrivacyPolicy();
359
+ let privacyError = null;
302
360
  if (privacyPolicy) {
303
- await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicyImpl(editedData), this.throwError.bind(this));
361
+ try {
362
+ await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicyImpl(editedData), this.throwError.bind(this));
363
+ }
364
+ catch (err) {
365
+ privacyError = err;
366
+ }
367
+ }
368
+ // privacyError should return first since it's less confusing
369
+ if (privacyError !== null) {
370
+ return [privacyError];
304
371
  }
305
372
  // have to run triggers which update fields first before field and other validators
306
373
  // so running this first to build things up
307
- let triggers = action?.triggers;
308
- if (triggers) {
309
- await this.triggers(action, builder, triggers);
374
+ if (action?.getTriggers) {
375
+ await this.triggers(action, builder, action.getTriggers());
376
+ }
377
+ let validators = [];
378
+ if (action?.getValidators) {
379
+ validators = action.getValidators();
310
380
  }
311
- let validators = action?.validators || [];
312
381
  // not ideal we're calling this twice. fix...
313
382
  // needed for now. may need to rewrite some of this?
314
383
  const editedFields2 = await this.options.editedFields();
315
- await Promise.all([
384
+ const [errors, errs2] = await Promise.all([
316
385
  this.formatAndValidateFields(schemaFields, editedFields2),
317
386
  this.validators(validators, action, builder),
318
387
  ]);
388
+ errors.push(...errs2);
389
+ return errors;
319
390
  }
320
391
  async triggers(action, builder, triggers) {
321
- await Promise.all(triggers.map(async (trigger) => {
322
- let ret = await trigger.changeset(builder, action.getInput());
323
- if (Array.isArray(ret)) {
324
- ret = await Promise.all(ret);
325
- }
326
- if (Array.isArray(ret)) {
327
- for (const v of ret) {
328
- if (typeof v === "object") {
329
- this.changesets.push(v);
330
- }
392
+ let groups = [];
393
+ let lastArray = 0;
394
+ let prevWasArray = false;
395
+ for (let i = 0; i < triggers.length; i++) {
396
+ let t = triggers[i];
397
+ if (Array.isArray(t)) {
398
+ if (!prevWasArray) {
399
+ // @ts-ignore
400
+ groups.push(triggers.slice(lastArray, i));
331
401
  }
402
+ groups.push(t);
403
+ prevWasArray = true;
404
+ lastArray++;
332
405
  }
333
- else if (ret) {
334
- this.changesets.push(ret);
406
+ else {
407
+ if (i === triggers.length - 1) {
408
+ // @ts-ignore
409
+ groups.push(triggers.slice(lastArray, i + 1));
410
+ }
411
+ prevWasArray = false;
335
412
  }
336
- }));
413
+ }
414
+ for (const triggers of groups) {
415
+ await Promise.all(triggers.map(async (trigger) => {
416
+ let ret = await trigger.changeset(builder, action.getInput());
417
+ if (Array.isArray(ret)) {
418
+ ret = await Promise.all(ret);
419
+ }
420
+ if (Array.isArray(ret)) {
421
+ for (const v of ret) {
422
+ if (typeof v === "object") {
423
+ this.changesets.push(v);
424
+ }
425
+ }
426
+ }
427
+ else if (ret) {
428
+ this.changesets.push(ret);
429
+ }
430
+ }));
431
+ }
337
432
  }
338
433
  async validators(validators, action, builder) {
339
- let promises = [];
340
- validators.forEach((validator) => {
341
- let res = validator.validate(builder, action.getInput());
342
- if (res) {
343
- promises.push(res);
434
+ const errors = [];
435
+ await Promise.all(validators.map(async (v) => {
436
+ try {
437
+ const r = await v.validate(builder, action.getInput());
438
+ if (r instanceof Error) {
439
+ errors.push(r);
440
+ }
344
441
  }
345
- });
346
- await Promise.all(promises);
442
+ catch (err) {
443
+ errors.push(err);
444
+ }
445
+ }));
446
+ return errors;
347
447
  }
348
448
  isBuilder(val) {
349
449
  return val.placeholderID !== undefined;
350
450
  }
451
+ getInputKey(k) {
452
+ return this.options.fieldInfo[k].inputKey;
453
+ }
454
+ getStorageKey(k) {
455
+ return this.options.fieldInfo[k].dbCol;
456
+ }
351
457
  async getFieldsWithDefaultValues(builder, schemaFields, editedFields, action) {
352
458
  let data = {};
353
459
  let defaultData = {};
@@ -357,24 +463,30 @@ class Orchestrator {
357
463
  // if action transformations. always do it
358
464
  // if disable transformations set, don't do schema transform and just do the right thing
359
465
  // else apply schema tranformation if it exists
360
- let transformed;
466
+ let transformed = null;
467
+ const sqlOp = this.getSQLStatementOperation();
361
468
  if (action?.transformWrite) {
362
469
  transformed = await action.transformWrite({
363
- viewer: builder.viewer,
364
- op: this.getSQLStatementOperation(),
470
+ builder,
471
+ input,
472
+ op: sqlOp,
365
473
  data: editedFields,
366
- existingEnt: this.existingEnt,
367
474
  });
368
475
  }
369
476
  else if (!this.disableTransformations) {
370
477
  transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
371
- viewer: builder.viewer,
372
- op: this.getSQLStatementOperation(),
478
+ builder,
479
+ input,
480
+ op: sqlOp,
373
481
  data: editedFields,
374
- existingEnt: this.existingEnt,
375
482
  });
376
483
  }
377
484
  if (transformed) {
485
+ if (sqlOp === schema_1.SQLStatementOperation.Insert && sqlOp !== transformed.op) {
486
+ if (!transformed.existingEnt) {
487
+ throw new Error(`cannot transform an insert operation without providing an existing ent`);
488
+ }
489
+ }
378
490
  if (transformed.data) {
379
491
  updateInput = true;
380
492
  for (const k in transformed.data) {
@@ -386,17 +498,23 @@ class Orchestrator {
386
498
  if (field.format) {
387
499
  val = field.format(transformed.data[k]);
388
500
  }
389
- let dbKey = (0, schema_1.getStorageKey)(field, k);
390
- data[dbKey] = val;
391
- this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(k)] = val;
501
+ data[this.getStorageKey(k)] = val;
502
+ this.defaultFieldsByTSName[this.getInputKey(k)] = val;
392
503
  // hmm do we need this?
393
504
  // TODO how to do this for local tests?
394
505
  // this.defaultFieldsByFieldName[k] = val;
395
506
  }
396
507
  }
508
+ if (transformed.changeset) {
509
+ const ct = await transformed.changeset();
510
+ this.changesets.push(ct);
511
+ }
397
512
  this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
398
513
  if (transformed.existingEnt) {
514
+ // @ts-ignore
399
515
  this.existingEnt = transformed.existingEnt;
516
+ // modify existing ent in builder. it's readonly in generated ents but doesn't apply here
517
+ builder.existingEnt = transformed.existingEnt;
400
518
  }
401
519
  }
402
520
  // transforming before doing default fields so that we don't create a new id
@@ -404,7 +522,7 @@ class Orchestrator {
404
522
  for (const [fieldName, field] of schemaFields) {
405
523
  let value = editedFields.get(fieldName);
406
524
  let defaultValue = undefined;
407
- let dbKey = (0, schema_1.getStorageKey)(field, fieldName);
525
+ let dbKey = this.getStorageKey(fieldName);
408
526
  if (value === undefined) {
409
527
  if (this.actualOperation === action_1.WriteOperation.Insert) {
410
528
  if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
@@ -432,8 +550,7 @@ class Orchestrator {
432
550
  updateInput = true;
433
551
  defaultData[dbKey] = defaultValue;
434
552
  this.defaultFieldsByFieldName[fieldName] = defaultValue;
435
- // TODO related to #510. we need this logic to be consistent so do this all in TypeScript or get it from go somehow
436
- this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
553
+ this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
437
554
  }
438
555
  }
439
556
  // if there's data changing, add data
@@ -459,7 +576,7 @@ class Orchestrator {
459
576
  // now format and validate...
460
577
  if (value === null) {
461
578
  if (!field.nullable) {
462
- throw new Error(`field ${fieldName} set to null for non-nullable field`);
579
+ return new Error(`field ${fieldName} set to null for non-nullable field`);
463
580
  }
464
581
  }
465
582
  else if (value === undefined) {
@@ -470,14 +587,14 @@ class Orchestrator {
470
587
  // server default allowed
471
588
  field.serverDefault === undefined &&
472
589
  this.actualOperation === action_1.WriteOperation.Insert) {
473
- throw new Error(`required field ${fieldName} not set`);
590
+ return new Error(`required field ${fieldName} not set`);
474
591
  }
475
592
  }
476
593
  else if (this.isBuilder(value)) {
477
594
  if (field.valid) {
478
595
  const valid = await field.valid(value);
479
596
  if (!valid) {
480
- throw new Error(`invalid field ${fieldName} with value ${value}`);
597
+ return new Error(`invalid field ${fieldName} with value ${value}`);
481
598
  }
482
599
  }
483
600
  // keep track of dependencies to resolve
@@ -490,66 +607,110 @@ class Orchestrator {
490
607
  // TODO this could be async. handle this better
491
608
  const valid = await field.valid(value);
492
609
  if (!valid) {
493
- throw new Error(`invalid field ${fieldName} with value ${value}`);
610
+ return new Error(`invalid field ${fieldName} with value ${value}`);
494
611
  }
495
612
  }
496
613
  if (field.format) {
497
- // TODO this could be async e.g. password. handle this better
498
- value = await Promise.resolve(field.format(value));
614
+ value = await field.format(value);
499
615
  }
500
616
  }
501
617
  return value;
502
618
  }
503
619
  async formatAndValidateFields(schemaFields, editedFields) {
620
+ const errors = [];
504
621
  const op = this.actualOperation;
505
622
  if (op === action_1.WriteOperation.Delete) {
506
- return;
623
+ return [];
507
624
  }
508
625
  // build up data to be saved...
509
626
  let data = {};
510
627
  let logValues = {};
628
+ let needsFullDataChecks = [];
511
629
  for (const [fieldName, field] of schemaFields) {
512
630
  let value = editedFields.get(fieldName);
631
+ if (field.validateWithFullData) {
632
+ needsFullDataChecks.push(fieldName);
633
+ }
513
634
  if (value === undefined && op === action_1.WriteOperation.Insert) {
514
635
  // null allowed
515
636
  value = this.defaultFieldsByFieldName[fieldName];
516
637
  }
517
- let dbKey = (0, schema_1.getStorageKey)(field, fieldName);
518
- value = await this.transformFieldValue(fieldName, field, dbKey, value);
638
+ let dbKey = this.getStorageKey(fieldName);
639
+ let ret = await this.transformFieldValue(fieldName, field, dbKey, value);
640
+ if (ret instanceof Error) {
641
+ errors.push(ret);
642
+ }
643
+ else {
644
+ value = ret;
645
+ }
519
646
  if (value !== undefined) {
520
647
  data[dbKey] = value;
521
648
  logValues[dbKey] = field.logValue(value);
522
649
  }
523
650
  }
651
+ for (const fieldName of needsFullDataChecks) {
652
+ const field = schemaFields.get(fieldName);
653
+ let value = editedFields.get(fieldName);
654
+ // @ts-ignore...
655
+ // type hackery because it's hard
656
+ const v = await field.validateWithFullData(value, this.options.builder);
657
+ if (!v) {
658
+ if (value === undefined) {
659
+ errors.push(new Error(`field ${fieldName} set to undefined when it can't be nullable`));
660
+ }
661
+ else {
662
+ errors.push(new Error(`field ${fieldName} set to null when it can't be nullable`));
663
+ }
664
+ }
665
+ }
524
666
  // we ignored default values while editing.
525
667
  // if we're editing and there's data, add default values
526
668
  if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
527
669
  for (const fieldName in this.defaultFieldsByFieldName) {
528
670
  const defaultValue = this.defaultFieldsByFieldName[fieldName];
529
671
  let field = schemaFields.get(fieldName);
530
- let dbKey = (0, schema_1.getStorageKey)(field, fieldName);
672
+ let dbKey = this.getStorageKey(fieldName);
531
673
  // no value, let's just default
532
674
  if (data[dbKey] === undefined) {
533
- const value = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
534
- data[dbKey] = value;
535
- logValues[dbKey] = field.logValue(value);
675
+ const ret = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
676
+ if (ret instanceof Error) {
677
+ errors.push(ret);
678
+ }
679
+ else {
680
+ data[dbKey] = ret;
681
+ logValues[dbKey] = field.logValue(ret);
682
+ }
536
683
  }
537
684
  }
538
685
  }
539
686
  this.validatedFields = data;
540
687
  this.logValues = logValues;
688
+ return errors;
541
689
  }
542
690
  async valid() {
543
- try {
544
- await this.validate();
545
- }
546
- catch (e) {
547
- (0, logger_1.log)("error", e);
691
+ const errors = await this.validate();
692
+ if (errors.length) {
693
+ errors.map((err) => (0, logger_1.log)("error", err));
548
694
  return false;
549
695
  }
550
696
  return true;
551
697
  }
552
698
  async validX() {
699
+ const errors = await this.validate();
700
+ if (errors.length) {
701
+ // just throw the first one...
702
+ // TODO we should ideally throw all of them
703
+ throw errors[0];
704
+ }
705
+ }
706
+ /**
707
+ * @experimental API that's not guaranteed to remain in the future which returns
708
+ * a list of errors encountered
709
+ * 0 errors indicates valid
710
+ * NOTE that this currently doesn't catch errors returned by validators().
711
+ * If those throws, this still throws and doesn't return them
712
+ */
713
+ async validWithErrors() {
553
714
  return this.validate();
554
715
  }
555
716
  async build() {
@@ -557,6 +718,7 @@ class Orchestrator {
557
718
  await this.validX();
558
719
  let ops = [this.buildMainOp()];
559
720
  await this.buildEdgeOps(ops);
721
+ // TODO throw if we try and create a new changeset after previously creating one
560
722
  return new EntChangeset(this.options.viewer, this.options.builder.placeholderID, this.options.loaderOptions.ent, ops, this.dependencies, this.changesets, this.options);
561
723
  }
562
724
  async viewerForEntLoad(data) {
@@ -564,7 +726,7 @@ class Orchestrator {
564
726
  if (!action || !action.viewerForEntLoad) {
565
727
  return this.options.viewer;
566
728
  }
567
- return action.viewerForEntLoad(data);
729
+ return action.viewerForEntLoad(data, action.builder.viewer.context);
568
730
  }
569
731
  async returnedRow() {
570
732
  if (this.mainOp && this.mainOp.returnedRow) {
@@ -599,6 +761,9 @@ class Orchestrator {
599
761
  }
600
762
  }
601
763
  exports.Orchestrator = Orchestrator;
764
+ function randomNum() {
765
+ return Math.random().toString(10).substring(2);
766
+ }
602
767
  class EntChangeset {
603
768
  constructor(viewer, placeholderID, ent, operations, dependencies, changesets, options) {
604
769
  this.viewer = viewer;
@@ -609,6 +774,9 @@ class EntChangeset {
609
774
  this.changesets = changesets;
610
775
  this.options = options;
611
776
  }
777
+ static changesetFrom(builder, ops) {
778
+ return new EntChangeset(builder.viewer, `$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, builder.ent, ops);
779
+ }
612
780
  executor() {
613
781
  if (this._executor) {
614
782
  return this._executor;
@@ -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
  }
@@ -0,0 +1,47 @@
1
+ import { Clause } from "../core/clause";
2
+ export interface RelativeFieldValue<T = BigInt | number> {
3
+ delta: T;
4
+ sqlExpression: (col: string) => Clause;
5
+ eval: (curr: T) => T;
6
+ }
7
+ export interface RelativeNumberValue<T> {
8
+ add?: T;
9
+ subtract?: T;
10
+ divide?: T;
11
+ multiply?: T;
12
+ modulo?: T;
13
+ }
14
+ declare function addNumber(delta: number): RelativeFieldValue<number>;
15
+ declare function addNumber(delta: BigInt): RelativeFieldValue<BigInt>;
16
+ declare function subtractNumber(delta: number): RelativeFieldValue<number>;
17
+ declare function subtractNumber(delta: BigInt): RelativeFieldValue<BigInt>;
18
+ declare function multiplyNumber(delta: number): RelativeFieldValue<number>;
19
+ declare function multiplyNumber(delta: BigInt): RelativeFieldValue<BigInt>;
20
+ declare function divideNumber(delta: number): RelativeFieldValue<number>;
21
+ declare function divideNumber(delta: BigInt): RelativeFieldValue<BigInt>;
22
+ declare function moduloNumber(delta: number): RelativeFieldValue<number>;
23
+ declare function moduloNumber(delta: BigInt): RelativeFieldValue<BigInt>;
24
+ export declare const NumberOps: {
25
+ addNumber: typeof addNumber;
26
+ moduloNumber: typeof moduloNumber;
27
+ divideNumber: typeof divideNumber;
28
+ subtractNumber: typeof subtractNumber;
29
+ multiplyNumber: typeof multiplyNumber;
30
+ };
31
+ export declare function convertRelativeInput(rel: RelativeNumberValue<BigInt>, col: string, existing: BigInt): {
32
+ value: BigInt;
33
+ clause: Clause;
34
+ };
35
+ export declare function convertRelativeInput(rel: RelativeNumberValue<number>, col: string, existing: number): {
36
+ value: number;
37
+ clause: Clause;
38
+ };
39
+ export declare function maybeConvertRelativeInputPlusExpressions(rel: number | RelativeNumberValue<number>, col: string, existing: number, expressions: Map<string, Clause>): number;
40
+ export declare function maybeConvertRelativeInputPlusExpressions(rel: number | RelativeNumberValue<number> | undefined, col: string, existing: number, expressions: Map<string, Clause>): number | undefined;
41
+ export declare function maybeConvertRelativeInputPlusExpressions(rel: number | RelativeNumberValue<number> | null, col: string, existing: number | null, expressions: Map<string, Clause>): number | null;
42
+ export declare function maybeConvertRelativeInputPlusExpressions(rel: number | RelativeNumberValue<number> | null | undefined, col: string, existing: number | null, expressions: Map<string, Clause>): number | undefined | null;
43
+ export declare function maybeConvertRelativeInputPlusExpressions(rel: BigInt | RelativeNumberValue<BigInt>, col: string, existing: BigInt, expressions: Map<string, Clause>): BigInt;
44
+ export declare function maybeConvertRelativeInputPlusExpressions(rel: BigInt | RelativeNumberValue<BigInt> | undefined, col: string, existing: BigInt, expressions: Map<string, Clause>): BigInt | undefined;
45
+ export declare function maybeConvertRelativeInputPlusExpressions(rel: BigInt | RelativeNumberValue<BigInt> | null, col: string, existing: BigInt | null, expressions: Map<string, Clause>): BigInt | null;
46
+ export declare function maybeConvertRelativeInputPlusExpressions(rel: BigInt | RelativeNumberValue<BigInt> | null | undefined, col: string, existing: BigInt | null, expressions: Map<string, Clause>): BigInt | null | undefined;
47
+ export {};