Skip to content
Draft
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
10 changes: 10 additions & 0 deletions .changeset/nine-words-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@graphql-hive/gateway-plugin-console-sdk': minor
'@graphql-hive/external-composition': minor
'@graphql-hive/envelop': minor
'@graphql-hive/apollo': minor
'@graphql-hive/core': minor
'@graphql-hive/yoga': minor
---

Add support for graphql `^17.0.0`.
10 changes: 10 additions & 0 deletions .changeset/nine-words-speaky.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@graphql-hive/gateway-plugin-console-sdk': minor
'@graphql-hive/external-composition': minor
'@graphql-hive/envelop': minor
'@graphql-hive/apollo': minor
'@graphql-hive/core': minor
'@graphql-hive/yoga': minor
---

**BREAKING** Drop support for graphql `^0.13.0` and `^14.0.0`.
3 changes: 3 additions & 0 deletions .github/workflows/tests-unit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ jobs:

- name: unit tests
run: pnpm test

- name: unit tests (GraphQL 16)
run: pnpm test --mode graphql-16
4 changes: 2 additions & 2 deletions integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@
"date-fns": "4.1.0",
"dockerode": "5.0.0",
"dotenv": "16.4.7",
"graphql": "16.9.0",
"graphql": "17.0.2",
"graphql-sse": "2.6.0",
"graphql-yoga": "5.22.0",
"human-id": "4.1.1",
"set-cookie-parser": "2.7.1",
"strip-ansi": "7.1.2",
"tslib": "2.8.1",
"vitest": "4.1.3",
"vitest": "4.1.11",
"zod": "3.25.76"
}
}
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"@graphql-inspector/cli": "7.0.0",
"@graphql-inspector/core": "8.0.0",
"@graphql-inspector/patch": "1.0.0",
"@graphql-tools/load": "8.1.16",
"@graphql-tools/load": "8.1.17",
"@manypkg/get-packages": "2.2.2",
"@parcel/watcher": "2.5.1",
"@playwright/test": "1.60.0",
Expand All @@ -96,7 +96,8 @@
"eslint": "8.57.1",
"eslint-plugin-better-tailwindcss": "^4.3.2",
"eslint-plugin-hive": "file:rules",
"graphql": "16.9.0",
"graphql": "17.0.2",
"graphql-16": "npm:graphql@16.14.2",
"jest-snapshot-serializer-raw": "2.0.0",
"pg": "8.13.1",
"prettier": "3.4.2",
Expand All @@ -109,7 +110,7 @@
"turbo": "2.9.14",
"typescript": "5.7.3",
"vite-tsconfig-paths": "5.1.4",
"vitest": "4.1.3"
"vitest": "4.1.11"
},
"pnpm": {
"overrides.esbuild": "To address CVE: https://github.com/graphql-hive/console/security/dependabot/259",
Expand Down Expand Up @@ -182,7 +183,8 @@
"sharp@<0.35.0": "0.35.0",
"body-parser@1.x.x": "1.20.6",
"prettier-plugin-sh": "0.19.0",
"pacote@<21.5.1": "^21.5.1"
"pacote@<21.5.1": "^21.5.1",
"@vitest/mocker>msw": "-"
},
"patchedDependencies": {
"mjml-core@4.14.0": "patches/mjml-core@4.14.0.patch",
Expand Down
2 changes: 1 addition & 1 deletion packages/internal/postgres/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
},
"devDependencies": {
"@hive/service-common": "workspace:*",
"vitest": "4.1.3"
"vitest": "4.1.11"
}
}
8 changes: 4 additions & 4 deletions packages/libraries/apollo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
},
"peerDependencies": {
"@apollo/server": "^4.0.0 || ^5.0.0",
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
"graphql": "^15.0.0 || ^16.0.0 || ^17.0.0"
},
"dependencies": {
"@graphql-hive/core": "workspace:*",
Expand All @@ -53,14 +53,14 @@
"devDependencies": {
"@apollo/server": "5.5.0",
"@as-integrations/express4": "1.1.2",
"@graphql-tools/schema": "10.1.0",
"@graphql-tools/schema": "10.1.1",
"@types/express": "4.17.21",
"body-parser": "2.3.0",
"express": "4.21.2",
"graphql": "16.9.0",
"graphql": "17.0.2",
"graphql-ws": "5.16.1",
"nock": "14.0.10",
"vitest": "4.1.3",
"vitest": "4.1.11",
"ws": "8.21.0"
},
"publishConfig": {
Expand Down
16 changes: 8 additions & 8 deletions packages/libraries/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@
"dependencies": {
"@graphql-hive/core": "workspace:*",
"@graphql-inspector/core": "^8.0.0",
"@graphql-tools/code-file-loader": "^8.1.37",
"@graphql-tools/graphql-file-loader": "^8.1.19",
"@graphql-tools/json-file-loader": "^8.0.33",
"@graphql-tools/load": "^8.1.16",
"@graphql-tools/url-loader": "^9.1.7",
"@graphql-tools/utils": "12.0.0",
"@graphql-tools/code-file-loader": "^8.1.38",
"@graphql-tools/graphql-file-loader": "^8.1.20",
"@graphql-tools/json-file-loader": "^8.0.34",
"@graphql-tools/load": "^8.1.17",
"@graphql-tools/url-loader": "^9.1.8",
"@graphql-tools/utils": "12.0.1",
"@graphql-typed-document-node/core": "3.2.0",
"@oclif/core": "3.26.6",
"@oclif/plugin-help": "6.2.36",
Expand All @@ -64,14 +64,14 @@
"cli-table3": "0.6.5",
"colors": "1.4.0",
"env-ci": "7.3.0",
"graphql": "^16.8.1",
"graphql": "^17.0.2",
"graphql-tag": "^2.12.6",
"tslib": "^2.8.1",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/env-ci": "3.1.4",
"graphql": "16.9.0",
"graphql": "17.0.2",
"oclif": "4.22.65",
"rimraf": "6.1.3",
"tsx": "4.19.2",
Expand Down
8 changes: 4 additions & 4 deletions packages/libraries/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
"graphql": "^15.0.0 || ^16.0.0 || ^17.0.0"
},
"dependencies": {
"@graphql-hive/logger": "^1.1.0",
"@graphql-hive/signal": "^2.0.0",
"@graphql-tools/utils": "^12.0.0",
"@graphql-tools/utils": "^12.0.1",
"@whatwg-node/fetch": "^0.10.13",
"async-retry": "^1.3.3",
"events": "^3.3.0",
Expand All @@ -63,10 +63,10 @@
"@types/async-retry": "1.4.8",
"@types/js-md5": "0.8.0",
"@types/lodash.sortby": "4.7.9",
"graphql": "16.9.0",
"graphql": "17.0.2",
"nock": "14.0.10",
"tslib": "2.8.1",
"vitest": "4.1.3"
"vitest": "4.1.11"
},
"publishConfig": {
"access": "public",
Expand Down
2 changes: 1 addition & 1 deletion packages/libraries/core/src/client/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export function createAgent<TEvent>(
const promise = captureAsync(event);
inProgressCaptures.push(promise);
void promise
.catch(e => logger.error('Failed to capture async event (error=%o)', e))
.catch(e => logger.error('Failed to capture async event (error=%s)', e))
.finally(() => {
inProgressCaptures = inProgressCaptures.filter(p => p !== promise);
});
Expand Down
40 changes: 38 additions & 2 deletions packages/libraries/core/src/client/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DocumentNode, GraphQLError, GraphQLSchema } from 'graphql';
import { versionInfo, type DocumentNode, type GraphQLError, type GraphQLSchema } from 'graphql';
import { Attributes, Logger } from '@graphql-hive/logger';
import { crypto, TextEncoder } from '@whatwg-node/fetch';
import { hiveClientSymbol } from './client.js';
Expand Down Expand Up @@ -91,8 +91,44 @@ export function cache<R, A, K, V>(
};
}

/**
* GraphQL 17 started omitting empty arrays within the Document AST.
* In order to retain the hash, we backfill these.
*/
const cacheDocumentReplacer = (_: any, value: any) => {
if (!value || typeof value !== 'object') {
return value;
}

switch (value.kind) {
case 'OperationDefinition':
return {
...value,
variableDefinitions: value.variableDefinitions ?? [],
directives: value.directives ?? [],
};
case 'VariableDefinition':
case 'FragmentSpread':
case 'InlineFragment':
case 'FragmentDefinition':
return { ...value, directives: value.directives ?? [] };
case 'Field':
return {
...value,
arguments: value.arguments ?? [],
directives: value.directives ?? [],
};
case 'Directive':
return { ...value, arguments: value.arguments ?? [] };
default:
return value;
}
};
Comment on lines +94 to +126

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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


export async function cacheDocumentKey<T, V>(doc: T, variables: V | null) {
const hasher = createHash('SHA-1').update(JSON.stringify(doc));
const hasher = createHash('SHA-1').update(
JSON.stringify(doc, versionInfo.major >= 17 ? cacheDocumentReplacer : undefined),
);

if (variables) {
hasher.update(
Expand Down
2 changes: 1 addition & 1 deletion packages/libraries/core/tests/client/agent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('createAgent', () => {
agent.capture(rejectedPromise);
await expect(agent.dispose()).resolves.not.toThrow(); // wait for the capture to be resolved/rejected
expect(mockLogger.getLogs()).toEqual(
expect.stringContaining('[ERR] Failed to capture async event (error={})'),
expect.stringContaining('[ERR] Failed to capture async event (error=Error: Testing)'),
);
process.off('unhandledRejection', unhandledRejectionSpy);
expect(unhandledRejectionSpy).not.toHaveBeenCalled();
Expand Down
52 changes: 51 additions & 1 deletion packages/libraries/core/tests/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,54 @@
import { cacheDocumentKey } from '../src/client/utils';
import { parse } from 'graphql';
import { cacheDocumentKey, createHash } from '../src/client/utils';

test('produce identical hash when absent AST lists are empty arrays or undefined', async () => {
const document = parse('query Test { viewer @cached }', { noLocation: true });
const operation = document.definitions[0];

if (operation.kind !== 'OperationDefinition') {
throw new Error('Expected an operation definition');
}

const field = operation.selectionSet.selections[0];
if (field.kind !== 'Field') {
throw new Error('Expected a field');
}

const directive = field.directives?.[0];
if (!directive) {
throw new Error('Expected a directive');
}

const graphql16Document = {
...document,
definitions: [
{
...operation,
variableDefinitions: [],
directives: [],
selectionSet: {
...operation.selectionSet,
selections: [
{
...field,
arguments: [],
directives: [{ ...directive, arguments: [] }],
},
],
},
},
],
};

const left = await cacheDocumentKey(document, null);
const right = await cacheDocumentKey(graphql16Document, null);
const previousHash = await createHash('SHA-1')
.update(JSON.stringify(graphql16Document))
.digest('hex');

expect(left).toEqual(right);
expect(right).toEqual(previousHash);
});

test('produce identical hash for the same document and the same keys but different values in variables', async () => {
const left = await cacheDocumentKey('doc', { a: true });
Expand Down
2 changes: 1 addition & 1 deletion packages/libraries/envelop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
},
"peerDependencies": {
"@envelop/types": "^5.0.0",
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
"graphql": "^15.0.0 || ^16.0.0 || ^17.0.0"
},
"dependencies": {
"@graphql-hive/core": "workspace:*"
Expand Down
4 changes: 2 additions & 2 deletions packages/libraries/external-composition/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@
"example": "pnpm build && PORT=3012 SECRET=secretsecret node dist/example.js"
},
"peerDependencies": {
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
"graphql": "^16.0.0 || ^17.0.0"
},
"devDependencies": {
"@apollo/composition": "2.14.0",
"@types/node": "24.13.3",
"esbuild": "0.28.1",
"fastify": "5.12.3",
"graphql": "16.9.0"
"graphql": "17.0.2"
},
"publishConfig": {
"access": "public",
Expand Down
2 changes: 1 addition & 1 deletion packages/libraries/gateway-plugin-console-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"peerDependencies": {
"@envelop/types": "^5.0.0",
"@graphql-hive/gateway-runtime": "^1.0.0 || ^2.0.0",
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
"graphql": "^15.0.0 || ^16.0.0 || ^17.0.0"
},
"dependencies": {
"@graphql-hive/core": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions packages/libraries/laboratory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
},
"dependencies": {
"@base-ui/react": "^1.1.0",
"@graphql-tools/url-loader": "^9.1.7",
"@graphql-tools/url-loader": "^9.1.8",
"dompurify": "3.4.13",
"graphql-language-service": "^5.5.0",
"radix-ui": "^1.4.3",
Expand Down Expand Up @@ -107,7 +107,7 @@
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.26",
"globals": "^16.5.0",
"graphql": "^16.14.0",
"graphql": "^17.0.2",
"graphql-ws": "6.0.6",
"graphql-yoga": "5.22.0",
"happy-dom": "^20.10.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/libraries/pubsub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
},
"devDependencies": {
"tslib": "2.8.1",
"vitest": "4.1.3"
"vitest": "4.1.11"
},
"sideEffects": false,
"typescript": {
Expand Down
6 changes: 3 additions & 3 deletions packages/libraries/yoga/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"check:build": "bob check"
},
"peerDependencies": {
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
"graphql": "^15.0.0 || ^16.0.0 || ^17.0.0",
"graphql-yoga": "^5.10.8"
},
"dependencies": {
Expand All @@ -52,15 +52,15 @@
"@graphql-yoga/plugin-persisted-operations": "^3.22.0"
},
"devDependencies": {
"@graphql-tools/schema": "10.1.0",
"@graphql-tools/schema": "10.1.1",
"@graphql-yoga/plugin-defer-stream": "3.22.0",
"@graphql-yoga/plugin-disable-introspection": "2.23.0",
"@graphql-yoga/plugin-graphql-sse": "3.22.0",
"@graphql-yoga/plugin-response-cache": "3.24.0",
"@whatwg-node/fetch": "0.10.13",
"graphql-ws": "5.16.1",
"graphql-yoga": "5.22.0",
"vitest": "4.1.3",
"vitest": "4.1.11",
"ws": "8.21.0"
},
"publishConfig": {
Expand Down
Loading
Loading