Skip to content

bug: discriminatorFields config key does not match auto-generated name for external union type aliases #232

@izumin5210

Description

@izumin5210

Summary

When a union type alias is defined outside the schema directory and used as a field type in a schema-exported type, the discriminatorFields config cannot match because:

  1. The type is not in knownTypeNames (not exported from schema directory)
  2. The nullable union alias fix (fix(cli): resolve nullable union type alias as reference instead of inline union #227) cannot apply — isKnownSchemaType requires knownTypeNames
  3. TypeScript flattens the union, losing the alias → expanded as an inline union
  4. The auto-generated name follows {ParentType}{FieldPascalCase} convention, which differs from the original type alias name
  5. flattenInlineUnionMembers fails to match → UNNAMEABLE_UNION_MEMBER error

Reproduction

Directory structure

src/
├── lib/
│   └── types.ts           # External type definition (outside schema dir)
└── gqlkit/
    └── schema/
        └── types.ts        # Uses the external type (does NOT re-export it)

Source files

// src/lib/types.ts
export type ContentPart =
  | { type: "text"; content: string }
  | { type: "image"; url: string };
// src/gqlkit/schema/types.ts
import type { ContentPart } from "../../lib/types";

export type Message = {
  parts: ContentPart[];
};

// Note: ContentPart is NOT re-exported from the schema directory
// gqlkit.config.ts
export default defineConfig({
  sourceDir: "src/gqlkit/schema",
  discriminatorFields: {
    ContentPart: "type",  // user uses the original alias name
  },
});

Expected

discriminatorFields config for ContentPart is applied. The union members are grouped by the type discriminator field, and code generation succeeds.

Actual

The following chain of events occurs:

  1. ContentPart is not in knownTypeNames (not exported from schema dir)
  2. field-type-resolver.ts cannot resolve ContentPart[] element type as a reference (the fix from fix(cli): resolve nullable union type alias as reference instead of inline union #227 requires isKnownSchemaType, which checks knownTypeNames)
  3. TypeScript expands the union into inline object members
  4. collectInlineUnionsFromTypes collects it with context { kind: "objectField", parentTypeName: "Message", fieldPath: ["parts"] }
  5. generateAutoTypeName produces MessageParts (not ContentPart)
  6. flattenInlineUnionMembers looks up discriminatorFields.get("MessageParts")not found (config key is ContentPart)
  7. The inline objects have no inlineObjectHintName and no __typename:
error[UNNAMEABLE_UNION_MEMBER]: Inline object union member at index 0 cannot be named.
Use a named type (type alias or interface) for each union member, or add a '__typename'
property with a string literal type.

Workaround

The user can use the auto-generated name as the config key:

discriminatorFields: {
  MessageParts: "type",  // auto-generated: {ParentType}{FieldPascalCase}
},

This is fragile and requires the user to know the internal naming convention.

Possible Solutions

A few approaches to consider (non-exhaustive):

  • Match by unionAliasName: flattenInlineUnionMembers could also check inlineUnion.unionAliasName against the discriminatorFields keys, not just the auto-generated name.
  • Expand knownTypeNames: Automatically include types transitively referenced by schema-exported types, so the nullable union alias fix from fix(cli): resolve nullable union type alias as reference instead of inline union #227 applies and the type resolves as a reference rather than being expanded inline.
  • Documentation: Document that discriminatorFields keys must match either the schema-exported type name or the auto-generated name.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions