Skip to content

fix: clone fields array in buildClassMetadata to avoid dual-type leakage - #1816

Open
raphyabak wants to merge 1 commit into
MichalLytek:masterfrom
raphyabak:fix/dual-type-shared-fields-array
Open

fix: clone fields array in buildClassMetadata to avoid dual-type leakage#1816
raphyabak wants to merge 1 commit into
MichalLytek:masterfrom
raphyabak:fix/dual-type-shared-fields-array

Conversation

@raphyabak

Copy link
Copy Markdown

Summary

Fixes #1810

MetadataStorage.buildClassMetadata() looks up the cached fields array for a class definition's target and assigns it directly to def.fields by reference:

const fields = this.fieldsCache.get(def.target) || [];
fields.forEach(field => { /* ... */ });
def.fields = fields;

A class decorated with both @ObjectType() and @InputType() shares a single target, so buildClassMetadata() is called once for the object-type definition and once for the input-type definition, and both end up with def.fields pointing at the exact same array instance.

When a @FieldResolver() is registered for that class, buildFieldResolverMetadata() pushes a new field onto the object type's fields array. Because the input type's fields array is the same reference, that push leaks into the input type too — and buildSchema() then throws CannotDetermineGraphQLTypeError while trying to resolve the field-resolver-added field as an input type field.

Fix

Clone the array before assigning it to def.fields, so each class metadata definition gets its own independent array:

def.fields = [...fields];

Test plan

  • Added a regression test in tests/functional/metadata-storage.ts reproducing the exact dual-type + @FieldResolver scenario from the issue. Confirmed it fails with the reported CannotDetermineGraphQLTypeError against the unpatched code, and passes with the fix.
  • Ran the full test suite (npx jest): 30 suites, 490 tests (489 previously existing + 1 new), all passing.
  • npx eslint and npx prettier --check on the changed files: clean.
  • npx tsc --project ./tests/tsconfig.json --noEmit: clean.

A class decorated with both @ObjectType() and @inputType() shares a
single `target`, so buildClassMetadata() looked up the same cached
fields array for both class metadata definitions and assigned it by
reference to def.fields on each one.

When a @FieldResolver() adds a field to the object type's fields
array (via buildFieldResolverMetadata's push), that mutation leaked
into the input type's fields array too, since both definitions
pointed at the exact same array. This caused buildSchema() to throw
CannotDetermineGraphQLTypeError for the field-resolver-added field
when generating the input type.

Fix by cloning the array with a spread before assigning it, so each
definition gets its own independent fields array.

Fixes MichalLytek#1810
@raphyabak
raphyabak requested a review from MichalLytek as a code owner August 28, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: FieldResolver metadata leaks into InputType for dual-type classes (shared fields array reference 2.0.0-rc.4)

1 participant