@snowtop/ent 0.1.0-alpha99 → 0.1.0

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 (115) hide show
  1. package/action/action.d.ts +8 -1
  2. package/action/executor.d.ts +16 -3
  3. package/action/executor.js +83 -27
  4. package/action/index.d.ts +2 -1
  5. package/action/operations.d.ts +126 -0
  6. package/action/operations.js +686 -0
  7. package/action/orchestrator.d.ts +22 -8
  8. package/action/orchestrator.js +278 -67
  9. package/core/base.d.ts +34 -24
  10. package/core/clause.d.ts +62 -79
  11. package/core/clause.js +77 -5
  12. package/core/config.d.ts +5 -1
  13. package/core/config.js +3 -0
  14. package/core/const.d.ts +3 -0
  15. package/core/const.js +6 -0
  16. package/core/context.d.ts +4 -3
  17. package/core/context.js +2 -1
  18. package/core/db.d.ts +1 -0
  19. package/core/db.js +7 -7
  20. package/core/ent.d.ts +53 -105
  21. package/core/ent.js +104 -599
  22. package/core/global_schema.d.ts +7 -0
  23. package/core/global_schema.js +51 -0
  24. package/core/loaders/assoc_count_loader.d.ts +4 -2
  25. package/core/loaders/assoc_count_loader.js +10 -2
  26. package/core/loaders/assoc_edge_loader.d.ts +2 -3
  27. package/core/loaders/assoc_edge_loader.js +16 -7
  28. package/core/loaders/index.d.ts +0 -1
  29. package/core/loaders/index.js +1 -3
  30. package/core/loaders/loader.d.ts +3 -3
  31. package/core/loaders/loader.js +3 -20
  32. package/core/loaders/object_loader.d.ts +30 -10
  33. package/core/loaders/object_loader.js +179 -40
  34. package/core/loaders/query_loader.d.ts +4 -4
  35. package/core/loaders/query_loader.js +14 -19
  36. package/core/loaders/raw_count_loader.d.ts +1 -0
  37. package/core/loaders/raw_count_loader.js +3 -2
  38. package/core/privacy.d.ts +19 -10
  39. package/core/privacy.js +47 -26
  40. package/core/query/assoc_query.js +1 -1
  41. package/core/query/custom_clause_query.d.ts +6 -3
  42. package/core/query/custom_clause_query.js +36 -9
  43. package/core/query/custom_query.d.ts +3 -1
  44. package/core/query/custom_query.js +29 -6
  45. package/core/query/query.d.ts +12 -2
  46. package/core/query/query.js +67 -38
  47. package/core/query/shared_assoc_test.js +151 -10
  48. package/core/query/shared_test.d.ts +2 -2
  49. package/core/query/shared_test.js +90 -30
  50. package/core/query_impl.d.ts +8 -0
  51. package/core/query_impl.js +28 -0
  52. package/core/viewer.d.ts +2 -0
  53. package/core/viewer.js +2 -0
  54. package/graphql/graphql.d.ts +103 -19
  55. package/graphql/graphql.js +169 -134
  56. package/graphql/graphql_field_helpers.d.ts +9 -3
  57. package/graphql/graphql_field_helpers.js +22 -2
  58. package/graphql/index.d.ts +2 -1
  59. package/graphql/index.js +5 -2
  60. package/graphql/scalars/orderby_direction.d.ts +2 -0
  61. package/graphql/scalars/orderby_direction.js +15 -0
  62. package/imports/dataz/example1/_auth.js +128 -47
  63. package/imports/dataz/example1/_viewer.js +87 -39
  64. package/imports/index.d.ts +1 -1
  65. package/imports/index.js +2 -2
  66. package/index.d.ts +12 -1
  67. package/index.js +18 -6
  68. package/package.json +20 -17
  69. package/parse_schema/parse.d.ts +10 -4
  70. package/parse_schema/parse.js +70 -24
  71. package/schema/base_schema.d.ts +8 -0
  72. package/schema/base_schema.js +11 -0
  73. package/schema/field.d.ts +6 -3
  74. package/schema/field.js +72 -17
  75. package/schema/index.d.ts +1 -1
  76. package/schema/index.js +2 -1
  77. package/schema/json_field.d.ts +3 -3
  78. package/schema/json_field.js +4 -1
  79. package/schema/schema.d.ts +42 -5
  80. package/schema/schema.js +35 -41
  81. package/schema/struct_field.d.ts +8 -6
  82. package/schema/struct_field.js +67 -8
  83. package/schema/union_field.d.ts +1 -1
  84. package/scripts/custom_compiler.js +4 -4
  85. package/scripts/custom_graphql.js +105 -75
  86. package/scripts/move_types.js +4 -1
  87. package/scripts/read_schema.js +2 -2
  88. package/testutils/action/complex_schemas.d.ts +1 -1
  89. package/testutils/action/complex_schemas.js +10 -3
  90. package/testutils/builder.d.ts +3 -0
  91. package/testutils/builder.js +6 -0
  92. package/testutils/db/temp_db.d.ts +9 -1
  93. package/testutils/db/temp_db.js +82 -14
  94. package/testutils/db_mock.js +1 -3
  95. package/testutils/ent-graphql-tests/index.d.ts +1 -1
  96. package/testutils/ent-graphql-tests/index.js +30 -19
  97. package/testutils/fake_comms.js +1 -1
  98. package/testutils/fake_data/fake_contact.d.ts +1 -1
  99. package/testutils/fake_data/fake_tag.d.ts +1 -1
  100. package/testutils/fake_data/fake_user.d.ts +3 -3
  101. package/testutils/fake_data/fake_user.js +15 -4
  102. package/testutils/fake_data/tag_query.js +8 -3
  103. package/testutils/fake_data/test_helpers.d.ts +3 -2
  104. package/testutils/fake_data/test_helpers.js +4 -4
  105. package/testutils/fake_data/user_query.d.ts +5 -2
  106. package/testutils/fake_data/user_query.js +19 -2
  107. package/testutils/fake_log.js +1 -1
  108. package/tsc/ast.js +2 -1
  109. package/tsc/move_generated.js +2 -2
  110. package/tsc/transform.d.ts +2 -2
  111. package/tsc/transform.js +4 -3
  112. package/tsc/transform_ent.js +2 -1
  113. package/tsc/transform_schema.js +4 -3
  114. package/core/loaders/index_loader.d.ts +0 -14
  115. package/core/loaders/index_loader.js +0 -27
