@graphql-codegen/near-operation-file-preset
Version:
GraphQL Code Generator preset for generating operation code near the operation file
145 lines (144 loc) • 6.93 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = buildFragmentResolver;
const graphql_1 = require("graphql");
const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
const utils_js_1 = require("./utils.js");
/**
* Creates fragment imports based on possible types and usage
*/
function createFragmentImports(baseVisitor, fragmentName, possibleTypes, usedTypes) {
const fragmentImports = [];
// Always include the document import
fragmentImports.push({
name: baseVisitor.getFragmentVariableName(fragmentName),
kind: 'document',
});
const fragmentSuffix = baseVisitor.getFragmentSuffix(fragmentName);
if (possibleTypes.length === 1) {
fragmentImports.push({
name: baseVisitor.convertName(fragmentName, {
useTypesPrefix: true,
suffix: fragmentSuffix,
}),
kind: 'type',
});
}
else if (possibleTypes.length > 0) {
const typesToImport = usedTypes && usedTypes.length > 0 ? usedTypes : possibleTypes;
typesToImport.forEach(typeName => {
fragmentImports.push({
name: baseVisitor.convertName(fragmentName, {
useTypesPrefix: true,
suffix: `_${typeName}` + (fragmentSuffix.length > 0 ? `_${fragmentSuffix}` : ''),
}),
kind: 'type',
});
});
}
return fragmentImports;
}
/**
* Used by `buildFragmentResolver` to build a mapping of fragmentNames to paths, importNames, and other useful info
*/
function buildFragmentRegistry(baseVisitor, { generateFilePath }, { documents }, schemaObject) {
const duplicateFragmentNames = [];
const registry = documents.reduce((prev, documentRecord) => {
const fragments = documentRecord.document.definitions.filter(d => d.kind === graphql_1.Kind.FRAGMENT_DEFINITION);
for (const fragment of fragments) {
const schemaType = schemaObject.getType(fragment.typeCondition.name.value);
if (!schemaType) {
throw new Error(`Fragment "${fragment.name.value}" is set on non-existing type "${fragment.typeCondition.name.value}"!`);
}
const fragmentName = fragment.name.value;
const filePath = generateFilePath(documentRecord.location);
const possibleTypes = (0, visitor_plugin_common_1.getPossibleTypes)(schemaObject, schemaType);
const possibleTypeNames = possibleTypes.map(t => t.name);
const imports = createFragmentImports(baseVisitor, fragment.name.value, possibleTypeNames);
if (prev[fragmentName] && (0, graphql_1.print)(fragment) !== (0, graphql_1.print)(prev[fragmentName].node)) {
duplicateFragmentNames.push(fragmentName);
}
prev[fragmentName] = {
filePath,
imports,
onType: fragment.typeCondition.name.value,
node: fragment,
possibleTypes: possibleTypeNames,
};
}
return prev;
}, {});
if (duplicateFragmentNames.length) {
throw new Error(`Multiple fragments with the name(s) "${duplicateFragmentNames.join(', ')}" were found.`);
}
return registry;
}
/**
* Creates a BaseVisitor with standard configuration
*/
function createBaseVisitor(config, schemaObject) {
return new visitor_plugin_common_1.BaseVisitor(config, {
scalars: (0, visitor_plugin_common_1.buildScalarsFromConfig)(schemaObject, config),
dedupeOperationSuffix: (0, visitor_plugin_common_1.getConfigValue)(config.dedupeOperationSuffix, false),
omitOperationSuffix: (0, visitor_plugin_common_1.getConfigValue)(config.omitOperationSuffix, false),
fragmentVariablePrefix: (0, visitor_plugin_common_1.getConfigValue)(config.fragmentVariablePrefix, ''),
fragmentVariableSuffix: (0, visitor_plugin_common_1.getConfigValue)(config.fragmentVariableSuffix, 'FragmentDoc'),
});
}
/**
* Builds a fragment "resolver" that collects `externalFragments` definitions and `fragmentImportStatements`
*/
function buildFragmentResolver(collectorOptions, presetOptions, schemaObject, dedupeFragments = false) {
const { config } = presetOptions;
const baseVisitor = createBaseVisitor(config, schemaObject);
const fragmentRegistry = buildFragmentRegistry(baseVisitor, collectorOptions, presetOptions, schemaObject);
const { baseOutputDir } = presetOptions;
const { baseDir, typesImport } = collectorOptions;
function resolveFragments(generatedFilePath, documentFileContent) {
const { fragmentsInUse, usedFragmentTypes } = (0, utils_js_1.analyzeFragmentUsage)(documentFileContent, fragmentRegistry, schemaObject);
const externalFragments = [];
const fragmentFileImports = {};
for (const [fragmentName, level] of Object.entries(fragmentsInUse)) {
const fragmentDetails = fragmentRegistry[fragmentName];
if (!fragmentDetails)
continue;
// add top level references to the import object
// we don't check or global namespace because the calling config can do so
if (level === 0 ||
(dedupeFragments &&
['OperationDefinition', 'FragmentDefinition'].includes(documentFileContent.definitions[0].kind))) {
if (fragmentDetails.filePath !== generatedFilePath) {
// don't emit imports to same location
const usedTypesForFragment = usedFragmentTypes[fragmentName] || [];
const filteredImports = createFragmentImports(baseVisitor, fragmentName, fragmentDetails.possibleTypes, usedTypesForFragment);
if (!fragmentFileImports[fragmentDetails.filePath]) {
fragmentFileImports[fragmentDetails.filePath] = [];
}
fragmentFileImports[fragmentDetails.filePath].push(...filteredImports);
}
}
externalFragments.push({
level,
isExternal: true,
name: fragmentName,
onType: fragmentDetails.onType,
node: fragmentDetails.node,
});
}
return {
externalFragments,
fragmentImports: Object.entries(fragmentFileImports).map(([fragmentsFilePath, identifiers]) => ({
baseDir,
baseOutputDir,
outputPath: generatedFilePath,
importSource: {
path: fragmentsFilePath,
identifiers,
},
emitLegacyCommonJSImports: presetOptions.config.emitLegacyCommonJSImports,
typesImport,
})),
};
}
return resolveFragments;
}