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

feat(core): Support Apollo Federation subscriptions multipart payloads (#3499)

Changed files
+75 -4
.changeset
packages
+5
.changeset/small-cheetahs-jam.md
···
···
+
---
+
'@urql/core': minor
+
---
+
+
Support [Apollo Federation's format](https://www.apollographql.com/docs/router/executing-operations/subscription-multipart-protocol/) for subscription results in `multipart/mixed` responses (result properties essentially are namespaced on a `payload` key)
+1
packages/core/src/types.ts
···
* @see {@link https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md#payload-format} for the DeferStream spec
*/
hasNext?: boolean;
}
/** A source of {@link OperationResult | OperationResults}, convertable to a promise, subscribable, or Wonka Source.
···
* @see {@link https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md#payload-format} for the DeferStream spec
*/
hasNext?: boolean;
+
payload?: Omit<ExecutionResult, 'payload'>;
}
/** A source of {@link OperationResult | OperationResults}, convertable to a promise, subscribable, or Wonka Source.
+58
packages/core/src/utils/result.test.ts
···
import { OperationResult } from '../types';
import { queryOperation, subscriptionOperation } from '../test-utils';
import { makeResult, mergeResultPatch } from './result';
describe('makeResult', () => {
it('adds extensions and errors correctly', () => {
···
expect(merged.data).not.toBe(prevResult.data);
expect(merged.data.event).toBe(2);
expect(merged.hasNext).toBe(true);
});
···
import { OperationResult } from '../types';
import { queryOperation, subscriptionOperation } from '../test-utils';
import { makeResult, mergeResultPatch } from './result';
+
import { GraphQLError } from '@0no-co/graphql.web';
+
import { CombinedError } from './error';
describe('makeResult', () => {
it('adds extensions and errors correctly', () => {
···
expect(merged.data).not.toBe(prevResult.data);
expect(merged.data.event).toBe(2);
+
expect(merged.hasNext).toBe(true);
+
});
+
+
it('should work with the payload property', () => {
+
const prevResult: OperationResult = {
+
operation: subscriptionOperation,
+
data: {
+
__typename: 'Subscription',
+
event: 1,
+
},
+
stale: false,
+
hasNext: true,
+
};
+
+
const merged = mergeResultPatch(prevResult, {
+
payload: {
+
data: {
+
__typename: 'Subscription',
+
event: 2,
+
},
+
},
+
});
+
+
expect(merged.data).not.toBe(prevResult.data);
+
expect(merged.data.event).toBe(2);
+
expect(merged.hasNext).toBe(true);
+
});
+
+
it('should work with the payload property and errors', () => {
+
const prevResult: OperationResult = {
+
operation: subscriptionOperation,
+
data: {
+
__typename: 'Subscription',
+
event: 1,
+
},
+
stale: false,
+
hasNext: true,
+
};
+
+
const merged = mergeResultPatch(prevResult, {
+
payload: {
+
data: {
+
__typename: 'Subscription',
+
event: 2,
+
},
+
},
+
errors: [new GraphQLError('Something went horribly wrong')],
+
});
+
+
expect(merged.data).not.toBe(prevResult.data);
+
expect(merged.data.event).toBe(2);
+
expect(merged.error).toEqual(
+
new CombinedError({
+
graphQLErrors: [new GraphQLError('Something went horribly wrong')],
+
})
+
);
expect(merged.hasNext).toBe(true);
});
+11 -4
packages/core/src/utils/result.ts
···
pending?: ExecutionResult['pending']
): OperationResult => {
let errors = prevResult.error ? prevResult.error.graphQLErrors : [];
-
let hasExtensions = !!prevResult.extensions || !!nextResult.extensions;
-
const extensions = { ...prevResult.extensions, ...nextResult.extensions };
let incremental = nextResult.incremental;
···
}
}
} else {
-
withData.data = nextResult.data || prevResult.data;
-
errors = (nextResult.errors as any[]) || errors;
}
return {
···
pending?: ExecutionResult['pending']
): OperationResult => {
let errors = prevResult.error ? prevResult.error.graphQLErrors : [];
+
let hasExtensions =
+
!!prevResult.extensions || !!(nextResult.payload || nextResult).extensions;
+
const extensions = {
+
...prevResult.extensions,
+
...(nextResult.payload || nextResult).extensions,
+
};
let incremental = nextResult.incremental;
···
}
}
} else {
+
withData.data = (nextResult.payload || nextResult).data || prevResult.data;
+
errors =
+
(nextResult.errors as any[]) ||
+
(nextResult.payload && nextResult.payload.errors) ||
+
errors;
}
return {