UNPKG

@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
"use strict"; 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