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(core): Support GraphQLRequest.extensions and update subscriptionExchange #3054

Merged
merged 7 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/clever-plants-greet.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changeset/slow-glasses-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/core': minor
---

Support `GraphQLRequest.extensions` as spec-extensions input to GraphQL requests.
11 changes: 4 additions & 7 deletions docs/advanced/subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}),
}),
}),
Expand Down Expand Up @@ -94,7 +91,7 @@ const client = new Client({
exchanges: [
...defaultExchanges,
subscriptionExchange({
forwardSubscription: (operation) => subscriptionClient.request(operation)
forwardSubscription: request => subscriptionClient.request(request),
}),
],
});
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/exchanges/subscription.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
35 changes: 12 additions & 23 deletions packages/core/src/exchanges/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -65,27 +65,19 @@ export interface ObservableLike<T> {
};
}

/** 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}.
*
* @param operation - 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. */
Expand Down Expand Up @@ -148,13 +140,10 @@ export const subscriptionExchange = ({
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;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/internal/fetchOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function makeFetchBody<
query: stringifyDocument(request.query),
operationName: getOperationName(request.query),
variables: request.variables || undefined,
extensions: undefined,
extensions: request.extensions,
};
}

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any> | undefined;
}

/** Parameters from which {@link GraphQLRequest | GraphQLRequests} are created from.
Expand Down
5 changes: 1 addition & 4 deletions packages/core/src/utils/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/utils/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,15 @@ export const createRequest = <
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.
Expand Down