Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
9 changes: 0 additions & 9 deletions scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,6 @@ async function main() {
sourcemap: false,
});

// Remove the types file from the dist-src folder
const typeFiles = await glob([
"./pkg/dist-src/**/types.js.map",
"./pkg/dist-src/**/types.js",
]);
for (const typeFile of typeFiles) {
await rm(typeFile);
}

const entryPoints = ["./pkg/dist-src/index.js"];

await esbuild.build({
Expand Down
16 changes: 16 additions & 0 deletions src/bottleneck.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/** Minimal typings for "bottleneck/light.js" based on the usage in this library. */
declare module "bottleneck/light.js" {
export type RetryableInfo = { readonly retryCount: number };

export class Bottleneck {
constructor(options?: any);

on(name: "failed", fn: (error: any, info: RetryableInfo) => any): void;

schedule<A extends unknown[], R>(
task: (...args: A) => Promise<R>,
...taskArgs: A
): Promise<R>;
}
export default Bottleneck;
}
13 changes: 10 additions & 3 deletions src/error-request.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// @ts-ignore
import { isRequestError, type RetryPlugin, type RetryState } from "./types.js";
import type { RequestRequestOptions } from "@octokit/types";
import type { RequestError } from "@octokit/request-error";

export async function errorRequest(state, octokit, error, options) {
if (!error.request || !error.request.request) {
export async function errorRequest(
state: RetryState,
octokit: RetryPlugin,
error: RequestError | Error,
options: { request: RequestRequestOptions },
): Promise<any> {
if (!isRequestError(error) || !error?.request.request) {
// address https://github.com/octokit/plugin-retry.js/issues/8
throw error;
}
Expand Down
29 changes: 18 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import type { Octokit } from "@octokit/core";
import type { Octokit, OctokitOptions } from "@octokit/core";
import type { RequestError } from "@octokit/request-error";

import { VERSION } from "./version.js";
import { errorRequest } from "./error-request.js";
import { wrapRequest } from "./wrap-request.js";
import type { RetryOptions, RetryPlugin, RetryState } from "./types.js";
import type { RequestRequestOptions } from "@octokit/types";
export { VERSION } from "./version.js";

export function retry(octokit: Octokit, octokitOptions: any) {
const state = Object.assign(
export function retry(
octokit: Octokit,
octokitOptions: OctokitOptions & Partial<RetryOptions>,
): RetryPlugin {
const state: RetryState = Object.assign(
{
enabled: true,
retryAfterBaseValue: 1000,
doNotRetry: [400, 401, 403, 404, 410, 422, 451],
retries: 3,
},
} satisfies RetryState,
octokitOptions.retry,
);

if (state.enabled) {
octokit.hook.error("request", errorRequest.bind(null, state, octokit));
octokit.hook.wrap("request", wrapRequest.bind(null, state, octokit));
}

return {
const retryPlugin: RetryPlugin = {
retry: {
retryRequest: (
error: RequestError,
Expand All @@ -32,11 +32,18 @@ export function retry(octokit: Octokit, octokitOptions: any) {
error.request.request = Object.assign({}, error.request.request, {
retries: retries,
retryAfter: retryAfter,
});
} satisfies RequestRequestOptions);

return error;
},
},
};

if (state.enabled) {
octokit.hook.error("request", errorRequest.bind(null, state, retryPlugin));
octokit.hook.wrap("request", wrapRequest.bind(null, state, retryPlugin));
}

return retryPlugin;
}
retry.VERSION = VERSION;
26 changes: 26 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { RequestError } from "@octokit/request-error";

export interface RetryPlugin {
retry: {
retryRequest: (
error: RequestError,
retries: number,
retryAfter: number,
) => RequestError;
};
}

export interface RetryState {
enabled: boolean;
retryAfterBaseValue: number;
doNotRetry: number[];
retries: number;
}

export interface RetryOptions {
retry: RetryOptions;
}

export function isRequestError(error: any): error is RequestError {
return error.request !== undefined;
}
34 changes: 22 additions & 12 deletions src/wrap-request.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
// @ts-nocheck
import Bottleneck from "bottleneck/light.js";
import Bottleneck, { type RetryableInfo } from "bottleneck/light.js";
import { RequestError } from "@octokit/request-error";
import { errorRequest } from "./error-request.js";
import type { RetryPlugin, RetryState } from "./types.js";
import type { EndpointDefaults, OctokitResponse } from "@octokit/types";

export async function wrapRequest(state, octokit, request, options) {
type RequestHook = (
options: Required<EndpointDefaults>,
) => OctokitResponse<any, number> | Promise<OctokitResponse<any, number>>;

export async function wrapRequest(
state: RetryState,
octokit: RetryPlugin,
request: RequestHook,
options: Required<EndpointDefaults>,
) {
const limiter = new Bottleneck();

limiter.on("failed", function (error, info) {
const maxRetries = ~~error.request.request.retries;
const after = ~~error.request.request.retryAfter;
limiter.on("failed", function (error: RequestError, info: RetryableInfo) {
const maxRetries = ~~error.request.request?.retries;
const after = ~~error.request.request?.retryAfter;
options.request.retryCount = info.retryCount + 1;

if (maxRetries > info.retryCount) {
Expand All @@ -25,12 +35,12 @@ export async function wrapRequest(state, octokit, request, options) {
}

async function requestWithGraphqlErrorHandling(
state,
octokit,
request,
options,
) {
const response = await request(request, options);
state: RetryState,
octokit: RetryPlugin,
request: RequestHook,
options: Required<EndpointDefaults>,
): Promise<OctokitResponse<any, number>> {
const response = await request(options);

if (
response.data &&
Expand Down