From 47cae71a44f0410ca55f58c7b0a307efca76fa7e Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 02:06:42 +0200 Subject: [PATCH 01/12] add sse --- packages/graphiql-toolkit/package.json | 7 +- .../src/create-fetcher/create-sse-fetcher.ts | 84 +++++++++++++++++++ .../src/create-fetcher/createFetcher.ts | 11 ++- .../src/create-fetcher/types.ts | 4 + packages/graphiql-toolkit/tsup.config.ts | 2 +- .../cypress/e2e/incremental-delivery.cy.ts | 60 ++++--------- packages/graphiql/resources/renderExample.js | 1 + packages/graphiql/test/beforeDevServer.js | 2 +- packages/graphiql/test/e2e-server.js | 30 ++++++- packages/graphiql/test/schema.js | 24 +++--- yarn.lock | 5 ++ 11 files changed, 166 insertions(+), 64 deletions(-) create mode 100644 packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts diff --git a/packages/graphiql-toolkit/package.json b/packages/graphiql-toolkit/package.json index 8e3c8bcfde2..945f16d6b21 100644 --- a/packages/graphiql-toolkit/package.json +++ b/packages/graphiql-toolkit/package.json @@ -32,17 +32,22 @@ "devDependencies": { "graphql": "^17.0.0-alpha.7", "graphql-ws": "^5.5.5", + "graphql-sse": "^2.5.3", "isomorphic-fetch": "^3.0.0", "subscriptions-transport-ws": "0.11.0", "tsup": "^8.2.4" }, "peerDependencies": { "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2", - "graphql-ws": ">= 4.5.0" + "graphql-ws": ">= 4.5.0", + "graphql-sse": "^2" }, "peerDependenciesMeta": { "graphql-ws": { "optional": true + }, + "graphql-sse": { + "optional": true } }, "keywords": [ diff --git a/packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts b/packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts new file mode 100644 index 00000000000..7703c5e4cbe --- /dev/null +++ b/packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts @@ -0,0 +1,84 @@ +import type { + ClientOptions, + createClient as createClientType, + ExecutionResult, +} from 'graphql-sse'; +import { Fetcher, FetcherParams } from './types'; + +export async function createSseFetcher(opts: ClientOptions): Promise { + const { createClient } = + process.env.USE_IMPORT === 'false' + ? (require('graphql-sse') as { createClient: typeof createClientType }) + : await import('graphql-sse'); + + const sseClient = createClient({ + retryAttempts: 0, + // @ts-expect-error + singleConnection: true, // or use false if you have an HTTP/2 server + // @ts-expect-error + lazy: false, // connect as soon as the page opens + ...opts, + }); + + function subscribe(payload: FetcherParams) { + let deferred: { + resolve: (arg: boolean) => void; + reject: (arg: unknown) => void; + }; + + const pending: ExecutionResult, unknown>[] = []; + let throwMe: unknown; + let done = false; + + const dispose = sseClient.subscribe( + { + ...payload, + // types are different with FetcherParams + operationName: payload.operationName ?? undefined, + }, + { + next(data) { + pending.push(data); + deferred?.resolve(false); + }, + error(err) { + throwMe = err; + deferred?.reject(throwMe); + }, + complete() { + done = true; + deferred?.resolve(true); + }, + }, + ); + + return { + [Symbol.asyncIterator]() { + return this; + }, + async next() { + if (done) { + return { done: true, value: undefined }; + } + if (throwMe) { + throw throwMe; + } + if (pending.length) { + return { value: pending.shift() }; + } + return (await new Promise((resolve, reject) => { + deferred = { resolve, reject }; + })) + ? { done: true, value: undefined } + : { value: pending.shift() }; + }, + async return() { + dispose(); + return { done: true, value: undefined }; + }, + }; + } + + // @ts-expect-error todo: fix type + return subscribe; +} diff --git a/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts b/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts index b90c65ee83e..36e3612d54f 100644 --- a/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts +++ b/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts @@ -6,11 +6,12 @@ import { isSubscriptionWithName, getWsFetcher, } from './lib'; +import { createSseFetcher } from './create-sse-fetcher'; /** - * build a GraphiQL fetcher that is: + * Build a GraphiQL fetcher that is: * - backwards compatible - * - optionally supports graphql-ws or ` + * - optionally supports graphql-ws or graphql-sse */ export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher { const httpFetch = @@ -40,7 +41,13 @@ export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher { graphQLParams.operationName || undefined, ) : false; + if (isSubscription) { + if (options.sseUrl) { + const sseFetcher = await createSseFetcher({ url: options.sseUrl }); + return sseFetcher(graphQLParams); + } + const wsFetcher = await getWsFetcher(options, fetcherOpts); if (!wsFetcher) { diff --git a/packages/graphiql-toolkit/src/create-fetcher/types.ts b/packages/graphiql-toolkit/src/create-fetcher/types.ts index 9ae06a67beb..d14eb185bfe 100644 --- a/packages/graphiql-toolkit/src/create-fetcher/types.ts +++ b/packages/graphiql-toolkit/src/create-fetcher/types.ts @@ -84,6 +84,10 @@ export interface CreateFetcherOptions { * url for websocket subscription requests */ subscriptionUrl?: string; + /** + * url for graphql-sse + */ + sseUrl?: string; /** * `wsClient` implementation that matches `ws-graphql` signature, * whether via `createClient()` itself or another client. diff --git a/packages/graphiql-toolkit/tsup.config.ts b/packages/graphiql-toolkit/tsup.config.ts index a4d1c15b46f..b9e919faea4 100644 --- a/packages/graphiql-toolkit/tsup.config.ts +++ b/packages/graphiql-toolkit/tsup.config.ts @@ -4,7 +4,6 @@ const opts: Options = { entry: ['src/**/*.ts', '!**/__tests__'], bundle: false, clean: true, - dts: true, minifySyntax: true, }; @@ -17,6 +16,7 @@ export default defineConfig([ env: { USE_IMPORT: 'true', }, + dts: true, }, { ...opts, diff --git a/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts b/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts index 1ea0c0ed822..eb57e3fa755 100644 --- a/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts +++ b/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts @@ -19,36 +19,16 @@ describeOrSkip('IncrementalDelivery support via fetcher', () => { const mockStreamSuccess = { data: { streamable: [ - { - text: 'Hi', - }, - { - text: '你好', - }, - { - text: 'Hola', - }, - { - text: 'أهلاً', - }, - { - text: 'Bonjour', - }, - { - text: 'سلام', - }, - { - text: '안녕', - }, - { - text: 'Ciao', - }, - { - text: 'हेलो', - }, - { - text: 'Здорово', - }, + { text: 'Hi' }, + { text: '你好' }, + { text: 'Hola' }, + { text: 'أهلاً' }, + { text: 'Bonjour' }, + { text: 'سلام' }, + { text: '안녕' }, + { text: 'Ciao' }, + { text: 'हेलो' }, + { text: 'Здорово' }, ], }, }; @@ -141,22 +121,10 @@ describeOrSkip('IncrementalDelivery support via fetcher', () => { person: { name: 'Mark', friends: [ - { - name: 'James', - age: 1000, - }, - { - name: 'Mary', - age: 1000, - }, - { - name: 'John', - age: 1000, - }, - { - name: 'Patrica', - age: 1000, - }, + { name: 'James', age: 1000 }, + { name: 'Mary', age: 1000 }, + { name: 'John', age: 1000 }, + { name: 'Patrica', age: 1000 }, ], age: 1000, }, diff --git a/packages/graphiql/resources/renderExample.js b/packages/graphiql/resources/renderExample.js index 0bbadd29069..bdee2ccaa3d 100644 --- a/packages/graphiql/resources/renderExample.js +++ b/packages/graphiql/resources/renderExample.js @@ -78,6 +78,7 @@ root.render( fetcher: GraphiQL.createFetcher({ url: getSchemaUrl(), subscriptionUrl: 'ws://localhost:8081/subscriptions', + sseUrl: parameters.sseUrl }), query: parameters.query, variables: parameters.variables, diff --git a/packages/graphiql/test/beforeDevServer.js b/packages/graphiql/test/beforeDevServer.js index 55c634caeed..15df19ecdf7 100644 --- a/packages/graphiql/test/beforeDevServer.js +++ b/packages/graphiql/test/beforeDevServer.js @@ -9,7 +9,7 @@ const express = require('express'); const path = require('node:path'); // eslint-disable-next-line import-x/no-extraneous-dependencies const { createHandler } = require('graphql-http/lib/use/express'); -const schema = require('./schema'); +const { schema } = require('./schema'); const badSchema = require('../cypress/fixtures/bad-schema.json'); module.exports = function beforeDevServer(app, _server, _compiler) { diff --git a/packages/graphiql/test/e2e-server.js b/packages/graphiql/test/e2e-server.js index 970b8144162..2dfbd1ca03d 100644 --- a/packages/graphiql/test/e2e-server.js +++ b/packages/graphiql/test/e2e-server.js @@ -15,8 +15,9 @@ const { 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'); -const schema = require('./schema'); +const { schema, sseSchema } = require('./schema'); const { customExecute } = require('./execute'); +const { createHandler } = require('graphql-sse/lib/use/express'); const app = express(); @@ -42,6 +43,33 @@ async function handler(req, res) { sendResult(result, res); } +app.use('/graphql/stream', (req, res, next) => { + // Fixes + // Access to fetch at 'http://localhost:8080/graphql/stream' from origin 'http://localhost:5173' has been blocked by + // CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' + // header is present on the requested resource. If an opaque response serves your needs, set the request's mode to + // 'no-cors' to fetch the resource with CORS disabled. + + // CORS headers + res.header('Access-Control-Allow-Origin', '*'); // restrict it to the required domain + res.header('Access-Control-Allow-Methods', 'GET,PUT,POST'); + // Set custom headers for CORS + res.header( + 'Access-Control-Allow-Headers', + 'content-type,x-graphql-event-stream-token', + ); + + if (req.method === 'OPTIONS') { + return res.status(200).end(); + } + next(); +}); + +// Create the GraphQL over SSE handler +const sseHandler = createHandler({ schema: sseSchema }); +// Serve all methods on `/graphql/stream` +app.use('/graphql/stream', sseHandler); + // Server app.use(express.json()); diff --git a/packages/graphiql/test/schema.js b/packages/graphiql/test/schema.js index d6c048e5514..6dee5086142 100644 --- a/packages/graphiql/test/schema.js +++ b/packages/graphiql/test/schema.js @@ -5,9 +5,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - -const graphql = require('graphql'); - const { GraphQLSchema, GraphQLObjectType, @@ -25,12 +22,7 @@ const { GraphQLStreamDirective, specifiedDirectives, version, -} = graphql; - -const directives = - parseInt(version, 10) > 16 - ? [...specifiedDirectives, GraphQLDeferDirective, GraphQLStreamDirective] - : specifiedDirectives; +} = require('graphql'); // Test Schema const TestEnum = new GraphQLEnumType({ @@ -392,12 +384,20 @@ const TestSubscriptionType = new GraphQLObjectType({ }, }); -const myTestSchema = new GraphQLSchema({ +const schemaConfig = { query: TestType, mutation: TestMutationType, subscription: TestSubscriptionType, description: 'This is a test schema for GraphiQL', - directives, +}; + +exports.schema = new GraphQLSchema({ + ...schemaConfig, + directives: + parseInt(version, 10) > 16 + ? [...specifiedDirectives, GraphQLDeferDirective, GraphQLStreamDirective] + : specifiedDirectives, }); -module.exports = myTestSchema; +// Same schema but without defer/stream directives +exports.sseSchema = new GraphQLSchema(schemaConfig); diff --git a/yarn.lock b/yarn.lock index b47771846d7..1e5659ea156 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10498,6 +10498,11 @@ graphql-http@^1.22.1: resolved "https://registry.yarnpkg.com/graphql-http/-/graphql-http-1.22.1.tgz#3857ac75366e55db189cfe09ade9cc4c4f2cfd09" integrity sha512-4Jor+LRbA7SfSaw7dfDUs2UBzvWg3cKrykfHRgKsOIvQaLuf+QOcG2t3Mx5N9GzSNJcuqMqJWz0ta5+BryEmXg== +graphql-sse@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/graphql-sse/-/graphql-sse-2.5.3.tgz#c3557803f2db306d8ac87fd3bc089b6d4bac8353" + integrity sha512-5IcFW3e7fPOG7oFkK1v3X1wWtCJArQKB/H1UJeNotjy7a/9EYA5K+EiHJF1BiDSVNx7y64bd0FlDETrNBSvIHQ== + graphql-subscriptions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-2.0.0.tgz#11ec181d475852d8aec879183e8e1eb94f2eb79a" From 77c332c84ceb81aa29a8273b6cd810b43ea6a4f2 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 02:24:57 +0200 Subject: [PATCH 02/12] support graphql-sse --- .../src/create-fetcher/create-sse-fetcher.ts | 3 ++ .../graphiql/cypress/e2e/graphql-ws.cy.ts | 20 ----------- packages/graphiql/cypress/e2e/ws-sse.cy.ts | 33 +++++++++++++++++++ packages/graphiql/test/schema.js | 2 +- 4 files changed, 37 insertions(+), 21 deletions(-) delete mode 100644 packages/graphiql/cypress/e2e/graphql-ws.cy.ts create mode 100644 packages/graphiql/cypress/e2e/ws-sse.cy.ts diff --git a/packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts b/packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts index 7703c5e4cbe..e7c5ca1c67f 100644 --- a/packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts +++ b/packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts @@ -5,6 +5,9 @@ import type { } from 'graphql-sse'; import { Fetcher, FetcherParams } from './types'; +/** + * Based on https://gist.github.com/enisdenjo/d7bc1a013433502349d2763c3d2f2b79 + */ export async function createSseFetcher(opts: ClientOptions): Promise { const { createClient } = process.env.USE_IMPORT === 'false' diff --git a/packages/graphiql/cypress/e2e/graphql-ws.cy.ts b/packages/graphiql/cypress/e2e/graphql-ws.cy.ts deleted file mode 100644 index 89bb792aeb6..00000000000 --- a/packages/graphiql/cypress/e2e/graphql-ws.cy.ts +++ /dev/null @@ -1,20 +0,0 @@ -describe('IncrementalDelivery support via fetcher', () => { - describe('When operation contains @stream', () => { - const testSubscription = /* GraphQL */ ` - subscription TestSubscription($delay: Int) { - message(delay: $delay) - } - `; - const mockSubscriptionSuccess = { - data: { - message: 'Zdravo', - }, - }; - - it('Expects a subscription to resolve', () => { - cy.visitWithOp({ query: testSubscription, variables: { delay: 0 } }); - cy.clickExecuteQuery(); - cy.assertQueryResult(mockSubscriptionSuccess); - }); - }); -}); diff --git a/packages/graphiql/cypress/e2e/ws-sse.cy.ts b/packages/graphiql/cypress/e2e/ws-sse.cy.ts new file mode 100644 index 00000000000..b5d754d5b3a --- /dev/null +++ b/packages/graphiql/cypress/e2e/ws-sse.cy.ts @@ -0,0 +1,33 @@ +describe('IncrementalDelivery support via fetcher', () => { + function assertResponse() { + for (const message of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) { + cy.assertQueryResult({ data: { message } }); + } + } + + it('should work with ws', () => { + const testSubscription = /* GraphQL */ ` + subscription TestSubscription { + message + } + `; + + cy.visit(`/?query=${testSubscription}`); + cy.clickExecuteQuery(); + assertResponse(); + }); + + it('should work with sse', () => { + const testSubscription = /* GraphQL */ ` + subscription Test { + message + } + `; + + cy.visit( + `/?sseUrl=http://localhost:8080/graphql/stream&query=${testSubscription}`, + ); + cy.clickExecuteQuery(); + assertResponse(); + }); +}); diff --git a/packages/graphiql/test/schema.js b/packages/graphiql/test/schema.js index 6dee5086142..f4dd5c5377f 100644 --- a/packages/graphiql/test/schema.js +++ b/packages/graphiql/test/schema.js @@ -374,7 +374,7 @@ const TestSubscriptionType = new GraphQLObjectType({ }, async *subscribe(root, args) { for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) { - if (args?.delay) { + if (args.delay) { await sleep(args.delay); } yield { message: hi }; From 1cb289eba81087065f8eacd0a3e5d2aa9266790e Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 02:26:25 +0200 Subject: [PATCH 03/12] lint --- packages/graphiql/test/e2e-server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/graphiql/test/e2e-server.js b/packages/graphiql/test/e2e-server.js index 2dfbd1ca03d..4c0fc5c8e94 100644 --- a/packages/graphiql/test/e2e-server.js +++ b/packages/graphiql/test/e2e-server.js @@ -17,6 +17,7 @@ const { const WebSocketsServer = require('./afterDevServer'); const { schema, sseSchema } = require('./schema'); const { customExecute } = require('./execute'); +// eslint-disable-next-line import-x/no-extraneous-dependencies const { createHandler } = require('graphql-sse/lib/use/express'); const app = express(); From 6bdfd6a58699c3c09d8131125cdf671454c77e16 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 02:29:25 +0200 Subject: [PATCH 04/12] fix jest --- .../src/__tests__/MessageProcessor.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ffbe0dc6f36..d50b84b1852 100644 --- a/packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts +++ b/packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts @@ -367,7 +367,7 @@ 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')); + mockSchema(require('../../../graphiql/test/schema').schema); const project = new MockProject({ files: [ @@ -499,7 +499,7 @@ describe('MessageProcessor with config', () => { }); it('caches multiple projects with files and schema with a URL config and a local schema', async () => { - mockSchema(require('../../../graphiql/test/schema')); + mockSchema(require('../../../graphiql/test/schema').schema); const project = new MockProject({ files: [ From 143413b848878b2cedbc60f488335257e43d6233 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 02:32:43 +0200 Subject: [PATCH 05/12] format --- packages/graphiql/resources/renderExample.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql/resources/renderExample.js b/packages/graphiql/resources/renderExample.js index bdee2ccaa3d..b58b37111c9 100644 --- a/packages/graphiql/resources/renderExample.js +++ b/packages/graphiql/resources/renderExample.js @@ -78,7 +78,7 @@ root.render( fetcher: GraphiQL.createFetcher({ url: getSchemaUrl(), subscriptionUrl: 'ws://localhost:8081/subscriptions', - sseUrl: parameters.sseUrl + sseUrl: parameters.sseUrl, }), query: parameters.query, variables: parameters.variables, From 4ef45f673d4a5ae2938aa3a05b791b76c95dbdd4 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 02:33:34 +0200 Subject: [PATCH 06/12] fix package modules --- packages/monaco-graphql/test/monaco-editor.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/monaco-graphql/test/monaco-editor.test.ts b/packages/monaco-graphql/test/monaco-editor.test.ts index b9fc918b879..52ea84e5a49 100644 --- a/packages/monaco-graphql/test/monaco-editor.test.ts +++ b/packages/monaco-graphql/test/monaco-editor.test.ts @@ -15,7 +15,7 @@ describe('monaco-editor', () => { // expect(lines[1]).toMatch(' building for production...'); // expect(lines[2]).toBe('transforming...'); expect(lines[3]).toMatch( - `✓ ${parseInt(version, 10) > 16 ? 862 : 843} modules transformed.`, + `✓ ${parseInt(version, 10) > 16 ? 869 : 843} modules transformed.`, ); // expect(lines[4]).toBe('rendering chunks...'); // expect(lines[5]).toBe('computing gzip size...'); From 59bf24724b3083fcb3c571daa1cda49440838301 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 03:06:44 +0200 Subject: [PATCH 07/12] f1xes --- packages/graphiql/test/afterDevServer.js | 2 +- packages/graphiql/test/e2e-server.js | 2 +- packages/graphiql/test/schema.js | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/graphiql/test/afterDevServer.js b/packages/graphiql/test/afterDevServer.js index 6d54fa923fa..d9912370978 100644 --- a/packages/graphiql/test/afterDevServer.js +++ b/packages/graphiql/test/afterDevServer.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies const { useServer } = require('graphql-ws/lib/use/ws'); const { Server: WebSocketServer } = require('ws'); -const schema = require('./schema'); +const { schema } = require('./schema'); module.exports = function afterDevServer(_app, _server, _compiler) { const wsServer = new WebSocketServer({ diff --git a/packages/graphiql/test/e2e-server.js b/packages/graphiql/test/e2e-server.js index 4c0fc5c8e94..f57a4668f1f 100644 --- a/packages/graphiql/test/e2e-server.js +++ b/packages/graphiql/test/e2e-server.js @@ -67,7 +67,7 @@ app.use('/graphql/stream', (req, res, next) => { }); // Create the GraphQL over SSE handler -const sseHandler = createHandler({ schema: sseSchema }); +const sseHandler = createHandler({ schema, execute: customExecute }); // Serve all methods on `/graphql/stream` app.use('/graphql/stream', sseHandler); diff --git a/packages/graphiql/test/schema.js b/packages/graphiql/test/schema.js index f4dd5c5377f..0b444783b9c 100644 --- a/packages/graphiql/test/schema.js +++ b/packages/graphiql/test/schema.js @@ -5,6 +5,9 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ + +const graphql = require('graphql'); + const { GraphQLSchema, GraphQLObjectType, @@ -22,7 +25,12 @@ const { GraphQLStreamDirective, specifiedDirectives, version, -} = require('graphql'); +} = graphql; + +const directives = + parseInt(version, 10) > 16 + ? [...specifiedDirectives, GraphQLDeferDirective, GraphQLStreamDirective] + : specifiedDirectives; // Test Schema const TestEnum = new GraphQLEnumType({ @@ -384,20 +392,12 @@ const TestSubscriptionType = new GraphQLObjectType({ }, }); -const schemaConfig = { +const myTestSchema = new GraphQLSchema({ query: TestType, mutation: TestMutationType, subscription: TestSubscriptionType, description: 'This is a test schema for GraphiQL', -}; - -exports.schema = new GraphQLSchema({ - ...schemaConfig, - directives: - parseInt(version, 10) > 16 - ? [...specifiedDirectives, GraphQLDeferDirective, GraphQLStreamDirective] - : specifiedDirectives, + directives, }); -// Same schema but without defer/stream directives -exports.sseSchema = new GraphQLSchema(schemaConfig); +exports.schema = myTestSchema; From 33b59520cf308719ce1fc34bc4795be78236b154 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 03:07:12 +0200 Subject: [PATCH 08/12] lint --- packages/graphiql/test/e2e-server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql/test/e2e-server.js b/packages/graphiql/test/e2e-server.js index f57a4668f1f..28e27fc86b5 100644 --- a/packages/graphiql/test/e2e-server.js +++ b/packages/graphiql/test/e2e-server.js @@ -15,7 +15,7 @@ const { 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'); -const { schema, sseSchema } = require('./schema'); +const { schema } = require('./schema'); const { customExecute } = require('./execute'); // eslint-disable-next-line import-x/no-extraneous-dependencies const { createHandler } = require('graphql-sse/lib/use/express'); From 8fa8b8c0c3586f0792014a36e87d903f71f7e32f Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 03:08:44 +0200 Subject: [PATCH 09/12] polish --- packages/graphiql/cypress/e2e/ws-sse.cy.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/graphiql/cypress/e2e/ws-sse.cy.ts b/packages/graphiql/cypress/e2e/ws-sse.cy.ts index b5d754d5b3a..2a758f79441 100644 --- a/packages/graphiql/cypress/e2e/ws-sse.cy.ts +++ b/packages/graphiql/cypress/e2e/ws-sse.cy.ts @@ -1,4 +1,10 @@ describe('IncrementalDelivery support via fetcher', () => { + const testSubscription = /* GraphQL */ ` + subscription Test { + message + } + `; + function assertResponse() { for (const message of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) { cy.assertQueryResult({ data: { message } }); @@ -6,24 +12,12 @@ describe('IncrementalDelivery support via fetcher', () => { } it('should work with ws', () => { - const testSubscription = /* GraphQL */ ` - subscription TestSubscription { - message - } - `; - cy.visit(`/?query=${testSubscription}`); cy.clickExecuteQuery(); assertResponse(); }); it('should work with sse', () => { - const testSubscription = /* GraphQL */ ` - subscription Test { - message - } - `; - cy.visit( `/?sseUrl=http://localhost:8080/graphql/stream&query=${testSubscription}`, ); From b1c6bcc56ce70a10bd816a22c7eabf8851c623c2 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sat, 24 Aug 2024 03:10:57 +0200 Subject: [PATCH 10/12] rollback unneded changes --- packages/graphiql/test/afterDevServer.js | 2 +- packages/graphiql/test/beforeDevServer.js | 2 +- packages/graphiql/test/e2e-server.js | 2 +- packages/graphiql/test/schema.js | 2 +- .../src/__tests__/MessageProcessor.spec.ts | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/graphiql/test/afterDevServer.js b/packages/graphiql/test/afterDevServer.js index d9912370978..6d54fa923fa 100644 --- a/packages/graphiql/test/afterDevServer.js +++ b/packages/graphiql/test/afterDevServer.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies const { useServer } = require('graphql-ws/lib/use/ws'); const { Server: WebSocketServer } = require('ws'); -const { schema } = require('./schema'); +const schema = require('./schema'); module.exports = function afterDevServer(_app, _server, _compiler) { const wsServer = new WebSocketServer({ diff --git a/packages/graphiql/test/beforeDevServer.js b/packages/graphiql/test/beforeDevServer.js index 15df19ecdf7..55c634caeed 100644 --- a/packages/graphiql/test/beforeDevServer.js +++ b/packages/graphiql/test/beforeDevServer.js @@ -9,7 +9,7 @@ const express = require('express'); const path = require('node:path'); // eslint-disable-next-line import-x/no-extraneous-dependencies const { createHandler } = require('graphql-http/lib/use/express'); -const { schema } = require('./schema'); +const schema = require('./schema'); const badSchema = require('../cypress/fixtures/bad-schema.json'); module.exports = function beforeDevServer(app, _server, _compiler) { diff --git a/packages/graphiql/test/e2e-server.js b/packages/graphiql/test/e2e-server.js index 28e27fc86b5..7436e063f7d 100644 --- a/packages/graphiql/test/e2e-server.js +++ b/packages/graphiql/test/e2e-server.js @@ -15,7 +15,7 @@ const { 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'); -const { schema } = require('./schema'); +const schema = require('./schema'); const { customExecute } = require('./execute'); // eslint-disable-next-line import-x/no-extraneous-dependencies const { createHandler } = require('graphql-sse/lib/use/express'); diff --git a/packages/graphiql/test/schema.js b/packages/graphiql/test/schema.js index 0b444783b9c..ad65af8e939 100644 --- a/packages/graphiql/test/schema.js +++ b/packages/graphiql/test/schema.js @@ -400,4 +400,4 @@ const myTestSchema = new GraphQLSchema({ directives, }); -exports.schema = myTestSchema; +module.exports = myTestSchema; 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 d50b84b1852..ffbe0dc6f36 100644 --- a/packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts +++ b/packages/graphql-language-service-server/src/__tests__/MessageProcessor.spec.ts @@ -367,7 +367,7 @@ 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').schema); + mockSchema(require('../../../graphiql/test/schema')); const project = new MockProject({ files: [ @@ -499,7 +499,7 @@ describe('MessageProcessor with config', () => { }); it('caches multiple projects with files and schema with a URL config and a local schema', async () => { - mockSchema(require('../../../graphiql/test/schema').schema); + mockSchema(require('../../../graphiql/test/schema')); const project = new MockProject({ files: [ From 596f47a7064d16cf5995161d0ce003859b6ac0d8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 26 Aug 2024 14:56:26 +0200 Subject: [PATCH 11/12] Update packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts --- packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts b/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts index 36e3612d54f..9db82e5eb2d 100644 --- a/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts +++ b/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts @@ -43,7 +43,7 @@ export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher { : false; if (isSubscription) { - if (options.sseUrl) { + if (!options.subscriptionUrl.startsWith('ws')) { const sseFetcher = await createSseFetcher({ url: options.sseUrl }); return sseFetcher(graphQLParams); } From c35884dd99613b0a43a5bf081be2ecb30d89e5ff Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 26 Aug 2024 14:59:55 +0200 Subject: [PATCH 12/12] aa --- .changeset/nasty-beers-bow.md | 5 +++++ .../graphiql-toolkit/src/create-fetcher/createFetcher.ts | 9 +++++++-- packages/graphiql-toolkit/src/create-fetcher/types.ts | 6 +----- packages/graphiql/cypress/e2e/ws-sse.cy.ts | 2 +- packages/graphiql/resources/renderExample.js | 4 ++-- 5 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 .changeset/nasty-beers-bow.md diff --git a/.changeset/nasty-beers-bow.md b/.changeset/nasty-beers-bow.md new file mode 100644 index 00000000000..2fe9941da42 --- /dev/null +++ b/.changeset/nasty-beers-bow.md @@ -0,0 +1,5 @@ +--- +'@graphiql/toolkit': minor +--- + +support graphql SSE for `options.subscriptionUrl` diff --git a/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts b/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts index 9db82e5eb2d..e242701984c 100644 --- a/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts +++ b/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts @@ -43,8 +43,13 @@ export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher { : false; if (isSubscription) { - if (!options.subscriptionUrl.startsWith('ws')) { - const sseFetcher = await createSseFetcher({ url: options.sseUrl }); + if ( + options.subscriptionUrl && + !options.subscriptionUrl.startsWith('ws') + ) { + const sseFetcher = await createSseFetcher({ + url: options.subscriptionUrl, + }); return sseFetcher(graphQLParams); } diff --git a/packages/graphiql-toolkit/src/create-fetcher/types.ts b/packages/graphiql-toolkit/src/create-fetcher/types.ts index d14eb185bfe..82ed1d2df31 100644 --- a/packages/graphiql-toolkit/src/create-fetcher/types.ts +++ b/packages/graphiql-toolkit/src/create-fetcher/types.ts @@ -81,13 +81,9 @@ export interface CreateFetcherOptions { */ url: string; /** - * url for websocket subscription requests + * url for websocket subscription requests or SSE */ subscriptionUrl?: string; - /** - * url for graphql-sse - */ - sseUrl?: string; /** * `wsClient` implementation that matches `ws-graphql` signature, * whether via `createClient()` itself or another client. diff --git a/packages/graphiql/cypress/e2e/ws-sse.cy.ts b/packages/graphiql/cypress/e2e/ws-sse.cy.ts index 2a758f79441..916506d3aca 100644 --- a/packages/graphiql/cypress/e2e/ws-sse.cy.ts +++ b/packages/graphiql/cypress/e2e/ws-sse.cy.ts @@ -19,7 +19,7 @@ describe('IncrementalDelivery support via fetcher', () => { it('should work with sse', () => { cy.visit( - `/?sseUrl=http://localhost:8080/graphql/stream&query=${testSubscription}`, + `/?subscriptionUrl=http://localhost:8080/graphql/stream&query=${testSubscription}`, ); cy.clickExecuteQuery(); assertResponse(); diff --git a/packages/graphiql/resources/renderExample.js b/packages/graphiql/resources/renderExample.js index b58b37111c9..7b42b47a1a8 100644 --- a/packages/graphiql/resources/renderExample.js +++ b/packages/graphiql/resources/renderExample.js @@ -77,8 +77,8 @@ root.render( React.createElement(GraphiQL, { fetcher: GraphiQL.createFetcher({ url: getSchemaUrl(), - subscriptionUrl: 'ws://localhost:8081/subscriptions', - sseUrl: parameters.sseUrl, + subscriptionUrl: + parameters.subscriptionUrl || 'ws://localhost:8081/subscriptions', }), query: parameters.query, variables: parameters.variables,