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: Multipart Subscriptions #10592

Merged
merged 33 commits into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
84b3a65
feat: send multipart/mixed in accept header for multipart-subscriptions
alessbell Feb 9, 2023
16fb132
chore: comment out unused import
alessbell Mar 17, 2023
e87721b
chore: bump bundlesize to 33kb (was 32.65kb)
alessbell Mar 17, 2023
4e0097a
chore: comment out unused test query
alessbell Mar 17, 2023
519982e
chore: do not call observer.next without data or errors
alessbell Mar 17, 2023
2b8f813
chore: update tests
alessbell Mar 22, 2023
debf60d
Merge branch 'main' into router-subs
alessbell Mar 22, 2023
f77e6c8
chore: remove whitespace from defer test
alessbell Mar 22, 2023
2ce04d2
Merge branch 'router-subs' of github.com:apollographql/apollo-client …
alessbell Mar 22, 2023
cba65b9
chore: remove test from useSubscription.test.tsx
alessbell Mar 22, 2023
e458d96
fix: rename interface to ApolloPayloadResult
alessbell Mar 27, 2023
daf0993
Update src/link/core/types.ts
alessbell Mar 27, 2023
2c77342
fix: add isApolloPayloadResult user-defined type guard
alessbell Mar 27, 2023
6014fbf
Merge branch 'router-subs' of github.com:apollographql/apollo-client …
alessbell Mar 27, 2023
c3a4b88
fix: call invariant.warn if defer used in multipart subscription
alessbell Mar 27, 2023
22674b1
Merge branch 'main' into router-subs
alessbell Mar 27, 2023
90ecc0a
Revert "Update src/link/core/types.ts"
alessbell Mar 27, 2023
09a2b39
fix: improves handling of transport-specific errors
alessbell Mar 28, 2023
9a8c5cb
fix: add ApolloError test, fix nits
alessbell Mar 28, 2023
99420e0
fix: use Symbol key to avoid naming collision within extensions
alessbell Mar 29, 2023
b552193
fix: remove duplicate error messages from snapshot
alessbell Mar 29, 2023
af56408
fix: update exports snapshot test
alessbell Mar 29, 2023
b8dfc11
Update src/link/http/createHttpLink.ts
alessbell Mar 29, 2023
ca2d678
Merge branch 'main' into router-subs
alessbell Mar 29, 2023
cc252d7
chore: bump bundlesize to 32.98kb (was 32.89kb)
alessbell Mar 29, 2023
8337eb0
chore: format protocolError messages passed to ApolloError
alessbell Mar 29, 2023
05222fd
chore: add comment about Symbol in src/errors/index.ts
alessbell Mar 29, 2023
3c7567f
fix: should handle both protocolErrors and graphQLErrors simultaneously
alessbell Mar 29, 2023
6ae538e
chore: bump bundlesize to 33.02kb (was 32.98kb)
alessbell Mar 29, 2023
e2a727f
chore: rename graphQLResultHasProtocolError > graphQLResultHasProtoco…
alessbell Mar 30, 2023
6106da4
fix: use ApolloErrorOptions to type graphQL/protocolErrors opts
alessbell Mar 30, 2023
8f6f693
fix: remove done from ApolloPayloadResult and add null to payload
alessbell Mar 30, 2023
97e4e60
Update src/utilities/common/incrementalResult.ts
alessbell Mar 30, 2023
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/nervous-tomatoes-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Adds support for multipart subscriptions in `HTTPLink`.
alessbell marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion config/bundlesize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { join } from "path";
import { gzipSync } from "zlib";
import bytes from "bytes";

const gzipBundleByteLengthLimit = bytes("32.65KB");
const gzipBundleByteLengthLimit = bytes("33KB");
const minFile = join("dist", "apollo-client.min.cjs");
const minPath = join(__dirname, "..", minFile);
const gzipByteLen = gzipSync(readFileSync(minPath)).byteLength;
Expand Down
3 changes: 1 addition & 2 deletions config/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,14 @@ const react18TestFileIgnoreList = [
// to avoid running them twice with both react versions
// since they do not import react
ignoreTSFiles,
// failing hoc tests (8)
// failing hoc tests (7)
'src/react/hoc/__tests__/mutations/queries.test.tsx',
'src/react/hoc/__tests__/mutations/recycled-queries.test.tsx',
'src/react/hoc/__tests__/queries/errors.test.tsx',
'src/react/hoc/__tests__/queries/lifecycle.test.tsx',
'src/react/hoc/__tests__/queries/loading.test.tsx',
'src/react/hoc/__tests__/queries/observableQuery.test.tsx',
'src/react/hoc/__tests__/queries/skip.test.tsx',
'src/react/hoc/__tests__/subscriptions/subscriptions.test.tsx',
// failing components tests (1)
'src/react/components/__tests__/client/Query.test.tsx',
];
Expand Down
4 changes: 4 additions & 0 deletions src/__tests__/__snapshots__/graphqlSubscriptions.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
exports[`GraphQL Subscriptions should throw an error if the result has errors on it 1`] = `[ApolloError: This is an error]`;

