@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
package/src/data/Type.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const Type = require('../graphql/ast/Type');
|
|
2
|
-
const
|
|
3
|
-
const Transformer = require('../core/Transformer');
|
|
2
|
+
const Pipeline = require('./Pipeline');
|
|
4
3
|
|
|
5
4
|
module.exports = class extends Type {
|
|
6
5
|
constructor(field) {
|
|
@@ -8,79 +7,25 @@ module.exports = class extends Type {
|
|
|
8
7
|
this.field = field;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const scalarType = this.field.getScalarRef();
|
|
10
|
+
getStructures() {
|
|
11
|
+
const type = this.field.getType();
|
|
14
12
|
const enumType = this.field.getEnumRef();
|
|
15
|
-
|
|
16
|
-
if (scalarType) {
|
|
17
|
-
Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
|
|
18
|
-
if (!Array.isArray(value)) value = [value];
|
|
19
|
-
if (key === 'enforce') rules.push(...value.map(r => Rule.getInstances()[r]));
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (enumType) {
|
|
24
|
-
const values = enumType.getValue();
|
|
25
|
-
rules.push(Rule.allow(...values));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return rules;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getTransformers() {
|
|
32
|
-
const transformers = [];
|
|
33
13
|
const scalarType = this.field.getScalarRef();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
|
|
51
|
-
if (!Array.isArray(value)) value = [value];
|
|
52
|
-
if (key === 'serialize') transformers.push(...value.map(t => Transformer.getInstances()[t]));
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return transformers;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
getDeserializers() {
|
|
60
|
-
const transformers = [];
|
|
61
|
-
const scalarType = this.field.getScalarRef();
|
|
62
|
-
|
|
63
|
-
if (scalarType) {
|
|
64
|
-
Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
|
|
65
|
-
if (!Array.isArray(value)) value = [value];
|
|
66
|
-
if (key === 'deserialize') transformers.push(...value.map(t => Transformer.getInstances()[t]));
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return transformers;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
getResolvers() {
|
|
74
|
-
const resolvers = [];
|
|
75
|
-
const scalarType = this.field.getScalarRef();
|
|
76
|
-
|
|
77
|
-
if (scalarType) {
|
|
78
|
-
Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
|
|
79
|
-
if (!Array.isArray(value)) value = [value];
|
|
80
|
-
if (key === 'resolve') resolvers.push(...value.map(t => Transformer.getInstances()[t]));
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return resolvers;
|
|
14
|
+
const structures = { instructs: [], restructs: [], destructs: [], constructs: [], $serializers: [], serializers: [], $deserializers: [], deserializers: [], transformers: [] };
|
|
15
|
+
|
|
16
|
+
if (enumType) structures.serializers.push(Pipeline.define(`$allow:${type}`, Pipeline.allow(...enumType.getValue()), { configurable: true }));
|
|
17
|
+
if (!scalarType) return structures;
|
|
18
|
+
|
|
19
|
+
return Object.entries(scalarType.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
|
|
20
|
+
if (!Array.isArray(value)) value = [value];
|
|
21
|
+
if (key === 'instruct') prev.instructs.push(...value.map(t => Pipeline[t]));
|
|
22
|
+
if (key === 'restruct') prev.restructs.push(...value.map(t => Pipeline[t]));
|
|
23
|
+
if (key === 'destruct') prev.destructs.push(...value.map(t => Pipeline[t]));
|
|
24
|
+
if (key === 'construct') prev.constructs.push(...value.map(t => Pipeline[t]));
|
|
25
|
+
if (key === 'serialize') prev.serializers.push(...value.map(t => Pipeline[t]));
|
|
26
|
+
if (key === 'deserialize') prev.deserializers.push(...value.map(t => Pipeline[t]));
|
|
27
|
+
if (key === 'transform') prev.transformers.push(...value.map(t => Pipeline[t]));
|
|
28
|
+
return prev;
|
|
29
|
+
}, structures);
|
|
85
30
|
}
|
|
86
31
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
const
|
|
1
|
+
const Util = require('util');
|
|
2
|
+
const { get } = require('lodash');
|
|
2
3
|
const { MongoClient, ObjectID } = require('mongodb');
|
|
3
|
-
const { proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
|
|
4
|
+
const { map, ensureArray, proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
|
|
4
5
|
|
|
5
6
|
module.exports = class MongoDriver {
|
|
6
7
|
constructor(config) {
|
|
@@ -24,7 +25,7 @@ module.exports = class MongoDriver {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
query(collection, method, ...args) {
|
|
27
|
-
if (
|
|
28
|
+
if (get(args[args.length - 1], 'debug') === true) console.log(collection, method, Util.inspect(args, { depth: null, showHidden: false, colors: true }));
|
|
28
29
|
if (method === 'aggregate') args.splice(2);
|
|
29
30
|
return this.raw(collection)[method](...args);
|
|
30
31
|
}
|
|
@@ -48,10 +49,7 @@ module.exports = class MongoDriver {
|
|
|
48
49
|
findMany(query) {
|
|
49
50
|
const { model, options = {}, flags } = query;
|
|
50
51
|
Object.assign(options, this.config.query || {});
|
|
51
|
-
|
|
52
|
-
return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query), options, flags).then((cursor) => {
|
|
53
|
-
return cursor.stream();
|
|
54
|
-
});
|
|
52
|
+
return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query), options, flags).then(cursor => cursor.stream());
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
count(query) {
|
|
@@ -142,10 +140,10 @@ module.exports = class MongoDriver {
|
|
|
142
140
|
return proxyDeep(toKeyObj(where), {
|
|
143
141
|
get(target, prop, rec) {
|
|
144
142
|
const value = Reflect.get(target, prop, rec);
|
|
145
|
-
if (Array.isArray(value)) return { $in: value };
|
|
146
143
|
if (typeof value === 'function') return value.bind(target);
|
|
147
|
-
|
|
148
|
-
return value;
|
|
144
|
+
const $value = map(value, v => (typeof v === 'string' ? globToRegex(v, { nocase: true, regex: true }) : v));
|
|
145
|
+
if (Array.isArray($value)) return { $in: $value };
|
|
146
|
+
return $value;
|
|
149
147
|
},
|
|
150
148
|
}).toObject();
|
|
151
149
|
}
|
|
@@ -153,13 +151,17 @@ module.exports = class MongoDriver {
|
|
|
153
151
|
static getAddFields(query) {
|
|
154
152
|
const { shape, where } = query;
|
|
155
153
|
|
|
156
|
-
return shape.reduce((prev, { from, type }) => {
|
|
157
|
-
|
|
154
|
+
return shape.reduce((prev, { from, type, isArray }) => {
|
|
155
|
+
// Basic checks to see if worth converting for regex
|
|
156
|
+
let value = where[from];
|
|
158
157
|
if (value === undefined) return prev;
|
|
159
158
|
if (!isScalarDataType(type)) return prev;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
|
|
160
|
+
// Do regex conversion
|
|
161
|
+
if (isArray) value = value.$in || value; // Where clause does not always use $in
|
|
162
|
+
if (!ensureArray(value).some(el => el instanceof RegExp)) return prev;
|
|
163
|
+
const conversion = isArray ? { $map: { input: `$${from}`, as: 'el', in: { $toString: '$$el' } } } : { $toString: `$${from}` };
|
|
164
|
+
return Object.assign(prev, { [from]: conversion });
|
|
163
165
|
}, {});
|
|
164
166
|
}
|
|
165
167
|
|
|
@@ -182,7 +184,7 @@ module.exports = class MongoDriver {
|
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
static aggregateQuery(query, count = false) {
|
|
185
|
-
const { where: $match, sort = {}, skip, limit, joins,
|
|
187
|
+
const { where: $match, sort = {}, skip, limit, joins, after, before, first } = query;
|
|
186
188
|
const $aggregate = [{ $match }];
|
|
187
189
|
|
|
188
190
|
// Used for $regex matching
|
|
@@ -215,9 +217,9 @@ module.exports = class MongoDriver {
|
|
|
215
217
|
if (before) $aggregate.push({ $match: { $or: Object.entries(before).reduce((prev, [key, value]) => prev.concat({ [key]: { [sort[key] === 1 ? '$lte' : '$gte']: value } }), []) } });
|
|
216
218
|
if (first) $aggregate.push({ $limit: first });
|
|
217
219
|
|
|
218
|
-
// Projection
|
|
219
|
-
const $project = MongoDriver.getProjectFields(shape);
|
|
220
|
-
$aggregate.push({ $project });
|
|
220
|
+
// // Projection
|
|
221
|
+
// const $project = MongoDriver.getProjectFields(shape);
|
|
222
|
+
// $aggregate.push({ $project });
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
return $aggregate;
|
package/src/graphql/.DS_Store
CHANGED
|
Binary file
|
package/src/graphql/ast/Field.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { get } = require('lodash');
|
|
2
1
|
const Node = require('./Node');
|
|
3
2
|
const Type = require('./Type');
|
|
4
3
|
const { uvl } = require('../../service/app.service');
|
|
@@ -44,27 +43,6 @@ module.exports = class Field extends Node {
|
|
|
44
43
|
return this.getDirectiveArg('field', 'default');
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
resolveBoundValue(query, initialValue) {
|
|
48
|
-
// If no bound value, return default value
|
|
49
|
-
const defaultValue = uvl(initialValue, this.getDefaultValue());
|
|
50
|
-
if (!this.hasBoundValue()) return defaultValue;
|
|
51
|
-
|
|
52
|
-
// Grab @value definition, if passive then check for initialValue
|
|
53
|
-
const { scope, path, passive = false } = this.getDirectiveArgs('value');
|
|
54
|
-
if (passive && initialValue !== undefined) return initialValue;
|
|
55
|
-
|
|
56
|
-
// Resolve @value
|
|
57
|
-
switch (scope) {
|
|
58
|
-
case 'context': {
|
|
59
|
-
const { resolver } = query.toObject();
|
|
60
|
-
const context = resolver.getContext();
|
|
61
|
-
const value = get(context, path);
|
|
62
|
-
return uvl((typeof value === 'function') ? value() : value, defaultValue);
|
|
63
|
-
}
|
|
64
|
-
default: return this[path];
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
46
|
// Model Methods
|
|
69
47
|
getSchema() {
|
|
70
48
|
return this.model.getSchema();
|
|
@@ -75,7 +53,8 @@ module.exports = class Field extends Node {
|
|
|
75
53
|
}
|
|
76
54
|
|
|
77
55
|
getModelRef() {
|
|
78
|
-
|
|
56
|
+
const refType = this.getDirectiveArg('field', 'id', this.getType());
|
|
57
|
+
return this.schema.getModel(refType);
|
|
79
58
|
}
|
|
80
59
|
|
|
81
60
|
getFieldRef() {
|
|
@@ -90,6 +69,10 @@ module.exports = class Field extends Node {
|
|
|
90
69
|
return model ? model.getField(this.getVirtualRef()) : null;
|
|
91
70
|
}
|
|
92
71
|
|
|
72
|
+
getIdModel() {
|
|
73
|
+
return this.getModelRef() || this.getModel();
|
|
74
|
+
}
|
|
75
|
+
|
|
93
76
|
resolveField() {
|
|
94
77
|
const field = this.getVirtualField() || this;
|
|
95
78
|
return field === this ? this : field.resolveField();
|
|
@@ -101,7 +84,7 @@ module.exports = class Field extends Node {
|
|
|
101
84
|
}
|
|
102
85
|
|
|
103
86
|
isDefaulted() {
|
|
104
|
-
return Boolean(this.
|
|
87
|
+
return Boolean(this.getDefaultValue() != null);
|
|
105
88
|
}
|
|
106
89
|
|
|
107
90
|
isRequired() {
|
|
@@ -117,6 +100,16 @@ module.exports = class Field extends Node {
|
|
|
117
100
|
return Boolean(modelRef && !this.isEmbedded());
|
|
118
101
|
}
|
|
119
102
|
|
|
103
|
+
isIdField() {
|
|
104
|
+
return this.isPrimaryKeyId() || this.isFKReference();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
isPrimaryKeyId() {
|
|
108
|
+
const key = this.getKey();
|
|
109
|
+
const idKey = this.getModel().idKey();
|
|
110
|
+
return key === idKey;
|
|
111
|
+
}
|
|
112
|
+
|
|
120
113
|
getJoinInfo() {
|
|
121
114
|
const modelRef = this.getModelRef();
|
|
122
115
|
if (!modelRef || this.isEmbedded()) return null;
|
|
@@ -179,4 +172,30 @@ module.exports = class Field extends Node {
|
|
|
179
172
|
if (this.isFKReference()) return this.isArray() ? '[ID]' : 'ID';
|
|
180
173
|
return this.getGQLType();
|
|
181
174
|
}
|
|
175
|
+
|
|
176
|
+
initialize() {
|
|
177
|
+
this.props = {
|
|
178
|
+
name: this.getName(),
|
|
179
|
+
type: this.getType(),
|
|
180
|
+
datatype: this.getDataType(),
|
|
181
|
+
defaultValue: this.getDefaultValue(),
|
|
182
|
+
isArray: this.isArray(),
|
|
183
|
+
isScalar: this.isScalar(),
|
|
184
|
+
isVirtual: this.isVirtual(),
|
|
185
|
+
isRequired: this.isRequired(),
|
|
186
|
+
isEmbedded: this.isEmbedded(),
|
|
187
|
+
isIdField: this.isIdField(),
|
|
188
|
+
isPrimaryKeyId: this.isPrimaryKeyId(),
|
|
189
|
+
isPersistable: this.isPersistable(),
|
|
190
|
+
modelRef: this.getModelRef(),
|
|
191
|
+
virtualRef: this.getVirtualRef(),
|
|
192
|
+
virtualField: this.getVirtualField(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
toObject() {
|
|
199
|
+
return this.props;
|
|
200
|
+
}
|
|
182
201
|
};
|
package/src/graphql/ast/Model.js
CHANGED
|
@@ -92,10 +92,6 @@ module.exports = class Model extends Node {
|
|
|
92
92
|
return this.getFields().filter(field => field.isDefaulted());
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
getBoundValueFields() {
|
|
96
|
-
return this.getFields().filter(field => field.hasBoundValue());
|
|
97
|
-
}
|
|
98
|
-
|
|
99
95
|
getDataRefFields() {
|
|
100
96
|
return this.getFields().filter(field => Boolean(field.getDataRef()));
|
|
101
97
|
}
|
|
@@ -104,10 +100,6 @@ module.exports = class Model extends Node {
|
|
|
104
100
|
return this.getFields().filter(field => Boolean(field.getModelRef()));
|
|
105
101
|
}
|
|
106
102
|
|
|
107
|
-
// getDataRefFields() {
|
|
108
|
-
// return this.fields.filter(field => Boolean(field.getDataRef() && !field.isEmbedded()));
|
|
109
|
-
// }
|
|
110
|
-
|
|
111
103
|
getEmbeddedFields() {
|
|
112
104
|
return this.getFields().filter(field => field.isEmbedded());
|
|
113
105
|
}
|
|
@@ -128,14 +120,6 @@ module.exports = class Model extends Node {
|
|
|
128
120
|
return this.getFields().filter(field => field.isPersistable());
|
|
129
121
|
}
|
|
130
122
|
|
|
131
|
-
getSerializeFields() {
|
|
132
|
-
return this.getFields().filter(field => field.getSerializers().length);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
getDeserializeFields() {
|
|
136
|
-
return this.getFields().filter(field => field.getDeserializers().length);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
123
|
// Misc
|
|
140
124
|
getIndexes() {
|
|
141
125
|
return this.getDirectives('index').map((d) => {
|
|
@@ -153,4 +137,9 @@ module.exports = class Model extends Node {
|
|
|
153
137
|
}, {});
|
|
154
138
|
});
|
|
155
139
|
}
|
|
140
|
+
|
|
141
|
+
initialize() {
|
|
142
|
+
this.fields.forEach(field => field.initialize());
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
156
145
|
};
|
package/src/graphql/ast/Node.js
CHANGED
|
@@ -2,7 +2,6 @@ const { get } = require('lodash');
|
|
|
2
2
|
const { Kind } = require('graphql');
|
|
3
3
|
const { nvl, uvl } = require('../../service/app.service');
|
|
4
4
|
const { mergeAST } = require('../../service/graphql.service');
|
|
5
|
-
// const Memoizer = require('../../data/Memoizer');
|
|
6
5
|
|
|
7
6
|
const operations = ['Query', 'Mutation', 'Subscription'];
|
|
8
7
|
const modelKinds = [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION, Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
|
|
@@ -18,7 +17,6 @@ module.exports = class Node {
|
|
|
18
17
|
this.toString = () => this.getName();
|
|
19
18
|
this.nodeType = nodeType;
|
|
20
19
|
this.name = get(this.ast, 'name.value');
|
|
21
|
-
// return new Memoizer(this, Object.getOwnPropertyNames(Node.prototype).filter(m => ['getContext'].indexOf(m) === -1));
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
// Basic AST Methods
|
|
@@ -130,14 +128,6 @@ module.exports = class Node {
|
|
|
130
128
|
return this.getDirectiveArg('model', 'meta');
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
getSerialize() {
|
|
134
|
-
return this.getDirectiveArg('field', 'serialize', this.getDirectiveArg('model', 'serialize'));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
getDeserialize() {
|
|
138
|
-
return this.getDirectiveArg('field', 'deserialize', this.getDirectiveArg('model', 'deserialize'));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
131
|
// Booleans
|
|
142
132
|
isModel() {
|
|
143
133
|
return Boolean(modelKinds.some(k => this.getKind() === k) && operations.every(o => this.getName() !== o));
|
|
@@ -166,13 +156,6 @@ module.exports = class Node {
|
|
|
166
156
|
return Boolean(this.getDirectiveArg('link', 'by'));
|
|
167
157
|
}
|
|
168
158
|
|
|
169
|
-
/**
|
|
170
|
-
* Does the model/field have a bound @value directive
|
|
171
|
-
*/
|
|
172
|
-
hasBoundValue() {
|
|
173
|
-
return Boolean(this.getDirective('value'));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
159
|
/**
|
|
177
160
|
* Is a model annotated with @model
|
|
178
161
|
*/
|
|
@@ -208,14 +191,6 @@ module.exports = class Node {
|
|
|
208
191
|
}
|
|
209
192
|
}
|
|
210
193
|
|
|
211
|
-
/**
|
|
212
|
-
* Can the field be changed after it's set
|
|
213
|
-
*/
|
|
214
|
-
isImmutable() {
|
|
215
|
-
const enforce = this.getDirectiveArg('field', 'enforce', '');
|
|
216
|
-
return Boolean(JSON.stringify(enforce).indexOf('immutable') > -1);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
194
|
/**
|
|
220
195
|
* Define it's behavior at the Data Access Layer
|
|
221
196
|
*
|
|
@@ -1,141 +1,137 @@
|
|
|
1
1
|
const FS = require('fs');
|
|
2
2
|
const Glob = require('glob');
|
|
3
3
|
const Merge = require('deepmerge');
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
4
|
+
const { Kind, print, parse, visit } = require('graphql');
|
|
5
|
+
const { mergeASTArray, makeExecutableSchema } = require('../../service/graphql.service');
|
|
6
|
+
const { deleteKeys } = require('../../service/app.service');
|
|
7
|
+
const frameworkExt = require('../extension/framework');
|
|
8
|
+
const typeExt = require('../extension/type');
|
|
9
|
+
const apiExt = require('../extension/api');
|
|
10
|
+
const TypeDefApi = require('./TypeDefApi');
|
|
6
11
|
const Node = require('./Node');
|
|
7
|
-
const Model = require('./Model');
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Schema
|
|
15
|
+
*
|
|
16
|
+
* This class helps facilitate dynamic modification of a schema before it is passed to makeExecutableSchema(). It allows
|
|
17
|
+
* for "intelligent" merging of schemas and exposes an API wrapper for typeDefs.
|
|
18
|
+
*
|
|
19
|
+
* A "schema" is defined by the following object attributes:
|
|
20
|
+
*
|
|
21
|
+
* typeDefs <String|Object> - GQL String or AST Object (also supports a mixed array of both)
|
|
22
|
+
* resolvers <Object> - GraphQL resolvers
|
|
23
|
+
* schemaDirectives <Object> - GraphQL directives
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
module.exports = class Schema extends TypeDefApi {
|
|
13
27
|
constructor(schema) {
|
|
14
|
-
|
|
15
|
-
schema
|
|
16
|
-
schema.
|
|
17
|
-
schema.context = schema.context || {};
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
super(schema.typeDefs);
|
|
21
|
-
this.schema = schema;
|
|
22
|
-
this.initialize();
|
|
28
|
+
super();
|
|
29
|
+
this.schema = { typeDefs: [], resolvers: {}, schemaDirectives: {} };
|
|
30
|
+
if (schema) this.mergeSchema(schema);
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Synchronously merge a schema
|
|
35
|
+
*/
|
|
36
|
+
mergeSchema(schema, options = {}) {
|
|
37
|
+
// Ensure this is a schema of sorts otherwise skip it
|
|
38
|
+
if (typeof schema !== 'string' && ['typeDefs', 'resolvers', 'schemaDirectives'].every(key => !schema[key])) return this;
|
|
39
|
+
|
|
40
|
+
// Here we want to normalize the schema into the shape { typeDefs, resolvers, schemaDirectives }
|
|
41
|
+
// We do NOT want to modify the schema object because that may cause unwanted side-effects.
|
|
42
|
+
const normalizedSchema = { ...schema };
|
|
43
|
+
if (typeof schema === 'string') normalizedSchema.typeDefs = [schema];
|
|
44
|
+
else if (schema.typeDefs && !Array.isArray(schema.typeDefs)) normalizedSchema.typeDefs = [schema.typeDefs];
|
|
45
|
+
|
|
46
|
+
// For typeDefs we want the AST so that it can be intelligently merged. Here we convert
|
|
47
|
+
// GQL strings to AST objects and also filter out anything that does not parse to AST.
|
|
48
|
+
if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) {
|
|
49
|
+
normalizedSchema.typeDefs = deleteKeys(normalizedSchema.typeDefs.map((td) => {
|
|
50
|
+
try {
|
|
51
|
+
const ast = typeof td === 'object' ? td : parse(td);
|
|
52
|
+
return ast.definitions;
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}), ['loc']).filter(Boolean).flat();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Now we're ready to merge the schema
|
|
60
|
+
const [left, right] = options.passive ? [normalizedSchema, this.schema] : [this.schema, normalizedSchema];
|
|
61
|
+
if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) this.schema.typeDefs = mergeASTArray(left.typeDefs.concat(right.typeDefs));
|
|
62
|
+
if (normalizedSchema.resolvers) this.schema.resolvers = Merge(left.resolvers, right.resolvers);
|
|
63
|
+
if (normalizedSchema.schemaDirectives) this.schema.schemaDirectives = Merge(left.schemaDirectives, right.schemaDirectives);
|
|
64
|
+
|
|
65
|
+
// Chaining
|
|
66
|
+
return this;
|
|
33
67
|
}
|
|
34
68
|
|
|
35
69
|
/**
|
|
36
|
-
*
|
|
37
|
-
* Here I'm making last-minute modifications to the schema that is exposed to the API.
|
|
70
|
+
* Asynchronously load files from a given glob pattern and merge each schema
|
|
38
71
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return
|
|
72
|
+
mergeSchemaFromFiles(globPattern, options) {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
Glob(globPattern, options, (err, files) => {
|
|
75
|
+
if (err) return reject(err);
|
|
76
|
+
|
|
77
|
+
return Promise.all(files.map((file) => {
|
|
78
|
+
return new Promise((res) => {
|
|
79
|
+
if (file.endsWith('.js')) res(require(file)); // eslint-disable-line global-require,import/no-dynamic-require
|
|
80
|
+
else res(FS.readFileSync(file, 'utf8'));
|
|
81
|
+
}).then(schema => this.mergeSchema(schema, options));
|
|
82
|
+
})).then(() => resolve(this)).catch(e => reject(e));
|
|
45
83
|
});
|
|
46
|
-
|
|
47
|
-
return definition;
|
|
48
84
|
});
|
|
49
|
-
|
|
50
|
-
const ast = Object.assign({}, this.ast, { definitions });
|
|
51
|
-
const schema = Object.assign({}, this.schema, { typeDefs: ast });
|
|
52
|
-
validateSchema(schema);
|
|
53
|
-
return schema;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
getModel(name) {
|
|
57
|
-
return this.modelsByName[name] || this.modelsByKey[name];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
getModels() {
|
|
61
|
-
return this.models;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
getModelNames() {
|
|
65
|
-
return this.getModels().map(model => model.getName());
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
getModelMap() {
|
|
69
|
-
return this.getModels().reduce((prev, model) => Object.assign(prev, { [model.getName()]: model }), {});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
getInput(name) {
|
|
73
|
-
return this.getInputs().find(input => input.getName() === name);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
getInputs() {
|
|
77
|
-
return this.inputs;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
getScalar(name) {
|
|
81
|
-
return this.getScalars().find(scalar => scalar.getName() === name);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
getScalars() {
|
|
85
|
-
return this.scalars;
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
getMarkedModels() {
|
|
97
|
-
return this.getModels().filter(model => model.isMarkedModel());
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
getEntityModels() {
|
|
101
|
-
return this.getModels().filter(model => model.isEntity());
|
|
87
|
+
/**
|
|
88
|
+
* Traverses the current schema's typeDefs in order to keep the TypeDefApi in sync. This operation
|
|
89
|
+
* only needs to be called when typeDefs have been changed and you want to keep the data model in sync.
|
|
90
|
+
*/
|
|
91
|
+
initialize() {
|
|
92
|
+
super.initialize(this.schema.typeDefs);
|
|
93
|
+
this.getModels().forEach(model => model.initialize());
|
|
94
|
+
return this;
|
|
102
95
|
}
|
|
103
96
|
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Decorate the schema with Autograph's default api/definitions
|
|
99
|
+
*/
|
|
100
|
+
decorate() {
|
|
101
|
+
this.initialize();
|
|
102
|
+
this.mergeSchema(frameworkExt(this), { passive: true });
|
|
103
|
+
this.mergeSchema(typeExt(this), { passive: true });
|
|
104
|
+
this.initialize();
|
|
105
|
+
this.mergeSchema(apiExt(this), { passive: true });
|
|
106
|
+
this.finalize();
|
|
107
|
+
return this;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
resolvers: {},
|
|
119
|
-
schemaDirectives: {},
|
|
110
|
+
/**
|
|
111
|
+
* This should be called once before passing to makeExecutableSchema()
|
|
112
|
+
*/
|
|
113
|
+
finalize() {
|
|
114
|
+
const definitions = visit(this.schema.typeDefs, {
|
|
115
|
+
[Kind.FIELD_DEFINITION]: (node) => {
|
|
116
|
+
const scope = new Node(node, 'field').getDirectiveArg('field', 'gqlScope', 'crud');
|
|
117
|
+
if (scope === null || scope.indexOf('r') === -1) return null; // Delete node
|
|
118
|
+
return false; // Stop traversing this node
|
|
119
|
+
},
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
this.schema.typeDefs = { kind: Kind.DOCUMENT, definitions };
|
|
123
|
+
return this;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.ast.definitions = mergeASTArray(this.ast.definitions.concat(...definitions));
|
|
128
|
-
this.schema.resolvers = Merge(schemas.reduce((prev, schema) => Merge(prev, schema.resolvers || {}), {}), this.schema.resolvers);
|
|
129
|
-
return this;
|
|
126
|
+
makeExecutableSchema() {
|
|
127
|
+
return makeExecutableSchema(this.schema);
|
|
130
128
|
}
|
|
131
129
|
|
|
132
|
-
|
|
133
|
-
this.
|
|
134
|
-
this.initialize();
|
|
135
|
-
return this;
|
|
130
|
+
toObject() {
|
|
131
|
+
return this.schema;
|
|
136
132
|
}
|
|
137
133
|
|
|
138
|
-
|
|
139
|
-
return
|
|
134
|
+
toString() {
|
|
135
|
+
return print(this.typeDefs);
|
|
140
136
|
}
|
|
141
137
|
};
|