@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.
- package/CHANGELOG.md +24 -0
- package/index.js +2 -6
- package/package.json +10 -10
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +35 -58
- package/src/core/Schema.js +5 -38
- package/src/core/ServerResolver.js +7 -93
- package/src/data/DataLoader.js +68 -27
- package/src/data/DataService.js +59 -58
- package/src/data/Field.js +71 -96
- package/src/data/Model.js +95 -113
- package/src/data/Pipeline.js +174 -0
- package/src/data/Type.js +19 -60
- package/src/driver/MongoDriver.js +53 -28
- package/src/graphql/ast/Field.js +44 -26
- package/src/graphql/ast/Model.js +5 -16
- package/src/graphql/ast/Node.js +0 -32
- package/src/graphql/ast/Schema.js +109 -112
- package/src/graphql/extension/api.js +22 -35
- package/src/graphql/extension/framework.js +25 -33
- package/src/graphql/extension/type.js +2 -2
- package/src/query/Query.js +73 -15
- package/src/query/QueryBuilder.js +37 -28
- package/src/query/QueryBuilderTransaction.js +3 -3
- package/src/query/QueryResolver.js +93 -44
- package/src/query/QueryService.js +31 -34
- package/src/service/app.service.js +56 -35
- package/src/service/decorator.service.js +21 -288
- package/src/service/event.service.js +5 -79
- package/src/service/graphql.service.js +0 -9
- package/src/service/schema.service.js +5 -3
- package/src/core/Rule.js +0 -107
- package/src/core/SchemaDecorator.js +0 -46
- package/src/core/Transformer.js +0 -68
- package/src/data/Memoizer.js +0 -39
- package/src/data/ResultSet.js +0 -246
- package/src/graphql/ast/SchemaDecorator.js +0 -138
- package/src/graphql/directive/authz.directive.js +0 -84
package/src/query/Query.js
CHANGED
|
@@ -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.
|
|
12
|
-
|
|
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 =
|
|
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 =
|
|
60
|
+
this.props.match = match;
|
|
48
61
|
return this;
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
select(select) {
|
|
52
|
-
this.props.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 =
|
|
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
|
|
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 '
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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.
|
|
19
|
-
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.
|
|
34
|
-
this.many = (...args) => this.
|
|
35
|
-
this.save = (...args) => this.
|
|
36
|
-
this.delete = (...args) => this.
|
|
37
|
-
this.remove = (...args) => this.
|
|
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.
|
|
40
|
-
this.push = (...args) => this.
|
|
41
|
-
this.pull = (...args) => this.
|
|
42
|
-
this.splice = (...args) => this.
|
|
43
|
-
this.first = (...args) => { this.query.first(...args); return this.
|
|
44
|
-
this.last = (...args) => { this.query.last(...args); return this.
|
|
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
|
-
|
|
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
|
|
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
|
|
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';
|
|
77
|
-
if (crud === 'create') { method =
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
34
|
-
model.
|
|
63
|
+
async createOne(query) {
|
|
64
|
+
const { model, input } = query.toObject();
|
|
65
|
+
const shape = model.getShape('create');
|
|
35
66
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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,
|
|
79
|
+
const { model, input, transaction } = query.toObject();
|
|
49
80
|
const txn = this.resolver.transaction(transaction);
|
|
50
|
-
|
|
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,
|
|
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
|
-
|
|
58
|
-
const { input } = query.toObject();
|
|
59
|
-
const merged = mergeDeep(doc, input);
|
|
92
|
+
await QueryService.resolveQuery(query);
|
|
60
93
|
|
|
61
|
-
return createSystemEvent('Mutation', {
|
|
62
|
-
const $
|
|
63
|
-
|
|
64
|
-
|
|
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,
|
|
103
|
+
const { model, input, match, transaction, flags } = query.toObject();
|
|
72
104
|
|
|
73
|
-
return this.resolver.match(model).match(match).
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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 &&
|
|
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 {
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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 (
|
|
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 && !
|
|
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, { [
|
|
25
|
+
return Object.assign(prev, { [from]: ids });
|
|
40
26
|
}
|
|
41
27
|
|
|
42
|
-
|
|
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
|
|
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
|
+
};
|