@backland/schema
Version:
TypeScript schema declaration and validation library with static type inference
305 lines (302 loc) • 9.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createGraphQLSchema = createGraphQLSchema;
exports.resolverKinds = void 0;
exports.resolversToTypescript = resolversToTypescript;
exports.resolversTypescriptParts = resolversTypescriptParts;
var _utils = require("@backland/utils");
var _graphql = require("graphql");
var _groupBy = _interopRequireDefault(require("lodash/groupBy"));
var _CircularDeps = require("./CircularDeps");
var _GraphType = require("./GraphType/GraphType");
var _generateClientUtils2 = require("./GraphType/generateClientUtils");
var _getInnerGraphTypeId = require("./GraphType/getInnerGraphTypeId");
var _getQueryTemplates = require("./GraphType/getQueryTemplates");
var _ObjectType = require("./ObjectType");
var _MetaFieldField = require("./fields/MetaFieldField");
var _mockObject = require("./mockObject");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const resolverKinds = (0, _utils.tupleEnum)('mutation', 'query', 'subscription');
exports.resolverKinds = resolverKinds;
function createGraphQLSchema(...args) {
const {
graphql: {
GraphQLSchema
},
GraphType
} = _CircularDeps.CircularDeps;
const registeredResolvers = GraphType.resolvers.entries.map(el => el[1]);
let resolvers = Array.isArray(args[0]) ? args[0] : registeredResolvers;
const schemaResolvers = resolvers.filter(el => el.__isResolver && !el.__isRelation);
const config = Array.isArray(args[0]) ? args[1] : args[0];
const grouped = (0, _groupBy.default)(schemaResolvers, item => item.kind);
function createFields(kind) {
const fields = {};
if (grouped[kind]) {
grouped[kind].forEach(item => {
fields[item.name] = item;
});
}
return fields;
}
const usedConfig = {
mutation: grouped.mutation ? new _graphql.GraphQLObjectType({
fields: createFields('mutation'),
name: 'Mutation'
}) : undefined,
query: grouped.query ? new _graphql.GraphQLObjectType({
fields: createFields('query'),
name: 'Query'
}) : undefined,
subscription: grouped.subscription ? new _graphql.GraphQLObjectType({
fields: createFields('subscription'),
name: 'Subscription'
}) : undefined,
...config
};
const schema = new GraphQLSchema(usedConfig);
let ts;
const utils = {
generateClientUtils() {
return Promise.reject('not implemented');
},
grouped,
print() {
return (0, _graphql.printSchema)(schema);
},
queryExamples(options) {
return queryExamples({
grouped,
schema,
...options
});
},
queryTemplates() {
return (0, _getQueryTemplates.getSchemaQueryTemplates)(schema);
},
registeredResolvers,
resolvers,
async typescript(options) {
return ts = ts || resolversToTypescript({
name: 'GraphQLTypes',
...options,
resolvers
});
},
usedConfig
};
const result = Object.assign(schema, {
utils
});
utils.generateClientUtils = function _generateClientUtils() {
return (0, _generateClientUtils2.generateClientUtils)(result);
};
return result;
}
async function resolversTypescriptParts(params) {
const {
name = 'Schema'
} = params;
let prefix = '';
const mainResolvers = params.resolvers.filter(el => !el.__isRelation);
const mainResolversConversion = mainResolvers.map(item => {
return convertResolver({
allResolvers: params.resolvers,
resolver: item
});
});
const lines = await Promise.all(mainResolversConversion);
let typesCode = '';
let interfaceCode = `export interface ${name} {`;
let queryCode = `export type QueryResolvers = {`;
let mutationCode = `export type MutationResolvers = {`;
let subscriptionCode = `export type SubscriptionResolvers = {`;
lines.forEach(el => {
let {
entryName,
code,
payloadName,
inputName,
resolver: {
description = '',
kind
}
} = el;
typesCode += `${code}\n`;
if (description) {
description = `\n/** ${description} **/\n`;
}
const resolverCode = `${description} ${entryName}(args: ${inputName}): Promise<${payloadName}>,`;
switch (kind) {
case 'mutation':
{
mutationCode += resolverCode;
break;
}
case 'query':
{
queryCode += resolverCode;
break;
}
case 'subscription':
{
subscriptionCode += resolverCode;
break;
}
}
interfaceCode += `${description} ${entryName}: {input: ${inputName}, payload: ${payloadName}},`;
});
let code = `${prefix}\n${typesCode}\n${interfaceCode}}\n${queryCode}}\n${mutationCode}}\n${subscriptionCode}}\n`.replace(/\n\n/gm, '\n') // remove multi line breaks
.replace(/^\n/gm, ''); // remove empty lines
// @ts-ignore circular
code = _CircularDeps.CircularDeps.prettier.format(code, {
parser: 'typescript'
});
return {
code,
lines
};
}
async function resolversToTypescript(params) {
const {
options = {}
} = params;
const {
format = true
} = options;
const {
code
} = await resolversTypescriptParts(params);
return format ?
// @ts-ignore circular
_CircularDeps.CircularDeps.prettier.format(code, {
parser: 'typescript',
printWidth: 100
}) : code;
}
async function convertResolver(options) {
const {
resolver,
allResolvers
} = options;
const inputName = resolver.argsType.id;
const payloadName = resolver.payloadType.id;
const payloadOriginName = (0, _getInnerGraphTypeId.getInnerGraphTypeId)(resolver.payloadType);
const payloadDef = {
// clearing ref because will be mutated to inject relations in definition
...(0, _MetaFieldField.cleanMetaField)(resolver.typeDef)
};
allResolvers.filter(el => el.__isRelation).forEach(rel => {
if (rel.__relatedToGraphTypeId === payloadOriginName) {
const typeRelatedToFinalPayload = _GraphType.GraphType.register.get(rel.__graphTypeId);
payloadDef.def[rel.name] = typeRelatedToFinalPayload.definition;
}
});
const [payload, args] = await Promise.all([convertType({
entryName: payloadName,
kind: 'output',
type: payloadDef // TODO generate type for User[] not for plain object
}), convertType({
entryName: inputName,
kind: 'input',
type: resolver.argsType.definition
})]);
let code = '';
code += `${args.comments}\nexport type ${inputName} = ${args.code};`;
code += `${payload.comments}\nexport type ${payloadName} = ${payload.code};`;
return {
args,
code,
entryName: resolver.name,
inputName,
payload,
payloadName,
resolver
};
}
async function convertType(options) {
const {
entryName,
type,
kind
} = options;
const parsed = (0, _ObjectType.parseFieldDefinitionConfig)(type, {
deep: {
omitMeta: true
}
});
const {
description
} = parsed;
// @ts-ignore circular
const result = await _CircularDeps.CircularDeps.objectToTypescript(entryName, {
CONVERT__REPLACE: {
...type
// prevents breaking the `export type...` etc, above. to improve.
}
}, {
...options,
format: false,
ignoreDefaultValues: kind !== 'input'
});
let code = result.split('\n').slice(1, -2).join('\n').replace('CONVERT__REPLACE', '');
if (code.startsWith('?')) {
code = `${code} | undefined`;
}
code = code.replace(/^\??: /, ``);
const comments = description ? `\n/** ${description} **/\n` : '';
return {
code,
comments,
description: description || ''
};
}
function queryExamples({
schema,
grouped,
randomText,
randomNumber,
resolver: resolverName
}) {
let templates = (0, _getQueryTemplates.getSchemaQueryTemplates)(schema);
let examples = '';
Object.entries(grouped).forEach(([_kind, resolvers]) => {
const kind = _kind;
if (!(resolvers !== null && resolvers !== void 0 && resolvers.length)) return;
const resolversRecord = templates.queryByResolver[kind];
Object.entries(resolversRecord).forEach(([name, parsed]) => {
var _resolver$argsType$__;
if (resolverName && resolverName !== name) return;
const resolver = (0, _utils.notNull)(resolvers.find(el => el.name === name));
const argsDef = (_resolver$argsType$__ = resolver.argsType.__lazyGetter.objectType) === null || _resolver$argsType$__ === void 0 ? void 0 : _resolver$argsType$__.definition;
const argsExamples = argsDef ? (0, _mockObject.objectMock)(argsDef, {
randomNumber,
randomText
}) : '';
examples += `${kind} ${name}${(0, _utils.capitalize)(kind)} { ${name}`;
if (parsed.argsParsed.vars.length) {
examples += '(';
const args = parsed.argsParsed.vars.map(val => {
const example = argsExamples[val.name];
const ser = typeof example === 'string' ? `"${example}"` : example && typeof example === 'object' ? _utils.BJSON.stringify(example, {
quoteKeys(str) {
if (str.match(/[-.]/)) return `"${str}"`;
return str;
}
}) : example;
return `${val.name}: ${ser}`;
});
examples += args.join(', ');
examples += ')';
}
if (parsed.query) {
examples += ` {${parsed.query}} `;
}
examples += '}\n';
});
});
return [(0, _utils.formatGraphQL)(examples), templates.fullQuery].join('\n');
}
//# sourceMappingURL=createGraphQLSchema.js.map