diff --git a/packages/batch-execute/src/mergeRequests.ts b/packages/batch-execute/src/mergeRequests.ts index 354d6b52751..a86bfad0d39 100644 --- a/packages/batch-execute/src/mergeRequests.ts +++ b/packages/batch-execute/src/mergeRequests.ts @@ -118,12 +118,18 @@ function prefixRequest(prefix: string, request: ExecutionRequest): ExecutionRequ let prefixedDocument = aliasTopLevelFields(prefix, request.document); const executionVariableNames = Object.keys(executionVariables); + const hasFragmentDefinitions = request.document.definitions.some(def => isFragmentDefinition(def)); + const fragmentSpreadImpl: Record = {}; - if (executionVariableNames.length > 0) { + if (executionVariableNames.length > 0 || hasFragmentDefinitions) { prefixedDocument = visit(prefixedDocument, { [Kind.VARIABLE]: prefixNode, [Kind.FRAGMENT_DEFINITION]: prefixNode, - [Kind.FRAGMENT_SPREAD]: prefixNode, + [Kind.FRAGMENT_SPREAD]: node => { + node = prefixNodeName(node, prefix); + fragmentSpreadImpl[node.name.value] = true; + return node; + }, }) as DocumentNode; } @@ -133,6 +139,15 @@ function prefixRequest(prefix: string, request: ExecutionRequest): ExecutionRequ prefixedVariables[prefix + variableName] = executionVariables[variableName]; } + if (hasFragmentDefinitions) { + prefixedDocument = { + ...prefixedDocument, + definitions: prefixedDocument.definitions.filter(def => { + return !isFragmentDefinition(def) || fragmentSpreadImpl[def.name.value]; + }), + }; + } + return { document: prefixedDocument, variables: prefixedVariables, diff --git a/packages/batch-execute/tests/batchExecute.test.ts b/packages/batch-execute/tests/batchExecute.test.ts index 99d8734a81d..aaa117da1ad 100644 --- a/packages/batch-execute/tests/batchExecute.test.ts +++ b/packages/batch-execute/tests/batchExecute.test.ts @@ -21,6 +21,10 @@ describe('batch execution', () => { field2: String field3(input: String): String boom(message: String): String + widget: Widget + } + type Widget { + name: String } `, resolvers: { @@ -29,6 +33,7 @@ describe('batch execution', () => { field2: () => '2', field3: (_root, { input }) => String(input), boom: (_root, { message }) => new Error(message), + widget: () => ({ name: 'wingnut' }), }, }, }); @@ -111,6 +116,33 @@ describe('batch execution', () => { expect(executorCalls).toEqual(1); }); + it('renames fragment definitions and spreads', async () => { + const [first, second] = await Promise.all([ + batchExec({ document: parse('fragment A on Widget { name } query{ widget { ...A } }') }), + batchExec({ document: parse('fragment A on Widget { name } query{ widget { ...A } }') }), + ]) as ExecutionResult[]; + + const squishedDoc = executorDocument?.replace(/\s+/g, ' '); + expect(squishedDoc).toMatch('_0_widget: widget { ..._0_A }'); + expect(squishedDoc).toMatch('_1_widget: widget { ..._1_A }'); + expect(squishedDoc).toMatch('fragment _0_A on Widget'); + expect(squishedDoc).toMatch('fragment _1_A on Widget'); + expect(first?.data).toEqual({ widget: { name: 'wingnut' } }); + expect(second?.data).toEqual({ widget: { name: 'wingnut' } }); + expect(executorCalls).toEqual(1); + }); + + it('removes expanded root fragment definitions', async () => { + const [first, second] = await Promise.all([ + batchExec({ document: parse('fragment A on Query { field1 } query{ ...A }') }), + batchExec({ document: parse('fragment A on Query { field2 } query{ ...A }') }), + ]) as ExecutionResult[]; + + expect(first?.data).toEqual({ field1: '1' }); + expect(second?.data).toEqual({ field2: '2' }); + expect(executorCalls).toEqual(1); + }); + it('preserves pathed errors in the final result', async () => { const [first, second] = await Promise.all([ batchExec({ document: parse('{ first: boom(message: "first error") }') }),