@eddeee888/gcg-typescript-resolver-files
Version:
This [GraphQL Code Generator](https://www.the-guild.dev/graphql/codegen) plugin creates resolvers given GraphQL schema.
224 lines • 10.4 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.addResolverMainFiles = void 0;
const tslib_1 = require("tslib");
const path = tslib_1.__importStar(require("path"));
const utils_1 = require("../utils");
const createDefaultFileDetails = () => ({
importLines: [],
queryFields: [],
mutationFields: [],
subscriptionFields: [],
objectTypes: {},
});
/**
* addResolverMainFiles
*
* @description Function to create resolver main file/s (default: resolvers.generated.ts)
* If resolverMainFileMode=merged, only one file is generated
* If resolverMainFileMode=modules, one file is generated for each module
* If no resolvers are created or imported, do not generate a file.
*/
const addResolverMainFiles = ({ config: { baseOutputDir, resolverTypesPath, resolverMainFile, resolverMainFileMode, emitLegacyCommonJSImports, generatedTypesFileMeta: { generatedResolverTypes }, }, result, }) => {
const baseIdentifierUsage = countBaseIdentifiersUsage(result.files);
const resolverMainFiles = Object.entries(result.files).reduce((res, [filepath, file]) => {
if (file.__filetype === 'file') {
return res;
}
const { meta } = file;
// If resolverMainFileMode === 'modules', generate main resolver files into modules, otherwise, do it at baseOutputDir
const resolverMainFilename = resolverMainFileMode === 'modules'
? path.posix.join(baseOutputDir, ...meta.relativePathFromBaseToModule, resolverMainFile)
: path.posix.join(baseOutputDir, resolverMainFile);
const outputDir = path.dirname(resolverMainFilename);
if (!res[resolverMainFilename]) {
res[resolverMainFilename] = createDefaultFileDetails();
}
const baseIdentifierName = makeNormalizedResolverNameVariableCompatible(file.meta.normalizedResolverName.base);
const identifierName = baseIdentifierUsage[baseIdentifierName] > 1
? makeNormalizedResolverNameVariableCompatible(file.meta.normalizedResolverName.withModule)
: baseIdentifierName;
const fieldMapping = {
propertyName: file.mainImportIdentifier,
identifierName,
};
// Non Root Object fields that was generated
if (file.__filetype === 'objectType' ||
file.__filetype === 'interfaceResolver' ||
file.__filetype === 'unionResolver' ||
file.__filetype === 'scalarResolver' ||
file.__filetype === 'enumResolver') {
res[resolverMainFilename].importLines.push((0, utils_1.printImportLine)({
isTypeImport: false,
module: (0, utils_1.relativeModulePath)(outputDir, filepath),
moduleType: 'file',
namedImports: [fieldMapping],
emitLegacyCommonJSImports,
}));
pushToObjectTypes({
objectTypes: res[resolverMainFilename].objectTypes,
property: file.mainImportIdentifier,
value: identifierName,
});
return res;
}
// Root object fields
res[resolverMainFilename].importLines.push((0, utils_1.printImportLine)({
isTypeImport: false,
module: (0, utils_1.relativeModulePath)(outputDir, filepath),
moduleType: 'file',
namedImports: [fieldMapping],
emitLegacyCommonJSImports,
}));
const rootObjectMap = {
Query: () => res[resolverMainFilename].queryFields.push(fieldMapping),
Mutation: () => res[resolverMainFilename].mutationFields.push(fieldMapping),
Subscription: () => res[resolverMainFilename].subscriptionFields.push(fieldMapping),
};
rootObjectMap[file.meta.belongsToRootObject]();
return res;
}, {});
Object.entries(result.externalImports).reduce((res, [module, meta]) => {
// If resolverMainFileMode === 'modules', generate main resolver files into modules, otherwise, do it at baseOutputDir
const resolverMainFilename = resolverMainFileMode === 'modules'
? path.posix.join(baseOutputDir, ...meta.relativePathFromBaseToModule, resolverMainFile)
: path.posix.join(baseOutputDir, resolverMainFile);
if (!res[resolverMainFilename]) {
res[resolverMainFilename] = createDefaultFileDetails();
}
res[resolverMainFilename].importLines.push((0, utils_1.printImportLine)({
isTypeImport: false,
module,
moduleType: 'preserve',
namedImports: meta.importLineMeta.namedImports,
defaultImport: meta.importLineMeta.defaultImport,
emitLegacyCommonJSImports,
}));
meta.identifierUsages.forEach((usage) => {
const resolverNameParts = usage.normalizedResolverName.split('.');
// Only 1 part, this is a GraphQL ObjectType
if (resolverNameParts.length === 1) {
pushToObjectTypes({
objectTypes: res[resolverMainFilename].objectTypes,
property: resolverNameParts[0],
value: usage.identifierName,
});
return;
}
// 2+ parts, treat as 2 parts. This is a GraphQL ObjectType with field e.g. Query.me, Mutation.updateUser
const [rootObjectType, field] = resolverNameParts;
if (!(0, utils_1.isRootObjectType)(rootObjectType)) {
throw new Error(`Unexpected root object type found: ${rootObjectType}`);
}
const fieldMapping = {
identifierName: usage.identifierName,
propertyName: field,
};
const rootObjectMap = {
Query: () => res[resolverMainFilename].queryFields.push(fieldMapping),
Mutation: () => res[resolverMainFilename].mutationFields.push(fieldMapping),
Subscription: () => res[resolverMainFilename].subscriptionFields.push(fieldMapping),
};
rootObjectMap[rootObjectType]();
});
return res;
}, resolverMainFiles);
const resolversIdentifier = 'resolvers';
const resolversTypeName = generatedResolverTypes.resolversMap.name;
Object.entries(resolverMainFiles).forEach(([resolverMainFilename, file]) => {
const outputDir = path.dirname(resolverMainFilename);
const relativePathToResolverTypes = (0, utils_1.relativeModulePath)(outputDir, resolverTypesPath);
const queries = file.queryFields.length > 0
? `Query: { ${file.queryFields.map(printObjectMapping).join(',')} },`
: '';
const mutations = file.mutationFields.length > 0
? `Mutation: { ${file.mutationFields
.map(printObjectMapping)
.join(',')} },`
: '';
const suscriptions = file.subscriptionFields.length > 0
? `Subscription: { ${file.subscriptionFields
.map(printObjectMapping)
.join(',')} },`
: '';
result.files[resolverMainFilename] = {
__filetype: 'file',
// TODO: type='virtual' means we will generate these main files every run.
// Consider checking if these files are already on the filesystem, and whether the content has changed before generating?
filesystem: {
type: 'virtual',
contentUpdated: false,
},
mainImportIdentifier: resolversIdentifier,
content: `/* This file was automatically generated. DO NOT UPDATE MANUALLY. */
${(0, utils_1.printImportLine)({
isTypeImport: true,
module: relativePathToResolverTypes,
moduleType: 'file',
namedImports: [resolversTypeName],
emitLegacyCommonJSImports,
})}
${file.importLines.join('\n')}
export const ${resolversIdentifier}: ${resolversTypeName} = {
${queries}
${mutations}
${suscriptions}
${printObjectTypes(file.objectTypes)}
}`,
};
});
};
exports.addResolverMainFiles = addResolverMainFiles;
const printObjectMapping = ({ propertyName, identifierName, }) => `${propertyName}: ${identifierName}`;
const pushToObjectTypes = ({ objectTypes, property, value, }) => {
if (!objectTypes[property]) {
objectTypes[property] = [];
}
objectTypes[property].push(value);
};
const printObjectTypes = (objectTypes) => {
return Object.entries(objectTypes)
.map(([property, values]) => {
if (values.length === 1) {
return `${property}: ${values[0]}`;
}
const spreadedValueString = values
.map((value) => `...${value}`)
.join(',');
return `${property}: { ${spreadedValueString} }`;
})
.join(',\n');
};
/**
* countBaseIdentifiersUsage
*
* function to count how many of the same identifier is used across all files
*/
const countBaseIdentifiersUsage = (files) => {
return Object.entries(files).reduce((res, [_filepath, file]) => {
if (file.__filetype === 'file') {
return res;
}
const identifier = makeNormalizedResolverNameVariableCompatible(file.meta.normalizedResolverName.base);
if (!res[identifier]) {
res[identifier] = 0;
}
res[identifier]++;
return res;
}, {});
};
/**
* makeNormalizedResolverNameVariableCompatible
* @description Function to replace `-` and `.` in a string with underscore to be used as a variable name
*
* Note: this is to make sure the resolver name is a valid variable name because we use it in resolvers.generated.ts
* However, this naive implementation means there could be a duplicated variable name if a mix of kebab-case and snake_case is used.
* e.g. `user-by-account-name` and `user_by_account_name` will both be normalized to `user_by_account_name`
*
* If users report this issue. We can try another approach.
* I'm banking my slim hope in humanity that no codebase is so chaotic that it uses both kebab-case and snake_case.
*/
const makeNormalizedResolverNameVariableCompatible = (identifier) => {
return identifier.replace(/[-.]/g, '_');
};
//# sourceMappingURL=addResolverMainFiles.js.map
;