@@ -29,12 +29,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.EntChangeset = exports.Orchestrator = exports.edgeDirection = void 0;
30
30
  const ent_1 = require("../core/ent");
31
31
  const schema_1 = require("../schema/schema");
32
+ const operations_1 = require("./operations");
32
33
  const action_1 = require("../action");
33
34
  const privacy_1 = require("../core/privacy");
34
35
  const executor_1 = require("./executor");
35
36
  const logger_1 = require("../core/logger");
36
37
  const memoizee_1 = __importDefault(require("memoizee"));
37
38
  const clause = __importStar(require("../core/clause"));
39
+ const types_1 = require("util/types");
40
+ const operations_2 = require("./operations");
38
41
  var edgeDirection;
39
42
  (function (edgeDirection) {
40
43
  edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
@@ -48,31 +51,38 @@ class edgeInputData {
48
51
  return id.placeholderID !== undefined;
49
52
  }
50
53
  }
51
- function getViewer(action) {
52
- if (!action.viewer.viewerID) {
54
+ function getViewer(viewer) {
55
+ if (!viewer.viewerID) {
53
56
  return "Logged out Viewer";
54
57
  }
55
58
  else {
56
- return `Viewer with ID ${action.viewer.viewerID}`;
59
+ return `Viewer with ID ${viewer.viewerID}`;
57
60
  }
58
61
  }
59
62
  class EntCannotCreateEntError extends Error {
60
63
  constructor(privacyPolicy, action) {
61
- let msg = `${getViewer(action)} does not have permission to create ${action.builder.ent.name}`;
64
+ let msg = `${getViewer(action.viewer)} does not have permission to create ${action.builder.ent.name}`;
62
65
  super(msg);
63
66
  this.privacyPolicy = privacyPolicy;
64
67
  }
65
68
  }
66
69
  class EntCannotEditEntError extends Error {
67
70
  constructor(privacyPolicy, action, ent) {
68
- let msg = `${getViewer(action)} does not have permission to edit ${ent.constructor.name}`;
71
+ let msg = `${getViewer(action.viewer)} does not have permission to edit ${ent.constructor.name}`;
72
+ super(msg);
73
+ this.privacyPolicy = privacyPolicy;
74
+ }
75
+ }
76
+ class EntCannotEditEntFieldError extends Error {
77
+ constructor(privacyPolicy, viewer, field, ent) {
78
+ let msg = `${getViewer(viewer)} does not have permission to edit field ${field} in ${ent.constructor.name}`;
69
79
  super(msg);
70
80
  this.privacyPolicy = privacyPolicy;
71
81
  }
72
82
  }
73
83
  class EntCannotDeleteEntError extends Error {
74
84
  constructor(privacyPolicy, action, ent) {
75
- let msg = `${getViewer(action)} does not have permission to delete ${ent.constructor.name}`;
85
+ let msg = `${getViewer(action.viewer)} does not have permission to delete ${ent.constructor.name}`;
76
86
  super(msg);
77
87
  this.privacyPolicy = privacyPolicy;
78
88
  }
@@ -82,6 +92,7 @@ class Orchestrator {
82
92
  this.options = options;
83
93
  this.edgeSet = new Set();
84
94
  this.edges = new Map();
95
+ this.conditionalEdges = new Map();
85
96
  this.changesets = [];
86
97
  this.dependencies = new Map();
87
98
  this.fieldsToResolve = [];
@@ -96,7 +107,7 @@ class Orchestrator {
96
107
  __getOptions() {
97
108
  return this.options;
98
109
  }
99
- addEdge(edge, op) {
110
+ addEdge(edge, op, conditional) {
100
111
  this.edgeSet.add(edge.edgeType);
101
112
  let m1 = this.edges.get(edge.edgeType) || new Map();
102
113
  let m2 = m1.get(op) || new Map();
@@ -111,11 +122,22 @@ class Orchestrator {
111
122
  // set or overwrite the new edge data for said id
112
123
  m2.set(id, edge);
113
124
  m1.set(op, m2);
114
- this.edges.set(edge.edgeType, m1);
125
+ if (conditional && this.onConflict) {
126
+ this.conditionalEdges.set(edge.edgeType, m1);
127
+ }
128
+ else {
129
+ this.edges.set(edge.edgeType, m1);
130
+ }
115
131
  }
116
132
  setDisableTransformations(val) {
117
133
  this.disableTransformations = val;
118
134
  }
135
+ setOnConflictOptions(onConflict) {
136
+ if (onConflict?.onConflictConstraint && !onConflict.updateCols) {
137
+ throw new Error(`cannot set onConflictConstraint without updateCols`);
138
+ }
139
+ this.onConflict = onConflict;
140
+ }
119
141
  addInboundEdge(id1, edgeType, nodeType, options) {
120
142
  this.addEdge(new edgeInputData({
121
143
  id: id1,
@@ -123,7 +145,7 @@ class Orchestrator {
123
145
  nodeType,
124
146
  options,
125
147
  direction: edgeDirection.inboundEdge,
126
- }), action_1.WriteOperation.Insert);
148
+ }), action_1.WriteOperation.Insert, options?.conditional);
127
149
  }
128
150
  addOutboundEdge(id2, edgeType, nodeType, options) {
129
151
  this.addEdge(new edgeInputData({
@@ -132,21 +154,23 @@ class Orchestrator {
132
154
  nodeType,
133
155
  options,
134
156
  direction: edgeDirection.outboundEdge,
135
- }), action_1.WriteOperation.Insert);
157
+ }), action_1.WriteOperation.Insert, options?.conditional);
136
158
  }
137
- removeInboundEdge(id1, edgeType) {
159
+ removeInboundEdge(id1, edgeType, options) {
138
160
  this.addEdge(new edgeInputData({
139
161
  id: id1,
140
162
  edgeType,
141
163
  direction: edgeDirection.inboundEdge,
142
- }), action_1.WriteOperation.Delete);
164
+ options,
165
+ }), action_1.WriteOperation.Delete, options?.conditional);
143
166
  }
144
- removeOutboundEdge(id2, edgeType) {
167
+ removeOutboundEdge(id2, edgeType, options) {
145
168
  this.addEdge(new edgeInputData({
146
169
  id: id2,
147
170
  edgeType,
148
171
  direction: edgeDirection.outboundEdge,
149
- }), action_1.WriteOperation.Delete);
172
+ options,
173
+ }), action_1.WriteOperation.Delete, options?.conditional);
150
174
  }
