You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
1. The issue provides a reproduction available on Github, Stackblitz or CodeSandbox
Make sure to fork this template and run yarn generate in the terminal.
Please make sure the GraphQL Tools package versions under package.json matches yours.
2. A failing test has been provided
3. A local solution has been provided
4. A pull request is pending review
Describe the bug
We have the need to combine several remote schemas into one. We use stitching for this.
For the basis and understanding of the following code:
We need both http and ws functionalities and authentication.
We have a Graphql-Playground activated.
Basically, everything works, but if an error and data are returned from the remote schema, the error is no longer present in various constellations after the wrapSchema or shortly before, although the query from the remote schema does contain the error in between.
Stitching:
We stitch a local schema and many other remote schemas to one and use for this stichSchemas and wrapSchema.
Full code:
The most of the code is more described in the links above.
/** * Stitches the local schema and the schemas from remote APIs to one new schema. * @see https://the-guild.dev/graphql/stitching/docs/getting-started/remote-subschemas */privateasyncstitch(localSchema: GraphQLSchema): Promise<GraphQLSchema>{const remoteSchemas =awaitPromise.all(// remoteApis includes the URL to the servicesremoteApis().map(async(remoteApi: RemoteApi)=>this.createRemoteSchema(remoteApi)),);returnstitchSchemas({subschemas: [localSchema, ...remoteSchemas.filter(Boolean)],});}/** * Fetches the schema from a remote service and * wrap it with transformations (renaming) in name and type to a new schema. */privateasynccreateRemoteSchema(remoteApi: RemoteApi): Promise<GraphQLSchema>{try{consthttpExecutor: AsyncExecutor=async({ document, variables, operationName, extensions })=>{constquery=print(document);constfetchResult=awaitfetch(remoteApi.url,{method: 'POST',headers: {/** * @see https://chillicream.com/docs/hotchocolate/v13/migrating/migrate-from-12-to-13#http-transport */Accept: 'application/json',Authorization: this.authHeaderToken,'Content-Type': 'application/json; charset=utf-8',},body: JSON.stringify({ query, variables, operationName, extensions }),});returnfetchResult.json();};/** * @see https://the-guild.dev/graphql/stitching/docs/getting-started/remote-subschemas#create-a-hybrid-executor-to-use-ws-for-subscriptions * @see https://stackoverflow.com/questions/75987086/apollo-server-subscription-middleware-to-intercept-request-body-to-and-request-s */constsubscriptionClient=remoteApi.ws
? createClient({/** * The webSocketImpl is necessary to work. * * @see https://stackoverflow.com/questions/72116940/apollo-graphql-graphqlwslink-subscriptions-troubles-cannot-get-websocket-imp * @see https://the-guild.dev/graphql/ws/recipes#client-usage-in-node-with-custom-headers-not-possible-in-browsers */webSocketImpl: WebSocket,url: remoteApi.ws,lazyCloseTimeout: 50000,shouldRetry: ()=>true,connectionParams: async()=>{return{Authorization: this.authHeaderToken,Accept: 'application/json',};},lazy: true,/** * onNonLazyError is used if lazy is set to false */// eslint-disable-next-line @typescript-eslint/no-empty-functiononNonLazyError: ()=>{},on: {connected: ()=>{console.debug(`graphql-ws connected`);},error: err=>console.log(err),},})
: ({}asClient);constwsExecutor: AsyncExecutor=remoteApi.ws
? async({ document, variables, operationName, extensions })=>observableToAsyncIterable({subscribe: observer=>({unsubscribe: subscriptionClient.subscribe({query: print(document),variables: variablesasRecord<string, any>,
operationName,
extensions,},{next: data=>observer.next?.(dataasany),error(err){if(!observer.error)return;if(errinstanceofError){observer.error(err);}elseif(Array.isArray(err)){observer.error(newError(err.map(({ message })=>message).join(', ')));}else{observer.error(newError(`Socket closed with event: ${err}`));}},complete: ()=>observer.complete?.(),},),}),})
: ({}asAsyncExecutor);constexecutor: AsyncExecutor=asyncexecutorRequest=>{// subscription operations should be handled by the wsExecutorif(remoteApi.ws&&executorRequest.operationType==='subscription'){returnwsExecutor(executorRequest);}// all other operations should be handles by the httpExecutorreturnhttpExecutor(executorRequest);};returnwrapSchema({schema: awaitschemaFromExecutor(executor),executor: executor,transforms: [newRenameTypes(type=>remoteApi.prefix+type,{renameBuiltins: false,renameScalars: false}),newRenameRootFields((operation,name)=>remoteApi.prefix+name),],});}catch(error){this.logger.error(`failed connecting '${remoteApi.name}'`);returnerror;}}
While debugging and logging I can see on executing httpExecutor(executorRequest) that all data we need, error and data is given.
// all other operations should be handles by the httpExecutorconstdebug=httpExecutor(executorRequest);debug.then(result=>console.log(result));returnhttpExecutor(executorRequest);
Behavior A:
If an error is given and the data is filled with an element which is null, the result with error is given in httpExecutor and Graphql-Playground.
// logging from httpExecutor section{errors: [{message: 'Specified key "X" is already used.'}],data: {createProduct: null}}
// logging from Graphql-Playground"errors": [{"message": "Specified key \"X\" is already used.","path": ["createProduct"],
...
}],"data": {"createProduct": null}
Behavior B:
If and error is given and the data is filled with an element which is not null, the result with error is given in the httpExecutor only and not passed through to the Graphql-Playground or other clients connecting to the server.
// logging from httpExecutor section{
errors: [{message: 'Specified key "PO" is already used.'}]
data: { updateProduct: {id: 'a1e12327-6456-43df-b789-b4ab32d23012'}}
// logging from Graphql-Playground"data": {"updateProduct": {"id": "a1e12327-6456-43df-b789-b4ab32d23012"}}
So the question is, what happen in wrapSchema or schemaFromExecutor that the error is swallowing or not passing through?
Expected behavior
The error should always present even if data is given.
Environment:
OS: Linux/Windows
"@graphql-tools/stitch": "9.2.10":
"@graphql-tools/utils": "10.5.4",:
"@graphql-tools/wrap": "10.0.5",:
NodeJS: v20.15.1
Additional context
I found some other older issues (2020) related to this and some resolutions working with the problem but these all 4 years old and I hope there is another solution:
Issue workflow progress
Progress of the issue based on the
Contributor Workflow
Describe the bug
We have the need to combine several remote schemas into one. We use stitching for this.
For the basis and understanding of the following code:
We need both http and ws functionalities and authentication.
We have a Graphql-Playground activated.
Basically, everything works, but if an error and data are returned from the remote schema, the error is no longer present in various constellations after the wrapSchema or shortly before, although the query from the remote schema does contain the error in between.
To Reproduce Steps to reproduce the behavior:
The base:
To handle ws subscriptions and query the rest over http we use hybrid executor to use WS for subscriptions
Also we use Introspecting Schemas using Executors
Stitching:
We stitch a local schema and many other remote schemas to one and use for this
stichSchemas
andwrapSchema
.Full code:
The most of the code is more described in the links above.
While debugging and logging I can see on executing
httpExecutor(executorRequest)
that all data we need, error and data is given.Behavior A:
If an error is given and the data is filled with an element which is null, the result with error is given in httpExecutor and Graphql-Playground.
Behavior B:
If and error is given and the data is filled with an element which is not null, the result with error is given in the httpExecutor only and not passed through to the Graphql-Playground or other clients connecting to the server.
So the question is, what happen in
wrapSchema
orschemaFromExecutor
that the error is swallowing or not passing through?Expected behavior
The error should always present even if data is given.
Environment:
"@graphql-tools/stitch": "9.2.10"
:"@graphql-tools/utils": "10.5.4",
:"@graphql-tools/wrap": "10.0.5",
:Additional context
I found some other older issues (2020) related to this and some resolutions working with the problem but these all 4 years old and I hope there is another solution:
We tested now the
transformResult
and this seems to help but I don't think that should be necessary, right? Or is this the way do handle the error?The text was updated successfully, but these errors were encountered: