-
-
Notifications
You must be signed in to change notification settings - Fork 12
Description
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 | TourismThis 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.