Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

feat(core): Support GraphQLRequest.extensions and update subscriptionExchange (#3054)

Changed files
+35 -38
.changeset
docs
advanced
packages
+5
.changeset/clever-plants-greet.md
···
+
---
+
'@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.
+5
.changeset/slow-glasses-attend.md
···
+
---
+
'@urql/core': minor
+
---
+
+
Support `GraphQLRequest.extensions` as spec-extensions input to GraphQL requests.
+4 -7
docs/advanced/subscriptions.md
···
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),
}),
}),
}),
···
exchanges: [
...defaultExchanges,
subscriptionExchange({
-
forwardSubscription: (operation) => subscriptionClient.request(operation)
+
forwardSubscription: request => subscriptionClient.request(request),
}),
],
});
-1
packages/core/src/exchanges/subscription.test.ts
···
stringifyDocument(subscriptionOperation.query)
);
expect(operation.variables).toBe(subscriptionOperation.variables);
-
expect(operation.context).toEqual(subscriptionOperation.context);
return {
subscribe(observer) {
+12 -23
packages/core/src/exchanges/subscription.ts
···
} 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.
*
···
};
}
-
/** 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<string, unknown> | undefined;
-
key: string;
-
context: OperationContext;
-
}
+
export type SubscriptionOperation = FetchBody;
/** A subscription forwarding function, which must accept a {@link SubscriptionOperation}.
*
···
* @returns An {@link ObservableLike} object issuing {@link ExecutionResult | ExecutionResults}.
*/
export type SubscriptionForwarder = (
-
operation: SubscriptionOperation
+
request: FetchBody,
+
operation: Operation
) => ObservableLike<ExecutionResult>;
/** This is called to create a subscription and needs to be hooked up to a transport client. */
···
const createSubscriptionSource = (
operation: Operation
): Source<OperationResult> => {
-
// 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<OperationResult>(({ next, complete }) => {
let isComplete = false;
+1 -1
packages/core/src/internal/fetchOptions.ts
···
query: stringifyDocument(request.query),
operationName: getOperationName(request.query),
variables: request.variables || undefined,
-
extensions: undefined,
+
extensions: request.extensions,
};
}
+4
packages/core/src/types.ts
···
* 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<string, any> | undefined;
}
/** Parameters from which {@link GraphQLRequest | GraphQLRequests} are created from.
+1 -4
packages/core/src/utils/operation.ts
···
function makeOperation(kind, request, context) {
if (!context) context = request.context;
-
return {
-
key: request.key,
-
query: request.query,
-
variables: request.variables,
+
...request,
kind,
context,
};
+3 -2
packages/core/src/utils/request.ts
···
Variables extends AnyVariables = AnyVariables
>(
_query: string | DocumentNode | TypedDocumentNode<Data, Variables>,
-
_variables: Variables
+
_variables: Variables,
+
extensions?: Record<string, any> | undefined
): GraphQLRequest<Data, Variables> => {
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.