Skip to content

Commit 2a64f7f

Browse files
authoredNov 18, 2023
fix(types): improvements to internal types and options parameter (#642)
* fix(types): improvements to internal types and `options` parameter * fix: remove unneeded `ts-expext-errror` directive * chore: remove most `ts-expect-error` annotations * Add internal `State` type * Type the `wrapRequest()` function * Add type augmentation of `OctokitResponse` * fix: improve options type * style: prettier * fix: add missing type import * fix: move `ts-expext-error` directive * test: add `ts-expect-error` * build(deps): update `@octokit/types` * chore: remove more `ts-expect-error` * chore: move `ts-expect-error` directive directly before function * build: workaround typescript Build the project first, then import from the build and then do the type check
1 parent f5d504e commit 2a64f7f

7 files changed

+78
-23
lines changed
 

‎package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
"update-endpoints": "npm-run-all update-endpoints:*",
1515
"update-endpoints:fetch-json": "node scripts/update-endpoints/fetch-json",
1616
"update-endpoints:code": "node scripts/update-endpoints/code",
17-
"validate:ts": "tsc --noEmit --noImplicitAny --target es2020 --esModuleInterop --moduleResolution node test/typescript-validate.ts"
17+
"validate:ts": "npm run build && tsc --noEmit --noImplicitAny --target es2020 --esModuleInterop --moduleResolution node test/typescript-validate.ts"
1818
},
1919
"repository": "github:octokit/plugin-throttling.js",
2020
"author": "Simon Grondin (http://github.com/SGrondin)",
2121
"license": "MIT",
2222
"dependencies": {
23-
"@octokit/types": "^12.0.0",
23+
"@octokit/types": "^12.2.0",
2424
"bottleneck": "^2.15.3"
2525
},
2626
"peerDependencies": {

‎src/index.ts

+32-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
// @ts-expect-error
22
import BottleneckLight from "bottleneck/light";
3+
import type TBottleneck from "bottleneck";
34
import { Octokit } from "@octokit/core";
45
import type { OctokitOptions } from "@octokit/core/dist-types/types.d";
5-
import type { Groups, ThrottlingOptions } from "./types";
6+
import type { Groups, State, ThrottlingOptions } from "./types";
67
import { VERSION } from "./version";
78

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

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

1618
const groups: Groups = {};
1719

18-
// @ts-expect-error
19-
const createGroups = function (Bottleneck, common) {
20+
const createGroups = function (
21+
Bottleneck: typeof TBottleneck,
22+
common: {
23+
connection:
24+
| TBottleneck.RedisConnection
25+
| TBottleneck.IORedisConnection
26+
| undefined;
27+
timeout: number;
28+
},
29+
) {
2030
groups.global = new Bottleneck.Group({
2131
id: "octokit-global",
2232
maxConcurrent: 10,
@@ -45,7 +55,7 @@ const createGroups = function (Bottleneck, common) {
4555
export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
4656
const {
4757
enabled = true,
48-
Bottleneck = BottleneckLight,
58+
Bottleneck = BottleneckLight as typeof TBottleneck,
4959
id = "no-id",
5060
timeout = 1000 * 60 * 2, // Redis TTL: 2 minutes
5161
connection,
@@ -59,15 +69,15 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
5969
createGroups(Bottleneck, common);
6070
}
6171

62-
const state = Object.assign(
72+
const state: State = Object.assign(
6373
{
6474
clustering: connection != null,
6575
triggersNotification,
6676
fallbackSecondaryRateRetryAfter: 60,
6777
retryAfterBaseValue: 1000,
6878
retryLimiter: new Bottleneck(),
6979
id,
70-
...groups,
80+
...(groups as Required<Groups>),
7181
},
7282
octokitOptions.throttle,
7383
);
@@ -100,9 +110,12 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
100110
octokit.log.warn("Error in throttling-plugin limit handler", e),
101111
);
102112

103-
// @ts-expect-error
104113
state.retryLimiter.on("failed", async function (error, info) {
105-
const [state, request, options] = info.args;
114+
const [state, request, options] = info.args as [
115+
State,
116+
OctokitResponse<any>,
117+
Required<EndpointDefaults>,
118+
];
106119
const { pathname } = new URL(options.url, "http://github.test");
107120
const shouldRetryGraphQL =
108121
pathname.startsWith("/graphql") && error.status !== 401;
@@ -174,6 +187,11 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
174187
}
175188
});
176189

190+
// The types for `before-after-hook` do not let us only pass through a Promise return value
191+
// the types expect that the function can return either a Promise of the response, or diectly return the response.
192+
// This is due to the fact that `@octokit/request` uses aysnc functions
193+
// Also, since we add the custom `retryCount` property to the request argument, the types are not compatible.
194+
// @ts-expect-error
177195
octokit.hook.wrap("request", wrapRequest.bind(null, state));
178196

179197
return {};
@@ -187,4 +205,10 @@ declare module "@octokit/core/dist-types/types.d" {
187205
}
188206
}
189207

208+
declare module "@octokit/types" {
209+
interface OctokitResponse<T, S extends number = number> {
210+
retryCount: number;
211+
}
212+
}
213+
190214
export type { ThrottlingOptions };

‎src/types.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Octokit } from "@octokit/core";
2+
import type { EndpointDefaults } from "@octokit/types";
23
import Bottleneck from "bottleneck";
34

45
type LimitHandler = (
56
retryAfter: number,
6-
options: object,
7+
options: Required<EndpointDefaults>,
78
octokit: Octokit,
89
retryCount: number,
910
) => void;
@@ -42,3 +43,13 @@ export type Groups = {
4243
search?: Bottleneck.Group;
4344
notifications?: Bottleneck.Group;
4445
};
46+
47+
export type State = {
48+
clustering: boolean;
49+
triggersNotification: (pathname: string) => boolean;
50+
fallbackSecondaryRateRetryAfter: number;
51+
retryAfterBaseValue: number;
52+
retryLimiter: Bottleneck;
53+
id: string;
54+
} & Required<Groups> &
55+
ThrottlingOptions;

‎src/wrap-request.ts

+25-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1+
import type { EndpointDefaults, OctokitResponse } from "@octokit/types";
2+
import type { State } from "./types";
3+
14
const noop = () => Promise.resolve();
25

3-
// @ts-expect-error
4-
export function wrapRequest(state, request, options) {
6+
export function wrapRequest(
7+
state: State,
8+
request: ((
9+
options: Required<EndpointDefaults>,
10+
) => Promise<OctokitResponse<any>>) & { retryCount: number },
11+
options: Required<EndpointDefaults>,
12+
) {
513
return state.retryLimiter.schedule(doRequest, state, request, options);
614
}
715

8-
// @ts-expect-error
9-
async function doRequest(state, request, options) {
16+
async function doRequest(
17+
state: State,
18+
request: ((
19+
options: Required<EndpointDefaults>,
20+
) => Promise<OctokitResponse<any>>) & { retryCount: number },
21+
options: Required<EndpointDefaults>,
22+
) {
1023
const isWrite = options.method !== "GET" && options.method !== "HEAD";
1124
const { pathname } = new URL(options.url, "http://github.test");
1225
const isSearch = options.method === "GET" && pathname.startsWith("/search/");
@@ -37,14 +50,19 @@ async function doRequest(state, request, options) {
3750
await state.search.key(state.id).schedule(jobOptions, noop);
3851
}
3952

40-
const req = state.global.key(state.id).schedule(jobOptions, request, options);
53+
const req = state.global
54+
.key(state.id)
55+
.schedule<OctokitResponse<any>, Required<EndpointDefaults>>(
56+
jobOptions,
57+
request,
58+
options,
59+
);
4160
if (isGraphQL) {
4261
const res = await req;
4362

4463
if (
4564
res.data.errors != null &&
46-
// @ts-expect-error
47-
res.data.errors.some((error) => error.type === "RATE_LIMITED")
65+
res.data.errors.some((error: any) => error.type === "RATE_LIMITED")
4866
) {
4967
const error = Object.assign(new Error("GraphQL Rate Limit Exceeded"), {
5068
response: res,

‎test/exports.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ describe("Exports", function () {
1010
};
1111

1212
options.enabled = false;
13+
// @ts-expect-error
1314
options.onRateLimit(10, {}, {} as Octokit, 0);
15+
// @ts-expect-error
1416
options.onSecondaryRateLimit(10, {}, {} as Octokit, 0);
1517
});
1618
});

‎test/typescript-validate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Octokit } from "@octokit/core";
2-
import { throttling } from "../src/index";
2+
import { throttling } from "../pkg";
33
// ************************************************************
44
// THIS CODE IS NOT EXECUTED. IT IS FOR TYPECHECKING ONLY
55
// ************************************************************

0 commit comments

Comments
 (0)
Please sign in to comment.