Skip to content

Commit

Permalink
Merge branch 'release-3.13' into jerel/suspense-use-fragment
Browse files Browse the repository at this point in the history
  • Loading branch information
jerelmiller authored Feb 3, 2025
2 parents 3d77e6e + e189596 commit 6c8c62f
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 13 deletions.
5 changes: 0 additions & 5 deletions .changeset/curvy-seahorses-walk.md

This file was deleted.

4 changes: 2 additions & 2 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"mode": "pre",
"tag": "alpha",
"tag": "rc",
"initialVersions": {
"@apollo/client": "3.12.2"
},
"changesets": []
}
}
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# @apollo/client

## 3.12.9

### Patch Changes

- [#12321](https://github.com/apollographql/apollo-client/pull/12321) [`daa4f33`](https://github.com/apollographql/apollo-client/commit/daa4f3303cfb81e8dca66c21ce3f3dc24946cafb) Thanks [@jerelmiller](https://github.com/jerelmiller)! - Fix type of `extensions` in `protocolErrors` for `ApolloError` and the `onError` link. According to the [multipart HTTP subscription protocol](https://www.apollographql.com/docs/graphos/routing/operations/subscriptions/multipart-protocol), fatal tranport errors follow the [GraphQL error format](https://spec.graphql.org/draft/#sec-Errors.Error-Result-Format) which require `extensions` to be a map as its value instead of an array.

- [#12318](https://github.com/apollographql/apollo-client/pull/12318) [`b17968b`](https://github.com/apollographql/apollo-client/commit/b17968b61f0e35b1ba20d081dacee66af8225491) Thanks [@jerelmiller](https://github.com/jerelmiller)! - Allow `RetryLink` to retry an operation when fatal [transport-level errors](https://www.apollographql.com/docs/graphos/routing/operations/subscriptions/multipart-protocol#message-and-error-format) are emitted from multipart subscriptions.

```js
const retryLink = new RetryLink({
attempts: (count, operation, error) => {
if (error instanceof ApolloError) {
// errors available on the `protocolErrors` field in `ApolloError`
console.log(error.protocolErrors);
}

return true;
},
});
```

## 3.12.8

### Patch Changes
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@apollo/client",
"version": "3.12.8",
"version": "3.12.9",
"description": "A fully-featured caching GraphQL client.",
"private": true,
"keywords": [
Expand Down
66 changes: 65 additions & 1 deletion src/link/retry/__tests__/retryLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { execute } from "../../core/execute";
import { Observable } from "../../../utilities/observables/Observable";
import { fromError } from "../../utils/fromError";
import { RetryLink } from "../retryLink";
import { ObservableStream } from "../../../testing/internal";
import {
mockMultipartSubscriptionStream,
ObservableStream,
} from "../../../testing/internal";
import { ApolloError } from "../../../core";

const query = gql`
{
Expand Down Expand Up @@ -210,4 +214,64 @@ describe("RetryLink", () => {
[3, operation, standardError],
]);
});

it("handles protocol errors from multipart subscriptions", async () => {
const subscription = gql`
subscription MySubscription {
aNewDieWasCreated {
die {
roll
sides
color
}
}
}
`;

const attemptStub = jest.fn();
attemptStub.mockReturnValueOnce(true);

const retryLink = new RetryLink({
delay: { initial: 1 },
attempts: attemptStub,
});

const { httpLink, enqueuePayloadResult, enqueueProtocolErrors } =
mockMultipartSubscriptionStream();
const link = ApolloLink.from([retryLink, httpLink]);
const stream = new ObservableStream(execute(link, { query: subscription }));

enqueueProtocolErrors([
{ message: "Error field", extensions: { code: "INTERNAL_SERVER_ERROR" } },
]);

enqueuePayloadResult({
data: {
aNewDieWasCreated: { die: { color: "blue", roll: 2, sides: 6 } },
},
});

await expect(stream).toEmitValue({
data: {
aNewDieWasCreated: { die: { color: "blue", roll: 2, sides: 6 } },
},
});

expect(attemptStub).toHaveBeenCalledTimes(1);
expect(attemptStub).toHaveBeenCalledWith(
1,
expect.objectContaining({
operationName: "MySubscription",
query: subscription,
}),
new ApolloError({
protocolErrors: [
{
message: "Error field",
extensions: { code: "INTERNAL_SERVER_ERROR" },
},
],
})
);
});
});
21 changes: 20 additions & 1 deletion src/link/retry/retryLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { buildDelayFunction } from "./delayFunction.js";
import type { RetryFunction, RetryFunctionOptions } from "./retryFunction.js";
import { buildRetryFunction } from "./retryFunction.js";
import type { SubscriptionObserver } from "zen-observable-ts";
import {
ApolloError,
graphQLResultHasProtocolErrors,
PROTOCOL_ERRORS_SYMBOL,
} from "../../errors/index.js";

export namespace RetryLink {
export interface Options {
Expand Down Expand Up @@ -54,7 +59,21 @@ class RetryableOperation {

private try() {
this.currentSubscription = this.forward(this.operation).subscribe({
next: this.observer.next.bind(this.observer),
next: (result) => {
if (graphQLResultHasProtocolErrors(result)) {
this.onError(
new ApolloError({
protocolErrors: result.extensions[PROTOCOL_ERRORS_SYMBOL],
})
);
// Unsubscribe from the current subscription to prevent the `complete`
// handler to be called as a result of the stream closing.
this.currentSubscription?.unsubscribe();
return;
}

this.observer.next(result);
},
error: this.onError,
complete: this.observer.complete.bind(this.observer),
});
Expand Down

0 comments on commit 6c8c62f

Please sign in to comment.