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

improvement: move incremental client from fmg to meros #1774

Merged
merged 9 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion packages/graphiql-create-fetcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

a utility for generating a full-featured fetcher for GraphiQL.

under the hood, it uses [`graphql-ws`](https://www.npmjs.com/package/graphql-ws) and [`fetch-multipart-graphql`](https://www.npmjs.com/package/fetch-multipart-graphql) to follow the [GraphQL over HTTP Working Group Spec](https://github.com/graphql/graphql-over-http) both accepted and advanced proposals.
under the hood, it uses [`graphql-ws`](https://www.npmjs.com/package/graphql-ws) and [`meros`](https://www.npmjs.com/package/meros) to follow the [GraphQL over HTTP Working Group Spec](https://github.com/graphql/graphql-over-http) both accepted and advanced proposals.

### Setup

Expand Down
2 changes: 1 addition & 1 deletion packages/graphiql-create-fetcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"dependencies": {
"graphql-ws": "^4.1.0",
"subscriptions-transport-ws": "^0.9.18",
"fetch-multipart-graphql": "^3.0.0",
"meros": "^1.0.0",
"@n1ru4l/push-pull-async-iterable-iterator": "^2.0.1",
"@graphiql/toolkit": "^0.0.1"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/graphiql-create-fetcher/src/createFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher {
}

const httpFetcher = options.enableIncrementalDelivery
? createMultipartFetcher(options)
? createMultipartFetcher(options, httpFetch)
: simpleFetcher;

return (graphQLParams, opts) => {
Expand Down
41 changes: 27 additions & 14 deletions packages/graphiql-create-fetcher/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { DocumentNode, visit } from 'graphql';
import fetchMultipart from 'fetch-multipart-graphql';
import { meros } from 'meros';
import { createClient, Client } from 'graphql-ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { makeAsyncIterableIteratorFromSink } from '@n1ru4l/push-pull-async-iterable-iterator';
import {
isAsyncIterable,
makeAsyncIterableIteratorFromSink,
} from '@n1ru4l/push-pull-async-iterable-iterator';

import type {
Fetcher,
FetcherResult,
FetcherParams,
FetcherOpts,
IncrementalDeliveryResult,
} from '@graphiql/toolkit';
import type { CreateFetcherOptions } from './types';

Expand Down Expand Up @@ -124,24 +128,33 @@ export const createLegacyWebsocketsFetcher = (
*/
export const createMultipartFetcher = (
options: CreateFetcherOptions,
): Fetcher => async (graphQLParams: FetcherParams, fetcherOpts?: FetcherOpts) =>
makeAsyncIterableIteratorFromSink<FetcherResult>(sink => {
fetchMultipart<FetcherResult>(options.url, {
httpFetch: typeof fetch,
): Fetcher =>
async function* (graphQLParams: FetcherParams, fetcherOpts?: FetcherOpts) {
const response = await httpFetch(options.url, {
method: 'POST',
body: JSON.stringify(graphQLParams),
headers: {
'content-type': 'application/json',
accept: 'application/json, multipart/mixed',
...options.headers,
// allow user-defined headers to override
// the static provided headers
...fetcherOpts?.headers,
},
onNext: parts => {
// @ts-ignore
sink.next(parts);
},
onError: sink.error,
onComplete: sink.complete,
});
return () => undefined;
});
}).then(response => meros<IncrementalDeliveryResult>(response));

// Follows the same as createSimpleFetcher above, in that we simply return it as json.
if (!isAsyncIterable(response)) {
return yield response.json();
}

for await (const part of response) {
if (!part.json) {
throw new Error(
`Expected multipart to be of json type, but got\n\nHeaders: ${part.headers}\n\nBody:${part.body}`,
);
}
yield part.body;
}
};
19 changes: 11 additions & 8 deletions packages/graphiql-toolkit/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,23 @@ export type FetcherOpts = {
documentAST?: DocumentNode;
};

export type FetcherResult =
export type IncrementalDeliveryResult = {
data?: any;
errors?: any[];
path?: [string, number];
hasNext: boolean;
};

export type FetcherResultObject =
maraisr marked this conversation as resolved.
Show resolved Hide resolved
| {
data: IntrospectionQuery;
errors?: Array<any>;
maraisr marked this conversation as resolved.
Show resolved Hide resolved
}
| string
| { data?: any; errors?: Array<any>; hasNext?: boolean }
// for IncrementalDelivery
| Array<{
data?: any;
errors?: any[];
path?: [string, number];
hasNext: boolean;
}>;
| IncrementalDeliveryResult;

export type FetcherResult = FetcherResultObject | string;
maraisr marked this conversation as resolved.
Show resolved Hide resolved

export type MaybePromise<T> = T | Promise<T>;

Expand Down
5 changes: 2 additions & 3 deletions packages/graphiql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"graphql-language-service": "^3.1.2",
"markdown-it": "^10.0.0",
"@graphiql/toolkit": "^0.0.1",
"@graphiql/create-fetcher": "^0.0.1"
"@graphiql/create-fetcher": "^0.0.1",
"dset": "^2.0.1"
},
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0",
Expand All @@ -65,7 +66,6 @@
"@types/markdown-it": "^0.0.9",
"@types/node": "^13.7.1",
"@types/testing-library__jest-dom": "^5.0.1",
"@n1ru4l/push-pull-async-iterable-iterator": "^2.0.1",
"babel-loader": "^8.1.0",
"babel-plugin-macros": "^2.8.0",
"cross-env": "^7.0.0",
Expand All @@ -77,7 +77,6 @@
"graphql": "experimental-stream-defer",
"graphql-ws": "^4.1.0",
"graphql-transport-ws": "^1.9.0",
"fetch-multipart-graphql": "^3.0.0",
"html-webpack-plugin": "^4.0.4",
"identity-obj-proxy": "^3.0.0",
"jest": "^24.8.0",
Expand Down
62 changes: 38 additions & 24 deletions packages/graphiql/src/components/GraphiQL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
} from 'graphql';
import copyToClipboard from 'copy-to-clipboard';
import { getFragmentDependenciesForAST } from 'graphql-language-service-utils';
// @ts-expect-error sadly no types
import dset from 'dset';

import { ExecuteButton } from './ExecuteButton';
import { ImagePreview } from './ImagePreview';
Expand Down Expand Up @@ -61,6 +63,8 @@ import type {
SyncFetcherResult,
Observable,
Unsubscribable,
IncrementalDeliveryResult,
FetcherResultObject,
} from '@graphiql/toolkit';

const DEFAULT_DOC_EXPLORER_WIDTH = 350;
Expand Down Expand Up @@ -1066,7 +1070,14 @@ export class GraphiQL extends React.Component<GraphiQLProps, GraphiQLState> {
);
}

const totalResponse: FetcherResult = { data: {} };
const isIncrementalDelivery = (
result: FetcherResult,
): result is IncrementalDeliveryResult => {
// @ts-ignore
maraisr marked this conversation as resolved.
Show resolved Hide resolved
return typeof result === 'object' && 'hasNext' in result;
maraisr marked this conversation as resolved.
Show resolved Hide resolved
};

const totalResponse: FetcherResultObject = { data: {} };

// _fetchQuery may return a subscription.
const subscription = await this._fetchQuery(
Expand All @@ -1077,31 +1088,35 @@ export class GraphiQL extends React.Component<GraphiQLProps, GraphiQLState> {
shouldPersistHeaders as boolean,
(result: FetcherResult) => {
if (queryID === this._editorQueryID) {
if (Array.isArray(result)) {
// for `IncrementalDelivery`
// https://github.com/graphql/graphql-over-http/blob/master/rfcs/IncrementalDelivery.md
// TODO: typescript types
const response = result.reduce((result, increment) => {
if (increment.errors) {
result.errors = [
...(result?.errors || []),
...increment?.errors,
];
}
if (increment.path) {
const [path, index] = increment.path;
const data = result?.data[path] || [];
// place them at the exact index. this matters a lot
data[index] = increment.data;
result.data = { ...result?.data, [path]: data };
} else {
result.data = { ...result?.data, ...increment.data };
if (isIncrementalDelivery(result)) {
// @ts-ignore
maraisr marked this conversation as resolved.
Show resolved Hide resolved
totalResponse.hasNext = result.hasNext;

if (result.errors) {
// We dont care about "index" here, just concat.
totalResponse.errors = [
...(totalResponse?.errors || []),
...result?.errors,
];
}

if (result.path) {
const pathKey = result.path.map(String).join('.');
if (!('data' in result)) {
throw new Error(
`Expected part to contain a data property, but got ${result}`,
);
}
return result;
}, totalResponse);
dset(totalResponse.data, pathKey, result.data);
} else if ('data' in result) {
// If there is no path, we don't know what to do with the payload,
// so we just set it.
totalResponse.data = result.data;
}

this.setState({
isWaitingForResponse: false,
response: GraphiQL.formatResult(response),
response: GraphiQL.formatResult(totalResponse),
});
} else {
this.setState({
Expand Down Expand Up @@ -1741,7 +1756,6 @@ function asyncIterableToPromise<T>(

iteratorNext()
.then(result => {
console.log(result.value);
resolve(result.value);
// ensure cleanup
iteratorReturn?.();
Expand Down
16 changes: 11 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10040,6 +10040,11 @@ dotenv@^6.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==

dset@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/dset/-/dset-2.0.1.tgz#a15fff3d1e4d60ac0c95634625cbd5441a76deb1"
integrity sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ==

duplexer2@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
Expand Down Expand Up @@ -11149,11 +11154,6 @@ [email protected]:
glob-to-regexp "^0.4.0"
path-to-regexp "^2.2.1"

fetch-multipart-graphql@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/fetch-multipart-graphql/-/fetch-multipart-graphql-3.0.0.tgz#61c9c1623beb4a7330e86ee72e6906148cd20acf"
integrity sha512-asrZG9CMaRUJmzqU+jqyBp52IB3MEzO38bhrPVB73BNAZ8ShBYgQUPOR9d/kYO4dfIrJIki/sTjeNYbUGuqOzQ==

figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
version "3.5.2"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
Expand Down Expand Up @@ -12121,6 +12121,7 @@ grapheme-breaker@^0.3.2:
codemirror "^5.54.0"
codemirror-graphql "^0.15.2"
copy-to-clipboard "^3.2.0"
dset "^2.0.1"
entities "^2.0.0"
graphql-language-service "^3.1.2"
markdown-it "^10.0.0"
Expand Down Expand Up @@ -15593,6 +15594,11 @@ merge@^1.2.0:
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==

meros@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/meros/-/meros-1.0.0.tgz#cb1fc818a60afc56bd86c577a9758b4510b3f027"
integrity sha512-gGhudbEI/7Z/5Mc3Mh2VsxWTm0byUB7O34azwchvvDRZ3oaxt5RaN80tF9mwN+plfi2JKDeIQQfJJWwuWmgqBQ==

methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
Expand Down