@eddeee888/gcg-typescript-resolver-files
Version:
This [GraphQL Code Generator](https://www.the-guild.dev/graphql/codegen) plugin creates resolvers given GraphQL schema.
168 lines • 8.42 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGraphQLObjectTypeResolversToGenerate = void 0;
const ts_morph_1 = require("ts-morph");
const getNodePropertyMap_1 = require("./getNodePropertyMap");
const getGraphQLObjectTypeResolversToGenerate = ({ tsMorphProject, typesSourceFile, typeMappersMap, userDefinedSchemaObjectTypeMap, generatedTypesFileMeta, }) => {
const typeMappersEntries = Object.entries(typeMappersMap);
if (typeMappersEntries.length === 0) {
return {};
}
/**
* `generatedTypesFileMeta.generatedResolverTypes.userDefined` is `schemaType` -> `generatedResolverTypes`
* We will be parsing `types.generated.ts` file for the `generatedResolverTypes`
* So, we need to invert `generatedTypesFileMeta.generatedResolverTypes.userDefined` to get `generatedResolverTypes` -> `schemaType`
* e.g.
*
* generatedTypesFileMeta.generatedResolverTypes.userDefined = {
* User: {
* name: 'UserResolvers'
* }
* }
*
* after transformation:
*
* generatedSchemaTypeNameMap = {
* UserResolvers: 'User'
* }
*/
const generatedSchemaTypeNameMap = Object.entries(generatedTypesFileMeta.generatedResolverTypes.userDefined).reduce((res, [schemaType, generatedSchemaTypeName]) => {
res[generatedSchemaTypeName.name] = schemaType;
return res;
}, {});
// 1. Get property map of all schema types
const schemaResolversTypePropertyMap = {};
const populateSchemaTypeResolversPropertyMap = (node) => {
const identifier = node.getNameNode();
const identifierName = identifier.getText();
const schemaType = generatedSchemaTypeNameMap[identifierName];
if (schemaType && userDefinedSchemaObjectTypeMap[schemaType]) {
schemaResolversTypePropertyMap[schemaType] = (0, getNodePropertyMap_1.getNodePropertyMap)({
tsMorphProject,
node,
});
}
};
typesSourceFile
.getDescendantsOfKind(ts_morph_1.SyntaxKind.TypeAliasDeclaration)
.forEach(populateSchemaTypeResolversPropertyMap);
typesSourceFile
.getDescendantsOfKind(ts_morph_1.SyntaxKind.InterfaceDeclaration)
.forEach(populateSchemaTypeResolversPropertyMap);
// 3. Find resolvers to generate and add reason
const result = {};
typeMappersEntries.forEach(([_, { schemaType, mapper }]) => {
const matchedSchemaTypePropertyMap = schemaResolversTypePropertyMap[schemaType];
if (!matchedSchemaTypePropertyMap) {
return;
}
const originalDeclarationNode = mustGetMapperOriginalDeclarationNode({
tsMorphProject,
mapper,
});
const typeMapperPropertyMap = (0, getNodePropertyMap_1.getNodePropertyMap)({
tsMorphProject,
node: originalDeclarationNode,
});
Object.values(matchedSchemaTypePropertyMap).forEach((schemaTypeProperty) => {
const typeMapperProperty = typeMapperPropertyMap[schemaTypeProperty.name];
const typeMapperPropertyIdentifier = `${mapper.name}.${schemaTypeProperty.name}`;
const schemaTypePropertyIdentifier = `${schemaType}.${schemaTypeProperty.name}`;
// Generated resolvers types may have one or more of these meta resolvers
// A mapper would most likely never have these resolvers, so we skip them
// Otherwise, these resolvers will always be generated
const metaResolvers = {
__isTypeOf: true,
__resolveReference: true,
};
if (metaResolvers[schemaTypeProperty.name]) {
return;
}
result[schemaType] = result[schemaType] || {};
// If mapper does not have a field in schema type, add missing resolver
if (!typeMapperProperty) {
result[schemaType][schemaTypeProperty.name] = {
resolverName: schemaTypeProperty.name,
resolverDeclaration: `async (_parent, _arg, _ctx) => { /* ${schemaTypePropertyIdentifier} resolver is required because ${schemaTypePropertyIdentifier} exists but ${typeMapperPropertyIdentifier} does not */ }`,
};
return;
}
/**
* FIXME: TypeScript's `isTypeAssignableTo` should be used to check if the mapper type vs resolver return type is compatible.
* The current challenge is to:
* - Switch from using the schema type to resolver return type e.g. `User` -> `UserResolver`
* - Take the ReturnType of the resolver function type e.g. `Resolver<ResolversTypes['UserRole'], ParentType, ContextType>`
*
* For now, the workaround now is to generate all resolvers with matching names,
* then use TS diagnostics to see if there's error when trying to merge the two keys.
*
* Note: this happens only when mappers are used
*/
result[schemaType][schemaTypeProperty.name] = {
resolverName: schemaTypeProperty.name,
resolverDeclaration: `({ ${schemaTypeProperty.name} }, _arg, _ctx) => {
/* ${schemaTypePropertyIdentifier} resolver is required because ${schemaTypePropertyIdentifier} and ${typeMapperPropertyIdentifier} are not compatible */
return ${schemaTypeProperty.name}
}`,
};
return;
});
});
return result;
};
exports.getGraphQLObjectTypeResolversToGenerate = getGraphQLObjectTypeResolversToGenerate;
const mustGetMapperOriginalDeclarationNode = ({ tsMorphProject, mapper, }) => {
const typeMapperFile = tsMorphProject.getSourceFile(mapper.filename);
if (!typeMapperFile) {
throw new Error(`Unable to find ${typeMapperFile} file after parsing. This shouldn't happen.`);
}
/**
* Finding `firstDescendantThatIsMapper` here is a bit of the duplicated traversing logic in `collectTypeMappersFromSourceFile`.
* However, in `collectTypeMappersFromSourceFile`, we find the mappers details.
* And here, we actually do look for the mapper nodes and run analysis on it.
*
* Previously, we were parsing the node property map in `collectTypeMappersFromSourceFile`
* but for some reason `isAssignableTo` has issue comparing types, so we have to move the static analysis here for now.
*/
const firstDescendantThatIsMapper = (() => {
for (const descendant of typeMapperFile.getDescendants()) {
const typedNode = descendant.isKind(mapper.kind);
if (typedNode) {
let identifierNode = descendant.getNameNode();
if (descendant.isKind(ts_morph_1.SyntaxKind.ExportSpecifier)) {
const aliasNode = descendant.getAliasNode();
if (aliasNode) {
identifierNode = aliasNode;
}
}
if ((identifierNode === null || identifierNode === void 0 ? void 0 : identifierNode.getText()) === mapper.name) {
return {
declarationNode: descendant,
identifierNode,
};
}
}
}
return;
})();
if (!firstDescendantThatIsMapper) {
throw new Error(`Unable to find ${mapper.name} node after parsing. This shouldn't happen.`);
}
return getOriginalDeclarationNode(firstDescendantThatIsMapper);
};
const getOriginalDeclarationNode = ({ declarationNode, identifierNode, }) => {
if (declarationNode.isKind(ts_morph_1.SyntaxKind.ExportSpecifier) ||
declarationNode.isKind(ts_morph_1.SyntaxKind.ClassDeclaration)) {
return identifierNode.getDefinitionNodes()[0];
}
// InterfaceDeclaration
if (declarationNode.isKind(ts_morph_1.SyntaxKind.InterfaceDeclaration)) {
return declarationNode;
}
// TypeAliasDeclaration
const typeNode = declarationNode.getTypeNodeOrThrow();
return ts_morph_1.Node.isTypeReference(typeNode)
? identifierNode.getDefinitionNodes()[0] // If type alias is a reference, go to definition using `getDefinitionNodes`
: declarationNode;
};
//# sourceMappingURL=getGraphQLObjectTypeResolversToGenerate.js.map
;