Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
7 changes: 6 additions & 1 deletion packages/graphiql-toolkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
87 changes: 87 additions & 0 deletions packages/graphiql-toolkit/src/create-fetcher/create-sse-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type {
ClientOptions,
createClient as createClientType,
ExecutionResult,
} from 'graphql-sse';
import { Fetcher, FetcherParams } from './types';

/**
* Based on https://gist.github.com/enisdenjo/d7bc1a013433502349d2763c3d2f2b79
*/
export async function createSseFetcher(opts: ClientOptions): Promise<Fetcher> {
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
Copy link

@derekwilling derekwilling Sep 10, 2024

Choose a reason for hiding this comment

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

How is this supposed to be configured by the user? Does it not support distinct connections mode?

I'm having issues getting the client to send POST requests (distinct connections mode) instead of PUT requests (single connection mode).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nice catch, we need to pass options in

const sseFetcher = await createSseFetcher({
url: options.subscriptionUrl,
});

// @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<Record<string, unknown>, 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;
}
11 changes: 9 additions & 2 deletions packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions packages/graphiql-toolkit/src/create-fetcher/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion packages/graphiql-toolkit/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const opts: Options = {
entry: ['src/**/*.ts', '!**/__tests__'],
bundle: false,
clean: true,
dts: true,
minifySyntax: true,
};

Expand All @@ -17,6 +16,7 @@ export default defineConfig([
env: {
USE_IMPORT: 'true',
},
dts: true,
},
{
...opts,
Expand Down
20 changes: 0 additions & 20 deletions packages/graphiql/cypress/e2e/graphql-ws.cy.ts

This file was deleted.

60 changes: 14 additions & 46 deletions packages/graphiql/cypress/e2e/incremental-delivery.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: 'Здорово' },
],
},
};
Expand Down Expand Up @@ -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,
},
Expand Down
27 changes: 27 additions & 0 deletions packages/graphiql/cypress/e2e/ws-sse.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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 } });
}
}

it('should work with ws', () => {
cy.visit(`/?query=${testSubscription}`);
cy.clickExecuteQuery();
assertResponse();
});

it('should work with sse', () => {
cy.visit(
`/?sseUrl=http://localhost:8080/graphql/stream&query=${testSubscription}`,
);
cy.clickExecuteQuery();
assertResponse();
});
});
1 change: 1 addition & 0 deletions packages/graphiql/resources/renderExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
29 changes: 29 additions & 0 deletions packages/graphiql/test/e2e-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const {
const WebSocketsServer = require('./afterDevServer');
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');

const app = express();

Expand All @@ -42,6 +44,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, execute: customExecute });
// Serve all methods on `/graphql/stream`
app.use('/graphql/stream', sseHandler);

// Server
app.use(express.json());

Expand Down
2 changes: 1 addition & 1 deletion packages/graphiql/test/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,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 };
Expand Down
2 changes: 1 addition & 1 deletion packages/monaco-graphql/test/monaco-editor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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...');
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down