@coderich/autograph 0.9.16 → 0.10.2

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 (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/index.js +2 -6
  3. package/package.json +10 -10
  4. package/src/.DS_Store +0 -0
  5. package/src/core/EventEmitter.js +2 -4
  6. package/src/core/Resolver.js +35 -58
  7. package/src/core/Schema.js +5 -38
  8. package/src/core/ServerResolver.js +7 -93
  9. package/src/data/DataLoader.js +68 -27
  10. package/src/data/DataService.js +59 -58
  11. package/src/data/Field.js +71 -96
  12. package/src/data/Model.js +95 -113
  13. package/src/data/Pipeline.js +174 -0
  14. package/src/data/Type.js +19 -60
  15. package/src/driver/MongoDriver.js +53 -28
  16. package/src/graphql/ast/Field.js +44 -26
  17. package/src/graphql/ast/Model.js +5 -16
  18. package/src/graphql/ast/Node.js +0 -32
  19. package/src/graphql/ast/Schema.js +109 -112
  20. package/src/graphql/extension/api.js +22 -35
  21. package/src/graphql/extension/framework.js +25 -33
  22. package/src/graphql/extension/type.js +2 -2
  23. package/src/query/Query.js +73 -15
  24. package/src/query/QueryBuilder.js +37 -28
  25. package/src/query/QueryBuilderTransaction.js +3 -3
  26. package/src/query/QueryResolver.js +93 -44
  27. package/src/query/QueryService.js +31 -34
  28. package/src/service/app.service.js +56 -35
  29. package/src/service/decorator.service.js +21 -288
  30. package/src/service/event.service.js +5 -79
  31. package/src/service/graphql.service.js +0 -9
  32. package/src/service/schema.service.js +5 -3
  33. package/src/core/Rule.js +0 -107
  34. package/src/core/SchemaDecorator.js +0 -46
  35. package/src/core/Transformer.js +0 -68
  36. package/src/data/Memoizer.js +0 -39
  37. package/src/data/ResultSet.js +0 -246
  38. package/src/graphql/ast/SchemaDecorator.js +0 -138
  39. package/src/graphql/directive/authz.directive.js +0 -84
@@ -1,15 +1,21 @@
1
1
  const Boom = require('../core/Boom');
2
- const { unravelObject } = require('../service/app.service');
3
2
 
4
3
  module.exports = class Query {
5
4
  constructor(props = {}) {
6
5
  this.props = {};
7
6
  this.timers = {};
7
+ this.isCursorPaging = false;
8
+ this.isClassicPaging = false;
8
9
  this.props.joins = this.props.joins || [];
9
10
  this.props.match = this.props.match || {};
10
11
  this.props.options = this.props.options || {};
11
- this.isClassicPaging = false;
12
- this.isCursorPaging = false;
12
+ this.props.flags = this.props.flags || {
13
+ debug: false,
14
+ silent: false,
15
+ validate: true, // { externals }
16
+ pipeline: true, // { instruct, construct, destruct, restruct, serialize, deserialize, transform, rekey }
17
+ required: false,
18
+ };
13
19
  this.merge(props);
14
20
  }
15
21
 
@@ -22,12 +28,19 @@ module.exports = class Query {
22
28
  id(id) {
23
29
  this.propCheck('id', 'where', 'native', 'sort', 'skip', 'limit', 'before', 'after', 'first', 'last');
24
30
  this.props.id = id;
31
+ this.props.batch = 'id';
32
+ this.props.where = { id };
25
33
  return this.match({ id });
26
34
  }
27
35
 
36
+ batch(batch) {
37
+ this.props.batch = batch;
38
+ return this;
39
+ }
40
+
28
41
  where(where) {
29
42
  this.propCheck('where', 'id', 'native');
30
- this.props.where = unravelObject(where);
43
+ this.props.where = where;
31
44
  return this.match(where);
32
45
  }
33
46
 
@@ -44,12 +57,12 @@ module.exports = class Query {
44
57
  }
45
58
 
46
59
  match(match) {
47
- this.props.match = unravelObject(match);
60
+ this.props.match = match;
48
61
  return this;
49
62
  }
50
63
 
51
64
  select(select) {
52
- this.props.select = unravelObject(select);
65
+ this.props.select = select;
53
66
  return this;
54
67
  }
55
68
 
@@ -70,7 +83,7 @@ module.exports = class Query {
70
83
 
71
84
  sort(sort) {
72
85
  this.propCheck('sort', 'id');
73
- this.props.sort = unravelObject(sort);
86
+ this.props.sort = sort;
74
87
  return this;
75
88
  }
76
89
 
@@ -142,7 +155,7 @@ module.exports = class Query {
142
155
  }
143
156
 
144
157
  flags(flags) {
145
- this.props.flags = flags;
158
+ Object.assign(this.props.flags, flags);
146
159
  return this;
147
160
  }
148
161
 
@@ -161,6 +174,12 @@ module.exports = class Query {
161
174
 
162
175
  resolver(resolver) {
163
176
  this.props.resolver = resolver;
177
+ this.props.context = resolver.getContext();
178
+ return this;
179
+ }
180
+
181
+ context(context) {
182
+ this.props.context = context;
164
183
  return this;
165
184
  }
166
185
 
@@ -175,7 +194,7 @@ module.exports = class Query {
175
194
  }
176
195
 
177
196
  cmd(cmd) {
178
- this.props.cmd = cmd;
197
+ this.props.cmd = cmd; // Terminal cmd from QueryBuilder
179
198
  return this;
180
199
  }
181
200
 
@@ -185,14 +204,32 @@ module.exports = class Query {
185
204
  switch (method) {
186
205
  case 'createOne': case 'createMany': {
187
206
  this.props.crud = 'create';
207
+ this.props.key = `create${this.props.model}`;
188
208
  break;
189
209
  }
190
210
  case 'updateOne': case 'updateMany': {
191
211
  this.props.crud = 'update';
212
+ this.props.key = `update${this.props.model}`;
192
213
  break;
193
214
  }
194
- case 'deleteOne': case 'deleteMay': case 'removeOne': case 'removeMany': {
215
+ case 'deleteOne': case 'deleteMany': case 'removeOne': case 'removeMany': {
195
216
  this.props.crud = 'delete';
217
+ this.props.key = `delete${this.props.model}`;
218
+ break;
219
+ }
220
+ case 'count': {
221
+ this.props.crud = 'read';
222
+ this.props.key = `count${this.props.model}`;
223
+ break;
224
+ }
225
+ case 'findOne': {
226
+ this.props.crud = 'read';
227
+ this.props.key = `get${this.props.model}`;
228
+ break;
229
+ }
230
+ case 'findMany': {
231
+ this.props.crud = 'read';
232
+ this.props.key = `find${this.props.model}`;
196
233
  break;
197
234
  }
198
235
  default: {
@@ -209,6 +246,11 @@ module.exports = class Query {
209
246
  return this;
210
247
  }
211
248
 
249
+ key(key) {
250
+ this.props.key = key;
251
+ return this;
252
+ }
253
+
212
254
  input(input = {}) { // Allows .save(/* empty */);
213
255
  // delete input.id; // We do not want to allow changing id via input
214
256
  this.props.input = input;
@@ -241,17 +283,20 @@ module.exports = class Query {
241
283
  }
242
284
 
243
285
  clone() {
244
- return new Query({ ...this.props });
286
+ const clone = new Query();
287
+ clone.props = { ...this.props };
288
+ return clone;
245
289
  }
246
290
 
247
291
  toDriver() {
292
+ const self = this;
248
293
  const { model } = this.props;
249
294
  const isSorted = Boolean(Object.keys(this.props.$sort || {}).length);
250
295
 
251
296
  return {
252
297
  isNative: Boolean(this.props.native),
253
298
  model: model.getKey(),
254
- shape: model.getShape('deserialize', false),
299
+ shape: model.getShape(),
255
300
  method: this.props.method,
256
301
  select: this.props.$select,
257
302
  joins: this.props.joins,
@@ -261,9 +306,22 @@ module.exports = class Query {
261
306
  skip: this.props.skip,
262
307
  limit: this.props.limit,
263
308
  first: isSorted ? this.props.first : undefined,
264
- after: isSorted && this.props.after ? model.normalize(this, JSON.parse(Buffer.from(this.props.after, 'base64').toString('ascii')), 'serialize') : undefined,
265
309
  last: isSorted ? this.props.last : undefined,
266
- before: isSorted && this.props.before ? model.normalize(this, JSON.parse(Buffer.from(this.props.before, 'base64').toString('ascii')), 'serialize') : undefined,
310
+ // before: isSorted && this.props.before ? model.normalize(this, JSON.parse(Buffer.from(this.props.before, 'base64').toString('ascii')), 'serialize') : undefined,
311
+ get before() {
312
+ if (!isSorted || !self.props.before) return undefined;
313
+ const shape = model.getShape('create', 'sort');
314
+ const before = JSON.parse(Buffer.from(self.props.before, 'base64').toString('ascii'));
315
+ const $before = model.shapeObject(shape, before, self);
316
+ return $before;
317
+ },
318
+ get after() {
319
+ if (!isSorted || !self.props.after) return undefined;
320
+ const shape = model.getShape('create', 'sort');
321
+ const after = JSON.parse(Buffer.from(self.props.after, 'base64').toString('ascii'));
322
+ const $after = model.shapeObject(shape, after, self);
323
+ return $after;
324
+ },
267
325
  options: this.props.options,
268
326
  input: this.props.$input,
269
327
  flags: this.props.flags,
@@ -278,7 +336,7 @@ module.exports = class Query {
278
336
 
279
337
  getCacheKey() {
280
338
  return {
281
- // model: `${this.props.model}`,
339
+ cmd: this.props.cmd,
282
340
  method: this.props.method,
283
341
  where: this.props.match,
284
342
  search: this.props.search,
@@ -6,7 +6,7 @@ const { unravelObject } = require('../service/app.service');
6
6
  * QueryBuilder
7
7
  *
8
8
  * Facilitates the creation and execution of a Query. It provides a chainable API to build a query
9
- * plus a list of terminal commands to resolve the query.
9
+ * plus a list of terminal commands to execute the query.
10
10
  */
11
11
  module.exports = class QueryBuilder {
12
12
  constructor(resolver, model) {
@@ -14,12 +14,13 @@ module.exports = class QueryBuilder {
14
14
 
15
15
  // Chainable commands
16
16
  this.id = (id) => { this.query.id(id); return this; };
17
- this.select = (select) => { this.query.select(select); return this; };
18
- this.where = (where) => { this.query.where(where); return this; };
19
- this.match = (match) => { this.query.match(match); return this; };
17
+ this.select = (select) => { this.query.select(unravelObject(select)); return this; };
18
+ // this.select = (select) => { this.query.select(Object.entries(toKeyObj(select)).reduce((prev, [key, value]) => Object.assign(prev, { [key.replace(/edges.node./g, '')]: !!value }), {})); return this; };
19
+ this.where = (where) => { this.query.where(unravelObject(where)); return this; };
20
+ this.match = (match) => { this.query.match(unravelObject(match)); return this; };
20
21
  this.native = (native) => { this.query.native(native); return this; };
21
- this.sort = (sort) => { this.query.sort(sort); return this; };
22
- this.sortBy = (sortBy) => { this.query.sort(sortBy); return this; };
22
+ this.sort = (sort) => { this.query.sort(unravelObject(sort)); return this; };
23
+ this.sortBy = (sortBy) => { this.query.sort(unravelObject(sortBy)); return this; };
23
24
  this.limit = (limit) => { this.query.limit(limit); return this; };
24
25
  this.skip = (skip) => { this.query.skip(skip); return this; };
25
26
  this.before = (cursor) => { this.query.before(cursor); return this; };
@@ -27,64 +28,72 @@ module.exports = class QueryBuilder {
27
28
  this.meta = (meta) => { this.query.meta(meta); return this; };
28
29
  this.flags = (flags) => { this.query.flags(flags); return this; };
29
30
  this.merge = (merge) => { this.query.merge(merge); return this; };
31
+ this.batch = (batch) => { this.query.batch(batch); return this; };
30
32
  this.transaction = (txn) => { this.query.transaction(txn); return this; };
31
33
 
32
34
  // Terminal commands
33
- this.one = (...args) => this.resolve('one', args);
34
- this.many = (...args) => this.resolve('many', args);
35
- this.save = (...args) => this.resolve('save', args.map(arg => unravelObject(arg)));
36
- this.delete = (...args) => this.resolve('delete', args);
37
- this.remove = (...args) => this.resolve('remove', args);
35
+ this.one = (...args) => this.execute('one', args);
36
+ this.many = (...args) => this.execute('many', args);
37
+ this.save = (...args) => this.execute('save', args.map(arg => unravelObject(arg)));
38
+ this.delete = (...args) => this.execute('delete', args);
39
+ this.remove = (...args) => this.execute('remove', args);
40
+ this.resolve = (...args) => this.execute('resolve', args);
38
41
  //
39
- this.count = (...args) => this.resolve('count', args);
40
- this.push = (...args) => this.resolve('push', args.map(arg => unravelObject(arg)));
41
- this.pull = (...args) => this.resolve('pull', args.map(arg => unravelObject(arg)));
42
- this.splice = (...args) => this.resolve('splice', args.map(arg => unravelObject(arg)));
43
- this.first = (...args) => { this.query.first(...args); return this.resolve('first', args); };
44
- this.last = (...args) => { this.query.last(...args); return this.resolve('last', args); };
42
+ this.count = (...args) => this.execute('count', args);
43
+ this.push = (...args) => this.execute('push', args.map(arg => unravelObject(arg)));
44
+ this.pull = (...args) => this.execute('pull', args.map(arg => unravelObject(arg)));
45
+ this.splice = (...args) => this.execute('splice', args.map(arg => unravelObject(arg)));
46
+ this.first = (...args) => { this.query.first(...args); return this.execute('first', args); };
47
+ this.last = (...args) => { this.query.last(...args); return this.execute('last', args); };
45
48
  //
46
49
  // this.min = (...args) => this.makeTheCall(query, 'min', args);
47
50
  // this.max = (...args) => this.makeTheCall(query, 'max', args);
48
51
  // this.avg = (...args) => this.makeTheCall(query, 'avg', args);
52
+ // this.sum = (...args) => this.makeTheCall(query, 'sum', args); // Would sum be different than count?
49
53
  // // Food for thought...
50
54
  // this.archive = (...args) => this.makeTheCall(query, 'archive', args); // Soft Delete
51
55
  // this.stream = (...args) => this.makeTheCall(query, 'stream', args); // Stream records 1 by 1
52
- // this.sum = (...args) => this.makeTheCall(query, 'sum', args); // Would sum be different than count?
53
56
  // this.rollup = (...args) => this.makeTheCall(query, 'rollup', args); // Like sum, but for nested attributes (eg. Person.rollupAuthoredChaptersPages)
54
57
  }
55
58
 
56
- resolve(cmd, args) {
57
- let method, crud, input = {};
58
- let { flags = {} } = this.query.toObject();
59
+ execute(cmd, args) {
60
+ let method, crud, input, flags = {};
59
61
  const { id, where } = this.query.toObject();
60
62
 
61
63
  switch (cmd) {
64
+ case 'resolve': {
65
+ crud = 'read';
66
+ method = 'autoResolve';
67
+ break;
68
+ }
62
69
  case 'one': case 'many': {
63
70
  crud = 'read';
64
- flags = args[0] || flags;
71
+ [flags] = args;
65
72
  method = cmd === 'one' ? 'findOne' : 'findMany';
66
73
  break;
67
74
  }
68
75
  case 'first': case 'last': {
69
76
  crud = 'read';
70
- flags = args[1] || flags;
77
+ [, flags] = args;
71
78
  method = cmd;
72
79
  break;
73
80
  }
74
81
  case 'save': {
82
+ [input, flags] = args;
75
83
  crud = id || where ? 'update' : 'create';
76
- if (crud === 'update') { method = id ? 'updateOne' : 'updateMany'; [input] = args; }
77
- if (crud === 'create') { method = args.length < 2 ? 'createOne' : 'createMany'; input = args.length < 2 ? args[0] || {} : args; }
84
+ if (crud === 'update') { method = id ? 'updateOne' : 'updateMany'; }
85
+ if (crud === 'create') { method = Array.isArray(input) ? 'createMany' : 'createOne'; }
78
86
  break;
79
87
  }
80
88
  case 'push': case 'pull': case 'splice': {
89
+ // [target, input, flags] = args;
81
90
  crud = 'update'; // Your logic wants this to be a simple "update". Sub documents systemEvents will emit either "create" or "udpate"
82
91
  method = id ? `${cmd}One` : `${cmd}Many`;
83
92
  break;
84
93
  }
85
94
  case 'remove': case 'delete': {
86
95
  crud = 'delete';
87
- flags = args[0] || flags;
96
+ [flags] = args;
88
97
  if (id) method = 'deleteOne';
89
98
  else if (where) method = 'deleteMany';
90
99
  else return Promise.reject(new Error('Remove requires an id() or where()'));
@@ -92,7 +101,7 @@ module.exports = class QueryBuilder {
92
101
  }
93
102
  case 'count': {
94
103
  crud = 'read';
95
- flags = args[0] || flags;
104
+ [flags] = args;
96
105
  method = 'count';
97
106
  break;
98
107
  }
@@ -101,6 +110,6 @@ module.exports = class QueryBuilder {
101
110
  }
102
111
  }
103
112
 
104
- return new QueryResolver(this.query.cmd(cmd).method(method).crud(crud).input(input).flags(flags).args(args)).resolve();
113
+ return new QueryResolver(this.query.method(method).cmd(cmd).crud(crud).input(input).flags(flags).args(args)).resolve();
105
114
  }
106
115
  };
@@ -6,7 +6,7 @@ module.exports = class QueryBuilderTransaction extends QueryBuilder {
6
6
  this.query.transaction(transaction);
7
7
  }
8
8
 
9
- resolve(cmd, args) {
9
+ execute(cmd, args) {
10
10
  return new Promise((resolve, reject) => {
11
11
  this.theCall = { cmd, args, resolve, reject };
12
12
  });
@@ -15,10 +15,10 @@ module.exports = class QueryBuilderTransaction extends QueryBuilder {
15
15
  exec(options) {
16
16
  if (!this.theCall) return undefined;
17
17
 
18
- this.query.options(options);
19
18
  const { cmd, args, resolve } = this.theCall;
19
+ this.query.options(options);
20
20
 
21
- return super.resolve(cmd, args).then((result) => {
21
+ return super.execute(cmd, args).then((result) => {
22
22
  resolve(result);
23
23
  return result;
24
24
  });
@@ -1,78 +1,110 @@
1
- const { get, isEmpty } = require('lodash');
1
+ const { get, set, isEmpty } = require('lodash');
2
2
  const Boom = require('../core/Boom');
3
3
  const QueryService = require('./QueryService');
4
4
  const DataService = require('../data/DataService');
5
5
  const { createSystemEvent } = require('../service/event.service');
6
- const { mergeDeep } = require('../service/app.service');
6
+ const { mergeDeep, getGQLReturnType } = require('../service/app.service');
7
7
 
8
8
  module.exports = class QueryResolver {
9
9
  constructor(query) {
10
10
  this.query = query;
11
11
  this.resolver = query.toObject().resolver;
12
+ this.context = this.resolver.getContext();
12
13
  }
13
14
 
14
- findOne(query) {
15
- return createSystemEvent('Query', { method: 'get', query }, () => {
15
+ autoResolve(query) {
16
+ const { args } = query.toObject();
17
+ const [,,, info] = args;
18
+
19
+ switch (getGQLReturnType(info.returnType)) {
20
+ case 'array': {
21
+ return new QueryResolver(this.query.clone().method('findMany')).resolve();
22
+ }
23
+ case 'number': {
24
+ return new QueryResolver(this.query.clone().method('count')).resolve();
25
+ }
26
+ case 'connection': {
27
+ return Promise.resolve({
28
+ count: () => new QueryResolver(this.query.clone().method('count')).resolve(),
29
+ edges: () => new QueryResolver(this.query.clone().method('findMany')).resolve(),
30
+ pageInfo: () => new QueryResolver(this.query.clone().method('findMany')).resolve(),
31
+ });
32
+ }
33
+ case 'scalar': default: {
34
+ return new QueryResolver(this.query.clone().method('findOne')).resolve();
35
+ }
36
+ }
37
+ }
38
+
39
+ async findOne(query) {
40
+ await QueryService.resolveQuery(query);
41
+
42
+ return createSystemEvent('Query', { query }, () => {
16
43
  return this.resolver.resolve(query);
17
44
  });
18
45
  }
19
46
 
20
- findMany(query) {
21
- return createSystemEvent('Query', { method: 'find', query }, () => {
47
+ async findMany(query) {
48
+ await QueryService.resolveQuery(query);
49
+
50
+ return createSystemEvent('Query', { query }, () => {
22
51
  return this.resolver.resolve(query);
23
52
  });
24
53
  }
25
54
 
26
- count(query) {
27
- return createSystemEvent('Query', { method: 'count', query }, () => {
55
+ async count(query) {
56
+ await QueryService.resolveQuery(query);
57
+
58
+ return createSystemEvent('Query', { query }, () => {
28
59
  return this.resolver.resolve(query);
29
60
  });
30
61
  }
31
62
 
32
- createOne(query) {
33
- const { model, input, flags } = query.toObject();
34
- model.appendDefaultFields(query, input);
63
+ async createOne(query) {
64
+ const { model, input } = query.toObject();
65
+ const shape = model.getShape('create');
35
66
 
36
- return createSystemEvent('Mutation', { method: 'create', query }, () => {
37
- const $input = model.serialize(query, model.appendCreateFields(input));
38
- query.$input($input);
39
- const promise = get(flags, 'novalidate') ? this.resolver.resolve(query) : model.validate(query, $input).then(() => this.resolver.resolve(query));
40
- return promise.then((doc) => {
41
- query.doc(doc);
42
- return doc;
43
- });
67
+ await QueryService.resolveQuery(query);
68
+
69
+ return createSystemEvent('Mutation', { query }, async () => {
70
+ const $input = model.shapeObject(shape, input, query);
71
+ await model.validate(query, $input);
72
+ const doc = await this.resolver.resolve(query.$input($input));
73
+ query.doc(doc);
74
+ return doc;
44
75
  });
45
76
  }
46
77
 
47
78
  createMany(query) {
48
- const { model, args, transaction } = query.toObject();
79
+ const { model, input, transaction } = query.toObject();
49
80
  const txn = this.resolver.transaction(transaction);
50
- args.forEach(arg => txn.match(model).save(arg));
81
+ input.forEach(arg => txn.match(model).save(arg));
51
82
  return txn.run();
52
83
  }
53
84
 
54
- updateOne(query) {
55
- const { model, match, flags } = query.toObject();
85
+ async updateOne(query) {
86
+ const { model, match, input } = query.toObject();
87
+
88
+ return this.resolver.match(model).match(match).one({ required: true }).then(async (doc) => {
89
+ const shape = model.getShape('update');
90
+ const merged = model.shapeObject(shape, mergeDeep(doc, input), query);
56
91
 
57
- return this.resolver.match(model).match(match).one({ required: true }).then((doc) => {
58
- const { input } = query.toObject();
59
- const merged = mergeDeep(doc, input);
92
+ await QueryService.resolveQuery(query);
60
93
 
61
- return createSystemEvent('Mutation', { method: 'update', query: query.doc(doc).merged(merged) }, async () => {
62
- const $input = model.serialize(query, model.appendUpdateFields(input), true);
63
- if (!get(flags, 'novalidate')) await model.validate(query, $input);
64
- const $doc = mergeDeep(model.serialize(query, doc, true), $input);
65
- return this.resolver.resolve(query.$doc($doc).$input($input));
94
+ return createSystemEvent('Mutation', { query: query.doc(doc).merged(merged) }, async () => {
95
+ const $doc = model.shapeObject(shape, mergeDeep(doc, input), query);
96
+ await model.validate(query, $doc);
97
+ return this.resolver.resolve(query.$doc($doc));
66
98
  });
67
99
  });
68
100
  }
69
101
 
70
102
  updateMany(query) {
71
- const { model, args, match, transaction, flags } = query.toObject();
103
+ const { model, input, match, transaction, flags } = query.toObject();
72
104
 
73
- return this.resolver.match(model).match(match).flags(flags).many().then((docs) => {
105
+ return this.resolver.match(model).match(match).many(flags).then((docs) => {
74
106
  const txn = this.resolver.transaction(transaction);
75
- docs.forEach(doc => txn.match(model).id(doc.id).save(...args));
107
+ docs.forEach(doc => txn.match(model).id(doc.id).save(input, flags));
76
108
  return txn.run();
77
109
  });
78
110
  }
@@ -80,8 +112,10 @@ module.exports = class QueryResolver {
80
112
  deleteOne(query) {
81
113
  const { model, id, flags } = query.toObject();
82
114
 
83
- return this.resolver.match(model).id(id).flags(flags).one({ required: true }).then((doc) => {
84
- return createSystemEvent('Mutation', { method: 'delete', query: query.doc(doc) }, () => {
115
+ return this.resolver.match(model).id(id).flags(flags).one({ required: true }).then(async (doc) => {
116
+ await QueryService.resolveQuery(query);
117
+
118
+ return createSystemEvent('Mutation', { query: query.doc(doc) }, () => {
85
119
  return QueryService.resolveReferentialIntegrity(query).then(() => {
86
120
  return this.resolver.resolve(query).then(() => doc);
87
121
  });
@@ -152,16 +186,28 @@ module.exports = class QueryResolver {
152
186
  }
153
187
 
154
188
  splice(query) {
155
- const { model, match, args, flags } = query.toObject();
189
+ const { model, match, args, flags = {} } = query.toObject();
156
190
  const [key, from, to] = args;
157
191
 
158
- return this.resolver.match(model).match(match).flags(flags).one({ required: true }).then(async (doc) => {
159
- await DataService.spliceEmbeddedArray(query, doc, key, from, to);
192
+ // Can only splice arrays
193
+ const field = model.getField(key);
194
+ const isArray = field.isArray();
195
+ if (!isArray) throw Boom.badRequest(`Cannot splice field '${model}.${field}'`);
160
196
 
161
- return createSystemEvent('Mutation', { method: 'update', query: query.doc(doc).merged(doc) }, async () => {
162
- await model.validate(query, doc);
163
- const $doc = model.serialize(query, doc, true);
164
- return this.resolver.resolve(query.method('updateOne').doc(doc).$doc($doc));
197
+ return this.resolver.match(model).match(match).flags(flags).one({ required: true }).then(async (doc) => {
198
+ const array = get(doc, key) || [];
199
+ const paramShape = model.getShape('create', 'spliceTo');
200
+ const $to = model.shapeObject(paramShape, { [key]: to }, query)[key] || to;
201
+ const $from = model.shapeObject(paramShape, { [key]: from }, query)[key] || from;
202
+ set(doc, key, DataService.spliceEmbeddedArray(array, $from, $to));
203
+
204
+ await QueryService.resolveQuery(query);
205
+
206
+ return createSystemEvent('Mutation', { query: query.method('updateOne').doc(doc).merged(doc) }, async () => {
207
+ const shape = model.getShape('update');
208
+ const $doc = model.shapeObject(shape, doc, query);
209
+ await model.validate(query, $doc);
210
+ return this.resolver.resolve(query.$doc($doc));
165
211
  });
166
212
  });
167
213
  }
@@ -177,8 +223,11 @@ module.exports = class QueryResolver {
177
223
  async resolve() {
178
224
  const { model, method, flags } = this.query.toObject();
179
225
 
226
+ // const resolveQueryMethods = ['findOne', 'findMany', 'count', 'createOne', 'updateOne', 'deleteOne', 'splice'];
227
+ // if (resolveQueryMethods.indexOf(method) > -1) await QueryService.resolveQuery(this.query);
228
+
180
229
  return this[method](this.query).then((data) => {
181
- if (flags.required && (data == null || isEmpty(data))) throw Boom.notFound(`${model} Not Found`);
230
+ if (flags.required && isEmpty(data)) throw Boom.notFound(`${model} Not Found`);
182
231
  if (data == null) return null; // Explicitly return null here
183
232
  return data;
184
233
  });
@@ -1,50 +1,31 @@
1
1
  const { get, set, uniq, flattenDeep } = require('lodash');
2
- const { map, keyPaths, ensureArray, isPlainObject } = require('../service/app.service');
3
-
4
- const resolveEmbeddedWhere = (ref, key, value) => {
5
- const resolved = ensureArray(map(value, (obj) => {
6
- return Object.entries(obj).reduce((p, [k, v]) => {
7
- const f = ref.getFieldByName(k);
8
-
9
- if (k === 'id') return Object.assign(p, { [k]: ref.idValue(v) });
10
- if (f.isScalar()) return Object.assign(p, { [k]: v });
11
- if (f.isEmbedded()) return Object.assign(p, { [k]: resolveEmbeddedWhere(f.getModelRef(), k, v) });
12
- return Object.assign(p, { [k]: v });
13
- }, {});
14
- }));
15
-
16
- return resolved.length > 1 ? resolved : resolved[0];
17
- };
2
+ const { keyPaths, ensureArray, isPlainObject } = require('../service/app.service');
18
3
 
4
+ /**
5
+ * The where clause may contain attributes that are NOT in the model
6
+ * This can happen because the where clause reaches into the schema via refs/virtual refs
7
+ */
19
8
  exports.resolveWhereClause = (query) => {
20
9
  const { resolver, model, match: where = {}, flags = {} } = query.toObject();
10
+ const shape = model.getShape('create', 'where');
21
11
 
22
- // This is needed for where clause (but why!?!)
23
- if (where.id) where.id = map(where.id, v => model.idValue(v));
12
+ const $where = Object.entries(where).reduce((prev, [from, value]) => {
13
+ const el = shape.find(s => s.from === from);
14
+ if (!el) return prev; // There's no knowing what this could be
24
15
 
25
- // Construct
26
- const $where = Object.entries(where).reduce((prev, [key, value]) => {
27
- const field = model.getField(key);
28
- if (!field) return prev;
29
- const modelRef = field.getModelRef();
16
+ const { isVirtual, isEmbedded, modelRef, virtualRef } = el.field.toObject();
30
17
 
31
- if (field.isVirtual()) {
32
- const virtualRef = field.getVirtualRef();
18
+ if (isVirtual) {
33
19
  const ids = Promise.all(ensureArray(value).map(v => resolver.match(modelRef).where(isPlainObject(v) ? v : { id: v }).many(flags).then(docs => docs.map(doc => doc[virtualRef])))).then(results => uniq(flattenDeep(results)));
34
20
  return Object.assign(prev, { id: ids });
35
21
  }
36
22
 
37
- if (modelRef && !field.isEmbedded()) {
23
+ if (modelRef && !isEmbedded) {
38
24
  const ids = Promise.all(ensureArray(value).map(v => (isPlainObject(v) ? resolver.match(modelRef).where(v).many(flags).then(docs => docs.map(doc => doc.id)) : Promise.resolve(v)))).then(results => uniq(flattenDeep(results)));
39
- return Object.assign(prev, { [key]: ids });
25
+ return Object.assign(prev, { [from]: ids });
40
26
  }
41
27
 
42
- // You do not have a unit-test that tests this (BUT ITS NEEDED)
43
- if (field.isEmbedded()) {
44
- return Object.assign(prev, { [key]: resolveEmbeddedWhere(modelRef, key, value) });
45
- }
46
-
47
- return Object.assign(prev, { [key]: value });
28
+ return Object.assign(prev, { [from]: value });
48
29
  }, {});
49
30
 
50
31
  // Resolve
@@ -61,7 +42,8 @@ exports.resolveWhereClause = (query) => {
61
42
 
62
43
  exports.resolveSortBy = (query) => {
63
44
  const { model, sort = {} } = query.toObject();
64
- const $sort = model.normalize(query, sort, 'serialize', true);
45
+ const shape = model.getShape('create', 'sortBy');
46
+ const $sort = model.shapeObject(shape, sort, query);
65
47
 
66
48
  // Because normalize casts the value (sometimes to an array) need special handling
67
49
  keyPaths($sort).forEach((path) => {
@@ -127,3 +109,18 @@ exports.resolveReferentialIntegrity = (query) => {
127
109
  }
128
110
  });
129
111
  };
112
+
113
+ exports.resolveQuery = async (query) => {
114
+ const { model, sort, native, batch, match } = query.toObject();
115
+
116
+ if (!native) {
117
+ const shape = model.getShape('create', 'where');
118
+ const $where = batch ? match : await exports.resolveWhereClause(query);
119
+ const $$where = model.shapeObject(shape, $where, query);
120
+ query.match($$where);
121
+ }
122
+
123
+ if (sort) {
124
+ query.$sort(exports.resolveSortBy(query));
125
+ }
126
+ };