I'm working on a legacy codebase where, in our GraphQL schema, an interface was defined as 'Subscription':
interface Subscription {
fieldOne: String
fieldTwo: String
}
type FirstSubscriptionType implements Subscription {
anotherField: String
}
type SecondSubscriptionType implements Subscription {
yetAnotherField: String
}
This has worked fine (we don't use subscriptions and don't have plans to), but when I try to use graphql-codegen to auto-generate Typescript types for us to use in our frontend code, the generates step fails with this error:
Subscription root type must be Object type if provided, it cannot be Subscription
The obvious answer is to rename the interface. Sure enough, renaming to something like ISubscription fixes everything. I'm wondering if there's a way to avoid having to change anything in our schema and have graphql-codegen translate/rename the type in just the frontend code. Is this possible?
Bit late to the party but I have found an answer.
I leveraged the lifecycle hooks that GraphQL Codegen exposes to call a typescript transformer script which de-dupes the type definitions.
Configure your codegen as shown below, so it calls the script after generating the file(s). If you need to de-dupe multiple files, you can change the lifecycle hook from afterOneFileWrite to afterAllFileWrite. The script will receive the filepath for the generated file(s) as process args.
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
overwrite: true,
schema: [
{
"/source/": {},
},
],
hooks: {
afterOneFileWrite: ["ts-node scripts/resolve-type-conflicts"],
afterAllFileWrite: ["prettier --write"],
},
documents: [
/** etc */
],
generates: {
"src/": {
/* Whatever your config is */
},
},
};
export default config;
Then write the resolve-type-conflicts.ts script as such
import * as fs from "fs";
import * as ts from "typescript";
const resolveTypeConflicts = () => {
// this will be the generated file from codegen
const [, , file] = process.argv;
// we track how often we have seen a given variable name here
const collisionsCounter: Record<string, number> = {};
const transformerFactory: ts.TransformerFactory<ts.Node> =
(context) => (rootNode) => {
function visitTypeAliasDeclaration(node: ts.Node): ts.Node {
// recurse through the Typescript AST of the file
node = ts.visitEachChild(
node,
visitTypeAliasDeclaration,
context
);
/**
*
* short-circuit logic to prevent handling nodes we don't
* care about. This might need to be adjusted based on what
* you need to de-dupe. In my case only types were being duped
*/
if (!ts.isTypeAliasDeclaration(node)) {
return node;
}
/**
* you may not need to cast to lowercase. In my case I had
* name collisions that different cases which screwed up a
* later step
*/
const nameInLowerCase = node.name.text.toLowerCase();
const suffix = collisionsCounter[nameInLowerCase] ?? "";
const encounterCount = collisionsCounter[nameInLowerCase];
collisionsCounter[nameInLowerCase] =
typeof encounterCount === "undefined"
? 1
: encounterCount + 1;
return context.factory.createTypeAliasDeclaration(
node.modifiers,
`${node.name.text}${suffix}`,
node.typeParameters,
node.type
);
}
return ts.visitNode(rootNode, visitTypeAliasDeclaration);
};
const program = ts.createProgram([file], {});
const sourceFile = program.getSourceFile(file);
const transformationResult = ts.transform(sourceFile, [transformerFactory]);
const transformedSourceFile = transformationResult.transformed[0];
const printer = ts.createPrinter();
// the deduped code is here as a string
const result = printer.printNode(
ts.EmitHint.Unspecified,
transformedSourceFile,
undefined
);
fs.writeFileSync(file, result, {});
};
resolveTypeConflicts();
There is a much easier way to do this than the accepted answer. The graphql codegen library allows you to supply a custom function for renaming types that is run as the code is generated. You can put the following into a file called rename.cjs:
function RenameTypes(str) {
if (str === 'Subscription') {
return 'ISubscription';
}
return str;
}
module.exports = RenameTypes;
Then add the following line to your codegen.ts config file:
config: {
namingConvention: './rename.cjs'
}
For example:
const config: CodegenConfig = {
overwrite: true,
schema: 'http://localhost:4000/graphql',
generates: {
'src/gql/': {
preset: 'client',
plugins: []
}
},
config: { namingConvention: './rename.cjs' }
};
That should rename the generated Subscription type to ISubscription.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With