@snowtop/ent 0.1.0-alpha9 → 0.1.0-alpha90

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