Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(runtime): compare the results from factory functions correctly #420

Merged
merged 32 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
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
7 changes: 7 additions & 0 deletions .changeset/@graphql-hive_gateway-runtime-420-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@graphql-hive/gateway-runtime': patch
---

dependencies updates:

- Added dependency [`@graphql-tools/executor-common@workspace:^` ↗︎](https://www.npmjs.com/package/@graphql-tools/executor-common/v/workspace:^) (to `dependencies`)
7 changes: 7 additions & 0 deletions .changeset/@graphql-mesh_fusion-runtime-420-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@graphql-mesh/fusion-runtime': patch
---

dependencies updates:

- Updated dependency [`@envelop/core@^5.0.3` ↗︎](https://www.npmjs.com/package/@envelop/core/v/5.0.3) (from `^5.0.1`, in `dependencies`)
7 changes: 7 additions & 0 deletions .changeset/@graphql-mesh_transport-common-420-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@graphql-mesh/transport-common': patch
---

dependencies updates:

- Updated dependency [`@envelop/core@^5.0.3` ↗︎](https://www.npmjs.com/package/@envelop/core/v/5.0.3) (from `^5.0.1`, in `dependencies`)
14 changes: 14 additions & 0 deletions .changeset/twelve-ladybugs-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@graphql-mesh/transport-http-callback': patch
'@graphql-mesh/transport-common': patch
'@graphql-mesh/transport-http': patch
'@graphql-tools/executor-http': patch
'@graphql-mesh/fusion-runtime': patch
'@graphql-hive/gateway-runtime': patch
---

- In case of schema reload, throw `SCHEMA_RELOAD` error while recreating the transports and executors
- In case of shut down, throw `SHUTTING_DOWN` error while cleaning the transports and executors up

Previously, these errors are only thrown for subscriptions not it is thrown in other type of operations as well.
And previously the thrown errors during these two cleanup and restart process were cryptic, now the mentioned two errors above are thrown with more clear messages
6 changes: 6 additions & 0 deletions .changeset/unlucky-yaks-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-mesh/fusion-runtime': patch
'@graphql-hive/gateway-runtime': patch
---

Leave the supergraph configuration handling logic to fusion-runtime package so it can compare bare read supergraph sdl directly inside unified graph manager to decide if the supergraph has changed.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ it('should allow "me" when authenticated', async () => {
with: 'mesh',
services: [await service('users')],
},
pipeLogs: 'gw.log',
});

await expect(
Expand Down
12 changes: 12 additions & 0 deletions e2e/polling/gateway.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import fs from 'fs';
import { defineConfig } from '@graphql-hive/gateway';

const SUPERGRAPH_PATH = process.env['SUPERGRAPH_PATH'] || 'supergraph.graphql';

export const gatewayConfig = defineConfig({
supergraph: async (): Promise<string> => {
console.log(`[${new Date().toISOString()}]`, 'Reading ' + SUPERGRAPH_PATH);
return fs.promises.readFile(SUPERGRAPH_PATH, 'utf8');
},
pollingInterval: 5_000,
});
22 changes: 22 additions & 0 deletions e2e/polling/mesh.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
defineConfig,
loadGraphQLHTTPSubgraph,
} from '@graphql-mesh/compose-cli';
import { Opts } from '@internal/testing';

const opts = Opts(process.argv);
const SERVICE_PORT = opts.getServicePort('Graph');

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGraphQLHTTPSubgraph('Graph', {
endpoint: `http://localhost:${SERVICE_PORT}/graphql`,
source: './services/Graph.graphql',
operationHeaders: {
Authorization: "{context.headers['authorization']}",
},
}),
},
],
});
9 changes: 9 additions & 0 deletions e2e/polling/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@e2e/polling",
"private": true,
"dependencies": {
"@graphql-mesh/compose-cli": "^1.2.13",
"express-graphql": "^0.12.0",
"graphql": "^16.10.0"
}
}
43 changes: 43 additions & 0 deletions e2e/polling/polling.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createTenv, handleDockerHostName } from '@internal/e2e';
import { describe, expect, it } from 'vitest';

describe('Polling', async () => {
const { service, gateway, composeWithMesh, gatewayRunner } =
createTenv(__dirname);
let { output } = await composeWithMesh({
services: [await service('Graph')],
output: 'graphql',
});
const volumes: {
host: string;
container: string;
}[] = [];
if (gatewayRunner.includes('docker')) {
output = await handleDockerHostName(output, volumes);
}
const gw = await gateway({
args: ['supergraph'],
env: {
SUPERGRAPH_PATH: output,
},
runner: {
docker: {
volumes,
},
},
});
it('should not break the long running query while polling and schema remaining the same', async () => {
const res = await gw.execute({
query: /* GraphQL */ `
query {
hello
}
`,
});
expect(res).toEqual({
data: {
hello: 'Hello world!',
},
});
}, 30_000);
});
3 changes: 3 additions & 0 deletions e2e/polling/services/Graph.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type Query {
hello: String
}
36 changes: 36 additions & 0 deletions e2e/polling/services/Graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import fs from 'fs';
import path from 'path';
import { Opts } from '@internal/testing';
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';

const app = express();
const opts = Opts(process.argv);
const port = opts.getServicePort('Graph');

