@snowtop/ent 0.1.0-alpha14 → 0.1.0-alpha140

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 (169) hide show
  1. package/action/action.d.ts +27 -16
  2. package/action/action.js +22 -7
  3. package/action/executor.d.ts +16 -3
  4. package/action/executor.js +88 -21
  5. package/action/experimental_action.d.ts +25 -16
  6. package/action/experimental_action.js +35 -9
  7. package/action/index.d.ts +3 -1
  8. package/action/index.js +7 -1
  9. package/action/operations.d.ts +125 -0
  10. package/action/operations.js +684 -0
  11. package/action/orchestrator.d.ts +34 -11
  12. package/action/orchestrator.js +355 -92
  13. package/action/relative_value.d.ts +47 -0
  14. package/action/relative_value.js +125 -0
  15. package/action/transaction.d.ts +10 -0
  16. package/action/transaction.js +23 -0
  17. package/auth/auth.d.ts +1 -1
  18. package/core/base.d.ts +51 -21
  19. package/core/base.js +7 -1
  20. package/core/clause.d.ts +85 -40
  21. package/core/clause.js +375 -64
  22. package/core/config.d.ts +12 -1
  23. package/core/config.js +7 -1
  24. package/core/const.d.ts +3 -0
  25. package/core/const.js +6 -0
  26. package/core/context.d.ts +4 -2
  27. package/core/context.js +20 -2
  28. package/core/convert.d.ts +1 -1
  29. package/core/date.js +1 -5
  30. package/core/db.d.ts +12 -8
  31. package/core/db.js +18 -8
  32. package/core/ent.d.ts +66 -93
  33. package/core/ent.js +517 -577
  34. package/core/global_schema.d.ts +7 -0
  35. package/core/global_schema.js +51 -0
  36. package/core/loaders/assoc_count_loader.d.ts +1 -0
  37. package/core/loaders/assoc_count_loader.js +10 -2
  38. package/core/loaders/assoc_edge_loader.d.ts +1 -1
  39. package/core/loaders/assoc_edge_loader.js +8 -11
  40. package/core/loaders/index.d.ts +1 -1
  41. package/core/loaders/index.js +1 -3
  42. package/core/loaders/index_loader.d.ts +3 -3
  43. package/core/loaders/loader.d.ts +2 -2
  44. package/core/loaders/loader.js +5 -5
  45. package/core/loaders/object_loader.d.ts +30 -9
  46. package/core/loaders/object_loader.js +225 -78
  47. package/core/loaders/query_loader.d.ts +6 -12
  48. package/core/loaders/query_loader.js +52 -11
  49. package/core/loaders/raw_count_loader.js +5 -1
  50. package/core/logger.d.ts +1 -1
  51. package/core/logger.js +1 -0
  52. package/core/privacy.d.ts +7 -6
  53. package/core/privacy.js +21 -25
  54. package/core/query/assoc_query.d.ts +3 -2
  55. package/core/query/assoc_query.js +9 -1
  56. package/core/query/custom_clause_query.d.ts +27 -0
  57. package/core/query/custom_clause_query.js +84 -0
  58. package/core/query/custom_query.d.ts +17 -2
  59. package/core/query/custom_query.js +87 -12
  60. package/core/query/index.d.ts +1 -0
  61. package/core/query/index.js +3 -1
  62. package/core/query/query.d.ts +7 -3
  63. package/core/query/query.js +101 -53
  64. package/core/query/shared_assoc_test.d.ts +2 -1
  65. package/core/query/shared_assoc_test.js +35 -45
  66. package/core/query/shared_test.d.ts +8 -1
  67. package/core/query/shared_test.js +470 -236
  68. package/core/viewer.js +1 -1
  69. package/graphql/graphql.d.ts +52 -19
  70. package/graphql/graphql.js +174 -136
  71. package/graphql/graphql_field_helpers.d.ts +7 -1
  72. package/graphql/graphql_field_helpers.js +21 -1
  73. package/graphql/index.d.ts +2 -2
  74. package/graphql/index.js +3 -5
  75. package/graphql/query/connection_type.d.ts +9 -9
  76. package/graphql/query/shared_assoc_test.js +1 -1
  77. package/graphql/query/shared_edge_connection.js +1 -19
  78. package/graphql/scalars/orderby_direction.d.ts +2 -0
  79. package/graphql/scalars/orderby_direction.js +15 -0
  80. package/imports/dataz/example1/_auth.js +128 -47
  81. package/imports/dataz/example1/_viewer.js +87 -39
  82. package/imports/index.d.ts +6 -1
  83. package/imports/index.js +19 -4
  84. package/index.d.ts +14 -5
  85. package/index.js +26 -10
  86. package/package.json +18 -17
  87. package/parse_schema/parse.d.ts +31 -9
  88. package/parse_schema/parse.js +156 -13
  89. package/schema/base_schema.d.ts +9 -3
  90. package/schema/base_schema.js +12 -0
  91. package/schema/field.d.ts +78 -21
  92. package/schema/field.js +231 -71
  93. package/schema/index.d.ts +2 -2
  94. package/schema/index.js +5 -1
  95. package/schema/json_field.d.ts +16 -4
  96. package/schema/json_field.js +32 -2
  97. package/schema/schema.d.ts +94 -19
  98. package/schema/schema.js +11 -13
  99. package/schema/struct_field.d.ts +15 -3
  100. package/schema/struct_field.js +117 -22
  101. package/schema/union_field.d.ts +1 -1
  102. package/scripts/custom_compiler.js +10 -6
  103. package/scripts/custom_graphql.js +142 -31
  104. package/scripts/migrate_v0.1.js +36 -0
  105. package/scripts/move_types.js +120 -0
  106. package/scripts/read_schema.js +20 -5
  107. package/testutils/action/complex_schemas.d.ts +69 -0
  108. package/testutils/action/complex_schemas.js +405 -0
  109. package/testutils/builder.d.ts +39 -43
  110. package/testutils/builder.js +75 -49
  111. package/testutils/db/fixture.d.ts +10 -0
  112. package/testutils/db/fixture.js +26 -0
  113. package/testutils/db/{test_db.d.ts → temp_db.d.ts} +25 -8
  114. package/testutils/db/{test_db.js → temp_db.js} +224 -47
  115. package/testutils/db/value.d.ts +7 -0
  116. package/testutils/db/value.js +251 -0
  117. package/testutils/db_mock.d.ts +16 -4
  118. package/testutils/db_mock.js +52 -7
  119. package/testutils/db_time_zone.d.ts +4 -0
  120. package/testutils/db_time_zone.js +41 -0
  121. package/testutils/ent-graphql-tests/index.d.ts +7 -1
  122. package/testutils/ent-graphql-tests/index.js +52 -23
  123. package/testutils/fake_comms.js +1 -1
  124. package/testutils/fake_data/const.d.ts +2 -1
  125. package/testutils/fake_data/const.js +3 -0
  126. package/testutils/fake_data/fake_contact.d.ts +7 -3
  127. package/testutils/fake_data/fake_contact.js +13 -7
  128. package/testutils/fake_data/fake_event.d.ts +4 -1
  129. package/testutils/fake_data/fake_event.js +7 -6
  130. package/testutils/fake_data/fake_tag.d.ts +36 -0
  131. package/testutils/fake_data/fake_tag.js +89 -0
  132. package/testutils/fake_data/fake_user.d.ts +8 -5
  133. package/testutils/fake_data/fake_user.js +16 -15
  134. package/testutils/fake_data/index.js +5 -1
  135. package/testutils/fake_data/internal.d.ts +2 -0
  136. package/testutils/fake_data/internal.js +7 -1
  137. package/testutils/fake_data/tag_query.d.ts +13 -0
  138. package/testutils/fake_data/tag_query.js +43 -0
  139. package/testutils/fake_data/test_helpers.d.ts +11 -4
  140. package/testutils/fake_data/test_helpers.js +28 -12
  141. package/testutils/fake_data/user_query.d.ts +11 -4
  142. package/testutils/fake_data/user_query.js +54 -22
  143. package/testutils/fake_log.js +1 -1
  144. package/testutils/parse_sql.d.ts +6 -0
  145. package/testutils/parse_sql.js +16 -2
  146. package/testutils/test_edge_global_schema.d.ts +15 -0
  147. package/testutils/test_edge_global_schema.js +62 -0
  148. package/testutils/write.d.ts +2 -2
  149. package/testutils/write.js +33 -7
  150. package/tsc/ast.d.ts +25 -2
  151. package/tsc/ast.js +141 -17
  152. package/tsc/compilerOptions.js +5 -1
  153. package/tsc/move_generated.d.ts +1 -0
  154. package/tsc/move_generated.js +164 -0
  155. package/tsc/transform.d.ts +22 -0
  156. package/tsc/transform.js +181 -0
  157. package/tsc/transform_action.d.ts +22 -0
  158. package/tsc/transform_action.js +183 -0
  159. package/tsc/transform_ent.d.ts +17 -0
  160. package/tsc/transform_ent.js +60 -0
  161. package/tsc/transform_schema.d.ts +27 -0
  162. package/{scripts → tsc}/transform_schema.js +146 -117
  163. package/graphql/enums.d.ts +0 -3
  164. package/graphql/enums.js +0 -25
  165. package/scripts/move_generated.js +0 -142
  166. package/scripts/transform_code.js +0 -113
  167. package/scripts/transform_schema.d.ts +0 -1
  168. /package/scripts/{move_generated.d.ts → migrate_v0.1.d.ts} +0 -0
  169. /package/scripts/{transform_code.d.ts → move_types.d.ts} +0 -0
