diff --git a/README.md b/README.md index 8daf2b3f..d4744d39 100644 --- a/README.md +++ b/README.md @@ -566,6 +566,26 @@ ReactDOM.render(document.getElementByID('graphiql'), ); +
+🔗 Client usage with retry on any connection problem + +```typescript +import { createClient } from 'graphql-ws'; +import { waitForHealthy } from './my-servers'; + +const client = createClient({ + url: 'ws://any.retry:4000/graphql', + // by default the client will immediately fail on any non-fatal + // `CloseEvent` problem thrown during the connection phase + // + // see `retryAttempts` documentation about which `CloseEvent`s are + // considered fatal regardless + shouldRetry: () => true, +}); +``` + +
+
🔗 Client usage with custom retry timeout strategy diff --git a/docs/interfaces/client.ClientOptions.md b/docs/interfaces/client.ClientOptions.md index 79164422..b3f653df 100644 --- a/docs/interfaces/client.ClientOptions.md +++ b/docs/interfaces/client.ClientOptions.md @@ -35,6 +35,7 @@ Configuration used for the GraphQL over WebSocket client. - [isFatalConnectionProblem](client.ClientOptions.md#isfatalconnectionproblem) - [onNonLazyError](client.ClientOptions.md#onnonlazyerror) - [retryWait](client.ClientOptions.md#retrywait) +- [shouldRetry](client.ClientOptions.md#shouldretry) ## Properties @@ -259,6 +260,8 @@ ___ ▸ `Optional` **isFatalConnectionProblem**(`errOrCloseEvent`): `boolean` +**`deprecated`** Use `shouldRetry` instead. + Check if the close event or connection error is fatal. If you return `true`, the client will fail immediately without additional retries; however, if you return `false`, the client will keep retrying until the `retryAttempts` have @@ -271,7 +274,7 @@ Beware, the library classifies a few close events as fatal regardless of what is returned. They are listed in the documentation of the `retryAttempts` option. -**`default`** 'Any non-CloseEvent' +**`default`** 'Any non-`CloseEvent`' #### Parameters @@ -339,3 +342,32 @@ by timing the resolution of the returned promise with the retries count. #### Returns `Promise`<`void`\> + +___ + +### shouldRetry + +▸ `Optional` **shouldRetry**(`errOrCloseEvent`): `boolean` + +Check if the close event or connection error is fatal. If you return `false`, +the client will fail immediately without additional retries; however, if you +return `true`, the client will keep retrying until the `retryAttempts` have +been exceeded. + +The argument is whatever has been thrown during the connection phase. + +Beware, the library classifies a few close events as fatal regardless of +what is returned here. They are listed in the documentation of the `retryAttempts` +option. + +**`default`** 'Only `CloseEvent`s' + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `errOrCloseEvent` | `unknown` | + +#### Returns + +`boolean` diff --git a/src/__tests__/client.ts b/src/__tests__/client.ts index c2ae0dbd..05919200 100644 --- a/src/__tests__/client.ts +++ b/src/__tests__/client.ts @@ -1612,7 +1612,10 @@ describe('reconnecting', () => { createClient({ url, retryAttempts: Infinity, // keep retrying forever - isFatalConnectionProblem: () => true, // even if all connection probles are fatal + // even if all connection problems are fatal + shouldRetry: () => false, + // @deprecated + isFatalConnectionProblem: () => true, }), { query: 'subscription { ping }', @@ -1648,6 +1651,33 @@ describe('reconnecting', () => { it('should report fatal connection problems immediately', async () => { const { url, ...server } = await startTServer(); + const sub = tsubscribe( + createClient({ + url, + retryAttempts: Infinity, // keep retrying forever + shouldRetry: (err) => { + expect((err as CloseEvent).code).toBe(4444); + expect((err as CloseEvent).reason).toBe('Is fatal?'); + return false; + }, + }), + { + query: 'subscription { ping }', + }, + ); + + await server.waitForClient((client) => { + client.close(4444, 'Is fatal?'); + }); + + await sub.waitForError((err) => { + expect((err as CloseEvent).code).toBe(4444); + }, 20); + }); + + it('should report fatal connection problems immediately (using deprecated `isFatalConnectionProblem`)', async () => { + const { url, ...server } = await startTServer(); + const sub = tsubscribe( createClient({ url, diff --git a/src/client.ts b/src/client.ts index 1cad17e3..84990500 100644 --- a/src/client.ts +++ b/src/client.ts @@ -355,6 +355,23 @@ export interface ClientOptions< */ retryWait?: (retries: number) => Promise; /** + * Check if the close event or connection error is fatal. If you return `false`, + * the client will fail immediately without additional retries; however, if you + * return `true`, the client will keep retrying until the `retryAttempts` have + * been exceeded. + * + * The argument is whatever has been thrown during the connection phase. + * + * Beware, the library classifies a few close events as fatal regardless of + * what is returned here. They are listed in the documentation of the `retryAttempts` + * option. + * + * @default 'Only `CloseEvent`s' + */ + shouldRetry?: (errOrCloseEvent: unknown) => boolean; + /** + * @deprecated Use `shouldRetry` instead. + * * Check if the close event or connection error is fatal. If you return `true`, * the client will fail immediately without additional retries; however, if you * return `false`, the client will keep retrying until the `retryAttempts` have @@ -367,7 +384,7 @@ export interface ClientOptions< * what is returned. They are listed in the documentation of the `retryAttempts` * option. * - * @default 'Any non-CloseEvent' + * @default 'Any non-`CloseEvent`' */ isFatalConnectionProblem?: (errOrCloseEvent: unknown) => boolean; /** @@ -470,6 +487,7 @@ export function createClient< ), ); }, + shouldRetry = isLikeCloseEvent, isFatalConnectionProblem = (errOrCloseEvent) => // non `CloseEvent`s are fatal by default !isLikeCloseEvent(errOrCloseEvent), @@ -827,7 +845,10 @@ export function createClient< // retries are not allowed or we tried to many times, report error if (!retryAttempts || retries >= retryAttempts) throw errOrCloseEvent; - // throw fatal connection problems immediately + // throw non-retryable connection problems + if (!shouldRetry(errOrCloseEvent)) throw errOrCloseEvent; + + // @deprecated throw fatal connection problems immediately if (isFatalConnectionProblem(errOrCloseEvent)) throw errOrCloseEvent; // looks good, start retrying