From 60d9cd5509d1b989f3ca6a9370850ce0aae41522 Mon Sep 17 00:00:00 2001 From: Denis Badurina Date: Tue, 3 Nov 2020 16:19:20 +0100 Subject: [PATCH] feat: Subscribe message `query` must be a string (#45) * feat: query can be only of string type * docs: update apollo example * docs(protocol): no document node --- PROTOCOL.md | 4 +--- README.md | 42 ++++++++++++++++++++++++------------------ src/message.ts | 7 +++---- src/server.ts | 3 +-- src/tests/server.ts | 45 --------------------------------------------- 5 files changed, 29 insertions(+), 72 deletions(-) diff --git a/PROTOCOL.md b/PROTOCOL.md index b5b910a3..3d68e69a 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -62,14 +62,12 @@ Requests an operation specified in the message `payload`. This message provides If there is already an active subscriber for a streaming operation matching the provided ID, the server will close the socket immediately with the event `4409: Subscriber for already exists`. The server may not assert this rule for operations returning a single result as they do not require reservations for additional future events. ```typescript -import { DocumentNode } from 'graphql'; - interface SubscribeMessage { id: ''; type: 'subscribe'; payload: { operationName?: string | null; - query: string | DocumentNode; + query: string; variables?: Record | null; }; } diff --git a/README.md b/README.md index fee891c6..ab0b059c 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,7 @@ export const network = Network.create(fetchOrSubscribe, fetchOrSubscribe); ```typescript import { ApolloLink, Operation, FetchResult, Observable } from '@apollo/client'; +import { print } from 'graphql'; import { createClient, Config, Client } from 'graphql-ws'; class WebSocketLink extends ApolloLink { @@ -284,25 +285,30 @@ class WebSocketLink extends ApolloLink { public request(operation: Operation): Observable { return new Observable((sink) => { - return this.client.subscribe(operation, { - ...sink, - error: (err) => { - if (err instanceof Error) { - sink.error(err); - } else if (err instanceof CloseEvent) { - sink.error( - new Error( - `Socket closed with event ${err.code}` + err.reason - ? `: ${err.reason}` // reason will be available on clean closes - : '', - ), - ); - } else { - // GraphQLError[] - sink.error(new Error(err.map(({ message }) => message).join(', '))); - } + return this.client.subscribe( + { ...operation, query: print(operation.query) }, + { + ...sink, + error: (err) => { + if (err instanceof Error) { + sink.error(err); + } else if (err instanceof CloseEvent) { + sink.error( + new Error( + `Socket closed with event ${err.code}` + err.reason + ? `: ${err.reason}` // reason will be available on clean closes + : '', + ), + ); + } else { + // GraphQLError[] + sink.error( + new Error(err.map(({ message }) => message).join(', ')), + ); + } + }, }, - }); + ); }); } } diff --git a/src/message.ts b/src/message.ts index 8197cb73..74ced870 100644 --- a/src/message.ts +++ b/src/message.ts @@ -4,7 +4,7 @@ * */ -import { GraphQLError, ExecutionResult, DocumentNode } from 'graphql'; +import { GraphQLError, ExecutionResult } from 'graphql'; import { isObject, areGraphQLErrors, @@ -41,7 +41,7 @@ export interface SubscribeMessage { export interface SubscribePayload { readonly operationName?: string | null; - readonly query: string | DocumentNode; + readonly query: string; readonly variables?: Record | null; } @@ -104,8 +104,7 @@ export function isMessage(val: unknown): val is Message { val.payload.operationName === undefined || val.payload.operationName === null || typeof val.payload.operationName === 'string') && - (hasOwnStringProperty(val.payload, 'query') || // string query or persisted query id - hasOwnObjectProperty(val.payload, 'query')) && // document node query + hasOwnStringProperty(val.payload, 'query') && (!hasOwnProperty(val.payload, 'variables') || val.payload.variables === undefined || val.payload.variables === null || diff --git a/src/server.ts b/src/server.ts index 1405d163..37267f01 100644 --- a/src/server.ts +++ b/src/server.ts @@ -567,11 +567,10 @@ export function createServer( } const { operationName, query, variables } = message.payload; - const document = typeof query === 'string' ? parse(query) : query; execArgs = { schema, operationName, - document, + document: parse(query), variableValues: variables, }; diff --git a/src/tests/server.ts b/src/tests/server.ts index 32bb2e86..356b1e7d 100644 --- a/src/tests/server.ts +++ b/src/tests/server.ts @@ -1164,51 +1164,6 @@ describe('Subscribe', () => { }); }); - it('should execute the query of `DocumentNode` type, "next" the result and then "complete"', async () => { - const { url } = await startTServer({ - schema, - }); - - const client = await createTClient(url); - client.ws.send( - stringifyMessage({ - type: MessageType.ConnectionInit, - }), - ); - - await client.waitForMessage(({ data }) => { - expect(parseMessage(data).type).toBe(MessageType.ConnectionAck); - client.ws.send( - stringifyMessage({ - id: '1', - type: MessageType.Subscribe, - payload: { - operationName: 'TestString', - query: parse(`query TestString { - getValue - }`), - variables: {}, - }, - }), - ); - }); - - await client.waitForMessage(({ data }) => { - expect(parseMessage(data)).toEqual({ - id: '1', - type: MessageType.Next, - payload: { data: { getValue: 'value' } }, - }); - }); - - await client.waitForMessage(({ data }) => { - expect(parseMessage(data)).toEqual({ - id: '1', - type: MessageType.Complete, - }); - }); - }); - it('should execute the query and "error" out because of validation errors', async () => { const { url } = await startTServer({ schema,