@@ -1,4 +1,27 @@
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
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
@@ -6,11 +29,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
29
  exports.EntChangeset = exports.Orchestrator = exports.edgeDirection = void 0;
7
30
  const ent_1 = require("../core/ent");
8
31
  const schema_1 = require("../schema/schema");
32
+ const operations_1 = require("./operations");
9
33
  const action_1 = require("../action");
10
34
  const privacy_1 = require("../core/privacy");
11
35
  const executor_1 = require("./executor");
12
36
  const logger_1 = require("../core/logger");
13
37
  const memoizee_1 = __importDefault(require("memoizee"));
38
+ const clause = __importStar(require("../core/clause"));
39
+ const util_1 = require("util");
14
40
  var edgeDirection;
15
41
  (function (edgeDirection) {
16
42
  edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
@@ -58,6 +84,7 @@ class Orchestrator {
58
84
  this.options = options;
59
85
  this.edgeSet = new Set();
60
86
  this.edges = new Map();
87
+ this.conditionalEdges = new Map();
61
88
  this.changesets = [];
62
89
  this.dependencies = new Map();
63
90
  this.fieldsToResolve = [];
@@ -68,7 +95,11 @@ class Orchestrator {
68
95
  this.existingEnt = this.options.builder.existingEnt;
69
96
  this.memoizedGetFields = (0, memoizee_1.default)(this.getFieldsInfo.bind(this));
70
97
  }
71
- addEdge(edge, op) {
98
+ // don't type this because we don't care
99
+ __getOptions() {
100
+ return this.options;
101
+ }
102
+ addEdge(edge, op, conditional) {
72
103
  this.edgeSet.add(edge.edgeType);
73
104
  let m1 = this.edges.get(edge.edgeType) || new Map();
74
105
  let m2 = m1.get(op) || new Map();
@@ -83,11 +114,22 @@ class Orchestrator {
83
114
  // set or overwrite the new edge data for said id
84
115
  m2.set(id, edge);
85
116
  m1.set(op, m2);
86
- this.edges.set(edge.edgeType, m1);
117
+ if (conditional && this.onConflict) {
118
+ this.conditionalEdges.set(edge.edgeType, m1);
119
+ }
120
+ else {
121
+ this.edges.set(edge.edgeType, m1);
122
+ }
87
123
  }
88
124
  setDisableTransformations(val) {
89
125
  this.disableTransformations = val;
90
126
  }
127
+ setOnConflictOptions(onConflict) {
128
+ if (onConflict?.onConflictConstraint && !onConflict.updateCols) {
129
+ throw new Error(`cannot set onConflictConstraint without updateCols`);
130
+ }
131
+ this.onConflict = onConflict;
132
+ }
91
133
  addInboundEdge(id1, edgeType, nodeType, options) {
92
134
  this.addEdge(new edgeInputData({
93
135
  id: id1,
@@ -95,7 +137,7 @@ class Orchestrator {
95
137
  nodeType,
96
138
  options,
97
139
  direction: edgeDirection.inboundEdge,
98
- }), action_1.WriteOperation.Insert);
140
+ }), action_1.WriteOperation.Insert, options?.conditional);
99
141
  }
100
142
  addOutboundEdge(id2, edgeType, nodeType, options) {
101
143
  this.addEdge(new edgeInputData({
@@ -104,21 +146,21 @@ class Orchestrator {
104
146
  nodeType,
105
147
  options,
106
148
  direction: edgeDirection.outboundEdge,
107
- }), action_1.WriteOperation.Insert);
149
+ }), action_1.WriteOperation.Insert, options?.conditional);
108
150
  }
109
- removeInboundEdge(id1, edgeType) {
151
+ removeInboundEdge(id1, edgeType, options) {
110
152
  this.addEdge(new edgeInputData({
111
153
  id: id1,
112
154
  edgeType,
113
155
  direction: edgeDirection.inboundEdge,
114
- }), action_1.WriteOperation.Delete);
156
+ }), action_1.WriteOperation.Delete, options?.conditional);
115
157
  }