exports[`GraphQL Subscriptions should throw an error if the result has errors on it 2`] = `[ApolloError: This is an error]`;

exports[`GraphQL Subscriptions should throw an error if the result has protocolErrors on it 1`] = `[ApolloError]`;

exports[`GraphQL Subscriptions should throw an error if the result has protocolErrors on it 2`] = `[ApolloError]`;
alessbell marked this conversation as resolved.
Show resolved Hide resolved
48 changes: 48 additions & 0 deletions src/__tests__/graphqlSubscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,52 @@ describe('GraphQL Subscriptions', () => {

link.simulateResult(results[0]);
});

it('should throw an error if the result has protocolErrors on it', () => {
const link = mockObservableLink();
const queryManager = new QueryManager({
link,
cache: new InMemoryCache({ addTypename: false }),
});

const obs = queryManager.startGraphQLSubscription(options);

const promises = [];
alessbell marked this conversation as resolved.
Show resolved Hide resolved
for (let i = 0; i < 2; i += 1) {
promises.push(
new Promise<void>((resolve, reject) => {
obs.subscribe({
next(result) {
reject('Should have hit the error block');
},
error(error) {
expect(error).toMatchSnapshot();
resolve();
},
});
}),
);
}

const errorResult = {
result: {
data: null,
extensions: {
protocolErrors: [
{
message: 'cannot read message from websocket',
extensions: [
{
code: "WEBSOCKET_MESSAGE_ERROR"
}
],
} as any,
],
}
},
};

link.simulateResult(errorResult);
return Promise.all(promises);
alessbell marked this conversation as resolved.
Show resolved Hide resolved
});
});
7 changes: 7 additions & 0 deletions src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getOperationName,
hasClientExports,
graphQLResultHasError,
graphQLResultHasProtocolError,
getGraphQLErrorsFromResult,
removeConnectionDirectiveFromDocument,
canUseWeakMap,
Expand Down Expand Up @@ -942,6 +943,12 @@ export class QueryManager<TStore> {
});
}

if (graphQLResultHasProtocolError(result)) {
throw new ApolloError({
protocolErrors: result.extensions?.protocolErrors
})
}
alessbell marked this conversation as resolved.
Show resolved Hide resolved

return result;
});

Expand Down
14 changes: 12 additions & 2 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '../utilities/globals';

import { GraphQLError } from 'graphql';
import { GraphQLError, GraphQLErrorExtensions } from 'graphql';

import { isNonEmptyArray } from '../utilities';
import { ServerParseError } from '../link/http';
Expand Down Expand Up @@ -45,6 +45,10 @@ export class ApolloError extends Error {
public name: string;
public message: string;
public graphQLErrors: GraphQLErrors;
public protocolErrors: ReadonlyArray<{
message: string;
extensions?: GraphQLErrorExtensions;
}>;
public clientErrors: ReadonlyArray<Error>;
public networkError: Error | ServerParseError | ServerError | null;

Expand All @@ -58,20 +62,26 @@ export class ApolloError extends Error {
// value or the constructed error will be meaningless.
constructor({
graphQLErrors,
protocolErrors,
clientErrors,
networkError,
errorMessage,
extraInfo,
}: {
graphQLErrors?: ReadonlyArray<GraphQLError>;
protocolErrors?: ReadonlyArray<{
message: string;
extensions?: GraphQLErrorExtensions;
}>;
clientErrors?: ReadonlyArray<Error>;
networkError?: Error | ServerParseError | ServerError | null;
errorMessage?: string;
extraInfo?: any;
}) {
}) {
super(errorMessage);
this.name = 'ApolloError';
this.graphQLErrors = graphQLErrors || [];
this.protocolErrors = protocolErrors || [];
this.clientErrors = clientErrors || [];
this.networkError = networkError || null;
this.message = errorMessage || generateErrorMessage(this);
Expand Down
18 changes: 14 additions & 4 deletions src/link/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ export interface ExecutionPatchInitialResult<
extensions?: TExtensions;
}

export interface IncrementalPayload<
TData,
TExtensions,
> {
export interface IncrementalPayload<TData, TExtensions> {
// data and path must both be present
// https://github.com/graphql/graphql-spec/pull/742/files#diff-98d0cd153b72b63c417ad4238e8cc0d3385691ccbde7f7674bc0d2a718b896ecR288-R293
data: TData | null;
Expand All @@ -48,6 +45,19 @@ export interface ExecutionPatchIncrementalResult<
extensions?: never;
}

export interface ApolloPayloadResult<
TData = Record<string, any>,
TExtensions = Record<string, any>
> {
payload: SingleExecutionResult | ExecutionPatchResult;
// Transport layer errors (as distinct from GraphQL or NetworkErrors),
// these are fatal errors that will include done: true.
errors?: ReadonlyArray<Error | string>;
// Done can be true or false and is present as `done: true` on the final
// chunk, or when fatal errors occur.
done?: boolean;
}

export type ExecutionPatchResult<
TData = Record<string, any>,
TExtensions = Record<string, any>
Expand Down
Loading