diff --git a/.changeset/config.json b/.changeset/config.json index e74f0c5a959..98990090231 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -10,5 +10,8 @@ "useCalculatedVersion": true, "prereleaseTemplate": "{tag}-{datetime}-{commit}" }, + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "updateInternalDependents": "always" + }, "ignore": ["website"] } diff --git a/.changeset/serious-pants-shave.md b/.changeset/serious-pants-shave.md new file mode 100644 index 00000000000..f61f5174b42 --- /dev/null +++ b/.changeset/serious-pants-shave.md @@ -0,0 +1,5 @@ +--- +'@graphql-tools/stitch': patch +--- + +Isolate computed fields return objects only if not referenced from other, non-isolated, objects diff --git a/packages/stitch/src/subschemaConfigTransforms/isolateComputedFieldsTransformer.ts b/packages/stitch/src/subschemaConfigTransforms/isolateComputedFieldsTransformer.ts index 64618a21cf6..fb06891fb9a 100644 --- a/packages/stitch/src/subschemaConfigTransforms/isolateComputedFieldsTransformer.ts +++ b/packages/stitch/src/subschemaConfigTransforms/isolateComputedFieldsTransformer.ts @@ -91,6 +91,17 @@ export function isolateComputedFieldsTransformer( for (const type of returnTypes) { const returnTypeMergeConfig = subschemaConfig.merge[type.name]; + // isolate the object type only if it's not accessible from other, non-isolated, objects' fields + if ( + Object.values(subschemaConfig.schema.getTypeMap()) + .filter(isObjectType) // only objects + .filter(t => t !== type) // not this type + .filter(t => !isolatedSchemaTypes[t.name]) // not an isolated type + .find(t => Object.values(t.getFields()).find(f => getNamedType(f.type) === type)) // has a field returning this type + ) { + continue; + } + if (isObjectType(type)) { const returnTypeSelectionSet = returnTypeMergeConfig?.selectionSet; if (returnTypeSelectionSet) { diff --git a/packages/stitch/tests/isolateComputedFieldsTransformer.test.ts b/packages/stitch/tests/isolateComputedFieldsTransformer.test.ts index c3cb311699d..bd45afb2eaa 100644 --- a/packages/stitch/tests/isolateComputedFieldsTransformer.test.ts +++ b/packages/stitch/tests/isolateComputedFieldsTransformer.test.ts @@ -470,6 +470,94 @@ type Mutation { }" `); }); + it.each([ + { + name: 'type', + variant: /* GraphQL */ ` + type SomeTypeWithDisappearingField { + otherField: String + disappearingField: SomeRequiredType + } + `, + }, + { + name: 'required type', + variant: /* GraphQL */ ` + type SomeTypeWithDisappearingField { + otherField: String + disappearingField: SomeRequiredType! + } + `, + }, + { + name: 'array', + variant: /* GraphQL */ ` + type SomeTypeWithDisappearingField { + otherField: String + disappearingField: [SomeRequiredType] + } + `, + }, + { + name: 'required array', + variant: /* GraphQL */ ` + type SomeTypeWithDisappearingField { + otherField: String + disappearingField: [SomeRequiredType]! + } + `, + }, + { + name: 'required array and items', + variant: /* GraphQL */ ` + type SomeTypeWithDisappearingField { + otherField: String + disappearingField: [SomeRequiredType!]! + } + `, + }, + ])('does not isolate $name referenced from other fields', async ({ variant }) => { + const [baseConfig, computedConfig] = isolateComputedFieldsTransformer({ + schema: makeExecutableSchema({ + typeDefs: /* GraphQL */ ` + scalar _Any + + union _Entity = User + + type Query { + someResolver: SomeTypeWithDisappearingField + _entities(representations: [_Any!]!): _Entity + } + + type SomeRequiredType { + id: String + } + + ${variant} + + type User { + id: ID! + requiresField: SomeRequiredType + } + `, + }), + merge: { + User: { + selectionSet: '{ id }', + fields: { + requiresField: { selectionSet: '{ externalField }', computed: true }, + }, + fieldName: '_entities', + }, + }, + }); + + assertSome(baseConfig.merge); + expect(baseConfig.merge['SomeRequiredType']).toBeUndefined(); + + assertSome(computedConfig.merge); + expect(computedConfig.merge['SomeRequiredType']).toBeUndefined(); + }); }); describe('with multiple entryPoints', () => {