116
- removeOutboundEdge(id2, edgeType) {
158
+ removeOutboundEdge(id2, edgeType, options) {
117
159
  this.addEdge(new edgeInputData({
118
160
  id: id2,
119
161
  edgeType,
120
162
  direction: edgeDirection.outboundEdge,
121
- }), action_1.WriteOperation.Delete);
163
+ }), action_1.WriteOperation.Delete, options?.conditional);
122
164
  }
123
165
  // this doesn't take a direction as that's an implementation detail
124
166
  // it doesn't make any sense to use the same edgeType for inbound and outbound edges
@@ -141,29 +183,39 @@ class Orchestrator {
141
183
  m.clear();
142
184
  }
143
185
  }
144
- buildMainOp() {
186
+ buildMainOp(conditionalBuilder) {
145
187
  // this assumes we have validated fields
146
188
  switch (this.actualOperation) {
147
189
  case action_1.WriteOperation.Delete:
148
- return new ent_1.DeleteNodeOperation(this.existingEnt.id, {
190
+ return new operations_1.DeleteNodeOperation(this.existingEnt.id, this.options.builder, {
149
191
  tableName: this.options.tableName,
150
192
  });
151
193
  default:
152
194
  if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
153
195
  throw new Error(`existing ent required with operation ${this.actualOperation}`);
154
196
  }
197
+ if (this.options.expressions &&
198
+ this.actualOperation !== action_1.WriteOperation.Edit) {
199
+ throw new Error(`expressions are only supported in edit operations for now`);
200
+ }
155
201
  const opts = {
156
202
  fields: this.validatedFields,
157
203
  tableName: this.options.tableName,
158
204
  fieldsToResolve: this.fieldsToResolve,
159
205
  key: this.options.key,
160
206
  loadEntOptions: this.options.loaderOptions,
161
- placeholderID: this.options.builder.placeholderID,
207
+ whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
208
+ expressions: this.options.expressions,
209
+ onConflict: this.onConflict,
210
+ builder: this.options.builder,
162
211
  };
163
212
  if (this.logValues) {
164
213
  opts.fieldsToLog = this.logValues;
165
214
  }
166
- this.mainOp = new ent_1.EditNodeOperation(opts, this.existingEnt);
215
+ this.mainOp = new operations_1.EditNodeOperation(opts, this.existingEnt);
216
+ if (conditionalBuilder) {
217
+ this.mainOp = new operations_1.ConditionalNodeOperation(this.mainOp, conditionalBuilder);
218
+ }
167
219
  return this.mainOp;
168
220
  }
169
221
  }
@@ -183,10 +235,10 @@ class Orchestrator {
183
235
  throw new Error(`no nodeType for edge when adding outboundEdge`);
184
236
  }
185
237
  if (edge.direction === edgeDirection.outboundEdge) {
186
- return ent_1.EdgeOperation.outboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
238
+ return operations_1.EdgeOperation.outboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
187
239
  }
188
240
  else {
189
- return ent_1.EdgeOperation.inboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
241
+ return operations_1.EdgeOperation.inboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
190
242
  }
191
243
  }
192
244
  else if (op === action_1.WriteOperation.Delete) {
@@ -195,30 +247,53 @@ class Orchestrator {
195
247
  }
196
248
  let id2 = edge.id;
197
249
  if (edge.direction === edgeDirection.outboundEdge) {
198
- return ent_1.EdgeOperation.removeOutboundEdge(this.options.builder, edgeType, id2);
250
+ return operations_1.EdgeOperation.removeOutboundEdge(this.options.builder, edgeType, id2);
199
251
  }
200
252
  else {
201
- return ent_1.EdgeOperation.removeInboundEdge(this.options.builder, edgeType, id2);
253
+ return operations_1.EdgeOperation.removeInboundEdge(this.options.builder, edgeType, id2);
202
254
  }
203
255
  }
204
256
  throw new Error("could not find an edge operation from the given parameters");
205
257
  }