151
175
  // this doesn't take a direction as that's an implementation detail
152
176
  // it doesn't make any sense to use the same edgeType for inbound and outbound edges
@@ -169,11 +193,11 @@ class Orchestrator {
169
193
  m.clear();
170
194
  }
171
195
  }
172
- buildMainOp() {
196
+ buildMainOp(conditionalBuilder) {
173
197
  // this assumes we have validated fields
174
198
  switch (this.actualOperation) {
175
199
  case action_1.WriteOperation.Delete:
176
- return new ent_1.DeleteNodeOperation(this.existingEnt.id, {
200
+ return new operations_1.DeleteNodeOperation(this.existingEnt.id, this.options.builder, {
177
201
  tableName: this.options.tableName,
178
202
  });
179
203
  default:
@@ -190,14 +214,18 @@ class Orchestrator {
190
214
  fieldsToResolve: this.fieldsToResolve,
191
215
  key: this.options.key,
192
216
  loadEntOptions: this.options.loaderOptions,
193
- placeholderID: this.options.builder.placeholderID,
194
217
  whereClause: clause.Eq(this.options.key, this.existingEnt?.id),
195
218
  expressions: this.options.expressions,
219
+ onConflict: this.onConflict,
220
+ builder: this.options.builder,
196
221
  };
197
222
  if (this.logValues) {
198
223
  opts.fieldsToLog = this.logValues;
199
224
  }
200
- this.mainOp = new ent_1.EditNodeOperation(opts, this.existingEnt);
225
+ this.mainOp = new operations_1.EditNodeOperation(opts, this.existingEnt);
226
+ if (conditionalBuilder) {
227
+ this.mainOp = new operations_1.ConditionalNodeOperation(this.mainOp, conditionalBuilder);
228
+ }
201
229
  return this.mainOp;
202
230
  }
203
231
  }
@@ -217,10 +245,10 @@ class Orchestrator {
217
245
  throw new Error(`no nodeType for edge when adding outboundEdge`);
218
246
  }
219
247
  if (edge.direction === edgeDirection.outboundEdge) {
220
- return ent_1.EdgeOperation.outboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
248
+ return operations_1.EdgeOperation.outboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
221
249
  }
222
250
  else {
223
- return ent_1.EdgeOperation.inboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
251
+ return operations_1.EdgeOperation.inboundEdge(this.options.builder, edgeType, edge.id, edge.nodeType, edge.options);
224
252
  }
225
253
  }
226
254
  else if (op === action_1.WriteOperation.Delete) {
@@ -229,30 +257,55 @@ class Orchestrator {
229
257
  }
230
258
  let id2 = edge.id;
231
259
  if (edge.direction === edgeDirection.outboundEdge) {
232
- return ent_1.EdgeOperation.removeOutboundEdge(this.options.builder, edgeType, id2);
260
+ return operations_1.EdgeOperation.removeOutboundEdge(this.options.builder, edgeType, id2, edge.options);
233
261
  }
234
262
  else {
235
- return ent_1.EdgeOperation.removeInboundEdge(this.options.builder, edgeType, id2);
263
+ return operations_1.EdgeOperation.removeInboundEdge(this.options.builder, edgeType, id2, edge.options);
236
264
  }
237
265
  }
238
266
  throw new Error("could not find an edge operation from the given parameters");
239
267
  }
240
- async buildEdgeOps(ops) {
268
+ async buildEdgeOps(ops, conditionalBuilder, conditionalOverride) {
241
269
  const edgeDatas = await (0, ent_1.loadEdgeDatas)(...Array.from(this.edgeSet.values()));
242
- for (const [edgeType, m] of this.edges) {
243
- for (const [op, m2] of m) {
244
- for (const [_, edge] of m2) {
245
- let edgeOp = this.getEdgeOperation(edgeType, op, edge);
246
- ops.push(edgeOp);
247
- const edgeData = edgeDatas.get(edgeType);
248
- if (!edgeData) {
249
- throw new Error(`could not load edge data for '${edgeType}'`);
250
- }
251
- if (edgeData.symmetricEdge) {
252
- ops.push(edgeOp.symmetricEdge());
253
- }
254
- if (edgeData.inverseEdgeType) {
255
- ops.push(edgeOp.inverseEdge(edgeData));
270
+ const edges = [
271
+ [this.edges, false],
272
+ [this.conditionalEdges, true],
273
+ ];
274
+ // conditional should only apply if onconflict...
275
+ // if no upsert and just create, nothing to do here
276
+ for (const edgeInfo of edges) {
277
+ const [edges, conditionalEdge] = edgeInfo;
278
+ const conditional = conditionalOverride || conditionalEdge;
279
+ for (const [edgeType, m] of edges) {
280
+ for (const [op, m2] of m) {
281
+ for (const [_, edge] of m2) {
282
+ let edgeOp = this.getEdgeOperation(edgeType, op, edge);
283
+ if (conditional) {
284
+ ops.push(new operations_1.ConditionalOperation(edgeOp, conditionalBuilder));
285
+ }
286
+ else {
287
+ ops.push(edgeOp);
288
+ }
289
+ const edgeData = edgeDatas.get(edgeType);
290
+ if (!edgeData) {
291
+ throw new Error(`could not load edge data for '${edgeType}'`);
292
+ }
293
+ // similar logic in EntChangeset.changesetFromEdgeOp
294
+ // doesn't support conditional edges
295
+ if (edgeData.symmetricEdge) {
296
+ let symmetric = edgeOp.symmetricEdge();
297
+ if (conditional) {
298
+ symmetric = new operations_1.ConditionalOperation(symmetric, conditionalBuilder);
299
+ }
300
+ ops.push(symmetric);
301
+ }
302
+ if (edgeData.inverseEdgeType) {
303
+ let inverse = edgeOp.inverseEdge(edgeData);
304
+ if (conditional) {
305
+ inverse = new operations_1.ConditionalOperation(inverse, conditionalBuilder);
306
+ }
307
+ ops.push(inverse);
308
+ }
256
309
  }
257
310
  }
258
311
  }
@@ -272,12 +325,50 @@ class Orchestrator {
272
325
  }
273
326
  return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
274
327
  }
275
- getEntForPrivacyPolicyImpl(editedData) {
328
+ async getRowForPrivacyPolicyImpl(schemaFields, editedData) {
329
+ // need to format fields if possible because ent constructors expect data that's
330
+ // in the format that's coming from the db
331
+ // required for object fields...
332
+ const formatted = { ...editedData };
333
+ for (const [fieldName, field] of schemaFields) {
334
+ if (!field.format) {
335
+ continue;
336
+ }
337
+ let dbKey = this.getStorageKey(fieldName);
338
+ let val = formatted[dbKey];
339
+ if (!val) {
340
+ continue;
341
+ }
342
+ if (field.valid) {
343
+ let valid = field.valid(val);
344
+ if ((0, types_1.isPromise)(valid)) {
345
+ valid = await valid;
346
+ }
347
+ // if not valid, don't format and don't pass to ent?
348
+ // or just early throw here
349
+ if (!valid) {
350
+ continue;
351
+ // throw new Error(`invalid field ${fieldName} with value ${val}`);
352
+ }
353
+ }
354
+ // nested so it's not JSON stringified or anything like that
355
+ val = field.format(formatted[dbKey], true);
356
+ if ((0, types_1.isPromise)(val)) {
357
+ val = await val;
358
+ }
359
+ formatted[dbKey] = val;
360
+ }
361
+ return formatted;
362
+ }
363
+ async getEntForPrivacyPolicyImpl(schemaFields, editedData, viewerToUse, rowToUse) {
276
364
  if (this.actualOperation !== action_1.WriteOperation.Insert) {
277
365
  return this.existingEnt;
278
366
  }
367
+ if (!rowToUse) {
368
+ rowToUse = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
369
+ }
279
370
  // we create an unsafe ent to be used for privacy policies
280
- return new this.options.builder.ent(this.options.builder.viewer, editedData);
371
+ return new this.options.builder.ent(viewerToUse, rowToUse);
281
372
  }
282
373
  getSQLStatementOperation() {
283
374
  switch (this.actualOperation) {
@@ -307,8 +398,8 @@ class Orchestrator {
307
398
  if (this.actualOperation !== action_1.WriteOperation.Insert) {
308
399
  return this.existingEnt;
309
400
  }
310
- const { editedData } = await this.memoizedGetFields();
311
- return this.getEntForPrivacyPolicyImpl(editedData);
401
+ const { schemaFields, editedData } = await this.memoizedGetFields();
402
+ return this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
312
403
  }
313
404
  // this gets the fields that were explicitly set plus any default or transformed values
314
405
  // mainly exists to get default fields e.g. default id to be used in triggers
@@ -334,9 +425,25 @@ class Orchestrator {
334
425
  const builder = this.options.builder;
335
426
  // future optimization: can get schemaFields to memoize based on different values
336
427
  const schemaFields = (0, schema_1.getFields)(this.options.schema);
428
+ // also future optimization, no need to go through the list of fields multiple times
429
+ let editPrivacyFields = new Map();
430
+ switch (this.actualOperation) {
431
+ case action_1.WriteOperation.Edit:
432
+ editPrivacyFields = (0, schema_1.getFieldsWithEditPrivacy)(this.options.schema, this.options.fieldInfo);
433
+ break;
434
+ case action_1.WriteOperation.Insert:
435
+ editPrivacyFields = (0, schema_1.getFieldsForCreateAction)(this.options.schema, this.options.fieldInfo);
436
+ break;
437
+ }
337
438
  const editedFields = await this.options.editedFields();
338
- let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
339
- return { editedData, editedFields, schemaFields };
439
+ let { data: editedData, userDefinedKeys } = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
440
+ return {
441
+ editedData,
442
+ editedFields,
443
+ schemaFields,
444
+ userDefinedKeys,
445
+ editPrivacyFields,
446
+ };
340
447
  }
341
448
  async validate() {
342
449
  // existing ent required for edit or delete operations
@@ -347,7 +454,7 @@ class Orchestrator {
347
454
  throw new Error(`existing ent required with operation ${this.actualOperation}`);
348
455
  }
349
456
  }
350
- const { schemaFields, editedData } = await this.memoizedGetFields();
457
+ const { schemaFields, editedData, userDefinedKeys, editPrivacyFields } = await this.memoizedGetFields();
351
458
  const action = this.options.action;
352
459
  const builder = this.options.builder;
353
460
  // this runs in following phases:
@@ -356,18 +463,40 @@ class Orchestrator {
356
463
  // * triggers
357
464
  // * validators
358
465
  let privacyPolicy = action?.getPrivacyPolicy();
359
- let privacyError = null;
466
+ const errors = [];
360
467
  if (privacyPolicy) {
468
+ const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
361
469
  try {
362
- await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, this.getEntForPrivacyPolicyImpl(editedData), this.throwError.bind(this));
470
+ await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, ent, () => this.throwError());
363
471
  }
364
472
  catch (err) {
365
- privacyError = err;
473
+ errors.push(err);
474
+ }
475
+ }
476
+ // we have edit privacy fields, so we need to apply privacy policy on those
477
+ const promises = [];
478
+ if (editPrivacyFields.size) {
479
+ // get row based on edited data
480
+ const row = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
481
+ // get viewer for ent load based on formatted row
482
+ const viewer = await this.viewerForEntLoad(row);
483
+ const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, viewer, row);
484
+ for (const [k, policy] of editPrivacyFields) {
485
+ if (editedData[k] === undefined || !userDefinedKeys.has(k)) {
486
+ continue;
487
+ }
488
+ promises.push((async () => {
489
+ const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
490
+ if (!r) {
491
+ errors.push(new EntCannotEditEntFieldError(policy, viewer, k, ent));
492
+ }
493
+ })());
366
494
  }
495
+ await Promise.all(promises);
367
496
  }
368
- // privacyError should return first since it's less confusing
369
- if (privacyError !== null) {
370
- return [privacyError];
497
+ // privacy or field errors should return first so it's less confusing
498
+ if (errors.length) {
499
+ return errors;
371
500
  }
372
501
  // have to run triggers which update fields first before field and other validators
373
502
  // so running this first to build things up
@@ -381,11 +510,12 @@ class Orchestrator {
381
510
  // not ideal we're calling this twice. fix...
382
511
  // needed for now. may need to rewrite some of this?
383
512
  const editedFields2 = await this.options.editedFields();
384
- const [errors, errs2] = await Promise.all([
513
+ const [errs2, errs3] = await Promise.all([
385
514
  this.formatAndValidateFields(schemaFields, editedFields2),
386
515
  this.validators(validators, action, builder),
387
516
  ]);
388
517
  errors.push(...errs2);
518
+ errors.push(...errs3);
389
519
  return errors;
390
520
  }
391
521
  async triggers(action, builder, triggers) {
@@ -465,6 +595,8 @@ class Orchestrator {
465
595
  // else apply schema tranformation if it exists
466
596
  let transformed = null;
467
597
  const sqlOp = this.getSQLStatementOperation();
598
+ // why is transform write technically different from upsert?
599
+ // it's create -> update just at the db level...
468
600
  if (action?.transformWrite) {
469
601
  transformed = await action.transformWrite({
470
602
  builder,
@@ -506,8 +638,8 @@ class Orchestrator {
506
638
  }
507
639
  }
508
640
  if (transformed.changeset) {
509
- const ct = await transformed.changeset();
510
- this.changesets.push(ct);
641
+ const changeset = await transformed.changeset();
642
+ this.changesets.push(changeset);
511
643
  }
512
644
  this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
513
645
  if (transformed.existingEnt) {
@@ -519,10 +651,15 @@ class Orchestrator {
519
651
  }
520
652
  // transforming before doing default fields so that we don't create a new id
521
653
  // and anything that depends on the type of operations knows what it is
654
+ const userDefinedKeys = new Set();
522
655
  for (const [fieldName, field] of schemaFields) {
523
656
  let value = editedFields.get(fieldName);
524
657
  let defaultValue = undefined;
525
658
  let dbKey = this.getStorageKey(fieldName);
659
+ let updateOnlyIfOther = field.onlyUpdateIfOtherFieldsBeingSet_BETA;
660
+ if (value !== undefined) {
661
+ userDefinedKeys.add(dbKey);
662
+ }
526
663
  if (value === undefined) {
527
664
  if (this.actualOperation === action_1.WriteOperation.Insert) {
528
665
  if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
@@ -536,11 +673,17 @@ class Orchestrator {
536
673
  if (defaultValue === undefined) {
537
674
  throw new Error(`defaultValueOnCreate() returned undefined for field ${fieldName}`);
538
675
  }
676
+ if ((0, types_1.isPromise)(defaultValue)) {
677
+ defaultValue = await defaultValue;
678
+ }
539
679
  }
540
680
  }
541
681
  if (field.defaultValueOnEdit &&
542
682
  this.actualOperation === action_1.WriteOperation.Edit) {
543
683
  defaultValue = field.defaultValueOnEdit(builder, input);
684
+ if ((0, types_1.isPromise)(defaultValue)) {
685
+ defaultValue = await defaultValue;
686
+ }
544
687
  }
545
688
  }
546
689
  if (value !== undefined) {
@@ -548,7 +691,12 @@ class Orchestrator {
548
691
  }
549
692
  if (defaultValue !== undefined) {
550
693
  updateInput = true;
551
- defaultData[dbKey] = defaultValue;
694
+ if (updateOnlyIfOther) {
695
+ defaultData[dbKey] = defaultValue;
696
+ }
697
+ else {
698
+ data[dbKey] = defaultValue;
699
+ }
552
700
  this.defaultFieldsByFieldName[fieldName] = defaultValue;
553
701
  this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
554
702
  }
@@ -564,7 +712,7 @@ class Orchestrator {
564
712
  this.options.updateInput(this.defaultFieldsByTSName);
565
713
  }
566
714
  }
567
- return data;
715
+ return { data, userDefinedKeys };
568
716
  }
569
717
  hasData(data) {
570
718
  for (const _k in data) {
@@ -592,7 +740,10 @@ class Orchestrator {
592
740
  }
593
741
  else if (this.isBuilder(value)) {
594
742
  if (field.valid) {
595
- const valid = await field.valid(value);
743
+ let valid = field.valid(value);
744
+ if ((0, types_1.isPromise)(valid)) {
745
+ valid = await valid;
746
+ }
596
747
  if (!valid) {
597
748
  return new Error(`invalid field ${fieldName} with value ${value}`);
598
749
  }
@@ -604,8 +755,10 @@ class Orchestrator {
604
755
  }
605
756
  else {
606
757
  if (field.valid) {
607
- // TODO this could be async. handle this better
608
- const valid = await field.valid(value);
758
+ let valid = field.valid(value);
759
+ if ((0, types_1.isPromise)(valid)) {
760
+ valid = await valid;
761
+ }
609
762
  if (!valid) {
610
763
  return new Error(`invalid field ${fieldName} with value ${value}`);
611
764
  }
@@ -713,13 +866,25 @@ class Orchestrator {
713
866
  async validWithErrors() {
714
867
  return this.validate();
715
868
  }
716
- async build() {
869
+ async buildPlusChangeset(conditionalBuilder, conditionalOverride) {
717
870
  // validate everything first
718
871
  await this.validX();
719
- let ops = [this.buildMainOp()];
720
- await this.buildEdgeOps(ops);
872
+ let ops = [
873
+ this.buildMainOp(conditionalOverride ? conditionalBuilder : undefined),
874
+ ];
875
+ await this.buildEdgeOps(ops, conditionalBuilder, conditionalOverride);
721
876
  // TODO throw if we try and create a new changeset after previously creating one
722
- return new EntChangeset(this.options.viewer, this.options.builder.placeholderID, this.options.loaderOptions.ent, ops, this.dependencies, this.changesets, this.options);
877
+ // TODO test actualOperation value
878
+ // observers is fine since they're run after and we have the actualOperation value...
879
+ return new EntChangeset(this.options.viewer, this.options.builder, this.options.builder.placeholderID, conditionalOverride, ops, this.dependencies, this.changesets, this.options);
880
+ }
881
+ async build() {
882
+ return this.buildPlusChangeset(this.options.builder, false);
883
+ }
884
+ async buildWithOptions_BETA(options) {
885
+ // set as dependency so that we do the right order of operations
886
+ this.dependencies.set(options.conditionalBuilder.placeholderID, options.conditionalBuilder);
887
+ return this.buildPlusChangeset(options.conditionalBuilder, true);
723
888
  }
724
889
  async viewerForEntLoad(data) {
725
890
  const action = this.options.action;
@@ -764,18 +929,58 @@ exports.Orchestrator = Orchestrator;
764
929
  function randomNum() {
765
930
  return Math.random().toString(10).substring(2);
766
931
  }
932
+ // each changeset is required to have a unique placeholderID
933
+ // used in executor. if we end up creating multiple changesets from a builder, we need
934
+ // different placeholders
935
+ // in practice, only applies to Entchangeset::changesetFrom()
767
936
  class EntChangeset {
768
- constructor(viewer, placeholderID, ent, operations, dependencies, changesets, options) {
937
+ constructor(viewer, builder, placeholderID, conditionalOverride, operations, dependencies, changesets, options) {
769
938
  this.viewer = viewer;
939
+ this.builder = builder;
770
940
  this.placeholderID = placeholderID;
771
- this.ent = ent;
941
+ this.conditionalOverride = conditionalOverride;
772
942
  this.operations = operations;
773
943
  this.dependencies = dependencies;
774
944
  this.changesets = changesets;
775
945
  this.options = options;
776
946
  }
777
947
  static changesetFrom(builder, ops) {
778
- return new EntChangeset(builder.viewer, `$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, builder.ent, ops);
948
+ return new EntChangeset(builder.viewer, builder,
949
+ // need unique placeholderID different from the builder. see comment above EntChangeset
950
+ `$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, false, ops);
951
+ }
952
+ static changesetFromQueries(builder, queries) {
953
+ return EntChangeset.changesetFrom(builder, [
954
+ new operations_2.RawQueryOperation(builder, queries),
955
+ ]);
956
+ }
957
+ static async changesetFromEdgeOp(builder, op, edgeType) {
958
+ const edgeData = await (0, ent_1.loadEdgeData)(edgeType);
959
+ const ops = [op];
960
+ if (!edgeData) {
961
+ throw new Error(`could not load edge data for '${edgeType}'`);
962
+ }
963
+ // similar logic in Orchestrator.buildEdgeOps
964
+ // doesn't support conditional edges
965
+ if (edgeData.symmetricEdge) {
966
+ ops.push(op.symmetricEdge());
967
+ }
968
+ if (edgeData.inverseEdgeType) {
969
+ ops.push(op.inverseEdge(edgeData));
970
+ }
971
+ return EntChangeset.changesetFrom(builder, ops);
972
+ }
973
+ static async changesetFromOutboundEdge(builder, edgeType, id2, nodeType, options) {
974
+ return EntChangeset.changesetFromEdgeOp(builder, operations_1.EdgeOperation.outboundEdge(builder, edgeType, id2, nodeType, options), edgeType);
975
+ }
976
+ static async changesetFromInboundEdge(builder, edgeType, id1, nodeType, options) {
977
+ return EntChangeset.changesetFromEdgeOp(builder, operations_1.EdgeOperation.inboundEdge(builder, edgeType, id1, nodeType, options), edgeType);
978
+ }
979
+ static changesetRemoveFromOutboundEdge(builder, edgeType, id2, options) {
980
+ return EntChangeset.changesetFromEdgeOp(builder, operations_1.EdgeOperation.removeOutboundEdge(builder, edgeType, id2, options), edgeType);
981
+ }
982
+ static changesetRemoveFromInboundEdge(builder, edgeType, id1, options) {
983
+ return EntChangeset.changesetFromEdgeOp(builder, operations_1.EdgeOperation.removeInboundEdge(builder, edgeType, id1, options), edgeType);
779
984
  }
780
985
  executor() {
781
986
  if (this._executor) {
@@ -786,9 +991,15 @@ class EntChangeset {
786
991
  // executor and depend on something else in the stack to handle this correctly
787
992
  // ComplexExecutor which could be a parent of this should make sure the dependency
788
993
  // is resolved beforehand
789
- return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options));
994
+ return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options, {
995
+ conditionalOverride: this.conditionalOverride,
996
+ builder: this.builder,
997
+ }));
790
998
  }
791
- return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options));
999
+ return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options, {
1000
+ conditionalOverride: this.conditionalOverride,
1001
+ builder: this.builder,
1002
+ }));
792
1003
  }
793
1004
  }
794
1005
  exports.EntChangeset = EntChangeset;