UNPKG

@cortexql/ts2graphql

Version:

A TypeScrpt transpiler to GraphQL for your project

709 lines 32.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) t[p[i]] = s[p[i]]; return t; }; var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } }; var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator]; return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); }; function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = require("path"); const appRootDir = require("app-root-dir"); const fs = require("fs"); const schemaPrinter_1 = require("graphql/utilities/schemaPrinter"); const createTypeDocJson_1 = require("./utils/createTypeDocJson"); const TypeDoc2GraphQL_1 = require("./utils/TypeDoc2GraphQL"); const graphql_tools_1 = require("graphql-tools"); const pathSync_1 = require("./utils/pathSync"); const capitalize_1 = require("./utils/capitalize"); const graphql_1 = require("graphql"); const glob_1 = require("glob"); const exec_1 = require("./utils/exec"); const Validator_1 = require("./utils/Validator"); const iterall_1 = require("iterall"); __export(require("./utils/TypeDoc2GraphQL")); __export(require("./utils/Validator")); if (Symbol.asyncIterator === undefined) { Symbol.asyncIterator = iterall_1.$$asyncIterator; } function graphqlParseOutput(value, context, info) { return __awaiter(this, void 0, void 0, function* () { if (Array.isArray(value)) { return yield Promise.all(value.map(item => graphqlParseOutput(item, context, info))); } if (value instanceof Promise) { return value.then(promisedValue => graphqlParseOutput(promisedValue, context, info)); } if (typeof value === 'object' && value != null) { let constructor; if (typeof value.toGraph === 'function') { constructor = value.constructor; value = yield value.toGraph(context, info); } if (value instanceof Date) { return value; } else if (typeof value.toJSON === 'function') { value = value.toJSON(); } else if (typeof value.toJSDate === 'function') { return value.toJSDate(); } else if (typeof value.toString === 'function' && value.toString !== Object.prototype.toString) { return value.toString(); } else if (typeof value.toNumber === 'function') { return value.toNumber(); } // TODO: Add Set and Map handlers if (typeof value === 'object' && value != null) { const obj = {}; if (typeof obj.__typename === 'undefined') { if (typeof constructor !== 'undefined' && typeof constructor.graphqlType !== 'undefined') { obj.__typename = constructor.graphqlType.name; } } for (const key of Object.keys(value)) { obj[key] = yield graphqlParseOutput(value[key], context, info); } return obj; } return value; } return value; }); } exports.graphqlParseOutput = graphqlParseOutput; function getValidator(rootPath, validatorDir) { const validatorsPath = path_1.resolve(rootPath, validatorDir); const glob = new glob_1.GlobSync('*.@(ts|js)', { ignore: '*.d.ts', cwd: validatorsPath, }); const validatorTypeMap = {}; for (const file of glob.found) { const fileName = path_1.resolve(validatorsPath, file); const typeName = path_1.basename(fileName).replace(/\.(ts|js)$/, ''); if (typeName === 'index') { continue; } const validatorFile = require(fileName); if (!('validators' in validatorFile)) { throw new Error(`The file ${path_1.relative(rootPath, fileName)} must export validators`); } const { validators } = validatorFile; if (!('sanitize' in validators)) { throw new Error(`The file ${path_1.relative(rootPath, fileName)} must export validators.sanitize`); } if (!('validate' in validators)) { throw new Error(`The file ${path_1.relative(rootPath, fileName)} must export validators.validate`); } validatorTypeMap[typeName] = validators; } return new Validator_1.Validator(validatorTypeMap); } exports.getValidator = getValidator; function getScalars(rootPath, scalarsDir) { const scalarsPath = path_1.resolve(rootPath, scalarsDir); const glob = new glob_1.GlobSync('*.@(ts|js)', { ignore: '*.d.ts', cwd: scalarsPath, }); const scalars = []; for (const file of glob.found) { const fileName = path_1.resolve(scalarsPath, file); const graphqlTypeName = path_1.basename(fileName).replace(/\.(ts|js)$/, ''); if (graphqlTypeName === 'index') { continue; } const nameMatch = graphqlTypeName.match(/^GraphQL(.*)/); if (nameMatch === null) { throw new Error(`The file ${path_1.relative(rootPath, fileName)} must start with GraphQL name`); } const scalarFile = require(fileName); if (!(graphqlTypeName in scalarFile)) { throw new Error(`The file ${path_1.relative(rootPath, fileName)} must export ${nameMatch[1]}`); } const { [graphqlTypeName]: type } = scalarFile; if (!(type instanceof graphql_1.GraphQLScalarType || type instanceof graphql_1.GraphQLObjectType || type instanceof graphql_1.GraphQLInputObjectType || type instanceof graphql_1.GraphQLInterfaceType || type instanceof graphql_1.GraphQLUnionType)) { throw new Error(`The file ${path_1.relative(rootPath, fileName)} must export a named type`); } if (type.name !== nameMatch[1]) { throw new Error(`The file ${path_1.relative(rootPath, fileName)} must export a type named ${nameMatch[1]}`); } scalars.push(type); } return scalars; } exports.getScalars = getScalars; class TypeScript2GraphQL extends TypeDoc2GraphQL_1.TypeDoc2GraphQL { constructor(options = {}) { const { types, parserHandler } = options; const paths = Object.assign({ root: appRootDir.get(), types: 'types', query: 'query', mutation: 'mutation', subscription: 'subscription', validators: 'validators', scalars: 'scalars' }, options.paths); let restPaths; let rootPath; ({ root: rootPath } = paths, restPaths = __rest(paths, ["root"])); const stats = fs.lstatSync(rootPath); if (!stats.isDirectory()) { throw new Error('The root path must be a directory'); } for (const name of Object.keys(restPaths)) { if (restPaths[name] !== undefined) { restPaths[name] = path_1.resolve(rootPath, restPaths[name]); } } super({ types: (types !== undefined ? types : []).concat(getScalars(rootPath, restPaths.scalars)), parserHandler: parserHandler !== undefined ? parserHandler : getValidator(rootPath, restPaths.validators), paths: restPaths, }); this.rootPath = rootPath; } generate() { if (this.typedoc === undefined) { const typedoc = createTypeDocJson_1.createTypeDocJson(this.rootPath); this.initialize(typedoc); } } restoreSourceMap(type, map) { if (type instanceof graphql_1.GraphQLEnumType && map.values !== undefined) { const typeValues = type.getValues(); for (const index in typeValues) { const value = typeValues[index]; typeValues[index].value = map.values[value.name]; } } else if (type instanceof graphql_1.GraphQLInputObjectType && map.fields !== undefined) { const fields = type.getFields(); for (const key of Object.keys(fields)) { if (Object.prototype.hasOwnProperty.call(fields, key)) { if (!map.fields.hasOwnProperty(key)) { continue; } if (map.fields[key].hasOwnProperty('validate')) { fields[key].validate = map.fields[key].validate; } if (map.fields[key].hasOwnProperty('sanitize')) { fields[key].sanitize = map.fields[key].sanitize; } } } } else if (type instanceof graphql_1.GraphQLObjectType && map.fields !== undefined) { const fields = type.getFields(); for (const key of Object.keys(fields)) { if (!Object.prototype.hasOwnProperty.call(fields, key)) { continue; } const { args } = fields[key]; if (args === undefined) { continue; } for (const index in args) { const argName = args[index].name; if (!map.hasOwnProperty('fields') || !map.fields.hasOwnProperty(key) || map.fields[key].validateArgs === undefined) { continue; } const validateArgs = map.fields[key].validateArgs; if (validateArgs.hasOwnProperty(argName)) { args[index].validate = validateArgs[argName]; } } } } } restoreSchema(inputDir = 'schema') { const glob = new glob_1.GlobSync('**/*.graphql', { cwd: inputDir, }); const defs = []; const maps = []; for (const file of glob.found) { defs.push(fs.readFileSync(path_1.resolve(inputDir, file)).toString()); const mapJsonFile = path_1.resolve(inputDir, file.replace(/\.graphql$/, '.map.json')); if (fs.existsSync(mapJsonFile)) { maps.push(JSON.parse(fs.readFileSync(mapJsonFile).toString())); } } const schema = graphql_1.buildSchema(defs.join('\n\n')); for (const map of maps) { const type = schema.getType(map.name); this.restoreSourceMap(type, map); } return schema; } dumpSchema(outputDir = 'schema') { const schema = this.getSchema(); const singleFileMatch = outputDir.match(/([^\/]+)\.graphql$/); let singleFile = ''; if (singleFileMatch !== null) { // tslint:disable-next-line no-parameter-reassignment outputDir = path_1.dirname(outputDir); if (outputDir === singleFileMatch[0]) { // tslint:disable-next-line no-parameter-reassignment outputDir = '.'; } singleFile = singleFileMatch[1]; } const outputPath = path_1.resolve(this.rootPath, outputDir); pathSync_1.pathSync(outputPath); if (singleFileMatch === null && outputPath !== this.rootPath) { exec_1.exec(`node_modules/.bin/rimraf ${outputPath}`); try { fs.mkdirSync(outputPath); } catch (err) { } } const types = [ schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType(), ...Object.values(schema.getTypeMap()) ]; const parts = []; for (const type of types) { if (type == null) { continue; } if (type instanceof graphql_1.GraphQLObjectType || type instanceof graphql_1.GraphQLInputObjectType || type instanceof graphql_1.GraphQLEnumType || type instanceof graphql_1.GraphQLScalarType || type instanceof graphql_1.GraphQLInterfaceType) { if (singleFileMatch === null) { fs.writeFileSync(path_1.resolve(outputPath, `${type.name}.graphql`), this.getSource(type)); const map = this.getSourceMap(type); if (map !== undefined) { fs.writeFileSync(path_1.resolve(outputPath, `${type.name}.map.json`), JSON.stringify(map, null, 2)); } } else { parts.push(this.getSource(type)); } } else { const name = type.name === undefined ? type.constructor.name : type.name; console.log(`skipping ${name} as it cannot be resolved to source`); } } if (singleFileMatch !== null) { fs.writeFileSync(path_1.resolve(outputPath, `${singleFile}.graphql`), parts.join('\n\n')); } } getSourceMap(type) { let map; if (type instanceof graphql_1.GraphQLEnumType) { graphql_1.GraphQLEnumType.name; map = { name: type.name, type: 'enum', values: {}, }; for (const value of type.getValues()) { map.values[value.name] = value.value; } } else if (type instanceof graphql_1.GraphQLInputObjectType) { const fields = type.getFields(); const createMap = () => { if (map === undefined) { map = { name: type.name, type: 'input', fields: {}, }; } }; const createFieldMap = (key) => { createMap(); if (!map.fields.hasOwnProperty(key)) { map.fields[key] = {}; } }; for (const key of Object.keys(fields)) { if (!Object.prototype.hasOwnProperty.call(fields, key)) { continue; } if (fields[key].hasOwnProperty('validate')) { createFieldMap(key); map.fields[key].validate = fields[key].validate; } if (fields[key].hasOwnProperty('sanitize')) { createFieldMap(key); map.fields[key].sanitize = fields[key].sanitize; } } } else if (type instanceof graphql_1.GraphQLObjectType) { const fields = type.getFields(); const createMap = () => { if (map === undefined) { map = { name: type.name, type: 'input', fields: {}, }; } }; const createFieldMap = (key) => { createMap(); if (!map.fields.hasOwnProperty(key)) { map.fields[key] = {}; } }; const createFieldValidateArgMap = (key) => { createFieldMap(key); if (!map.fields[key].hasOwnProperty('validateArgs')) { map.fields[key].validateArgs = {}; } }; const createFieldSanitizeArgMap = (key) => { createFieldMap(key); if (!map.fields[key].hasOwnProperty('sanitizeArgs')) { map.fields[key].sanitizeArgs = {}; } }; for (const key of Object.keys(fields)) { if (Object.prototype.hasOwnProperty.call(fields, key)) { const field = fields[key]; for (const arg of field.args) { if (arg.hasOwnProperty('validate')) { createFieldValidateArgMap(key); map.fields[key].validateArgs[arg.name] = arg.validate; } if (arg.hasOwnProperty('sanitize')) { createFieldSanitizeArgMap(key); map.fields[key].sanitizeArgs[arg.name] = arg.sanitize; } } } } } return map; } injectParserHandlerOnObjectType(type) { const fields = type.getFields(); for (const fieldName of Object.keys(fields)) { if (!Object.prototype.hasOwnProperty.call(fields, fieldName)) { continue; } const field = fields[fieldName]; const resolveFunction = field.resolve; const sanitizeRules = []; const validateRules = []; for (const arg of field.args) { const sanitize = arg.sanitize; if (sanitize !== undefined) { for (const parser of sanitize) { sanitizeRules.push(Object.assign({ path: [arg.name], valueType: arg.type }, parser)); } } const validate = arg.validate; if (validate !== undefined) { for (const parser of validate) { validateRules.push(Object.assign({ path: [arg.name], valueType: arg.type }, parser)); } } const parseInputObjectTypeArg = (type, path) => { if (type instanceof graphql_1.GraphQLNonNull || 'ofType' in type) { // tslint:disable-next-line no-parameter-reassignment type = type.ofType; } if (!(type instanceof graphql_1.GraphQLInputObjectType)) { return; } const input = type; const inputFields = input.getFields(); for (const inputKey of Object.keys(inputFields)) { if (!Object.prototype.hasOwnProperty.call(inputFields, inputKey)) { continue; } const inputField = inputFields[inputKey]; const sanitize = inputField.sanitize; if (sanitize !== undefined) { for (const parser of sanitize) { sanitizeRules.push(Object.assign({ path: path.concat(inputKey), valueType: inputField.type }, parser)); } } const validate = inputField.validate; if (validate !== undefined) { for (const parser of validate) { validateRules.push(Object.assign({ path: path.concat(inputKey), valueType: inputField.type }, parser)); } } if (inputField.type instanceof graphql_1.GraphQLInputObjectType) { parseInputObjectTypeArg(inputField.type, path.concat(inputKey)); } } }; parseInputObjectTypeArg(arg.type, [arg.name]); } if (sanitizeRules.length === 0 && validateRules.length === 0) { continue; } field.resolve = (source, args, context, info) => __awaiter(this, void 0, void 0, function* () { yield this.parserHandler.parse({ type, fieldName, args, sanitizeRules, validateRules, source, context, info, }); if (resolveFunction !== undefined) { return yield resolveFunction(source, args, context, info); } return null; }); } } injectParserHandler(schema) { const typeMap = schema.getTypeMap(); for (const name of Object.keys(typeMap)) { const type = typeMap[name]; if (type instanceof graphql_1.GraphQLObjectType) { this.injectParserHandlerOnObjectType(type); } } } addResolveFunctionsToSchema(schema, resolveFunctions, resolverValidationOptions) { graphql_tools_1.addResolveFunctionsToSchema(schema, resolveFunctions, resolverValidationOptions); this.injectParserHandler(schema); } getSource(type) { return schemaPrinter_1.printType(type); } getMethodResolvers(dir, name) { const resolvers = {}; const methodPath = path_1.resolve(this.rootPath, dir); const glob = new glob_1.GlobSync('*.@(ts|js)', { ignore: ['*.d.ts'], cwd: methodPath, }); for (const file of glob.found) { const fileName = path_1.resolve(methodPath, file); const methodName = path_1.basename(fileName).replace(/\.(ts|js)$/, ''); if (methodName === 'index') { continue; } if (!resolvers.hasOwnProperty(name)) { resolvers[name] = {}; } const exports = require(fileName); let resolver; const upperCased = methodName.charAt(0).toUpperCase() + methodName.slice(1); if (methodName in exports) { resolver = exports[methodName]; } else { if (name === 'Subscription') { resolver = exports[`subscribe${upperCased}`]; if (typeof resolver === 'undefined') { resolver = exports.subscribe; } } else { resolver = exports[`resolve${upperCased}`]; if (typeof resolver === 'undefined') { resolver = exports.resolve; } } } if (typeof resolver === 'undefined') { throw new Error(`Couldn't find an exported variable for ${name}.${methodName}`); } const mappedResolver = (obj, args, context, info) => resolver(args, context, info); if (name === 'Subscription') { resolvers[name][methodName] = { resolve: (obj, args, context, info) => { if (obj === undefined) { throw new graphql_1.GraphQLError('You must execute subscription under WebSocket'); } return graphqlParseOutput(obj[methodName], context, info); }, subscribe: (obj, args, context, info) => { const iterable = mappedResolver(obj, args, context, info); if ('subscribe' in iterable) { return (function () { return __asyncGenerator(this, arguments, function* () { function nextResolver() { const resolver = {}; resolver.promise = new Promise((resolve, reject) => { resolver.resolve = resolve; resolver.reject = reject; }); return resolver; } const buffer = [nextResolver()]; const subscription = iterable.subscribe((value) => { const { resolve } = buffer[buffer.length - 1]; buffer.push(nextResolver()); resolve({ value }); }, (error) => { resolver.reject(error); }, (value) => { const { resolve } = buffer[buffer.length - 1]; buffer.push(nextResolver()); resolve({ value, done: true }); }); try { while (true) { const { value, done } = yield __await((buffer[0].promise)); buffer.shift(); if (done === true) { return value !== undefined ? { [methodName]: value } : value; } yield { [methodName]: value }; } } finally { subscription.dispose(); while (buffer.shift()) { } } }); })(); } if (iterall_1.isAsyncIterable(iterable)) { return (function () { return __asyncGenerator(this, arguments, function* () { try { for (var iterable_1 = __asyncValues(iterable), iterable_1_1; iterable_1_1 = yield __await(iterable_1.next()), !iterable_1_1.done;) { const result = yield __await(iterable_1_1.value); yield { [methodName]: result }; } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (iterable_1_1 && !iterable_1_1.done && (_a = iterable_1.return)) yield __await(_a.call(iterable_1)); } finally { if (e_1) throw e_1.error; } } var e_1, _a; }); })(); } throw new Error(`Could not resolve subscribe method ${name}.${methodName}, did not return AsyncIterator or Observable`); }, }; } else { resolvers[name][methodName] = (obj, args, context, info) => graphqlParseOutput(mappedResolver(obj, args, context, info), context, info); } } return resolvers; } getTypeResolvers(schema) { const resolvers = {}; const methodPath = path_1.resolve(this.rootPath, 'types'); const glob = new glob_1.GlobSync('*.@(ts|js)', { ignore: '*.d.ts', cwd: methodPath, }); for (const file of glob.found) { const fileName = path_1.resolve(methodPath, file); const typeName = path_1.basename(fileName).replace(/\.(ts|js)$/, ''); if (typeName === 'index') { continue; } const type = require(fileName)[typeName]; const graphqlType = schema.getType(typeName); if (graphqlType instanceof graphql_1.GraphQLObjectType) { Object.defineProperty(type, 'graphqlType', { value: graphqlType }); Object.defineProperty(type, 'originalType', { value: type }); if (typeof type.isTypeOf === 'function') { graphqlType.isTypeOf = (value, context, info) => { return type.isTypeOf(value, context, info); }; } } if (typeof type === 'function') { const resolveObject = {}; const { graphqlType } = type; if (typeof graphqlType !== 'undefined') { const fields = graphqlType.getFields(); for (const fieldName of Object.keys(fields)) { resolveObject[fieldName] = (obj, args, context, info) => { let result = null; const methodName = `resolve${capitalize_1.capitalize(fieldName)}`; if (typeof obj[methodName] === 'function') { result = obj[methodName](args, context, info); } else if (obj[fieldName] != null) { result = obj[fieldName]; } return graphqlParseOutput(result, context, info); }; } } resolvers[graphqlType.name] = resolveObject; } } return resolvers; } getResolvers(schema) { return Object.assign({}, this.getTypeResolvers(schema), this.getMethodResolvers('query', 'Query'), this.getMethodResolvers('mutation', 'Mutation'), this.getMethodResolvers('subscription', 'Subscription')); } getSchemaConfig() { this.generate(); return super.getSchemaConfig(); } getSchema() { const schema = super.getSchema(); return schema; } getSchemaWithResolvers() { const schema = this.getSchema(); const resolvers = this.getResolvers(schema); this.addResolveFunctionsToSchema(schema, resolvers); return schema; } restoreSchemaWithResolvers(inputDir = 'schema') { const schema = this.restoreSchema(inputDir); const resolvers = this.getResolvers(schema); this.addResolveFunctionsToSchema(schema, resolvers); return schema; } } exports.TypeScript2GraphQL = TypeScript2GraphQL; //# sourceMappingURL=index.js.map