@backland/schema
Version:
TypeScript schema declaration and validation library with static type inference
476 lines (474 loc) • 12.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getInnerType = getInnerType;
exports.getQueryTemplates = getQueryTemplates;
exports.getSchemaQueryTemplates = getSchemaQueryTemplates;
exports.processField = processField;
var _utils = require("@backland/utils");
var _graphql = require("graphql");
var _createGraphQLSchema = require("../createGraphQLSchema");
var _LiteralField = require("../fields/LiteralField");
function getSchemaQueryTemplates(schema, options = {}) {
const {
depthLimit = 5000,
includeDeprecatedFields = true
} = options;
const query = schema.getQueryType();
const mutation = schema.getMutationType();
const subscription = schema.getSubscriptionType();
let fullQuery = '';
const queryByResolver = {
mutation: {},
query: {},
subscription: {}
};
const items = [query ? {
kind: _createGraphQLSchema.resolverKinds.query,
value: query
} : undefined, mutation ? {
kind: _createGraphQLSchema.resolverKinds.mutation,
value: mutation
} : undefined, subscription ? {
kind: _createGraphQLSchema.resolverKinds.subscription,
value: subscription
} : undefined].filter(Boolean);
items.forEach(item => {
if (!item) return;
const {
value,
kind
} = item;
Object.values(value.getFields()).forEach(graphQLField => {
const item = getQueryTemplates({
depthLimit,
graphQLField,
includeDeprecatedFields,
kind,
queryKind: 'mainQuery'
});
queryByResolver[kind][graphQLField.name] = item;
fullQuery += `${item.fullQuery}`;
});
});
return {
fullQuery: fullQuery.trim(),
queryByResolver
};
}
/**
* Generate the query for the specified field
* @param params
*/
function getQueryTemplates(params) {
const {
kind = 'query'
} = params;
const {
name
} = params.graphQLField;
const {
breadcrumb = [name],
graphQLField,
queryKind,
includeDeprecatedFields = true,
format = true
} = params;
const cache = {};
const {
payload
} = processField({
cache,
graphQLField,
includeDeprecatedFields
});
const fieldStrings = fieldsToString({
breadcrumb,
cache,
field: payload,
isTopQuery: false
});
const innerQuery = payload.isObject ? `{${fieldStrings.query}}` : '';
let fullQuery = '';
let fieldQuery = `${name} ${fieldStrings.argsParsed.strings.innerArgsString} ${innerQuery}`;
if (queryKind === 'mainQuery') {
const {
allArgs
} = fieldStrings;
const argValues = Object.values(allArgs);
const topArgs = argValues.map(el => el.strings.topArgsStringPart).filter(Boolean);
const innerArgs = argValues.map(el => el.strings.innerArgsStringPart).filter(Boolean);
const topArgsString = topArgs.length ? `(${topArgs.join(',')})` : '';
const innerArgsString = innerArgs.length ? `(${innerArgs.join(',')})` : '';
fullQuery += `${kind} ${name}${topArgsString} { ${name} ${innerArgsString} ${innerQuery}}`;
} else {
fullQuery += `${kind} ${fieldQuery}`;
}
if (format) {
fullQuery = prettifyQuery(fullQuery, 'mainQuery');
fieldQuery = prettifyQuery(fieldQuery, 'fieldQuery');
}
fullQuery += `\n`;
return {
...fieldStrings,
fieldQuery,
fields: payload,
fullQuery
};
}
function getFieldPayload({
graphQLField,
cache
}) {
const {
args,
type
} = graphQLField;
const {
innerType
} = getInnerType(type);
const innerTypeString = innerType.toJSON();
const {
hash: key,
readableHash
} = hashField(graphQLField);
if (cache[key]) return cache[key];
const payload = cache[key] = {
args,
children: [],
deprecationReason: (0, _utils.getByPath)(innerType, 'deprecationReason'),
description: (0, _utils.getByPath)(innerType, 'description'),
fields: {},
hash: key,
innerTypeString: innerTypeString,
isObject: false,
isUnion: false,
readableHash
};
if ((0, _graphql.isObjectType)(innerType)) {
payload.isObject = true;
Object.entries(innerType.getFields()).forEach(([name, field]) => {
payload.fields[name] = getFieldPayload({
cache,
graphQLField: field
});
});
}
if ((0, _graphql.isUnionType)(innerType)) {
const unionTypes = innerType.getTypes();
payload.isUnion = true;
unionTypes.forEach(unionType => {
const unionItem = getFieldPayload({
cache,
graphQLField: {
args: [],
astNode: unionType.astNode,
deprecationReason: (0, _utils.getByPath)(unionType, 'deprecationReason'),
description: unionType.description,
extensions: unionType.extensions,
name: unionType.name,
type: unionType
}
});
payload.children.push(unionItem);
});
}
return payload;
}
function processField(config) {
const {
includeDeprecatedFields,
cache = {},
graphQLField
} = config;
const {
innerType: type
} = getInnerType(graphQLField.type);
const {
hash: key
} = hashField(graphQLField);
if (cache[key] !== undefined) {
return {
cache,
payload: cache[key]
};
}
const payload = getFieldPayload({
cache,
graphQLField
});
if ((0, _graphql.isObjectType)(type)) {
const gqlTypeFields = type.getFields();
const fields = Object.entries(gqlTypeFields).filter(([_, child]) => {
return includeDeprecatedFields || !child.deprecationReason;
});
fields.forEach(([_, field]) => {
const item = getFieldPayload({
cache,
graphQLField: field
});
if (!item.isObject) {
return;
}
processField({
cache,
graphQLField: field,
includeDeprecatedFields
});
});
}
return {
cache,
payload
};
}
function fieldsToString(config) {
const {
field,
//
cache,
breadcrumb,
fieldsToStringCache = {}
} = config;
const {
fields,
args,
hash
} = field;
if (fieldsToStringCache[hash]) {
return fieldsToStringCache[hash];
}
const allArgs = config.allArgs || {};
const argsParsed = parseArgs({
args,
breadcrumb
});
allArgs[hash] = argsParsed;
const self = {
allArgs,
argsParsed,
query: ''
};
fieldsToStringCache[hash] = self;
if (field.isObject) {
Object.entries(fields || {}).forEach(([name, field]) => {
const _breadcrumb = [...breadcrumb, name];
if (!field.isObject && !field.isUnion) {
const {
argsParsed: {
strings: {
innerArgsString
}
}
} = fieldsToString({
allArgs,
breadcrumb: _breadcrumb,
cache,
field,
fieldsToStringCache,
isTopQuery: false
});
self.query += ` ${name}${innerArgsString} `;
return;
}
if (field.isObject) {
const child = fieldsToString({
allArgs,
breadcrumb: _breadcrumb,
cache,
field,
fieldsToStringCache,
isTopQuery: false
});
const {
argsParsed: {
strings: {
innerArgsString
}
}
} = child;
self.query += ` ${name}${innerArgsString} { `;
self.query += child.query;
self.query += ` } `;
}
if (field.isUnion) {
let childQuery = '';
const argsStrings = [];
const topArgsStrings = [];
field.children.forEach(item => {
const u_field = cache[item.hash];
const child = fieldsToString({
breadcrumb: _breadcrumb,
cache,
field: u_field,
fieldsToStringCache,
isTopQuery: false
});
const {
argsParsed: {
strings: {
innerArgsStringPart,
topArgsStringPart
}
}
} = child;
argsStrings.push(innerArgsStringPart);
topArgsStrings.push(topArgsStringPart);
childQuery += `... on ${item.innerTypeString} { ${child.query} }`;
});
self.query += ` ${name}${argsStrings ? `${argsStrings.join()}` : ''} { `;
self.query += childQuery;
self.query += ` } `;
}
});
} else {
const {
argsParsed: {
strings: {
innerArgsString
}
}
} = fieldsToString({
allArgs,
breadcrumb,
cache,
field,
fieldsToStringCache,
isTopQuery: false
});
self.query += `${innerArgsString}`;
}
return self;
}
function parseArgs(config) {
const {
args,
breadcrumb
} = config;
const vars = [];
if (!(args !== null && args !== void 0 && args.length)) {
return {
strings: parsedArgsToString(vars),
vars
};
}
let prefix = breadcrumb.length ? breadcrumb.join('_') + '_' : '';
args.forEach(arg => {
const {
type,
description,
deprecationReason,
defaultValue,
name
} = arg;
const varName = `$${prefix}${name}`;
const comments = descriptionsToComments(deprecationReason, description);
let _defaultValue = arg.defaultValue !== undefined ? _LiteralField.LiteralField.utils.serialize(defaultValue) : undefined;
if (typeof defaultValue === 'string') {
_defaultValue = JSON.stringify(_defaultValue);
}
vars.push({
comments,
defaultValue: _defaultValue,
name,
type: type.toString(),
varName
});
});
return {
strings: parsedArgsToString(vars),
vars
};
}
function parsedArgsToString(parsed) {
const innerParts = [];
const topParts = [];
parsed.forEach(({
type,
defaultValue,
name,
varName,
comments
}) => {
topParts.push(`${comments}${varName}: ${type}`);
innerParts.push(`${comments}${name}: ${varName}`);
if (defaultValue !== undefined) {
topParts.push(`= ${defaultValue}`);
}
});
const topArgsStringPart = topParts.join(',').replace(',=', ' = ');
const innerArgsStringPart = innerParts.join(',').replace(',=', ' = ');
return {
innerArgsString: innerArgsStringPart.length ? `(${innerArgsStringPart})` : '',
innerArgsStringPart: innerArgsStringPart,
topArgsString: topArgsStringPart.length ? `(${topArgsStringPart})` : '',
topArgsStringPart
};
}
function getInnerType(graphqlType) {
const wrappers = [];
while ('ofType' in graphqlType) {
if ((0, _graphql.isUnionType)(graphqlType)) {
wrappers.push('union');
}
if (!(0, _graphql.isNonNullType)(graphqlType)) {
wrappers.push('optional');
}
if ((0, _graphql.isListType)(graphqlType)) {
wrappers.push('list');
}
graphqlType = graphqlType.ofType;
}
const wrappersPath = wrappers.join('');
const innerTypeJSON = graphqlType.toJSON();
return {
innerType: graphqlType,
innerTypeJSON,
wrappers,
wrappersPath
};
}
function hashField(graphQLField) {
const {
wrappersPath,
innerType
} = getInnerType(graphQLField.type);
let argString = '';
graphQLField.args.forEach(({
type,
description,
deprecationReason,
defaultValue,
name
}) => {
argString += [name, type.toString(), description, deprecationReason, defaultValue].filter(el => el !== null && el !== undefined).join('');
});
const suffix = `${wrappersPath}${argString}`;
const hash = (0, _utils.hashString)(suffix);
return {
hash: `${innerType}${hash}`,
readableHash: `${innerType}${suffix}`
};
}
function descriptionsToComments(...list) {
const commentsList = [...list].filter(Boolean);
let comments = '';
if (commentsList.length) {
return `\n#${commentsList.join('\n#')}\n`;
}
return comments;
}
function prettifyQuery(value, queryKind) {
const isMainQuery = queryKind === 'mainQuery';
value = value.trim();
try {
value = (0, _utils.formatGraphQL)(isMainQuery ? value : `{${value}}`);
} catch (e) {
// throw new Error(`Failed to prettify:\n${value}\n\n\n${e.stack}`);
}
if (isMainQuery) return value;
return value.trim() //
.replace(/^{/, '').replace(/}$/, '');
}
//# sourceMappingURL=getQueryTemplates.js.map