@snowtop/ent 0.1.0-alpha1 → 0.1.0-alpha100

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 (172) hide show
  1. package/action/action.d.ts +38 -30
  2. package/action/action.js +22 -7
  3. package/action/executor.d.ts +4 -4
  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 +48 -16
  10. package/action/orchestrator.js +343 -81
  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 +54 -27
  18. package/core/base.js +23 -1
  19. package/core/clause.d.ts +105 -3
  20. package/core/clause.js +563 -30
  21. package/core/config.d.ts +30 -1
  22. package/core/config.js +24 -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 +14 -11
  28. package/core/db.js +22 -8
  29. package/core/ent.d.ts +82 -28
  30. package/core/ent.js +692 -202
  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 +3 -3
  34. package/core/loaders/assoc_edge_loader.js +13 -15
  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/index_loader.js +1 -0
  39. package/core/loaders/loader.js +5 -5
  40. package/core/loaders/object_loader.d.ts +13 -7
  41. package/core/loaders/object_loader.js +95 -32
  42. package/core/loaders/query_loader.d.ts +6 -12
  43. package/core/loaders/query_loader.js +52 -11
  44. package/core/loaders/raw_count_loader.d.ts +2 -2
  45. package/core/loaders/raw_count_loader.js +5 -1
  46. package/core/logger.d.ts +1 -1
  47. package/core/logger.js +1 -0
  48. package/core/privacy.d.ts +26 -25
  49. package/core/privacy.js +23 -24
  50. package/core/query/assoc_query.d.ts +7 -6
  51. package/core/query/assoc_query.js +9 -1
  52. package/core/query/custom_clause_query.d.ts +26 -0
  53. package/core/query/custom_clause_query.js +78 -0
  54. package/core/query/custom_query.d.ts +20 -5
  55. package/core/query/custom_query.js +87 -12
  56. package/core/query/index.d.ts +1 -0
  57. package/core/query/index.js +3 -1
  58. package/core/query/query.d.ts +8 -4
  59. package/core/query/query.js +101 -53
  60. package/core/query/shared_assoc_test.d.ts +2 -1
  61. package/core/query/shared_assoc_test.js +35 -45
  62. package/core/query/shared_test.d.ts +8 -1
  63. package/core/query/shared_test.js +469 -236
  64. package/core/viewer.d.ts +4 -3
  65. package/core/viewer.js +5 -1
  66. package/graphql/builtins/connection.js +3 -3
  67. package/graphql/builtins/edge.js +2 -2
  68. package/graphql/builtins/node.js +1 -1
  69. package/graphql/graphql.d.ts +17 -9
  70. package/graphql/graphql.js +47 -30
  71. package/graphql/index.d.ts +1 -1
  72. package/graphql/index.js +3 -4
  73. package/graphql/mutations/union.d.ts +2 -0
  74. package/graphql/mutations/union.js +35 -0
  75. package/graphql/node_resolver.d.ts +0 -1
  76. package/graphql/query/connection_type.d.ts +9 -9
  77. package/graphql/query/connection_type.js +6 -6
  78. package/graphql/query/edge_connection.d.ts +9 -9
  79. package/graphql/query/page_info.d.ts +1 -1
  80. package/graphql/query/page_info.js +4 -4
  81. package/graphql/query/shared_assoc_test.js +3 -3
  82. package/graphql/query/shared_edge_connection.js +1 -19
  83. package/graphql/scalars/time.d.ts +1 -1
  84. package/imports/index.d.ts +6 -1
  85. package/imports/index.js +19 -4
  86. package/index.d.ts +23 -1
  87. package/index.js +32 -6
  88. package/package.json +18 -17
  89. package/parse_schema/parse.d.ts +45 -8
  90. package/parse_schema/parse.js +193 -15
  91. package/schema/base_schema.d.ts +38 -1
  92. package/schema/base_schema.js +53 -2
  93. package/schema/field.d.ts +75 -21
  94. package/schema/field.js +185 -72
  95. package/schema/index.d.ts +4 -2
  96. package/schema/index.js +15 -2
  97. package/schema/json_field.d.ts +13 -1
  98. package/schema/json_field.js +28 -1
  99. package/schema/schema.d.ts +125 -10
  100. package/schema/schema.js +133 -5
  101. package/schema/struct_field.d.ts +27 -0
  102. package/schema/struct_field.js +138 -0
  103. package/schema/union_field.d.ts +23 -0
  104. package/schema/union_field.js +79 -0
  105. package/scripts/custom_compiler.js +10 -6
  106. package/scripts/custom_graphql.js +224 -36
  107. package/scripts/{transform_schema.d.ts → migrate_v0.1.d.ts} +0 -0
  108. package/scripts/migrate_v0.1.js +36 -0
  109. package/scripts/move_types.d.ts +1 -0
  110. package/scripts/move_types.js +117 -0
  111. package/scripts/read_schema.js +35 -6
  112. package/testutils/action/complex_schemas.d.ts +69 -0
  113. package/testutils/action/complex_schemas.js +398 -0
  114. package/testutils/builder.d.ts +52 -49
  115. package/testutils/builder.js +143 -44
  116. package/testutils/context/test_context.d.ts +2 -2
  117. package/testutils/context/test_context.js +7 -1
  118. package/testutils/db/fixture.d.ts +10 -0
  119. package/testutils/db/fixture.js +26 -0
  120. package/testutils/db/{test_db.d.ts → temp_db.d.ts} +26 -9
  121. package/testutils/db/{test_db.js → temp_db.js} +190 -46
  122. package/testutils/db/value.d.ts +7 -0
  123. package/testutils/db/value.js +251 -0
  124. package/testutils/db_mock.d.ts +16 -4
  125. package/testutils/db_mock.js +51 -6
  126. package/testutils/db_time_zone.d.ts +4 -0
  127. package/testutils/db_time_zone.js +41 -0
  128. package/testutils/ent-graphql-tests/index.d.ts +9 -1
  129. package/testutils/ent-graphql-tests/index.js +53 -25
  130. package/testutils/fake_data/const.d.ts +2 -1
  131. package/testutils/fake_data/const.js +3 -0
  132. package/testutils/fake_data/fake_contact.d.ts +10 -10
  133. package/testutils/fake_data/fake_contact.js +23 -21
  134. package/testutils/fake_data/fake_event.d.ts +8 -9
  135. package/testutils/fake_data/fake_event.js +25 -28
  136. package/testutils/fake_data/fake_tag.d.ts +36 -0
  137. package/testutils/fake_data/fake_tag.js +89 -0
  138. package/testutils/fake_data/fake_user.d.ts +10 -11
  139. package/testutils/fake_data/fake_user.js +20 -23
  140. package/testutils/fake_data/index.js +5 -1
  141. package/testutils/fake_data/internal.d.ts +2 -0
  142. package/testutils/fake_data/internal.js +7 -1
  143. package/testutils/fake_data/tag_query.d.ts +13 -0
  144. package/testutils/fake_data/tag_query.js +43 -0
  145. package/testutils/fake_data/test_helpers.d.ts +11 -4
  146. package/testutils/fake_data/test_helpers.js +29 -13
  147. package/testutils/fake_data/user_query.d.ts +13 -6
  148. package/testutils/fake_data/user_query.js +54 -22
  149. package/testutils/fake_log.d.ts +3 -3
  150. package/testutils/parse_sql.d.ts +6 -0
  151. package/testutils/parse_sql.js +16 -2
  152. package/testutils/test_edge_global_schema.d.ts +15 -0
  153. package/testutils/test_edge_global_schema.js +62 -0
  154. package/testutils/write.d.ts +2 -2
  155. package/testutils/write.js +33 -7
  156. package/tsc/ast.d.ts +44 -0
  157. package/tsc/ast.js +277 -0
  158. package/tsc/compilerOptions.d.ts +6 -0
  159. package/tsc/compilerOptions.js +45 -2
  160. package/tsc/move_generated.d.ts +1 -0
  161. package/tsc/move_generated.js +164 -0
  162. package/tsc/transform.d.ts +22 -0
  163. package/tsc/transform.js +181 -0
  164. package/tsc/transform_action.d.ts +22 -0
  165. package/tsc/transform_action.js +183 -0
  166. package/tsc/transform_ent.d.ts +17 -0
  167. package/tsc/transform_ent.js +59 -0
  168. package/tsc/transform_schema.d.ts +27 -0
  169. package/tsc/transform_schema.js +383 -0
  170. package/graphql/enums.d.ts +0 -3
  171. package/graphql/enums.js +0 -25
  172. package/scripts/transform_schema.js +0 -288
