@coderich/autograph 0.9.11 → 0.9.12
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/index.js +2 -0
- package/package.json +5 -1
- package/src/core/SchemaDecorator.js +46 -0
- package/src/graphql/ast/.DS_Store +0 -0
- package/src/graphql/ast/Model.js +4 -0
- package/src/graphql/ast/Schema.js +0 -1
- package/src/graphql/ast/SchemaDecorator.js +138 -0
- package/src/graphql/ast/TypeDefApi.js +93 -0
- package/src/graphql/core/.DS_Store +0 -0
- package/src/graphql/core/Field.js +20 -0
- package/src/graphql/core/GraphQL.js +21 -0
- package/src/graphql/core/Model.js +23 -0
- package/src/graphql/core/Node.js +34 -0
- package/src/graphql/core/Schema.js +63 -0
- package/src/query/QueryResolver.js +5 -1
- package/src/service/app.service.js +6 -0
- package/src/service/event.service.js +1 -0
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const Schema = require('./src/core/Schema');
|
|
2
|
+
const SchemaDecorator = require('./src/core/SchemaDecorator');
|
|
2
3
|
const GraphQL = require('./src/core/GraphQL');
|
|
3
4
|
const Resolver = require('./src/core/Resolver');
|
|
4
5
|
const Rule = require('./src/core/Rule');
|
|
@@ -8,6 +9,7 @@ const { eventEmitter: Emitter } = require('./src/service/event.service');
|
|
|
8
9
|
|
|
9
10
|
module.exports = {
|
|
10
11
|
Schema,
|
|
12
|
+
SchemaDecorator,
|
|
11
13
|
GraphQL,
|
|
12
14
|
Resolver,
|
|
13
15
|
Rule,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderich/autograph",
|
|
3
3
|
"author": "Richard Livolsi (coderich)",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.12",
|
|
5
5
|
"description": "AutoGraph",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"graphql",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"ratchet": "ratchet"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"@graphql-tools/schema": "^8.3.14",
|
|
33
34
|
"@hapi/boom": "^9.1.0",
|
|
34
35
|
"axios": "^0.21.4",
|
|
35
36
|
"dataloader": "^2.0.0",
|
|
@@ -54,6 +55,9 @@
|
|
|
54
55
|
"redis": "^2.8.0",
|
|
55
56
|
"redis-mock": "^0.47.0"
|
|
56
57
|
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"graphql": ">15"
|
|
60
|
+
},
|
|
57
61
|
"repository": {
|
|
58
62
|
"type": "git",
|
|
59
63
|
"url": "git@github.com:coderich/autograph.git"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const Model = require('../data/Model');
|
|
2
|
+
const SchemaDecorator = require('../graphql/ast/SchemaDecorator');
|
|
3
|
+
const { identifyOnDeletes } = require('../service/schema.service');
|
|
4
|
+
const { createSystemEvent } = require('../service/event.service');
|
|
5
|
+
|
|
6
|
+
// Export class
|
|
7
|
+
module.exports = class extends SchemaDecorator {
|
|
8
|
+
constructor(schema, stores) {
|
|
9
|
+
super(schema);
|
|
10
|
+
|
|
11
|
+
// Create drivers
|
|
12
|
+
this.drivers = Object.entries(stores).reduce((prev, [key, value]) => {
|
|
13
|
+
const { Driver } = value;
|
|
14
|
+
|
|
15
|
+
return Object.assign(prev, {
|
|
16
|
+
[key]: {
|
|
17
|
+
dao: new Driver(value, this),
|
|
18
|
+
idKey: Driver.idKey,
|
|
19
|
+
idValue: Driver.idValue,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}, {});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setup() {
|
|
26
|
+
return createSystemEvent('Setup', this, () => {
|
|
27
|
+
const entities = this.models.filter(m => m.isEntity());
|
|
28
|
+
|
|
29
|
+
// Create model indexes
|
|
30
|
+
return Promise.all(entities.map(async (model) => {
|
|
31
|
+
const key = model.getKey();
|
|
32
|
+
const indexes = model.getIndexes();
|
|
33
|
+
const driver = model.getDriver();
|
|
34
|
+
if (driver.createCollection) await driver.createCollection(key);
|
|
35
|
+
return driver.createIndexes(key, indexes);
|
|
36
|
+
}));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
initialize() {
|
|
41
|
+
super.initialize();
|
|
42
|
+
this.models = super.getModels().map(model => new Model(this, model, this.drivers[model.getDriverName()]));
|
|
43
|
+
this.models.forEach(model => model.referentialIntegrity(identifyOnDeletes(this.models, model)));
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
Binary file
|
package/src/graphql/ast/Model.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const FS = require('fs');
|
|
2
2
|
const Glob = require('glob');
|
|
3
|
-
const Path = require('path');
|
|
4
3
|
const Merge = require('deepmerge');
|
|
5
4
|
const { nvl, uvl } = require('../../service/app.service');
|
|
6
5
|
const { validateSchema, makeExecutableSchema, mergeASTSchema, mergeASTArray } = require('../../service/graphql.service');
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const FS = require('fs');
|
|
2
|
+
const Glob = require('glob');
|
|
3
|
+
const Merge = require('deepmerge');
|
|
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');
|
|
11
|
+
const Node = require('./Node');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* SchemaDecorator
|
|
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
|
+
* context <Object> - Globally shared object by all resolvers
|
|
22
|
+
* typeDefs <String|Object> - GQL String or AST Object (also supports a mixed array of both)
|
|
23
|
+
* resolvers <Object> - GraphQL resolvers
|
|
24
|
+
* schemaDirectives <Object> - GraphQL directives
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
module.exports = class SchemaDecorator extends TypeDefApi {
|
|
28
|
+
constructor(schema) {
|
|
29
|
+
super();
|
|
30
|
+
this.schema = { context: {}, typeDefs: [], resolvers: {}, schemaDirectives: {} };
|
|
31
|
+
if (schema) this.mergeSchema(schema);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Synchronously merge a schema
|
|
36
|
+
*/
|
|
37
|
+
mergeSchema(schema, options = {}) {
|
|
38
|
+
// Here we want to normalize the schema into the shape { context, typeDefs, resolvers, schemaDirectives }
|
|
39
|
+
// We do NOT want to modify the schema object because that may cause unwanted side-effects.
|
|
40
|
+
const normalizedSchema = { ...schema };
|
|
41
|
+
if (typeof schema === 'string') normalizedSchema.typeDefs = [schema];
|
|
42
|
+
else if (schema.typeDefs && !Array.isArray(schema.typeDefs)) normalizedSchema.typeDefs = [schema.typeDefs];
|
|
43
|
+
|
|
44
|
+
// For typeDefs we want the AST so that it can be intelligently merged. Here we convert
|
|
45
|
+
// GQL strings to AST objects and also filter out anything that does not parse to AST.
|
|
46
|
+
if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) {
|
|
47
|
+
normalizedSchema.typeDefs = deleteKeys(normalizedSchema.typeDefs.map((td) => {
|
|
48
|
+
try {
|
|
49
|
+
const ast = typeof td === 'object' ? td : parse(td);
|
|
50
|
+
return ast.definitions;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}), ['loc']).filter(Boolean).flat();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Now we're ready to merge the schema
|
|
58
|
+
const [left, right] = options.passive ? [normalizedSchema, this.schema] : [this.schema, normalizedSchema];
|
|
59
|
+
if (normalizedSchema.typeDefs && normalizedSchema.typeDefs.length) this.schema.typeDefs = mergeASTArray(left.typeDefs.concat(right.typeDefs));
|
|
60
|
+
if (normalizedSchema.context) this.schema.context = Merge(left.context, right.context);
|
|
61
|
+
if (normalizedSchema.resolvers) this.schema.resolvers = Merge(left.resolvers, right.resolvers);
|
|
62
|
+
if (normalizedSchema.schemaDirectives) this.schema.schemaDirectives = Merge(left.schemaDirectives, right.schemaDirectives);
|
|
63
|
+
|
|
64
|
+
// Chaining
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Asynchronously load files from a given glob pattern and merge each schema
|
|
70
|
+
*/
|
|
71
|
+
mergeSchemaFromFiles(globPattern, options) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
Glob(globPattern, options, (err, files) => {
|
|
74
|
+
if (err) return reject(err);
|
|
75
|
+
|
|
76
|
+
return Promise.all(files.map((file) => {
|
|
77
|
+
return new Promise((res) => {
|
|
78
|
+
if (file.endsWith('.js')) res(require(file)); // eslint-disable-line global-require,import/no-dynamic-require
|
|
79
|
+
else res(FS.readFileSync(file, 'utf8'));
|
|
80
|
+
}).then(schema => this.mergeSchema(schema, options));
|
|
81
|
+
})).then(() => resolve(this)).catch(e => reject(e));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Traverses the current schema's typeDefs in order to keep the TypeDefApi in sync. This operation
|
|
88
|
+
* only needs to be called when typeDefs have been changed and you want to keep the data model in sync.
|
|
89
|
+
*/
|
|
90
|
+
initialize() {
|
|
91
|
+
return super.initialize(this.schema.typeDefs);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Decorate the schema with Autograph's default api/definitions
|
|
96
|
+
*/
|
|
97
|
+
decorate() {
|
|
98
|
+
this.initialize();
|
|
99
|
+
this.mergeSchema(frameworkExt(this));
|
|
100
|
+
this.mergeSchema(typeExt(this));
|
|
101
|
+
this.initialize();
|
|
102
|
+
this.mergeSchema(apiExt(this), { passive: true });
|
|
103
|
+
this.finalize();
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* This should be called once before passing to makeExecutableSchema()
|
|
109
|
+
*/
|
|
110
|
+
finalize() {
|
|
111
|
+
const definitions = visit(this.schema.typeDefs, {
|
|
112
|
+
[Kind.FIELD_DEFINITION]: (node) => {
|
|
113
|
+
const scope = new Node(node, 'field').getDirectiveArg('field', 'gqlScope', 'crud');
|
|
114
|
+
if (scope === null || scope.indexOf('r') === -1) return null; // Delete node
|
|
115
|
+
return false; // Stop traversing this node
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.schema.typeDefs = { kind: Kind.DOCUMENT, definitions };
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
makeExecutableSchema() {
|
|
124
|
+
return makeExecutableSchema(this.schema);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getContext() {
|
|
128
|
+
return this.schema.context;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
toObject() {
|
|
132
|
+
return this.schema;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
toString() {
|
|
136
|
+
return print(this.typeDefs);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const { Kind, visit } = require('graphql');
|
|
2
|
+
const Model = require('./Model');
|
|
3
|
+
const Node = require('./Node');
|
|
4
|
+
|
|
5
|
+
const operations = ['Query', 'Mutation', 'Subscription'];
|
|
6
|
+
const modelKinds = [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION, Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
|
|
7
|
+
const inputKinds = [Kind.INPUT_OBJECT_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_EXTENSION];
|
|
8
|
+
const scalarKinds = [Kind.SCALAR_TYPE_DEFINITION, Kind.SCALAR_TYPE_EXTENSION];
|
|
9
|
+
const enumKinds = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION];
|
|
10
|
+
|
|
11
|
+
module.exports = class TypeDefApi {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.models = [];
|
|
14
|
+
this.scalars = [];
|
|
15
|
+
this.inputs = [];
|
|
16
|
+
this.enums = [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
initialize(typeDefs) {
|
|
20
|
+
this.models.length = 0;
|
|
21
|
+
this.scalars.length = 0;
|
|
22
|
+
this.inputs.length = 0;
|
|
23
|
+
this.enums.length = 0;
|
|
24
|
+
|
|
25
|
+
visit(typeDefs, {
|
|
26
|
+
enter: (node) => {
|
|
27
|
+
if (modelKinds.indexOf(node.kind) > -1 && operations.indexOf(node.name.value) === -1) {
|
|
28
|
+
this.models.push(new Model(this, node));
|
|
29
|
+
} else if (scalarKinds.indexOf(node.kind) > -1) {
|
|
30
|
+
this.scalars.push(new Node(node));
|
|
31
|
+
} else if (inputKinds.indexOf(node.kind) > -1) {
|
|
32
|
+
this.inputs.push(new Node(node));
|
|
33
|
+
} else if (enumKinds.indexOf(node.kind) > -1) {
|
|
34
|
+
this.enums.push(new Node(node));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return false; // Stop traversing this node
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Models
|
|
45
|
+
getModel(name) {
|
|
46
|
+
return this.models.find(m => m.getName() === name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getModels() {
|
|
50
|
+
return this.models;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getModelNames() {
|
|
54
|
+
return this.getModels().map(model => model.getName());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getModelMap() {
|
|
58
|
+
return this.getModels().reduce((prev, model) => Object.assign(prev, { [model.getName()]: model }), {});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getMarkedModels() {
|
|
62
|
+
return Object.values(this.models).filter(model => model.isMarkedModel());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getEntityModels() {
|
|
66
|
+
return Object.values(this.models).filter(model => model.isEntity());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Definitions
|
|
70
|
+
getInput(name) {
|
|
71
|
+
return this.getInputs().find(input => input.getName() === name);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getInputs() {
|
|
75
|
+
return this.inputs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getScalar(name) {
|
|
79
|
+
return this.getScalars().find(scalar => scalar.getName() === name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getScalars() {
|
|
83
|
+
return this.scalars;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getEnum(name) {
|
|
87
|
+
return this.getEnums().find(el => el.getName() === name);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getEnums() {
|
|
91
|
+
return this.enums;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { Kind, visit } = require('graphql');
|
|
2
|
+
const Node = require('./Node');
|
|
3
|
+
|
|
4
|
+
module.exports = class Field extends Node {
|
|
5
|
+
constructor(ast) {
|
|
6
|
+
super(ast);
|
|
7
|
+
// this.fields = {};
|
|
8
|
+
// if (ast) this.appendAST(ast);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
appendAST(ast) {
|
|
12
|
+
// visit(ast, {
|
|
13
|
+
// [Kind.FIELD_DEFINITION]: (node) => {
|
|
14
|
+
// const name = node.name.value;
|
|
15
|
+
// if (this.fields[name]) this.fields[name].appendAST(node);
|
|
16
|
+
// else this.fields[name] = new Field(node);
|
|
17
|
+
// },
|
|
18
|
+
// });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { graphql, execute, validate } = require('graphql');
|
|
2
|
+
const Schema = require('./Schema');
|
|
3
|
+
|
|
4
|
+
module.exports = class GraphQL {
|
|
5
|
+
constructor(schema) {
|
|
6
|
+
this.schema = (schema instanceof Schema ? schema : new Schema(schema)).makeExecutableSchema();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
exec(source, variableValues) {
|
|
10
|
+
const { schema } = this.schema;
|
|
11
|
+
return graphql({ schema, source, variableValues, contextValue: schema.context });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
execute(source, variableValues) {
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
validate(source, variableValues) {
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { Kind, visit } = require('graphql');
|
|
2
|
+
const Field = require('./Field');
|
|
3
|
+
const Node = require('./Node');
|
|
4
|
+
|
|
5
|
+
module.exports = class Model extends Node {
|
|
6
|
+
constructor(ast) {
|
|
7
|
+
super(ast);
|
|
8
|
+
this.fields = {};
|
|
9
|
+
if (ast) this.appendAST(ast);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
appendAST(ast) {
|
|
13
|
+
visit(ast, {
|
|
14
|
+
[Kind.FIELD_DEFINITION]: (node) => {
|
|
15
|
+
const name = node.name.value;
|
|
16
|
+
if (this.fields[name]) this.fields[name].appendAST(node);
|
|
17
|
+
else this.fields[name] = new Field(node);
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { Kind } = require('graphql');
|
|
2
|
+
|
|
3
|
+
module.exports = class Node {
|
|
4
|
+
constructor(ast) {
|
|
5
|
+
this.ast = ast;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
getKind() {
|
|
9
|
+
return this.ast.kind;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getName() {
|
|
13
|
+
return this.ast.name.value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getValue(ast = this.ast) {
|
|
17
|
+
const { value = {} } = ast;
|
|
18
|
+
|
|
19
|
+
switch (value.kind) {
|
|
20
|
+
case Kind.NULL: return null;
|
|
21
|
+
case Kind.LIST: return value.values.map(el => this.getValue({ value: el }));
|
|
22
|
+
case Kind.OBJECT: {
|
|
23
|
+
return value.fields.reduce((prev, field) => {
|
|
24
|
+
const node = new Node(field);
|
|
25
|
+
return Object.assign(prev, { [node.getName()]: node.getValue() });
|
|
26
|
+
}, {});
|
|
27
|
+
}
|
|
28
|
+
default: {
|
|
29
|
+
if (ast.values) return ast.values.map(v => v.name.value);
|
|
30
|
+
return value.value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const FS = require('fs');
|
|
2
|
+
const Glob = require('glob');
|
|
3
|
+
const Merge = require('deepmerge');
|
|
4
|
+
const { Kind, parse, visit, printSchema, buildSchema } = require('graphql');
|
|
5
|
+
const { makeExecutableSchema } = require('graphql-tools');
|
|
6
|
+
const Model = require('./Model');
|
|
7
|
+
|
|
8
|
+
const modelKinds = [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION, Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION];
|
|
9
|
+
|
|
10
|
+
module.exports = class Schema {
|
|
11
|
+
constructor(schema) {
|
|
12
|
+
this.models = {};
|
|
13
|
+
this.schema = { typeDefs: [], context: {}, resolvers: {}, schemaDirectives: {} };
|
|
14
|
+
if (schema) this.appendSchema(schema);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
appendSchema(schema) {
|
|
18
|
+
// Normalize schema
|
|
19
|
+
if (typeof schema === 'string') schema = { typeDefs: [schema] };
|
|
20
|
+
else if (schema.typeDefs && !Array.isArray(schema.typeDefs)) schema.typeDefs = [schema.typeDefs];
|
|
21
|
+
|
|
22
|
+
// Merge schema
|
|
23
|
+
this.schema = Merge(this.schema, schema);
|
|
24
|
+
|
|
25
|
+
// Visit AST to maintain model definitions
|
|
26
|
+
if (schema.typeDefs) {
|
|
27
|
+
visit(parse(schema.typeDefs.join('\n')), {
|
|
28
|
+
enter: (node) => {
|
|
29
|
+
if (modelKinds.indexOf(node.kind) > -1) {
|
|
30
|
+
const name = node.name.value;
|
|
31
|
+
if (this.models[name]) this.models[name].appendAST(node);
|
|
32
|
+
else this.models[name] = new Model(node);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
appendSchemaFromFile(file) {
|
|
42
|
+
if (file.endsWith('.js')) this.appendSchema(require(file)); // eslint-disable-line global-require,import/no-dynamic-require
|
|
43
|
+
else this.appendSchema(FS.readFileSync(file, 'utf8'));
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
appendSchemaFromDirectory(dir, options) {
|
|
48
|
+
Glob.sync(`${dir}/**/*.{js,gql,graphql}`, options).forEach(file => this.appendSchemaFromFile(file));
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
makeExecutableSchema() {
|
|
53
|
+
return makeExecutableSchema(this.schema);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
printSchema() {
|
|
57
|
+
return printSchema(buildSchema(this.toString()));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
toString() {
|
|
61
|
+
return this.schema.typeDefs.join('\n');
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -36,7 +36,11 @@ module.exports = class QueryResolver {
|
|
|
36
36
|
return createSystemEvent('Mutation', { method: 'create', query }, () => {
|
|
37
37
|
const $input = model.serialize(query, model.appendCreateFields(input));
|
|
38
38
|
query.$input($input);
|
|
39
|
-
|
|
39
|
+
const promise = get(flags, 'novalidate') ? this.resolver.resolve(query) : model.validate(query, $input).then(() => this.resolver.resolve(query));
|
|
40
|
+
return promise.then((doc) => {
|
|
41
|
+
query.doc(doc);
|
|
42
|
+
return doc;
|
|
43
|
+
});
|
|
40
44
|
});
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -69,6 +69,12 @@ exports.renameObjectKey = (obj, oldKey, newKey) => {
|
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
exports.deleteKeys = (obj, keys) => {
|
|
73
|
+
if (Array.isArray(obj)) obj.map(item => exports.deleteKeys(item, keys));
|
|
74
|
+
else if (obj === Object(obj)) { keys.forEach(key => delete obj[key]); Object.values(obj).forEach(v => exports.deleteKeys(v, keys)); }
|
|
75
|
+
return obj;
|
|
76
|
+
};
|
|
77
|
+
|
|
72
78
|
exports.getDeep = (obj, path, defaultValue) => {
|
|
73
79
|
const [prop, ...rest] = path.split('.');
|
|
74
80
|
const normalize = data => (Array.isArray(data) ? _.flatten(data) : data);
|
|
@@ -56,6 +56,7 @@ exports.createSystemEvent = (name, mixed = {}, thunk = () => {}) => {
|
|
|
56
56
|
return middleware().then(thunk);
|
|
57
57
|
}).then((result) => {
|
|
58
58
|
event.result = result;
|
|
59
|
+
if (event.crud === 'create') event.doc = event.query.toObject().doc;
|
|
59
60
|
return systemEvent.emit('system', { type: `post${type}`, data: event }).then((postResult = result) => postResult);
|
|
60
61
|
}).then((result) => {
|
|
61
62
|
if (name === 'Response') return result;
|