diff --git a/.changeset/two-bulldogs-doubt.md b/.changeset/two-bulldogs-doubt.md
new file mode 100644
index 00000000000..01a8939e915
--- /dev/null
+++ b/.changeset/two-bulldogs-doubt.md
@@ -0,0 +1,8 @@
+---
+'@graphiql/toolkit': minor
+'graphiql': patch
+---
+
+`GraphiQL.createClient()` accepts custom `legacyClient`, exports typescript types, fixes #1800.
+
+`createGraphiQLFetcher` now only attempts an `graphql-ws` connection when only `subscriptionUrl` is provided. In order to use `graphql-transport-ws`, you'll need to provide the `legacyClient` option only, and no `subscriptionUrl` or `wsClient` option.
diff --git a/packages/graphiql-toolkit/README.md b/packages/graphiql-toolkit/README.md
index 15bd97f0dae..2848d8fba93 100644
--- a/packages/graphiql-toolkit/README.md
+++ b/packages/graphiql-toolkit/README.md
@@ -2,14 +2,16 @@
General purpose library as a dependency of GraphiQL.
-The goal is to make this and related packages a set of general purpose tools used to build an end implementation like GraphiQL
+Part of the GraphiQL 2.0.0 initiative.
-It also allows us to share utilities, libraries and components that can be used by
+## Docs
+
+- **`createFetcher` [(Docs)](./docs/create-fetcher.md)** : a utility for creating a `fetcher` prop implementation for HTTP GET, POST including multipart, websockets fetcher
+- more to come!
## Todo
- [x] Begin porting common type definitions used by GraphiQL and it's dependencies
-- [ ] Port over the GraphiQL components library created by @walaura and designed by @orta
+- [ ] `createFetcher` utility for an easier `fetcher`
- [ ] Migrate over general purpose `graphiql/src/utilities`
-- [ ] Frontend framework agnostic state implementation
-- [ ] React components and hooks? Or should react specifics live seperately?
+- [ ] Utility to generate json schema spec from `getQueryFacts` for monaco, vscode, etc
diff --git a/packages/graphiql-toolkit/docs/create-fetcher.md b/packages/graphiql-toolkit/docs/create-fetcher.md
index 4e678526b5c..89120a07479 100644
--- a/packages/graphiql-toolkit/docs/create-fetcher.md
+++ b/packages/graphiql-toolkit/docs/create-fetcher.md
@@ -79,12 +79,16 @@ This is url used for all `HTTP` requests, and for schema introspection.
#### `subscriptionUrl`
-This generates a `graphql-ws` client.
+This generates a `graphql-ws` client using the provided url. Note that a server must be compatible with the new `graphql-ws` subscriptions spec for this to work.
#### `wsClient`
provide your own subscriptions client. bypasses `subscriptionUrl`. In theory, this could be any client using any transport, as long as it matches `graphql-ws` `Client` signature.
+#### `legacyClient`
+
+provide a legacy subscriptions client. bypasses `subscriptionUrl`. In theory, this could be any client using any transport, as long as it matches `subscriptions-transport-ws` `Client` signature.
+
#### `headers`
Pass headers to any and all requests
@@ -97,7 +101,7 @@ Pass a custom fetch implementation such as `isomorphic-feth`
#### Custom `wsClient` Example
-Just by providing the `subscriptionUrl`
+Just by providing the `wsClient`
```ts
import * as React from 'react';
@@ -123,6 +127,31 @@ export const App = () => ;
ReactDOM.render(document.getElementByID('graphiql'), );
```
+#### Custom `legacyClient` Example
+
+By providing the `legacyClient` you can support a `subscriptions-transport-ws` client implementation, or equivalent
+
+```ts
+import * as React from 'react';
+import ReactDOM from 'react-dom';
+import { GraphiQL } from 'graphiql';
+import { SubscriptionClient } from 'subscriptions-transport-ws';
+import { createGraphiQLFetcher } from '@graphiql/toolkit';
+
+const url = 'https://myschema.com/graphql';
+
+const subscriptionUrl = 'wss://myschema.com/graphql';
+
+const fetcher = createGraphiQLFetcher({
+ url,
+ legacyClient: new SubscriptionsClient(subscriptionUrl),
+});
+
+export const App = () => ;
+
+ReactDOM.render(document.getElementByID('graphiql'), );
+```
+
#### Custom `fetcher` Example
For SSR, we might want to use something like `isomorphic-fetch`
@@ -148,4 +177,4 @@ ReactDOM.render(document.getElementByID('graphiql'), );
## Credits
-This is inspired from `graphql-subscriptions-fetcher` and thanks to @Urigo
+This is originally inspired by `graphql-subscriptions-fetcher` created by @Urigo
diff --git a/packages/graphiql-toolkit/package.json b/packages/graphiql-toolkit/package.json
index 997856d3fc9..dbc8dba4b57 100644
--- a/packages/graphiql-toolkit/package.json
+++ b/packages/graphiql-toolkit/package.json
@@ -21,13 +21,16 @@
"scripts": {},
"dependencies": {
"@n1ru4l/push-pull-async-iterable-iterator": "^2.0.1",
- "graphql-ws": "^4.1.0",
- "meros": "^1.1.2",
- "subscriptions-transport-ws": "^0.9.18"
+ "graphql-ws": "^4.3.2",
+ "meros": "^1.1.4"
},
"devDependencies": {
"isomorphic-fetch": "^3.0.0",
- "graphql": "experimental-stream-defer"
+ "graphql": "experimental-stream-defer",
+ "subscriptions-transport-ws": "^0.9.18"
+ },
+ "optionalDependencies": {
+ "subscriptions-transport-ws": "^0.9.18"
},
"keywords": [
"graphql",
diff --git a/packages/graphiql-toolkit/src/__tests__/buildFetcher.spec.ts b/packages/graphiql-toolkit/src/create-fetcher/__tests__/buildFetcher.spec.ts
similarity index 80%
rename from packages/graphiql-toolkit/src/__tests__/buildFetcher.spec.ts
rename to packages/graphiql-toolkit/src/create-fetcher/__tests__/buildFetcher.spec.ts
index 2fd6de568bd..620177fa9fe 100644
--- a/packages/graphiql-toolkit/src/__tests__/buildFetcher.spec.ts
+++ b/packages/graphiql-toolkit/src/create-fetcher/__tests__/buildFetcher.spec.ts
@@ -13,8 +13,11 @@ import {
createWebsocketsFetcherFromUrl,
createMultipartFetcher,
createSimpleFetcher,
+ createWebsocketsFetcherFromClient,
+ createLegacyWebsocketsFetcher,
} from '../lib';
import { createClient } from 'graphql-ws';
+import { SubscriptionClient } from 'subscriptions-transport-ws';
const exampleWithSubscripton = /* GraphQL */ `
subscription Example {
@@ -85,9 +88,6 @@ describe('createGraphiQLFetcher', () => {
createGraphiQLFetcher(args);
expect(createMultipartFetcher.mock.calls).toEqual([[args, fetch]]);
- expect(createWebsocketsFetcherFromUrl.mock.calls).toEqual([
- [args.subscriptionUrl],
- ]);
});
it('returns fetcher with custom wsClient', () => {
@@ -106,4 +106,23 @@ describe('createGraphiQLFetcher', () => {
expect(createMultipartFetcher.mock.calls).toEqual([[args, fetch]]);
expect(createWebsocketsFetcherFromUrl.mock.calls).toEqual([]);
});
+
+ it('returns fetcher with custom legacyClient', () => {
+ createClient.mockReturnValue('WSClient');
+ createLegacyWebsocketsFetcher.mockReturnValue('CustomWSSFetcher');
+
+ const legacyClient = new SubscriptionClient(wssURL);
+ const args = {
+ url: serverURL,
+ legacyClient,
+ enableIncrementalDelivery: true,
+ };
+
+ createGraphiQLFetcher(args);
+
+ expect(createMultipartFetcher.mock.calls).toEqual([[args, fetch]]);
+ expect(createWebsocketsFetcherFromUrl.mock.calls).toEqual([]);
+ expect(createWebsocketsFetcherFromClient.mock.calls).toEqual([]);
+ expect(createLegacyWebsocketsFetcher.mock.calls).toEqual([]);
+ });
});
diff --git a/packages/graphiql-toolkit/src/__tests__/lib.spec.ts b/packages/graphiql-toolkit/src/create-fetcher/__tests__/lib.spec.ts
similarity index 51%
rename from packages/graphiql-toolkit/src/__tests__/lib.spec.ts
rename to packages/graphiql-toolkit/src/create-fetcher/__tests__/lib.spec.ts
index 77367bb0dd1..48d18a7c99b 100644
--- a/packages/graphiql-toolkit/src/__tests__/lib.spec.ts
+++ b/packages/graphiql-toolkit/src/create-fetcher/__tests__/lib.spec.ts
@@ -1,5 +1,9 @@
import { parse } from 'graphql';
-import { isSubscriptionWithName, createWebsocketsFetcherFromUrl } from '../lib';
+import {
+ isSubscriptionWithName,
+ createWebsocketsFetcherFromUrl,
+ getWsFetcher,
+} from '../lib';
import 'isomorphic-fetch';
@@ -48,14 +52,53 @@ describe('createWebsocketsFetcherFromUrl', () => {
createWebsocketsFetcherFromUrl('wss://example.com');
// @ts-ignore
expect(createClient.mock.calls[0][0]).toEqual({ url: 'wss://example.com' });
- expect(SubscriptionClient.mock.calls).toEqual([]);
});
- it('creates a websockets client using provided url that fails to legacy client', async () => {
+ it('creates a websockets client using provided url that fails', async () => {
createClient.mockReturnValue(false);
- await createWebsocketsFetcherFromUrl('wss://example.com');
+ expect(
+ await createWebsocketsFetcherFromUrl('wss://example.com'),
+ ).toThrowError();
// @ts-ignore
expect(createClient.mock.calls[0][0]).toEqual({ url: 'wss://example.com' });
- expect(SubscriptionClient.mock.calls[0][0]).toEqual('wss://example.com');
+ });
+});
+
+describe('getWsFetcher', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+ it('provides an observable wsClient when custom wsClient option is provided', () => {
+ createClient.mockReturnValue(true);
+ getWsFetcher({
+ url: '',
+ // @ts-ignore
+ wsClient: true,
+ });
+ // @ts-ignore
+ expect(createClient.mock.calls).toHaveLength(0);
+ });
+ it('creates a subscriptions-transports-ws observable when custom legacyClient option is provided', () => {
+ createClient.mockReturnValue(true);
+ getWsFetcher({
+ url: '',
+ // @ts-ignore
+ legacyClient: true,
+ });
+ // @ts-ignore
+ expect(createClient.mock.calls).toHaveLength(0);
+ expect(SubscriptionClient.mock.calls).toHaveLength(0);
+ });
+
+ it('if subscriptionsUrl is provided, create a client on the fly', () => {
+ createClient.mockReturnValue(true);
+ getWsFetcher({
+ url: '',
+ subscriptionUrl: 'wss://example',
+ });
+ expect(createClient.mock.calls[0]).toEqual([
+ { connectionParams: undefined, url: 'wss://example' },
+ ]);
+ expect(SubscriptionClient.mock.calls).toHaveLength(0);
});
});
diff --git a/packages/graphiql-toolkit/src/createFetcher.ts b/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts
similarity index 81%
rename from packages/graphiql-toolkit/src/createFetcher.ts
rename to packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts
index 910d9eb0ed0..55140749f30 100644
--- a/packages/graphiql-toolkit/src/createFetcher.ts
+++ b/packages/graphiql-toolkit/src/create-fetcher/createFetcher.ts
@@ -4,8 +4,7 @@ import {
createMultipartFetcher,
createSimpleFetcher,
isSubscriptionWithName,
- createWebsocketsFetcherFromUrl,
- createWebsocketsFetcherFromClient,
+ getWsFetcher,
} from './lib';
/**
@@ -18,7 +17,6 @@ import {
*/
export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher {
let httpFetch;
- let wsFetcher: null | Fetcher | void = null;
if (typeof window !== null && window?.fetch) {
httpFetch = window.fetch;
}
@@ -37,13 +35,7 @@ export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher {
// simpler fetcher for schema requests
const simpleFetcher = createSimpleFetcher(options, httpFetch);
- if (options.subscriptionUrl) {
- wsFetcher = createWebsocketsFetcherFromUrl(options.subscriptionUrl);
- }
- if (options.wsClient) {
- wsFetcher = createWebsocketsFetcherFromClient(options.wsClient);
- }
-
+ const wsFetcher = getWsFetcher(options);
const httpFetcher = options.enableIncrementalDelivery
? createMultipartFetcher(options, httpFetch)
: simpleFetcher;
@@ -65,7 +57,7 @@ export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher {
`Your GraphiQL createFetcher is not properly configured for websocket subscriptions yet. ${
options.subscriptionUrl
? `Provided URL ${options.subscriptionUrl} failed`
- : `Try providing options.subscriptionUrl or options.wsClient first.`
+ : `Please provide subscriptionUrl, wsClient or legacyClient option first.`
}`,
);
}
diff --git a/packages/graphiql-toolkit/src/create-fetcher/index.ts b/packages/graphiql-toolkit/src/create-fetcher/index.ts
new file mode 100644
index 00000000000..64e2d32f4fc
--- /dev/null
+++ b/packages/graphiql-toolkit/src/create-fetcher/index.ts
@@ -0,0 +1,4 @@
+export * from './types';
+export { createGraphiQLFetcher } from './createFetcher';
+
+// TODO: move the most useful utilities from graphiql to here
diff --git a/packages/graphiql-toolkit/src/lib.ts b/packages/graphiql-toolkit/src/create-fetcher/lib.ts
similarity index 84%
rename from packages/graphiql-toolkit/src/lib.ts
rename to packages/graphiql-toolkit/src/create-fetcher/lib.ts
index 4616c9d4693..e37c79a16f7 100644
--- a/packages/graphiql-toolkit/src/lib.ts
+++ b/packages/graphiql-toolkit/src/create-fetcher/lib.ts
@@ -71,33 +71,16 @@ export const createWebsocketsFetcherFromUrl = (
url: string,
connectionParams?: ClientOptions['connectionParams'],
) => {
- let wsClient: Client | null = null;
- let legacyClient: SubscriptionClient | null = null;
- if (url) {
- try {
- try {
- // TODO: defaults?
- wsClient = createClient({
- url,
- connectionParams,
- });
- if (!wsClient) {
- legacyClient = new SubscriptionClient(url, { connectionParams });
- }
- } catch (err) {
- legacyClient = new SubscriptionClient(url, { connectionParams });
- }
- } catch (err) {
- console.error(`Error creating websocket client for:\n${url}\n\n${err}`);
- }
- }
-
- if (wsClient) {
+ let wsClient;
+ try {
+ // TODO: defaults?
+ wsClient = createClient({
+ url,
+ connectionParams,
+ });
return createWebsocketsFetcherFromClient(wsClient);
- } else if (legacyClient) {
- return createLegacyWebsocketsFetcher(legacyClient);
- } else if (url) {
- throw Error('subscriptions client failed to initialize');
+ } catch (err) {
+ console.error(`Error creating websocket client for:\n${url}\n\n${err}`);
}
};
@@ -169,3 +152,20 @@ export const createMultipartFetcher = (
yield chunk.map(part => part.body);
}
};
+
+/**
+ * If `wsClient` or `legacyClient` are provided, then `subscriptionUrl` is overridden.
+ * @param options {CreateFetcherOptions}
+ * @returns
+ */
+export const getWsFetcher = (options: CreateFetcherOptions) => {
+ if (options.wsClient) {
+ return createWebsocketsFetcherFromClient(options.wsClient);
+ }
+ if (options.legacyClient) {
+ return createLegacyWebsocketsFetcher(options.legacyClient);
+ }
+ if (options.subscriptionUrl) {
+ return createWebsocketsFetcherFromUrl(options.subscriptionUrl);
+ }
+};
diff --git a/packages/graphiql-toolkit/src/types.ts b/packages/graphiql-toolkit/src/create-fetcher/types.ts
similarity index 93%
rename from packages/graphiql-toolkit/src/types.ts
rename to packages/graphiql-toolkit/src/create-fetcher/types.ts
index fd188df535c..ea5c5731e56 100644
--- a/packages/graphiql-toolkit/src/types.ts
+++ b/packages/graphiql-toolkit/src/create-fetcher/types.ts
@@ -89,6 +89,11 @@ export interface CreateFetcherOptions {
* whether via `createClient()` itself or another client.
*/
wsClient?: Client;
+ /**
+ * `legacyClient` implementation that matches `subscriptions-transport-ws` signature,
+ * whether via `new SubcriptionsClient()` itself or another client with a similar signature.
+ */
+ legacyClient?: SubscriptionClient;
/**
* Headers you can provide statically.
*
diff --git a/packages/graphiql-toolkit/src/index.ts b/packages/graphiql-toolkit/src/index.ts
index f424dd8eb62..a3c68903ec5 100644
--- a/packages/graphiql-toolkit/src/index.ts
+++ b/packages/graphiql-toolkit/src/index.ts
@@ -1,16 +1,2 @@
-export * from './types';
-export { createGraphiQLFetcher } from './createFetcher';
-
-export type {
- CreateFetcherOptions,
- Fetcher,
- FetcherOpts,
- FetcherParams,
- FetcherResult,
- FetcherResultPayload,
- FetcherReturnType,
- Observable,
- Unsubscribable,
-} from './types';
-
+export * from './create-fetcher';
// TODO: move the most useful utilities from graphiql to here
diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json
index 62ba33e9b5a..caf56655aa8 100644
--- a/packages/graphiql/package.json
+++ b/packages/graphiql/package.json
@@ -74,8 +74,6 @@
"express-graphql": "experimental-stream-defer",
"fork-ts-checker-webpack-plugin": "4.1.3",
"graphql": "experimental-stream-defer",
- "graphql-transport-ws": "^1.9.0",
- "graphql-ws": "^4.1.0",
"html-webpack-plugin": "^4.0.4",
"identity-obj-proxy": "^3.0.0",
"jest": "^24.8.0",
diff --git a/yarn.lock b/yarn.lock
index 6cd85b5a170..340da584dd2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11137,17 +11137,10 @@ graphql-config@^3.0.2:
string-env-interpolation "1.0.1"
tslib "^2.0.0"
-graphql-transport-ws@^1.9.0:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/graphql-transport-ws/-/graphql-transport-ws-1.9.0.tgz#81891de870619d4e39242954a9e0f832dd980179"
- integrity sha512-yrw7nIR4V+lWWRCVCa5ogagHWjlPLDO/Ld1177V4S4fqcMO4qVJyTgMKbTcAUXBhlEATqN7Scb5Oy8Ly+zKFwg==
- dependencies:
- ws "^7.3.1"
-
-graphql-ws@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-4.1.0.tgz#cebe281474b5501d7be66210fb5711633b27fd78"
- integrity sha512-DxJP1y2YzCqVLy7DrQN0iuR2l48vMOBWukX2d/J9aN2o5x9un5psIIq/2UFRh91UGARmfvPH86y1p4qbC1dITg==
+graphql-ws@^4.3.2:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-4.3.2.tgz#c58b03acc3bd5d4a92a6e9f729d29ba5e90d46a3"
+ integrity sha512-jsW6eOlko7fJek1iaSGQFj97AWuhexL9A3PuxYtyke/VlMdbSFzmDR4PlPPCTBBskRg6tNRb5RTbBVSd2T60JQ==
graphql@experimental-stream-defer:
version "15.4.0-experimental-stream-defer.1"
@@ -14471,10 +14464,10 @@ merge@^1.2.0:
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
-meros@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/meros/-/meros-1.1.2.tgz#12d5f520458ba8ae1536092824c1744fa09cf79d"
- integrity sha512-BvOjEcEtGBOSts+lCCuqDe4LhSvzwQsQNxDB86ZY8RiAVQsPcmzxqm1/OjBBWv7vCufEEq8jstf4QJBBAHlDXg==
+meros@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/meros/-/meros-1.1.4.tgz#c17994d3133db8b23807f62bec7f0cb276cfd948"
+ integrity sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==
methods@~1.1.2:
version "1.1.2"
@@ -21333,11 +21326,6 @@ ws@^7.2.1, ws@^7.2.3:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
-ws@^7.3.1:
- version "7.4.2"
- resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.2.tgz#782100048e54eb36fe9843363ab1c68672b261dd"
- integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==
-
wsrun@^5.2.4:
version "5.2.4"
resolved "https://registry.yarnpkg.com/wsrun/-/wsrun-5.2.4.tgz#6eb6c3ccd3327721a8df073a5e3578fb0dea494e"