Rename types with graphql-codegen

Rename types with graphql-codegen


4

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?

1 Answer
1


0

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();



Leave a Reply

Your email address will not be published. Required fields are marked *