2972 lines
129 KiB
JavaScript
2972 lines
129 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
|
|
const graphql = require('graphql');
|
|
const pluginHelpers = require('@graphql-codegen/plugin-helpers');
|
|
const changeCaseAll = require('change-case-all');
|
|
const path = require('path');
|
|
const parse = _interopDefault(require('parse-filepath'));
|
|
const autoBind = _interopDefault(require('auto-bind'));
|
|
const flatMap = _interopDefault(require('array.prototype.flatmap'));
|
|
const dependencyGraph = require('dependency-graph');
|
|
const gqlTag = _interopDefault(require('graphql-tag'));
|
|
const optimize = require('@graphql-tools/optimize');
|
|
const relayOperationOptimizer = require('@graphql-tools/relay-operation-optimizer');
|
|
|
|
const DEFAULT_SCALARS = {
|
|
ID: 'string',
|
|
String: 'string',
|
|
Boolean: 'boolean',
|
|
Int: 'number',
|
|
Float: 'number',
|
|
};
|
|
|
|
function isExternalMapperType(m) {
|
|
return !!m.import;
|
|
}
|
|
var MapperKind;
|
|
(function (MapperKind) {
|
|
MapperKind[MapperKind["Namespace"] = 0] = "Namespace";
|
|
MapperKind[MapperKind["Default"] = 1] = "Default";
|
|
MapperKind[MapperKind["Regular"] = 2] = "Regular";
|
|
})(MapperKind || (MapperKind = {}));
|
|
function prepareLegacy(mapper) {
|
|
const items = mapper.split('#');
|
|
const isNamespace = items.length === 3;
|
|
const isDefault = items[1].trim() === 'default' || items[1].startsWith('default ');
|
|
const hasAlias = items[1].includes(' as ');
|
|
return {
|
|
items,
|
|
isDefault,
|
|
isNamespace,
|
|
hasAlias,
|
|
};
|
|
}
|
|
function prepare(mapper) {
|
|
const [source, path] = mapper.split('#');
|
|
const isNamespace = path.includes('.');
|
|
const isDefault = path.trim() === 'default' || path.startsWith('default ');
|
|
const hasAlias = path.includes(' as ');
|
|
return {
|
|
items: isNamespace ? [source, ...path.split('.')] : [source, path],
|
|
isDefault,
|
|
isNamespace,
|
|
hasAlias,
|
|
};
|
|
}
|
|
function isLegacyMode(mapper) {
|
|
return mapper.split('#').length === 3;
|
|
}
|
|
function parseMapper(mapper, gqlTypeName = null, suffix) {
|
|
if (isExternalMapper(mapper)) {
|
|
const { isNamespace, isDefault, hasAlias, items } = isLegacyMode(mapper) ? prepareLegacy(mapper) : prepare(mapper);
|
|
const mapperKind = isNamespace
|
|
? MapperKind.Namespace
|
|
: isDefault
|
|
? MapperKind.Default
|
|
: MapperKind.Regular;
|
|
function handleAlias(isDefault = false) {
|
|
const [importedType, aliasType] = items[1].split(/\s+as\s+/);
|
|
const type = maybeSuffix(aliasType);
|
|
return {
|
|
importElement: isDefault ? type : `${importedType} as ${type}`,
|
|
type: type,
|
|
};
|
|
}
|
|
function maybeSuffix(type) {
|
|
if (suffix) {
|
|
return addSuffix(type, suffix);
|
|
}
|
|
return type;
|
|
}
|
|
function handle() {
|
|
switch (mapperKind) {
|
|
// ./my/module#Namespace#Identifier
|
|
case MapperKind.Namespace: {
|
|
const [, ns, identifier] = items;
|
|
return {
|
|
type: `${ns}.${identifier}`,
|
|
importElement: ns,
|
|
};
|
|
}
|
|
case MapperKind.Default: {
|
|
// ./my/module#default as alias
|
|
if (hasAlias) {
|
|
return handleAlias(true);
|
|
}
|
|
const type = maybeSuffix(`${gqlTypeName}`);
|
|
// ./my/module#default
|
|
return {
|
|
importElement: type,
|
|
type,
|
|
};
|
|
}
|
|
case MapperKind.Regular: {
|
|
// ./my/module#Identifier as alias
|
|
if (hasAlias) {
|
|
return handleAlias();
|
|
}
|
|
const identifier = items[1];
|
|
const type = maybeSuffix(identifier);
|
|
// ./my/module#Identifier
|
|
return {
|
|
type,
|
|
importElement: suffix ? `${identifier} as ${type}` : type,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
const { type, importElement } = handle();
|
|
return {
|
|
default: isDefault,
|
|
isExternal: true,
|
|
source: items[0],
|
|
type,
|
|
import: importElement.replace(/<(.*?)>/g, ''),
|
|
};
|
|
}
|
|
return {
|
|
isExternal: false,
|
|
type: mapper,
|
|
};
|
|
}
|
|
function addSuffix(element, suffix) {
|
|
const generic = element.indexOf('<');
|
|
if (generic === -1) {
|
|
return `${element}${suffix}`;
|
|
}
|
|
return `${element.slice(0, generic)}${suffix}${element.slice(generic)}`;
|
|
}
|
|
function isExternalMapper(value) {
|
|
return value.includes('#');
|
|
}
|
|
function transformMappers(rawMappers, mapperTypeSuffix) {
|
|
const result = {};
|
|
Object.keys(rawMappers).forEach(gqlTypeName => {
|
|
const mapperDef = rawMappers[gqlTypeName];
|
|
const parsedMapper = parseMapper(mapperDef, gqlTypeName, mapperTypeSuffix);
|
|
result[gqlTypeName] = parsedMapper;
|
|
});
|
|
return result;
|
|
}
|
|
function buildMapperImport(source, types, useTypeImports) {
|
|
if (!types || types.length === 0) {
|
|
return null;
|
|
}
|
|
const defaultType = types.find(t => t.asDefault === true);
|
|
let namedTypes = types.filter(t => !t.asDefault);
|
|
if (useTypeImports) {
|
|
if (defaultType) {
|
|
// default as Baz
|
|
namedTypes = [{ identifier: `default as ${defaultType.identifier}` }, ...namedTypes];
|
|
}
|
|
// { Foo, Bar as BarModel }
|
|
const namedImports = namedTypes.length ? `{ ${namedTypes.map(t => t.identifier).join(', ')} }` : '';
|
|
// { default as Baz, Foo, Bar as BarModel }
|
|
return `import type ${[namedImports].filter(Boolean).join(', ')} from '${source}';`;
|
|
}
|
|
// { Foo, Bar as BarModel }
|
|
const namedImports = namedTypes.length ? `{ ${namedTypes.map(t => t.identifier).join(', ')} }` : '';
|
|
// Baz
|
|
const defaultImport = defaultType ? defaultType.identifier : '';
|
|
// Baz, { Foo, Bar as BarModel }
|
|
return `import ${[defaultImport, namedImports].filter(Boolean).join(', ')} from '${source}';`;
|
|
}
|
|
|
|
const getConfigValue = (value, defaultValue) => {
|
|
if (value === null || value === undefined) {
|
|
return defaultValue;
|
|
}
|
|
return value;
|
|
};
|
|
function quoteIfNeeded(array, joinWith = ' & ') {
|
|
if (array.length === 0) {
|
|
return '';
|
|
}
|
|
else if (array.length === 1) {
|
|
return array[0];
|
|
}
|
|
else {
|
|
return `(${array.join(joinWith)})`;
|
|
}
|
|
}
|
|
function block(array) {
|
|
return array && array.length !== 0 ? '{\n' + array.join('\n') + '\n}' : '';
|
|
}
|
|
function wrapWithSingleQuotes(value, skipNumericCheck = false) {
|
|
if (skipNumericCheck) {
|
|
if (typeof value === 'number') {
|
|
return `${value}`;
|
|
}
|
|
else {
|
|
return `'${value}'`;
|
|
}
|
|
}
|
|
if (typeof value === 'number' ||
|
|
(typeof value === 'string' && !isNaN(parseInt(value)) && parseFloat(value).toString() === value)) {
|
|
return `${value}`;
|
|
}
|
|
return `'${value}'`;
|
|
}
|
|
function breakLine(str) {
|
|
return str + '\n';
|
|
}
|
|
function indent(str, count = 1) {
|
|
return new Array(count).fill(' ').join('') + str;
|
|
}
|
|
function indentMultiline(str, count = 1) {
|
|
const indentation = new Array(count).fill(' ').join('');
|
|
const replaceWith = '\n' + indentation;
|
|
return indentation + str.replace(/\n/g, replaceWith);
|
|
}
|
|
function transformComment(comment, indentLevel = 0, disabled = false) {
|
|
if (!comment || comment === '' || disabled) {
|
|
return '';
|
|
}
|
|
if (isStringValueNode(comment)) {
|
|
comment = comment.value;
|
|
}
|
|
comment = comment.split('*/').join('*\\/');
|
|
let lines = comment.split('\n');
|
|
if (lines.length === 1) {
|
|
return indent(`/** ${lines[0]} */\n`, indentLevel);
|
|
}
|
|
lines = ['/**', ...lines.map(line => ` * ${line}`), ' */\n'];
|
|
return stripTrailingSpaces(lines.map(line => indent(line, indentLevel)).join('\n'));
|
|
}
|
|
class DeclarationBlock {
|
|
constructor(_config) {
|
|
this._config = _config;
|
|
this._decorator = null;
|
|
this._export = false;
|
|
this._name = null;
|
|
this._kind = null;
|
|
this._methodName = null;
|
|
this._content = null;
|
|
this._block = null;
|
|
this._nameGenerics = null;
|
|
this._comment = null;
|
|
this._ignoreBlockWrapper = false;
|
|
this._config = {
|
|
blockWrapper: '',
|
|
blockTransformer: block => block,
|
|
enumNameValueSeparator: ':',
|
|
...this._config,
|
|
};
|
|
}
|
|
withDecorator(decorator) {
|
|
this._decorator = decorator;
|
|
return this;
|
|
}
|
|
export(exp = true) {
|
|
if (!this._config.ignoreExport) {
|
|
this._export = exp;
|
|
}
|
|
return this;
|
|
}
|
|
asKind(kind) {
|
|
this._kind = kind;
|
|
return this;
|
|
}
|
|
withComment(comment, disabled = false) {
|
|
const nonEmptyComment = isStringValueNode(comment) ? !!comment.value : !!comment;
|
|
if (nonEmptyComment && !disabled) {
|
|
this._comment = transformComment(comment, 0);
|
|
}
|
|
return this;
|
|
}
|
|
withMethodCall(methodName, ignoreBlockWrapper = false) {
|
|
this._methodName = methodName;
|
|
this._ignoreBlockWrapper = ignoreBlockWrapper;
|
|
return this;
|
|
}
|
|
withBlock(block) {
|
|
this._block = block;
|
|
return this;
|
|
}
|
|
withContent(content) {
|
|
this._content = content;
|
|
return this;
|
|
}
|
|
withName(name, generics = null) {
|
|
this._name = name;
|
|
this._nameGenerics = generics;
|
|
return this;
|
|
}
|
|
get string() {
|
|
let result = '';
|
|
if (this._decorator) {
|
|
result += this._decorator + '\n';
|
|
}
|
|
if (this._export) {
|
|
result += 'export ';
|
|
}
|
|
if (this._kind) {
|
|
let extra = '';
|
|
let name = '';
|
|
if (['type', 'const', 'var', 'let'].includes(this._kind)) {
|
|
extra = '= ';
|
|
}
|
|
if (this._name) {
|
|
name = this._name + (this._nameGenerics || '') + ' ';
|
|
}
|
|
result += this._kind + ' ' + name + extra;
|
|
}
|
|
if (this._block) {
|
|
if (this._content) {
|
|
result += this._content;
|
|
}
|
|
const blockWrapper = this._ignoreBlockWrapper ? '' : this._config.blockWrapper;
|
|
const before = '{' + blockWrapper;
|
|
const after = blockWrapper + '}';
|
|
const block = [before, this._block, after].filter(val => !!val).join('\n');
|
|
if (this._methodName) {
|
|
result += `${this._methodName}(${this._config.blockTransformer(block)})`;
|
|
}
|
|
else {
|
|
result += this._config.blockTransformer(block);
|
|
}
|
|
}
|
|
else if (this._content) {
|
|
result += this._content;
|
|
}
|
|
else if (this._kind) {
|
|
result += this._config.blockTransformer('{}');
|
|
}
|
|
return stripTrailingSpaces((this._comment ? this._comment : '') +
|
|
result +
|
|
(this._kind === 'interface' || this._kind === 'enum' || this._kind === 'namespace' || this._kind === 'function'
|
|
? ''
|
|
: ';') +
|
|
'\n');
|
|
}
|
|
}
|
|
function getBaseTypeNode(typeNode) {
|
|
if (typeNode.kind === graphql.Kind.LIST_TYPE || typeNode.kind === graphql.Kind.NON_NULL_TYPE) {
|
|
return getBaseTypeNode(typeNode.type);
|
|
}
|
|
return typeNode;
|
|
}
|
|
function convertNameParts(str, func, removeUnderscore = false) {
|
|
if (removeUnderscore) {
|
|
return func(str);
|
|
}
|
|
return str
|
|
.split('_')
|
|
.map(s => func(s))
|
|
.join('_');
|
|
}
|
|
function buildScalarsFromConfig(schema, config, defaultScalarsMapping = DEFAULT_SCALARS, defaultScalarType = 'any') {
|
|
return buildScalars(schema, config.scalars, defaultScalarsMapping, config.strictScalars ? null : config.defaultScalarType || defaultScalarType);
|
|
}
|
|
function buildScalars(schema, scalarsMapping, defaultScalarsMapping = DEFAULT_SCALARS, defaultScalarType = 'any') {
|
|
const result = {};
|
|
Object.keys(defaultScalarsMapping).forEach(name => {
|
|
result[name] = parseMapper(defaultScalarsMapping[name]);
|
|
});
|
|
if (schema) {
|
|
const typeMap = schema.getTypeMap();
|
|
Object.keys(typeMap)
|
|
.map(typeName => typeMap[typeName])
|
|
.filter(type => graphql.isScalarType(type))
|
|
.map((scalarType) => {
|
|
const name = scalarType.name;
|
|
if (typeof scalarsMapping === 'string') {
|
|
const value = parseMapper(scalarsMapping + '#' + name, name);
|
|
result[name] = value;
|
|
}
|
|
else if (scalarsMapping && typeof scalarsMapping[name] === 'string') {
|
|
const value = parseMapper(scalarsMapping[name], name);
|
|
result[name] = value;
|
|
}
|
|
else if (scalarsMapping && scalarsMapping[name]) {
|
|
result[name] = {
|
|
isExternal: false,
|
|
type: JSON.stringify(scalarsMapping[name]),
|
|
};
|
|
}
|
|
else if (!defaultScalarsMapping[name]) {
|
|
if (defaultScalarType === null) {
|
|
throw new Error(`Unknown scalar type ${name}. Please override it using the "scalars" configuration field!`);
|
|
}
|
|
result[name] = {
|
|
isExternal: false,
|
|
type: defaultScalarType,
|
|
};
|
|
}
|
|
});
|
|
}
|
|
else if (scalarsMapping) {
|
|
if (typeof scalarsMapping === 'string') {
|
|
throw new Error('Cannot use string scalars mapping when building without a schema');
|
|
}
|
|
Object.keys(scalarsMapping).forEach(name => {
|
|
if (typeof scalarsMapping[name] === 'string') {
|
|
const value = parseMapper(scalarsMapping[name], name);
|
|
result[name] = value;
|
|
}
|
|
else {
|
|
result[name] = {
|
|
isExternal: false,
|
|
type: JSON.stringify(scalarsMapping[name]),
|
|
};
|
|
}
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
function isStringValueNode(node) {
|
|
return node && typeof node === 'object' && node.kind === graphql.Kind.STRING;
|
|
}
|
|
function isRootType(type, schema) {
|
|
return (graphql.isEqualType(type, schema.getQueryType()) ||
|
|
graphql.isEqualType(type, schema.getMutationType()) ||
|
|
graphql.isEqualType(type, schema.getSubscriptionType()));
|
|
}
|
|
function getRootTypeNames(schema) {
|
|
return [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]
|
|
.filter(t => t)
|
|
.map(t => t.name);
|
|
}
|
|
function stripMapperTypeInterpolation(identifier) {
|
|
return identifier.trim().replace(/<{.*}>/, '');
|
|
}
|
|
const OMIT_TYPE = 'export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;';
|
|
const REQUIRE_FIELDS_TYPE = `export type RequireFields<T, K extends keyof T> = { [X in Exclude<keyof T, K>]?: T[X] } & { [P in K]-?: NonNullable<T[P]> };`;
|
|
function mergeSelectionSets(selectionSet1, selectionSet2) {
|
|
const newSelections = [...selectionSet1.selections];
|
|
for (const selection2 of selectionSet2.selections) {
|
|
if (selection2.kind === 'FragmentSpread') {
|
|
newSelections.push(selection2);
|
|
continue;
|
|
}
|
|
if (selection2.kind !== 'Field') {
|
|
throw new TypeError('Invalid state.');
|
|
}
|
|
const match = newSelections.find(selection1 => selection1.kind === 'Field' && getFieldNodeNameValue(selection1) === getFieldNodeNameValue(selection2));
|
|
if (match) {
|
|
// recursively merge all selection sets
|
|
if (match.kind === 'Field' && match.selectionSet && selection2.selectionSet) {
|
|
mergeSelectionSets(match.selectionSet, selection2.selectionSet);
|
|
}
|
|
continue;
|
|
}
|
|
newSelections.push(selection2);
|
|
}
|
|
// replace existing selections
|
|
selectionSet1.selections = newSelections;
|
|
}
|
|
const getFieldNodeNameValue = (node) => {
|
|
return (node.alias || node.name).value;
|
|
};
|
|
function separateSelectionSet(selections) {
|
|
return {
|
|
fields: selections.filter(s => s.kind === graphql.Kind.FIELD),
|
|
inlines: selections.filter(s => s.kind === graphql.Kind.INLINE_FRAGMENT),
|
|
spreads: selections.filter(s => s.kind === graphql.Kind.FRAGMENT_SPREAD),
|
|
};
|
|
}
|
|
function getPossibleTypes(schema, type) {
|
|
if (graphql.isListType(type) || graphql.isNonNullType(type)) {
|
|
return getPossibleTypes(schema, type.ofType);
|
|
}
|
|
else if (graphql.isObjectType(type)) {
|
|
return [type];
|
|
}
|
|
else if (graphql.isAbstractType(type)) {
|
|
return schema.getPossibleTypes(type);
|
|
}
|
|
return [];
|
|
}
|
|
function hasConditionalDirectives(field) {
|
|
var _a;
|
|
const CONDITIONAL_DIRECTIVES = ['skip', 'include'];
|
|
return (_a = field.directives) === null || _a === void 0 ? void 0 : _a.some(directive => CONDITIONAL_DIRECTIVES.includes(directive.name.value));
|
|
}
|
|
function wrapTypeWithModifiers(baseType, type, options) {
|
|
let currentType = type;
|
|
const modifiers = [];
|
|
while (currentType) {
|
|
if (graphql.isNonNullType(currentType)) {
|
|
currentType = currentType.ofType;
|
|
}
|
|
else {
|
|
modifiers.push(options.wrapOptional);
|
|
}
|
|
if (graphql.isListType(currentType)) {
|
|
modifiers.push(options.wrapArray);
|
|
currentType = currentType.ofType;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
return modifiers.reduceRight((result, modifier) => modifier(result), baseType);
|
|
}
|
|
function removeDescription(nodes) {
|
|
return nodes.map(node => ({ ...node, description: undefined }));
|
|
}
|
|
function wrapTypeNodeWithModifiers(baseType, typeNode) {
|
|
switch (typeNode.kind) {
|
|
case graphql.Kind.NAMED_TYPE: {
|
|
return `Maybe<${baseType}>`;
|
|
}
|
|
case graphql.Kind.NON_NULL_TYPE: {
|
|
const innerType = wrapTypeNodeWithModifiers(baseType, typeNode.type);
|
|
return clearOptional(innerType);
|
|
}
|
|
case graphql.Kind.LIST_TYPE: {
|
|
const innerType = wrapTypeNodeWithModifiers(baseType, typeNode.type);
|
|
return `Maybe<Array<${innerType}>>`;
|
|
}
|
|
}
|
|
}
|
|
function clearOptional(str) {
|
|
const rgx = new RegExp(`^Maybe<(.*?)>$`, 'i');
|
|
if (str.startsWith(`Maybe`)) {
|
|
return str.replace(rgx, '$1');
|
|
}
|
|
return str;
|
|
}
|
|
function stripTrailingSpaces(str) {
|
|
return str.replace(/ +\n/g, '\n');
|
|
}
|
|
|
|
function getKind(node) {
|
|
if (typeof node === 'string') {
|
|
return 'typeNames';
|
|
}
|
|
if (['EnumValueDefinition', 'EnumValue'].includes(node.kind)) {
|
|
return 'enumValues';
|
|
}
|
|
return 'typeNames';
|
|
}
|
|
function getName(node) {
|
|
if (node == null) {
|
|
return undefined;
|
|
}
|
|
if (typeof node === 'string') {
|
|
return node;
|
|
}
|
|
switch (node.kind) {
|
|
case 'OperationDefinition':
|
|
case 'Variable':
|
|
case 'Argument':
|
|
case 'FragmentSpread':
|
|
case 'FragmentDefinition':
|
|
case 'ObjectField':
|
|
case 'Directive':
|
|
case 'NamedType':
|
|
case 'ScalarTypeDefinition':
|
|
case 'ObjectTypeDefinition':
|
|
case 'FieldDefinition':
|
|
case 'InputValueDefinition':
|
|
case 'InterfaceTypeDefinition':
|
|
case 'UnionTypeDefinition':
|
|
case 'EnumTypeDefinition':
|
|
case 'EnumValueDefinition':
|
|
case 'InputObjectTypeDefinition':
|
|
case 'DirectiveDefinition': {
|
|
return getName(node.name);
|
|
}
|
|
case 'Name': {
|
|
return node.value;
|
|
}
|
|
case 'Field': {
|
|
return getName(node.alias || node.name);
|
|
}
|
|
case 'VariableDefinition': {
|
|
return getName(node.variable);
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
function convertFactory(config) {
|
|
function resolveConventionName(type) {
|
|
if (!config.namingConvention) {
|
|
return (str, opts = {}) => {
|
|
return convertNameParts(str, changeCaseAll.pascalCase, getConfigValue((opts || {}).transformUnderscore, false));
|
|
};
|
|
}
|
|
if (typeof config.namingConvention === 'string') {
|
|
if (config.namingConvention === 'keep') {
|
|
return str => str;
|
|
}
|
|
return (str, opts = {}) => {
|
|
return convertNameParts(str, pluginHelpers.resolveExternalModuleAndFn(config.namingConvention), getConfigValue((opts || {}).transformUnderscore, false));
|
|
};
|
|
}
|
|
if (typeof config.namingConvention === 'function') {
|
|
return (str, opts = {}) => {
|
|
return convertNameParts(str, config.namingConvention, getConfigValue((opts || {}).transformUnderscore, false));
|
|
};
|
|
}
|
|
if (typeof config.namingConvention === 'object' && config.namingConvention[type] === 'keep') {
|
|
return str => str;
|
|
}
|
|
if (typeof config.namingConvention === 'object') {
|
|
if (!config.namingConvention[type]) {
|
|
return (str, opts = {}) => {
|
|
const transformUnderscore = config.namingConvention.transformUnderscore || (opts || {}).transformUnderscore;
|
|
return convertNameParts(str, changeCaseAll.pascalCase, getConfigValue(transformUnderscore, false));
|
|
};
|
|
}
|
|
return (str, opts = {}) => {
|
|
return convertNameParts(str, pluginHelpers.resolveExternalModuleAndFn(config.namingConvention[type]), getConfigValue((opts || {}).transformUnderscore, true));
|
|
};
|
|
}
|
|
return config.namingConvention[type];
|
|
}
|
|
return (node, opts) => {
|
|
const prefix = opts && opts.prefix;
|
|
const suffix = opts && opts.suffix;
|
|
const kind = getKind(node);
|
|
const str = [prefix || '', getName(node), suffix || ''].join('');
|
|
return resolveConventionName(kind)(str, opts);
|
|
};
|
|
}
|
|
|
|
function escapeString(str) {
|
|
return str.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/'/g, "\\'");
|
|
}
|
|
function parseEnumValues({ schema, mapOrStr = {}, ignoreEnumValuesFromSchema, }) {
|
|
const allTypes = schema.getTypeMap();
|
|
const allEnums = Object.keys(allTypes).filter(t => graphql.isEnumType(allTypes[t]));
|
|
if (typeof mapOrStr === 'object') {
|
|
if (!ignoreEnumValuesFromSchema) {
|
|
for (const enumTypeName of allEnums) {
|
|
const enumType = schema.getType(enumTypeName);
|
|
for (const { name, value } of enumType.getValues()) {
|
|
if (value && value !== name) {
|
|
mapOrStr[enumTypeName] = mapOrStr[enumTypeName] || {};
|
|
if (typeof mapOrStr[enumTypeName] !== 'string' && !mapOrStr[enumTypeName][name]) {
|
|
mapOrStr[enumTypeName][name] = typeof value === 'string' ? escapeString(value) : value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const invalidMappings = Object.keys(mapOrStr).filter(gqlName => !allEnums.includes(gqlName));
|
|
if (invalidMappings.length > 0) {
|
|
throw new pluginHelpers.DetailedError(`Invalid 'enumValues' mapping!`, `The following types does not exist in your GraphQL schema: ${invalidMappings.join(', ')}`);
|
|
}
|
|
return Object.keys(mapOrStr).reduce((prev, gqlIdentifier) => {
|
|
const pointer = mapOrStr[gqlIdentifier];
|
|
if (typeof pointer === 'string') {
|
|
const mapper = parseMapper(pointer, gqlIdentifier);
|
|
return {
|
|
...prev,
|
|
[gqlIdentifier]: {
|
|
isDefault: mapper.isExternal && mapper.default,
|
|
typeIdentifier: gqlIdentifier,
|
|
sourceFile: mapper.isExternal ? mapper.source : null,
|
|
sourceIdentifier: mapper.type,
|
|
importIdentifier: mapper.isExternal ? mapper.import : null,
|
|
mappedValues: null,
|
|
},
|
|
};
|
|
}
|
|
else if (typeof pointer === 'object') {
|
|
return {
|
|
...prev,
|
|
[gqlIdentifier]: {
|
|
isDefault: false,
|
|
typeIdentifier: gqlIdentifier,
|
|
sourceFile: null,
|
|
sourceIdentifier: null,
|
|
importIdentifier: null,
|
|
mappedValues: pointer,
|
|
},
|
|
};
|
|
}
|
|
else {
|
|
throw new pluginHelpers.DetailedError(`Invalid "enumValues" configuration`, `Enum "${gqlIdentifier}": expected string or object (with enum values mapping)`);
|
|
}
|
|
}, {});
|
|
}
|
|
else if (typeof mapOrStr === 'string') {
|
|
return allEnums
|
|
.filter(enumName => !enumName.startsWith('__'))
|
|
.reduce((prev, enumName) => {
|
|
return {
|
|
...prev,
|
|
[enumName]: {
|
|
isDefault: false,
|
|
typeIdentifier: enumName,
|
|
sourceFile: mapOrStr,
|
|
sourceIdentifier: enumName,
|
|
importIdentifier: enumName,
|
|
mappedValues: null,
|
|
},
|
|
};
|
|
}, {});
|
|
}
|
|
return {};
|
|
}
|
|
|
|
const DEFAULT_DECLARATION_KINDS = {
|
|
scalar: 'type',
|
|
input: 'type',
|
|
type: 'type',
|
|
interface: 'type',
|
|
arguments: 'type',
|
|
};
|
|
function normalizeDeclarationKind(declarationKind) {
|
|
if (typeof declarationKind === 'string') {
|
|
return {
|
|
scalar: declarationKind,
|
|
input: declarationKind,
|
|
type: declarationKind,
|
|
interface: declarationKind,
|
|
arguments: declarationKind,
|
|
};
|
|
}
|
|
return {
|
|
...DEFAULT_DECLARATION_KINDS,
|
|
...declarationKind,
|
|
};
|
|
}
|
|
|
|
const DEFAULT_AVOID_OPTIONALS = {
|
|
object: false,
|
|
inputValue: false,
|
|
field: false,
|
|
defaultValue: false,
|
|
};
|
|
function normalizeAvoidOptionals(avoidOptionals) {
|
|
if (typeof avoidOptionals === 'boolean') {
|
|
return {
|
|
object: avoidOptionals,
|
|
inputValue: avoidOptionals,
|
|
field: avoidOptionals,
|
|
defaultValue: avoidOptionals,
|
|
};
|
|
}
|
|
return {
|
|
...DEFAULT_AVOID_OPTIONALS,
|
|
...avoidOptionals,
|
|
};
|
|
}
|
|
|
|
function generateFragmentImportStatement(statement, kind) {
|
|
const { importSource: fragmentImportSource, ...rest } = statement;
|
|
const { identifiers, path, namespace } = fragmentImportSource;
|
|
const importSource = {
|
|
identifiers: identifiers
|
|
.filter(fragmentImport => kind === 'both' || kind === fragmentImport.kind)
|
|
.map(({ name }) => name),
|
|
path,
|
|
namespace,
|
|
};
|
|
return generateImportStatement({
|
|
importSource,
|
|
...rest,
|
|
typesImport: kind === 'type' ? statement.typesImport : false,
|
|
});
|
|
}
|
|
function generateImportStatement(statement) {
|
|
const { baseDir, importSource, outputPath, typesImport } = statement;
|
|
const importPath = resolveImportPath(baseDir, outputPath, importSource.path);
|
|
const importNames = importSource.identifiers && importSource.identifiers.length
|
|
? `{ ${Array.from(new Set(importSource.identifiers)).join(', ')} }`
|
|
: '*';
|
|
const importAlias = importSource.namespace ? ` as ${importSource.namespace}` : '';
|
|
const importStatement = typesImport ? 'import type' : 'import';
|
|
return `${importStatement} ${importNames}${importAlias} from '${importPath}';${importAlias ? '\n' : ''}`;
|
|
}
|
|
function resolveImportPath(baseDir, outputPath, sourcePath) {
|
|
const shouldAbsolute = !sourcePath.startsWith('~');
|
|
if (shouldAbsolute) {
|
|
const absGeneratedFilePath = path.resolve(baseDir, outputPath);
|
|
const absImportFilePath = path.resolve(baseDir, sourcePath);
|
|
return resolveRelativeImport(absGeneratedFilePath, absImportFilePath);
|
|
}
|
|
else {
|
|
return sourcePath.replace(`~`, '');
|
|
}
|
|
}
|
|
function resolveRelativeImport(from, to) {
|
|
if (!path.isAbsolute(from)) {
|
|
throw new Error(`Argument 'from' must be an absolute path, '${from}' given.`);
|
|
}
|
|
if (!path.isAbsolute(to)) {
|
|
throw new Error(`Argument 'to' must be an absolute path, '${to}' given.`);
|
|
}
|
|
return fixLocalFilePath(clearExtension(path.relative(path.dirname(from), to)));
|
|
}
|
|
function resolveImportSource(source) {
|
|
return typeof source === 'string' ? { path: source } : source;
|
|
}
|
|
function clearExtension(path$1) {
|
|
const parsedPath = parse(path$1);
|
|
return path.join(parsedPath.dir, parsedPath.name).replace(/\\/g, '/');
|
|
}
|
|
function fixLocalFilePath(path) {
|
|
return !path.startsWith('..') ? `./${path}` : path;
|
|
}
|
|
|
|
class BaseVisitor {
|
|
constructor(rawConfig, additionalConfig) {
|
|
this._declarationBlockConfig = {};
|
|
this._parsedConfig = {
|
|
convert: convertFactory(rawConfig),
|
|
typesPrefix: rawConfig.typesPrefix || '',
|
|
typesSuffix: rawConfig.typesSuffix || '',
|
|
externalFragments: rawConfig.externalFragments || [],
|
|
fragmentImports: rawConfig.fragmentImports || [],
|
|
addTypename: !rawConfig.skipTypename,
|
|
nonOptionalTypename: !!rawConfig.nonOptionalTypename,
|
|
useTypeImports: !!rawConfig.useTypeImports,
|
|
dedupeFragments: !!rawConfig.dedupeFragments,
|
|
allowEnumStringTypes: !!rawConfig.allowEnumStringTypes,
|
|
...(additionalConfig || {}),
|
|
};
|
|
this.scalars = {};
|
|
Object.keys(this.config.scalars || {}).forEach(key => {
|
|
this.scalars[key] = this.config.scalars[key].type;
|
|
});
|
|
autoBind(this);
|
|
}
|
|
getVisitorKindContextFromAncestors(ancestors) {
|
|
if (!ancestors) {
|
|
return [];
|
|
}
|
|
return ancestors.map(t => t.kind).filter(Boolean);
|
|
}
|
|
get config() {
|
|
return this._parsedConfig;
|
|
}
|
|
convertName(node, options) {
|
|
const useTypesPrefix = typeof (options && options.useTypesPrefix) === 'boolean' ? options.useTypesPrefix : true;
|
|
const useTypesSuffix = typeof (options && options.useTypesSuffix) === 'boolean' ? options.useTypesSuffix : true;
|
|
let convertedName = '';
|
|
if (useTypesPrefix) {
|
|
convertedName += this.config.typesPrefix;
|
|
}
|
|
convertedName += this.config.convert(node, options);
|
|
if (useTypesSuffix) {
|
|
convertedName += this.config.typesSuffix;
|
|
}
|
|
return convertedName;
|
|
}
|
|
getOperationSuffix(node, operationType) {
|
|
const { omitOperationSuffix = false, dedupeOperationSuffix = false } = this.config;
|
|
const operationName = typeof node === 'string' ? node : node.name ? node.name.value : '';
|
|
return omitOperationSuffix
|
|
? ''
|
|
: dedupeOperationSuffix && operationName.toLowerCase().endsWith(operationType.toLowerCase())
|
|
? ''
|
|
: operationType;
|
|
}
|
|
getFragmentSuffix(node) {
|
|
return this.getOperationSuffix(node, 'Fragment');
|
|
}
|
|
getFragmentName(node) {
|
|
return this.convertName(node, {
|
|
suffix: this.getFragmentSuffix(node),
|
|
useTypesPrefix: false,
|
|
});
|
|
}
|
|
getFragmentVariableName(node) {
|
|
const { omitOperationSuffix = false, dedupeOperationSuffix = false, fragmentVariableSuffix = 'FragmentDoc', fragmentVariablePrefix = '', } = this.config;
|
|
const fragmentName = typeof node === 'string' ? node : node.name.value;
|
|
const suffix = omitOperationSuffix
|
|
? ''
|
|
: dedupeOperationSuffix &&
|
|
fragmentName.toLowerCase().endsWith('fragment') &&
|
|
fragmentVariableSuffix.toLowerCase().startsWith('fragment')
|
|
? fragmentVariableSuffix.substring('fragment'.length)
|
|
: fragmentVariableSuffix;
|
|
return this.convertName(node, {
|
|
prefix: fragmentVariablePrefix,
|
|
suffix,
|
|
useTypesPrefix: false,
|
|
});
|
|
}
|
|
getPunctuation(_declarationKind) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
class OperationVariablesToObject {
|
|
constructor(_scalars, _convertName, _namespacedImportName = null, _enumNames = [], _enumPrefix = true, _enumValues = {}, _applyCoercion = false) {
|
|
this._scalars = _scalars;
|
|
this._convertName = _convertName;
|
|
this._namespacedImportName = _namespacedImportName;
|
|
this._enumNames = _enumNames;
|
|
this._enumPrefix = _enumPrefix;
|
|
this._enumValues = _enumValues;
|
|
this._applyCoercion = _applyCoercion;
|
|
autoBind(this);
|
|
}
|
|
getName(node) {
|
|
if (node.name) {
|
|
if (typeof node.name === 'string') {
|
|
return node.name;
|
|
}
|
|
return node.name.value;
|
|
}
|
|
else if (node.variable) {
|
|
return node.variable.name.value;
|
|
}
|
|
return null;
|
|
}
|
|
transform(variablesNode) {
|
|
if (!variablesNode || variablesNode.length === 0) {
|
|
return null;
|
|
}
|
|
return (variablesNode.map(variable => indent(this.transformVariable(variable))).join(`${this.getPunctuation()}\n`) +
|
|
this.getPunctuation());
|
|
}
|
|
getScalar(name) {
|
|
const prefix = this._namespacedImportName ? `${this._namespacedImportName}.` : '';
|
|
return `${prefix}Scalars['${name}']`;
|
|
}
|
|
transformVariable(variable) {
|
|
let typeValue = null;
|
|
const prefix = this._namespacedImportName ? `${this._namespacedImportName}.` : '';
|
|
if (typeof variable.type === 'string') {
|
|
typeValue = variable.type;
|
|
}
|
|
else {
|
|
const baseType = getBaseTypeNode(variable.type);
|
|
const typeName = baseType.name.value;
|
|
if (this._scalars[typeName]) {
|
|
typeValue = this.getScalar(typeName);
|
|
}
|
|
else if (this._enumValues[typeName] && this._enumValues[typeName].sourceFile) {
|
|
typeValue = this._enumValues[typeName].typeIdentifier || this._enumValues[typeName].sourceIdentifier;
|
|
}
|
|
else {
|
|
typeValue = `${prefix}${this._convertName(baseType, {
|
|
useTypesPrefix: this._enumNames.includes(typeName) ? this._enumPrefix : true,
|
|
})}`;
|
|
}
|
|
}
|
|
const fieldName = this.getName(variable);
|
|
const fieldType = this.wrapAstTypeWithModifiers(typeValue, variable.type, this._applyCoercion);
|
|
const hasDefaultValue = variable.defaultValue != null && typeof variable.defaultValue !== 'undefined';
|
|
const isNonNullType = variable.type.kind === graphql.Kind.NON_NULL_TYPE;
|
|
const formattedFieldString = this.formatFieldString(fieldName, isNonNullType, hasDefaultValue);
|
|
const formattedTypeString = this.formatTypeString(fieldType, isNonNullType, hasDefaultValue);
|
|
return `${formattedFieldString}: ${formattedTypeString}`;
|
|
}
|
|
wrapAstTypeWithModifiers(_baseType, _typeNode, _applyCoercion) {
|
|
throw new Error(`You must override "wrapAstTypeWithModifiers" of OperationVariablesToObject!`);
|
|
}
|
|
formatFieldString(fieldName, isNonNullType, _hasDefaultValue) {
|
|
return fieldName;
|
|
}
|
|
formatTypeString(fieldType, isNonNullType, hasDefaultValue) {
|
|
const prefix = this._namespacedImportName ? `${this._namespacedImportName}.` : '';
|
|
if (hasDefaultValue) {
|
|
return `${prefix}Maybe<${fieldType}>`;
|
|
}
|
|
return fieldType;
|
|
}
|
|
getPunctuation() {
|
|
return ',';
|
|
}
|
|
}
|
|
|
|
class BaseTypesVisitor extends BaseVisitor {
|
|
constructor(_schema, rawConfig, additionalConfig, defaultScalars = DEFAULT_SCALARS) {
|
|
super(rawConfig, {
|
|
enumPrefix: getConfigValue(rawConfig.enumPrefix, true),
|
|
onlyOperationTypes: getConfigValue(rawConfig.onlyOperationTypes, false),
|
|
addUnderscoreToArgsType: getConfigValue(rawConfig.addUnderscoreToArgsType, false),
|
|
enumValues: parseEnumValues({
|
|
schema: _schema,
|
|
mapOrStr: rawConfig.enumValues,
|
|
ignoreEnumValuesFromSchema: rawConfig.ignoreEnumValuesFromSchema,
|
|
}),
|
|
declarationKind: normalizeDeclarationKind(rawConfig.declarationKind),
|
|
scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
|
|
fieldWrapperValue: getConfigValue(rawConfig.fieldWrapperValue, 'T'),
|
|
wrapFieldDefinitions: getConfigValue(rawConfig.wrapFieldDefinitions, false),
|
|
entireFieldWrapperValue: getConfigValue(rawConfig.entireFieldWrapperValue, 'T'),
|
|
wrapEntireDefinitions: getConfigValue(rawConfig.wrapEntireFieldDefinitions, false),
|
|
ignoreEnumValuesFromSchema: getConfigValue(rawConfig.ignoreEnumValuesFromSchema, false),
|
|
...additionalConfig,
|
|
});
|
|
this._schema = _schema;
|
|
this._argumentsTransformer = new OperationVariablesToObject(this.scalars, this.convertName);
|
|
}
|
|
getExportPrefix() {
|
|
return 'export ';
|
|
}
|
|
getFieldWrapperValue() {
|
|
if (this.config.fieldWrapperValue) {
|
|
return `${this.getExportPrefix()}type FieldWrapper<T> = ${this.config.fieldWrapperValue};`;
|
|
}
|
|
return '';
|
|
}
|
|
getEntireFieldWrapperValue() {
|
|
if (this.config.entireFieldWrapperValue) {
|
|
return `${this.getExportPrefix()}type EntireFieldWrapper<T> = ${this.config.entireFieldWrapperValue};`;
|
|
}
|
|
return '';
|
|
}
|
|
getScalarsImports() {
|
|
return Object.keys(this.config.scalars)
|
|
.map(enumName => {
|
|
const mappedValue = this.config.scalars[enumName];
|
|
if (mappedValue.isExternal) {
|
|
return this._buildTypeImport(mappedValue.import, mappedValue.source, mappedValue.default);
|
|
}
|
|
return null;
|
|
})
|
|
.filter(a => a);
|
|
}
|
|
get scalarsDefinition() {
|
|
const allScalars = Object.keys(this.config.scalars).map(scalarName => {
|
|
const scalarValue = this.config.scalars[scalarName].type;
|
|
const scalarType = this._schema.getType(scalarName);
|
|
const comment = scalarType && scalarType.astNode && scalarType.description ? transformComment(scalarType.description, 1) : '';
|
|
const { scalar } = this._parsedConfig.declarationKind;
|
|
return comment + indent(`${scalarName}: ${scalarValue}${this.getPunctuation(scalar)}`);
|
|
});
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(this._parsedConfig.declarationKind.scalar)
|
|
.withName('Scalars')
|
|
.withComment('All built-in and custom scalars, mapped to their actual values')
|
|
.withBlock(allScalars.join('\n')).string;
|
|
}
|
|
setDeclarationBlockConfig(config) {
|
|
this._declarationBlockConfig = config;
|
|
}
|
|
setArgumentsTransformer(argumentsTransfomer) {
|
|
this._argumentsTransformer = argumentsTransfomer;
|
|
}
|
|
NonNullType(node) {
|
|
const asString = node.type;
|
|
return asString;
|
|
}
|
|
getInputObjectDeclarationBlock(node) {
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(this._parsedConfig.declarationKind.input)
|
|
.withName(this.convertName(node))
|
|
.withComment(node.description)
|
|
.withBlock(node.fields.join('\n'));
|
|
}
|
|
InputObjectTypeDefinition(node) {
|
|
return this.getInputObjectDeclarationBlock(node).string;
|
|
}
|
|
InputValueDefinition(node) {
|
|
const comment = transformComment(node.description, 1);
|
|
const { input } = this._parsedConfig.declarationKind;
|
|
return comment + indent(`${node.name}: ${node.type}${this.getPunctuation(input)}`);
|
|
}
|
|
Name(node) {
|
|
return node.value;
|
|
}
|
|
FieldDefinition(node) {
|
|
const typeString = node.type;
|
|
const { type } = this._parsedConfig.declarationKind;
|
|
const comment = this.getFieldComment(node);
|
|
return comment + indent(`${node.name}: ${typeString}${this.getPunctuation(type)}`);
|
|
}
|
|
UnionTypeDefinition(node, key, parent) {
|
|
if (this.config.onlyOperationTypes)
|
|
return '';
|
|
const originalNode = parent[key];
|
|
const possibleTypes = originalNode.types
|
|
.map(t => (this.scalars[t.name.value] ? this._getScalar(t.name.value) : this.convertName(t)))
|
|
.join(' | ');
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind('type')
|
|
.withName(this.convertName(node))
|
|
.withComment(node.description)
|
|
.withContent(possibleTypes).string;
|
|
}
|
|
mergeInterfaces(interfaces, hasOtherFields) {
|
|
return interfaces.join(' & ') + (interfaces.length && hasOtherFields ? ' & ' : '');
|
|
}
|
|
appendInterfacesAndFieldsToBlock(block, interfaces, fields) {
|
|
block.withContent(this.mergeInterfaces(interfaces, fields.length > 0));
|
|
block.withBlock(this.mergeAllFields(fields, interfaces.length > 0));
|
|
}
|
|
getObjectTypeDeclarationBlock(node, originalNode) {
|
|
const optionalTypename = this.config.nonOptionalTypename ? '__typename' : '__typename?';
|
|
const { type, interface: interfacesType } = this._parsedConfig.declarationKind;
|
|
const allFields = [
|
|
...(this.config.addTypename
|
|
? [
|
|
indent(`${this.config.immutableTypes ? 'readonly ' : ''}${optionalTypename}: '${node.name}'${this.getPunctuation(type)}`),
|
|
]
|
|
: []),
|
|
...node.fields,
|
|
];
|
|
const interfacesNames = originalNode.interfaces ? originalNode.interfaces.map(i => this.convertName(i)) : [];
|
|
const declarationBlock = new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(type)
|
|
.withName(this.convertName(node))
|
|
.withComment(node.description);
|
|
if (type === 'interface' || type === 'class') {
|
|
if (interfacesNames.length > 0) {
|
|
const keyword = interfacesType === 'interface' && type === 'class' ? 'implements' : 'extends';
|
|
declarationBlock.withContent(`${keyword} ` + interfacesNames.join(', ') + (allFields.length > 0 ? ' ' : ' {}'));
|
|
}
|
|
declarationBlock.withBlock(this.mergeAllFields(allFields, false));
|
|
}
|
|
else {
|
|
this.appendInterfacesAndFieldsToBlock(declarationBlock, interfacesNames, allFields);
|
|
}
|
|
return declarationBlock;
|
|
}
|
|
getFieldComment(node) {
|
|
let commentText = node.description;
|
|
const deprecationDirective = node.directives.find((v) => v.name === 'deprecated');
|
|
if (deprecationDirective) {
|
|
const deprecationReason = this.getDeprecationReason(deprecationDirective);
|
|
commentText = `${commentText ? `${commentText}\n` : ''}@deprecated ${deprecationReason}`;
|
|
}
|
|
const comment = transformComment(commentText, 1);
|
|
return comment;
|
|
}
|
|
mergeAllFields(allFields, _hasInterfaces) {
|
|
return allFields.join('\n');
|
|
}
|
|
ObjectTypeDefinition(node, key, parent) {
|
|
if (this.config.onlyOperationTypes)
|
|
return '';
|
|
const originalNode = parent[key];
|
|
return [this.getObjectTypeDeclarationBlock(node, originalNode).string, this.buildArgumentsBlock(originalNode)]
|
|
.filter(f => f)
|
|
.join('\n\n');
|
|
}
|
|
getInterfaceTypeDeclarationBlock(node, _originalNode) {
|
|
const declarationBlock = new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(this._parsedConfig.declarationKind.interface)
|
|
.withName(this.convertName(node))
|
|
.withComment(node.description);
|
|
return declarationBlock.withBlock(node.fields.join('\n'));
|
|
}
|
|
InterfaceTypeDefinition(node, key, parent) {
|
|
if (this.config.onlyOperationTypes)
|
|
return '';
|
|
const originalNode = parent[key];
|
|
return [this.getInterfaceTypeDeclarationBlock(node, originalNode).string, this.buildArgumentsBlock(originalNode)]
|
|
.filter(f => f)
|
|
.join('\n\n');
|
|
}
|
|
ScalarTypeDefinition(_node) {
|
|
// We empty this because we handle scalars in a different way, see constructor.
|
|
return '';
|
|
}
|
|
_buildTypeImport(identifier, source, asDefault = false) {
|
|
const { useTypeImports } = this.config;
|
|
if (asDefault) {
|
|
if (useTypeImports) {
|
|
return `import type { default as ${identifier} } from '${source}';`;
|
|
}
|
|
return `import ${identifier} from '${source}';`;
|
|
}
|
|
return `import${useTypeImports ? ' type' : ''} { ${identifier} } from '${source}';`;
|
|
}
|
|
handleEnumValueMapper(typeIdentifier, importIdentifier, sourceIdentifier, sourceFile) {
|
|
const importStatement = this._buildTypeImport(importIdentifier || sourceIdentifier, sourceFile);
|
|
if (importIdentifier !== sourceIdentifier || sourceIdentifier !== typeIdentifier) {
|
|
return [importStatement, `import ${typeIdentifier} = ${sourceIdentifier};`];
|
|
}
|
|
return [importStatement];
|
|
}
|
|
getEnumsImports() {
|
|
return flatMap(Object.keys(this.config.enumValues), enumName => {
|
|
const mappedValue = this.config.enumValues[enumName];
|
|
if (mappedValue.sourceFile) {
|
|
if (mappedValue.isDefault) {
|
|
return [this._buildTypeImport(mappedValue.typeIdentifier, mappedValue.sourceFile, true)];
|
|
}
|
|
return this.handleEnumValueMapper(mappedValue.typeIdentifier, mappedValue.importIdentifier, mappedValue.sourceIdentifier, mappedValue.sourceFile);
|
|
}
|
|
return [];
|
|
}).filter(a => a);
|
|
}
|
|
EnumTypeDefinition(node) {
|
|
const enumName = node.name;
|
|
// In case of mapped external enum string
|
|
if (this.config.enumValues[enumName] && this.config.enumValues[enumName].sourceFile) {
|
|
return null;
|
|
}
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind('enum')
|
|
.withName(this.convertName(node, { useTypesPrefix: this.config.enumPrefix }))
|
|
.withComment(node.description)
|
|
.withBlock(this.buildEnumValuesBlock(enumName, node.values)).string;
|
|
}
|
|
// We are using it in order to transform "description" field
|
|
StringValue(node) {
|
|
return node.value;
|
|
}
|
|
makeValidEnumIdentifier(identifier) {
|
|
if (/^[0-9]/.exec(identifier)) {
|
|
return wrapWithSingleQuotes(identifier, true);
|
|
}
|
|
return identifier;
|
|
}
|
|
buildEnumValuesBlock(typeName, values) {
|
|
const schemaEnumType = this._schema
|
|
? this._schema.getType(typeName)
|
|
: undefined;
|
|
return values
|
|
.map(enumOption => {
|
|
const optionName = this.makeValidEnumIdentifier(this.convertName(enumOption, { useTypesPrefix: false, transformUnderscore: true }));
|
|
const comment = transformComment(enumOption.description, 1);
|
|
const schemaEnumValue = schemaEnumType && !this.config.ignoreEnumValuesFromSchema
|
|
? schemaEnumType.getValue(enumOption.name).value
|
|
: undefined;
|
|
let enumValue = typeof schemaEnumValue !== 'undefined' ? schemaEnumValue : enumOption.name;
|
|
if (this.config.enumValues[typeName] &&
|
|
this.config.enumValues[typeName].mappedValues &&
|
|
typeof this.config.enumValues[typeName].mappedValues[enumValue] !== 'undefined') {
|
|
enumValue = this.config.enumValues[typeName].mappedValues[enumValue];
|
|
}
|
|
return (comment +
|
|
indent(`${optionName}${this._declarationBlockConfig.enumNameValueSeparator} ${wrapWithSingleQuotes(enumValue, typeof schemaEnumValue !== 'undefined')}`));
|
|
})
|
|
.join(',\n');
|
|
}
|
|
DirectiveDefinition(_node) {
|
|
return '';
|
|
}
|
|
getArgumentsObjectDeclarationBlock(node, name, field) {
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(this._parsedConfig.declarationKind.arguments)
|
|
.withName(this.convertName(name))
|
|
.withComment(node.description)
|
|
.withBlock(this._argumentsTransformer.transform(field.arguments));
|
|
}
|
|
getArgumentsObjectTypeDefinition(node, name, field) {
|
|
return this.getArgumentsObjectDeclarationBlock(node, name, field).string;
|
|
}
|
|
buildArgumentsBlock(node) {
|
|
const fieldsWithArguments = node.fields.filter(field => field.arguments && field.arguments.length > 0) || [];
|
|
return fieldsWithArguments
|
|
.map(field => {
|
|
const name = node.name.value +
|
|
(this.config.addUnderscoreToArgsType ? '_' : '') +
|
|
this.convertName(field, {
|
|
useTypesPrefix: false,
|
|
useTypesSuffix: false,
|
|
}) +
|
|
'Args';
|
|
return this.getArgumentsObjectTypeDefinition(node, name, field);
|
|
})
|
|
.join('\n\n');
|
|
}
|
|
_getScalar(name) {
|
|
return `Scalars['${name}']`;
|
|
}
|
|
_getTypeForNode(node) {
|
|
const typeAsString = node.name;
|
|
if (this.scalars[typeAsString]) {
|
|
return this._getScalar(typeAsString);
|
|
}
|
|
else if (this.config.enumValues[typeAsString]) {
|
|
return this.config.enumValues[typeAsString].typeIdentifier;
|
|
}
|
|
const schemaType = this._schema.getType(node.name);
|
|
if (schemaType && graphql.isEnumType(schemaType)) {
|
|
return this.convertName(node, { useTypesPrefix: this.config.enumPrefix });
|
|
}
|
|
return this.convertName(node);
|
|
}
|
|
NamedType(node, key, parent, path, ancestors) {
|
|
const currentVisitContext = this.getVisitorKindContextFromAncestors(ancestors);
|
|
const isVisitingInputType = currentVisitContext.includes(graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION);
|
|
const typeToUse = this._getTypeForNode(node);
|
|
if (!isVisitingInputType && this.config.fieldWrapperValue && this.config.wrapFieldDefinitions) {
|
|
return `FieldWrapper<${typeToUse}>`;
|
|
}
|
|
return typeToUse;
|
|
}
|
|
ListType(node) {
|
|
const asString = node.type;
|
|
return this.wrapWithListType(asString);
|
|
}
|
|
SchemaDefinition() {
|
|
return null;
|
|
}
|
|
getDeprecationReason(directive) {
|
|
if (directive.name === 'deprecated') {
|
|
const hasArguments = directive.arguments.length > 0;
|
|
let reason = 'Field no longer supported';
|
|
if (hasArguments) {
|
|
reason = directive.arguments[0].value;
|
|
}
|
|
return reason;
|
|
}
|
|
}
|
|
wrapWithListType(str) {
|
|
return `Array<${str}>`;
|
|
}
|
|
}
|
|
|
|
function getRootType(operation, schema) {
|
|
switch (operation) {
|
|
case 'query':
|
|
return schema.getQueryType();
|
|
case 'mutation':
|
|
return schema.getMutationType();
|
|
case 'subscription':
|
|
return schema.getSubscriptionType();
|
|
}
|
|
}
|
|
class BaseDocumentsVisitor extends BaseVisitor {
|
|
constructor(rawConfig, additionalConfig, _schema, defaultScalars = DEFAULT_SCALARS) {
|
|
super(rawConfig, {
|
|
exportFragmentSpreadSubTypes: getConfigValue(rawConfig.exportFragmentSpreadSubTypes, false),
|
|
enumPrefix: getConfigValue(rawConfig.enumPrefix, true),
|
|
preResolveTypes: getConfigValue(rawConfig.preResolveTypes, false),
|
|
dedupeOperationSuffix: getConfigValue(rawConfig.dedupeOperationSuffix, false),
|
|
omitOperationSuffix: getConfigValue(rawConfig.omitOperationSuffix, false),
|
|
skipTypeNameForRoot: getConfigValue(rawConfig.skipTypeNameForRoot, false),
|
|
namespacedImportName: getConfigValue(rawConfig.namespacedImportName, null),
|
|
experimentalFragmentVariables: getConfigValue(rawConfig.experimentalFragmentVariables, false),
|
|
addTypename: !rawConfig.skipTypename,
|
|
globalNamespace: !!rawConfig.globalNamespace,
|
|
operationResultSuffix: getConfigValue(rawConfig.operationResultSuffix, ''),
|
|
scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
|
|
...(additionalConfig || {}),
|
|
});
|
|
this._schema = _schema;
|
|
this._unnamedCounter = 1;
|
|
this._globalDeclarations = new Set();
|
|
autoBind(this);
|
|
this._variablesTransfomer = new OperationVariablesToObject(this.scalars, this.convertName, this.config.namespacedImportName);
|
|
}
|
|
getGlobalDeclarations(noExport = false) {
|
|
return Array.from(this._globalDeclarations).map(t => (noExport ? t : `export ${t}`));
|
|
}
|
|
setSelectionSetHandler(handler) {
|
|
this._selectionSetToObject = handler;
|
|
}
|
|
setDeclarationBlockConfig(config) {
|
|
this._declarationBlockConfig = config;
|
|
}
|
|
setVariablesTransformer(variablesTransfomer) {
|
|
this._variablesTransfomer = variablesTransfomer;
|
|
}
|
|
get schema() {
|
|
return this._schema;
|
|
}
|
|
get addTypename() {
|
|
return this._parsedConfig.addTypename;
|
|
}
|
|
handleAnonymousOperation(node) {
|
|
const name = node.name && node.name.value;
|
|
if (name) {
|
|
return this.convertName(name, {
|
|
useTypesPrefix: false,
|
|
useTypesSuffix: false,
|
|
});
|
|
}
|
|
return this.convertName(this._unnamedCounter++ + '', {
|
|
prefix: 'Unnamed_',
|
|
suffix: '_',
|
|
useTypesPrefix: false,
|
|
useTypesSuffix: false,
|
|
});
|
|
}
|
|
FragmentDefinition(node) {
|
|
const fragmentRootType = this._schema.getType(node.typeCondition.name.value);
|
|
const selectionSet = this._selectionSetToObject.createNext(fragmentRootType, node.selectionSet);
|
|
const fragmentSuffix = this.getFragmentSuffix(node);
|
|
return [
|
|
selectionSet.transformFragmentSelectionSetToTypes(node.name.value, fragmentSuffix, this._declarationBlockConfig),
|
|
this.config.experimentalFragmentVariables
|
|
? new DeclarationBlock({
|
|
...this._declarationBlockConfig,
|
|
blockTransformer: t => this.applyVariablesWrapper(t),
|
|
})
|
|
.export()
|
|
.asKind('type')
|
|
.withName(this.convertName(node.name.value, {
|
|
suffix: fragmentSuffix + 'Variables',
|
|
}))
|
|
.withBlock(this._variablesTransfomer.transform(node.variableDefinitions)).string
|
|
: undefined,
|
|
]
|
|
.filter(r => r)
|
|
.join('\n\n');
|
|
}
|
|
applyVariablesWrapper(variablesBlock) {
|
|
return variablesBlock;
|
|
}
|
|
OperationDefinition(node) {
|
|
const name = this.handleAnonymousOperation(node);
|
|
const operationRootType = getRootType(node.operation, this._schema);
|
|
if (!operationRootType) {
|
|
throw new Error(`Unable to find root schema type for operation type "${node.operation}"!`);
|
|
}
|
|
const selectionSet = this._selectionSetToObject.createNext(operationRootType, node.selectionSet);
|
|
const visitedOperationVariables = this._variablesTransfomer.transform(node.variableDefinitions);
|
|
const operationType = changeCaseAll.pascalCase(node.operation);
|
|
const operationTypeSuffix = this.getOperationSuffix(name, operationType);
|
|
const operationResult = new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind('type')
|
|
.withName(this.convertName(name, {
|
|
suffix: operationTypeSuffix + this._parsedConfig.operationResultSuffix,
|
|
}))
|
|
.withContent(selectionSet.transformSelectionSet()).string;
|
|
const operationVariables = new DeclarationBlock({
|
|
...this._declarationBlockConfig,
|
|
blockTransformer: t => this.applyVariablesWrapper(t),
|
|
})
|
|
.export()
|
|
.asKind('type')
|
|
.withName(this.convertName(name, {
|
|
suffix: operationTypeSuffix + 'Variables',
|
|
}))
|
|
.withBlock(visitedOperationVariables).string;
|
|
return [operationVariables, operationResult].filter(r => r).join('\n\n');
|
|
}
|
|
}
|
|
|
|
class BaseResolversVisitor extends BaseVisitor {
|
|
constructor(rawConfig, additionalConfig, _schema, defaultScalars = DEFAULT_SCALARS) {
|
|
super(rawConfig, {
|
|
immutableTypes: getConfigValue(rawConfig.immutableTypes, false),
|
|
optionalResolveType: getConfigValue(rawConfig.optionalResolveType, false),
|
|
enumPrefix: getConfigValue(rawConfig.enumPrefix, true),
|
|
federation: getConfigValue(rawConfig.federation, false),
|
|
resolverTypeWrapperSignature: getConfigValue(rawConfig.resolverTypeWrapperSignature, 'Promise<T> | T'),
|
|
enumValues: parseEnumValues({
|
|
schema: _schema,
|
|
mapOrStr: rawConfig.enumValues,
|
|
}),
|
|
addUnderscoreToArgsType: getConfigValue(rawConfig.addUnderscoreToArgsType, false),
|
|
onlyResolveTypeForInterfaces: getConfigValue(rawConfig.onlyResolveTypeForInterfaces, false),
|
|
contextType: parseMapper(rawConfig.contextType || 'any', 'ContextType'),
|
|
fieldContextTypes: getConfigValue(rawConfig.fieldContextTypes, []),
|
|
resolverTypeSuffix: getConfigValue(rawConfig.resolverTypeSuffix, 'Resolvers'),
|
|
allResolversTypeName: getConfigValue(rawConfig.allResolversTypeName, 'Resolvers'),
|
|
rootValueType: parseMapper(rawConfig.rootValueType || '{}', 'RootValueType'),
|
|
namespacedImportName: getConfigValue(rawConfig.namespacedImportName, ''),
|
|
avoidOptionals: getConfigValue(rawConfig.avoidOptionals, false),
|
|
defaultMapper: rawConfig.defaultMapper
|
|
? parseMapper(rawConfig.defaultMapper || 'any', 'DefaultMapperType')
|
|
: null,
|
|
mappers: transformMappers(rawConfig.mappers || {}, rawConfig.mapperTypeSuffix),
|
|
scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
|
|
internalResolversPrefix: getConfigValue(rawConfig.internalResolversPrefix, '__'),
|
|
...(additionalConfig || {}),
|
|
});
|
|
this._schema = _schema;
|
|
this._declarationBlockConfig = {};
|
|
this._collectedResolvers = {};
|
|
this._collectedDirectiveResolvers = {};
|
|
this._usedMappers = {};
|
|
this._resolversTypes = {};
|
|
this._resolversParentTypes = {};
|
|
this._rootTypeNames = [];
|
|
this._globalDeclarations = new Set();
|
|
this._hasScalars = false;
|
|
this._hasFederation = false;
|
|
autoBind(this);
|
|
this._federation = new pluginHelpers.ApolloFederation({ enabled: this.config.federation, schema: this.schema });
|
|
this._rootTypeNames = getRootTypeNames(_schema);
|
|
this._variablesTransfomer = new OperationVariablesToObject(this.scalars, this.convertName, this.config.namespacedImportName);
|
|
this._resolversTypes = this.createResolversFields(type => this.applyResolverTypeWrapper(type), type => this.clearResolverTypeWrapper(type), name => this.getTypeToUse(name));
|
|
this._resolversParentTypes = this.createResolversFields(type => type, type => type, name => this.getParentTypeToUse(name), namedType => !graphql.isEnumType(namedType));
|
|
this._fieldContextTypeMap = this.createFieldContextTypeMap();
|
|
}
|
|
getResolverTypeWrapperSignature() {
|
|
return `export type ResolverTypeWrapper<T> = ${this.config.resolverTypeWrapperSignature};`;
|
|
}
|
|
shouldMapType(type, checkedBefore = {}, duringCheck = []) {
|
|
if (checkedBefore[type.name] !== undefined) {
|
|
return checkedBefore[type.name];
|
|
}
|
|
if (type.name.startsWith('__') || this.config.scalars[type.name]) {
|
|
return false;
|
|
}
|
|
if (this.config.mappers[type.name]) {
|
|
return true;
|
|
}
|
|
if (graphql.isObjectType(type) || graphql.isInterfaceType(type)) {
|
|
const fields = type.getFields();
|
|
return Object.keys(fields)
|
|
.filter(fieldName => {
|
|
const field = fields[fieldName];
|
|
const fieldType = pluginHelpers.getBaseType(field.type);
|
|
return !duringCheck.includes(fieldType.name);
|
|
})
|
|
.some(fieldName => {
|
|
const field = fields[fieldName];
|
|
const fieldType = pluginHelpers.getBaseType(field.type);
|
|
if (checkedBefore[fieldType.name] !== undefined) {
|
|
return checkedBefore[fieldType.name];
|
|
}
|
|
if (this.config.mappers[type.name]) {
|
|
return true;
|
|
}
|
|
duringCheck.push(type.name);
|
|
const innerResult = this.shouldMapType(fieldType, checkedBefore, duringCheck);
|
|
return innerResult;
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
convertName(node, options, applyNamespacedImport = false) {
|
|
const sourceType = super.convertName(node, options);
|
|
return `${applyNamespacedImport && this.config.namespacedImportName ? this.config.namespacedImportName + '.' : ''}${sourceType}`;
|
|
}
|
|
// Kamil: this one is heeeeavvyyyy
|
|
createResolversFields(applyWrapper, clearWrapper, getTypeToUse, shouldInclude) {
|
|
const allSchemaTypes = this._schema.getTypeMap();
|
|
const nestedMapping = {};
|
|
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
|
|
typeNames.forEach(typeName => {
|
|
const schemaType = allSchemaTypes[typeName];
|
|
nestedMapping[typeName] = this.shouldMapType(schemaType, nestedMapping);
|
|
});
|
|
return typeNames.reduce((prev, typeName) => {
|
|
const schemaType = allSchemaTypes[typeName];
|
|
if (typeName.startsWith('__') || (shouldInclude && !shouldInclude(schemaType))) {
|
|
return prev;
|
|
}
|
|
let shouldApplyOmit = false;
|
|
const isRootType = this._rootTypeNames.includes(typeName);
|
|
const isMapped = this.config.mappers[typeName];
|
|
const isScalar = this.config.scalars[typeName];
|
|
const hasDefaultMapper = !!(this.config.defaultMapper && this.config.defaultMapper.type);
|
|
if (isRootType) {
|
|
prev[typeName] = applyWrapper(this.config.rootValueType.type);
|
|
return prev;
|
|
}
|
|
else if (isMapped && this.config.mappers[typeName].type) {
|
|
this.markMapperAsUsed(typeName);
|
|
prev[typeName] = applyWrapper(this.config.mappers[typeName].type);
|
|
}
|
|
else if (graphql.isInterfaceType(schemaType)) {
|
|
const allTypesMap = this._schema.getTypeMap();
|
|
const implementingTypes = [];
|
|
for (const graphqlType of Object.values(allTypesMap)) {
|
|
if (graphqlType instanceof graphql.GraphQLObjectType) {
|
|
const allInterfaces = graphqlType.getInterfaces();
|
|
if (allInterfaces.some(int => int.name === schemaType.name)) {
|
|
implementingTypes.push(graphqlType.name);
|
|
}
|
|
}
|
|
}
|
|
const possibleTypes = implementingTypes.map(name => getTypeToUse(name)).join(' | ') || 'never';
|
|
prev[typeName] = possibleTypes;
|
|
return prev;
|
|
}
|
|
else if (graphql.isEnumType(schemaType) && this.config.enumValues[typeName]) {
|
|
prev[typeName] =
|
|
this.config.enumValues[typeName].sourceIdentifier ||
|
|
this.convertName(this.config.enumValues[typeName].typeIdentifier);
|
|
}
|
|
else if (hasDefaultMapper && !hasPlaceholder(this.config.defaultMapper.type)) {
|
|
prev[typeName] = applyWrapper(this.config.defaultMapper.type);
|
|
}
|
|
else if (isScalar) {
|
|
prev[typeName] = applyWrapper(this._getScalar(typeName));
|
|
}
|
|
else if (graphql.isUnionType(schemaType)) {
|
|
prev[typeName] = schemaType
|
|
.getTypes()
|
|
.map(type => getTypeToUse(type.name))
|
|
.join(' | ');
|
|
}
|
|
else {
|
|
shouldApplyOmit = true;
|
|
prev[typeName] = this.convertName(typeName, { useTypesPrefix: this.config.enumPrefix }, true);
|
|
}
|
|
if (shouldApplyOmit && prev[typeName] !== 'any' && graphql.isObjectType(schemaType)) {
|
|
const fields = schemaType.getFields();
|
|
const relevantFields = this._federation
|
|
.filterFieldNames(Object.keys(fields))
|
|
.map(fieldName => {
|
|
const field = fields[fieldName];
|
|
const baseType = pluginHelpers.getBaseType(field.type);
|
|
const isUnion = graphql.isUnionType(baseType);
|
|
if (!this.config.mappers[baseType.name] && !isUnion && !nestedMapping[baseType.name]) {
|
|
return null;
|
|
}
|
|
const addOptionalSign = !this.config.avoidOptionals && !graphql.isNonNullType(field.type);
|
|
return {
|
|
addOptionalSign,
|
|
fieldName,
|
|
replaceWithType: wrapTypeWithModifiers(getTypeToUse(baseType.name), field.type, {
|
|
wrapOptional: this.applyMaybe,
|
|
wrapArray: this.wrapWithArray,
|
|
}),
|
|
};
|
|
})
|
|
.filter(a => a);
|
|
if (relevantFields.length > 0) {
|
|
// Puts ResolverTypeWrapper on top of an entire type
|
|
prev[typeName] = applyWrapper(this.replaceFieldsInType(prev[typeName], relevantFields));
|
|
}
|
|
else {
|
|
// We still want to use ResolverTypeWrapper, even if we don't touch any fields
|
|
prev[typeName] = applyWrapper(prev[typeName]);
|
|
}
|
|
}
|
|
if (isMapped && hasPlaceholder(prev[typeName])) {
|
|
prev[typeName] = replacePlaceholder(prev[typeName], typeName);
|
|
}
|
|
if (!isMapped && hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) {
|
|
// Make sure the inner type has no ResolverTypeWrapper
|
|
const name = clearWrapper(isScalar ? this._getScalar(typeName) : prev[typeName]);
|
|
const replaced = replacePlaceholder(this.config.defaultMapper.type, name);
|
|
// Don't wrap Union with ResolverTypeWrapper, each inner type already has it
|
|
if (graphql.isUnionType(schemaType)) {
|
|
prev[typeName] = replaced;
|
|
}
|
|
else {
|
|
prev[typeName] = applyWrapper(replacePlaceholder(this.config.defaultMapper.type, name));
|
|
}
|
|
}
|
|
return prev;
|
|
}, {});
|
|
}
|
|
replaceFieldsInType(typeName, relevantFields) {
|
|
this._globalDeclarations.add(OMIT_TYPE);
|
|
return `Omit<${typeName}, ${relevantFields.map(f => `'${f.fieldName}'`).join(' | ')}> & { ${relevantFields
|
|
.map(f => `${f.fieldName}${f.addOptionalSign ? '?' : ''}: ${f.replaceWithType}`)
|
|
.join(', ')} }`;
|
|
}
|
|
applyMaybe(str) {
|
|
const namespacedImportPrefix = this.config.namespacedImportName ? this.config.namespacedImportName + '.' : '';
|
|
return `${namespacedImportPrefix}Maybe<${str}>`;
|
|
}
|
|
applyResolverTypeWrapper(str) {
|
|
return `ResolverTypeWrapper<${this.clearResolverTypeWrapper(str)}>`;
|
|
}
|
|
clearMaybe(str) {
|
|
const namespacedImportPrefix = this.config.namespacedImportName ? this.config.namespacedImportName + '.' : '';
|
|
if (str.startsWith(`${namespacedImportPrefix}Maybe<`)) {
|
|
const maybeRe = new RegExp(`${namespacedImportPrefix.replace('.', '\\.')}Maybe<(.*?)>$`);
|
|
return str.replace(maybeRe, '$1');
|
|
}
|
|
return str;
|
|
}
|
|
clearResolverTypeWrapper(str) {
|
|
if (str.startsWith('ResolverTypeWrapper<')) {
|
|
return str.replace(/ResolverTypeWrapper<(.*?)>$/, '$1');
|
|
}
|
|
return str;
|
|
}
|
|
wrapWithArray(t) {
|
|
if (this.config.immutableTypes) {
|
|
return `ReadonlyArray<${t}>`;
|
|
}
|
|
return `Array<${t}>`;
|
|
}
|
|
createFieldContextTypeMap() {
|
|
return this.config.fieldContextTypes.reduce((prev, fieldContextType) => {
|
|
const items = fieldContextType.split('#');
|
|
if (items.length === 3) {
|
|
const [path, source, contextTypeName] = items;
|
|
return { ...prev, [path]: parseMapper(`${source}#${contextTypeName}`) };
|
|
}
|
|
const [path, contextType] = items;
|
|
return { ...prev, [path]: parseMapper(contextType) };
|
|
}, {});
|
|
}
|
|
buildResolversTypes() {
|
|
const declarationKind = 'type';
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(declarationKind)
|
|
.withName(this.convertName('ResolversTypes'))
|
|
.withComment('Mapping between all available schema types and the resolvers types')
|
|
.withBlock(Object.keys(this._resolversTypes)
|
|
.map(typeName => indent(`${typeName}: ${this._resolversTypes[typeName]}${this.getPunctuation(declarationKind)}`))
|
|
.join('\n')).string;
|
|
}
|
|
buildResolversParentTypes() {
|
|
const declarationKind = 'type';
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(declarationKind)
|
|
.withName(this.convertName('ResolversParentTypes'))
|
|
.withComment('Mapping between all available schema types and the resolvers parents')
|
|
.withBlock(Object.keys(this._resolversParentTypes)
|
|
.map(typeName => indent(`${typeName}: ${this._resolversParentTypes[typeName]}${this.getPunctuation(declarationKind)}`))
|
|
.join('\n')).string;
|
|
}
|
|
get schema() {
|
|
return this._schema;
|
|
}
|
|
get defaultMapperType() {
|
|
return this.config.defaultMapper.type;
|
|
}
|
|
get unusedMappers() {
|
|
return Object.keys(this.config.mappers).filter(name => !this._usedMappers[name]);
|
|
}
|
|
get globalDeclarations() {
|
|
return Array.from(this._globalDeclarations);
|
|
}
|
|
isMapperImported(groupedMappers, identifier, source) {
|
|
const exists = !groupedMappers[source] ? false : !!groupedMappers[source].find(m => m.identifier === identifier);
|
|
const existsFromEnums = !!Object.keys(this.config.enumValues)
|
|
.map(key => this.config.enumValues[key])
|
|
.find(o => o.sourceFile === source && o.typeIdentifier === identifier);
|
|
return exists || existsFromEnums;
|
|
}
|
|
get mappersImports() {
|
|
const groupedMappers = {};
|
|
const addMapper = (source, identifier, asDefault) => {
|
|
if (!this.isMapperImported(groupedMappers, identifier, source)) {
|
|
if (!groupedMappers[source]) {
|
|
groupedMappers[source] = [];
|
|
}
|
|
groupedMappers[source].push({ identifier, asDefault });
|
|
}
|
|
};
|
|
Object.keys(this.config.mappers)
|
|
.map(gqlTypeName => ({ gqlType: gqlTypeName, mapper: this.config.mappers[gqlTypeName] }))
|
|
.filter(({ mapper }) => mapper.isExternal)
|
|
.forEach(({ mapper }) => {
|
|
const externalMapper = mapper;
|
|
const identifier = stripMapperTypeInterpolation(externalMapper.import);
|
|
addMapper(externalMapper.source, identifier, externalMapper.default);
|
|
});
|
|
if (this.config.contextType.isExternal) {
|
|
addMapper(this.config.contextType.source, this.config.contextType.import, this.config.contextType.default);
|
|
}
|
|
if (this.config.rootValueType.isExternal) {
|
|
addMapper(this.config.rootValueType.source, this.config.rootValueType.import, this.config.rootValueType.default);
|
|
}
|
|
if (this.config.defaultMapper && this.config.defaultMapper.isExternal) {
|
|
const identifier = stripMapperTypeInterpolation(this.config.defaultMapper.import);
|
|
addMapper(this.config.defaultMapper.source, identifier, this.config.defaultMapper.default);
|
|
}
|
|
Object.values(this._fieldContextTypeMap).forEach(parsedMapper => {
|
|
if (parsedMapper.isExternal) {
|
|
addMapper(parsedMapper.source, parsedMapper.import, parsedMapper.default);
|
|
}
|
|
});
|
|
return Object.keys(groupedMappers)
|
|
.map(source => buildMapperImport(source, groupedMappers[source], this.config.useTypeImports))
|
|
.filter(Boolean);
|
|
}
|
|
setDeclarationBlockConfig(config) {
|
|
this._declarationBlockConfig = config;
|
|
}
|
|
setVariablesTransformer(variablesTransfomer) {
|
|
this._variablesTransfomer = variablesTransfomer;
|
|
}
|
|
hasScalars() {
|
|
return this._hasScalars;
|
|
}
|
|
hasFederation() {
|
|
return this._hasFederation;
|
|
}
|
|
getRootResolver() {
|
|
const name = this.convertName(this.config.allResolversTypeName);
|
|
const declarationKind = 'type';
|
|
const contextType = `<ContextType = ${this.config.contextType.type}>`;
|
|
// This is here because we don't want to break IResolvers, so there is a mapping by default,
|
|
// and if the developer is overriding typesPrefix, it won't get generated at all.
|
|
const deprecatedIResolvers = !this.config.typesPrefix
|
|
? `
|
|
/**
|
|
* @deprecated
|
|
* Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config.
|
|
*/
|
|
export type IResolvers${contextType} = ${name}<ContextType>;`
|
|
: '';
|
|
return [
|
|
new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(declarationKind)
|
|
.withName(name, contextType)
|
|
.withBlock(Object.keys(this._collectedResolvers)
|
|
.map(schemaTypeName => {
|
|
const resolverType = this._collectedResolvers[schemaTypeName];
|
|
return indent(this.formatRootResolver(schemaTypeName, resolverType, declarationKind));
|
|
})
|
|
.join('\n')).string,
|
|
deprecatedIResolvers,
|
|
].join('\n');
|
|
}
|
|
formatRootResolver(schemaTypeName, resolverType, declarationKind) {
|
|
return `${schemaTypeName}${this.config.avoidOptionals ? '' : '?'}: ${resolverType}${this.getPunctuation(declarationKind)}`;
|
|
}
|
|
getAllDirectiveResolvers() {
|
|
if (Object.keys(this._collectedDirectiveResolvers).length) {
|
|
const declarationKind = 'type';
|
|
const name = this.convertName('DirectiveResolvers');
|
|
const contextType = `<ContextType = ${this.config.contextType.type}>`;
|
|
// This is here because we don't want to break IResolvers, so there is a mapping by default,
|
|
// and if the developer is overriding typesPrefix, it won't get generated at all.
|
|
const deprecatedIResolvers = !this.config.typesPrefix
|
|
? `
|
|
/**
|
|
* @deprecated
|
|
* Use "DirectiveResolvers" root object instead. If you wish to get "IDirectiveResolvers", add "typesPrefix: I" to your config.
|
|
*/
|
|
export type IDirectiveResolvers${contextType} = ${name}<ContextType>;`
|
|
: '';
|
|
return [
|
|
new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(declarationKind)
|
|
.withName(name, contextType)
|
|
.withBlock(Object.keys(this._collectedDirectiveResolvers)
|
|
.map(schemaTypeName => {
|
|
const resolverType = this._collectedDirectiveResolvers[schemaTypeName];
|
|
return indent(this.formatRootResolver(schemaTypeName, resolverType, declarationKind));
|
|
})
|
|
.join('\n')).string,
|
|
deprecatedIResolvers,
|
|
].join('\n');
|
|
}
|
|
return '';
|
|
}
|
|
Name(node) {
|
|
return node.value;
|
|
}
|
|
ListType(node) {
|
|
const asString = node.type;
|
|
return this.wrapWithArray(asString);
|
|
}
|
|
_getScalar(name) {
|
|
return `${this.config.namespacedImportName ? this.config.namespacedImportName + '.' : ''}Scalars['${name}']`;
|
|
}
|
|
NamedType(node) {
|
|
const nameStr = node.name;
|
|
if (this.config.scalars[nameStr]) {
|
|
return this._getScalar(nameStr);
|
|
}
|
|
return this.convertName(node, null, true);
|
|
}
|
|
NonNullType(node) {
|
|
const asString = node.type;
|
|
return asString;
|
|
}
|
|
markMapperAsUsed(name) {
|
|
this._usedMappers[name] = true;
|
|
}
|
|
getTypeToUse(name) {
|
|
const resolversType = this.convertName('ResolversTypes');
|
|
return `${resolversType}['${name}']`;
|
|
}
|
|
getParentTypeToUse(name) {
|
|
const resolversType = this.convertName('ResolversParentTypes');
|
|
return `${resolversType}['${name}']`;
|
|
}
|
|
getParentTypeForSignature(_node) {
|
|
return 'ParentType';
|
|
}
|
|
transformParentGenericType(parentType) {
|
|
return `ParentType extends ${parentType} = ${parentType}`;
|
|
}
|
|
FieldDefinition(node, key, parent) {
|
|
const hasArguments = node.arguments && node.arguments.length > 0;
|
|
const declarationKind = 'type';
|
|
return (parentName) => {
|
|
const original = parent[key];
|
|
const baseType = getBaseTypeNode(original.type);
|
|
const realType = baseType.name.value;
|
|
const parentType = this.schema.getType(parentName);
|
|
if (this._federation.skipField({ fieldNode: original, parentType: parentType })) {
|
|
return null;
|
|
}
|
|
const typeToUse = this.getTypeToUse(realType);
|
|
const mappedType = this._variablesTransfomer.wrapAstTypeWithModifiers(typeToUse, original.type);
|
|
const subscriptionType = this._schema.getSubscriptionType();
|
|
const isSubscriptionType = subscriptionType && subscriptionType.name === parentName;
|
|
let argsType = hasArguments
|
|
? this.convertName(parentName +
|
|
(this.config.addUnderscoreToArgsType ? '_' : '') +
|
|
this.convertName(node.name, {
|
|
useTypesPrefix: false,
|
|
useTypesSuffix: false,
|
|
}) +
|
|
'Args', {
|
|
useTypesPrefix: true,
|
|
}, true)
|
|
: null;
|
|
if (argsType !== null) {
|
|
const argsToForceRequire = original.arguments.filter(arg => !!arg.defaultValue || arg.type.kind === 'NonNullType');
|
|
if (argsToForceRequire.length > 0) {
|
|
argsType = this.applyRequireFields(argsType, argsToForceRequire);
|
|
}
|
|
else if (original.arguments.length > 0) {
|
|
argsType = this.applyOptionalFields(argsType, original.arguments);
|
|
}
|
|
}
|
|
const parentTypeSignature = this._federation.transformParentType({
|
|
fieldNode: original,
|
|
parentType,
|
|
parentTypeSignature: this.getParentTypeForSignature(node),
|
|
});
|
|
const mappedTypeKey = isSubscriptionType ? `${mappedType}, "${node.name}"` : mappedType;
|
|
const signature = {
|
|
name: node.name,
|
|
modifier: this.config.avoidOptionals ? '' : '?',
|
|
type: isSubscriptionType ? 'SubscriptionResolver' : 'Resolver',
|
|
genericTypes: [
|
|
mappedTypeKey,
|
|
parentTypeSignature,
|
|
this._fieldContextTypeMap[`${parentName}.${node.name}`]
|
|
? this._fieldContextTypeMap[`${parentName}.${node.name}`].type
|
|
: 'ContextType',
|
|
argsType,
|
|
].filter(f => f),
|
|
};
|
|
if (this._federation.isResolveReferenceField(node)) {
|
|
this._hasFederation = true;
|
|
signature.type = 'ReferenceResolver';
|
|
if (signature.genericTypes.length >= 3) {
|
|
signature.genericTypes = signature.genericTypes.slice(0, 3);
|
|
}
|
|
}
|
|
return indent(`${signature.name}${signature.modifier}: ${signature.type}<${signature.genericTypes.join(', ')}>${this.getPunctuation(declarationKind)}`);
|
|
};
|
|
}
|
|
applyRequireFields(argsType, fields) {
|
|
this._globalDeclarations.add(REQUIRE_FIELDS_TYPE);
|
|
return `RequireFields<${argsType}, ${fields.map(f => `'${f.name.value}'`).join(' | ')}>`;
|
|
}
|
|
applyOptionalFields(argsType, _fields) {
|
|
this._globalDeclarations.add(REQUIRE_FIELDS_TYPE);
|
|
return `RequireFields<${argsType}, never>`;
|
|
}
|
|
ObjectTypeDefinition(node) {
|
|
var _a, _b, _c;
|
|
const declarationKind = 'type';
|
|
const name = this.convertName(node, {
|
|
suffix: this.config.resolverTypeSuffix,
|
|
});
|
|
const typeName = node.name;
|
|
const parentType = this.getParentTypeToUse(typeName);
|
|
const isRootType = [
|
|
(_a = this.schema.getQueryType()) === null || _a === void 0 ? void 0 : _a.name,
|
|
(_b = this.schema.getMutationType()) === null || _b === void 0 ? void 0 : _b.name,
|
|
(_c = this.schema.getSubscriptionType()) === null || _c === void 0 ? void 0 : _c.name,
|
|
].includes(typeName);
|
|
const fieldsContent = node.fields.map((f) => f(node.name));
|
|
if (!isRootType) {
|
|
fieldsContent.push(indent(`${this.config.internalResolversPrefix}isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>${this.getPunctuation(declarationKind)}`));
|
|
}
|
|
const block = new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(declarationKind)
|
|
.withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
|
|
.withBlock(fieldsContent.join('\n'));
|
|
this._collectedResolvers[node.name] = name + '<ContextType>';
|
|
return block.string;
|
|
}
|
|
UnionTypeDefinition(node, key, parent) {
|
|
const declarationKind = 'type';
|
|
const name = this.convertName(node, {
|
|
suffix: this.config.resolverTypeSuffix,
|
|
});
|
|
const originalNode = parent[key];
|
|
const possibleTypes = originalNode.types
|
|
.map(node => node.name.value)
|
|
.map(f => `'${f}'`)
|
|
.join(' | ');
|
|
this._collectedResolvers[node.name] = name + '<ContextType>';
|
|
const parentType = this.getParentTypeToUse(node.name);
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(declarationKind)
|
|
.withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
|
|
.withBlock(indent(`${this.config.internalResolversPrefix}resolveType${this.config.optionalResolveType ? '?' : ''}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`)).string;
|
|
}
|
|
ScalarTypeDefinition(node) {
|
|
const nameAsString = node.name;
|
|
const baseName = this.getTypeToUse(nameAsString);
|
|
if (this._federation.skipScalar(nameAsString)) {
|
|
return null;
|
|
}
|
|
this._hasScalars = true;
|
|
this._collectedResolvers[node.name] = 'GraphQLScalarType';
|
|
return new DeclarationBlock({
|
|
...this._declarationBlockConfig,
|
|
blockTransformer(block) {
|
|
return block;
|
|
},
|
|
})
|
|
.export()
|
|
.asKind('interface')
|
|
.withName(this.convertName(node, {
|
|
suffix: 'ScalarConfig',
|
|
}), ` extends GraphQLScalarTypeConfig<${baseName}, any>`)
|
|
.withBlock(indent(`name: '${node.name}'${this.getPunctuation('interface')}`)).string;
|
|
}
|
|
DirectiveDefinition(node, key, parent) {
|
|
if (this._federation.skipDirective(node.name)) {
|
|
return null;
|
|
}
|
|
const directiveName = this.convertName(node, {
|
|
suffix: 'DirectiveResolver',
|
|
});
|
|
const sourceNode = parent[key];
|
|
const hasArguments = sourceNode.arguments && sourceNode.arguments.length > 0;
|
|
this._collectedDirectiveResolvers[node.name] = directiveName + '<any, any, ContextType>';
|
|
const directiveArgsTypeName = this.convertName(node, {
|
|
suffix: 'DirectiveArgs',
|
|
});
|
|
return [
|
|
new DeclarationBlock({
|
|
...this._declarationBlockConfig,
|
|
blockTransformer(block) {
|
|
return block;
|
|
},
|
|
})
|
|
.export()
|
|
.asKind('type')
|
|
.withName(directiveArgsTypeName)
|
|
.withContent(`{ ${hasArguments ? this._variablesTransfomer.transform(sourceNode.arguments) : ''} }`).string,
|
|
new DeclarationBlock({
|
|
...this._declarationBlockConfig,
|
|
blockTransformer(block) {
|
|
return block;
|
|
},
|
|
})
|
|
.export()
|
|
.asKind('type')
|
|
.withName(directiveName, `<Result, Parent, ContextType = ${this.config.contextType.type}, Args = ${directiveArgsTypeName}>`)
|
|
.withContent(`DirectiveResolverFn<Result, Parent, ContextType, Args>`).string,
|
|
].join('\n');
|
|
}
|
|
buildEnumResolverContentBlock(_node, _mappedEnumType) {
|
|
throw new Error(`buildEnumResolverContentBlock is not implemented!`);
|
|
}
|
|
buildEnumResolversExplicitMappedValues(_node, _valuesMapping) {
|
|
throw new Error(`buildEnumResolversExplicitMappedValues is not implemented!`);
|
|
}
|
|
EnumTypeDefinition(node) {
|
|
const rawTypeName = node.name;
|
|
// If we have enumValues set, and it's point to an external enum - we need to allow internal values resolvers
|
|
// In case we have enumValues set but as explicit values, no need to to do mapping since it's already
|
|
// have type validation (the original enum has been modified by base types plugin).
|
|
// If we have mapper for that type - we can skip
|
|
if (!this.config.mappers[rawTypeName] && !this.config.enumValues[rawTypeName]) {
|
|
return null;
|
|
}
|
|
const name = this.convertName(node, { suffix: this.config.resolverTypeSuffix });
|
|
this._collectedResolvers[rawTypeName] = name;
|
|
const hasExplicitValues = this.config.enumValues[rawTypeName] && this.config.enumValues[rawTypeName].mappedValues;
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind('type')
|
|
.withName(name)
|
|
.withContent(hasExplicitValues
|
|
? this.buildEnumResolversExplicitMappedValues(node, this.config.enumValues[rawTypeName].mappedValues)
|
|
: this.buildEnumResolverContentBlock(node, this.getTypeToUse(rawTypeName))).string;
|
|
}
|
|
InterfaceTypeDefinition(node) {
|
|
const name = this.convertName(node, {
|
|
suffix: this.config.resolverTypeSuffix,
|
|
});
|
|
const declarationKind = 'type';
|
|
const allTypesMap = this._schema.getTypeMap();
|
|
const implementingTypes = [];
|
|
this._collectedResolvers[node.name] = name + '<ContextType>';
|
|
for (const graphqlType of Object.values(allTypesMap)) {
|
|
if (graphqlType instanceof graphql.GraphQLObjectType) {
|
|
const allInterfaces = graphqlType.getInterfaces();
|
|
if (allInterfaces.find(int => int.name === node.name)) {
|
|
implementingTypes.push(graphqlType.name);
|
|
}
|
|
}
|
|
}
|
|
const parentType = this.getParentTypeToUse(node.name);
|
|
const possibleTypes = implementingTypes.map(name => `'${name}'`).join(' | ') || 'null';
|
|
const fields = this.config.onlyResolveTypeForInterfaces ? [] : node.fields || [];
|
|
return new DeclarationBlock(this._declarationBlockConfig)
|
|
.export()
|
|
.asKind(declarationKind)
|
|
.withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
|
|
.withBlock([
|
|
indent(`${this.config.internalResolversPrefix}resolveType${this.config.optionalResolveType ? '?' : ''}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`),
|
|
...fields.map((f) => f(node.name)),
|
|
].join('\n')).string;
|
|
}
|
|
SchemaDefinition() {
|
|
return null;
|
|
}
|
|
}
|
|
function replacePlaceholder(pattern, typename) {
|
|
return pattern.replace(/\{T\}/g, typename);
|
|
}
|
|
function hasPlaceholder(pattern) {
|
|
return pattern.includes('{T}');
|
|
}
|
|
|
|
gqlTag.enableExperimentalFragmentVariables();
|
|
(function (DocumentMode) {
|
|
DocumentMode["graphQLTag"] = "graphQLTag";
|
|
DocumentMode["documentNode"] = "documentNode";
|
|
DocumentMode["documentNodeImportFragments"] = "documentNodeImportFragments";
|
|
DocumentMode["external"] = "external";
|
|
DocumentMode["string"] = "string";
|
|
})(exports.DocumentMode || (exports.DocumentMode = {}));
|
|
const EXTENSIONS_TO_REMOVE = ['.ts', '.tsx', '.js', '.jsx'];
|
|
class ClientSideBaseVisitor extends BaseVisitor {
|
|
constructor(_schema, _fragments, rawConfig, additionalConfig, documents) {
|
|
super(rawConfig, {
|
|
scalars: buildScalarsFromConfig(_schema, rawConfig),
|
|
dedupeOperationSuffix: getConfigValue(rawConfig.dedupeOperationSuffix, false),
|
|
optimizeDocumentNode: getConfigValue(rawConfig.optimizeDocumentNode, true),
|
|
omitOperationSuffix: getConfigValue(rawConfig.omitOperationSuffix, false),
|
|
gqlImport: rawConfig.gqlImport || null,
|
|
documentNodeImport: rawConfig.documentNodeImport || null,
|
|
noExport: !!rawConfig.noExport,
|
|
importOperationTypesFrom: getConfigValue(rawConfig.importOperationTypesFrom, null),
|
|
operationResultSuffix: getConfigValue(rawConfig.operationResultSuffix, ''),
|
|
documentVariablePrefix: getConfigValue(rawConfig.documentVariablePrefix, ''),
|
|
documentVariableSuffix: getConfigValue(rawConfig.documentVariableSuffix, 'Document'),
|
|
fragmentVariablePrefix: getConfigValue(rawConfig.fragmentVariablePrefix, ''),
|
|
fragmentVariableSuffix: getConfigValue(rawConfig.fragmentVariableSuffix, 'FragmentDoc'),
|
|
documentMode: ((rawConfig) => {
|
|
if (typeof rawConfig.noGraphQLTag === 'boolean') {
|
|
return rawConfig.noGraphQLTag ? exports.DocumentMode.documentNode : exports.DocumentMode.graphQLTag;
|
|
}
|
|
return getConfigValue(rawConfig.documentMode, exports.DocumentMode.graphQLTag);
|
|
})(rawConfig),
|
|
importDocumentNodeExternallyFrom: getConfigValue(rawConfig.importDocumentNodeExternallyFrom, ''),
|
|
pureMagicComment: getConfigValue(rawConfig.pureMagicComment, false),
|
|
experimentalFragmentVariables: getConfigValue(rawConfig.experimentalFragmentVariables, false),
|
|
...additionalConfig,
|
|
});
|
|
this._schema = _schema;
|
|
this._fragments = _fragments;
|
|
this._collectedOperations = [];
|
|
this._documents = [];
|
|
this._additionalImports = [];
|
|
this._imports = new Set();
|
|
this._documents = documents;
|
|
autoBind(this);
|
|
}
|
|
_extractFragments(document, withNested = false) {
|
|
if (!document) {
|
|
return [];
|
|
}
|
|
const names = new Set();
|
|
graphql.visit(document, {
|
|
enter: {
|
|
FragmentSpread: (node) => {
|
|
names.add(node.name.value);
|
|
if (withNested) {
|
|
const foundFragment = this._fragments.find(f => f.name === node.name.value);
|
|
if (foundFragment) {
|
|
const childItems = this._extractFragments(foundFragment.node, true);
|
|
if (childItems && childItems.length > 0) {
|
|
for (const item of childItems) {
|
|
names.add(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
});
|
|
return Array.from(names);
|
|
}
|
|
_transformFragments(document) {
|
|
const includeNestedFragments = this.config.documentMode === exports.DocumentMode.documentNode ||
|
|
(this.config.dedupeFragments && document.kind === 'OperationDefinition');
|
|
return this._extractFragments(document, includeNestedFragments).map(document => this.getFragmentVariableName(document));
|
|
}
|
|
_includeFragments(fragments) {
|
|
if (fragments && fragments.length > 0) {
|
|
if (this.config.documentMode === exports.DocumentMode.documentNode) {
|
|
return this._fragments
|
|
.filter(f => fragments.includes(this.getFragmentVariableName(f.name)))
|
|
.map(fragment => graphql.print(fragment.node))
|
|
.join('\n');
|
|
}
|
|
else if (this.config.documentMode === exports.DocumentMode.documentNodeImportFragments) {
|
|
return '';
|
|
}
|
|
else {
|
|
return `${fragments.map(name => '${' + name + '}').join('\n')}`;
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
_prepareDocument(documentStr) {
|
|
return documentStr;
|
|
}
|
|
_gql(node) {
|
|
const fragments = this._transformFragments(node);
|
|
const doc = this._prepareDocument(`
|
|
${graphql.print(node).split('\\').join('\\\\') /* Re-escape escaped values in GraphQL syntax */}
|
|
${this._includeFragments(fragments)}`);
|
|
if (this.config.documentMode === exports.DocumentMode.documentNode) {
|
|
let gqlObj = gqlTag([doc]);
|
|
if (this.config.optimizeDocumentNode) {
|
|
gqlObj = optimize.optimizeDocumentNode(gqlObj);
|
|
}
|
|
return JSON.stringify(gqlObj);
|
|
}
|
|
else if (this.config.documentMode === exports.DocumentMode.documentNodeImportFragments) {
|
|
let gqlObj = gqlTag([doc]);
|
|
if (this.config.optimizeDocumentNode) {
|
|
gqlObj = optimize.optimizeDocumentNode(gqlObj);
|
|
}
|
|
if (fragments.length > 0 && (!this.config.dedupeFragments || node.kind === 'OperationDefinition')) {
|
|
const definitions = [
|
|
...gqlObj.definitions.map(t => JSON.stringify(t)),
|
|
...fragments.map(name => `...${name}.definitions`),
|
|
].join();
|
|
return `{"kind":"${graphql.Kind.DOCUMENT}","definitions":[${definitions}]}`;
|
|
}
|
|
return JSON.stringify(gqlObj);
|
|
}
|
|
else if (this.config.documentMode === exports.DocumentMode.string) {
|
|
return '`' + doc + '`';
|
|
}
|
|
const gqlImport = this._parseImport(this.config.gqlImport || 'graphql-tag');
|
|
return (gqlImport.propName || 'gql') + '`' + doc + '`';
|
|
}
|
|
_generateFragment(fragmentDocument) {
|
|
const name = this.getFragmentVariableName(fragmentDocument);
|
|
const fragmentTypeSuffix = this.getFragmentSuffix(fragmentDocument);
|
|
return `export const ${name} =${this.config.pureMagicComment ? ' /*#__PURE__*/' : ''} ${this._gql(fragmentDocument)}${this.getDocumentNodeSignature(this.convertName(fragmentDocument.name.value, {
|
|
useTypesPrefix: true,
|
|
suffix: fragmentTypeSuffix,
|
|
}), this.config.experimentalFragmentVariables
|
|
? this.convertName(fragmentDocument.name.value, {
|
|
suffix: fragmentTypeSuffix + 'Variables',
|
|
})
|
|
: 'unknown', fragmentDocument)};`;
|
|
}
|
|
get fragmentsGraph() {
|
|
const graph = new dependencyGraph.DepGraph({ circular: true });
|
|
for (const fragment of this._fragments) {
|
|
if (graph.hasNode(fragment.name)) {
|
|
const cachedAsString = graphql.print(graph.getNodeData(fragment.name).node);
|
|
const asString = graphql.print(fragment.node);
|
|
if (cachedAsString !== asString) {
|
|
throw new Error(`Duplicated fragment called '${fragment.name}'!`);
|
|
}
|
|
}
|
|
graph.addNode(fragment.name, fragment);
|
|
}
|
|
this._fragments.forEach(fragment => {
|
|
const depends = this._extractFragments(fragment.node);
|
|
if (depends && depends.length > 0) {
|
|
depends.forEach(name => {
|
|
graph.addDependency(fragment.name, name);
|
|
});
|
|
}
|
|
});
|
|
return graph;
|
|
}
|
|
get fragments() {
|
|
if (this._fragments.length === 0 || this.config.documentMode === exports.DocumentMode.external) {
|
|
return '';
|
|
}
|
|
const graph = this.fragmentsGraph;
|
|
const orderedDeps = graph.overallOrder();
|
|
const localFragments = orderedDeps
|
|
.filter(name => !graph.getNodeData(name).isExternal)
|
|
.map(name => this._generateFragment(graph.getNodeData(name).node));
|
|
return localFragments.join('\n');
|
|
}
|
|
_parseImport(importStr) {
|
|
// This is a special case when we want to ignore importing, and just use `gql` provided from somewhere else
|
|
// Plugins that uses that will need to ensure to add import/declaration for the gql identifier
|
|
if (importStr === 'gql') {
|
|
return {
|
|
moduleName: null,
|
|
propName: 'gql',
|
|
};
|
|
}
|
|
// This is a special use case, when we don't want this plugin to manage the import statement
|
|
// of the gql tag. In this case, we provide something like `Namespace.gql` and it will be used instead.
|
|
if (importStr.includes('.gql')) {
|
|
return {
|
|
moduleName: null,
|
|
propName: importStr,
|
|
};
|
|
}
|
|
const [moduleName, propName] = importStr.split('#');
|
|
return {
|
|
moduleName,
|
|
propName,
|
|
};
|
|
}
|
|
_generateImport({ moduleName, propName }, varName, isTypeImport) {
|
|
const typeImport = isTypeImport && this.config.useTypeImports ? 'import type' : 'import';
|
|
const propAlias = propName === varName ? '' : ` as ${varName}`;
|
|
if (moduleName) {
|
|
return `${typeImport} ${propName ? `{ ${propName}${propAlias} }` : varName} from '${moduleName}';`;
|
|
}
|
|
return null;
|
|
}
|
|
clearExtension(path$1) {
|
|
const extension = path.extname(path$1);
|
|
if (EXTENSIONS_TO_REMOVE.includes(extension)) {
|
|
return path$1.replace(/\.[^/.]+$/, '');
|
|
}
|
|
return path$1;
|
|
}
|
|
getImports(options = {}) {
|
|
(this._additionalImports || []).forEach(i => this._imports.add(i));
|
|
switch (this.config.documentMode) {
|
|
case exports.DocumentMode.documentNode:
|
|
case exports.DocumentMode.documentNodeImportFragments: {
|
|
const documentNodeImport = this._parseImport(this.config.documentNodeImport || 'graphql#DocumentNode');
|
|
const tagImport = this._generateImport(documentNodeImport, 'DocumentNode', true);
|
|
if (tagImport) {
|
|
this._imports.add(tagImport);
|
|
}
|
|
break;
|
|
}
|
|
case exports.DocumentMode.graphQLTag: {
|
|
const gqlImport = this._parseImport(this.config.gqlImport || 'graphql-tag');
|
|
const tagImport = this._generateImport(gqlImport, 'gql', false);
|
|
if (tagImport) {
|
|
this._imports.add(tagImport);
|
|
}
|
|
break;
|
|
}
|
|
case exports.DocumentMode.external: {
|
|
if (this._collectedOperations.length > 0) {
|
|
if (this.config.importDocumentNodeExternallyFrom === 'near-operation-file' && this._documents.length === 1) {
|
|
this._imports.add(`import * as Operations from './${this.clearExtension(path.basename(this._documents[0].location))}';`);
|
|
}
|
|
else {
|
|
if (!this.config.importDocumentNodeExternallyFrom) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn('importDocumentNodeExternallyFrom must be provided if documentMode=external');
|
|
}
|
|
this._imports.add(`import * as Operations from '${this.clearExtension(this.config.importDocumentNodeExternallyFrom)}';`);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!options.excludeFragments && !this.config.globalNamespace) {
|
|
const { documentMode, fragmentImports } = this.config;
|
|
if (documentMode === exports.DocumentMode.graphQLTag ||
|
|
documentMode === exports.DocumentMode.string ||
|
|
documentMode === exports.DocumentMode.documentNodeImportFragments) {
|
|
fragmentImports.forEach(fragmentImport => {
|
|
this._imports.add(generateFragmentImportStatement(fragmentImport, 'document'));
|
|
});
|
|
}
|
|
}
|
|
return Array.from(this._imports);
|
|
}
|
|
buildOperation(_node, _documentVariableName, _operationType, _operationResultType, _operationVariablesTypes, _hasRequiredVariables) {
|
|
return null;
|
|
}
|
|
getDocumentNodeSignature(_resultType, _variablesTypes, _node) {
|
|
if (this.config.documentMode === exports.DocumentMode.documentNode ||
|
|
this.config.documentMode === exports.DocumentMode.documentNodeImportFragments) {
|
|
return ` as unknown as DocumentNode`;
|
|
}
|
|
return '';
|
|
}
|
|
/**
|
|
* Checks if the specific operation has variables that are non-null (required), and also doesn't have default.
|
|
* This is useful for deciding of `variables` should be optional or not.
|
|
* @param node
|
|
*/
|
|
checkVariablesRequirements(node) {
|
|
const variables = node.variableDefinitions || [];
|
|
if (variables.length === 0) {
|
|
return false;
|
|
}
|
|
return variables.some(variableDef => variableDef.type.kind === graphql.Kind.NON_NULL_TYPE && !variableDef.defaultValue);
|
|
}
|
|
OperationDefinition(node) {
|
|
this._collectedOperations.push(node);
|
|
const documentVariableName = this.convertName(node, {
|
|
suffix: this.config.documentVariableSuffix,
|
|
prefix: this.config.documentVariablePrefix,
|
|
useTypesPrefix: false,
|
|
});
|
|
const operationType = changeCaseAll.pascalCase(node.operation);
|
|
const operationTypeSuffix = this.getOperationSuffix(node, operationType);
|
|
const operationResultType = this.convertName(node, {
|
|
suffix: operationTypeSuffix + this._parsedConfig.operationResultSuffix,
|
|
});
|
|
const operationVariablesTypes = this.convertName(node, {
|
|
suffix: operationTypeSuffix + 'Variables',
|
|
});
|
|
let documentString = '';
|
|
if (this.config.documentMode !== exports.DocumentMode.external) {
|
|
// only generate exports for named queries
|
|
if (documentVariableName !== '') {
|
|
documentString = `${this.config.noExport ? '' : 'export'} const ${documentVariableName} =${this.config.pureMagicComment ? ' /*#__PURE__*/' : ''} ${this._gql(node)}${this.getDocumentNodeSignature(operationResultType, operationVariablesTypes, node)};`;
|
|
}
|
|
}
|
|
const hasRequiredVariables = this.checkVariablesRequirements(node);
|
|
const additional = this.buildOperation(node, documentVariableName, operationType, operationResultType, operationVariablesTypes, hasRequiredVariables);
|
|
return [documentString, additional].filter(a => a).join('\n');
|
|
}
|
|
}
|
|
|
|
function isMetadataFieldName(name) {
|
|
return ['__schema', '__type'].includes(name);
|
|
}
|
|
const metadataFieldMap = {
|
|
__schema: graphql.SchemaMetaFieldDef,
|
|
__type: graphql.TypeMetaFieldDef,
|
|
};
|
|
class SelectionSetToObject {
|
|
constructor(_processor, _scalars, _schema, _convertName, _getFragmentSuffix, _loadedFragments, _config, _parentSchemaType, _selectionSet) {
|
|
this._processor = _processor;
|
|
this._scalars = _scalars;
|
|
this._schema = _schema;
|
|
this._convertName = _convertName;
|
|
this._getFragmentSuffix = _getFragmentSuffix;
|
|
this._loadedFragments = _loadedFragments;
|
|
this._config = _config;
|
|
this._parentSchemaType = _parentSchemaType;
|
|
this._selectionSet = _selectionSet;
|
|
this._primitiveFields = [];
|
|
this._primitiveAliasedFields = [];
|
|
this._linksFields = [];
|
|
this._queriedForTypename = false;
|
|
autoBind(this);
|
|
}
|
|
createNext(parentSchemaType, selectionSet) {
|
|
return new SelectionSetToObject(this._processor, this._scalars, this._schema, this._convertName.bind(this), this._getFragmentSuffix.bind(this), this._loadedFragments, this._config, parentSchemaType, selectionSet);
|
|
}
|
|
/**
|
|
* traverse the inline fragment nodes recursively for collecting the selectionSets on each type
|
|
*/
|
|
_collectInlineFragments(parentType, nodes, types) {
|
|
if (graphql.isListType(parentType) || graphql.isNonNullType(parentType)) {
|
|
return this._collectInlineFragments(parentType.ofType, nodes, types);
|
|
}
|
|
else if (graphql.isObjectType(parentType)) {
|
|
for (const node of nodes) {
|
|
const typeOnSchema = node.typeCondition ? this._schema.getType(node.typeCondition.name.value) : parentType;
|
|
const { fields, inlines, spreads } = separateSelectionSet(node.selectionSet.selections);
|
|
const spreadsUsage = this.buildFragmentSpreadsUsage(spreads);
|
|
if (graphql.isObjectType(typeOnSchema)) {
|
|
this._appendToTypeMap(types, typeOnSchema.name, fields);
|
|
this._appendToTypeMap(types, typeOnSchema.name, spreadsUsage[typeOnSchema.name]);
|
|
this._collectInlineFragments(typeOnSchema, inlines, types);
|
|
}
|
|
else if (graphql.isInterfaceType(typeOnSchema) && parentType.getInterfaces().includes(typeOnSchema)) {
|
|
this._appendToTypeMap(types, parentType.name, fields);
|
|
this._appendToTypeMap(types, parentType.name, spreadsUsage[parentType.name]);
|
|
this._collectInlineFragments(typeOnSchema, inlines, types);
|
|
}
|
|
}
|
|
}
|
|
else if (graphql.isInterfaceType(parentType)) {
|
|
const possibleTypes = getPossibleTypes(this._schema, parentType);
|
|
for (const node of nodes) {
|
|
const schemaType = node.typeCondition ? this._schema.getType(node.typeCondition.name.value) : parentType;
|
|
const { fields, inlines, spreads } = separateSelectionSet(node.selectionSet.selections);
|
|
const spreadsUsage = this.buildFragmentSpreadsUsage(spreads);
|
|
if (graphql.isObjectType(schemaType) && possibleTypes.find(possibleType => possibleType.name === schemaType.name)) {
|
|
this._appendToTypeMap(types, schemaType.name, fields);
|
|
this._appendToTypeMap(types, schemaType.name, spreadsUsage[schemaType.name]);
|
|
this._collectInlineFragments(schemaType, inlines, types);
|
|
}
|
|
else if (graphql.isInterfaceType(schemaType) && schemaType.name === parentType.name) {
|
|
for (const possibleType of possibleTypes) {
|
|
this._appendToTypeMap(types, possibleType.name, fields);
|
|
this._appendToTypeMap(types, possibleType.name, spreadsUsage[possibleType.name]);
|
|
this._collectInlineFragments(schemaType, inlines, types);
|
|
}
|
|
}
|
|
else {
|
|
// it must be an interface type that is spread on an interface field
|
|
for (const possibleType of possibleTypes) {
|
|
if (!node.typeCondition) {
|
|
throw new Error('Invalid state. Expected type condition for interface spread on a interface field.');
|
|
}
|
|
const fragmentSpreadType = this._schema.getType(node.typeCondition.name.value);
|
|
// the field should only be added to the valid selections
|
|
// in case the possible type actually implements the given interface
|
|
if (graphql.isTypeSubTypeOf(this._schema, possibleType, fragmentSpreadType)) {
|
|
this._appendToTypeMap(types, possibleType.name, fields);
|
|
this._appendToTypeMap(types, possibleType.name, spreadsUsage[possibleType.name]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (graphql.isUnionType(parentType)) {
|
|
const possibleTypes = parentType.getTypes();
|
|
for (const node of nodes) {
|
|
const schemaType = node.typeCondition ? this._schema.getType(node.typeCondition.name.value) : parentType;
|
|
const { fields, inlines, spreads } = separateSelectionSet(node.selectionSet.selections);
|
|
const spreadsUsage = this.buildFragmentSpreadsUsage(spreads);
|
|
if (graphql.isObjectType(schemaType) && possibleTypes.find(possibleType => possibleType.name === schemaType.name)) {
|
|
this._appendToTypeMap(types, schemaType.name, fields);
|
|
this._appendToTypeMap(types, schemaType.name, spreadsUsage[schemaType.name]);
|
|
this._collectInlineFragments(schemaType, inlines, types);
|
|
}
|
|
else if (graphql.isInterfaceType(schemaType)) {
|
|
const possibleInterfaceTypes = getPossibleTypes(this._schema, schemaType);
|
|
for (const possibleType of possibleTypes) {
|
|
if (possibleInterfaceTypes.find(possibleInterfaceType => possibleInterfaceType.name === possibleType.name)) {
|
|
this._appendToTypeMap(types, possibleType.name, fields);
|
|
this._appendToTypeMap(types, possibleType.name, spreadsUsage[possibleType.name]);
|
|
this._collectInlineFragments(schemaType, inlines, types);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (const possibleType of possibleTypes) {
|
|
this._appendToTypeMap(types, possibleType.name, fields);
|
|
this._appendToTypeMap(types, possibleType.name, spreadsUsage[possibleType.name]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_createInlineFragmentForFieldNodes(parentType, fieldNodes) {
|
|
return {
|
|
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
typeCondition: {
|
|
kind: graphql.Kind.NAMED_TYPE,
|
|
name: {
|
|
kind: graphql.Kind.NAME,
|
|
value: parentType.name,
|
|
},
|
|
},
|
|
directives: [],
|
|
selectionSet: {
|
|
kind: graphql.Kind.SELECTION_SET,
|
|
selections: fieldNodes,
|
|
},
|
|
};
|
|
}
|
|
buildFragmentSpreadsUsage(spreads) {
|
|
const selectionNodesByTypeName = {};
|
|
for (const spread of spreads) {
|
|
const fragmentSpreadObject = this._loadedFragments.find(lf => lf.name === spread.name.value);
|
|
if (fragmentSpreadObject) {
|
|
const schemaType = this._schema.getType(fragmentSpreadObject.onType);
|
|
const possibleTypesForFragment = getPossibleTypes(this._schema, schemaType);
|
|
for (const possibleType of possibleTypesForFragment) {
|
|
const fragmentSuffix = this._getFragmentSuffix(spread.name.value);
|
|
const usage = this.buildFragmentTypeName(spread.name.value, fragmentSuffix, possibleTypesForFragment.length === 1 ? null : possibleType.name);
|
|
if (!selectionNodesByTypeName[possibleType.name]) {
|
|
selectionNodesByTypeName[possibleType.name] = [];
|
|
}
|
|
selectionNodesByTypeName[possibleType.name].push(usage);
|
|
}
|
|
}
|
|
}
|
|
return selectionNodesByTypeName;
|
|
}
|
|
flattenSelectionSet(selections) {
|
|
const selectionNodesByTypeName = new Map();
|
|
const inlineFragmentSelections = [];
|
|
const fieldNodes = [];
|
|
const fragmentSpreads = [];
|
|
for (const selection of selections) {
|
|
switch (selection.kind) {
|
|
case graphql.Kind.FIELD:
|
|
fieldNodes.push(selection);
|
|
break;
|
|
case graphql.Kind.INLINE_FRAGMENT:
|
|
inlineFragmentSelections.push(selection);
|
|
break;
|
|
case graphql.Kind.FRAGMENT_SPREAD:
|
|
fragmentSpreads.push(selection);
|
|
break;
|
|
}
|
|
}
|
|
if (fieldNodes.length) {
|
|
inlineFragmentSelections.push(this._createInlineFragmentForFieldNodes(this._parentSchemaType, fieldNodes));
|
|
}
|
|
this._collectInlineFragments(this._parentSchemaType, inlineFragmentSelections, selectionNodesByTypeName);
|
|
const fragmentsUsage = this.buildFragmentSpreadsUsage(fragmentSpreads);
|
|
Object.keys(fragmentsUsage).forEach(typeName => {
|
|
this._appendToTypeMap(selectionNodesByTypeName, typeName, fragmentsUsage[typeName]);
|
|
});
|
|
return selectionNodesByTypeName;
|
|
}
|
|
_appendToTypeMap(types, typeName, nodes) {
|
|
if (!types.has(typeName)) {
|
|
types.set(typeName, []);
|
|
}
|
|
if (nodes && nodes.length > 0) {
|
|
types.get(typeName).push(...nodes);
|
|
}
|
|
}
|
|
_buildGroupedSelections() {
|
|
if (!this._selectionSet || !this._selectionSet.selections || this._selectionSet.selections.length === 0) {
|
|
return {};
|
|
}
|
|
const selectionNodesByTypeName = this.flattenSelectionSet(this._selectionSet.selections);
|
|
const grouped = getPossibleTypes(this._schema, this._parentSchemaType).reduce((prev, type) => {
|
|
const typeName = type.name;
|
|
const schemaType = this._schema.getType(typeName);
|
|
if (!graphql.isObjectType(schemaType)) {
|
|
throw new TypeError(`Invalid state! Schema type ${typeName} is not a valid GraphQL object!`);
|
|
}
|
|
const selectionNodes = selectionNodesByTypeName.get(typeName) || [];
|
|
if (!prev[typeName]) {
|
|
prev[typeName] = [];
|
|
}
|
|
const transformedSet = this.buildSelectionSetString(schemaType, selectionNodes);
|
|
if (transformedSet) {
|
|
prev[typeName].push(transformedSet);
|
|
}
|
|
return prev;
|
|
}, {});
|
|
return grouped;
|
|
}
|
|
buildSelectionSetString(parentSchemaType, selectionNodes) {
|
|
const primitiveFields = new Map();
|
|
const primitiveAliasFields = new Map();
|
|
const linkFieldSelectionSets = new Map();
|
|
let requireTypename = false;
|
|
const fragmentsSpreadUsages = [];
|
|
for (const selectionNode of selectionNodes) {
|
|
if (typeof selectionNode === 'string') {
|
|
fragmentsSpreadUsages.push(selectionNode);
|
|
}
|
|
else if (selectionNode.kind === 'Field') {
|
|
if (!selectionNode.selectionSet) {
|
|
if (selectionNode.alias) {
|
|
primitiveAliasFields.set(selectionNode.alias.value, selectionNode);
|
|
}
|
|
else if (selectionNode.name.value === '__typename') {
|
|
requireTypename = true;
|
|
}
|
|
else {
|
|
primitiveFields.set(selectionNode.name.value, selectionNode);
|
|
}
|
|
}
|
|
else {
|
|
let selectedField = null;
|
|
const fields = parentSchemaType.getFields();
|
|
selectedField = fields[selectionNode.name.value];
|
|
if (isMetadataFieldName(selectionNode.name.value)) {
|
|
selectedField = metadataFieldMap[selectionNode.name.value];
|
|
}
|
|
if (!selectedField) {
|
|
continue;
|
|
}
|
|
const fieldName = getFieldNodeNameValue(selectionNode);
|
|
let linkFieldNode = linkFieldSelectionSets.get(fieldName);
|
|
if (!linkFieldNode) {
|
|
linkFieldNode = {
|
|
selectedFieldType: selectedField.type,
|
|
field: selectionNode,
|
|
};
|
|
linkFieldSelectionSets.set(fieldName, linkFieldNode);
|
|
}
|
|
else {
|
|
mergeSelectionSets(linkFieldNode.field.selectionSet, selectionNode.selectionSet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const linkFields = [];
|
|
for (const { field, selectedFieldType } of linkFieldSelectionSets.values()) {
|
|
const realSelectedFieldType = pluginHelpers.getBaseType(selectedFieldType);
|
|
const selectionSet = this.createNext(realSelectedFieldType, field.selectionSet);
|
|
const isConditional = hasConditionalDirectives(field);
|
|
linkFields.push({
|
|
alias: field.alias ? this._processor.config.formatNamedField(field.alias.value, selectedFieldType) : undefined,
|
|
name: this._processor.config.formatNamedField(field.name.value, selectedFieldType, isConditional),
|
|
type: realSelectedFieldType.name,
|
|
selectionSet: this._processor.config.wrapTypeWithModifiers(selectionSet.transformSelectionSet().split(`\n`).join(`\n `), isConditional ? pluginHelpers.removeNonNullWrapper(selectedFieldType) : selectedFieldType),
|
|
});
|
|
}
|
|
const typeInfoField = this.buildTypeNameField(parentSchemaType, this._config.nonOptionalTypename, this._config.addTypename, requireTypename, this._config.skipTypeNameForRoot);
|
|
const transformed = [
|
|
...(typeInfoField ? this._processor.transformTypenameField(typeInfoField.type, typeInfoField.name) : []),
|
|
...this._processor.transformPrimitiveFields(parentSchemaType, Array.from(primitiveFields.values()).map(field => ({
|
|
isConditional: hasConditionalDirectives(field),
|
|
fieldName: field.name.value,
|
|
}))),
|
|
...this._processor.transformAliasesPrimitiveFields(parentSchemaType, Array.from(primitiveAliasFields.values()).map(field => ({
|
|
alias: field.alias.value,
|
|
fieldName: field.name.value,
|
|
}))),
|
|
...this._processor.transformLinkFields(linkFields),
|
|
].filter(Boolean);
|
|
const allStrings = transformed.filter(t => typeof t === 'string');
|
|
const allObjectsMerged = transformed
|
|
.filter(t => typeof t !== 'string')
|
|
.map((t) => `${t.name}: ${t.type}`);
|
|
let mergedObjectsAsString = null;
|
|
if (allObjectsMerged.length > 0) {
|
|
mergedObjectsAsString = this._processor.buildFieldsIntoObject(allObjectsMerged);
|
|
}
|
|
const fields = [...allStrings, mergedObjectsAsString, ...fragmentsSpreadUsages].filter(Boolean);
|
|
return this._processor.buildSelectionSetFromStrings(fields);
|
|
}
|
|
isRootType(type) {
|
|
const rootType = [this._schema.getQueryType(), this._schema.getMutationType(), this._schema.getSubscriptionType()]
|
|
.filter(Boolean)
|
|
.map(t => t.name);
|
|
return rootType.includes(type.name);
|
|
}
|
|
buildTypeNameField(type, nonOptionalTypename = this._config.nonOptionalTypename, addTypename = this._config.addTypename, queriedForTypename = this._queriedForTypename, skipTypeNameForRoot = this._config.skipTypeNameForRoot) {
|
|
if (this.isRootType(type) && skipTypeNameForRoot && !queriedForTypename) {
|
|
return null;
|
|
}
|
|
if (nonOptionalTypename || addTypename || queriedForTypename) {
|
|
const optionalTypename = !queriedForTypename && !nonOptionalTypename;
|
|
return {
|
|
name: `${this._processor.config.formatNamedField('__typename')}${optionalTypename ? '?' : ''}`,
|
|
type: `'${type.name}'`,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
getUnknownType() {
|
|
return 'never';
|
|
}
|
|
transformSelectionSet() {
|
|
const grouped = this._buildGroupedSelections();
|
|
// This might happen in case we have an interface, that is being queries, without any GraphQL
|
|
// "type" that implements it. It will lead to a runtime error, but we aim to try to reflect that in
|
|
// build time as well.
|
|
if (Object.keys(grouped).length === 0) {
|
|
return this.getUnknownType();
|
|
}
|
|
return Object.keys(grouped)
|
|
.map(typeName => {
|
|
const relevant = grouped[typeName].filter(Boolean);
|
|
if (relevant.length === 0) {
|
|
return null;
|
|
}
|
|
else if (relevant.length === 1) {
|
|
return relevant[0];
|
|
}
|
|
else {
|
|
return `( ${relevant.join(' & ')} )`;
|
|
}
|
|
})
|
|
.filter(Boolean)
|
|
.join(' | ');
|
|
}
|
|
transformFragmentSelectionSetToTypes(fragmentName, fragmentSuffix, declarationBlockConfig) {
|
|
const grouped = this._buildGroupedSelections();
|
|
const subTypes = Object.keys(grouped)
|
|
.map(typeName => {
|
|
const possibleFields = grouped[typeName].filter(Boolean);
|
|
const declarationName = this.buildFragmentTypeName(fragmentName, fragmentSuffix, typeName);
|
|
if (possibleFields.length === 0) {
|
|
if (!this._config.addTypename) {
|
|
return { name: declarationName, content: '{}' };
|
|
}
|
|
return null;
|
|
}
|
|
return { name: declarationName, content: possibleFields.join(' & ') };
|
|
})
|
|
.filter(Boolean);
|
|
if (subTypes.length === 1) {
|
|
return new DeclarationBlock(declarationBlockConfig)
|
|
.export()
|
|
.asKind('type')
|
|
.withName(this.buildFragmentTypeName(fragmentName, fragmentSuffix))
|
|
.withContent(subTypes[0].content).string;
|
|
}
|
|
return [
|
|
...subTypes.map(t => new DeclarationBlock(declarationBlockConfig)
|
|
.export(this._config.exportFragmentSpreadSubTypes)
|
|
.asKind('type')
|
|
.withName(t.name)
|
|
.withContent(t.content).string),
|
|
new DeclarationBlock(declarationBlockConfig)
|
|
.export()
|
|
.asKind('type')
|
|
.withName(this.buildFragmentTypeName(fragmentName, fragmentSuffix))
|
|
.withContent(subTypes.map(t => t.name).join(' | ')).string,
|
|
].join('\n');
|
|
}
|
|
buildFragmentTypeName(name, suffix, typeName = '') {
|
|
return this._convertName(name, {
|
|
useTypesPrefix: true,
|
|
suffix: typeName ? `_${typeName}_${suffix}` : suffix,
|
|
});
|
|
}
|
|
}
|
|
|
|
class BaseSelectionSetProcessor {
|
|
constructor(config) {
|
|
this.config = config;
|
|
}
|
|
buildFieldsIntoObject(allObjectsMerged) {
|
|
return `{ ${allObjectsMerged.join(', ')} }`;
|
|
}
|
|
buildSelectionSetFromStrings(pieces) {
|
|
if (pieces.length === 0) {
|
|
return null;
|
|
}
|
|
else if (pieces.length === 1) {
|
|
return pieces[0];
|
|
}
|
|
else {
|
|
return `(\n ${pieces.join(`\n & `)}\n)`;
|
|
}
|
|
}
|
|
transformPrimitiveFields(_schemaType, _fields) {
|
|
throw new Error(`Please override "transformPrimitiveFields" as part of your BaseSelectionSetProcessor implementation!`);
|
|
}
|
|
transformAliasesPrimitiveFields(_schemaType, _fields) {
|
|
throw new Error(`Please override "transformAliasesPrimitiveFields" as part of your BaseSelectionSetProcessor implementation!`);
|
|
}
|
|
transformLinkFields(_fields) {
|
|
throw new Error(`Please override "transformLinkFields" as part of your BaseSelectionSetProcessor implementation!`);
|
|
}
|
|
transformTypenameField(_type, _name) {
|
|
throw new Error(`Please override "transformTypenameField" as part of your BaseSelectionSetProcessor implementation!`);
|
|
}
|
|
}
|
|
|
|
class PreResolveTypesProcessor extends BaseSelectionSetProcessor {
|
|
transformTypenameField(type, name) {
|
|
return [
|
|
{
|
|
type,
|
|
name,
|
|
},
|
|
];
|
|
}
|
|
transformPrimitiveFields(schemaType, fields) {
|
|
if (fields.length === 0) {
|
|
return [];
|
|
}
|
|
return fields.map(field => {
|
|
const fieldObj = schemaType.getFields()[field.fieldName];
|
|
const baseType = pluginHelpers.getBaseType(fieldObj.type);
|
|
let typeToUse = baseType.name;
|
|
const useInnerType = field.isConditional && graphql.isNonNullType(fieldObj.type);
|
|
const innerType = useInnerType ? pluginHelpers.removeNonNullWrapper(fieldObj.type) : undefined;
|
|
if (graphql.isEnumType(baseType)) {
|
|
typeToUse =
|
|
(this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '') +
|
|
this.config.convertName(baseType.name, { useTypesPrefix: this.config.enumPrefix });
|
|
}
|
|
else if (this.config.scalars[baseType.name]) {
|
|
typeToUse = this.config.scalars[baseType.name];
|
|
}
|
|
const name = this.config.formatNamedField(field.fieldName, useInnerType ? innerType : fieldObj.type);
|
|
const wrappedType = this.config.wrapTypeWithModifiers(typeToUse, useInnerType ? innerType : fieldObj.type);
|
|
return {
|
|
name,
|
|
type: wrappedType,
|
|
};
|
|
});
|
|
}
|
|
transformAliasesPrimitiveFields(schemaType, fields) {
|
|
if (fields.length === 0) {
|
|
return [];
|
|
}
|
|
return fields.map(aliasedField => {
|
|
if (aliasedField.fieldName === '__typename') {
|
|
const name = this.config.formatNamedField(aliasedField.alias, null);
|
|
return {
|
|
name,
|
|
type: `'${schemaType.name}'`,
|
|
};
|
|
}
|
|
else {
|
|
const fieldObj = schemaType.getFields()[aliasedField.fieldName];
|
|
const baseType = pluginHelpers.getBaseType(fieldObj.type);
|
|
let typeToUse = this.config.scalars[baseType.name] || baseType.name;
|
|
if (graphql.isEnumType(baseType)) {
|
|
typeToUse =
|
|
(this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '') +
|
|
this.config.convertName(baseType.name, { useTypesPrefix: this.config.enumPrefix });
|
|
}
|
|
const name = this.config.formatNamedField(aliasedField.alias, fieldObj.type);
|
|
const wrappedType = this.config.wrapTypeWithModifiers(typeToUse, fieldObj.type);
|
|
return {
|
|
name,
|
|
type: wrappedType,
|
|
};
|
|
}
|
|
});
|
|
}
|
|
transformLinkFields(fields) {
|
|
if (fields.length === 0) {
|
|
return [];
|
|
}
|
|
return fields.map(field => ({
|
|
name: field.alias || field.name,
|
|
type: field.selectionSet,
|
|
}));
|
|
}
|
|
}
|
|
|
|
function optimizeOperations(schema, documents, options) {
|
|
const newDocuments = relayOperationOptimizer.optimizeDocuments(schema, documents.map(s => s.document), options);
|
|
return newDocuments.map(document => ({
|
|
location: 'optimized by relay',
|
|
document,
|
|
}));
|
|
}
|
|
|
|
exports.BaseDocumentsVisitor = BaseDocumentsVisitor;
|
|
exports.BaseResolversVisitor = BaseResolversVisitor;
|
|
exports.BaseSelectionSetProcessor = BaseSelectionSetProcessor;
|
|
exports.BaseTypesVisitor = BaseTypesVisitor;
|
|
exports.BaseVisitor = BaseVisitor;
|
|
exports.ClientSideBaseVisitor = ClientSideBaseVisitor;
|
|
exports.DEFAULT_AVOID_OPTIONALS = DEFAULT_AVOID_OPTIONALS;
|
|
exports.DEFAULT_DECLARATION_KINDS = DEFAULT_DECLARATION_KINDS;
|
|
exports.DEFAULT_SCALARS = DEFAULT_SCALARS;
|
|
exports.DeclarationBlock = DeclarationBlock;
|
|
exports.OMIT_TYPE = OMIT_TYPE;
|
|
exports.OperationVariablesToObject = OperationVariablesToObject;
|
|
exports.PreResolveTypesProcessor = PreResolveTypesProcessor;
|
|
exports.REQUIRE_FIELDS_TYPE = REQUIRE_FIELDS_TYPE;
|
|
exports.SelectionSetToObject = SelectionSetToObject;
|
|
exports.block = block;
|
|
exports.breakLine = breakLine;
|
|
exports.buildMapperImport = buildMapperImport;
|
|
exports.buildScalars = buildScalars;
|
|
exports.buildScalarsFromConfig = buildScalarsFromConfig;
|
|
exports.clearExtension = clearExtension;
|
|
exports.convertFactory = convertFactory;
|
|
exports.convertNameParts = convertNameParts;
|
|
exports.fixLocalFilePath = fixLocalFilePath;
|
|
exports.generateFragmentImportStatement = generateFragmentImportStatement;
|
|
exports.generateImportStatement = generateImportStatement;
|
|
exports.getBaseTypeNode = getBaseTypeNode;
|
|
exports.getConfigValue = getConfigValue;
|
|
exports.getFieldNodeNameValue = getFieldNodeNameValue;
|
|
exports.getPossibleTypes = getPossibleTypes;
|
|
exports.getRootTypeNames = getRootTypeNames;
|
|
exports.hasConditionalDirectives = hasConditionalDirectives;
|
|
exports.indent = indent;
|
|
exports.indentMultiline = indentMultiline;
|
|
exports.isExternalMapper = isExternalMapper;
|
|
exports.isExternalMapperType = isExternalMapperType;
|
|
exports.isRootType = isRootType;
|
|
exports.mergeSelectionSets = mergeSelectionSets;
|
|
exports.normalizeAvoidOptionals = normalizeAvoidOptionals;
|
|
exports.normalizeDeclarationKind = normalizeDeclarationKind;
|
|
exports.optimizeOperations = optimizeOperations;
|
|
exports.parseEnumValues = parseEnumValues;
|
|
exports.parseMapper = parseMapper;
|
|
exports.quoteIfNeeded = quoteIfNeeded;
|
|
exports.removeDescription = removeDescription;
|
|
exports.resolveImportSource = resolveImportSource;
|
|
exports.resolveRelativeImport = resolveRelativeImport;
|
|
exports.separateSelectionSet = separateSelectionSet;
|
|
exports.stripMapperTypeInterpolation = stripMapperTypeInterpolation;
|
|
exports.transformComment = transformComment;
|
|
exports.transformMappers = transformMappers;
|
|
exports.wrapTypeNodeWithModifiers = wrapTypeNodeWithModifiers;
|
|
exports.wrapTypeWithModifiers = wrapTypeWithModifiers;
|
|
exports.wrapWithSingleQuotes = wrapWithSingleQuotes;
|
|
//# sourceMappingURL=index.cjs.js.map
|