const schemaPath = path.join(__dirname, 'Graph.graphql');
const schemaContent = fs.readFileSync(schemaPath, 'utf8');

const root = {
hello: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Hello world!');
}, 20_000);
});
},
};

app.use(
'/graphql',
graphqlHTTP({
schema: buildSchema(schemaContent),
rootValue: root,
graphiql: true,
}),
);

app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}/graphql`);
});
2 changes: 1 addition & 1 deletion internal/e2e/src/tenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export const dockerHostName = boolEnv('CI')
? '172.17.0.1'
: 'host.docker.internal';

async function handleDockerHostName(
export async function handleDockerHostName(
supergraph: string,
volumes: {
host: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/executors/http/src/handleEventStreamResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function handleEventStreamResponse(
let currChunk = '';
async function pump() {
if (signal?.aborted) {
await push(createResultForAbort(signal));
await push(createResultForAbort(signal.reason));
return stop();
}
if (!body?.locked) {
Expand Down
9 changes: 6 additions & 3 deletions packages/executors/http/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { handleMultipartMixedResponse } from './handleMultipartMixedResponse.js'
import { isLiveQueryOperationDefinitionNode } from './isLiveQueryOperationDefinitionNode.js';
import { prepareGETUrl } from './prepareGETUrl.js';
import {
createAbortErrorReason,
createGraphQLErrorForAbort,
createResultForAbort,
hashSHA256,
Expand Down Expand Up @@ -125,6 +124,10 @@ export interface HTTPExecutorOptions {
* @deprecated The executors are always disposable, and this option will be removed in the next major version, there is no need to have a flag for this.
*/
disposable?: boolean;
/**
* On dispose abort error
*/
getDisposeReason?(): Error | undefined;
}

export type HeadersConfig = Record<string, string>;
Expand Down Expand Up @@ -514,14 +517,14 @@ export function buildHTTPExecutor(
[DisposableSymbols.dispose]: {
get() {
return function dispose() {
return disposeCtrl.abort(createAbortErrorReason());
return disposeCtrl.abort(options?.getDisposeReason?.());
};
},
},
[DisposableSymbols.asyncDispose]: {
get() {
return function asyncDispose() {
return disposeCtrl.abort(createAbortErrorReason());
return disposeCtrl.abort(options?.getDisposeReason?.());
};
},
},
Expand Down
11 changes: 9 additions & 2 deletions packages/executors/http/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ export function createGraphQLErrorForAbort(
}
if (reason?.name === 'TimeoutError') {
return createGraphQLError(reason.message, {
extensions,
extensions: {
http: {
status: 504,
...(extensions?.['http'] || {}),
},
code: 'TIMEOUT_ERROR',
...(extensions || {}),
},
originalError: reason,
});
}
return createGraphQLError('The operation was aborted. reason: ' + reason, {
return createGraphQLError(reason.message, {
extensions,
originalError: reason,
});
Expand Down
41 changes: 17 additions & 24 deletions packages/executors/http/tests/buildHTTPExecutor.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { setTimeout } from 'timers/promises';
import { createGraphQLError } from '@graphql-tools/utils';
import { assertAsyncIterable, createDisposableServer } from '@internal/testing';
import {
assertAsyncIterable,
assertSingleExecutionValue,
createDisposableServer,
} from '@internal/testing';
import { Repeater } from '@repeaterjs/repeater';
import { DisposableSymbols } from '@whatwg-node/disposablestack';
import { ReadableStream, Request, Response } from '@whatwg-node/fetch';
Expand Down Expand Up @@ -240,43 +243,33 @@ describe('buildHTTPExecutor', () => {
await using executor = buildHTTPExecutor({
endpoint: server.url,
});
const result = executor({
const result$ = executor({
document: parse(/* GraphQL */ `
query {
hello
}
`),
});
await executor[Symbol.asyncDispose]();
await expect(result).resolves.toMatchObject({
errors: [
{
message: expect.stringContaining('Executor was disposed'),
},
],
});
const result = await result$;
assertSingleExecutionValue(result);
expect(result?.errors?.[0]?.message).toContain('operation was aborted');
neverResolves.resolve(Response.error());
});
it('does not allow new requests when the executor is disposed', async () => {
await using executor = buildHTTPExecutor({
fetch: () => Response.json({ data: { hello: 'world' } }),
});
executor[DisposableSymbols.asyncDispose]?.();
expect(
executor({
document: parse(/* GraphQL */ `
query {
hello
}
`),
}),
).toMatchObject({
errors: [
createGraphQLError(
'The operation was aborted. reason: Error: Executor was disposed.',
),
],
const res = await executor({
document: parse(/* GraphQL */ `
query {
hello
}
`),
});
assertSingleExecutionValue(res);
expect(res?.errors?.[0]?.message).toContain('operation was aborted');
});
it('should return return GraphqlError instances', async () => {
await using executor = buildHTTPExecutor({
Expand Down
2 changes: 1 addition & 1 deletion packages/fusion-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
},
"dependencies": {
"@envelop/core": "^5.0.1",
"@envelop/core": "^5.0.3",
"@graphql-mesh/cross-helpers": "^0.4.9",
"@graphql-mesh/transport-common": "workspace:^",
"@graphql-mesh/types": "^0.103.6",
Expand Down
Loading
Loading