diff --git a/.changeset/wild-frogs-invite.md b/.changeset/wild-frogs-invite.md new file mode 100644 index 00000000000..0d85ba8c87b --- /dev/null +++ b/.changeset/wild-frogs-invite.md @@ -0,0 +1,19 @@ +--- +'graphql-language-service-server': minor +'@graphiql/plugin-code-exporter': minor +'graphql-language-service-cli': minor +'@graphiql/plugin-explorer': minor +'graphql-language-service': minor +'vscode-graphql-execution': minor +'codemirror-graphql': minor +'@graphiql/toolkit': minor +'@graphiql/react': minor +'monaco-graphql': minor +'vscode-graphql': minor +'cm6-graphql': minor +'graphiql': minor +--- + +Support v17 of `graphql-js` from `17.0.0-alpha.2` forward. + +Includes support for the latest incremental delivery response format. For further details, see https://github.com/graphql/defer-stream-wg/discussions/69. diff --git a/.github/workflows/pr-graphql-compat-check.yml b/.github/workflows/pr-graphql-compat-check.yml index c945e490d30..7643e219f43 100644 --- a/.github/workflows/pr-graphql-compat-check.yml +++ b/.github/workflows/pr-graphql-compat-check.yml @@ -23,7 +23,8 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - release: ['15.5.3', '^15.8.0', '16.1.0', '16.2.0', '16.3.0'] + release: + ['15.5.3', '^15.8.0', '16.1.0', '16.2.0', '16.3.0', '17.0.0-alpha.5'] steps: - name: Checkout Code uses: actions/checkout@v4 diff --git a/functions/graphql.ts b/functions/graphql.ts index c4ae5bd53b3..f1e5fe2dfa1 100644 --- a/functions/graphql.ts +++ b/functions/graphql.ts @@ -45,7 +45,7 @@ export function createHandler( statusCode: init.status, }; } catch (err) { - // The handler should'nt throw errors. + // The handler shouldn't throw errors. // If you wish to handle them differently, consider implementing your own request handler. console.error( 'Internal error occurred during request handling. ' + diff --git a/package.json b/package.json index 3250086fa90..2d38f54e708 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "gen-agenda": "wgutils agenda gen" }, "dependencies": { + "graphql-http": "^1.22.1", "@babel/cli": "^7.21.0", "@babel/core": "^7.21.0", "@babel/plugin-proposal-class-properties": "^7.18.6", @@ -143,6 +144,7 @@ "resolutions": { "@babel/traverse": "^7.23.2", "vscode-languageserver-types": "3.17.3", - "markdown-it": "14.1.0" + "markdown-it": "14.1.0", + "graphql": "17.0.0-alpha.5" } } diff --git a/packages/cm6-graphql/package.json b/packages/cm6-graphql/package.json index 74a3fb5832c..4522fa33196 100644 --- a/packages/cm6-graphql/package.json +++ b/packages/cm6-graphql/package.json @@ -30,7 +30,7 @@ "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.1.0", "esbuild": "0.18.10", - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "rollup": "^2.60.2", "rollup-plugin-dts": "^4.0.1", "rollup-plugin-esbuild": "^4.9.1", @@ -44,7 +44,7 @@ "@codemirror/state": "6.2.0", "@codemirror/view": "6.2.1", "@lezer/highlight": "^1.0.0", - "graphql": "^16.5.0" + "graphql": "^16.5.0 || ^17.0.0-alpha.2" }, "license": "MIT" } diff --git a/packages/codemirror-graphql/package.json b/packages/codemirror-graphql/package.json index 23b29225446..413a0ee36c9 100644 --- a/packages/codemirror-graphql/package.json +++ b/packages/codemirror-graphql/package.json @@ -40,7 +40,7 @@ "peerDependencies": { "@codemirror/language": "6.0.0", "codemirror": "^5.65.3", - "graphql": "^15.5.0 || ^16.0.0" + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2" }, "// TEMPORARILY PINNED until we fix graphql 15 support": "", "dependencies": { @@ -51,7 +51,7 @@ "@codemirror/language": "^6.0.0", "codemirror": "^5.65.3", "cross-env": "^7.0.2", - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "rimraf": "^3.0.2", "sane": "2.0.0" } diff --git a/packages/codemirror-graphql/src/__tests__/lint-test.ts b/packages/codemirror-graphql/src/__tests__/lint-test.ts index 0df6a8a3c9e..e603873dd9b 100644 --- a/packages/codemirror-graphql/src/__tests__/lint-test.ts +++ b/packages/codemirror-graphql/src/__tests__/lint-test.ts @@ -11,7 +11,7 @@ import CodeMirror from 'codemirror'; import 'codemirror/addon/lint/lint'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; -import { GraphQLError, OperationDefinitionNode } from 'graphql'; +import { GraphQLError, OperationDefinitionNode, version } from 'graphql'; import '../lint'; import '../mode'; import { TestSchema } from './testSchema'; @@ -61,7 +61,13 @@ describe('graphql-lint', () => { const noMutationOperationRule = (context: any) => ({ OperationDefinition(node: OperationDefinitionNode) { if (node.operation === 'mutation') { - context.reportError(new GraphQLError('I like turtles.', node)); + context.reportError( + new GraphQLError( + 'I like turtles.', + // @ts-expect-error + parseInt(version, 10) > 16 ? { nodes: node } : node, + ), + ); } return false; }, diff --git a/packages/graphiql-plugin-code-exporter/package.json b/packages/graphiql-plugin-code-exporter/package.json index 66ca389d957..5ec3139ba42 100644 --- a/packages/graphiql-plugin-code-exporter/package.json +++ b/packages/graphiql-plugin-code-exporter/package.json @@ -34,14 +34,14 @@ }, "peerDependencies": { "@graphiql/react": "^0.23.0", - "graphql": "^15.5.0 || ^16.0.0", + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2", "react": "^16.8.0 || ^17 || ^18", "react-dom": "^16.8.0 || ^17 || ^18" }, "devDependencies": { "@graphiql/react": "^0.23.0", "@vitejs/plugin-react": "^4.3.1", - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "postcss-nesting": "^10.1.7", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/graphiql-plugin-explorer/package.json b/packages/graphiql-plugin-explorer/package.json index ed4213f1c44..0e840d66dc1 100644 --- a/packages/graphiql-plugin-explorer/package.json +++ b/packages/graphiql-plugin-explorer/package.json @@ -33,14 +33,14 @@ }, "peerDependencies": { "@graphiql/react": "^0.23.0", - "graphql": "^15.5.0 || ^16.0.0", + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2", "react": "^16.8.0 || ^17 || ^18", "react-dom": "^16.8.0 || ^17 || ^18" }, "devDependencies": { "@graphiql/react": "^0.23.0", "@vitejs/plugin-react": "^4.3.1", - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^4.6.3", diff --git a/packages/graphiql-react/package.json b/packages/graphiql-react/package.json index 838dafdf9e9..140314cacd9 100644 --- a/packages/graphiql-react/package.json +++ b/packages/graphiql-react/package.json @@ -43,7 +43,7 @@ "build": "tsc --emitDeclarationOnly && vite build" }, "peerDependencies": { - "graphql": "^15.5.0 || ^16.0.0", + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2", "react": "^16.8.0 || ^17 || ^18", "react-dom": "^16.8.0 || ^17 || ^18" }, @@ -60,17 +60,19 @@ "codemirror-graphql": "^2.0.13", "copy-to-clipboard": "^3.2.0", "framer-motion": "^6.5.1", + "get-value": "^3.0.1", "graphql-language-service": "^5.2.2", "markdown-it": "^14.1.0", "set-value": "^4.1.0" }, "devDependencies": { - "@types/markdown-it": "^14.1.2", "@babel/helper-string-parser": "^7.19.4", "@testing-library/react": "14.0.0", + "@types/markdown-it": "^14.1.2", + "@types/get-value": "^3.0.5", "@types/set-value": "^4.0.1", "@vitejs/plugin-react": "^4.3.1", - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "postcss-nesting": "^10.1.7", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index be1b8bcaced..5df69e39bbf 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -15,6 +15,7 @@ import { import { getFragmentDependenciesForAST } from 'graphql-language-service'; import { ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import setValue from 'set-value'; +import getValue from 'get-value'; import { useAutoCompleteLeafs, useEditorContext } from './editor'; import { UseAutoCompleteLeafsArgs } from './editor/hooks'; @@ -343,8 +344,20 @@ type IncrementalResult = { incremental?: ReadonlyArray; label?: string; items?: ReadonlyArray> | null; + pending?: ReadonlyArray<{ id: string; path: ReadonlyArray }>; + completed?: ReadonlyArray<{ + id: string; + errors?: ReadonlyArray; + }>; + id?: string; + subPath?: ReadonlyArray; }; +const pathsMap = new WeakMap< + ExecutionResult, + Map> +>(); + /** * @param executionResult The complete execution result object which will be * mutated by merging the contents of the incremental result. @@ -352,22 +365,64 @@ type IncrementalResult = { * complete execution result. */ function mergeIncrementalResult( - executionResult: ExecutionResult, + executionResult: IncrementalResult, incrementalResult: IncrementalResult, ): void { - const path = ['data', ...(incrementalResult.path ?? [])]; - - if (incrementalResult.items) { - for (const item of incrementalResult.items) { - setValue(executionResult, path.join('.'), item); - // Increment the last path segment (the array index) to merge the next item at the next index - // eslint-disable-next-line unicorn/prefer-at -- cannot mutate the array using Array.at() - (path[path.length - 1] as number)++; + let path: ReadonlyArray | undefined = [ + 'data', + ...(incrementalResult.path ?? []), + ]; + + for (const result of [executionResult, incrementalResult]) { + if (result.pending) { + let paths = pathsMap.get(executionResult); + if (paths === undefined) { + paths = new Map(); + pathsMap.set(executionResult, paths); + } + + for (const { id, path: pendingPath } of result.pending) { + paths.set(id, ['data', ...pendingPath]); + } } } - if (incrementalResult.data) { - setValue(executionResult, path.join('.'), incrementalResult.data, { + const { items } = incrementalResult; + if (items) { + const { id } = incrementalResult; + if (id) { + path = pathsMap.get(executionResult)?.get(id); + if (path === undefined) { + throw new Error('Invalid incremental delivery format.'); + } + + const list = getValue(executionResult, path.join('.')); + list.push(...items); + } else { + path = ['data', ...(incrementalResult.path ?? [])]; + for (const item of items) { + setValue(executionResult, path.join('.'), item); + // Increment the last path segment (the array index) to merge the next item at the next index + // eslint-disable-next-line unicorn/prefer-at -- cannot mutate the array using Array.at() + (path[path.length - 1] as number)++; + } + } + } + + const { data } = incrementalResult; + if (data) { + const { id } = incrementalResult; + if (id) { + path = pathsMap.get(executionResult)?.get(id); + if (path === undefined) { + throw new Error('Invalid incremental delivery format.'); + } + const { subPath } = incrementalResult; + if (subPath !== undefined) { + path = [...path, ...subPath]; + } + } + setValue(executionResult, path.join('.'), data, { merge: true, }); } @@ -390,4 +445,16 @@ function mergeIncrementalResult( mergeIncrementalResult(executionResult, incrementalSubResult); } } + + if (incrementalResult.completed) { + // Remove tracking and add additional errors + for (const { id, errors } of incrementalResult.completed) { + pathsMap.get(executionResult)?.delete(id); + + if (errors) { + executionResult.errors ||= []; + (executionResult.errors as GraphQLError[]).push(...errors); + } + } + } } diff --git a/packages/graphiql-toolkit/package.json b/packages/graphiql-toolkit/package.json index 20face9b64a..8f8c753c946 100644 --- a/packages/graphiql-toolkit/package.json +++ b/packages/graphiql-toolkit/package.json @@ -24,13 +24,13 @@ "meros": "^1.1.4" }, "devDependencies": { - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "graphql-ws": "^5.5.5", "isomorphic-fetch": "^3.0.0", "subscriptions-transport-ws": "0.11.0" }, "peerDependencies": { - "graphql": "^15.5.0 || ^16.0.0", + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2", "graphql-ws": ">= 4.5.0" }, "peerDependenciesMeta": { diff --git a/packages/graphiql/cypress/e2e/errors.cy.ts b/packages/graphiql/cypress/e2e/errors.cy.ts index 0509e320bee..a6613665f19 100644 --- a/packages/graphiql/cypress/e2e/errors.cy.ts +++ b/packages/graphiql/cypress/e2e/errors.cy.ts @@ -1,8 +1,12 @@ -import { version } from 'graphql'; +import { GraphQLError, version } from 'graphql'; describe('Errors', () => { it('Should show an error when the HTTP request fails', () => { - cy.visit('/?http-error=true'); + cy.intercept('/graphql', { + statusCode: 502, + body: 'Bad Gateway', + }); + cy.visit('/'); cy.assertQueryResult({ errors: [ { @@ -21,23 +25,27 @@ describe('Errors', () => { }); it('Should show an error when introspection fails', () => { - cy.visit('/?graphql-error=true'); + cy.intercept('/graphql', { + body: { errors: [new GraphQLError('Something unexpected happened...')] }, + }); + cy.visit('/'); cy.assertQueryResult({ errors: [{ message: 'Something unexpected happened...' }], }); }); it('Should show an error when the schema is invalid', () => { - cy.visit('/?bad=true'); + cy.intercept('/graphql', { fixture: 'bad-schema.json' }); + cy.visit('/'); /** * We can't use `cy.assertQueryResult` here because the stack contains line * and column numbers of the `graphiql.min.js` bundle which are not stable. */ cy.get('section.result-window').should(element => { expect(element.get(0).innerText).to.contain( - version.startsWith('16.') - ? 'Names must only contain [_a-zA-Z0-9] but \\"\\" does not.' - : 'Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \\"\\" does not.', + version.startsWith('15') + ? 'Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \\"\\" does not.' + : 'Names must only contain [_a-zA-Z0-9] but \\"\\" does not.', ); }); }); diff --git a/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts b/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts index 0ab5710b5ea..1ea0c0ed822 100644 --- a/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts +++ b/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts @@ -1,10 +1,9 @@ import { version } from 'graphql'; -let describeOrSkip = describe.skip; +let describeOrSkip: Mocha.SuiteFunction | Mocha.PendingSuiteFunction = describe; -// TODO: disable when defer/stream is merged to graphql -if (version.includes('stream')) { - describeOrSkip = describe; +if (parseInt(version, 10) < 17) { + describeOrSkip = describe.skip; } describeOrSkip('IncrementalDelivery support via fetcher', () => { @@ -52,7 +51,6 @@ describeOrSkip('IncrementalDelivery support via fetcher', () => { }, ], }, - hasNext: false, }; it('Expects slower streams to resolve in several increments, and the payloads to patch properly', () => { @@ -99,7 +97,6 @@ describeOrSkip('IncrementalDelivery support via fetcher', () => { 'Oops, this took 1 seconds longer than I thought it would!', }, }, - hasNext: false, }); }); @@ -164,7 +161,6 @@ describeOrSkip('IncrementalDelivery support via fetcher', () => { age: 1000, }, }, - hasNext: false, }); }); }); diff --git a/packages/graphiql/cypress/e2e/init.cy.ts b/packages/graphiql/cypress/e2e/init.cy.ts index 960faecd627..15f2b84e39d 100644 --- a/packages/graphiql/cypress/e2e/init.cy.ts +++ b/packages/graphiql/cypress/e2e/init.cy.ts @@ -49,7 +49,8 @@ describe('GraphiQL On Initialization', () => { cy.assertQueryResult(mockSuccess); }); it('Shows the expected error when the schema is invalid', () => { - cy.visit('/?bad=true'); + cy.intercept('/graphql', { fixture: 'bad-schema.json' }); + cy.visit('/'); cy.get('section.result-window').should(element => { expect(element.get(0).innerText).to.contain('Names must'); }); diff --git a/packages/graphiql/cypress/fixtures/bad-schema.json b/packages/graphiql/cypress/fixtures/bad-schema.json new file mode 100644 index 00000000000..b05417a54bf --- /dev/null +++ b/packages/graphiql/cypress/fixtures/bad-schema.json @@ -0,0 +1,99 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "user", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + } + ], + "directives": [] + } + } +} diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index a663807b947..a38ee09b3cd 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -50,7 +50,7 @@ "@graphiql/react": "^0.23.1" }, "peerDependencies": { - "graphql": "^15.5.0 || ^16.0.0", + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2", "react": "^16.8.0 || ^17 || ^18", "react-dom": "^16.8.0 || ^17 || ^18" }, @@ -67,8 +67,8 @@ "cypress": "^13.13.2", "express": "^4.19.2", "fork-ts-checker-webpack-plugin": "7.3.0", - "graphql": "^16.8.1", - "graphql-http": "^1.19.0", + "graphql": "^17.0.0-alpha.5", + "graphql-helix": "^1.13.0", "graphql-subscriptions": "^2.0.0", "html-webpack-plugin": "^5.5.0", "identity-obj-proxy": "^3.0.0", diff --git a/packages/graphiql/resources/renderExample.js b/packages/graphiql/resources/renderExample.js index b2fc32c7d25..017eea99397 100644 --- a/packages/graphiql/resources/renderExample.js +++ b/packages/graphiql/resources/renderExample.js @@ -63,16 +63,6 @@ function getSchemaUrl() { const isDev = window.location.hostname.match(/localhost$/); if (isDev) { - // This supports an e2e test which ensures that invalid schemas do not load. - if (parameters.bad === 'true') { - return '/bad/graphql'; - } - if (parameters['http-error'] === 'true') { - return '/http-error/graphql'; - } - if (parameters['graphql-error'] === 'true') { - return '/graphql-error/graphql'; - } return '/graphql'; } return '/.netlify/functions/graphql'; diff --git a/packages/graphiql/test/bad-schema.js b/packages/graphiql/test/bad-schema.js deleted file mode 100644 index 2ec51e0625c..00000000000 --- a/packages/graphiql/test/bad-schema.js +++ /dev/null @@ -1,97 +0,0 @@ -module.exports.schema = { - __schema: { - queryType: { - name: 'Query', - }, - mutationType: null, - subscriptionType: null, - types: [ - { - kind: 'OBJECT', - name: 'Query', - description: null, - fields: [ - { - name: 'user', - description: null, - args: [ - { - name: 'id', - description: null, - type: { - kind: 'NON_NULL', - name: null, - ofType: { - kind: 'SCALAR', - name: 'ID', - ofType: null, - }, - }, - defaultValue: null, - }, - ], - type: { - kind: 'OBJECT', - name: '', - ofType: null, - }, - isDeprecated: false, - deprecationReason: null, - }, - ], - inputFields: null, - interfaces: [], - enumValues: null, - possibleTypes: null, - }, - { - kind: 'SCALAR', - name: 'ID', - description: '', - fields: null, - inputFields: null, - interfaces: null, - enumValues: null, - possibleTypes: null, - }, - { - kind: 'OBJECT', - name: '', - description: null, - fields: [ - { - name: 'name', - description: null, - args: [], - type: { - kind: 'NON_NULL', - name: null, - ofType: { - kind: 'SCALAR', - name: 'String', - ofType: null, - }, - }, - isDeprecated: false, - deprecationReason: null, - }, - ], - inputFields: null, - interfaces: [], - enumValues: null, - possibleTypes: null, - }, - { - kind: 'SCALAR', - name: 'String', - description: '', - fields: null, - inputFields: null, - interfaces: null, - enumValues: null, - possibleTypes: null, - }, - ], - directives: [], - }, -}; diff --git a/packages/graphiql/test/beforeDevServer.js b/packages/graphiql/test/beforeDevServer.js deleted file mode 100644 index d386ae47922..00000000000 --- a/packages/graphiql/test/beforeDevServer.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2021 GraphQL Contributors. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -const express = require('express'); -const path = require('node:path'); -const { createHandler } = require('graphql-http/lib/use/express'); -const schema = require('./schema'); -const { schema: badSchema } = require('./bad-schema'); - -module.exports = function beforeDevServer(app, _server, _compiler) { - // GraphQL Server - app.post('/graphql', createHandler({ schema })); - app.get('/graphql', createHandler({ schema })); - - app.post('/bad/graphql', (_req, res, next) => { - res.json({ data: badSchema }); - next(); - }); - - app.use('/images', express.static(path.join(__dirname, 'images'))); - - app.use( - '/resources/renderExample.js', - express.static(path.join(__dirname, '../resources/renderExample.js')), - ); -}; diff --git a/packages/graphiql/test/e2e-server.js b/packages/graphiql/test/e2e-server.js index 38b2f9f08ac..c734c459d66 100644 --- a/packages/graphiql/test/e2e-server.js +++ b/packages/graphiql/test/e2e-server.js @@ -9,31 +9,68 @@ const { createServer } = require('node:http'); const express = require('express'); const path = require('node:path'); -const { createHandler } = require('graphql-http/lib/use/express'); -const { GraphQLError } = require('graphql'); +const { + execute, + experimentalExecuteIncrementally, + version, +} = require('graphql'); const schema = require('./schema'); const app = express(); -const { schema: badSchema } = require('./bad-schema'); +const { + getGraphQLParameters, + processRequest, + sendResult, +} = require('graphql-helix'); // update when `graphql-http` is upgraded to support multipart requests for incremental delivery https://github.com/graphql/graphiql/pull/3682#discussion_r1715545279 const WebSocketsServer = require('./afterDevServer'); -// Server -app.post('/graphql', createHandler({ schema })); -app.get('/graphql', createHandler({ schema })); +const customExecute = + parseInt(version, 10) > 16 + ? async (...args) => { + const result = await experimentalExecuteIncrementally(...args); -app.post('/bad/graphql', (_req, res, next) => { - res.json({ data: badSchema }); - next(); -}); + if (!('subsequentResults' in result)) { + return result; + } -app.post('/http-error/graphql', (_req, res, next) => { - res.status(502).send('Bad Gateway'); - next(); -}); + const { initialResult, subsequentResults } = result; + if (typeof subsequentResults[Symbol.asyncIterator] !== 'function') { + return result; + } -app.post('/graphql-error/graphql', (_req, res, next) => { - res.json({ errors: [new GraphQLError('Something unexpected happened...')] }); - next(); -}); + return (async function* () { + yield initialResult; + yield* subsequentResults; + })(); + } + : execute; + +async function handler(req, res) { + const request = { + body: req.body, + headers: req.headers, + method: req.method, + query: req.query, + }; + + const { operationName, query, variables } = getGraphQLParameters(request); + + const result = await processRequest({ + operationName, + query, + variables, + request, + schema, + execute: customExecute, + }); + + sendResult(result, res); +} + +// Server +app.use(express.json()); + +app.post('/graphql', handler); +app.get('/graphql', handler); app.use(express.static(path.resolve(__dirname, '../'))); app.use('index.html', express.static(path.resolve(__dirname, '../dev.html'))); diff --git a/packages/graphiql/test/schema.js b/packages/graphiql/test/schema.js index fcd648096f1..d6c048e5514 100644 --- a/packages/graphiql/test/schema.js +++ b/packages/graphiql/test/schema.js @@ -6,6 +6,8 @@ * LICENSE file in the root directory of this source tree. */ +const graphql = require('graphql'); + const { GraphQLSchema, GraphQLObjectType, @@ -19,7 +21,16 @@ const { GraphQLString, GraphQLID, GraphQLList, -} = require('graphql'); + GraphQLDeferDirective, + GraphQLStreamDirective, + specifiedDirectives, + version, +} = graphql; + +const directives = + parseInt(version, 10) > 16 + ? [...specifiedDirectives, GraphQLDeferDirective, GraphQLStreamDirective] + : specifiedDirectives; // Test Schema const TestEnum = new GraphQLEnumType({ @@ -386,6 +397,7 @@ const myTestSchema = new GraphQLSchema({ mutation: TestMutationType, subscription: TestSubscriptionType, description: 'This is a test schema for GraphiQL', + directives, }); module.exports = myTestSchema; diff --git a/packages/graphql-language-service-cli/package.json b/packages/graphql-language-service-cli/package.json index 5aa65f3e105..b36caae4043 100644 --- a/packages/graphql-language-service-cli/package.json +++ b/packages/graphql-language-service-cli/package.json @@ -32,7 +32,7 @@ "LSP" ], "peerDependencies": { - "graphql": "^15.5.0 || ^16.0.0" + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2" }, "dependencies": { "@babel/polyfill": "^7.12.1", @@ -42,6 +42,6 @@ "yargs": "^16.2.0" }, "devDependencies": { - "graphql": "^16.8.1" + "graphql": "^17.0.0-alpha.5" } } diff --git a/packages/graphql-language-service-server/package.json b/packages/graphql-language-service-server/package.json index 4b63f14ccb8..44aa7c601dc 100644 --- a/packages/graphql-language-service-server/package.json +++ b/packages/graphql-language-service-server/package.json @@ -34,7 +34,7 @@ "module": "esm/index.js", "typings": "esm/index.d.ts", "peerDependencies": { - "graphql": "^15.5.0 || ^16.0.0" + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2" }, "COMMENT": "please do not remove dependencies without thorough testing. many dependencies are not imported directly, as they are peer dependencies", "dependencies": { @@ -68,7 +68,7 @@ "@types/mock-fs": "^4.13.4", "cross-env": "^7.0.2", "debounce-promise": "^3.1.2", - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "mock-fs": "^5.2.0" } } diff --git a/packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts b/packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts index 97923c6e999..882e577e127 100644 --- a/packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts +++ b/packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts @@ -12,6 +12,7 @@ import { buildASTSchema, introspectionFromSchema, parse, + version, } from 'graphql'; import fetchMock from 'fetch-mock'; @@ -361,6 +362,8 @@ describe('MessageProcessor with config', () => { }); it('caches files and schema with a URL config', async () => { + const offset = parseInt(version, 10) > 16 ? 25 : 0; + mockSchema(require('../../../graphiql/test/schema')); const project = new MockProject({ @@ -428,29 +431,29 @@ describe('MessageProcessor with config', () => { expect(serializeRange(typeDefinitions[0].range)).toEqual({ start: { - line: 11, + line: 11 + offset, character: 0, }, end: { - line: 102, + line: 102 + offset, character: 1, }, }); const schemaDefs = await project.lsp.handleDefinitionRequest({ textDocument: { uri: URI.parse(genSchemaPath).toString() }, - position: { character: 20, line: 18 }, + position: { character: 20, line: 18 + offset }, }); expect(schemaDefs[0].uri).toEqual(URI.parse(genSchemaPath).toString()); // note: if the graphiql test schema changes, // this might break, please adjust if you see a failure here expect(serializeRange(schemaDefs[0].range)).toEqual({ start: { - line: 104, + line: 104 + offset, character: 0, }, end: { - line: 112, + line: 112 + offset, character: 1, }, }); diff --git a/packages/graphql-language-service/package.json b/packages/graphql-language-service/package.json index ac6b50ff1d3..16cea41ffeb 100644 --- a/packages/graphql-language-service/package.json +++ b/packages/graphql-language-service/package.json @@ -32,7 +32,7 @@ "graphql": "./dist/temp-bin.js" }, "peerDependencies": { - "graphql": "^15.5.0 || ^16.0.0" + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2" }, "dependencies": { "debounce-promise": "^3.1.2", @@ -45,7 +45,7 @@ "@types/json-schema": "7.0.9", "@types/picomatch": "^2.3.0", "benchmark": "^2.1.4", - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "graphql-config": "5.0.3", "lodash": "^4.17.15", "platform": "^1.3.5", diff --git a/packages/graphql-language-service/src/interface/__tests__/getDiagnostics-test.ts b/packages/graphql-language-service/src/interface/__tests__/getDiagnostics-test.ts index 85ce2fe7171..ccb2bc71311 100644 --- a/packages/graphql-language-service/src/interface/__tests__/getDiagnostics-test.ts +++ b/packages/graphql-language-service/src/interface/__tests__/getDiagnostics-test.ts @@ -17,6 +17,7 @@ import { ValidationContext, ASTVisitor, FragmentDefinitionNode, + version, } from 'graphql'; import path from 'node:path'; @@ -87,7 +88,13 @@ describe('getDiagnostics', () => { for (const definition of node.definitions) { // add a custom error to every definition validationContext.reportError( - new GraphQLError('This is a custom error.', definition), + new GraphQLError( + 'This is a custom error.', + // @ts-expect-error + parseInt(version, 10) > 16 + ? { nodes: definition } + : definition, + ), ); } return false; @@ -158,7 +165,13 @@ describe('getDiagnostics', () => { const noQueryRule = (context: ValidationContext): ASTVisitor => ({ OperationDefinition(node) { if (node.operation === 'query') { - context.reportError(new GraphQLError('No query allowed.', node.name)); + context.reportError( + new GraphQLError( + 'No query allowed.', + // @ts-expect-error + parseInt(version, 10) > 16 ? { nodes: node } : node, + ), + ); } }, }); diff --git a/packages/graphql-language-service/src/utils/__tests__/validateWithCustomRules-test.ts b/packages/graphql-language-service/src/utils/__tests__/validateWithCustomRules-test.ts index 6ba7de8002b..b65c35c1eab 100644 --- a/packages/graphql-language-service/src/utils/__tests__/validateWithCustomRules-test.ts +++ b/packages/graphql-language-service/src/utils/__tests__/validateWithCustomRules-test.ts @@ -14,6 +14,7 @@ import { GraphQLSchema, ValidationContext, ArgumentNode, + version, } from 'graphql'; import { join } from 'node:path'; @@ -37,7 +38,8 @@ describe('validateWithCustomRules', () => { context.reportError( new GraphQLError( 'Argument ID must be a number written in string type.', - [node], + // @ts-expect-error + parseInt(version, 10) > 16 ? { nodes: node } : node, ), ); } diff --git a/packages/monaco-graphql/package.json b/packages/monaco-graphql/package.json index 7a08cd9ee81..8d42bcf8dbd 100644 --- a/packages/monaco-graphql/package.json +++ b/packages/monaco-graphql/package.json @@ -71,13 +71,13 @@ }, "devDependencies": { "execa": "^7.1.1", - "graphql": "^16.8.1", + "graphql": "^17.0.0-alpha.5", "monaco-editor": "^0.39.0", "prettier": "3.3.2", "vscode-languageserver-types": "^3.17.1" }, "peerDependencies": { - "graphql": "^15.5.0 || ^16.0.0", + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2", "monaco-editor": ">= 0.20.0 < 1", "prettier": "^2.8.0 || ^3.0.0" } diff --git a/packages/monaco-graphql/test/monaco-editor.test.ts b/packages/monaco-graphql/test/monaco-editor.test.ts index 018c5c05a52..fee02855f48 100644 --- a/packages/monaco-graphql/test/monaco-editor.test.ts +++ b/packages/monaco-graphql/test/monaco-editor.test.ts @@ -1,4 +1,5 @@ import { $ } from 'execa'; +import { version } from 'graphql'; // eslint-disable-next-line no-control-regex const ANSI_COLOR_REGEX = /\u001b\[\d+m/g; @@ -13,7 +14,9 @@ describe('monaco-editor', () => { // expect(lines[0]).toBe('$ vite build'); // expect(lines[1]).toMatch(' building for production...'); // expect(lines[2]).toBe('transforming...'); - expect(lines[3]).toMatch('✓ 843 modules transformed.'); + expect(lines[3]).toMatch( + `✓ ${parseInt(version, 10) > 16 ? 856 : 843} modules transformed.`, + ); // expect(lines[4]).toBe('rendering chunks...'); // expect(lines[5]).toBe('computing gzip size...'); // expect(lines[6]).toMatch('dist/index.html'); diff --git a/packages/vscode-graphql-execution/package.json b/packages/vscode-graphql-execution/package.json index 655e6dcd5f1..604d704d1b9 100644 --- a/packages/vscode-graphql-execution/package.json +++ b/packages/vscode-graphql-execution/package.json @@ -109,7 +109,7 @@ "cosmiconfig": "8.2.0", "cosmiconfig-toml-loader": "^1.0.0", "dotenv": "10.0.0", - "graphql": "^16.8.1", + "graphql": "^16.8.1 || ^17.0.0-alpha.2", "graphql-config": "5.0.3", "graphql-tag": "2.12.6", "graphql-ws": "5.10.0", diff --git a/packages/vscode-graphql/package.json b/packages/vscode-graphql/package.json index 0ec42fb25c2..ae90d284835 100644 --- a/packages/vscode-graphql/package.json +++ b/packages/vscode-graphql/package.json @@ -179,7 +179,7 @@ "ovsx": "0.8.3" }, "dependencies": { - "graphql": "^16.8.1", + "graphql": "^16.8.1 || ^17.0.0-alpha.2", "graphql-language-service-server": "^2.13.3", "vscode-languageclient": "8.0.2", "typescript": "^5.3.3" diff --git a/resources/patches/graphql+17.0.0-alpha.5.patch b/resources/patches/graphql+17.0.0-alpha.5.patch new file mode 100644 index 00000000000..82e4f39cf16 --- /dev/null +++ b/resources/patches/graphql+17.0.0-alpha.5.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/graphql/execution/execute.js b/node_modules/graphql/execution/execute.js +index eb56e4b..5a4a398 100644 +--- a/node_modules/graphql/execution/execute.js ++++ b/node_modules/graphql/execution/execute.js +@@ -2055,7 +2055,7 @@ async function getNextAsyncStreamItemResult( + getNextAsyncStreamItemResult( + streamItemQueue, + streamPath, +- index, ++ index + 1, + asyncIterator, + exeContext, + fieldGroup, +diff --git a/node_modules/graphql/execution/execute.mjs b/node_modules/graphql/execution/execute.mjs +index 641c067..c7641d4 100644 +--- a/node_modules/graphql/execution/execute.mjs ++++ b/node_modules/graphql/execution/execute.mjs +@@ -1984,7 +1984,7 @@ async function getNextAsyncStreamItemResult( + getNextAsyncStreamItemResult( + streamItemQueue, + streamPath, +- index, ++ index + 1, + asyncIterator, + exeContext, + fieldGroup, diff --git a/yarn.lock b/yarn.lock index 1f69ccb1532..f491819c980 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4239,6 +4239,11 @@ resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.3.2.tgz#58805ba36a9357be92cc8c008dbfda937e9f7d8f" integrity sha512-NCEfv49jmDsBAixjMjEHKVgmVQlJ+uK56FOc+2roYPExnXCZDpi6mJOHQ3v23BiO84hBDStND9R2itJr7PNoow== +"@types/get-value@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/get-value/-/get-value-3.0.5.tgz#4ea0e0b0a31c256636b3e7e0026c2ad38baea6f6" + integrity sha512-+o8nw0TId5cDwtdVrhlc8rvzaxbCU+JksFeu8ZunY9vUaODxngXiNceTFj2gkSwGWNRpe3PtaSWt1y0VB71PvA== + "@types/glob@^8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.1.0.tgz#b63e70155391b0584dce44e7ea25190bbc38f2fc" @@ -9957,6 +9962,13 @@ get-tsconfig@^4.7.3: dependencies: resolve-pkg-maps "^1.0.0" +get-value@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" + integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA== + dependencies: + isobject "^3.0.1" + getos@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" @@ -10202,10 +10214,15 @@ graphql-config@5.0.3: string-env-interpolation "^1.0.1" tslib "^2.4.0" -graphql-http@^1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/graphql-http/-/graphql-http-1.19.0.tgz#6f0fff0dbd9a8e797c99b5ac0ca160566e7927da" - integrity sha512-fOD3hfp/G+Lhx2FWW5HsfmtJSsw6CikcpOboG7/mFo/pPUzn3yOwKdTFRnJ8MVY4ru69MT1nSPr/1gI/iuGNlw== +graphql-helix@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/graphql-helix/-/graphql-helix-1.13.0.tgz#e64dad5ef5f622ef38c97fa033f56f3d953c0104" + integrity sha512-cqDKMoRywKjnL0ZWCTB0GOiBgsH6d3nU4JGDF6RuzAyd35tmalzKpSxkx3NNp4H5RvnKWnrukWzR51wUq277ng== + +graphql-http@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/graphql-http/-/graphql-http-1.22.1.tgz#3857ac75366e55db189cfe09ade9cc4c4f2cfd09" + integrity sha512-4Jor+LRbA7SfSaw7dfDUs2UBzvWg3cKrykfHRgKsOIvQaLuf+QOcG2t3Mx5N9GzSNJcuqMqJWz0ta5+BryEmXg== graphql-subscriptions@^2.0.0: version "2.0.0" @@ -10231,10 +10248,10 @@ graphql-ws@5.14.0, graphql-ws@^5.5.5: resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.14.0.tgz#766f249f3974fc2c48fae0d1fb20c2c4c79cd591" integrity sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g== -graphql@^16.8.1: - version "16.8.1" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" - integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== +graphql@17.0.0-alpha.5, graphql@^16.8.1, "graphql@^16.8.1 || ^17.0.0-alpha.2", graphql@^17.0.0-alpha.5: + version "17.0.0-alpha.5" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-17.0.0-alpha.5.tgz#d094b76c14abbbfc7eabde0501a74709103d3c6b" + integrity sha512-gUBugdZH5nIl8G7cfz4WOKRtjcyuwpPTfFqWWMkcxpQCiO6wPRe7OfrjHe7p8mHEFG1a+ySzC7eB8idGZ27AGQ== gzip-size@^5.0.0: version "5.1.1"