diff --git a/.changeset/clever-plants-greet.md b/.changeset/clever-plants-greet.md new file mode 100644 index 0000000000..49a44136d8 --- /dev/null +++ b/.changeset/clever-plants-greet.md @@ -0,0 +1,5 @@ +--- +'@urql/core': major +--- + +Update `subscriptionExchange` to receive `FetchBody` instead. In the usual usage of `subscriptionExchange` (for instance with `graphql-ws`) you can expect no breaking changes. However, the `key` and `extensions` field has been removed and instead the `forwardSubscription` function receives the full `Operation` as a second argument. diff --git a/.changeset/slow-glasses-attend.md b/.changeset/slow-glasses-attend.md new file mode 100644 index 0000000000..938f3d1cd7 --- /dev/null +++ b/.changeset/slow-glasses-attend.md @@ -0,0 +1,5 @@ +--- +'@urql/core': minor +--- + +Support `GraphQLRequest.extensions` as spec-extensions input to GraphQL requests. diff --git a/docs/advanced/subscriptions.md b/docs/advanced/subscriptions.md index 96df7a5c4e..f626c9b46b 100644 --- a/docs/advanced/subscriptions.md +++ b/docs/advanced/subscriptions.md @@ -57,12 +57,9 @@ const client = createClient({ exchanges: [ ...defaultExchanges, subscriptionExchange({ - forwardSubscription: (operation) => ({ - subscribe: (sink) => ({ - unsubscribe: wsClient.subscribe( - { query: operation.query, variables: operation.variables }, - sink - ), + forwardSubscription: request => ({ + subscribe: sink => ({ + unsubscribe: wsClient.subscribe(request, sink), }), }), }), @@ -94,7 +91,7 @@ const client = new Client({ exchanges: [ ...defaultExchanges, subscriptionExchange({ - forwardSubscription: (operation) => subscriptionClient.request(operation) + forwardSubscription: request => subscriptionClient.request(request), }), ], }); diff --git a/packages/core/src/exchanges/subscription.test.ts b/packages/core/src/exchanges/subscription.test.ts index 2f0ef7fc90..99a178de53 100644 --- a/packages/core/src/exchanges/subscription.test.ts +++ b/packages/core/src/exchanges/subscription.test.ts @@ -29,7 +29,6 @@ it('should return response data from forwardSubscription observable', async () = stringifyDocument(subscriptionOperation.query) ); expect(operation.variables).toBe(subscriptionOperation.variables); - expect(operation.context).toEqual(subscriptionOperation.context); return { subscribe(observer) { diff --git a/packages/core/src/exchanges/subscription.ts b/packages/core/src/exchanges/subscription.ts index 45a4b4ee4e..4759573c7b 100644 --- a/packages/core/src/exchanges/subscription.ts +++ b/packages/core/src/exchanges/subscription.ts @@ -11,21 +11,21 @@ import { } from 'wonka'; import { - stringifyDocument, makeResult, + mergeResultPatch, makeErrorResult, makeOperation, - mergeResultPatch, } from '../utils'; import { Exchange, ExecutionResult, Operation, - OperationContext, OperationResult, } from '../types'; +import { FetchBody, makeFetchBody } from '../internal'; + /** An abstract observer-like interface. * * @remarks @@ -65,19 +65,10 @@ export interface ObservableLike { }; } -/** A more cross-compatible version of the {@link Operation} structure. - * - * @remarks - * When the `subscriptionExchange` was first created, some transports needed a specific shape - * of {@link GraphQLRequest} objects to be passed to them. This is a shim that is as compatible - * with most transports out of the box as possible. +/** A more cross-compatible version of the {@link GraphQLRequest} structure. + * {@link FetchBody} for more details */ -export interface SubscriptionOperation { - query: string; - variables: Record | undefined; - key: string; - context: OperationContext; -} +export type SubscriptionOperation = FetchBody; /** A subscription forwarding function, which must accept a {@link SubscriptionOperation}. * @@ -85,7 +76,8 @@ export interface SubscriptionOperation { * @returns An {@link ObservableLike} object issuing {@link ExecutionResult | ExecutionResults}. */ export type SubscriptionForwarder = ( - operation: SubscriptionOperation + request: FetchBody, + operation: Operation ) => ObservableLike; /** This is called to create a subscription and needs to be hooked up to a transport client. */ @@ -148,13 +140,10 @@ export const subscriptionExchange = ({ const createSubscriptionSource = ( operation: Operation ): Source => { - // This excludes the query's name as a field although subscription-transport-ws does accept it since it's optional - const observableish = forwardSubscription({ - key: operation.key.toString(36), - query: stringifyDocument(operation.query), - variables: operation.variables!, - context: { ...operation.context }, - }); + const observableish = forwardSubscription( + makeFetchBody(operation), + operation + ); return make(({ next, complete }) => { let isComplete = false; diff --git a/packages/core/src/internal/fetchOptions.ts b/packages/core/src/internal/fetchOptions.ts index 3ea94e1750..cb6870705f 100644 --- a/packages/core/src/internal/fetchOptions.ts +++ b/packages/core/src/internal/fetchOptions.ts @@ -28,7 +28,7 @@ export function makeFetchBody< query: stringifyDocument(request.query), operationName: getOperationName(request.query), variables: request.variables || undefined, - extensions: undefined, + extensions: request.extensions, }; } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 095b012942..a1e6bcf464 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -236,6 +236,10 @@ export interface GraphQLRequest< * generic, are sent to the GraphQL API to execute a request. */ variables: Variables; + /** Additional metadata that a GraphQL API may accept for spec extensions. + * @see {@link https://github.com/graphql/graphql-over-http/blob/1928447/spec/GraphQLOverHTTP.md#request-parameters} for the GraphQL over HTTP spec + */ + extensions?: Record | undefined; } /** Parameters from which {@link GraphQLRequest | GraphQLRequests} are created from. diff --git a/packages/core/src/utils/operation.ts b/packages/core/src/utils/operation.ts index fd11668c3e..34e9667e7c 100644 --- a/packages/core/src/utils/operation.ts +++ b/packages/core/src/utils/operation.ts @@ -49,11 +49,8 @@ function makeOperation< function makeOperation(kind, request, context) { if (!context) context = request.context; - return { - key: request.key, - query: request.query, - variables: request.variables, + ...request, kind, context, }; diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index 8da3e8ca0c..468b2cf804 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -152,14 +152,15 @@ export const createRequest = < Variables extends AnyVariables = AnyVariables >( _query: string | DocumentNode | TypedDocumentNode, - _variables: Variables + _variables: Variables, + extensions?: Record | undefined ): GraphQLRequest => { const variables = _variables || ({} as Variables); const query = keyDocument(_query); const printedVars = stringifyVariables(variables); let key = query.__key; if (printedVars !== '{}') key = phash(printedVars, key); - return { key, query, variables }; + return { key, query, variables, extensions }; }; /** Returns the name of the `DocumentNode`'s operation, if any.