├── .gitignore ├── README.md ├── generateResolveFunctions.js ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-aql-generator 2 | schema graphql query – query GraphQL with only a GraphQL IDL schema. 3 | 4 | Are you tired of writing GraphQL.js schema files? 5 | 6 | Then try `graphql-aql-generator`. `graphql-aql-generator` needs only a GraphQL IDL file and generates the GraphQL.js schema automatically. 7 | 8 | 9 | 10 | # Example 11 | 12 | 13 | ```es6 14 | 'use strict'; 15 | 16 | const graphql = require('graphql-sync').graphql; 17 | const generator = require('graphql-aql-generator'); 18 | 19 | 20 | let typeDefs = [` 21 | type BlogEntry { 22 | _key: String! 23 | authorKey: String! 24 | 25 | author: Author @aql(exec: "FOR author in Author filter author._key == @current.authorKey return author") 26 | } 27 | 28 | type Author { 29 | _key: String! 30 | name: String 31 | } 32 | 33 | type Query { 34 | blogEntry(_key: String!): BlogEntry 35 | } 36 | `] 37 | 38 | const schema = generator(typeDefs); 39 | 40 | const query = ` 41 | { 42 | blogEntry(_key: "1") { 43 | _key 44 | authorKey 45 | author { 46 | name 47 | } 48 | } 49 | }`; 50 | 51 | const result = graphql(schema, query); 52 | print(result); 53 | /* 54 | { 55 | "data" : { 56 | "blogEntry" : { 57 | "_key" : "1", 58 | "authorKey" : "2", 59 | "author" : { 60 | "name" : "Plumbum" 61 | } 62 | } 63 | } 64 | } 65 | */` 66 | 67 | 68 | // or use it with a GraphQLRouter 69 | 70 | 'use strict'; 71 | 72 | const createRouter = require('@arangodb/foxx/router'); 73 | const router = createRouter(); 74 | module.context.use(router); 75 | 76 | const createGraphQLRouter = require('@arangodb/foxx/graphql'); 77 | 78 | const graphql = require('graphql-sync').graphql; 79 | const sgq = require('sgq'); 80 | 81 | const typeDefs = [` 82 | type BlogEntry { 83 | _key: String! 84 | authorKey: String! 85 | 86 | author: Author @aql(exec: "FOR author in Author filter author._key == @current.authorKey return author") 87 | } 88 | 89 | type Author { 90 | _key: String! 91 | name: String 92 | } 93 | 94 | type Query { 95 | blogEntry(_key: String!): BlogEntry 96 | } 97 | `] 98 | 99 | const schema = sgq(typeDefs); 100 | 101 | router.use('/graphql', createGraphQLRouter({ 102 | schema: schema, 103 | graphiql: true, 104 | graphql: require('graphql-sync') 105 | })); 106 | 107 | ``` 108 | In this example two types and a query are defined. `BlogEntry` and `Author`. BlogEntry has a sub attribute Author which is fetched with a @aql directive. The query returns a BlogEntry with the corresponding Author depending on the BlogEntries _key. 109 | -------------------------------------------------------------------------------- /generateResolveFunctions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const db = require('@arangodb').db; 4 | 5 | module.exports = (ast, resolveFunctions) => { 6 | ast.definitions.forEach( objectDefinition => { 7 | 8 | if (objectDefinition.kind !== 'ObjectTypeDefinition') return; 9 | 10 | if (objectDefinition.name.value == 'Query') { 11 | // console.log('objectDefinition Query'); 12 | 13 | objectDefinition.fields.forEach(field => { 14 | const fieldName = field.name.value; 15 | // print('fieldName', fieldName); 16 | const collection = field.type.name.value; 17 | // print('collection', collection); 18 | 19 | /*const args = field.arguments; 20 | const argList = []; 21 | 22 | for (const arg of args) { 23 | print('arg.name.value', arg.name.value); 24 | print('arg.type.type.name.value', arg.type.type.name.value); 25 | 26 | argList.push({[arg.name.value]: arg.type.type.name.value}); 27 | } // for*/ 28 | 29 | if (!resolveFunctions['Query']) resolveFunctions['Query'] = {}; 30 | resolveFunctions['Query'][fieldName] = function(dontknow, args, dontknow2, astPart) { // a undefined, b id:4, undefined, astpart 31 | 32 | // print(args); 33 | 34 | const filterList = Object.keys(args).map(arg => `doc.${arg} == ${'string' === typeof args[arg] ? "'" + args[arg] + "'" : args[arg] }`); 35 | 36 | // print('filterList', filterList); 37 | // print(`for doc in ${collection} filter ${filterList.join(' AND ')} return doc`); 38 | const res = db._query(`for doc in ${collection} filter ${filterList.join(' AND ')} return doc`).toArray(); 39 | 40 | return res.pop(); 41 | } // function 42 | }); // fields.forEach 43 | } else { // if field.name.value == Query 44 | /*print('----- != QUERY -----'); 45 | print(JSON.parse(JSON.stringify(objectDefinition))); 46 | print('----- != QUERY -----');*/ 47 | const objectTypeName = objectDefinition.name.value; 48 | // print('objectTypeName', objectTypeName); 49 | objectDefinition.fields.forEach(field => { 50 | const fieldName = field.name.value; 51 | 52 | const isArray = 'ListType' === field.type.kind ? true : false; 53 | 54 | if (field.directives.length) { 55 | const directive = field.directives.shift(); 56 | 57 | if ('aql' === directive.name.value) { 58 | const arg = directive.arguments.shift(); 59 | if ('exec' === arg.name.value) { 60 | const aql = arg.value.value; 61 | const parseResult = db._parse(aql); 62 | /*print('---parse result-----'); 63 | print(parseResult); 64 | print('---parse result-----');*/ 65 | 66 | 67 | // ?: will be fixed by https://github.com/arangodb/arangodb/pull/2772 sometimes 68 | const usesCurrent = !!~(parseResult.bindVars ? parseResult.bindVars : parseResult.parameters).indexOf('current'); 69 | 70 | if (!resolveFunctions[objectTypeName]) resolveFunctions[objectTypeName] = {}; 71 | 72 | resolveFunctions[objectTypeName][fieldName] = function(obj, emptyObject, dontknow, returnTypeOperationDesc) { 73 | 74 | /*print('-- ARGUMENTS --'); 75 | print(JSON.parse(JSON.stringify(arguments))); 76 | print('-- ARGUMENTS --'); 77 | print('-----OBJ-----'); 78 | print(obj); 79 | print('-----OBJ-----');*/ 80 | 81 | const params = {}; 82 | if (usesCurrent) { 83 | params.current = obj; 84 | } 85 | const res = db._query(aql, params).toArray(); 86 | 87 | if (isArray) { 88 | return res; 89 | } else { 90 | return res.pop(); 91 | } 92 | } 93 | } 94 | } // if 95 | } // if 96 | }); 97 | } 98 | }); // forEach 99 | } 100 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | original by https://github.com/apollographql/graphql-tools 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2015 - 2016 Meteor Development Group, Inc. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | */ 28 | 29 | 'use strict'; 30 | 31 | const gql = require('graphql-sync'); 32 | const graphql = gql.graphql; 33 | 34 | const lodash_1 = require('lodash'); 35 | 36 | const generateResolveFunctions = require('./generateResolveFunctions'); 37 | 38 | const buildASTSchema = gql.buildASTSchema; 39 | const extendSchema = gql.extendSchema; 40 | const parse = gql.parse; 41 | 42 | const graphql_1 = gql; 43 | const graphql_3 = gql; 44 | 45 | function makeExecutableSchema( 46 | typeDefs, 47 | resolvers = {}, 48 | connectors, 49 | logger, 50 | allowUndefinedInResolve = true, 51 | resolverValidationOptions = {}) { 52 | const jsSchema = _generateSchema(typeDefs, resolvers, logger, allowUndefinedInResolve, resolverValidationOptions); 53 | return jsSchema; 54 | } 55 | 56 | function _generateSchema( 57 | typeDefinitions, 58 | resolveFunctions, 59 | logger, 60 | // TODO: rename to allowUndefinedInResolve to be consistent 61 | allowUndefinedInResolve, 62 | resolverValidationOptions 63 | ) { 64 | if (typeof resolverValidationOptions !== 'object') { 65 | throw new Error('Expected `resolverValidationOptions` to be an object'); 66 | } 67 | if (!typeDefinitions) { 68 | throw new Error('Must provide typeDefs'); 69 | } 70 | if (!resolveFunctions) { 71 | throw new Error('Must provide resolvers'); 72 | } 73 | 74 | // TODO: check that typeDefinitions is either string or array of strings 75 | 76 | const [ast, schema] = buildSchemaFromTypeDefinitions(typeDefinitions); 77 | /*print('------- AST ----------'); 78 | print(JSON.stringify(ast, false, 2)); 79 | print('------- AST ----------');*/ 80 | 81 | generateResolveFunctions(ast, resolveFunctions); 82 | 83 | 84 | 85 | addResolveFunctionsToSchema(schema, resolveFunctions); 86 | 87 | assertResolveFunctionsPresent(schema, resolverValidationOptions); 88 | 89 | if (!allowUndefinedInResolve) { 90 | addCatchUndefinedToSchema(schema); 91 | } 92 | 93 | if (logger) { 94 | addErrorLoggingToSchema(schema, logger); 95 | } 96 | 97 | /*print('---------- SCHEMA ----------'); 98 | print(JSON.parse(JSON.stringify(schema))); 99 | print('---------- SCHEMA ----------');*/ 100 | 101 | return schema; 102 | } 103 | 104 | function buildSchemaFromTypeDefinitions(typeDefinitions) { 105 | // TODO: accept only array here, otherwise interfaces get confusing. 106 | let myDefinitions = typeDefinitions; 107 | let astDocument; 108 | 109 | if (isDocumentNode(typeDefinitions)) { 110 | astDocument = typeDefinitions; 111 | } else if (typeof myDefinitions !== 'string') { 112 | if (!Array.isArray(myDefinitions)) { 113 | const type = typeof myDefinitions; 114 | throw new Error(`typeDefs must be a string, array or schema AST, got ${type}`); 115 | } 116 | myDefinitions = concatenateTypeDefs(myDefinitions); 117 | } 118 | 119 | if (typeof myDefinitions === 'string') { 120 | astDocument = parse(myDefinitions); 121 | } 122 | 123 | let schema = buildASTSchema(astDocument); 124 | 125 | const extensionsAst = extractExtensionDefinitions(astDocument); 126 | if (extensionsAst.definitions.length > 0) { 127 | schema = extendSchema(schema, extensionsAst); 128 | } 129 | 130 | return [astDocument, schema]; 131 | } 132 | 133 | function isDocumentNode(typeDefinitions) { 134 | return typeDefinitions.kind !== undefined; 135 | } 136 | 137 | function concatenateTypeDefs(typeDefinitionsAry, calledFunctionRefs) { 138 | if (calledFunctionRefs === void 0) { calledFunctionRefs = []; } 139 | var resolvedTypeDefinitions = []; 140 | typeDefinitionsAry.forEach(function (typeDef) { 141 | if (isDocumentNode(typeDef)) { 142 | typeDef = graphql_1.print(typeDef); 143 | } 144 | if (typeof typeDef === 'function') { 145 | if (calledFunctionRefs.indexOf(typeDef) === -1) { 146 | calledFunctionRefs.push(typeDef); 147 | resolvedTypeDefinitions = resolvedTypeDefinitions.concat(concatenateTypeDefs(typeDef(), calledFunctionRefs)); 148 | } 149 | } 150 | else if (typeof typeDef === 'string') { 151 | resolvedTypeDefinitions.push(typeDef.trim()); 152 | } 153 | else { 154 | var type = typeof typeDef; 155 | throw new SchemaError("typeDef array must contain only strings and functions, got " + type); 156 | } 157 | }); 158 | return lodash_1.uniq(resolvedTypeDefinitions.map(function (x) { return x.trim(); })).join('\n'); 159 | } 160 | 161 | function extractExtensionDefinitions(ast) { 162 | var extensionDefs = ast.definitions.filter(function (def) { return def.kind === graphql_1.Kind.TYPE_EXTENSION_DEFINITION; }); 163 | return Object.assign({}, ast, { 164 | definitions: extensionDefs, 165 | }); 166 | } 167 | 168 | function addResolveFunctionsToSchema(schema, resolveFunctions) { 169 | Object.keys(resolveFunctions).forEach(function (typeName) { 170 | var type = schema.getType(typeName); 171 | if (!type && typeName !== '__schema') { 172 | throw new Error("\"" + typeName + "\" defined in resolvers, but not in schema"); 173 | } 174 | Object.keys(resolveFunctions[typeName]).forEach(function (fieldName) { 175 | if (fieldName.startsWith('__')) { 176 | // this is for isTypeOf and resolveType and all the other stuff. 177 | // TODO require resolveType for unions and interfaces. 178 | type[fieldName.substring(2)] = resolveFunctions[typeName][fieldName]; 179 | return; 180 | } 181 | if (type instanceof graphql_3.GraphQLScalarType) { 182 | type[fieldName] = resolveFunctions[typeName][fieldName]; 183 | return; 184 | } 185 | var fields = getFieldsForType(type); 186 | if (!fields) { 187 | throw new Error(typeName + " was defined in resolvers, but it's not an object"); 188 | } 189 | if (!fields[fieldName]) { 190 | throw new Error(typeName + "." + fieldName + " defined in resolvers, but not in schema"); 191 | } 192 | var field = fields[fieldName]; 193 | var fieldResolve = resolveFunctions[typeName][fieldName]; 194 | if (typeof fieldResolve === 'function') { 195 | // for convenience. Allows shorter syntax in resolver definition file 196 | setFieldProperties(field, { resolve: fieldResolve }); 197 | } 198 | else { 199 | if (typeof fieldResolve !== 'object') { 200 | throw new SchemaError("Resolver " + typeName + "." + fieldName + " must be object or function"); 201 | } 202 | setFieldProperties(field, fieldResolve); 203 | } 204 | }); 205 | }); 206 | } 207 | 208 | function setFieldProperties(field, propertiesObj) { 209 | Object.keys(propertiesObj).forEach(function (propertyName) { 210 | field[propertyName] = propertiesObj[propertyName]; 211 | }); 212 | } 213 | 214 | 215 | function getFieldsForType(type) { 216 | if ((type instanceof graphql_3.GraphQLObjectType) || 217 | (type instanceof graphql_3.GraphQLInterfaceType)) { 218 | return type.getFields(); 219 | } 220 | else { 221 | return undefined; 222 | } 223 | } 224 | 225 | function assertResolveFunctionsPresent(schema, resolverValidationOptions) { 226 | if (resolverValidationOptions === void 0) { resolverValidationOptions = {}; } 227 | var _a = resolverValidationOptions.requireResolversForArgs, requireResolversForArgs = _a === void 0 ? false : _a, _b = resolverValidationOptions.requireResolversForNonScalar, requireResolversForNonScalar = _b === void 0 ? false : _b, _c = resolverValidationOptions.requireResolversForAllFields, requireResolversForAllFields = _c === void 0 ? false : _c; 228 | if (requireResolversForAllFields && (requireResolversForArgs || requireResolversForNonScalar)) { 229 | throw new TypeError('requireResolversForAllFields takes precedence over the more specific assertions. ' + 230 | 'Please configure either requireResolversForAllFields or requireResolversForArgs / ' + 231 | 'requireResolversForNonScalar, but not a combination of them.'); 232 | } 233 | forEachField(schema, function (field, typeName, fieldName) { 234 | // requires a resolve function for *every* field. 235 | if (requireResolversForAllFields) { 236 | expectResolveFunction(field, typeName, fieldName); 237 | } 238 | // requires a resolve function on every field that has arguments 239 | if (requireResolversForArgs && field.args.length > 0) { 240 | expectResolveFunction(field, typeName, fieldName); 241 | } 242 | // requires a resolve function on every field that returns a non-scalar type 243 | if (requireResolversForNonScalar && !(graphql_3.getNamedType(field.type) instanceof graphql_3.GraphQLScalarType)) { 244 | expectResolveFunction(field, typeName, fieldName); 245 | } 246 | }); 247 | } 248 | 249 | function forEachField(schema, fn) { 250 | var typeMap = schema.getTypeMap(); 251 | Object.keys(typeMap).forEach(function (typeName) { 252 | var type = typeMap[typeName]; 253 | // TODO: maybe have an option to include these? 254 | if (!graphql_3.getNamedType(type).name.startsWith('__') && type instanceof graphql_3.GraphQLObjectType) { 255 | var fields_1 = type.getFields(); 256 | Object.keys(fields_1).forEach(function (fieldName) { 257 | var field = fields_1[fieldName]; 258 | fn(field, typeName, fieldName); 259 | }); 260 | } 261 | }); 262 | } 263 | 264 | module.exports = (typeDefs) => makeExecutableSchema(typeDefs); 265 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sgq", 3 | "version": "2.1.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "graphql": { 7 | "version": "0.10.3", 8 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.10.3.tgz", 9 | "integrity": "sha1-wxOv1VGOZzNRvuGPtj4qDkh0B6s=" 10 | }, 11 | "graphql-sync": { 12 | "version": "0.10.1-sync", 13 | "resolved": "https://registry.npmjs.org/graphql-sync/-/graphql-sync-0.10.1-sync.tgz", 14 | "integrity": "sha1-NoYCLWNHQzlD39dvJqYChV4RGx0=" 15 | }, 16 | "iterall": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.1.tgz", 19 | "integrity": "sha1-9/CvEemgTsZCYmD1AZ2fzKTVAhQ=" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-aql-generator", 3 | "version": "2.1.0", 4 | "description": "query graphql with only a graphql idl schema", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/baslr/graphql-aql-generator.git" 12 | }, 13 | "keywords": [ 14 | "graphql", 15 | "aql", 16 | "generator", 17 | "idl", 18 | "schema", 19 | "query" 20 | ], 21 | "author": "Manuel Baesler", 22 | "license": "AGPL-3.0", 23 | "bugs": { 24 | "url": "https://github.com/baslr/graphql-aql-generator/issues" 25 | }, 26 | "homepage": "https://github.com/baslr/graphql-aql-generator#readme", 27 | "dependencies": { 28 | "graphql-sync": "0.10.1-sync" 29 | }, 30 | "devDependencies": {} 31 | } 32 | --------------------------------------------------------------------------------