Skip to content

bug: Inherited/implemented fields not resolved at runtime when using extends or implements #110

@kleberbaum

Description

@kleberbaum

Bug: Inherited/implemented fields not resolved at runtime when using extends or implements

Description

When using class inheritance (extends) or interface implementation (implements) with Pylon v3, the build step succeeds and correctly generates the IBaseClass interface with all fields. However, at runtime, GraphQL schema validation fails because the inherited/implemented fields are not registered on the subclass types.

Note: This affects both extends and implements. The bug is in the runtime resolver map generation, not in the build-time schema analysis.

Reproduction

// Base class with shared fields and resolver methods
export class RegionNode {
  public id: string = ""
  public name: string = ""
  public type: string = "TRAFFIC"
  public gtfsImportId: number = 1

  async accumulatedDistance(): Promise<number> { return 0 }
  async totalLines(): Promise<number> { return 0 }
  async price(): Promise<number> { return 0 }
}

// Subclass with no additional fields
export class Traffic extends RegionNode {}

// Subclass with one additional field
export class Tourism extends RegionNode {
  public lake: string = "Woerthersee"
}

export const graphql = {
  Query: {
    regions: (): RegionNode[] => [new Traffic(), new Tourism()]
  }
}

Expected behavior

Based on the inheritance test, Pylon should generate:

interface IRegionNode {
  id: String!
  name: String!
  type: String!
  gtfsImportId: Number!
  accumulatedDistance: Float!
  totalLines: Int!
  price: Float!
}

type Traffic implements IRegionNode {
  id: String!
  name: String!
  type: String!
  gtfsImportId: Number!
  accumulatedDistance: Float!
  totalLines: Int!
  price: Float!
}

type Tourism implements IRegionNode {
  id: String!
  name: String!
  type: String!
  gtfsImportId: Number!
  accumulatedDistance: Float!
  totalLines: Int!
  price: Float!
  lake: String!
}

And both Traffic and Tourism should correctly expose all inherited fields at runtime.

Actual behavior

Build succeeds with the correct schema generation. But at runtime, the first GraphQL request fails with:

Error: Interface field IRegionNode.id expected but Tourism does not provide it.

Interface field IRegionNode.gtfsImportId expected but Tourism does not provide it.

Interface field IRegionNode.type expected but Tourism does not provide it.

Interface field IRegionNode.accumulatedDistance expected but Tourism does not provide it.

Interface field IRegionNode.totalLines expected but Tourism does not provide it.

Interface field IRegionNode.price expected but Tourism does not provide it.

Interface field IRegionNode.routes expected but Tourism does not provide it.

Interface field IRegionNode.id expected but Traffic does not provide it.

Interface field IRegionNode.gtfsImportId expected but Traffic does not provide it.

[...same for all inherited fields on Traffic...]

This means the runtime resolver map does not include the inherited fields for the subclass types, even though the build-time schema correctly lists them.

Environment

  • @getcronit/pylon: 3.0.0-canary-20260206115650
  • @getcronit/pylon-dev: 2.0.0-canary-20260206115650
  • Runtime: Cloudflare Workers
  • Node.js: 22.x

Workaround

Currently we duplicate all shared methods in both Traffic and Tourism classes using implements on a shared TypeScript interface instead of extends:

interface RegionNodeBase { /* shared shape */ }
class Traffic implements RegionNodeBase { /* ~530 lines of methods */ }
class Tourism implements RegionNodeBase { /* ~530 lines of identical methods */ }
type RegionNode = Traffic | Tourism

This works but results in ~500 lines of duplicated code.

Root Cause Hypothesis

The build-time schema analysis (TypeScript AST) correctly resolves inherited fields via extends. But the runtime resolver map generation appears to only register fields that are directly declared on each class, not fields inherited from a parent class. The resolver map needs to walk the prototype chain or class hierarchy to include inherited fields.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions