@coderich/autograph 0.10.0 → 0.10.3

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 (44) hide show
  1. package/CHANGELOG.md +20 -3
  2. package/index.js +2 -8
  3. package/package.json +5 -7
  4. package/src/.DS_Store +0 -0
  5. package/src/core/EventEmitter.js +2 -4
  6. package/src/core/Resolver.js +32 -57
  7. package/src/core/Schema.js +5 -38
  8. package/src/data/.DS_Store +0 -0
  9. package/src/data/DataLoader.js +71 -32
  10. package/src/data/DataService.js +82 -59
  11. package/src/data/Field.js +59 -126
  12. package/src/data/Model.js +113 -105
  13. package/src/data/Pipeline.js +184 -0
  14. package/src/data/Type.js +38 -74
  15. package/src/driver/MongoDriver.js +27 -22
  16. package/src/graphql/.DS_Store +0 -0
  17. package/src/graphql/ast/Field.js +46 -24
  18. package/src/graphql/ast/Model.js +5 -16
  19. package/src/graphql/ast/Node.js +0 -25
  20. package/src/graphql/ast/Schema.js +105 -112
  21. package/src/graphql/extension/api.js +20 -18
  22. package/src/graphql/extension/framework.js +27 -33
  23. package/src/graphql/extension/type.js +2 -2
  24. package/src/query/Query.js +82 -14
  25. package/src/query/QueryBuilder.js +38 -30
  26. package/src/query/QueryBuilderTransaction.js +3 -3
  27. package/src/query/QueryResolver.js +77 -41
  28. package/src/query/QueryService.js +24 -42
  29. package/src/service/app.service.js +70 -13
  30. package/src/service/event.service.js +30 -73
  31. package/src/service/graphql.service.js +0 -9
  32. package/src/service/schema.service.js +5 -3
  33. package/src/core/GraphQL.js +0 -21
  34. package/src/core/Rule.js +0 -107
  35. package/src/core/SchemaDecorator.js +0 -46
  36. package/src/core/Transformer.js +0 -68
  37. package/src/data/Memoizer.js +0 -39
  38. package/src/data/ResultSet.js +0 -205
  39. package/src/data/stream/DataHydrator.js +0 -58
  40. package/src/data/stream/ResultSet.js +0 -34
  41. package/src/data/stream/ResultSetItem.js +0 -158
  42. package/src/data/stream/ResultSetItemProxy.js +0 -161
  43. package/src/graphql/ast/SchemaDecorator.js +0 -141
  44. package/src/graphql/directive/authz.directive.js +0 -84
@@ -1,12 +1,12 @@
1
1
  const Query = require('./Query');
2
2
  const QueryResolver = require('./QueryResolver');
3
- const { toKeyObj, unravelObject } = require('../service/app.service');
3
+ const { unravelObject } = require('../service/app.service');
4
4
 
