@snowtop/ent 0.1.0-alpha9 → 0.1.0-alpha91

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