@@ -1,14 +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 snake_case_1 = require("snake-case");
8
- const camel_case_1 = require("camel-case");
9
33
  const privacy_1 = require("../core/privacy");
10
34
  const executor_1 = require("./executor");
11
35
  const logger_1 = require("../core/logger");
36
+ const memoizee_1 = __importDefault(require("memoizee"));
37
+ const clause = __importStar(require("../core/clause"));
12
38
  var edgeDirection;
13
39
  (function (edgeDirection) {
14
40
  edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
@@ -62,6 +88,13 @@ class Orchestrator {
62
88
  this.defaultFieldsByFieldName = {};
63
89
  this.defaultFieldsByTSName = {};
64
90
  this.viewer = options.viewer;
91
+ this.actualOperation = this.options.operation;
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;
65
98
  }
66
99
  addEdge(edge, op) {
67
100
  this.edgeSet.add(edge.edgeType);
@@ -80,6 +113,9 @@ class Orchestrator {
80
113
  m1.set(op, m2);
81
114
  this.edges.set(edge.edgeType, m1);
82
115
  }
116
+ setDisableTransformations(val) {
117
+ this.disableTransformations = val;
118
+ }
83
119
  addInboundEdge(id1, edgeType, nodeType, options) {
84
120
  this.addEdge(new edgeInputData({
85
121
  id: id1,
@@ -135,24 +171,33 @@ class Orchestrator {
135
171
  }
136
172
  buildMainOp() {
137
173
  // this assumes we have validated fields
138
- switch (this.options.operation) {
174
+ switch (this.actualOperation) {
139
175
  case action_1.WriteOperation.Delete:
140
- return new ent_1.DeleteNodeOperation(this.options.builder.existingEnt.id, {
176
+ return new ent_1.DeleteNodeOperation(this.existingEnt.id, {
141
177
  tableName: this.options.tableName,
142
178
  });
143
179
  default:
180
+ if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
181
+ throw new Error(`existing ent required with operation ${this.actualOperation}`);
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
+ }
144
187
  const opts = {
145
188
  fields: this.validatedFields,
146
189
  tableName: this.options.tableName,
147
190
  fieldsToResolve: this.fieldsToResolve,
148
191
  key: this.options.key,
149
- ent: this.options.loaderOptions.ent,
192
+ loadEntOptions: this.options.loaderOptions,
150
193
  placeholderID: this.options.builder.placeholderID,
194
+ whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
195
+ expressions: this.options.expressions,
151
196
  };
152
197
  if (this.logValues) {
153
198
  opts.fieldsToLog = this.logValues;
154
199
  }
155
- this.mainOp = new ent_1.EditNodeOperation(opts, this.options.builder.existingEnt);
200
+ this.mainOp = new ent_1.EditNodeOperation(opts, this.existingEnt);
156
201
  return this.mainOp;
157
202
  }
158
203
  }
@@ -201,7 +246,7 @@ class Orchestrator {
201
246
  ops.push(edgeOp);
202
247
  const edgeData = edgeDatas.get(edgeType);
203
248
  if (!edgeData) {
204
- throw new Error(`could not load edge data for ${edgeType}`);
249
+ throw new Error(`could not load edge data for '${edgeType}'`);
205
250
  }
206
251
  if (edgeData.symmetricEdge) {
207
252
  ops.push(edgeOp.symmetricEdge());
@@ -219,99 +264,267 @@ class Orchestrator {
219
264
  if (!privacyPolicy || !action) {
220
265
  throw new Error(`shouldn't get here if no privacyPolicy for action`);
221
266
  }
222
- if (this.options.operation === action_1.WriteOperation.Insert) {
267
+ if (this.actualOperation === action_1.WriteOperation.Insert) {
223
268
  return new EntCannotCreateEntError(privacyPolicy, action);
224
269
  }
225
- else if (this.options.operation === action_1.WriteOperation.Edit) {
226
- return new EntCannotEditEntError(privacyPolicy, action, this.options.builder.existingEnt);
270
+ else if (this.actualOperation === action_1.WriteOperation.Edit) {
271
+ return new EntCannotEditEntError(privacyPolicy, action, this.existingEnt);
227
272
  }
228
- return new EntCannotDeleteEntError(privacyPolicy, action, this.options.builder.existingEnt);
273
+ return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
229
274
  }
230
- getEntForPrivacyPolicy(editedData) {
231
- if (this.options.operation !== action_1.WriteOperation.Insert) {
232
- return this.options.builder.existingEnt;
275
+ getEntForPrivacyPolicyImpl(editedData) {
276
+ if (this.actualOperation !== action_1.WriteOperation.Insert) {
277
+ return this.existingEnt;
233
278
  }
234
279
  // we create an unsafe ent to be used for privacy policies
235
280
  return new this.options.builder.ent(this.options.builder.viewer, editedData);
236
281
  }
282
+ getSQLStatementOperation() {
283
+ switch (this.actualOperation) {
284
+ case action_1.WriteOperation.Edit:
285
+ return schema_1.SQLStatementOperation.Update;
286
+ case action_1.WriteOperation.Insert:
287
+ return schema_1.SQLStatementOperation.Insert;
288
+ case action_1.WriteOperation.Delete:
289
+ return schema_1.SQLStatementOperation.Delete;
290
+ }
291
+ }
292
+ getWriteOpForSQLStamentOp(op) {
293
+ switch (op) {
294
+ case schema_1.SQLStatementOperation.Update:
295
+ return action_1.WriteOperation.Edit;
296
+ case schema_1.SQLStatementOperation.Insert:
297
+ return action_1.WriteOperation.Insert;
298
+ case schema_1.SQLStatementOperation.Update:
299
+ return action_1.WriteOperation.Delete;
300
+ default:
301
+ throw new Error("invalid path");
302
+ }
303
+ }
304
+ // if you're doing custom privacy within an action and want to
305
+ // get either the unsafe ent or the existing ent that's being edited
306
+ async getPossibleUnsafeEntForPrivacy() {
307
+ if (this.actualOperation !== action_1.WriteOperation.Insert) {
308
+ return this.existingEnt;
309
+ }
310
+ const { editedData } = await this.memoizedGetFields();
311
+ return this.getEntForPrivacyPolicyImpl(editedData);
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
332
+ async getFieldsInfo() {
333
+ const action = this.options.action;
334
+ const builder = this.options.builder;
335
+ // future optimization: can get schemaFields to memoize based on different values
336
+ const schemaFields = (0, schema_1.getFields)(this.options.schema);
337
+ const editedFields = await this.options.editedFields();
338
+ let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
339
+ return { editedData, editedFields, schemaFields };
340
+ }
237
341
  async validate() {
238
342
  // existing ent required for edit or delete operations
239
- switch (this.options.operation) {
343
+ switch (this.actualOperation) {
240
344
  case action_1.WriteOperation.Delete:
241
345
  case action_1.WriteOperation.Edit:
242
- if (!this.options.builder.existingEnt) {
243
- throw new Error("existing ent required with operation");
346
+ if (!this.existingEnt) {
347
+ throw new Error(`existing ent required with operation ${this.actualOperation}`);
244
348
  }
245
349
  }
350
+ const { schemaFields, editedData } = await this.memoizedGetFields();
246
351
  const action = this.options.action;
247
352
  const builder = this.options.builder;
248
- // future optimization: can get schemaFields to memoize based on different values
249
- const schemaFields = (0, schema_1.getFields)(this.options.schema);
250
- let editedData = this.getFieldsWithDefaultValues(builder, schemaFields, action);
251
353
  // this runs in following phases:
252
354
  // * set default fields and pass to builder so the value can be checked by triggers/observers/validators
253
355
  // * privacy policy (use unsafe ent if we have it)
254
356
  // * triggers
255
357
  // * validators
256
358
  let privacyPolicy = action?.getPrivacyPolicy();
359
+ let privacyError = null;
257
360
  if (privacyPolicy) {
258
- await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicy(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];
259
371
  }
260
372
  // have to run triggers which update fields first before field and other validators
261
373
  // so running this first to build things up
262
- let triggers = action?.triggers;
263
- if (triggers) {
264
- await this.triggers(action, builder, triggers);
265
- }
266
- let validators = action?.validators || [];
267
- await Promise.all([
268
- this.formatAndValidateFields(schemaFields),
374
+ if (action?.getTriggers) {
375
+ await this.triggers(action, builder, action.getTriggers());
376
+ }
377
+ let validators = [];
378
+ if (action?.getValidators) {
379
+ validators = action.getValidators();
380
+ }
381
+ // not ideal we're calling this twice. fix...
382
+ // needed for now. may need to rewrite some of this?
383
+ const editedFields2 = await this.options.editedFields();
384
+ const [errors, errs2] = await Promise.all([
385
+ this.formatAndValidateFields(schemaFields, editedFields2),
269
386
  this.validators(validators, action, builder),
270
387
  ]);
388
+ errors.push(...errs2);
389
+ return errors;
271
390
  }
272
391
  async triggers(action, builder, triggers) {
273
- await Promise.all(triggers.map(async (trigger) => {
274
- let ret = await trigger.changeset(builder, action.getInput());
275
- if (Array.isArray(ret)) {
276
- ret = await Promise.all(ret);
277
- }
278
- if (Array.isArray(ret)) {
279
- for (const v of ret) {
280
- if (typeof v === "object") {
281
- this.changesets.push(v);
282
- }
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));
283
401
  }
402
+ groups.push(t);
403
+ prevWasArray = true;
404
+ lastArray++;
284
405
  }
285
- else if (ret) {
286
- 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;
287
412
  }
288
- }));
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
+ }
289
432
  }
290
433
  async validators(validators, action, builder) {
291
- let promises = [];
292
- validators.forEach((validator) => {
293
- let res = validator.validate(builder, action.getInput());
294
- if (res) {
295
- 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
+ }
296
441
  }
297
- });
298
- await Promise.all(promises);
442
+ catch (err) {
443
+ errors.push(err);
444
+ }
445
+ }));
446
+ return errors;
299
447
  }
300
448
  isBuilder(val) {
301
449
  return val.placeholderID !== undefined;
302
450
  }
303
- getFieldsWithDefaultValues(builder, schemaFields, action) {
304
- const editedFields = this.options.editedFields();
451
+ getInputKey(k) {
452
+ return this.options.fieldInfo[k].inputKey;
453
+ }
454
+ getStorageKey(k) {
455
+ return this.options.fieldInfo[k].dbCol;
456
+ }
457
+ async getFieldsWithDefaultValues(builder, schemaFields, editedFields, action) {
305
458
  let data = {};
306
459
  let defaultData = {};
307
460
  let input = action?.getInput() || {};
308
461
  let updateInput = false;
462
+ // transformations
463
+ // if action transformations. always do it
464
+ // if disable transformations set, don't do schema transform and just do the right thing
465
+ // else apply schema tranformation if it exists
466
+ let transformed = null;
467
+ const sqlOp = this.getSQLStatementOperation();
468
+ if (action?.transformWrite) {
469
+ transformed = await action.transformWrite({
470
+ builder,
471
+ input,
472
+ op: sqlOp,
473
+ data: editedFields,
474
+ });
475
+ }
476
+ else if (!this.disableTransformations) {
477
+ transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
478
+ builder,
479
+ input,
480
+ op: sqlOp,
481
+ data: editedFields,
482
+ });
483
+ }
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
+ }
490
+ if (transformed.data) {
491
+ updateInput = true;
492
+ for (const k in transformed.data) {
493
+ let field = schemaFields.get(k);
494
+ if (!field) {
495
+ throw new Error(`tried to transform field with unknown field ${k}`);
496
+ }
497
+ let val = transformed.data[k];
498
+ if (field.format) {
499
+ val = field.format(transformed.data[k]);
500
+ }
501
+ data[this.getStorageKey(k)] = val;
502
+ this.defaultFieldsByTSName[this.getInputKey(k)] = val;
503
+ // hmm do we need this?
504
+ // TODO how to do this for local tests?
505
+ // this.defaultFieldsByFieldName[k] = val;
506
+ }
507
+ }
508
+ if (transformed.changeset) {
509
+ const ct = await transformed.changeset();
510
+ this.changesets.push(ct);
511
+ }
512
+ this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
513
+ if (transformed.existingEnt) {
514
+ // @ts-ignore
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;
518
+ }
519
+ }
520
+ // transforming before doing default fields so that we don't create a new id
521
+ // and anything that depends on the type of operations knows what it is
309
522
  for (const [fieldName, field] of schemaFields) {
310
523
  let value = editedFields.get(fieldName);
311
524
  let defaultValue = undefined;
312
- let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(fieldName);
525
+ let dbKey = this.getStorageKey(fieldName);
313
526
  if (value === undefined) {
314
- if (this.options.operation === action_1.WriteOperation.Insert) {
527
+ if (this.actualOperation === action_1.WriteOperation.Insert) {
315
528
  if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
316
529
  throw new Error(`cannot set both defaultToViewerOnCreate and defaultValueOnCreate`);
317
530
  }
@@ -326,7 +539,7 @@ class Orchestrator {
326
539
  }
327
540
  }
328
541
  if (field.defaultValueOnEdit &&
329
- this.options.operation === action_1.WriteOperation.Edit) {
542
+ this.actualOperation === action_1.WriteOperation.Edit) {
330
543
  defaultValue = field.defaultValueOnEdit(builder, input);
331
544
  }
332
545
  }
@@ -337,8 +550,7 @@ class Orchestrator {
337
550
  updateInput = true;
338
551
  defaultData[dbKey] = defaultValue;
339
552
  this.defaultFieldsByFieldName[fieldName] = defaultValue;
340
- // TODO related to #510. we need this logic to be consistent so do this all in TypeScript or get it from go somehow
341
- this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
553
+ this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
342
554
  }
343
555
  }
344
556
  // if there's data changing, add data
@@ -364,7 +576,7 @@ class Orchestrator {
364
576
  // now format and validate...
365
577
  if (value === null) {
366
578
  if (!field.nullable) {
367
- 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`);
368
580
  }
369
581
  }
370
582
  else if (value === undefined) {
@@ -374,15 +586,15 @@ class Orchestrator {
374
586
  // not setting server default as we're depending on the database handling that.
375
587
  // server default allowed
376
588
  field.serverDefault === undefined &&
377
- this.options.operation === action_1.WriteOperation.Insert) {
378
- throw new Error(`required field ${fieldName} not set`);
589
+ this.actualOperation === action_1.WriteOperation.Insert) {
590
+ return new Error(`required field ${fieldName} not set`);
379
591
  }
380
592
  }
381
593
  else if (this.isBuilder(value)) {
382
594
  if (field.valid) {
383
- const valid = await Promise.resolve(field.valid(value));
595
+ const valid = await field.valid(value);
384
596
  if (!valid) {
385
- throw new Error(`invalid field ${fieldName} with value ${value}`);
597
+ return new Error(`invalid field ${fieldName} with value ${value}`);
386
598
  }
387
599
  }
388
600
  // keep track of dependencies to resolve
@@ -393,69 +605,112 @@ class Orchestrator {
393
605
  else {
394
606
  if (field.valid) {
395
607
  // TODO this could be async. handle this better
396
- const valid = await Promise.resolve(field.valid(value));
608
+ const valid = await field.valid(value);
397
609
  if (!valid) {
398
- throw new Error(`invalid field ${fieldName} with value ${value}`);
610
+ return new Error(`invalid field ${fieldName} with value ${value}`);
399
611
  }
400
612
  }
401
613
  if (field.format) {
402
- // TODO this could be async e.g. password. handle this better
403
- value = await Promise.resolve(field.format(value));
614
+ value = await field.format(value);
404
615
  }
405
616
  }
406
617
  return value;
407
618
  }
408
- async formatAndValidateFields(schemaFields) {
409
- const op = this.options.operation;
619
+ async formatAndValidateFields(schemaFields, editedFields) {
620
+ const errors = [];
621
+ const op = this.actualOperation;
410
622
  if (op === action_1.WriteOperation.Delete) {
411
- return;
623
+ return [];
412
624
  }
413
- const editedFields = this.options.editedFields();
414
625
  // build up data to be saved...
415
626
  let data = {};
416
627
  let logValues = {};
628
+ let needsFullDataChecks = [];
417
629
  for (const [fieldName, field] of schemaFields) {
418
630
  let value = editedFields.get(fieldName);
631
+ if (field.validateWithFullData) {
632
+ needsFullDataChecks.push(fieldName);
633
+ }
419
634
  if (value === undefined && op === action_1.WriteOperation.Insert) {
420
635
  // null allowed
421
636
  value = this.defaultFieldsByFieldName[fieldName];
422
637
  }
423
- let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(fieldName);
424
- 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
+ }
425
646
  if (value !== undefined) {
426
647
  data[dbKey] = value;
427
648
  logValues[dbKey] = field.logValue(value);
428
649
  }
429
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
+ }
430
666
  // we ignored default values while editing.
431
667
  // if we're editing and there's data, add default values
432
668
  if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
433
669
  for (const fieldName in this.defaultFieldsByFieldName) {
434
670
  const defaultValue = this.defaultFieldsByFieldName[fieldName];
435
671
  let field = schemaFields.get(fieldName);
436
- let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(fieldName);
672
+ let dbKey = this.getStorageKey(fieldName);
437
673
  // no value, let's just default
438
674
  if (data[dbKey] === undefined) {
439
- const value = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
440
- data[dbKey] = value;
441
- 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
+ }
442
683
  }
443
684
  }
444
685
  }
445
686
  this.validatedFields = data;
446
687
  this.logValues = logValues;
688
+ return errors;
447
689
  }
448
690
  async valid() {
449
- try {
450
- await this.validate();
451
- }
452
- catch (e) {
453
- (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));
454
694
  return false;
455
695
  }
456
696
  return true;
457
697
  }
458
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() {
459
714
  return this.validate();
460
715
  }
461
716
  async build() {
@@ -463,6 +718,7 @@ class Orchestrator {
463
718
  await this.validX();
464
719
  let ops = [this.buildMainOp()];
465
720
  await this.buildEdgeOps(ops);
721
+ // TODO throw if we try and create a new changeset after previously creating one
466
722
  return new EntChangeset(this.options.viewer, this.options.builder.placeholderID, this.options.loaderOptions.ent, ops, this.dependencies, this.changesets, this.options);
467
723
  }
468
724
  async viewerForEntLoad(data) {
@@ -470,7 +726,7 @@ class Orchestrator {
470
726
  if (!action || !action.viewerForEntLoad) {
471
727
  return this.options.viewer;
472
728
  }
473
- return action.viewerForEntLoad(data);
729
+ return action.viewerForEntLoad(data, action.builder.viewer.context);
474
730
  }
475
731
  async returnedRow() {
476
732
  if (this.mainOp && this.mainOp.returnedRow) {
@@ -494,7 +750,7 @@ class Orchestrator {
494
750
  const viewer = await this.viewerForEntLoad(row);
495
751
  const ent = await (0, ent_1.applyPrivacyPolicyForRow)(viewer, this.options.loaderOptions, row);
496
752
  if (!ent) {
497
- if (this.options.operation == action_1.WriteOperation.Insert) {
753
+ if (this.actualOperation == action_1.WriteOperation.Insert) {
498
754
  throw new Error(`was able to create ent but not load it`);
499
755
  }
500
756
  else {
@@ -505,6 +761,9 @@ class Orchestrator {
505
761
  }
506
762
  }
507
763
  exports.Orchestrator = Orchestrator;
764
+ function randomNum() {
765
+ return Math.random().toString(10).substring(2);
766
+ }
508
767
  class EntChangeset {
509
768
  constructor(viewer, placeholderID, ent, operations, dependencies, changesets, options) {
510
769
  this.viewer = viewer;
@@ -515,6 +774,9 @@ class EntChangeset {
515
774
  this.changesets = changesets;
516
775
  this.options = options;
517
776
  }
777
+ static changesetFrom(builder, ops) {
778
+ return new EntChangeset(builder.viewer, `$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, builder.ent, ops);
779
+ }
518
780
  executor() {
519
781
  if (this._executor) {
520
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
  }