Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: createFetcher legacyClient option, simplify, docs #1819

Merged
merged 3 commits into from
Apr 3, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions .changeset/two-bulldogs-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphiql/toolkit': minor
'graphiql': patch
---

`GraphiQL.createClient()` accepts custom `legacyClient`, exports typescript types, fixes #1800
12 changes: 7 additions & 5 deletions packages/graphiql-toolkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
35 changes: 32 additions & 3 deletions packages/graphiql-toolkit/docs/create-fetcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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';
Expand All @@ -123,6 +127,31 @@ export const App = () => <GraphiQL fetcher={fetcher} />;
ReactDOM.render(document.getElementByID('graphiql'), <App />);
```

#### 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 = () => <GraphiQL fetcher={fetcher} />;

ReactDOM.render(document.getElementByID('graphiql'), <App />);
```

#### Custom `fetcher` Example

For SSR, we might want to use something like `isomorphic-fetch`
Expand All @@ -148,4 +177,4 @@ ReactDOM.render(document.getElementByID('graphiql'), <App />);

## Credits

This is inspired from `graphql-subscriptions-fetcher` and thanks to @Urigo
This is originally inspired by `graphql-subscriptions-fetcher` created by @Urigo
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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([]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ 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');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {
createMultipartFetcher,
createSimpleFetcher,
isSubscriptionWithName,
createWebsocketsFetcherFromUrl,
createWebsocketsFetcherFromClient,
getWsFetcher,
} from './lib';

/**
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions packages/graphiql-toolkit/src/create-fetcher/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './types';
export { createGraphiQLFetcher } from './createFetcher';

// TODO: move the most useful utilities from graphiql to here
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
};

Expand Down Expand Up @@ -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);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
16 changes: 1 addition & 15 deletions packages/graphiql-toolkit/src/index.ts
Original file line number Diff line number Diff line change
@@ -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