@coderich/autograph 0.10.1 → 0.10.4
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 +4 -5
- package/index.js +0 -2
- package/package.json +4 -6
- package/src/core/Resolver.js +12 -20
- package/src/core/Schema.js +11 -3
- package/src/data/DataLoader.js +1 -3
- package/src/data/DataService.js +35 -13
- package/src/data/Field.js +13 -30
- package/src/data/Model.js +87 -46
- package/src/data/Pipeline.js +20 -10
- package/src/data/Type.js +22 -3
- package/src/driver/MongoDriver.js +9 -9
- package/src/graphql/ast/Field.js +6 -1
- package/src/graphql/ast/Model.js +2 -2
- package/src/graphql/ast/Schema.js +2 -6
- package/src/graphql/extension/api.js +2 -2
- package/src/graphql/extension/framework.js +5 -3
- package/src/graphql/extension/type.js +1 -1
- package/src/query/Query.js +10 -0
- package/src/query/QueryBuilder.js +5 -0
- package/src/query/QueryResolver.js +36 -50
- package/src/query/QueryService.js +8 -23
- package/src/service/app.service.js +5 -6
- package/src/service/event.service.js +37 -6
- package/src/service/graphql.service.js +0 -9
- package/src/core/GraphQL.js +0 -21
package/src/data/Type.js
CHANGED
|
@@ -11,20 +11,39 @@ module.exports = class extends Type {
|
|
|
11
11
|
const type = this.field.getType();
|
|
12
12
|
const enumType = this.field.getEnumRef();
|
|
13
13
|
const scalarType = this.field.getScalarRef();
|
|
14
|
-
const structures = {
|
|
14
|
+
const structures = {
|
|
15
|
+
validators: [],
|
|
16
|
+
instructs: [],
|
|
17
|
+
restructs: [],
|
|
18
|
+
destructs: [],
|
|
19
|
+
constructs: [],
|
|
20
|
+
normalizers: [],
|
|
21
|
+
$serializers: [],
|
|
22
|
+
$deserializers: [],
|
|
23
|
+
serializers: [],
|
|
24
|
+
deserializers: [],
|
|
25
|
+
transforms: [],
|
|
26
|
+
};
|
|
15
27
|
|
|
16
|
-
|
|
28
|
+
// Built-in pipelines
|
|
29
|
+
structures.castValue = Pipeline.castValue;
|
|
30
|
+
structures.defaultValue = Pipeline.defaultValue;
|
|
31
|
+
structures.ensureArrayValue = Pipeline.ensureArrayValue;
|
|
32
|
+
|
|
33
|
+
if (enumType) structures.validators.push(Pipeline.define(`allow:${type}`, Pipeline.Allow(...enumType.getValue()), { configurable: true }));
|
|
17
34
|
if (!scalarType) return structures;
|
|
18
35
|
|
|
19
36
|
return Object.entries(scalarType.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
|
|
20
37
|
if (!Array.isArray(value)) value = [value];
|
|
38
|
+
if (key === 'validate') prev.validators.push(...value.map(t => Pipeline[t]));
|
|
21
39
|
if (key === 'instruct') prev.instructs.push(...value.map(t => Pipeline[t]));
|
|
22
40
|
if (key === 'restruct') prev.restructs.push(...value.map(t => Pipeline[t]));
|
|
23
41
|
if (key === 'destruct') prev.destructs.push(...value.map(t => Pipeline[t]));
|
|
24
42
|
if (key === 'construct') prev.constructs.push(...value.map(t => Pipeline[t]));
|
|
43
|
+
if (key === 'transform') prev.transforms.push(...value.map(t => Pipeline[t]));
|
|
44
|
+
if (key === 'normalize') prev.normalizers.push(...value.map(t => Pipeline[t]));
|
|
25
45
|
if (key === 'serialize') prev.serializers.push(...value.map(t => Pipeline[t]));
|
|
26
46
|
if (key === 'deserialize') prev.deserializers.push(...value.map(t => Pipeline[t]));
|
|
27
|
-
if (key === 'transform') prev.transformers.push(...value.map(t => Pipeline[t]));
|
|
28
47
|
return prev;
|
|
29
48
|
}, structures);
|
|
30
49
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const Util = require('util');
|
|
2
2
|
const { get } = require('lodash');
|
|
3
|
-
const { MongoClient,
|
|
3
|
+
const { MongoClient, ObjectId } = require('mongodb');
|
|
4
4
|
const { map, ensureArray, proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
|
|
5
5
|
|
|
6
6
|
module.exports = class MongoDriver {
|
|
@@ -64,15 +64,12 @@ module.exports = class MongoDriver {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
createOne({ model, input, options, flags }) {
|
|
67
|
+
// console.log(JSON.stringify(input, null, 2));
|
|
67
68
|
return this.query(model, 'insertOne', input, options, flags).then(result => Object.assign(input, { id: result.insertedId }));
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
updateOne({ model, where, $doc, options, flags }) {
|
|
71
|
-
const $update =
|
|
72
|
-
Object.assign(prev.$set, { [key]: value });
|
|
73
|
-
return prev;
|
|
74
|
-
}, { $set: {} });
|
|
75
|
-
|
|
72
|
+
const $update = { $set: $doc };
|
|
76
73
|
return this.query(model, 'updateOne', where, $update, options, flags).then(() => $doc);
|
|
77
74
|
}
|
|
78
75
|
|
|
@@ -126,10 +123,10 @@ module.exports = class MongoDriver {
|
|
|
126
123
|
}
|
|
127
124
|
|
|
128
125
|
static idValue(value) {
|
|
129
|
-
if (value instanceof
|
|
126
|
+
if (value instanceof ObjectId) return value;
|
|
130
127
|
|
|
131
128
|
try {
|
|
132
|
-
const id =
|
|
129
|
+
const id = ObjectId(value);
|
|
133
130
|
return id;
|
|
134
131
|
} catch (e) {
|
|
135
132
|
return value;
|
|
@@ -142,7 +139,10 @@ module.exports = class MongoDriver {
|
|
|
142
139
|
const value = Reflect.get(target, prop, rec);
|
|
143
140
|
if (typeof value === 'function') return value.bind(target);
|
|
144
141
|
const $value = map(value, v => (typeof v === 'string' ? globToRegex(v, { nocase: true, regex: true }) : v));
|
|
145
|
-
if (Array.isArray($value))
|
|
142
|
+
if (Array.isArray($value)) {
|
|
143
|
+
// console.log(Util.inspect({ value, $value }, { depth: null, showHidden: false, colors: true }));
|
|
144
|
+
return { $in: $value };
|
|
145
|
+
}
|
|
146
146
|
return $value;
|
|
147
147
|
},
|
|
148
148
|
}).toObject();
|
package/src/graphql/ast/Field.js
CHANGED
|
@@ -173,20 +173,25 @@ module.exports = class Field extends Node {
|
|
|
173
173
|
return this.getGQLType();
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
finalize() {
|
|
177
177
|
this.props = {
|
|
178
|
+
key: this.getKey(),
|
|
178
179
|
name: this.getName(),
|
|
179
180
|
type: this.getType(),
|
|
181
|
+
model: this.model,
|
|
180
182
|
datatype: this.getDataType(),
|
|
181
183
|
defaultValue: this.getDefaultValue(),
|
|
184
|
+
isEnum: this.isEnum(),
|
|
182
185
|
isArray: this.isArray(),
|
|
183
186
|
isScalar: this.isScalar(),
|
|
184
187
|
isVirtual: this.isVirtual(),
|
|
185
188
|
isRequired: this.isRequired(),
|
|
186
189
|
isEmbedded: this.isEmbedded(),
|
|
190
|
+
isBasicType: this.isBasicType(),
|
|
187
191
|
isIdField: this.isIdField(),
|
|
188
192
|
isPrimaryKeyId: this.isPrimaryKeyId(),
|
|
189
193
|
isPersistable: this.isPersistable(),
|
|
194
|
+
idModel: this.getIdModel(),
|
|
190
195
|
modelRef: this.getModelRef(),
|
|
191
196
|
virtualRef: this.getVirtualRef(),
|
|
192
197
|
virtualField: this.getVirtualField(),
|
package/src/graphql/ast/Model.js
CHANGED
|
@@ -2,7 +2,7 @@ const FS = require('fs');
|
|
|
2
2
|
const Glob = require('glob');
|
|
3
3
|
const Merge = require('deepmerge');
|
|
4
4
|
const { Kind, print, parse, visit } = require('graphql');
|
|
5
|
-
const { mergeASTArray
|
|
5
|
+
const { mergeASTArray } = require('../../service/graphql.service');
|
|
6
6
|
const { deleteKeys } = require('../../service/app.service');
|
|
7
7
|
const frameworkExt = require('../extension/framework');
|
|
8
8
|
const typeExt = require('../extension/type');
|
|
@@ -90,7 +90,6 @@ module.exports = class Schema extends TypeDefApi {
|
|
|
90
90
|
*/
|
|
91
91
|
initialize() {
|
|
92
92
|
super.initialize(this.schema.typeDefs);
|
|
93
|
-
this.getModels().forEach(model => model.initialize());
|
|
94
93
|
return this;
|
|
95
94
|
}
|
|
96
95
|
|
|
@@ -119,14 +118,11 @@ module.exports = class Schema extends TypeDefApi {
|
|
|
119
118
|
},
|
|
120
119
|
});
|
|
121
120
|
|
|
121
|
+
this.getModels().forEach(model => model.finalize());
|
|
122
122
|
this.schema.typeDefs = { kind: Kind.DOCUMENT, definitions };
|
|
123
123
|
return this;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
makeExecutableSchema() {
|
|
127
|
-
return makeExecutableSchema(this.schema);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
126
|
toObject() {
|
|
131
127
|
return this.schema;
|
|
132
128
|
}
|
|
@@ -163,8 +163,8 @@ module.exports = (schema) => {
|
|
|
163
163
|
[modelName]: fieldResolvers,
|
|
164
164
|
[`${modelName}Connection`]: {
|
|
165
165
|
count: ({ count }) => count(),
|
|
166
|
-
edges: ({ edges }) => edges().then(rs => rs.map(node => ({ cursor: get(node, '
|
|
167
|
-
pageInfo: ({ pageInfo }) => pageInfo().then(rs => get(rs, '
|
|
166
|
+
edges: ({ edges }) => edges().then(rs => rs.map(node => ({ cursor: get(node, '$cursor'), node }))),
|
|
167
|
+
pageInfo: ({ pageInfo }) => pageInfo().then(rs => get(rs, '$pageInfo')),
|
|
168
168
|
},
|
|
169
169
|
});
|
|
170
170
|
}, {
|
|
@@ -31,11 +31,11 @@ module.exports = (schema) => {
|
|
|
31
31
|
|
|
32
32
|
directive @field(
|
|
33
33
|
id: String # Specify the ModelRef this field FK References
|
|
34
|
+
ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)
|
|
34
35
|
key: String # Specify db key
|
|
35
36
|
persist: Boolean # Persist this field (default true)
|
|
36
37
|
connection: Boolean # Treat this field as a connection type (default false - rolling this out slowly)
|
|
37
38
|
default: AutoGraphMixed # Define a default value
|
|
38
|
-
ref: AutoGraphMixed # Specify the modelRef field's name (overrides isEmbedded)
|
|
39
39
|
gqlScope: AutoGraphMixed # Dictate how GraphQL API behaves
|
|
40
40
|
dalScope: AutoGraphMixed # Dictate how the DAL behaves
|
|
41
41
|
fieldScope: AutoGraphMixed # Dictate how a FIELD may use me
|
|
@@ -44,13 +44,15 @@ module.exports = (schema) => {
|
|
|
44
44
|
authz: AutoGraphAuthzEnum # Access level used for authorization (default: private)
|
|
45
45
|
|
|
46
46
|
# Pipeline Structure
|
|
47
|
+
validate: [AutoGraphPipelineEnum!]
|
|
47
48
|
instruct: [AutoGraphPipelineEnum!]
|
|
48
|
-
destruct: [AutoGraphPipelineEnum!]
|
|
49
49
|
restruct: [AutoGraphPipelineEnum!]
|
|
50
|
+
destruct: [AutoGraphPipelineEnum!]
|
|
50
51
|
construct: [AutoGraphPipelineEnum!]
|
|
52
|
+
transform: [AutoGraphPipelineEnum!]
|
|
53
|
+
normalize: [AutoGraphPipelineEnum!]
|
|
51
54
|
serialize: [AutoGraphPipelineEnum!]
|
|
52
55
|
deserialize: [AutoGraphPipelineEnum!]
|
|
53
|
-
transform: [AutoGraphPipelineEnum!]
|
|
54
56
|
) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | SCALAR
|
|
55
57
|
|
|
56
58
|
directive @link(
|
|
@@ -17,7 +17,7 @@ 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}",
|
|
20
|
+
${createdAt ? `createdAt: AutoGraphDateTime @field(key: "${createdAt}", construct: createdAt, gqlScope: r)` : ''}
|
|
21
21
|
${updatedAt ? `updatedAt: AutoGraphDateTime @field(key: "${updatedAt}", serialize: timestamp, gqlScope: r)` : ''}
|
|
22
22
|
}
|
|
23
23
|
`;
|
package/src/query/Query.js
CHANGED
|
@@ -272,6 +272,16 @@ module.exports = class Query {
|
|
|
272
272
|
return this;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
payload(payload) {
|
|
276
|
+
this.props.payload = payload;
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
result(result) {
|
|
281
|
+
this.props.result = result;
|
|
282
|
+
return this;
|
|
283
|
+
}
|
|
284
|
+
|
|
275
285
|
$doc($doc) {
|
|
276
286
|
this.props.$doc = $doc;
|
|
277
287
|
return this;
|
|
@@ -10,6 +10,7 @@ const { unravelObject } = require('../service/app.service');
|
|
|
10
10
|
*/
|
|
11
11
|
module.exports = class QueryBuilder {
|
|
12
12
|
constructor(resolver, model) {
|
|
13
|
+
this.terminated = false; // Prevent accidental re-use of the QueryBuilder
|
|
13
14
|
this.query = new Query({ model, resolver });
|
|
14
15
|
|
|
15
16
|
// Chainable commands
|
|
@@ -57,6 +58,10 @@ module.exports = class QueryBuilder {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
execute(cmd, args) {
|
|
61
|
+
// Do not allow re-use
|
|
62
|
+
if (this.terminated) return Promise.reject(new Error('This query has already been executed'));
|
|
63
|
+
|
|
64
|
+
this.terminated = true;
|
|
60
65
|
let method, crud, input, flags = {};
|
|
61
66
|
const { id, where } = this.query.toObject();
|
|
62
67
|
|
|
@@ -16,7 +16,7 @@ module.exports = class QueryResolver {
|
|
|
16
16
|
const { args } = query.toObject();
|
|
17
17
|
const [,,, info] = args;
|
|
18
18
|
|
|
19
|
-
switch (getGQLReturnType(info.returnType)) {
|
|
19
|
+
switch (getGQLReturnType(`${info.returnType}`)) {
|
|
20
20
|
case 'array': {
|
|
21
21
|
return new QueryResolver(this.query.clone().method('findMany')).resolve();
|
|
22
22
|
}
|
|
@@ -36,42 +36,35 @@ module.exports = class QueryResolver {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return createSystemEvent('Query', { query }, () => {
|
|
39
|
+
findOne(query) {
|
|
40
|
+
return createSystemEvent('Query', { query }, async () => {
|
|
43
41
|
return this.resolver.resolve(query);
|
|
44
42
|
});
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return createSystemEvent('Query', { query }, () => {
|
|
45
|
+
findMany(query) {
|
|
46
|
+
return createSystemEvent('Query', { query }, async () => {
|
|
51
47
|
return this.resolver.resolve(query);
|
|
52
48
|
});
|
|
53
49
|
}
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return createSystemEvent('Query', { query }, () => {
|
|
51
|
+
count(query) {
|
|
52
|
+
return createSystemEvent('Query', { query }, async () => {
|
|
59
53
|
return this.resolver.resolve(query);
|
|
60
54
|
});
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
createOne(query) {
|
|
64
58
|
const { model, input } = query.toObject();
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
query.
|
|
74
|
-
return doc;
|
|
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)));
|
|
75
68
|
});
|
|
76
69
|
}
|
|
77
70
|
|
|
@@ -84,17 +77,16 @@ module.exports = class QueryResolver {
|
|
|
84
77
|
|
|
85
78
|
async updateOne(query) {
|
|
86
79
|
const { model, match, input } = query.toObject();
|
|
80
|
+
const inputShape = model.getShape('update', 'input');
|
|
81
|
+
const docShape = model.getShape('update', 'doc');
|
|
87
82
|
|
|
88
|
-
return this.resolver.match(model).match(match).one({ required: true }).then(
|
|
89
|
-
const
|
|
90
|
-
const merged = model.shapeObject(shape, mergeDeep(doc, input), query);
|
|
91
|
-
|
|
92
|
-
await QueryService.resolveQuery(query);
|
|
83
|
+
return this.resolver.match(model).match(match).one({ required: true }).then((doc) => {
|
|
84
|
+
const merged = mergeDeep(doc, input);
|
|
93
85
|
|
|
94
86
|
return createSystemEvent('Mutation', { query: query.doc(doc).merged(merged) }, async () => {
|
|
95
|
-
const
|
|
96
|
-
await model.
|
|
97
|
-
return this.resolver.resolve(query.$doc(
|
|
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)));
|
|
98
90
|
});
|
|
99
91
|
});
|
|
100
92
|
}
|
|
@@ -110,11 +102,9 @@ module.exports = class QueryResolver {
|
|
|
110
102
|
}
|
|
111
103
|
|
|
112
104
|
deleteOne(query) {
|
|
113
|
-
const { model, id
|
|
114
|
-
|
|
115
|
-
return this.resolver.match(model).id(id).flags(flags).one({ required: true }).then(async (doc) => {
|
|
116
|
-
await QueryService.resolveQuery(query);
|
|
105
|
+
const { model, id } = query.toObject();
|
|
117
106
|
|
|
107
|
+
return this.resolver.match(model).id(id).one({ required: true }).then(async (doc) => {
|
|
118
108
|
return createSystemEvent('Mutation', { query: query.doc(doc) }, () => {
|
|
119
109
|
return QueryService.resolveReferentialIntegrity(query).then(() => {
|
|
120
110
|
return this.resolver.resolve(query).then(() => doc);
|
|
@@ -186,7 +176,10 @@ module.exports = class QueryResolver {
|
|
|
186
176
|
}
|
|
187
177
|
|
|
188
178
|
splice(query) {
|
|
189
|
-
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');
|
|
190
183
|
const [key, from, to] = args;
|
|
191
184
|
|
|
192
185
|
// Can only splice arrays
|
|
@@ -194,20 +187,16 @@ module.exports = class QueryResolver {
|
|
|
194
187
|
const isArray = field.isArray();
|
|
195
188
|
if (!isArray) throw Boom.badRequest(`Cannot splice field '${model}.${field}'`);
|
|
196
189
|
|
|
197
|
-
return this.resolver.match(model).match(match).
|
|
190
|
+
return this.resolver.match(model).match(match).one({ required: true }).then(async (doc) => {
|
|
198
191
|
const array = get(doc, key) || [];
|
|
199
|
-
const
|
|
200
|
-
const $
|
|
201
|
-
const $from = model.shapeObject(paramShape, { [key]: from }, query)[key] || from;
|
|
192
|
+
const $to = model.shapeObject(spliceShape, { [key]: to }, query)[key] || to;
|
|
193
|
+
const $from = model.shapeObject(spliceShape, { [key]: from }, query)[key] || from;
|
|
202
194
|
set(doc, key, DataService.spliceEmbeddedArray(array, $from, $to));
|
|
203
195
|
|
|
204
|
-
await QueryService.resolveQuery(query);
|
|
205
|
-
|
|
206
196
|
return createSystemEvent('Mutation', { query: query.method('updateOne').doc(doc).merged(doc) }, async () => {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return this.resolver.resolve(query.$doc($doc));
|
|
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)));
|
|
211
200
|
});
|
|
212
201
|
});
|
|
213
202
|
}
|
|
@@ -223,9 +212,6 @@ module.exports = class QueryResolver {
|
|
|
223
212
|
async resolve() {
|
|
224
213
|
const { model, method, flags } = this.query.toObject();
|
|
225
214
|
|
|
226
|
-
// const resolveQueryMethods = ['findOne', 'findMany', 'count', 'createOne', 'updateOne', 'deleteOne', 'splice'];
|
|
227
|
-
// if (resolveQueryMethods.indexOf(method) > -1) await QueryService.resolveQuery(this.query);
|
|
228
|
-
|
|
229
215
|
return this[method](this.query).then((data) => {
|
|
230
216
|
if (flags.required && isEmpty(data)) throw Boom.notFound(`${model} Not Found`);
|
|
231
217
|
if (data == null) return null; // Explicitly return null here
|
|
@@ -6,7 +6,7 @@ const { keyPaths, ensureArray, isPlainObject } = require('../service/app.service
|
|
|
6
6
|
* This can happen because the where clause reaches into the schema via refs/virtual refs
|
|
7
7
|
*/
|
|
8
8
|
exports.resolveWhereClause = (query) => {
|
|
9
|
-
const { resolver, model, match: where = {}
|
|
9
|
+
const { resolver, model, match: where = {} } = query.toObject();
|
|
10
10
|
const shape = model.getShape('create', 'where');
|
|
11
11
|
|
|
12
12
|
const $where = Object.entries(where).reduce((prev, [from, value]) => {
|
|
@@ -16,12 +16,12 @@ exports.resolveWhereClause = (query) => {
|
|
|
16
16
|
const { isVirtual, isEmbedded, modelRef, virtualRef } = el.field.toObject();
|
|
17
17
|
|
|
18
18
|
if (isVirtual) {
|
|
19
|
-
const ids = Promise.all(ensureArray(value).map(v => resolver.match(modelRef).where(isPlainObject(v) ? v : { id: v }).many(
|
|
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)));
|
|
20
20
|
return Object.assign(prev, { id: ids });
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (modelRef && !isEmbedded) {
|
|
24
|
-
const ids = Promise.all(ensureArray(value).map(v => (isPlainObject(v) ? resolver.match(modelRef).where(v).many(
|
|
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
25
|
return Object.assign(prev, { [from]: ids });
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -67,7 +67,7 @@ exports.resolveSortBy = (query) => {
|
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
exports.resolveReferentialIntegrity = (query) => {
|
|
70
|
-
const { id, model, resolver, transaction
|
|
70
|
+
const { id, model, resolver, transaction } = query.toObject();
|
|
71
71
|
const txn = resolver.transaction(transaction);
|
|
72
72
|
|
|
73
73
|
return new Promise((resolve, reject) => {
|
|
@@ -79,18 +79,18 @@ exports.resolveReferentialIntegrity = (query) => {
|
|
|
79
79
|
switch (op) {
|
|
80
80
|
case 'cascade': {
|
|
81
81
|
if (isArray) {
|
|
82
|
-
txn.match(ref).where($where).
|
|
82
|
+
txn.match(ref).where($where).pull(fieldStr, id);
|
|
83
83
|
} else {
|
|
84
|
-
txn.match(ref).where($where).
|
|
84
|
+
txn.match(ref).where($where).remove();
|
|
85
85
|
}
|
|
86
86
|
break;
|
|
87
87
|
}
|
|
88
88
|
case 'nullify': {
|
|
89
|
-
txn.match(ref).where($where).
|
|
89
|
+
txn.match(ref).where($where).save({ [fieldStr]: null });
|
|
90
90
|
break;
|
|
91
91
|
}
|
|
92
92
|
case 'restrict': {
|
|
93
|
-
txn.match(ref).where($where).
|
|
93
|
+
txn.match(ref).where($where).count().then(count => (count ? reject(new Error('Restricted')) : count));
|
|
94
94
|
break;
|
|
95
95
|
}
|
|
96
96
|
case 'defer': {
|
|
@@ -109,18 +109,3 @@ exports.resolveReferentialIntegrity = (query) => {
|
|
|
109
109
|
}
|
|
110
110
|
});
|
|
111
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
|
-
};
|
|
@@ -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');
|
|
@@ -95,8 +95,7 @@ exports.map = (mixed, fn) => {
|
|
|
95
95
|
if (mixed == null) return mixed;
|
|
96
96
|
const isArray = Array.isArray(mixed);
|
|
97
97
|
const arr = isArray ? mixed : [mixed];
|
|
98
|
-
|
|
99
|
-
const results = arr.map((el, i, a) => fn(el, isArray ? i : undefined, isArray ? a : undefined));
|
|
98
|
+
const results = isArray ? arr.map((...args) => fn(...args)) : arr.map(el => fn(el));
|
|
100
99
|
return isArray ? results : results[0];
|
|
101
100
|
};
|
|
102
101
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const QueryService = require('../query/QueryService');
|
|
1
2
|
const EventEmitter = require('../core/EventEmitter');
|
|
2
3
|
const { ucFirst } = require('./app.service');
|
|
3
4
|
|
|
@@ -8,19 +9,49 @@ const systemEvent = new EventEmitter().setMaxListeners(100).on('system', async (
|
|
|
8
9
|
next(await eventEmitter.emit(type, data)); // Return result from user-defined middleware
|
|
9
10
|
});
|
|
10
11
|
|
|
12
|
+
const makeEvent = (mixed) => {
|
|
13
|
+
const { query } = mixed;
|
|
14
|
+
const event = query.toObject();
|
|
15
|
+
event.query = query;
|
|
16
|
+
return event;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const makeMiddleware = () => {
|
|
20
|
+
return (mixed) => {
|
|
21
|
+
const { query } = mixed;
|
|
22
|
+
const { model, native, sort, match, batch } = query.toObject();
|
|
23
|
+
|
|
24
|
+
return new Promise(async (resolve) => {
|
|
25
|
+
if (!native) {
|
|
26
|
+
const whereShape = model.getShape('create', 'where');
|
|
27
|
+
const $where = batch ? match : await QueryService.resolveWhereClause(query);
|
|
28
|
+
const $$where = model.shapeObject(whereShape, $where, query);
|
|
29
|
+
query.match($$where);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (sort) {
|
|
33
|
+
query.$sort(QueryService.resolveSortBy(query));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
resolve();
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
11
41
|
//
|
|
12
42
|
exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
13
43
|
let event = mixed;
|
|
44
|
+
let middleware = () => Promise.resolve();
|
|
14
45
|
const type = ucFirst(name);
|
|
15
46
|
|
|
16
|
-
if (name !== '
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
event.query = query;
|
|
47
|
+
if (name !== 'Response') {
|
|
48
|
+
event = makeEvent(mixed);
|
|
49
|
+
middleware = makeMiddleware();
|
|
20
50
|
}
|
|
21
51
|
|
|
22
|
-
return systemEvent.emit('system', { type: `pre${type}`, data: event }).then((result) => {
|
|
23
|
-
|
|
52
|
+
return systemEvent.emit('system', { type: `pre${type}`, data: event }).then(async (result) => {
|
|
53
|
+
if (result !== undefined) return result; // Allowing middleware to dictate result
|
|
54
|
+
return middleware(mixed).then(thunk);
|
|
24
55
|
}).then((result) => {
|
|
25
56
|
event.result = result;
|
|
26
57
|
if (event.crud === 'create') event.doc = event.query.toObject().doc;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const { get } = require('lodash');
|
|
2
2
|
const { Kind, parse, print } = require('graphql');
|
|
3
|
-
const { validate } = require('graphql/validation');
|
|
4
|
-
const { makeExecutableSchema } = require('@graphql-tools/schema');
|
|
5
3
|
|
|
6
4
|
//
|
|
7
5
|
const mergePairs = [
|
|
@@ -81,13 +79,6 @@ exports.mergeASTArray = (arr) => {
|
|
|
81
79
|
}, []).filter(el => !el.deleteFlag);
|
|
82
80
|
};
|
|
83
81
|
|
|
84
|
-
exports.validateSchema = (ast) => {
|
|
85
|
-
const errs = validate(makeExecutableSchema(ast), ast.typeDefs).filter(({ message }) => message.indexOf('not executable') === -1).map(({ message }) => message);
|
|
86
|
-
if (errs.length) throw new Error(errs.join('\n'));
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
exports.makeExecutableSchema = makeExecutableSchema;
|
|
90
|
-
|
|
91
82
|
exports.toAST = (a) => {
|
|
92
83
|
if (typeof a === 'string') return parse(a);
|
|
93
84
|
if (Array.isArray(a)) return parse(a.map(e => exports.toGQL(e)).join('\n\n'));
|
package/src/core/GraphQL.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const { graphql } = require('graphql');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GraphQL.
|
|
5
|
-
*
|
|
6
|
-
* This is a wrapper class to the underlying GraphQL Executable Schema.
|
|
7
|
-
* It can be useful for testing and/or exercising the API as an outside caller would.
|
|
8
|
-
*
|
|
9
|
-
* Reference: https://github.com/graphql/graphql-js/blob/master/src/graphql.js#L32-L33
|
|
10
|
-
*/
|
|
11
|
-
module.exports = class GraphQL {
|
|
12
|
-
constructor(schema, resolver) {
|
|
13
|
-
this.schema = schema.makeExecutableSchema();
|
|
14
|
-
this.contextValue = resolver.getContext();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
exec(source, variableValues) {
|
|
18
|
-
const { schema, contextValue = {} } = this;
|
|
19
|
-
return graphql({ schema, source, variableValues, contextValue });
|
|
20
|
-
}
|
|
21
|
-
};
|