5
5
  /*
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,13 +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.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(where); return this; };
20
- 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; };
21
21
  this.native = (native) => { this.query.native(native); return this; };
22
- this.sort = (sort) => { this.query.sort(sort); return this; };
23
- 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; };
24
24
  this.limit = (limit) => { this.query.limit(limit); return this; };
25
25
  this.skip = (skip) => { this.query.skip(skip); return this; };
26
26
  this.before = (cursor) => { this.query.before(cursor); return this; };
@@ -28,64 +28,72 @@ module.exports = class QueryBuilder {
28
28
  this.meta = (meta) => { this.query.meta(meta); return this; };
29
29
  this.flags = (flags) => { this.query.flags(flags); return this; };
30
30
  this.merge = (merge) => { this.query.merge(merge); return this; };
31
+ this.batch = (batch) => { this.query.batch(batch); return this; };
31
32
  this.transaction = (txn) => { this.query.transaction(txn); return this; };
32
33
 
33
34
  // Terminal commands
34
- this.one = (...args) => this.resolve('one', args);
35
- this.many = (...args) => this.resolve('many', args);
36
- this.save = (...args) => this.resolve('save', args.map(arg => unravelObject(arg)));
37
- this.delete = (...args) => this.resolve('delete', args);
38
- 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);
39
41
  //
40
- this.count = (...args) => this.resolve('count', args);
41
- this.push = (...args) => this.resolve('push', args.map(arg => unravelObject(arg)));
42
- this.pull = (...args) => this.resolve('pull', args.map(arg => unravelObject(arg)));
43
- this.splice = (...args) => this.resolve('splice', args.map(arg => unravelObject(arg)));
44
- this.first = (...args) => { this.query.first(...args); return this.resolve('first', args); };
45
- 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); };
46
48
  //
47
49
  // this.min = (...args) => this.makeTheCall(query, 'min', args);
48
50
  // this.max = (...args) => this.makeTheCall(query, 'max', args);
49
51
  // this.avg = (...args) => this.makeTheCall(query, 'avg', args);
52
+ // this.sum = (...args) => this.makeTheCall(query, 'sum', args); // Would sum be different than count?
50
53
  // // Food for thought...
51
54
  // this.archive = (...args) => this.makeTheCall(query, 'archive', args); // Soft Delete
52
55
  // this.stream = (...args) => this.makeTheCall(query, 'stream', args); // Stream records 1 by 1
53
- // this.sum = (...args) => this.makeTheCall(query, 'sum', args); // Would sum be different than count?
54
56
  // this.rollup = (...args) => this.makeTheCall(query, 'rollup', args); // Like sum, but for nested attributes (eg. Person.rollupAuthoredChaptersPages)
55
57
  }
56
58
 
57
- resolve(cmd, args) {
58
- let method, crud, input = {};
59
- let { flags = {} } = this.query.toObject();
59
+ execute(cmd, args) {
60
+ let method, crud, input, flags = {};
60
61
  const { id, where } = this.query.toObject();
61
62
 
62
63
  switch (cmd) {
64
+ case 'resolve': {
65
+ crud = 'read';
66
+ method = 'autoResolve';
67
+ break;
68
+ }
63
69
  case 'one': case 'many': {
64
70
  crud = 'read';
65
- flags = args[0] || flags;
71
+ [flags] = args;
66
72
  method = cmd === 'one' ? 'findOne' : 'findMany';
67
73
  break;
68
74
  }
69
75
  case 'first': case 'last': {
70
76
  crud = 'read';
71
- flags = args[1] || flags;
77
+ [, flags] = args;
72
78
  method = cmd;
73
79
  break;
74
80
  }
75
81
  case 'save': {
82
+ [input, flags] = args;
76
83
  crud = id || where ? 'update' : 'create';
77
- if (crud === 'update') { method = id ? 'updateOne' : 'updateMany'; [input] = args; }
78
- 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'; }
79
86
  break;
80
87
  }
81
88
  case 'push': case 'pull': case 'splice': {
89
+ // [target, input, flags] = args;
82
90
  crud = 'update'; // Your logic wants this to be a simple "update". Sub documents systemEvents will emit either "create" or "udpate"
83
91
  method = id ? `${cmd}One` : `${cmd}Many`;
84
92
  break;
85
93
  }
86
94
  case 'remove': case 'delete': {
87
95
  crud = 'delete';
88
- flags = args[0] || flags;
96
+ [flags] = args;
89
97
  if (id) method = 'deleteOne';
90
98
  else if (where) method = 'deleteMany';
91
99
  else return Promise.reject(new Error('Remove requires an id() or where()'));
@@ -93,7 +101,7 @@ module.exports = class QueryBuilder {
93
101
  }
94
102
  case 'count': {
95
103
  crud = 'read';
96
- flags = args[0] || flags;
104
+ [flags] = args;
97
105
  method = 'count';
98
106
  break;
99
107
  }
@@ -102,6 +110,6 @@ module.exports = class QueryBuilder {
102
110
  }
103
111
  }
104
112
 
105
- 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();
106
114
  }
107
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,86 +1,111 @@
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();
13
+ }
14
+
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
+ }
12
37
  }
13
38
 
14
39
  findOne(query) {
15
- return createSystemEvent('Query', { method: 'get', query }, () => {
40
+ return createSystemEvent('Query', { query }, async () => {
16
41
  return this.resolver.resolve(query);
17
42
  });
18
43
  }
19
44
 
20
45
  findMany(query) {
21
- return createSystemEvent('Query', { method: 'find', query }, () => {
46
+ return createSystemEvent('Query', { query }, async () => {
22
47
  return this.resolver.resolve(query);
23
48
  });
24
49
  }
25
50
 
26
51
  count(query) {
27
- return createSystemEvent('Query', { method: 'count', query }, () => {
52
+ return createSystemEvent('Query', { query }, async () => {
28
53
  return this.resolver.resolve(query);
29
54
  });
30
55
  }
31
56
 
32
57
  createOne(query) {
33
- const { model, input, flags } = query.toObject();
34
- model.appendDefaultFields(query, input);
35
-
36
- return createSystemEvent('Mutation', { method: 'create', query }, async () => {
37
- const $input = model.serialize(query, model.appendCreateFields(input));
38
- query.$input($input);
39
- if (!get(flags, 'novalidate')) await model.validate(query, $input);
40
- const doc = await this.resolver.resolve(query);
41
- query.doc(doc);
42
- return doc;
58
+ const { model, input } = query.toObject();
59
+ const inputShape = model.getShape('create', 'input');
60
+ const docShape = model.getShape('create', 'doc');
61
+ const doc = model.shapeObject(inputShape, {}, query); // We use input shape here
62
+ const merged = mergeDeep(doc, input);
63
+
64
+ return createSystemEvent('Mutation', { query: query.doc(doc).merged(merged) }, async () => {
65
+ const payload = model.shapeObject(inputShape, merged, query);
66
+ await model.validateObject(inputShape, payload, query.payload(payload));
67
+ return this.resolver.resolve(query.$input(model.shapeObject(docShape, payload, query)));
43
68
  });
44
69
  }
45
70
 
46
71
  createMany(query) {
47
- const { model, args, transaction } = query.toObject();
72
+ const { model, input, transaction } = query.toObject();
48
73
  const txn = this.resolver.transaction(transaction);
49
- args.forEach(arg => txn.match(model).save(arg));
74
+ input.forEach(arg => txn.match(model).save(arg));
50
75
  return txn.run();
51
76
  }
52
77
 
53
- updateOne(query) {
54
- const { model, match, flags } = query.toObject();
78
+ async updateOne(query) {
79
+ const { model, match, input } = query.toObject();
80
+ const inputShape = model.getShape('update', 'input');
81
+ const docShape = model.getShape('update', 'doc');
55
82
 
56
83
  return this.resolver.match(model).match(match).one({ required: true }).then((doc) => {
57
- const { input } = query.toObject();
58
84
  const merged = mergeDeep(doc, input);
59
85
 
60
- return createSystemEvent('Mutation', { method: 'update', query: query.doc(doc).merged(merged) }, async () => {
61
- const $input = model.serialize(query, model.appendUpdateFields(input), true);
62
- if (!get(flags, 'novalidate')) await model.validate(query, $input);
63
- const $doc = mergeDeep(model.serialize(query, doc, true), $input);
64
- return this.resolver.resolve(query.$doc($doc).$input($input));
86
+ return createSystemEvent('Mutation', { query: query.doc(doc).merged(merged) }, async () => {
87
+ const payload = model.shapeObject(inputShape, merged, query);
88
+ await model.validateObject(inputShape, payload, query.payload(payload));
89
+ return this.resolver.resolve(query.$doc(model.shapeObject(docShape, payload, query)));
65
90
  });
66
91
  });
67
92
  }
68
93
 
69
94
  updateMany(query) {
70
- const { model, args, match, transaction, flags } = query.toObject();
95
+ const { model, input, match, transaction, flags } = query.toObject();
71
96
 
72
- return this.resolver.match(model).match(match).flags(flags).many().then((docs) => {
97
+ return this.resolver.match(model).match(match).many(flags).then((docs) => {
73
98
  const txn = this.resolver.transaction(transaction);
74
- docs.forEach(doc => txn.match(model).id(doc.id).save(...args));
99
+ docs.forEach(doc => txn.match(model).id(doc.id).save(input, flags));
75
100
  return txn.run();
76
101
  });
77
102
  }
78
103
 
79
104
  deleteOne(query) {
80
- const { model, id, flags } = query.toObject();
105
+ const { model, id } = query.toObject();
81
106
 
82
- return this.resolver.match(model).id(id).flags(flags).one({ required: true }).then((doc) => {
83
- return createSystemEvent('Mutation', { method: 'delete', query: query.doc(doc) }, () => {
107
+ return this.resolver.match(model).id(id).one({ required: true }).then(async (doc) => {
108
+ return createSystemEvent('Mutation', { query: query.doc(doc) }, () => {
84
109
  return QueryService.resolveReferentialIntegrity(query).then(() => {
85
110
  return this.resolver.resolve(query).then(() => doc);
86
111
  });
@@ -151,16 +176,27 @@ module.exports = class QueryResolver {
151
176
  }
152
177
 
153
178
  splice(query) {
154
- const { model, match, args, flags } = query.toObject();
179
+ const { model, match, args } = query.toObject();
180
+ const docShape = model.getShape('update', 'doc');
181
+ const inputShape = model.getShape('update', 'input');
182
+ const spliceShape = model.getShape('update', 'splice');
155
183
  const [key, from, to] = args;
156
184
 
157
- return this.resolver.match(model).match(match).flags(flags).one({ required: true }).then(async (doc) => {
158
- await DataService.spliceEmbeddedArray(query, doc, key, from, to);
159
-
160
- return createSystemEvent('Mutation', { method: 'update', query: query.doc(doc).merged(doc) }, async () => {
161
- await model.validate(query, doc);
162
- const $doc = model.serialize(query, doc, true);
163
- return this.resolver.resolve(query.method('updateOne').doc(doc).$doc($doc));
185
+ // Can only splice arrays
186
+ const field = model.getField(key);
187
+ const isArray = field.isArray();
188
+ if (!isArray) throw Boom.badRequest(`Cannot splice field '${model}.${field}'`);
189
+
190
+ return this.resolver.match(model).match(match).one({ required: true }).then(async (doc) => {
191
+ const array = get(doc, key) || [];
192
+ const $to = model.shapeObject(spliceShape, { [key]: to }, query)[key] || to;
193
+ const $from = model.shapeObject(spliceShape, { [key]: from }, query)[key] || from;
194
+ set(doc, key, DataService.spliceEmbeddedArray(array, $from, $to));
195
+
196
+ return createSystemEvent('Mutation', { query: query.method('updateOne').doc(doc).merged(doc) }, async () => {
197
+ const payload = model.shapeObject(inputShape, doc, query);
198
+ await model.validateObject(inputShape, payload, query.payload(payload));
199
+ return this.resolver.resolve(query.$doc(model.shapeObject(docShape, payload, query)));
164
200
  });
165
201
  });
166
202
  }
@@ -173,11 +209,11 @@ module.exports = class QueryResolver {
173
209
  return this.findMany(query.method('findMany'));
174
210
  }
175
211
 
176
- resolve() {
212
+ async resolve() {
177
213
  const { model, method, flags } = this.query.toObject();
178
214
 
179
215
  return this[method](this.query).then((data) => {
180
- if (flags.required && (data == null || isEmpty(data))) throw Boom.notFound(`${model} Not Found`);
216
+ if (flags.required && isEmpty(data)) throw Boom.notFound(`${model} Not Found`);
181
217
  if (data == null) return null; // Explicitly return null here
182
218
  return data;
183
219
  });
@@ -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
- const { resolver, model, match: where = {}, flags = {} } = query.toObject();
9
+ const { resolver, model, match: where = {} } = 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();
33
- 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)));
18
+ if (isVirtual) {
19
+ const ids = Promise.all(ensureArray(value).map(v => resolver.match(modelRef).where(isPlainObject(v) ? v : { id: v }).many().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()) {
38
- 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 });
40
- }
41
-
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) });
23
+ if (modelRef && !isEmbedded) {
24
+ const ids = Promise.all(ensureArray(value).map(v => (isPlainObject(v) ? resolver.match(modelRef).where(v).many().then(docs => docs.map(doc => doc.id)) : Promise.resolve(v)))).then(results => uniq(flattenDeep(results)));
25
+ return Object.assign(prev, { [from]: ids });
45
26
  }
46
27
 
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) => {
@@ -85,7 +67,7 @@ exports.resolveSortBy = (query) => {
85
67
  };
86
68
 
87
69
  exports.resolveReferentialIntegrity = (query) => {
88
- const { id, model, resolver, transaction, flags } = query.toObject();
70
+ const { id, model, resolver, transaction } = query.toObject();
89
71
  const txn = resolver.transaction(transaction);
90
72
 
91
73
  return new Promise((resolve, reject) => {
@@ -97,18 +79,18 @@ exports.resolveReferentialIntegrity = (query) => {
97
79
  switch (op) {
98
80
  case 'cascade': {
99
81
  if (isArray) {
100
- txn.match(ref).where($where).flags(flags).pull(fieldStr, id);
82
+ txn.match(ref).where($where).pull(fieldStr, id);
101
83
  } else {
102
- txn.match(ref).where($where).flags(flags).remove();
84
+ txn.match(ref).where($where).remove();
103
85
  }
104
86
  break;
105
87
  }
106
88
  case 'nullify': {
107
- txn.match(ref).where($where).flags(flags).save({ [fieldStr]: null });
89
+ txn.match(ref).where($where).save({ [fieldStr]: null });
108
90
  break;
109
91
  }
110
92
  case 'restrict': {
111
- txn.match(ref).where($where).flags(flags).count().then(count => (count ? reject(new Error('Restricted')) : count));
93
+ txn.match(ref).where($where).count().then(count => (count ? reject(new Error('Restricted')) : count));
112
94
  break;
113
95
  }
114
96
  case 'defer': {
@@ -2,7 +2,7 @@ const _ = require('lodash');
2
2
  const PicoMatch = require('picomatch');
3
3
  const FillRange = require('fill-range');
4
4
  const DeepMerge = require('deepmerge');
5
- const { ObjectID } = require('mongodb');
5
+ const { ObjectId } = require('mongodb');
6
6
  const ObjectHash = require('object-hash');
7
7
 
8
8
  // const combineMerge = (target, source, options) => {
@@ -32,15 +32,15 @@ exports.id = '3d896496-02a3-4ee5-8e42-2115eb215f7e';
32
32
  exports.ucFirst = string => string.charAt(0).toUpperCase() + string.slice(1);
33
33
  exports.lcFirst = string => string.charAt(0).toLowerCase() + string.slice(1);
34
34
  exports.isNumber = value => typeof value === 'number' && Number.isFinite(value);
35
- exports.isBasicObject = obj => obj != null && typeof obj === 'object' && !(ObjectID.isValid(obj)) && !(obj instanceof Date) && typeof (obj.then) !== 'function';
35
+ exports.isBasicObject = obj => obj != null && typeof obj === 'object' && !(ObjectId.isValid(obj)) && !(obj instanceof Date) && typeof (obj.then) !== 'function';
36
36
  exports.isPlainObject = obj => exports.isBasicObject(obj) && !Array.isArray(obj);
37
37
  exports.isScalarValue = value => typeof value !== 'object' && typeof value !== 'function';
38
38
  exports.isScalarDataType = value => ['String', 'Float', 'Int', 'Boolean', 'DateTime'].indexOf(value) > -1;
39
- exports.isIdValue = value => exports.isScalarValue(value) || value instanceof ObjectID;
39
+ exports.isIdValue = value => exports.isScalarValue(value) || value instanceof ObjectId;
40
40
  exports.mergeDeep = (...args) => DeepMerge.all(args, { isMergeableObject: obj => (exports.isPlainObject(obj) || Array.isArray(obj)), arrayMerge: smartMerge });
41
41
  exports.uniq = arr => [...new Set(arr.map(a => `${a}`))];
42
42
  exports.timeout = ms => new Promise(res => setTimeout(res, ms));
43
- exports.hashObject = obj => ObjectHash(obj, { respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedArrays: true, ignoreUnknown: true, replacer: r => (r instanceof ObjectID ? `${r}` : r) });
43
+ exports.hashObject = obj => ObjectHash(obj, { respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedArrays: true, ignoreUnknown: true, replacer: r => (r instanceof ObjectId ? `${r}` : r) });
44
44
  exports.globToRegex = (glob, options = {}) => PicoMatch.makeRe(glob, { ...options, expandRange: (a, b) => `(${FillRange(a, b, { toRegex: true })})` });
45
45
  exports.globToRegexp = (glob, options = {}) => PicoMatch.toRegex(exports.globToRegex(glob, options));
46
46
  exports.toGUID = (model, id) => Buffer.from(`${model},${`${id}`}`).toString('base64');
@@ -54,6 +54,11 @@ exports.stripObjectUndefineds = obj => Object.entries(obj).reduce((prev, [key, v
54
54
  exports.pushIt = (arr, it) => arr[arr.push(it) - 1];
55
55
  exports.toKeyObj = obj => exports.keyPaths(obj).reduce((prev, path) => Object.assign(prev, { [path]: _.get(obj, path) }), {});
56
56
 
57
+ exports.getGQLReturnType = (returnType) => {
58
+ const typeMap = { array: /^\[.+\].?$/, connection: /.+Connection!?$/, number: /^(Int|Float)!?$/, scalar: /.*/ };
59
+ return Object.entries(typeMap).find(([type, pattern]) => returnType.match(pattern))[0];
60
+ };
61
+
57
62
  exports.removeUndefinedDeep = (obj) => {
58
63
  return exports.unravelObject(exports.keyPaths(obj).reduce((prev, path) => {
59
64
  const value = _.get(obj, path);
@@ -90,7 +95,7 @@ exports.map = (mixed, fn) => {
90
95
  if (mixed == null) return mixed;
91
96
  const isArray = Array.isArray(mixed);
92
97
  const arr = isArray ? mixed : [mixed];
93
- const results = arr.map(el => fn(el));
98
+ const results = isArray ? arr.map((...args) => fn(...args)) : arr.map(el => fn(el));
94
99
  return isArray ? results : results[0];
95
100
  };
96
101
 
@@ -141,14 +146,6 @@ exports.objectContaining = (a, b) => {
141
146
  return exports.hashObject(a) === exports.hashObject(b);
142
147
  };
143
148
 
144
- exports.serialize = (field, value) => {
145
- if (!exports.isPlainObject(value)) return value;
146
- const model = field.getModelRef();
147
- if (!model) return value;
148
- const key = model.idKey();
149
- return value[key];
150
- };
151
-
152
149
  /**
153
150
  * Transform an object with dot.notation keys into an expanded object.
154
151
  * eg. { 'user.name': 'richard' } => { user: { name: 'richard' } }
@@ -266,3 +263,63 @@ exports.proxyDeep = (obj, handler, proxyMap = new WeakMap(), path = '') => {
266
263
 
267
264
  return finalProxy;
268
265
  };
266
+
267
+ exports.resolveDataObject = (obj) => {
268
+ return Promise.all(Object.keys(obj).map(async (key) => {
269
+ const value = await obj[key];
270
+ return { key, value };
271
+ })).then((results) => {
272
+ return results.reduce((prev, { key, value }) => {
273
+ return Object.assign(prev, { [key]: value });
274
+ }, {});
275
+ });
276
+ };
277
+
278
+ exports.seek = (obj, paths, hint) => {
279
+ // We first do a normal get
280
+ const value = _.get(obj, paths);
281
+ if (!hint || value !== undefined) return value;
282
+
283
+ // Normalize paths & hint for traversal
284
+ const $paths = Array.isArray(paths) ? paths : paths.split('.');
285
+ const $hint = exports.unravelObject(hint);
286
+
287
+ // Traverse paths and get as close to the value as possible
288
+ const { currentValue, pathsToGo } = $paths.reduce((prev, path, i, arr) => {
289
+ if (prev.currentValue === undefined) return prev;
290
+ if (!Object.prototype.hasOwnProperty.call(prev.currentValue, path)) return prev;
291
+ prev.currentValue = prev.currentValue[path];
292
+ prev.pathsToGo = arr.slice(i + 1);
293
+ return prev;
294
+ }, { currentValue: obj, pathsToGo: $paths });
295
+
296
+ // Only if we hit an array can we continue
297
+ if (!Array.isArray(currentValue)) return undefined;
298
+
299
+ // If we got to the last segment we need the hint in order to verify
300
+ const lastPath = Boolean(pathsToGo.length === 1);
301
+ const arr = lastPath ? currentValue.filter(v => exports.objectContaining(v, $hint)) : currentValue;
302
+
303
+ // We keep going, recursive, till we find the first value
304
+ return arr.reduce((prev, v) => prev || exports.seek(v, pathsToGo, $hint), undefined);
305
+ };
306
+
307
+ exports.deseek = (shape, obj, paths, hint) => {
308
+ // Normalize paths
309
+ const $paths = (Array.isArray(paths) ? paths : paths.split('.')).map((path) => {
310
+ const item = shape.find(s => s.to === path); // Deserializing from unknown to expected
311
+ return item ? item.from : path;
312
+ });
313
+
314
+ // Normalize hint
315
+ const $hint = Object.entries(exports.toKeyObj(hint)).reduce((prev, [key, value]) => {
316
+ const segments = key.split('.').map((path) => {
317
+ const item = shape.find(s => s.to === path); // Deserializing from unknown to expected
318
+ return item ? item.from : path;
319
+ });
320
+
321
+ return Object.assign(prev, { [segments.join('.')]: value });
322
+ }, {});
323
+
324
+ return exports.seek(obj, $paths, $hint);
325
+ };