@snowtop/ent 0.0.28 → 0.0.31

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.
@@ -7,7 +7,7 @@ export declare enum WriteOperation {
7
7
  Delete = "delete"
8
8
  }
9
9
  export interface Builder<T extends Ent> {
10
- existingEnt?: Ent;
10
+ existingEnt?: T;
11
11
  ent: EntConstructor<T>;
12
12
  placeholderID: ID;
13
13
  readonly viewer: Viewer;
@@ -27,9 +27,8 @@ export interface Changeset<T extends Ent> {
27
27
  executor(): Executor;
28
28
  viewer: Viewer;
29
29
  placeholderID: ID;
30
- ent: EntConstructor<T>;
31
30
  changesets?: Changeset<Ent>[];
32
- dependencies?: Map<ID, Builder<T>>;
31
+ dependencies?: Map<ID, Builder<Ent>>;
33
32
  }
34
33
  export declare type TriggerReturn = void | Promise<Changeset<Ent> | void | (Changeset<Ent> | void)[]> | Promise<Changeset<Ent>>[];
35
34
  export interface Trigger<T extends Ent> {
@@ -1,22 +1,21 @@
1
- import { ID, Data, Ent, Viewer, EntConstructor, Context } from "../core/base";
1
+ import { ID, Data, Ent, Viewer, Context } from "../core/base";
2
2
  import { DataOperation } from "../core/ent";
3
- import { Changeset, Executor } from "../action";
3
+ import { Changeset, Executor } from "../action/action";
4
4
  import { Builder } from "../action";
5
5
  import { OrchestratorOptions } from "./orchestrator";
6
6
  import { Queryer } from "../core/db";
7
7
  export declare class ListBasedExecutor<T extends Ent> implements Executor {
8
8
  private viewer;
9
9
  placeholderID: ID;
10
- private ent;
11
10
  private operations;
12
11
  private options?;
13
12
  private idx;
14
- constructor(viewer: Viewer, placeholderID: ID, ent: EntConstructor<T>, operations: DataOperation[], options?: OrchestratorOptions<T, Data> | undefined);
13
+ constructor(viewer: Viewer, placeholderID: ID, operations: DataOperation<T>[], options?: OrchestratorOptions<T, Data> | undefined);
15
14
  private lastOp;
16
15
  private createdEnt;
17
16
  resolveValue(val: ID): Ent | null;
18
17
  [Symbol.iterator](): this;
19
- next(): IteratorResult<DataOperation>;
18
+ next(): IteratorResult<DataOperation<T>>;
20
19
  executeObservers(): Promise<void>;
21
20
  execute(): Promise<void>;
22
21
  preFetch?(queryer: Queryer, context: Context): Promise<void>;
@@ -25,23 +24,19 @@ export declare class ListBasedExecutor<T extends Ent> implements Executor {
25
24
  export declare class ComplexExecutor<T extends Ent> implements Executor {
26
25
  private viewer;
27
26
  placeholderID: ID;
28
- private ent;
29
- private options?;
30
27
  private idx;
31
28
  private mapper;
32
29
  private lastOp;
33
30
  private allOperations;
34
- private changesetMap;
35
- private nodeOpMap;
36
31
  private executors;
37
- constructor(viewer: Viewer, placeholderID: ID, ent: EntConstructor<T>, operations: DataOperation[], dependencies: Map<ID, Builder<T>>, changesets: Changeset<T>[], options?: OrchestratorOptions<T, Data> | undefined);
32
+ constructor(viewer: Viewer, placeholderID: ID, operations: DataOperation[], dependencies: Map<ID, Builder<T>>, changesets: Changeset<T>[], options?: OrchestratorOptions<T, Data>);
38
33
  [Symbol.iterator](): this;
39
34
  private handleCreatedEnt;
40
- next(): IteratorResult<DataOperation>;
35
+ next(): IteratorResult<DataOperation<Ent>>;
41
36
  resolveValue(val: ID): Ent | null;
42
37
  executeObservers(): Promise<void>;
43
38
  execute(): Promise<void>;
44
39
  preFetch?(queryer: Queryer, context: Context): Promise<void>;
45
40
  postFetch?(queryer: Queryer, context: Context): Promise<void>;
46
41
  }
47
- export declare function executeOperations(executor: Executor, context?: Context, trackOps?: true): Promise<DataOperation[]>;
42
+ export declare function executeOperations(executor: Executor, context?: Context, trackOps?: true): Promise<DataOperation<Ent>[]>;
@@ -9,10 +9,9 @@ const db_1 = __importDefault(require("../core/db"));
9
9
  const logger_1 = require("../core/logger");
10
10
  // private to ent
11
11
  class ListBasedExecutor {
12
- constructor(viewer, placeholderID, ent, operations, options) {
12
+ constructor(viewer, placeholderID, operations, options) {
13
13
  this.viewer = viewer;
14
14
  this.placeholderID = placeholderID;
15
- this.ent = ent;
16
15
  this.operations = operations;
17
16
  this.options = options;
18
17
  this.idx = 0;
@@ -27,8 +26,9 @@ class ListBasedExecutor {
27
26
  [Symbol.iterator]() {
28
27
  return this;
29
28
  }
29
+ // returns true and null|undefined when done
30
30
  next() {
31
- let createdEnt = getCreatedEnt(this.viewer, this.lastOp, this.ent);
31
+ let createdEnt = getCreatedEnt(this.viewer, this.lastOp);
32
32
  if (createdEnt) {
33
33
  this.createdEnt = createdEnt;
34
34
  }
@@ -36,6 +36,10 @@ class ListBasedExecutor {
36
36
  const op = this.operations[this.idx];
37
37
  this.idx++;
38
38
  this.lastOp = op;
39
+ // reset since this could be called multiple times. not needed if we have getSortedOps or something like that
40
+ if (done) {
41
+ this.idx = 0;
42
+ }
39
43
  return {
40
44
  value: op,
41
45
  done: done,
@@ -74,30 +78,24 @@ class ListBasedExecutor {
74
78
  }
75
79
  }
76
80
  exports.ListBasedExecutor = ListBasedExecutor;
77
- function getCreatedEnt(viewer, op, ent) {
78
- if (op && op.returnedEntRow) {
79
- let row = op.returnedEntRow();
80
- if (row) {
81
- return new ent(viewer, row);
82
- }
81
+ function getCreatedEnt(viewer, op) {
82
+ if (op && op.createdEnt) {
83
+ return op.createdEnt(viewer);
83
84
  }
84
85
  return null;
85
86
  }
86
87
  class ComplexExecutor {
87
- constructor(viewer, placeholderID, ent, operations, dependencies, changesets, options) {
88
+ constructor(viewer, placeholderID, operations, dependencies, changesets, options) {
88
89
  this.viewer = viewer;
89
90
  this.placeholderID = placeholderID;
90
- this.ent = ent;
91
- this.options = options;
92
91
  this.idx = 0;
93
92
  this.mapper = new Map();
94
93
  this.allOperations = [];
95
- this.changesetMap = new Map();
96
- this.nodeOpMap = new Map();
97
94
  this.executors = [];
98
95
  let graph = (0, graph_data_structure_1.default)();
96
+ const changesetMap = new Map();
99
97
  const impl = (c) => {
100
- this.changesetMap.set(c.placeholderID.toString(), c);
98
+ changesetMap.set(c.placeholderID.toString(), c);
101
99
  graph.addNode(c.placeholderID.toString());
102
100
  if (c.dependencies) {
103
101
  for (let [key, builder] of c.dependencies) {
@@ -117,20 +115,19 @@ class ComplexExecutor {
117
115
  impl({
118
116
  viewer: this.viewer,
119
117
  placeholderID: this.placeholderID,
120
- ent: this.ent,
121
118
  changesets: changesets,
122
119
  dependencies: dependencies,
123
120
  executor: () => {
124
- return new ListBasedExecutor(this.viewer, this.placeholderID, this.ent, operations, this.options);
121
+ return new ListBasedExecutor(this.viewer, this.placeholderID, operations, options);
125
122
  },
126
123
  });
127
124
  // use a set to handle repeated ops because of how the executor logic currently works
128
- // TODO: this logic can be rewritten to be smarter and probably not need a set
125
+ // TODO: can this logic be rewritten to not have a set yet avoid duplicates?
129
126
  let nodeOps = new Set();
130
127
  let remainOps = new Set();
131
128
  let sorted = graph.topologicalSort(graph.nodes());
132
129
  sorted.forEach((node) => {
133
- let c = this.changesetMap.get(node);
130
+ let c = changesetMap.get(node);
134
131
  if (!c) {
135
132
  // phew. expect it to be handled somewhere else
136
133
  // we can just skip it and expect the resolver to handle this correctly
@@ -143,9 +140,8 @@ class ComplexExecutor {
143
140
  // get ordered list of ops
144
141
  let executor = c.executor();
145
142
  for (let op of executor) {
146
- if (op.returnedEntRow) {
143
+ if (op.createdEnt) {
147
144
  nodeOps.add(op);
148
- this.nodeOpMap.set(op, c);
149
145
  }
150
146
  else {
151
147
  remainOps.add(op);
@@ -165,26 +161,30 @@ class ComplexExecutor {
165
161
  return this;
166
162
  }
167
163
  handleCreatedEnt() {
168
- let c = this.nodeOpMap.get(this.lastOp);
169
- if (!c) {
170
- // nothing to do here
164
+ if (!this.lastOp) {
171
165
  return;
172
166
  }
173
- let createdEnt = getCreatedEnt(this.viewer, this.lastOp, c.ent);
167
+ let createdEnt = getCreatedEnt(this.viewer, this.lastOp);
174
168
  if (!createdEnt) {
175
169
  return;
176
170
  }
177
- let placeholderID = c.placeholderID;
171
+ const placeholderID = this.lastOp.placeholderID;
172
+ if (!placeholderID) {
173
+ console.error(`op ${this.lastOp} which implements getCreatedEnt doesn't have a placeholderID`);
174
+ return;
175
+ }
178
176
  this.mapper.set(placeholderID, createdEnt);
179
177
  }
180
178
  next() {
181
- if (this.lastOp) {
182
- this.handleCreatedEnt();
183
- }
179
+ this.handleCreatedEnt();
184
180
  const done = this.idx === this.allOperations.length;
185
181
  const op = this.allOperations[this.idx];
186
182
  this.idx++;
187
183
  this.lastOp = op;
184
+ // reset since this could be called multiple times. not needed if we have getSortedOps or something like that
185
+ if (done) {
186
+ this.idx = 0;
187
+ }
188
188
  return {
189
189
  value: op,
190
190
  done: done,
@@ -195,6 +195,12 @@ class ComplexExecutor {
195
195
  if (ent) {
196
196
  return ent;
197
197
  }
198
+ for (const c of this.executors) {
199
+ const ent = c.resolveValue(val);
200
+ if (ent) {
201
+ return ent;
202
+ }
203
+ }
198
204
  return null;
199
205
  }
200
206
  async executeObservers() {
@@ -1,4 +1,4 @@
1
- import { Viewer, Ent } from "../core/base";
1
+ import { Viewer, Ent, Data } from "../core/base";
2
2
  import { Action, WriteOperation, Builder, Trigger, Observer, Changeset, Validator } from "./action";
3
3
  export interface ActionOptions<T extends Ent> {
4
4
  existingEnt?: T | null;
@@ -35,4 +35,6 @@ export declare class BaseAction<T extends Ent> implements Action<T> {
35
35
  interface BuilderConstructor<T extends Ent> {
36
36
  new (viewer: Viewer, operation: WriteOperation, action: Action<T>, existingEnt?: T | undefined): EntBuilder<T>;
37
37
  }
38
+ export declare function updateRawObject<TEnt extends Ent, TInput extends Data>(viewer: Viewer, builderCtr: BuilderConstructor<TEnt>, existingEnt: TEnt, input: TInput): Promise<TEnt>;
39
+ export declare function getSimpleEditAction<TEnt extends Ent, TInput extends Data>(viewer: Viewer, builderCtr: BuilderConstructor<TEnt>, existingEnt: TEnt, input: TInput): Action<TEnt>;
38
40
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BaseAction = void 0;
3
+ exports.getSimpleEditAction = exports.updateRawObject = exports.BaseAction = void 0;
4
4
  const privacy_1 = require("../core/privacy");
5
5
  const action_1 = require("./action");
6
6
  class BaseAction {
@@ -66,3 +66,28 @@ class BaseAction {
66
66
  }
67
67
  }
68
68
  exports.BaseAction = BaseAction;
69
+ // this provides a way to just update a row in the database.
70
+ // skips privacy, triggers, observers, etc
71
+ // does do field validation
72
+ // note that only editable fields in the builder can be passed here
73
+ async function updateRawObject(viewer, builderCtr, existingEnt, input) {
74
+ const action = new BaseAction(viewer, builderCtr, {
75
+ existingEnt: existingEnt,
76
+ operation: action_1.WriteOperation.Edit,
77
+ input,
78
+ });
79
+ return action.saveX();
80
+ }
81
+ exports.updateRawObject = updateRawObject;
82
+ // creates an action which has no privacy, triggers, observers etc
83
+ // does do field validation
84
+ // useful to batch a bunch of writes together with BaseAction.bulkAction
85
+ // note that only editable fields in the builder can be passed here
86
+ function getSimpleEditAction(viewer, builderCtr, existingEnt, input) {
87
+ return new BaseAction(viewer, builderCtr, {
88
+ existingEnt: existingEnt,
89
+ operation: action_1.WriteOperation.Edit,
90
+ input,
91
+ });
92
+ }
93
+ exports.getSimpleEditAction = getSimpleEditAction;
package/action/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { WriteOperation, Builder, Executor, Changeset, Trigger, Observer, Validator, Action, saveBuilder, saveBuilderX, setEdgeTypeInGroup, TriggerReturn, } from "./action";
1
+ export { WriteOperation, Builder, Changeset, Trigger, Observer, Validator, Action, saveBuilder, saveBuilderX, setEdgeTypeInGroup, TriggerReturn, } from "./action";
2
2
  export { OrchestratorOptions, Orchestrator, EntChangeset, EdgeInputData, } from "./orchestrator";
3
3
  export { DenyIfBuilder, AllowIfBuilder } from "./privacy";
@@ -1,7 +1,7 @@
1
1
  import { ID, Data, Ent, Viewer, EntConstructor, LoadEntOptions } from "../core/base";
2
2
  import { AssocEdgeInputOptions, DataOperation } from "../core/ent";
3
3
  import { SchemaInputType } from "../schema/schema";
4
- import { Changeset, Executor } from "../action";
4
+ import { Changeset, Executor } from "../action/action";
5
5
  import { WriteOperation, Builder, Action } from "../action";
6
6
  export interface OrchestratorOptions<T extends Ent, TData extends Data> {
7
7
  viewer: Viewer;
@@ -59,12 +59,14 @@ export declare class Orchestrator<T extends Ent> {
59
59
  private validators;
60
60
  private isBuilder;
61
61
  private getFieldsWithDefaultValues;
62
+ private hasData;
63
+ private transformFieldValue;
62
64
  private formatAndValidateFields;
63
65
  valid(): Promise<boolean>;
64
66
  validX(): Promise<void>;
65
67
  build(): Promise<EntChangeset<T>>;
66
68
  private viewerForEntLoad;
67
- returnedRow(): Promise<Data | null>;
69
+ private returnedRow;
68
70
  editedEnt(): Promise<T | null>;
69
71
  editedEntX(): Promise<T>;
70
72
  }
@@ -73,10 +75,11 @@ export declare class EntChangeset<T extends Ent> implements Changeset<T> {
73
75
  readonly placeholderID: ID;
74
76
  readonly ent: EntConstructor<T>;
75
77
  operations: DataOperation[];
76
- dependencies?: Map<ID, Builder<T>> | undefined;
78
+ dependencies?: Map<ID, Builder<Ent>> | undefined;
77
79
  changesets?: Changeset<Ent>[] | undefined;
78
80
  private options?;
79
- constructor(viewer: Viewer, placeholderID: ID, ent: EntConstructor<T>, operations: DataOperation[], dependencies?: Map<ID, Builder<T>> | undefined, changesets?: Changeset<Ent>[] | undefined, options?: OrchestratorOptions<T, Data> | undefined);
81
+ private _executor;
82
+ constructor(viewer: Viewer, placeholderID: ID, ent: EntConstructor<T>, operations: DataOperation[], dependencies?: Map<ID, Builder<Ent>> | undefined, changesets?: Changeset<Ent>[] | undefined, options?: OrchestratorOptions<T, Data> | undefined);
80
83
  executor(): Executor;
81
84
  }
82
85
  export {};
@@ -146,6 +146,8 @@ class Orchestrator {
146
146
  tableName: this.options.tableName,
147
147
  fieldsToResolve: this.fieldsToResolve,
148
148
  key: this.options.key,
149
+ ent: this.options.loaderOptions.ent,
150
+ placeholderID: this.options.builder.placeholderID,
149
151
  };
150
152
  if (this.logValues) {
151
153
  opts.fieldsToLog = this.logValues;
@@ -301,6 +303,7 @@ class Orchestrator {
301
303
  getFieldsWithDefaultValues(builder, schemaFields, action) {
302
304
  const editedFields = this.options.editedFields();
303
305
  let data = {};
306
+ let defaultData = {};
304
307
  let input = action?.getInput() || {};
305
308
  let updateInput = false;
306
309
  for (const [fieldName, field] of schemaFields) {
@@ -328,23 +331,79 @@ class Orchestrator {
328
331
  // TODO special case this if this is the only thing changing and don't do the write.
329
332
  }
330
333
  }
331
- data[dbKey] = value;
334
+ if (value !== undefined) {
335
+ data[dbKey] = value;
336
+ }
332
337
  if (defaultValue !== undefined) {
333
338
  updateInput = true;
334
- data[dbKey] = defaultValue;
339
+ defaultData[dbKey] = defaultValue;
335
340
  this.defaultFieldsByFieldName[fieldName] = defaultValue;
336
341
  // TODO related to #510. we need this logic to be consistent so do this all in TypeScript or get it from go somehow
337
342
  this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
338
343
  }
339
344
  }
340
- if (updateInput && this.options.updateInput) {
341
- // this basically fixes #605. just needs to be exposed correctly
342
- this.options.updateInput(this.defaultFieldsByTSName);
345
+ // if there's data changing, add data
346
+ if (this.hasData(data)) {
347
+ data = {
348
+ ...data,
349
+ ...defaultData,
350
+ };
351
+ if (updateInput && this.options.updateInput) {
352
+ // this basically fixes #605. just needs to be exposed correctly
353
+ this.options.updateInput(this.defaultFieldsByTSName);
354
+ }
343
355
  }
344
356
  return data;
345
357
  }
358
+ hasData(data) {
359
+ for (const _k in data) {
360
+ return true;
361
+ }
362
+ return false;
363
+ }
364
+ async transformFieldValue(field, dbKey, value) {
365
+ // now format and validate...
366
+ if (value === null) {
367
+ if (!field.nullable) {
368
+ throw new Error(`field ${field.name} set to null for non-nullable field`);
369
+ }
370
+ }
371
+ else if (value === undefined) {
372
+ if (!field.nullable &&
373
+ // required field can be skipped if server default set
374
+ // not checking defaultValueOnCreate() or defaultValueOnEdit() as that's set above
375
+ // not setting server default as we're depending on the database handling that.
376
+ // server default allowed
377
+ field.serverDefault === undefined &&
378
+ this.options.operation === action_1.WriteOperation.Insert) {
379
+ throw new Error(`required field ${field.name} not set`);
380
+ }
381
+ }
382
+ else if (this.isBuilder(value)) {
383
+ let builder = value;
384
+ // keep track of dependencies to resolve
385
+ this.dependencies.set(builder.placeholderID, builder);
386
+ // keep track of fields to resolve
387
+ this.fieldsToResolve.push(dbKey);
388
+ }
389
+ else {
390
+ if (field.valid) {
391
+ // TODO this could be async. handle this better
392
+ let valid = await Promise.resolve(field.valid(value));
393
+ if (!valid) {
394
+ throw new Error(`invalid field ${field.name} with value ${value}`);
395
+ }
396
+ }
397
+ if (field.format) {
398
+ // TODO this could be async e.g. password. handle this better
399
+ value = await Promise.resolve(field.format(value));
400
+ }
401
+ }
402
+ return value;
403
+ }
346
404
  async formatAndValidateFields(schemaFields) {
347
- if (this.options.operation == action_1.WriteOperation.Delete) {
405
+ const op = this.options.operation;
406
+ if (op === action_1.WriteOperation.Delete) {
348
407
  return;
349
408
  }
350
409
  const editedFields = this.options.editedFields();
@@ -353,53 +412,32 @@ class Orchestrator {
353
412
  let logValues = {};
354
413
  for (const [fieldName, field] of schemaFields) {
355
414
  let value = editedFields.get(fieldName);
356
- if (value === undefined) {
415
+ if (value === undefined && op === action_1.WriteOperation.Insert) {
357
416
  // null allowed
358
417
  value = this.defaultFieldsByFieldName[fieldName];
359
418
  }
360
419
  let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
361
- // now format and validate...
362
- if (value === null) {
363
- if (!field.nullable) {
364
- throw new Error(`field ${field.name} set to null for non-nullable field`);
365
- }
366
- }
367
- else if (value === undefined) {
368
- if (!field.nullable &&
369
- // required field can be skipped if server default set
370
- // not checking defaultValueOnCreate() or defaultValueOnEdit() as that's set above
371
- // not setting server default as we're depending on the database handling that.
372
- // server default allowed
373
- field.serverDefault === undefined &&
374
- this.options.operation === action_1.WriteOperation.Insert) {
375
- throw new Error(`required field ${field.name} not set`);
376
- }
377
- }
378
- else if (this.isBuilder(value)) {
379
- let builder = value;
380
- // keep track of dependencies to resolve
381
- this.dependencies.set(builder.placeholderID, builder);
382
- // keep track of fields to resolve
383
- this.fieldsToResolve.push(dbKey);
384
- }
385
- else {
386
- if (field.valid) {
387
- // TODO this could be async. handle this better
388
- let valid = await Promise.resolve(field.valid(value));
389
- if (!valid) {
390
- throw new Error(`invalid field ${field.name} with value ${value}`);
391
- }
392
- }
393
- if (field.format) {
394
- // TODO this could be async e.g. password. handle this better
395
- value = await Promise.resolve(field.format(value));
396
- }
397
- }
420
+ value = await this.transformFieldValue(field, dbKey, value);
398
421
  if (value !== undefined) {
399
422
  data[dbKey] = value;
400
423
  logValues[dbKey] = field.logValue(value);
401
424
  }
402
425
  }
426
+ // we ignored default values while editing.
427
+ // if we're editing and there's data, add default values
428
+ if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
429
+ for (const fieldName in this.defaultFieldsByFieldName) {
430
+ const defaultValue = this.defaultFieldsByFieldName[fieldName];
431
+ let field = schemaFields.get(fieldName);
432
+ let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
433
+ // no value, let's just default
434
+ if (data[dbKey] === undefined) {
435
+ const value = await this.transformFieldValue(field, dbKey, defaultValue);
436
+ data[dbKey] = value;
437
+ logValues[dbKey] = field.logValue(value);
438
+ }
439
+ }
440
+ }
403
441
  this.validatedFields = data;
404
442
  this.logValues = logValues;
405
443
  }
@@ -432,8 +470,8 @@ class Orchestrator {
432
470
  return action.viewerForEntLoad(data);
433
471
  }
434
472
  async returnedRow() {
435
- if (this.mainOp && this.mainOp.returnedEntRow) {
436
- return this.mainOp.returnedEntRow();
473
+ if (this.mainOp && this.mainOp.returnedRow) {
474
+ return this.mainOp.returnedRow();
437
475
  }
438
476
  return null;
439
477
  }
@@ -475,13 +513,17 @@ class EntChangeset {
475
513
  this.options = options;
476
514
  }
477
515
  executor() {
478
- // TODO: write comment here similar to go
479
- // if we have dependencies but no changesets, we just need a simple
480
- // executor and depend on something else in the stack to handle this correctly
481
- if (this.changesets?.length) {
482
- return new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.ent, this.operations, this.dependencies, this.changesets, this.options);
516
+ if (this._executor) {
517
+ return this._executor;
518
+ }
519
+ if (!this.changesets?.length) {
520
+ // if we have dependencies but no changesets, we just need a simple
521
+ // executor and depend on something else in the stack to handle this correctly
522
+ // ComplexExecutor which could be a parent of this should make sure the dependency
523
+ // is resolved beforehand
524
+ return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options));
483
525
  }
484
- return new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.ent, this.operations, this.options);
526
+ return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options));
485
527
  }
486
528
  }
487
529
  exports.EntChangeset = EntChangeset;
package/core/clause.js CHANGED
@@ -177,7 +177,7 @@ function Or(...args) {
177
177
  return new compositeClause(args, " OR ");
178
178
  }
179
179
  exports.Or = Or;
180
- // todo?
180
+ // TODO this breaks if values.length ===1 and array. todo fix
181
181
  function In(col, ...values) {
182
182
  return new inClause(col, values);
183
183
  }
package/core/ent.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Queryer, SyncQueryer } from "./db";
2
- import { Viewer, Ent, ID, LoadRowsOptions, LoadRowOptions, Data, DataOptions, QueryableDataOptions, EditRowOptions, LoadEntOptions, LoadCustomEntOptions, EdgeQueryableDataOptions, Context, SelectBaseDataOptions, CreateRowOptions, QueryDataOptions } from "./base";
3
- import { Executor } from "../action";
2
+ import { Viewer, Ent, ID, LoadRowsOptions, LoadRowOptions, Data, DataOptions, QueryableDataOptions, EditRowOptions, LoadEntOptions, LoadCustomEntOptions, EdgeQueryableDataOptions, Context, SelectBaseDataOptions, CreateRowOptions, QueryDataOptions, EntConstructor } from "./base";
3
+ import { Executor } from "../action/action";
4
4
  import * as clause from "./clause";
5
5
  import { Builder } from "../action";
6
6
  import DataLoader from "dataloader";
@@ -39,27 +39,34 @@ interface GroupQueryOptions {
39
39
  limit: number;
40
40
  }
41
41
  export declare function buildGroupQuery(options: GroupQueryOptions): [string, clause.Clause];
42
- export interface DataOperation {
42
+ export interface DataOperation<T extends Ent = Ent> {
43
43
  preFetch?(queryer: Queryer, context?: Context): Promise<void>;
44
44
  performWriteSync(queryer: SyncQueryer, context?: Context): void;
45
45
  performWrite(queryer: Queryer, context?: Context): Promise<void>;
46
- returnedEntRow?(): Data | null;
46
+ placeholderID?: ID;
47
+ returnedRow?(): Data | null;
48
+ createdEnt?(viewer: Viewer): T | null;
47
49
  resolve?(executor: Executor): void;
48
50
  postFetch?(queryer: Queryer, context?: Context): Promise<void>;
49
51
  }
50
- export interface EditNodeOptions extends EditRowOptions {
52
+ export interface EditNodeOptions<T extends Ent> extends EditRowOptions {
51
53
  fieldsToResolve: string[];
54
+ ent: EntConstructor<T>;
55
+ placeholderID?: ID;
52
56
  }
53
- export declare class EditNodeOperation implements DataOperation {
54
- options: EditNodeOptions;
57
+ export declare class EditNodeOperation<T extends Ent> implements DataOperation {
58
+ options: EditNodeOptions<T>;
55
59
  private existingEnt;
56
60
  row: Data | null;
57
- constructor(options: EditNodeOptions, existingEnt?: Ent | null);
61
+ placeholderID?: ID | undefined;
62
+ constructor(options: EditNodeOptions<T>, existingEnt?: Ent | null);
58
63
  resolve<T extends Ent>(executor: Executor): void;
64
+ private hasData;
59
65
  performWrite(queryer: Queryer, context?: Context): Promise<void>;
60
- reloadRow(queryer: SyncQueryer, id: ID, options: EditNodeOptions): void;
66
+ private reloadRow;
61
67
  performWriteSync(queryer: SyncQueryer, context?: Context): void;
62
- returnedEntRow(): Data | null;
68
+ returnedRow(): Data | null;
69
+ createdEnt(viewer: Viewer): T | null;
63
70
  }
64
71
  export declare class EdgeOperation implements DataOperation {
65
72
  edgeInput: AssocEdgeInput;
package/core/ent.js CHANGED
@@ -401,6 +401,7 @@ class EditNodeOperation {
401
401
  constructor(options, existingEnt = null) {
402
402
  this.options = options;
403
403
  this.existingEnt = existingEnt;
404
+ this.placeholderID = options.placeholderID;
404
405
  }
405
406
  resolve(executor) {
406
407
  if (!this.options.fieldsToResolve.length) {
@@ -420,13 +421,24 @@ class EditNodeOperation {
420
421
  });
421
422
  this.options.fields = fields;
422
423
  }
424
+ hasData(data) {
425
+ for (const _k in data) {
426
+ return true;
427
+ }
428
+ return false;
429
+ }
423
430
  async performWrite(queryer, context) {
424
431
  let options = {
425
432
  ...this.options,
426
433
  context,
427
434
  };
428
435
  if (this.existingEnt) {
429
- this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
436
+ if (this.hasData(options.fields)) {
437
+ this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
438
+ }
439
+ else {
440
+ this.row = this.existingEnt["data"];
441
+ }
430
442
  }
431
443
  else {
432
444
  this.row = await createRow(queryer, options, "RETURNING *");
@@ -454,8 +466,13 @@ class EditNodeOperation {
454
466
  context,
455
467
  };
456
468
  if (this.existingEnt) {
457
- editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
458
- this.reloadRow(queryer, this.existingEnt.id, options);
469
+ if (this.hasData(this.options.fields)) {
470
+ editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
471
+ this.reloadRow(queryer, this.existingEnt.id, options);
472
+ }
473
+ else {
474
+ this.row = this.existingEnt["data"];
475
+ }
459
476
  }
460
477
  else {
461
478
  createRowSync(queryer, options, "RETURNING *");
@@ -463,9 +480,15 @@ class EditNodeOperation {
463
480
  this.reloadRow(queryer, id, options);
464
481
  }
465
482
  }
466
- returnedEntRow() {
483
+ returnedRow() {
467
484
  return this.row;
468
485
  }
486
+ createdEnt(viewer) {
487
+ if (!this.row) {
488
+ return null;
489
+ }
490
+ return new this.options.ent(viewer, this.row);
491
+ }
469
492
  }
470
493
  exports.EditNodeOperation = EditNodeOperation;
471
494
  class EdgeOperation {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowtop/ent",
3
- "version": "0.0.28",
3
+ "version": "0.0.31",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -168,6 +168,10 @@ export interface Action {
168
168
  graphQLName?: string;
169
169
  hideFromGraphQL?: boolean;
170
170
  actionOnlyFields?: ActionField[];
171
+ excludedFields?: string[];
172
+ optionalFields?: string[];
173
+ requiredFields?: string[];
174
+ noFields?: boolean;
171
175
  }
172
176
  export declare const NoFields = "__NO_FIELDS__";
173
177
  export declare function requiredField(field: string): string;
File without changes
File without changes
@@ -66,12 +66,12 @@ export declare class SimpleBuilder<T extends Ent> implements Builder<T> {
66
66
  viewer: Viewer;
67
67
  private schema;
68
68
  operation: WriteOperation;
69
- existingEnt: Ent | undefined;
69
+ existingEnt: T | undefined;
70
70
  ent: EntConstructor<T>;
71
71
  placeholderID: ID;
72
72
  orchestrator: Orchestrator<T>;
73
73
  fields: Map<string, any>;
74
- constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation?: WriteOperation, existingEnt?: Ent | undefined, action?: Action<T> | undefined);
74
+ constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation?: WriteOperation, existingEnt?: T | undefined, action?: Action<T> | undefined);
75
75
  build(): Promise<Changeset<T>>;
76
76
  editedEnt(): Promise<T | null>;
77
77
  editedEntX(): Promise<T>;
@@ -93,12 +93,13 @@ export declare class SimpleAction<T extends Ent> implements Action<T> {
93
93
  viewerForEntLoad: viewerEntLoadFunc | undefined;
94
94
  constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation?: WriteOperation, existingEnt?: T | undefined);
95
95
  getPrivacyPolicy(): import("../core/base").PrivacyPolicy;
96
- getInput(): Map<string, any>;
96
+ getInput(): Data;
97
97
  changeset(): Promise<Changeset<T>>;
98
98
  valid(): Promise<boolean>;
99
99
  validX(): Promise<void>;
100
100
  save(): Promise<T | null>;
101
101
  saveX(): Promise<T>;
102
102
  editedEnt(): Promise<T | null>;
103
+ editedEntX(): Promise<T>;
103
104
  }
104
105
  export {};
@@ -185,7 +185,11 @@ class SimpleAction {
185
185
  return privacy_1.AlwaysAllowPrivacyPolicy;
186
186
  }
187
187
  getInput() {
188
- return this.fields;
188
+ const ret = {};
189
+ for (const [k, v] of this.fields) {
190
+ ret[k] = v;
191
+ }
192
+ return ret;
189
193
  }
190
194
  changeset() {
191
195
  return this.builder.build();
@@ -199,16 +203,19 @@ class SimpleAction {
199
203
  async save() {
200
204
  await (0, action_1.saveBuilder)(this.builder);
201
205
  if (this.builder.operation !== action_1.WriteOperation.Delete) {
202
- return await this.builder.orchestrator.editedEnt();
206
+ return this.builder.orchestrator.editedEnt();
203
207
  }
204
208
  return null;
205
209
  }
206
210
  async saveX() {
207
211
  await (0, action_1.saveBuilderX)(this.builder);
208
- return await this.builder.orchestrator.editedEntX();
212
+ return this.builder.orchestrator.editedEntX();
209
213
  }
210
214
  async editedEnt() {
211
- return await this.builder.orchestrator.editedEnt();
215
+ return this.builder.orchestrator.editedEnt();
216
+ }
217
+ async editedEntX() {
218
+ return this.builder.orchestrator.editedEntX();
212
219
  }
213
220
  }
214
221
  exports.SimpleAction = SimpleAction;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.QueryRecorder = exports.queryType = void 0;
4
4
  const uuid_1 = require("uuid");
5
- const utils_1 = require("ts-jest/utils");
5
+ const jest_mock_1 = require("jest-mock");
6
6
  const parse_sql_1 = require("./parse_sql");
7
7
  const eventEmitter = {
8
8
  on: jest.fn(),
@@ -185,7 +185,7 @@ class QueryRecorder {
185
185
  }
186
186
  }
187
187
  static mockPool(pool) {
188
- const mockedPool = (0, utils_1.mocked)(pool, true);
188
+ const mockedPool = (0, jest_mock_1.mocked)(pool, true);
189
189
  mockedPool.mockImplementation(() => {
190
190
  return {
191
191
  totalCount: 1,
@@ -286,11 +286,11 @@ async function expectFromRoot(config, ...options) {
286
286
  let fields = query?.getFields();
287
287
  if (!fields) {
288
288
  // TODO custom error?
289
- fail("schema doesn't have query or fields");
289
+ throw new Error("schema doesn't have query or fields");
290
290
  }
291
291
  let field = fields[config.root];
292
292
  if (!field) {
293
- fail(`could not find field ${config.root} in GraphQL query schema`);
293
+ throw new Error(`could not find field ${config.root} in GraphQL query schema`);
294
294
  }
295
295
  let fieldArgs = field.args;
296
296
  let queryParams = [];
@@ -361,7 +361,7 @@ async function expectFromRoot(config, ...options) {
361
361
  expect(errors[0].message).toMatch(config.expectedError);
362
362
  }
363
363
  else {
364
- fail(`unhandled error ${JSON.stringify(errors)}`);
364
+ throw new Error(`unhandled error ${JSON.stringify(errors)}`);
365
365
  }
366
366
  return st;
367
367
  }
@@ -416,7 +416,7 @@ async function expectFromRoot(config, ...options) {
416
416
  if (idx !== -1) {
417
417
  let endIdx = part.indexOf("]");
418
418
  if (endIdx === -1) {
419
- fail("can't have a beginning index without an end index");
419
+ throw new Error("can't have a beginning index without an end index");
420
420
  }
421
421
  // get the idx we care about
422
422
  listIdx = parseInt(part.substr(idx + 1, endIdx - idx), 10);
@@ -428,7 +428,7 @@ async function expectFromRoot(config, ...options) {
428
428
  if (idx !== -1) {
429
429
  let endIdx = part.indexOf(")");
430
430
  if (endIdx === -1) {
431
- fail("can't have a beginning index without an end index");
431
+ throw new Error("can't have a beginning index without an end index");
432
432
  }
433
433
  // update part
434
434
  part = part.substr(0, idx);
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAllEvents = exports.tempDBTables = exports.setupTempDB = exports.createTestEvent = exports.edgeTableNames = exports.createEdges = exports.verifyUserToContacts = exports.verifyUserToContactRawData = exports.verifyUserToContactEdges = exports.addEdge = exports.createUserPlusFriendRequests = exports.createAllContacts = exports.inputs = exports.createTestUser = exports.getEventInput = exports.getUserInput = exports.getContactInput = void 0;
4
- const assert_1 = require("assert");
5
4
  const jest_date_mock_1 = require("jest-date-mock");
6
5
  const viewer_1 = require("../../core/viewer");
7
6
  const ent_1 = require("../../core/ent");
@@ -56,7 +55,7 @@ async function createTestUser(input) {
56
55
  ...input,
57
56
  });
58
57
  if (!user) {
59
- (0, assert_1.fail)("error creating user");
58
+ throw new Error("error creating user");
60
59
  }
61
60
  return user;
62
61
  }
@@ -74,7 +74,7 @@ function getColumns(cols) {
74
74
  count = true;
75
75
  }
76
76
  else {
77
- fail("unsupported expr type");
77
+ throw new Error("unsupported expr type");
78
78
  }
79
79
  }
80
80
  }