Skip to content

Incorrectly generates TypeScript type {} for empty object #10385

@jaydenseric

Description

@jaydenseric

Which packages are impacted by your issue?

@graphql-codegen/typescript, @graphql-codegen/typescript-operations

Describe the bug

Codegen creates the TypeScript type {} to represent an empty object, but that is incorrect because in TypeScript that means any types other than null and undefined.

The correct type to represent an empty object in TypeScript is { [key: PropertyKey]: never } (my favourite way) or Record<PropertyKey, never>.

Deno lint's recommended rule set will actually put a lint error on {}, via the rule ban-types:

https://docs.deno.com/lint/rules/ban-types/

Your Example Website or App

Nah

Steps to Reproduce the Bug or Issue

An example of where empty objects can happen in GraphQL operations is:

query Foo {
  node(id: "") {
    ... on User {
      __typename
    }
  }
}

Which will result in something like this:

export type FooQuery = {
  readonly node:
    | {
      readonly __typename: "User";
    }
    | {}
    | null;
};

Because in theory, you could use a node ID for something that's not a User, and end up with an empty object in the query data.

Expected behavior

Here is a more correct type:

export type FooQuery = {
  readonly node:
    | {
      readonly __typename: "User";
    }
    | { [key: PropertyKey]: never }
    | null;
};

Screenshots or Videos

No response

Platform

  • OS: macOS
  • Deno: 2.4.2
  • @graphql-codegen/cli: 5.0.7
  • @graphql-codegen/near-operation-file-preset: 3.1.0
  • @graphql-codegen/typescript: 4.1.6
  • @graphql-codegen/typescript-operations: 4.6.1
  • graphql: 16.11.0

Codegen Config File

In graphql.config.json:

{
  "documents": "**/*.{graphql,mjs}",
  "schema": "./schema.graphql",
  "extensions": {
    "codegen": {
      "config": {
        "avoidOptionals": true,
        "defaultScalarType": "unknown",
        "disableDescriptions": true,
        "enumsAsTypes": true,
        "immutableTypes": true,
        "namingConvention": "keep",
        "printFieldsOnNewLines": true,
        "scalars": {
          "Color": "string",
          "DateTime": "string",
          "Decimal": "string",
          "HTML": "string",
          "ISO8601DateTime": "string",
          "JSON": "string",
          "URL": "string",
          "UnsignedInt64": "string"
        },
        "useTypeImports": true
      },
      "hooks": {
        "afterOneFileWrite": ["deno fmt"]
      },
      "generates": {
        "public/types/schema.mts": {
          "plugins": ["typescript"],
          "config": {
            "onlyOperationTypes": true
          }
        },
        "public/": {
          "preset": "near-operation-file",
          "presetConfig": {
            "extension": ".types.mts",
            "baseTypesPath": "types/schema.mts.mts"
          },
          "plugins": ["typescript-operations"],
          "config": {
            "skipTypename": true
          }
        }
      }
    }
  }
}

Additional context

A temporary workaround in the codegen config:

{
  "hooks": {
    "afterOneFileWrite": [
      "sed -i '' 's/{}/{ [key: PropertyKey]: never }/g'"
    ]
  }
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions