@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.
- package/CHANGELOG.md +20 -3
- package/index.js +2 -8
- package/package.json +5 -7
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +32 -57
- package/src/core/Schema.js +5 -38
- package/src/data/.DS_Store +0 -0
- package/src/data/DataLoader.js +71 -32
- package/src/data/DataService.js +82 -59
- package/src/data/Field.js +59 -126
- package/src/data/Model.js +113 -105
- package/src/data/Pipeline.js +184 -0
- package/src/data/Type.js +38 -74
- package/src/driver/MongoDriver.js +27 -22
- package/src/graphql/.DS_Store +0 -0
- package/src/graphql/ast/Field.js +46 -24
- package/src/graphql/ast/Model.js +5 -16
- package/src/graphql/ast/Node.js +0 -25
- package/src/graphql/ast/Schema.js +105 -112
- package/src/graphql/extension/api.js +20 -18
- package/src/graphql/extension/framework.js +27 -33
- package/src/graphql/extension/type.js +2 -2
- package/src/query/Query.js +82 -14
- package/src/query/QueryBuilder.js +38 -30
- package/src/query/QueryBuilderTransaction.js +3 -3
- package/src/query/QueryResolver.js +77 -41
- package/src/query/QueryService.js +24 -42
- package/src/service/app.service.js +70 -13
- package/src/service/event.service.js +30 -73
- package/src/service/graphql.service.js +0 -9
- package/src/service/schema.service.js +5 -3
- package/src/core/GraphQL.js +0 -21
- 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 -205
- package/src/data/stream/DataHydrator.js +0 -58
- package/src/data/stream/ResultSet.js +0 -34
- package/src/data/stream/ResultSetItem.js +0 -158
- package/src/data/stream/ResultSetItemProxy.js +0 -161
- package/src/graphql/ast/SchemaDecorator.js +0 -141
- 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 {
|
|
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
|
|
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
|
-
|
|
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.
|
|
35
|
-
this.many = (...args) => this.
|
|
36
|
-
this.save = (...args) => this.
|
|
37
|
-
this.delete = (...args) => this.
|
|
38
|
-
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);
|
|
39
41
|
//
|
|
40
|
-
this.count = (...args) => this.
|
|
41
|
-
this.push = (...args) => this.
|
|
42
|
-
this.pull = (...args) => this.
|
|
43
|
-
this.splice = (...args) => this.
|
|
44
|
-
this.first = (...args) => { this.query.first(...args); return this.
|
|
45
|
-
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); };
|
|
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
|
-
|
|
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
|
|
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
|
|
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';
|
|
78
|
-
if (crud === 'create') { method =
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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,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', {
|
|
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', {
|
|
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', {
|
|
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
|
|
34
|
-
model.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
query.
|
|
42
|
-
return
|
|
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,
|
|
72
|
+
const { model, input, transaction } = query.toObject();
|
|
48
73
|
const txn = this.resolver.transaction(transaction);
|
|
49
|
-
|
|
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,
|
|
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', {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
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,
|
|
95
|
+
const { model, input, match, transaction, flags } = query.toObject();
|
|
71
96
|
|
|
72
|
-
return this.resolver.match(model).match(match).
|
|
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(
|
|
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
|
|
105
|
+
const { model, id } = query.toObject();
|
|
81
106
|
|
|
82
|
-
return this.resolver.match(model).id(id).
|
|
83
|
-
return createSystemEvent('Mutation', {
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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 &&
|
|
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 {
|
|
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 = {}
|
|
9
|
+
const { resolver, model, match: where = {} } = 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
|
|
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 && !
|
|
38
|
-
const ids = Promise.all(ensureArray(value).map(v => (isPlainObject(v) ? resolver.match(modelRef).where(v).many(
|
|
39
|
-
return Object.assign(prev, { [
|
|
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, { [
|
|
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) => {
|
|
@@ -85,7 +67,7 @@ exports.resolveSortBy = (query) => {
|
|
|
85
67
|
};
|
|
86
68
|
|
|
87
69
|
exports.resolveReferentialIntegrity = (query) => {
|
|
88
|
-
const { id, model, resolver, transaction
|
|
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).
|
|
82
|
+
txn.match(ref).where($where).pull(fieldStr, id);
|
|
101
83
|
} else {
|
|
102
|
-
txn.match(ref).where($where).
|
|
84
|
+
txn.match(ref).where($where).remove();
|
|
103
85
|
}
|
|
104
86
|
break;
|
|
105
87
|
}
|
|
106
88
|
case 'nullify': {
|
|
107
|
-
txn.match(ref).where($where).
|
|
89
|
+
txn.match(ref).where($where).save({ [fieldStr]: null });
|
|
108
90
|
break;
|
|
109
91
|
}
|
|
110
92
|
case 'restrict': {
|
|
111
|
-
txn.match(ref).where($where).
|
|
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 {
|
|
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' && !(
|
|
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
|
|
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
|
|
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
|
+
};
|