diff --git a/src/error-request.ts b/src/error-request.ts index da4a7a5d..1e225ed7 100644 --- a/src/error-request.ts +++ b/src/error-request.ts @@ -1,6 +1,13 @@ -// @ts-ignore +import type { RequestError } from "@octokit/request-error"; +import type { State, Octokit } from "./types.js"; +import type { EndpointDefaults } from "@octokit/types"; -export async function errorRequest(state, octokit, error, options) { +export async function errorRequest( + state: State, + octokit: Octokit, + error: RequestError, + options: Required, +) { if (!error.request || !error.request.request) { // address https://github.com/octokit/plugin-retry.js/issues/8 throw error; @@ -9,8 +16,10 @@ export async function errorRequest(state, octokit, error, options) { // retry all >= 400 && not doNotRetry if (error.status >= 400 && !state.doNotRetry.includes(error.status)) { const retries = - options.request.retries != null ? options.request.retries : state.retries; - const retryAfter = Math.pow((options.request.retryCount || 0) + 1, 2); + options.request?.retries != null + ? options.request.retries + : state.retries; + const retryAfter = Math.pow((options.request?.retryCount || 0) + 1, 2); throw octokit.retry.retryRequest(error, retries, retryAfter); } diff --git a/src/index.ts b/src/index.ts index 4a1cf398..33523f16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,15 @@ -import type { Octokit } from "@octokit/core"; +import type { Octokit as OctokitCore } from "@octokit/core"; import type { RequestError } from "@octokit/request-error"; +import type { OctokitOptions } from "@octokit/core/types"; import { VERSION } from "./version.js"; import { errorRequest } from "./error-request.js"; import { wrapRequest } from "./wrap-request.js"; +import type { RetryOptions, State } from "./types.js"; export { VERSION } from "./version.js"; -export function retry(octokit: Octokit, octokitOptions: any) { - const state = Object.assign( +export function retry(octokit: OctokitCore, octokitOptions: OctokitOptions) { + const state: State = Object.assign( { enabled: true, retryAfterBaseValue: 1000, @@ -18,7 +20,13 @@ export function retry(octokit: Octokit, octokitOptions: any) { ); if (state.enabled) { + // @ts-expect-error octokit.hook.error("request", errorRequest.bind(null, state, octokit)); + // The types for `before-after-hook` do not let us only pass through a Promise return value + // the types expect that the function can return either a Promise of the response, or diectly return the response. + // This is due to the fact that `@octokit/request` uses aysnc functions + // Also, since we add the custom `retryCount` property to the request argument, the types are not compatible. + // @ts-expect-error octokit.hook.wrap("request", wrapRequest.bind(null, state, octokit)); } @@ -40,3 +48,9 @@ export function retry(octokit: Octokit, octokitOptions: any) { }; } retry.VERSION = VERSION; + +declare module "@octokit/core/types" { + interface OctokitOptions { + retry?: RetryOptions; + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..418f3832 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,20 @@ +import type { Octokit as OctokitCore } from "@octokit/core"; +import type { RequestError } from "@octokit/request-error"; + +export type RetryOptions = { + enabled?: boolean; + doNotRetry?: number[]; + retries?: number; + retryAfterBaseValue?: number; +}; + +export type State = Required; +export type Octokit = OctokitCore & { + retry: { + retryRequest: ( + error: RequestError, + retries: number, + retryAfter: number, + ) => RequestError; + }; +}; diff --git a/src/wrap-request.ts b/src/wrap-request.ts index e9f1ccab..bb37b9eb 100644 --- a/src/wrap-request.ts +++ b/src/wrap-request.ts @@ -1,10 +1,24 @@ -// @ts-nocheck +// @ts-expect-error import Bottleneck from "bottleneck/light.js"; +import type TBottleneck from "bottleneck"; import { RequestError } from "@octokit/request-error"; import { errorRequest } from "./error-request.js"; +import type { Octokit, State } from "./types.js"; +import type { EndpointDefaults, OctokitResponse } from "@octokit/types"; -export async function wrapRequest(state, octokit, request, options) { - const limiter = new Bottleneck(); +type Request = ( + request: Request, + options: Required, +) => Promise>; +export async function wrapRequest( + state: State, + octokit: Octokit, + request: ( + options: Required, + ) => Promise>, + options: Required, +) { + const limiter: TBottleneck = new Bottleneck(); limiter.on("failed", function (error, info) { const maxRetries = ~~error.request.request.retries; @@ -19,16 +33,20 @@ export async function wrapRequest(state, octokit, request, options) { }); return limiter.schedule( + // @ts-expect-error requestWithGraphqlErrorHandling.bind(null, state, octokit, request), options, ); } async function requestWithGraphqlErrorHandling( - state, - octokit, - request, - options, + state: State, + octokit: Octokit, + request: ( + request: Request, + options: Required, + ) => Promise>, + options: Required, ) { const response = await request(request, options); diff --git a/test/retry.test.ts b/test/retry.test.ts index f16d31a4..134e6026 100644 --- a/test/retry.test.ts +++ b/test/retry.test.ts @@ -427,6 +427,7 @@ describe("errorRequest", function () { delete error.request; try { + // @ts-expect-error await errorRequest(state, octokit, error, errorOptions); expect(1).not.toBe(1); } catch (e: any) { @@ -455,6 +456,7 @@ describe("errorRequest", function () { const error = new RequestError("Internal server error", 500, errorOptions); try { + // @ts-expect-error await errorRequest(state, octokit, error, errorOptions); expect(1).not.toBe(1); } catch (e: any) {