@graphql-codegen/near-operation-file-preset
Version:
GraphQL Code Generator preset for generating operation code near the operation file
151 lines (150 loc) • 7.11 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 a fragment's type imports based on possible types and usage
*/
function createFragmentTypeImports(baseVisitor, fragmentName, possibleTypes, usedTypes) {
const fragmentSuffix = baseVisitor.getFragmentSuffix(fragmentName);
if (possibleTypes.length === 1) {
return [
{
name: baseVisitor.convertName(fragmentName, {
useTypesPrefix: true,
suffix: fragmentSuffix,
}),
kind: 'type',
},
];
}
if (possibleTypes.length > 0) {
const typesToImport = usedTypes && usedTypes.length > 0 ? usedTypes : possibleTypes;
return typesToImport.map(typeName => ({
name: baseVisitor.convertName(fragmentName, {
useTypesPrefix: true,
suffix: `_${typeName}` + (fragmentSuffix.length > 0 ? `_${fragmentSuffix}` : ''),
}),
kind: 'type',
}));
}
return [];
}
/**
* Used by `buildFragmentResolver` to build a mapping of fragmentNames to paths, nodes, and other useful info
*/
function buildFragmentRegistry({ 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 filePath = generateFilePath({
location: documentRecord.location,
meta: { operations: [], fragments: [fragment] },
});
const fragmentName = fragment.name.value;
const possibleTypes = (0, visitor_plugin_common_1.getPossibleTypes)(schemaObject, schemaType);
const possibleTypeNames = possibleTypes.map(t => t.name);
if (prev[fragmentName] && (0, graphql_1.print)(fragment) !== (0, graphql_1.print)(prev[fragmentName].node)) {
duplicateFragmentNames.push(fragmentName);
}
prev[fragmentName] = {
filePath,
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(collectorOptions, presetOptions, schemaObject);
const { baseOutputDir } = presetOptions;
const { baseDir, typesImport } = collectorOptions;
function resolveFragments(generatedFilePath, documentFileContent) {
var _a;
var _b;
const { fragmentsInUse, usedFragmentTypes, operationFragments } = (0, utils_js_1.analyzeFragmentUsage)(documentFileContent, fragmentRegistry, schemaObject);
// graphQLTag operations inline every fragment they transitively spread
const { documentMode = visitor_plugin_common_1.DocumentMode.graphQLTag } = config;
const isGraphQLTagMode = documentMode === visitor_plugin_common_1.DocumentMode.graphQLTag;
const externalFragments = [];
const fragmentFileImports = {};
for (const [fragmentName, level] of Object.entries(fragmentsInUse)) {
const fragmentDetails = fragmentRegistry[fragmentName];
if (!fragmentDetails)
continue;
// don't emit imports to the same location
if (fragmentDetails.filePath !== generatedFilePath) {
const imports = [];
const isSpreadDirectly = level === 0 || dedupeFragments;
const needsDocument = isGraphQLTagMode
? operationFragments.has(fragmentName)
: isSpreadDirectly;
if (needsDocument) {
imports.push({
name: baseVisitor.getFragmentVariableName(fragmentName),
kind: 'document',
});
}
if (isSpreadDirectly) {
imports.push(...createFragmentTypeImports(baseVisitor, fragmentName, fragmentDetails.possibleTypes, usedFragmentTypes[fragmentName] || []));
}
if (imports.length > 0) {
((_a = fragmentFileImports[_b = fragmentDetails.filePath]) !== null && _a !== void 0 ? _a : (fragmentFileImports[_b] = [])).push(...imports);
}
}
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,
importExtension: presetOptions.config.importExtension,
typesImport,
})),
};
}
return resolveFragments;
}