@coderich/autograph 0.10.0 → 0.10.1
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 +21 -3
- package/index.js +2 -6
- package/package.json +4 -4
- package/src/.DS_Store +0 -0
- package/src/core/EventEmitter.js +2 -4
- package/src/core/Resolver.js +43 -60
- package/src/core/Schema.js +3 -36
- package/src/data/.DS_Store +0 -0
- package/src/data/DataLoader.js +71 -32
- package/src/data/DataService.js +59 -58
- package/src/data/Field.js +71 -121
- package/src/data/Model.js +98 -108
- package/src/data/Pipeline.js +174 -0
- package/src/data/Type.js +19 -74
- package/src/driver/MongoDriver.js +21 -19
- package/src/graphql/.DS_Store +0 -0
- package/src/graphql/ast/Field.js +43 -24
- package/src/graphql/ast/Model.js +5 -16
- package/src/graphql/ast/Node.js +0 -25
- package/src/graphql/ast/Schema.js +107 -111
- package/src/graphql/extension/api.js +20 -18
- package/src/graphql/extension/framework.js +25 -33
- package/src/graphql/extension/type.js +2 -2
- package/src/query/Query.js +72 -14
- package/src/query/QueryBuilder.js +38 -30
- package/src/query/QueryBuilderTransaction.js +3 -3
- package/src/query/QueryResolver.js +92 -42
- package/src/query/QueryService.js +31 -34
- package/src/service/app.service.js +67 -9
- package/src/service/event.service.js +5 -79
- 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 -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,7 +1,7 @@
|
|
|
1
1
|
const { get } = require('lodash');
|
|
2
2
|
const { Kind } = require('graphql');
|
|
3
3
|
const ServerResolver = require('../../core/ServerResolver');
|
|
4
|
-
const { ucFirst, fromGUID } = require('../../service/app.service');
|
|
4
|
+
const { ucFirst, toGUID, fromGUID } = require('../../service/app.service');
|
|
5
5
|
const { findGQLModels } = require('../../service/schema.service');
|
|
6
6
|
const { makeCreateAPI, makeReadAPI, makeUpdateAPI, makeDeleteAPI, makeSubscriptionAPI, makeQueryResolver, makeMutationResolver } = require('../../service/decorator.service');
|
|
7
7
|
|
|
@@ -10,7 +10,6 @@ const interfaceKinds = [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTE
|
|
|
10
10
|
const getGQLWhereFields = (model) => {
|
|
11
11
|
return model.getFields().filter((field) => {
|
|
12
12
|
if (!field.hasGQLScope('r')) return false;
|
|
13
|
-
if (field.hasBoundValue() && !field.getDirectiveArg('value', 'passive')) return false;
|
|
14
13
|
const modelRef = field.getModelRef();
|
|
15
14
|
if (modelRef && !modelRef.isEmbedded() && !modelRef.isEntity()) return false;
|
|
16
15
|
return true;
|
|
@@ -51,9 +50,9 @@ module.exports = (schema) => {
|
|
|
51
50
|
${model.getFields().filter(field => field.hasGQLScope('r')).map(field => `${field.getName()}${field.getExtendArgs()}: ${field.getPayloadType()}`)}
|
|
52
51
|
}
|
|
53
52
|
type ${model.getName()}Connection {
|
|
53
|
+
count: Int!
|
|
54
54
|
pageInfo: PageInfo!
|
|
55
55
|
edges: [${model.getName()}Edge]
|
|
56
|
-
count: Int!
|
|
57
56
|
}
|
|
58
57
|
type ${model.getName()}Edge {
|
|
59
58
|
node: ${model.getName()}
|
|
@@ -133,21 +132,20 @@ module.exports = (schema) => {
|
|
|
133
132
|
const isConnection = field.isConnection();
|
|
134
133
|
|
|
135
134
|
return Object.assign(def, {
|
|
136
|
-
[fieldName]: (
|
|
137
|
-
if (fieldName === 'id') return autograph.legacyMode ?
|
|
138
|
-
|
|
139
|
-
const $fieldName = `$${fieldName}`;
|
|
135
|
+
[fieldName]: (doc, args, { autograph }, info) => {
|
|
136
|
+
if (fieldName === 'id') return autograph.legacyMode ? doc.id : toGUID(modelName, doc.id);
|
|
140
137
|
|
|
138
|
+
// If this field is a connection we return thunks in order to delay query
|
|
139
|
+
// until the "Connection" resolver (below) is run
|
|
141
140
|
if (isConnection) {
|
|
142
141
|
return {
|
|
143
|
-
args,
|
|
144
|
-
edges:
|
|
145
|
-
pageInfo:
|
|
146
|
-
count: root[`${$fieldName}:count`], // Thunk to $$count
|
|
142
|
+
count: () => field.count(autograph.resolver, doc, args),
|
|
143
|
+
edges: () => field.resolve(autograph.resolver, doc, args),
|
|
144
|
+
pageInfo: () => field.resolve(autograph.resolver, doc, args),
|
|
147
145
|
};
|
|
148
146
|
}
|
|
149
147
|
|
|
150
|
-
return
|
|
148
|
+
return field.resolve(autograph.resolver, doc, args);
|
|
151
149
|
},
|
|
152
150
|
});
|
|
153
151
|
}, {});
|
|
@@ -164,24 +162,28 @@ module.exports = (schema) => {
|
|
|
164
162
|
return Object.assign(prev, {
|
|
165
163
|
[modelName]: fieldResolvers,
|
|
166
164
|
[`${modelName}Connection`]: {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
count: ({ count }) => count(),
|
|
166
|
+
edges: ({ edges }) => edges().then(rs => rs.map(node => ({ cursor: get(node, '$$cursor'), node }))),
|
|
167
|
+
pageInfo: ({ pageInfo }) => pageInfo().then(rs => get(rs, '$$pageInfo')),
|
|
170
168
|
},
|
|
171
169
|
});
|
|
172
170
|
}, {
|
|
173
171
|
Node: {
|
|
174
|
-
__resolveType: (
|
|
172
|
+
__resolveType: (doc, args, context, info) => doc.__typename, // eslint-disable-line no-underscore-dangle
|
|
175
173
|
},
|
|
176
174
|
|
|
177
175
|
Query: entityModels.reduce((prev, model) => {
|
|
178
176
|
return Object.assign(prev, makeQueryResolver(model.getName(), model, resolver));
|
|
179
177
|
}, {
|
|
180
|
-
node: (
|
|
178
|
+
node: (doc, args, context, info) => {
|
|
181
179
|
const { id } = args;
|
|
182
180
|
const [modelName] = fromGUID(id);
|
|
183
181
|
const model = schema.getModel(modelName);
|
|
184
|
-
return resolver.get(context, model, args, false, info)
|
|
182
|
+
return resolver.get(context, model, args, false, info).then((result) => {
|
|
183
|
+
if (result == null) return result;
|
|
184
|
+
result.__typename = modelName; // eslint-disable-line no-underscore-dangle
|
|
185
|
+
return result;
|
|
186
|
+
});
|
|
185
187
|
},
|
|
186
188
|
}),
|
|
187
189
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const Pipeline = require('../../data/Pipeline');
|
|
2
|
+
|
|
3
|
+
Pipeline.createPresets();
|
|
3
4
|
|
|
4
5
|
module.exports = (schema) => {
|
|
5
6
|
return {
|
|
@@ -7,51 +8,49 @@ module.exports = (schema) => {
|
|
|
7
8
|
scalar AutoGraphMixed
|
|
8
9
|
scalar AutoGraphDriver
|
|
9
10
|
scalar AutoGraphDateTime @field(transform: toDate)
|
|
10
|
-
enum
|
|
11
|
-
enum AutoGraphTransformEnum { ${Object.keys(Transformer.getInstances()).join(' ')} }
|
|
11
|
+
enum AutoGraphPipelineEnum { ${Object.keys(Pipeline).join(' ')} }
|
|
12
12
|
enum AutoGraphAuthzEnum { private protected public }
|
|
13
|
-
enum AutoGraphValueScopeEnum { self context }
|
|
14
13
|
enum AutoGraphOnDeleteEnum { cascade nullify restrict defer }
|
|
15
14
|
enum AutoGraphIndexEnum { unique }
|
|
16
15
|
|
|
17
16
|
directive @model(
|
|
18
|
-
|
|
17
|
+
id: String # Specify db key (default "id")
|
|
18
|
+
key: String # Specify db table/collection name
|
|
19
|
+
createdAt: String # Specify db key (default "createdAt")
|
|
20
|
+
updatedAt: String # Specify db key (default "updatedAt")
|
|
21
|
+
meta: AutoGraphMixed # Custom input "meta" field for mutations
|
|
22
|
+
embed: Boolean # Mark this an embedded model (default false)
|
|
23
|
+
persist: Boolean # Persist this model (default true)
|
|
19
24
|
gqlScope: AutoGraphMixed # Dictate how GraphQL API behaves
|
|
20
25
|
dalScope: AutoGraphMixed # Dictate how the DAL behaves
|
|
21
26
|
fieldScope: AutoGraphMixed # Dictate how a FIELD may use me
|
|
22
|
-
meta: AutoGraphMixed # Custom input 'meta' field for mutations
|
|
23
|
-
embed: Boolean # Mark this an embedded model (default false)
|
|
24
|
-
persist: Boolean # Persist this model (default true)
|
|
25
27
|
driver: AutoGraphDriver # External data driver
|
|
26
28
|
authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
|
|
27
29
|
namespace: String # Logical grouping of models that can be globbed (useful for authz)
|
|
28
|
-
|
|
29
|
-
# Override auto-gen
|
|
30
|
-
id: String
|
|
31
|
-
createdAt: String
|
|
32
|
-
updatedAt: String
|
|
33
30
|
) on OBJECT | INTERFACE
|
|
34
31
|
|
|
35
32
|
directive @field(
|
|
36
|
-
|
|
33
|
+
id: String # Specify the ModelRef this field FK References
|
|
34
|
+
key: String # Specify db key
|
|
35
|
+
persist: Boolean # Persist this field (default true)
|
|
36
|
+
connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
|
|
37
|
+
default: AutoGraphMixed # Define a default value
|
|
37
38
|
ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)
|
|
38
39
|
gqlScope: AutoGraphMixed # Dictate how GraphQL API behaves
|
|
39
40
|
dalScope: AutoGraphMixed # Dictate how the DAL behaves
|
|
40
41
|
fieldScope: AutoGraphMixed # Dictate how a FIELD may use me
|
|
41
|
-
|
|
42
|
-
default: AutoGraphMixed # Define a default value
|
|
43
|
-
connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
|
|
44
|
-
|
|
45
|
-
noRepeat: Boolean
|
|
42
|
+
onDelete: AutoGraphOnDeleteEnum # onDelete behavior
|
|
46
43
|
|
|
47
44
|
authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
|
|
48
|
-
onDelete: AutoGraphOnDeleteEnum
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
# Pipeline Structure
|
|
47
|
+
instruct: [AutoGraphPipelineEnum!]
|
|
48
|
+
destruct: [AutoGraphPipelineEnum!]
|
|
49
|
+
restruct: [AutoGraphPipelineEnum!]
|
|
50
|
+
construct: [AutoGraphPipelineEnum!]
|
|
51
|
+
serialize: [AutoGraphPipelineEnum!]
|
|
52
|
+
deserialize: [AutoGraphPipelineEnum!]
|
|
53
|
+
transform: [AutoGraphPipelineEnum!]
|
|
55
54
|
) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
|
|
56
55
|
|
|
57
56
|
directive @link(
|
|
@@ -60,13 +59,6 @@ module.exports = (schema) => {
|
|
|
60
59
|
use: AutoGraphMixed # The VALUE to use (default's to @link'd value); useful for many-to-many relationships
|
|
61
60
|
) on FIELD_DEFINITION
|
|
62
61
|
|
|
63
|
-
directive @value(
|
|
64
|
-
path: String! # The path to the data
|
|
65
|
-
# merge: Boolean # Deep merge the data? (default false - overwrite) [does not even look supported at the moment]
|
|
66
|
-
passive: Boolean # If value exists leave it alone (default false)
|
|
67
|
-
scope: AutoGraphValueScopeEnum # Where to look for the data (default self)
|
|
68
|
-
) on OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
|
|
69
|
-
|
|
70
62
|
directive @index(
|
|
71
63
|
name: String
|
|
72
64
|
on: [AutoGraphMixed!]!
|
|
@@ -17,8 +17,8 @@ module.exports = (schema) => {
|
|
|
17
17
|
return `
|
|
18
18
|
extend type ${modelName}${interfacesGQL} {
|
|
19
19
|
${id ? `id: ID! @field(key: "${id}", gqlScope: r)` : ''}
|
|
20
|
-
${createdAt ? `createdAt: AutoGraphDateTime @field(key: "${createdAt}", gqlScope: r)` : ''}
|
|
21
|
-
${updatedAt ? `updatedAt: AutoGraphDateTime @field(key: "${updatedAt}", gqlScope: r)` : ''}
|
|
20
|
+
${createdAt ? `createdAt: AutoGraphDateTime @field(key: "${createdAt}", serialize: createdAt, gqlScope: r)` : ''}
|
|
21
|
+
${updatedAt ? `updatedAt: AutoGraphDateTime @field(key: "${updatedAt}", serialize: timestamp, gqlScope: r)` : ''}
|
|
22
22
|
}
|
|
23
23
|
`;
|
|
24
24
|
}
|
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,10 +283,13 @@ 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
|
|
|
@@ -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,
|
|
@@ -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
|
});
|