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

fix(types): improvements to internal types and options parameter #642

Merged
merged 13 commits into from
Nov 18, 2023
Merged
8 changes: 4 additions & 4 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
"update-endpoints": "npm-run-all update-endpoints:*",
"update-endpoints:fetch-json": "node scripts/update-endpoints/fetch-json",
"update-endpoints:code": "node scripts/update-endpoints/code",
"validate:ts": "tsc --noEmit --noImplicitAny --target es2020 --esModuleInterop --moduleResolution node test/typescript-validate.ts"
"validate:ts": "npm run build && tsc --noEmit --noImplicitAny --target es2020 --esModuleInterop --moduleResolution node test/typescript-validate.ts"
},
"repository": "github:octokit/plugin-throttling.js",
"author": "Simon Grondin (http://github.com/SGrondin)",
"license": "MIT",
"dependencies": {
"@octokit/types": "^12.0.0",
"@octokit/types": "^12.2.0",
"bottleneck": "^2.15.3"
},
"peerDependencies": {
Expand Down
40 changes: 32 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
// @ts-expect-error
import BottleneckLight from "bottleneck/light";
import type TBottleneck from "bottleneck";
import { Octokit } from "@octokit/core";
import type { OctokitOptions } from "@octokit/core/dist-types/types.d";
import type { Groups, ThrottlingOptions } from "./types";
import type { Groups, State, ThrottlingOptions } from "./types";
import { VERSION } from "./version";

import { wrapRequest } from "./wrap-request";
import triggersNotificationPaths from "./generated/triggers-notification-paths";
import { routeMatcher } from "./route-matcher";
import type { EndpointDefaults, OctokitResponse } from "@octokit/types";

// Workaround to allow tests to directly access the triggersNotification function.
const regex = routeMatcher(triggersNotificationPaths);
const triggersNotification = regex.test.bind(regex);

const groups: Groups = {};

// @ts-expect-error
const createGroups = function (Bottleneck, common) {
const createGroups = function (
Bottleneck: typeof TBottleneck,
common: {
connection:
| TBottleneck.RedisConnection
| TBottleneck.IORedisConnection
| undefined;
timeout: number;
},
) {
groups.global = new Bottleneck.Group({
id: "octokit-global",
maxConcurrent: 10,
Expand Down Expand Up @@ -45,7 +55,7 @@ const createGroups = function (Bottleneck, common) {
export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
const {
enabled = true,
Bottleneck = BottleneckLight,
Bottleneck = BottleneckLight as typeof TBottleneck,
id = "no-id",
timeout = 1000 * 60 * 2, // Redis TTL: 2 minutes
connection,
Expand All @@ -59,15 +69,15 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
createGroups(Bottleneck, common);
}

const state = Object.assign(
const state: State = Object.assign(
{
clustering: connection != null,
triggersNotification,
fallbackSecondaryRateRetryAfter: 60,
retryAfterBaseValue: 1000,
retryLimiter: new Bottleneck(),
id,
...groups,
...(groups as Required<Groups>),
},
octokitOptions.throttle,
);
Expand Down Expand Up @@ -100,9 +110,12 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
octokit.log.warn("Error in throttling-plugin limit handler", e),
);

// @ts-expect-error
state.retryLimiter.on("failed", async function (error, info) {
const [state, request, options] = info.args;
const [state, request, options] = info.args as [
State,
OctokitResponse<any>,
wolfy1339 marked this conversation as resolved.
Show resolved Hide resolved
Required<EndpointDefaults>,
];
const { pathname } = new URL(options.url, "http://github.test");
const shouldRetryGraphQL =
pathname.startsWith("/graphql") && error.status !== 401;
Expand Down Expand Up @@ -174,6 +187,11 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
}
});

// 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));

return {};
Expand All @@ -187,4 +205,10 @@ declare module "@octokit/core/dist-types/types.d" {
}
}

declare module "@octokit/types" {
interface OctokitResponse<T, S extends number = number> {
retryCount: number;
}
}

export type { ThrottlingOptions };
13 changes: 12 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Octokit } from "@octokit/core";
import type { EndpointDefaults } from "@octokit/types";
import Bottleneck from "bottleneck";

type LimitHandler = (
retryAfter: number,
options: object,
options: Required<EndpointDefaults>,
octokit: Octokit,
retryCount: number,
) => void;
Expand Down Expand Up @@ -42,3 +43,13 @@ export type Groups = {
search?: Bottleneck.Group;
notifications?: Bottleneck.Group;
};

export type State = {
clustering: boolean;
triggersNotification: (pathname: string) => boolean;
fallbackSecondaryRateRetryAfter: number;
retryAfterBaseValue: number;
retryLimiter: Bottleneck;
id: string;
} & Required<Groups> &
ThrottlingOptions;
32 changes: 25 additions & 7 deletions src/wrap-request.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import type { EndpointDefaults, OctokitResponse } from "@octokit/types";
import type { State } from "./types";

const noop = () => Promise.resolve();

// @ts-expect-error
export function wrapRequest(state, request, options) {
export function wrapRequest(
state: State,
request: ((
options: Required<EndpointDefaults>,
) => Promise<OctokitResponse<any>>) & { retryCount: number },
options: Required<EndpointDefaults>,
) {
return state.retryLimiter.schedule(doRequest, state, request, options);
}

// @ts-expect-error
async function doRequest(state, request, options) {
async function doRequest(
state: State,
request: ((
options: Required<EndpointDefaults>,
) => Promise<OctokitResponse<any>>) & { retryCount: number },
options: Required<EndpointDefaults>,
) {
const isWrite = options.method !== "GET" && options.method !== "HEAD";
const { pathname } = new URL(options.url, "http://github.test");
const isSearch = options.method === "GET" && pathname.startsWith("/search/");
Expand Down Expand Up @@ -37,14 +50,19 @@ async function doRequest(state, request, options) {
await state.search.key(state.id).schedule(jobOptions, noop);
}

const req = state.global.key(state.id).schedule(jobOptions, request, options);
const req = state.global
.key(state.id)
.schedule<OctokitResponse<any>, Required<EndpointDefaults>>(
jobOptions,
request,
options,
);
if (isGraphQL) {
const res = await req;

if (
res.data.errors != null &&
// @ts-expect-error
res.data.errors.some((error) => error.type === "RATE_LIMITED")
res.data.errors.some((error: any) => error.type === "RATE_LIMITED")
) {
const error = Object.assign(new Error("GraphQL Rate Limit Exceeded"), {
response: res,
Expand Down
2 changes: 2 additions & 0 deletions test/exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ describe("Exports", function () {
};

options.enabled = false;
// @ts-expect-error
options.onRateLimit(10, {}, {} as Octokit, 0);
// @ts-expect-error
options.onSecondaryRateLimit(10, {}, {} as Octokit, 0);
});
wolfy1339 marked this conversation as resolved.
Show resolved Hide resolved
});
2 changes: 1 addition & 1 deletion test/typescript-validate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Octokit } from "@octokit/core";
import { throttling } from "../src/index";
import { throttling } from "../pkg";
// ************************************************************
// THIS CODE IS NOT EXECUTED. IT IS FOR TYPECHECKING ONLY
// ************************************************************
Expand Down
Loading