206
- async buildEdgeOps(ops) {
258
+ async buildEdgeOps(ops, conditionalBuilder, conditionalOverride) {
207
259
  const edgeDatas = await (0, ent_1.loadEdgeDatas)(...Array.from(this.edgeSet.values()));
208
- for (const [edgeType, m] of this.edges) {
209
- for (const [op, m2] of m) {
210
- for (const [_, edge] of m2) {
211
- let edgeOp = this.getEdgeOperation(edgeType, op, edge);
212
- ops.push(edgeOp);
213
- const edgeData = edgeDatas.get(edgeType);
214
- if (!edgeData) {
215
- throw new Error(`could not load edge data for ${edgeType}`);
216
- }
217
- if (edgeData.symmetricEdge) {
218
- ops.push(edgeOp.symmetricEdge());
219
- }
220
- if (edgeData.inverseEdgeType) {
221
- ops.push(edgeOp.inverseEdge(edgeData));
260
+ const edges = [
261
+ [this.edges, false],
262
+ [this.conditionalEdges, true],
263
+ ];
264
+ // conditional should only apply if onconflict...
265
+ // if no upsert and just create, nothing to do here
266
+ for (const edgeInfo of edges) {
267
+ const [edges, conditionalEdge] = edgeInfo;
268
+ const conditional = conditionalOverride || conditionalEdge;
269
+ for (const [edgeType, m] of edges) {
270
+ for (const [op, m2] of m) {
271
+ for (const [_, edge] of m2) {
272
+ let edgeOp = this.getEdgeOperation(edgeType, op, edge);
273
+ if (conditional) {
274
+ ops.push(new operations_1.ConditionalOperation(edgeOp, conditionalBuilder));
275
+ }
276
+ else {
277
+ ops.push(edgeOp);
278
+ }
279
+ const edgeData = edgeDatas.get(edgeType);
280
+ if (!edgeData) {
281
+ throw new Error(`could not load edge data for '${edgeType}'`);
282
+ }
283
+ if (edgeData.symmetricEdge) {
284
+ let symmetric = edgeOp.symmetricEdge();
285
+ if (conditional) {
286
+ symmetric = new operations_1.ConditionalOperation(symmetric, conditionalBuilder);
287
+ }
288
+ ops.push(symmetric);
289
+ }
290
+ if (edgeData.inverseEdgeType) {
291
+ let inverse = edgeOp.inverseEdge(edgeData);
292
+ if (conditional) {
293
+ inverse = new operations_1.ConditionalOperation(inverse, conditionalBuilder);
294
+ }
295
+ ops.push(inverse);
296
+ }
222
297
  }
223
298
  }
224
299
  }
@@ -238,12 +313,44 @@ class Orchestrator {
238
313
  }
239
314
  return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
240
315
  }
241
- getEntForPrivacyPolicyImpl(editedData) {
316
+ async getEntForPrivacyPolicyImpl(schemaFields, editedData) {
242
317
  if (this.actualOperation !== action_1.WriteOperation.Insert) {
243
318
  return this.existingEnt;
244
319
  }
320
+ // need to format fields if possible because ent constructors expect data that's
321
+ // in the format that's coming from the db
322
+ // required for object fields...
323
+ const formatted = { ...editedData };
324
+ for (const [fieldName, field] of schemaFields) {
325
+ if (!field.format) {
326
+ continue;
327
+ }
328
+ let dbKey = this.getStorageKey(fieldName);
329
+ let val = formatted[dbKey];
330
+ if (!val) {
331
+ continue;
332
+ }
333
+ if (field.valid) {
334
+ let valid = field.valid(val);
335
+ if (util_1.types.isPromise(valid)) {
336
+ valid = await valid;
337
+ }
338
+ // if not valid, don't format and don't pass to ent?
339
+ // or just early throw here
340
+ if (!valid) {
341
+ continue;
342
+ // throw new Error(`invalid field ${fieldName} with value ${val}`);
343
+ }
344
+ }
345
+ // nested so it's not JSON stringified or anything like that
346
+ val = field.format(formatted[dbKey], true);
347
+ if (util_1.types.isPromise(val)) {
348
+ val = await val;
349
+ }
350
+ formatted[dbKey] = val;
351
+ }
245
352
  // we create an unsafe ent to be used for privacy policies
246
- return new this.options.builder.ent(this.options.builder.viewer, editedData);
353
+ return new this.options.builder.ent(this.options.builder.viewer, formatted);
247
354
  }
248
355
  getSQLStatementOperation() {
249
356
  switch (this.actualOperation) {
@@ -273,8 +380,8 @@ class Orchestrator {
273
380
  if (this.actualOperation !== action_1.WriteOperation.Insert) {
274
381
  return this.existingEnt;
275
382
  }
276
- const { editedData } = await this.memoizedGetFields();
277
- return this.getEntForPrivacyPolicyImpl(editedData);
383
+ const { schemaFields, editedData } = await this.memoizedGetFields();
384
+ return this.getEntForPrivacyPolicyImpl(schemaFields, editedData);
278
385
  }
279
386
  // this gets the fields that were explicitly set plus any default or transformed values
280
387
  // mainly exists to get default fields e.g. default id to be used in triggers
@@ -284,6 +391,16 @@ class Orchestrator {
284
391
  const { editedData } = await this.memoizedGetFields();
285
392
  return editedData;
286
393
  }
394
+ /**
395
+ * @returns validated and formatted fields that would be written to the db
396
+ * throws an error if called before valid() or validX() has been called
397
+ */
398
+ getValidatedFields() {
399
+ if (this.validatedFields === null) {
400
+ throw new Error(`trying to call getValidatedFields before validating fields`);
401
+ }
402
+ return this.validatedFields;
403
+ }
287
404
  // Note: this is memoized. call memoizedGetFields instead
288
405
  async getFieldsInfo() {
289
406
  const action = this.options.action;
@@ -312,51 +429,94 @@ class Orchestrator {
312
429
  // * triggers
313
430
  // * validators
314
431
  let privacyPolicy = action?.getPrivacyPolicy();
432
+ let privacyError = null;
315
433
  if (privacyPolicy) {
316
- await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicyImpl(editedData), this.throwError.bind(this));
434
+ try {
435
+ await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, await this.getEntForPrivacyPolicyImpl(schemaFields, editedData), this.throwError.bind(this));
436
+ }
437
+ catch (err) {
438
+ privacyError = err;
439
+ }
440
+ }
441
+ // privacyError should return first since it's less confusing
442
+ if (privacyError !== null) {
443
+ return [privacyError];
317
444
  }
318
445
  // have to run triggers which update fields first before field and other validators
319
446
  // so running this first to build things up
320
- let triggers = action?.triggers;
321
- if (triggers) {
322
- await this.triggers(action, builder, triggers);
447
+ if (action?.getTriggers) {
448
+ await this.triggers(action, builder, action.getTriggers());
449
+ }
450
+ let validators = [];
451
+ if (action?.getValidators) {
452
+ validators = action.getValidators();
323
453
  }
324
- let validators = action?.validators || [];
325
454
  // not ideal we're calling this twice. fix...
326
455
  // needed for now. may need to rewrite some of this?
327
456
  const editedFields2 = await this.options.editedFields();
328
- await Promise.all([
457
+ const [errors, errs2] = await Promise.all([
329
458
  this.formatAndValidateFields(schemaFields, editedFields2),
330
459
  this.validators(validators, action, builder),
331
460
  ]);
461
+ errors.push(...errs2);
462
+ return errors;
332
463
  }
333
464
  async triggers(action, builder, triggers) {
334
- await Promise.all(triggers.map(async (trigger) => {
335
- let ret = await trigger.changeset(builder, action.getInput());
336
- if (Array.isArray(ret)) {
337
- ret = await Promise.all(ret);
338
- }
339
- if (Array.isArray(ret)) {
340
- for (const v of ret) {
341
- if (typeof v === "object") {
342
- this.changesets.push(v);
343
- }
465
+ let groups = [];
466
+ let lastArray = 0;
467
+ let prevWasArray = false;
468
+ for (let i = 0; i < triggers.length; i++) {
469
+ let t = triggers[i];
470
+ if (Array.isArray(t)) {
471
+ if (!prevWasArray) {
472
+ // @ts-ignore
473
+ groups.push(triggers.slice(lastArray, i));
344
474
  }
475
+ groups.push(t);
476
+ prevWasArray = true;
477
+ lastArray++;
345
478
  }
346
- else if (ret) {
347
- this.changesets.push(ret);
479
+ else {
480
+ if (i === triggers.length - 1) {
481
+ // @ts-ignore
482
+ groups.push(triggers.slice(lastArray, i + 1));
483
+ }
484
+ prevWasArray = false;
348
485
  }
349
- }));
486
+ }
487
+ for (const triggers of groups) {
488
+ await Promise.all(triggers.map(async (trigger) => {
489
+ let ret = await trigger.changeset(builder, action.getInput());
490
+ if (Array.isArray(ret)) {
491
+ ret = await Promise.all(ret);
492
+ }
493
+ if (Array.isArray(ret)) {
494
+ for (const v of ret) {
495
+ if (typeof v === "object") {
496
+ this.changesets.push(v);
497
+ }
498
+ }
499
+ }
500
+ else if (ret) {
501
+ this.changesets.push(ret);
502
+ }
503
+ }));
504
+ }
350
505
  }
351
506
  async validators(validators, action, builder) {
352
- let promises = [];
353
- validators.forEach((validator) => {
354
- let res = validator.validate(builder, action.getInput());
355
- if (res) {
356
- promises.push(res);
507
+ const errors = [];
508
+ await Promise.all(validators.map(async (v) => {
509
+ try {
510
+ const r = await v.validate(builder, action.getInput());
511
+ if (r instanceof Error) {
512
+ errors.push(r);
513
+ }
514
+ }
515
+ catch (err) {
516
+ errors.push(err);
357
517
  }
358
- });
359
- await Promise.all(promises);
518
+ }));
519
+ return errors;
360
520
  }
361
521
  isBuilder(val) {
362
522
  return val.placeholderID !== undefined;
@@ -377,23 +537,31 @@ class Orchestrator {
377
537
  // if disable transformations set, don't do schema transform and just do the right thing
378
538
  // else apply schema tranformation if it exists
379
539
  let transformed = null;
540
+ const sqlOp = this.getSQLStatementOperation();
541
+ // why is transform write technically different from upsert?
542
+ // it's create -> update just at the db level...
380
543
  if (action?.transformWrite) {
381
544
  transformed = await action.transformWrite({
382
- viewer: builder.viewer,
383
- op: this.getSQLStatementOperation(),
545
+ builder,
546
+ input,
547
+ op: sqlOp,
384
548
  data: editedFields,
385
- existingEnt: this.existingEnt,
386
549
  });
387
550
  }
388
551
  else if (!this.disableTransformations) {
389
552
  transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
390
- viewer: builder.viewer,
391
- op: this.getSQLStatementOperation(),
553
+ builder,
554
+ input,
555
+ op: sqlOp,
392
556
  data: editedFields,
393
- existingEnt: this.existingEnt,
394
557
  });
395
558
  }
396
559
  if (transformed) {
560
+ if (sqlOp === schema_1.SQLStatementOperation.Insert && sqlOp !== transformed.op) {
561
+ if (!transformed.existingEnt) {
562
+ throw new Error(`cannot transform an insert operation without providing an existing ent`);
563
+ }
564
+ }
397
565
  if (transformed.data) {
398
566
  updateInput = true;
399
567
  for (const k in transformed.data) {
@@ -412,10 +580,16 @@ class Orchestrator {
412
580
  // this.defaultFieldsByFieldName[k] = val;
413
581
  }
414
582
  }
583
+ if (transformed.changeset) {
584
+ const changeset = await transformed.changeset();
585
+ this.changesets.push(changeset);
586
+ }
415
587
  this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
416
588
  if (transformed.existingEnt) {
417
589
  // @ts-ignore
418
590
  this.existingEnt = transformed.existingEnt;
591
+ // modify existing ent in builder. it's readonly in generated ents but doesn't apply here
592
+ builder.existingEnt = transformed.existingEnt;
419
593
  }
420
594
  }
421
595
  // transforming before doing default fields so that we don't create a new id
@@ -424,6 +598,7 @@ class Orchestrator {
424
598
  let value = editedFields.get(fieldName);
425
599
  let defaultValue = undefined;
426
600
  let dbKey = this.getStorageKey(fieldName);
601
+ let updateOnlyIfOther = field.onlyUpdateIfOtherFieldsBeingSet_BETA;
427
602
  if (value === undefined) {
428
603
  if (this.actualOperation === action_1.WriteOperation.Insert) {
429
604
  if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
@@ -437,11 +612,17 @@ class Orchestrator {
437
612
  if (defaultValue === undefined) {
438
613
  throw new Error(`defaultValueOnCreate() returned undefined for field ${fieldName}`);
439
614
  }
615
+ if (util_1.types.isPromise(defaultValue)) {
616
+ defaultValue = await defaultValue;
617
+ }
440
618
  }
441
619
  }
442
620
  if (field.defaultValueOnEdit &&
443
621
  this.actualOperation === action_1.WriteOperation.Edit) {
444
622
  defaultValue = field.defaultValueOnEdit(builder, input);
623
+ if (util_1.types.isPromise(defaultValue)) {
624
+ defaultValue = await defaultValue;
625
+ }
445
626
  }
446
627
  }
447
628
  if (value !== undefined) {
@@ -449,7 +630,12 @@ class Orchestrator {
449
630
  }
450
631
  if (defaultValue !== undefined) {
451
632
  updateInput = true;
452
- defaultData[dbKey] = defaultValue;
633
+ if (updateOnlyIfOther) {
634
+ defaultData[dbKey] = defaultValue;
635
+ }
636
+ else {
637
+ data[dbKey] = defaultValue;
638
+ }
453
639
  this.defaultFieldsByFieldName[fieldName] = defaultValue;
454
640
  this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
455
641
  }
@@ -477,7 +663,7 @@ class Orchestrator {
477
663
  // now format and validate...
478
664
  if (value === null) {
479
665
  if (!field.nullable) {
480
- throw new Error(`field ${fieldName} set to null for non-nullable field`);
666
+ return new Error(`field ${fieldName} set to null for non-nullable field`);
481
667
  }
482
668
  }
483
669
  else if (value === undefined) {
@@ -488,14 +674,14 @@ class Orchestrator {
488
674
  // server default allowed
489
675
  field.serverDefault === undefined &&
490
676
  this.actualOperation === action_1.WriteOperation.Insert) {
491
- throw new Error(`required field ${fieldName} not set`);
677
+ return new Error(`required field ${fieldName} not set`);
492
678
  }
493
679
  }
494
680
  else if (this.isBuilder(value)) {
495
681
  if (field.valid) {
496
682
  const valid = await field.valid(value);
497
683
  if (!valid) {
498
- throw new Error(`invalid field ${fieldName} with value ${value}`);
684
+ return new Error(`invalid field ${fieldName} with value ${value}`);
499
685
  }
500
686
  }
501
687
  // keep track of dependencies to resolve
@@ -508,7 +694,7 @@ class Orchestrator {
508
694
  // TODO this could be async. handle this better
509
695
  const valid = await field.valid(value);
510
696
  if (!valid) {
511
- throw new Error(`invalid field ${fieldName} with value ${value}`);
697
+ return new Error(`invalid field ${fieldName} with value ${value}`);
512
698
  }
513
699
  }
514
700
  if (field.format) {
@@ -518,26 +704,52 @@ class Orchestrator {
518
704
  return value;
519
705
  }
520
706
  async formatAndValidateFields(schemaFields, editedFields) {
707
+ const errors = [];
521
708
  const op = this.actualOperation;
522
709
  if (op === action_1.WriteOperation.Delete) {
523
- return;
710
+ return [];
524
711
  }
525
712
  // build up data to be saved...
526
713
  let data = {};
527
714
  let logValues = {};
715
+ let needsFullDataChecks = [];
528
716
  for (const [fieldName, field] of schemaFields) {
529
717
  let value = editedFields.get(fieldName);
718
+ if (field.validateWithFullData) {
719
+ needsFullDataChecks.push(fieldName);
720
+ }
530
721
  if (value === undefined && op === action_1.WriteOperation.Insert) {
531
722
  // null allowed
532
723
  value = this.defaultFieldsByFieldName[fieldName];
533
724
  }
534
725
  let dbKey = this.getStorageKey(fieldName);
535
- value = await this.transformFieldValue(fieldName, field, dbKey, value);
726
+ let ret = await this.transformFieldValue(fieldName, field, dbKey, value);
727
+ if (ret instanceof Error) {
728
+ errors.push(ret);
729
+ }
730
+ else {
731
+ value = ret;
732
+ }
536
733
  if (value !== undefined) {
537
734
  data[dbKey] = value;
538
735
  logValues[dbKey] = field.logValue(value);
539
736
  }
540
737
  }
738
+ for (const fieldName of needsFullDataChecks) {
739
+ const field = schemaFields.get(fieldName);
740
+ let value = editedFields.get(fieldName);
741
+ // @ts-ignore...
742
+ // type hackery because it's hard
743
+ const v = await field.validateWithFullData(value, this.options.builder);
744
+ if (!v) {
745
+ if (value === undefined) {
746
+ errors.push(new Error(`field ${fieldName} set to undefined when it can't be nullable`));
747
+ }
748
+ else {
749
+ errors.push(new Error(`field ${fieldName} set to null when it can't be nullable`));
750
+ }
751
+ }
752
+ }
541
753
  // we ignored default values while editing.
542
754
  // if we're editing and there's data, add default values
543
755
  if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
@@ -547,41 +759,73 @@ class Orchestrator {
547
759
  let dbKey = this.getStorageKey(fieldName);
548
760
  // no value, let's just default
549
761
  if (data[dbKey] === undefined) {
550
- const value = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
551
- data[dbKey] = value;
552
- logValues[dbKey] = field.logValue(value);
762
+ const ret = await this.transformFieldValue(fieldName, field, dbKey, defaultValue);
763
+ if (ret instanceof Error) {
764
+ errors.push(ret);
765
+ }
766
+ else {
767
+ data[dbKey] = ret;
768
+ logValues[dbKey] = field.logValue(ret);
769
+ }
553
770
  }
554
771
  }
555
772
  }
556
773
  this.validatedFields = data;
557
774
  this.logValues = logValues;
775
+ return errors;
558
776
  }
559
777
  async valid() {
560
- try {
561
- await this.validate();
562
- }
563
- catch (e) {
564
- (0, logger_1.log)("error", e);
778
+ const errors = await this.validate();
779
+ if (errors.length) {
780
+ errors.map((err) => (0, logger_1.log)("error", err));
565
781
  return false;
566
782
  }
567
783
  return true;
568
784
  }
569
785
  async validX() {
786
+ const errors = await this.validate();
787
+ if (errors.length) {
788
+ // just throw the first one...
789
+ // TODO we should ideally throw all of them
790
+ throw errors[0];
791
+ }
792
+ }
793
+ /**
794
+ * @experimental API that's not guaranteed to remain in the future which returns
795
+ * a list of errors encountered
796
+ * 0 errors indicates valid
797
+ * NOTE that this currently doesn't catch errors returned by validators().
798
+ * If those throws, this still throws and doesn't return them
799
+ */
800
+ async validWithErrors() {
570
801
  return this.validate();
571
802
  }
572
- async build() {
803
+ async buildPlusChangeset(conditionalBuilder, conditionalOverride) {
573
804
  // validate everything first
574
805
  await this.validX();
575
- let ops = [this.buildMainOp()];
576
- await this.buildEdgeOps(ops);
577
- return new EntChangeset(this.options.viewer, this.options.builder.placeholderID, this.options.loaderOptions.ent, ops, this.dependencies, this.changesets, this.options);
806
+ let ops = [
807
+ this.buildMainOp(conditionalOverride ? conditionalBuilder : undefined),
808
+ ];
809
+ await this.buildEdgeOps(ops, conditionalBuilder, conditionalOverride);
810
+ // TODO throw if we try and create a new changeset after previously creating one
811
+ // TODO test actualOperation value
812
+ // observers is fine since they're run after and we have the actualOperation value...
813
+ return new EntChangeset(this.options.viewer, this.options.builder, this.options.builder.placeholderID, conditionalOverride, ops, this.dependencies, this.changesets, this.options);
814
+ }
815
+ async build() {
816
+ return this.buildPlusChangeset(this.options.builder, false);
817
+ }
818
+ async buildWithOptions_BETA(options) {
819
+ // set as dependency so that we do the right order of operations
820
+ this.dependencies.set(options.conditionalBuilder.placeholderID, options.conditionalBuilder);
821
+ return this.buildPlusChangeset(options.conditionalBuilder, true);
578
822
  }
579
823
  async viewerForEntLoad(data) {
580
824
  const action = this.options.action;
581
825
  if (!action || !action.viewerForEntLoad) {
582
826
  return this.options.viewer;
583
827
  }
584
- return action.viewerForEntLoad(data);
828
+ return action.viewerForEntLoad(data, action.builder.viewer.context);
585
829
  }
586
830
  async returnedRow() {
587
831
  if (this.mainOp && this.mainOp.returnedRow) {
@@ -616,16 +860,29 @@ class Orchestrator {
616
860
  }
617
861
  }
618
862
  exports.Orchestrator = Orchestrator;
863
+ function randomNum() {
864
+ return Math.random().toString(10).substring(2);
865
+ }
866
+ // each changeset is required to have a unique placeholderID
867
+ // used in executor. if we end up creating multiple changesets from a builder, we need
868
+ // different placeholders
869
+ // in practice, only applies to Entchangeset::changesetFrom()
619
870
  class EntChangeset {
620
- constructor(viewer, placeholderID, ent, operations, dependencies, changesets, options) {
871
+ constructor(viewer, builder, placeholderID, conditionalOverride, operations, dependencies, changesets, options) {
621
872
  this.viewer = viewer;
873
+ this.builder = builder;
622
874
  this.placeholderID = placeholderID;
623
- this.ent = ent;
875
+ this.conditionalOverride = conditionalOverride;
624
876
  this.operations = operations;
625
877
  this.dependencies = dependencies;
626
878
  this.changesets = changesets;
627
879
  this.options = options;
628
880
  }
881
+ static changesetFrom(builder, ops) {
882
+ return new EntChangeset(builder.viewer, builder,
883
+ // need unique placeholderID different from the builder. see comment above EntChangeset
884
+ `$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, false, ops);
885
+ }
629
886
  executor() {
630
887
  if (this._executor) {
631
888
  return this._executor;
@@ -635,9 +892,15 @@ class EntChangeset {
635
892
  // executor and depend on something else in the stack to handle this correctly
636
893
  // ComplexExecutor which could be a parent of this should make sure the dependency
637
894
  // is resolved beforehand
638
- return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options));
639
- }
640
- return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options));
895
+ return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options, {
896
+ conditionalOverride: this.conditionalOverride,
897
+ builder: this.builder,
898
+ }));
899
+ }
900
+ return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options, {
901
+ conditionalOverride: this.conditionalOverride,
902
+ builder: this.builder,
903
+ }));
641
904
  }
642
905
  }
643
906
  exports.EntChangeset = EntChangeset;