Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fast-parrots-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/transport-http': patch
---

Fix an infinite loop when providing options for subscriptions transport of subgraphs using HTTP transport for both queries/mutations and subscrpitions.
3 changes: 3 additions & 0 deletions packages/transports/http/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ export default {
options: {
...payload.transportEntry.options,
...payload.transportEntry.options?.subscriptions?.options,
// Make sure to remove subscription option to avoid infinite loop.
// This option doesn't make sense here but is present in `transportEntry.options`
subscriptions: undefined,
},
Comment on lines 107 to 113
Copy link
Preview

Copilot AI Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Instead of setting subscriptions: undefined, consider using object destructuring with rest operator to exclude the subscriptions property entirely. This would be more explicit: const { subscriptions, ...optionsWithoutSubscriptions } = payload.transportEntry.options; options: { ...optionsWithoutSubscriptions, ...payload.transportEntry.options?.subscriptions?.options }

Suggested change
options: {
...payload.transportEntry.options,
...payload.transportEntry.options?.subscriptions?.options,
// Make sure to remove subscription option to avoid infinite loop.
// This option doesn't make sense here but is present in `transportEntry.options`
subscriptions: undefined,
},
options: (() => {
const { subscriptions, ...optionsWithoutSubscriptions } = payload.transportEntry.options ?? {};
return {
...optionsWithoutSubscriptions,
...payload.transportEntry.options?.subscriptions?.options,
};
})(),

Copilot uses AI. Check for mistakes.

}),
(resolvedSubscriptionsExecutor) => {
Expand Down
80 changes: 64 additions & 16 deletions packages/transports/http/tests/http.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TransportEntry } from '@graphql-mesh/transport-common';
import type { MeshFetch } from '@graphql-mesh/types';
import { buildSchema, parse } from 'graphql';
import { buildSchema, OperationTypeNode, parse } from 'graphql';
import { describe, expect, it, vi } from 'vitest';
import httpTransport from '../src';

Expand All @@ -14,22 +15,25 @@ describe('HTTP Transport', () => {
}),
);
const expectedToken = 'wowmuchsecret';
const executor = httpTransport.getSubgraphExecutor({
subgraphName,
transportEntry: {
kind: 'http',
subgraph: subgraphName,
headers: [['x-test', '{context.myToken}']],
},
fetch,
// @ts-expect-error - transport kind is const in httpTransport but string is expected
transportExecutorFactoryGetter: () => httpTransport.getSubgraphExecutor,
subgraph: buildSchema(/* GraphQL */ `
type Query {
test: String
}
`),
const getTransportExecutor = (transportEntry: TransportEntry) =>
httpTransport.getSubgraphExecutor({
subgraphName,
transportEntry,
fetch,
getTransportExecutor,
subgraph: buildSchema(/* GraphQL */ `
type Query {
test: String
}
`),
});

const executor = getTransportExecutor({
kind: 'http',
subgraph: subgraphName,
headers: [['x-test', '{context.myToken}']],
});

await executor({
document: parse(/* GraphQL */ `
query {
Expand All @@ -46,4 +50,48 @@ describe('HTTP Transport', () => {
},
});
});

it('should allow to specify subscription specific options', async () => {
const fetch = vi.fn<MeshFetch>();

const getTransportExecutor = (transportEntry: TransportEntry) =>
httpTransport.getSubgraphExecutor({
subgraphName,
transportEntry,
fetch,
getTransportExecutor,
subgraph: buildSchema(/* GraphQL */ `
type Subscription {
test: String
}
`),
});

const executor = getTransportExecutor({
kind: 'http',
subgraph: subgraphName,
options: {
subscriptions: {
kind: 'http',
subgraph: subgraphName,
options: {
method: 'POST',
},
},
},
});

await executor({
operationType: OperationTypeNode.SUBSCRIPTION,
document: parse(/* GraphQL */ `
subscription {
test
}
`),
});

expect(fetch.mock.calls[0]?.[1]).toMatchObject({
method: 'POST',
});
});
});
Loading