@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +21 -3
  2. package/index.js +2 -6
  3. package/package.json +4 -4
  4. package/src/.DS_Store +0 -0
  5. package/src/core/EventEmitter.js +2 -4
  6. package/src/core/Resolver.js +43 -60
  7. package/src/core/Schema.js +3 -36
  8. package/src/data/.DS_Store +0 -0
  9. package/src/data/DataLoader.js +71 -32
  10. package/src/data/DataService.js +59 -58
  11. package/src/data/Field.js +71 -121
  12. package/src/data/Model.js +98 -108
  13. package/src/data/Pipeline.js +174 -0
  14. package/src/data/Type.js +19 -74
  15. package/src/driver/MongoDriver.js +21 -19
  16. package/src/graphql/.DS_Store +0 -0
  17. package/src/graphql/ast/Field.js +43 -24
  18. package/src/graphql/ast/Model.js +5 -16
  19. package/src/graphql/ast/Node.js +0 -25
  20. package/src/graphql/ast/Schema.js +107 -111
  21. package/src/graphql/extension/api.js +20 -18
  22. package/src/graphql/extension/framework.js +25 -33
  23. package/src/graphql/extension/type.js +2 -2
  24. package/src/query/Query.js +72 -14
  25. package/src/query/QueryBuilder.js +38 -30
  26. package/src/query/QueryBuilderTransaction.js +3 -3
  27. package/src/query/QueryResolver.js +92 -42
  28. package/src/query/QueryService.js +31 -34
  29. package/src/service/app.service.js +67 -9
  30. package/src/service/event.service.js +5 -79
  31. package/src/service/schema.service.js +5 -3
  32. package/src/core/Rule.js +0 -107
  33. package/src/core/SchemaDecorator.js +0 -46
  34. package/src/core/Transformer.js +0 -68
  35. package/src/data/Memoizer.js +0 -39
  36. package/src/data/ResultSet.js +0 -205
  37. package/src/data/stream/DataHydrator.js +0 -58
  38. package/src/data/stream/ResultSet.js +0 -34
  39. package/src/data/stream/ResultSetItem.js +0 -158
  40. package/src/data/stream/ResultSetItemProxy.js +0 -161
  41. package/src/graphql/ast/SchemaDecorator.js +0 -141
  42. 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 Rule = require('../core/Rule');
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
- getRules() {
12
- const rules = [];
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
- if (scalarType) {
36
- Object.entries(scalarType.getDirectiveArgs('field', {})).forEach(([key, value]) => {
37
- if (!Array.isArray(value)) value = [value];
38
- if (key === 'transform') transformers.push(...value.map(t => Transformer.getInstances()[t]));
39
- });
40
- }
41
-
42
- return transformers;
43
- }
44
-
45
- getSerializers() {
46
- const transformers = [];
47
- const scalarType = this.field.getScalarRef();
48
-
49
- if (scalarType) {
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 { get, has } = require('lodash');
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 (has(args[args.length - 1], 'debug')) console.log(collection, method, JSON.stringify(args, null, 2));
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
- if (typeof value === 'string') { return globToRegex(value, { nocase: true, regex: true }); }
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
- const value = where[from];
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
- const stype = String((type === 'Float' || type === 'Int' ? 'Number' : type)).toLowerCase();
161
- if (String(typeof value) === `${stype}`) return prev;
162
- return Object.assign(prev, { [from]: { $toString: `$${from}` } });
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, shape, after, before, first } = query;
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;
Binary file
@@ -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
- return this.schema.getModel(this.getType());
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.hasBoundValue() || this.getDefaultValue() != null);
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
  };
@@ -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
  };
@@ -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 { nvl, uvl } = require('../../service/app.service');
5
- const { validateSchema, makeExecutableSchema, mergeASTSchema, mergeASTArray } = require('../../service/graphql.service');
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
- const loadFile = file => FS.readFileSync(file, 'utf8');
10
- const reqFile = file => require(file); // eslint-disable-line global-require,import/no-dynamic-require
11
-
12
- module.exports = class Schema extends Node {
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
- // Ensure schema
15
- schema.resolvers = schema.resolvers || {};
16
- schema.schemaDirectives = schema.schemaDirectives || {};
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
- initialize() {
26
- const definitions = this.ast.definitions.map(d => new Node(d));
27
- this.models = definitions.filter(d => d.isModel()).map(d => new Model(this, d.getAST()));
28
- this.modelsByName = this.models.reduce((prev, model) => Object.assign(prev, { [model.getName()]: model }), {});
29
- this.modelsByKey = this.models.reduce((prev, model) => Object.assign(prev, { [model.getKey()]: model }), {});
30
- this.inputs = definitions.filter(d => d.isInput()).map(d => new Model(this, d.getAST()));
31
- this.scalars = definitions.filter(d => d.isScalar());
32
- this.enums = definitions.filter(d => d.isEnum());
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
- * This is the final call used to pass in a schema to makeExecutableSchema.
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
- getSchema() {
40
- const definitions = this.ast.definitions.map((definition) => {
41
- definition.fields = (definition.fields || []).filter((f) => {
42
- const node = new Node(f, 'field');
43
- const scope = nvl(uvl(node.getDirectiveArg('field', 'gqlScope'), 'crud'), '');
44
- return scope.indexOf('r') > -1;
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
- getEnum(name) {
89
- return this.getEnums().find(el => el.getName() === name);
90
- }
91
-
92
- getEnums() {
93
- return this.enums;
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
- getContext() {
105
- return this.schema.context;
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
- loadDir(dir, options) {
109
- // Typedefs
110
- const typeDefs = Glob.sync(`${dir}/**/*.{gql,graphql}`, options).map(file => loadFile(file)).join('\n\n');
111
-
112
- // Possibly full schema definitions
113
- const schema = Glob.sync(`${dir}/**/*.js`, options).map(file => reqFile(file)).reduce((prev, data) => {
114
- return Merge(prev, data);
115
- }, {
116
- typeDefs: typeDefs.length ? typeDefs : undefined,
117
- context: {},
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
- return this.extend(schema);
122
+ this.schema.typeDefs = { kind: Kind.DOCUMENT, definitions };
123
+ return this;
123
124
  }
124
125
 
125
- sextend(...schemas) {
126
- const definitions = schemas.filter(schema => schema.typeDefs).map(schema => mergeASTSchema(schema.typeDefs).definitions);
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
- extend(...schemas) {
133
- this.sextend(...schemas);
134
- this.initialize();
135
- return this;
130
+ toObject() {
131
+ return this.schema;
136
132
  }
137
133
 
138
- makeExecutableSchema() {
139
- return makeExecutableSchema(this.getSchema());
134
+ toString() {
135
+ return print(this.typeDefs);
140
136
  }
141
137
  };