First upload version 0.0.1
This commit is contained in:
21
node_modules/@octokit/plugin-throttling/LICENSE
generated
vendored
Normal file
21
node_modules/@octokit/plugin-throttling/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
237
node_modules/@octokit/plugin-throttling/README.md
generated
vendored
Normal file
237
node_modules/@octokit/plugin-throttling/README.md
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
# plugin-throttling.js
|
||||
|
||||
> Octokit plugin for GitHub’s recommended request throttling
|
||||
|
||||
[](https://www.npmjs.com/package/@octokit/plugin-throttling)
|
||||
[](https://github.com/octokit/plugin-throttling.js/actions?workflow=Test)
|
||||
|
||||
Implements all [recommended best practices](https://docs.github.com/en/rest/guides/best-practices-for-integrators) to prevent hitting secondary rate limits.
|
||||
|
||||
## Usage
|
||||
|
||||
<table>
|
||||
<tbody valign=top align=left>
|
||||
<tr><th>
|
||||
Browsers
|
||||
</th><td width=100%>
|
||||
|
||||
Load `@octokit/plugin-throttling` and [`@octokit/core`](https://github.com/octokit/core.js) (or core-compatible module) directly from [esm.sh](https://esm.sh)
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { Octokit } from "https://esm.sh/@octokit/core";
|
||||
import { throttling } from "https://esm.sh/@octokit/plugin-throttling";
|
||||
</script>
|
||||
```
|
||||
|
||||
</td></tr>
|
||||
<tr><th>
|
||||
Node
|
||||
</th><td>
|
||||
|
||||
Install with `npm install @octokit/core @octokit/plugin-throttling`. Optionally replace `@octokit/core` with a core-compatible module.
|
||||
|
||||
```js
|
||||
import { Octokit } from "@octokit/core";
|
||||
import { throttling } from "@octokit/plugin-throttling";
|
||||
```
|
||||
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> As we use [conditional exports](https://nodejs.org/api/packages.html#conditional-exports), you will need to adapt your `tsconfig.json` by setting `"moduleResolution": "node16", "module": "node16"`.
|
||||
>
|
||||
> See the TypeScript docs on [package.json "exports"](https://www.typescriptlang.org/docs/handbook/modules/reference.html#packagejson-exports).<br>
|
||||
> See this [helpful guide on transitioning to ESM](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) from [@sindresorhus](https://github.com/sindresorhus)
|
||||
|
||||
The code below creates a "Hello, world!" issue on every repository in a given organization. Without the throttling plugin it would send many requests in parallel and would hit rate limits very quickly. But the `@octokit/plugin-throttling` slows down your requests according to the official guidelines, so you don't get blocked before your quota is exhausted.
|
||||
|
||||
The `throttle.onSecondaryRateLimit` and `throttle.onRateLimit` options are required. Return `true` to automatically retry the request after `retryAfter` seconds.
|
||||
|
||||
```js
|
||||
const MyOctokit = Octokit.plugin(throttling);
|
||||
|
||||
const octokit = new MyOctokit({
|
||||
auth: `secret123`,
|
||||
throttle: {
|
||||
onRateLimit: (retryAfter, options, octokit, retryCount) => {
|
||||
octokit.log.warn(
|
||||
`Request quota exhausted for request ${options.method} ${options.url}`,
|
||||
);
|
||||
|
||||
if (retryCount < 1) {
|
||||
// only retries once
|
||||
octokit.log.info(`Retrying after ${retryAfter} seconds!`);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
onSecondaryRateLimit: (retryAfter, options, octokit) => {
|
||||
// does not retry, only logs a warning
|
||||
octokit.log.warn(
|
||||
`SecondaryRateLimit detected for request ${options.method} ${options.url}`,
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function createIssueOnAllRepos(org) {
|
||||
const repos = await octokit.paginate(
|
||||
octokit.repos.listForOrg.endpoint({ org }),
|
||||
);
|
||||
return Promise.all(
|
||||
repos.map(({ name }) =>
|
||||
octokit.issues.create({
|
||||
owner,
|
||||
repo: name,
|
||||
title: "Hello, world!",
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Pass `{ throttle: { enabled: false } }` to disable this plugin.
|
||||
|
||||
### Clustering
|
||||
|
||||
Enabling Clustering support ensures that your application will not go over rate limits **across Octokit instances and across Nodejs processes**.
|
||||
|
||||
First install either `redis` or `ioredis`:
|
||||
|
||||
```
|
||||
# NodeRedis (https://github.com/NodeRedis/node_redis)
|
||||
npm install --save redis
|
||||
|
||||
# or ioredis (https://github.com/luin/ioredis)
|
||||
npm install --save ioredis
|
||||
```
|
||||
|
||||
Then in your application:
|
||||
|
||||
```js
|
||||
import Bottleneck from "bottleneck";
|
||||
import Redis from "redis";
|
||||
|
||||
const client = Redis.createClient({
|
||||
/* options */
|
||||
});
|
||||
const connection = new Bottleneck.RedisConnection({ client });
|
||||
connection.on("error", err => console.error(err));
|
||||
|
||||
const octokit = new MyOctokit({
|
||||
auth: 'secret123'
|
||||
throttle: {
|
||||
onSecondaryRateLimit: (retryAfter, options, octokit) => {
|
||||
/* ... */
|
||||
},
|
||||
onRateLimit: (retryAfter, options, octokit) => {
|
||||
/* ... */
|
||||
},
|
||||
|
||||
// The Bottleneck connection object
|
||||
connection,
|
||||
|
||||
// A "throttling ID". All octokit instances with the same ID
|
||||
// using the same Redis server will share the throttling.
|
||||
id: "my-super-app",
|
||||
|
||||
// Otherwise the plugin uses a lighter version of Bottleneck without Redis support
|
||||
Bottleneck
|
||||
}
|
||||
});
|
||||
|
||||
// To close the connection and allow your application to exit cleanly:
|
||||
await connection.disconnect();
|
||||
```
|
||||
|
||||
To use the `ioredis` library instead:
|
||||
|
||||
```js
|
||||
import Redis from "ioredis";
|
||||
const client = new Redis({
|
||||
/* options */
|
||||
});
|
||||
const connection = new Bottleneck.IORedisConnection({ client });
|
||||
connection.on("error", (err) => console.error(err));
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
<table>
|
||||
<thead align=left>
|
||||
<tr>
|
||||
<th>
|
||||
name
|
||||
</th>
|
||||
<th>
|
||||
type
|
||||
</th>
|
||||
<th width=100%>
|
||||
description
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody align=left valign=top>
|
||||
<tr>
|
||||
<th>
|
||||
<code>options.retryAfterBaseValue</code>
|
||||
</th>
|
||||
<td>
|
||||
<code>Number</code>
|
||||
</td>
|
||||
<td>
|
||||
Number of milliseconds that will be used to multiply the time to wait based on `retry-after` or `x-ratelimit-reset` headers. Defaults to <code>1000</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<code>options.fallbackSecondaryRateRetryAfter</code>
|
||||
</th>
|
||||
<td>
|
||||
<code>Number</code>
|
||||
</td>
|
||||
<td>
|
||||
Number of seconds to wait until retrying a request in case a secondary rate limit is hit and no <code>retry-after</code> header was present in the response. Defaults to <code>60</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<code>options.connection</code>
|
||||
</th>
|
||||
<td>
|
||||
<code>Bottleneck.RedisConnection</code>
|
||||
</td>
|
||||
<td>
|
||||
A Bottleneck connection instance. See <a href="#clustering">Clustering</a> above.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<code>options.id</code>
|
||||
</th>
|
||||
<td>
|
||||
<code>string</code>
|
||||
</td>
|
||||
<td>
|
||||
A "throttling ID". All octokit instances with the same ID using the same Redis server will share the throttling. See <a href="#clustering">Clustering</a> above. Defaults to <code>no-id</code>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<code>options.Bottleneck</code>
|
||||
</th>
|
||||
<td>
|
||||
<code>Bottleneck</code>
|
||||
</td>
|
||||
<td>
|
||||
Bottleneck constructor. See <a href="#clustering">Clustering</a> above. Defaults to `bottleneck/light`.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## LICENSE
|
||||
|
||||
[MIT](LICENSE)
|
||||
228
node_modules/@octokit/plugin-throttling/dist-bundle/index.js
generated
vendored
Normal file
228
node_modules/@octokit/plugin-throttling/dist-bundle/index.js
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
// pkg/dist-src/index.js
|
||||
import BottleneckLight from "bottleneck/light.js";
|
||||
|
||||
// pkg/dist-src/version.js
|
||||
var VERSION = "0.0.0-development";
|
||||
|
||||
// pkg/dist-src/wrap-request.js
|
||||
var noop = () => Promise.resolve();
|
||||
function wrapRequest(state, request, options) {
|
||||
return state.retryLimiter.schedule(doRequest, state, request, options);
|
||||
}
|
||||
async function doRequest(state, request, options) {
|
||||
const { pathname } = new URL(options.url, "http://github.test");
|
||||
const isAuth = isAuthRequest(options.method, pathname);
|
||||
const isWrite = !isAuth && options.method !== "GET" && options.method !== "HEAD";
|
||||
const isSearch = options.method === "GET" && pathname.startsWith("/search/");
|
||||
const isGraphQL = pathname.startsWith("/graphql");
|
||||
const retryCount = ~~request.retryCount;
|
||||
const jobOptions = retryCount > 0 ? { priority: 0, weight: 0 } : {};
|
||||
if (state.clustering) {
|
||||
jobOptions.expiration = 1e3 * 60;
|
||||
}
|
||||
if (isWrite || isGraphQL) {
|
||||
await state.write.key(state.id).schedule(jobOptions, noop);
|
||||
}
|
||||
if (isWrite && state.triggersNotification(pathname)) {
|
||||
await state.notifications.key(state.id).schedule(jobOptions, noop);
|
||||
}
|
||||
if (isSearch) {
|
||||
await state.search.key(state.id).schedule(jobOptions, noop);
|
||||
}
|
||||
const req = (isAuth ? state.auth : state.global).key(state.id).schedule(jobOptions, request, options);
|
||||
if (isGraphQL) {
|
||||
const res = await req;
|
||||
if (res.data.errors != null && res.data.errors.some((error) => error.type === "RATE_LIMITED")) {
|
||||
const error = Object.assign(new Error("GraphQL Rate Limit Exceeded"), {
|
||||
response: res,
|
||||
data: res.data
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return req;
|
||||
}
|
||||
function isAuthRequest(method, pathname) {
|
||||
return method === "PATCH" && // https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-a-scoped-access-token
|
||||
/^\/applications\/[^/]+\/token\/scoped$/.test(pathname) || method === "POST" && // https://docs.github.com/en/rest/apps/oauth-applications?apiVersion=2022-11-28#reset-a-token
|
||||
(/^\/applications\/[^/]+\/token$/.test(pathname) || // https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app
|
||||
/^\/app\/installations\/[^/]+\/access_tokens$/.test(pathname) || // https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
|
||||
pathname === "/login/oauth/access_token");
|
||||
}
|
||||
|
||||
// pkg/dist-src/generated/triggers-notification-paths.js
|
||||
var triggers_notification_paths_default = [
|
||||
"/orgs/{org}/invitations",
|
||||
"/orgs/{org}/invitations/{invitation_id}",
|
||||
"/orgs/{org}/teams/{team_slug}/discussions",
|
||||
"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments",
|
||||
"/repos/{owner}/{repo}/collaborators/{username}",
|
||||
"/repos/{owner}/{repo}/commits/{commit_sha}/comments",
|
||||
"/repos/{owner}/{repo}/issues",
|
||||
"/repos/{owner}/{repo}/issues/{issue_number}/comments",
|
||||
"/repos/{owner}/{repo}/issues/{issue_number}/sub_issue",
|
||||
"/repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority",
|
||||
"/repos/{owner}/{repo}/pulls",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/comments",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/merge",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/reviews",
|
||||
"/repos/{owner}/{repo}/releases",
|
||||
"/teams/{team_id}/discussions",
|
||||
"/teams/{team_id}/discussions/{discussion_number}/comments"
|
||||
];
|
||||
|
||||
// pkg/dist-src/route-matcher.js
|
||||
function routeMatcher(paths) {
|
||||
const regexes = paths.map(
|
||||
(path) => path.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
|
||||
);
|
||||
const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
|
||||
return new RegExp(regex2, "i");
|
||||
}
|
||||
|
||||
// pkg/dist-src/index.js
|
||||
var regex = routeMatcher(triggers_notification_paths_default);
|
||||
var triggersNotification = regex.test.bind(regex);
|
||||
var groups = {};
|
||||
var createGroups = function(Bottleneck, common) {
|
||||
groups.global = new Bottleneck.Group({
|
||||
id: "octokit-global",
|
||||
maxConcurrent: 10,
|
||||
...common
|
||||
});
|
||||
groups.auth = new Bottleneck.Group({
|
||||
id: "octokit-auth",
|
||||
maxConcurrent: 1,
|
||||
...common
|
||||
});
|
||||
groups.search = new Bottleneck.Group({
|
||||
id: "octokit-search",
|
||||
maxConcurrent: 1,
|
||||
minTime: 2e3,
|
||||
...common
|
||||
});
|
||||
groups.write = new Bottleneck.Group({
|
||||
id: "octokit-write",
|
||||
maxConcurrent: 1,
|
||||
minTime: 1e3,
|
||||
...common
|
||||
});
|
||||
groups.notifications = new Bottleneck.Group({
|
||||
id: "octokit-notifications",
|
||||
maxConcurrent: 1,
|
||||
minTime: 3e3,
|
||||
...common
|
||||
});
|
||||
};
|
||||
function throttling(octokit, octokitOptions) {
|
||||
const {
|
||||
enabled = true,
|
||||
Bottleneck = BottleneckLight,
|
||||
id = "no-id",
|
||||
timeout = 1e3 * 60 * 2,
|
||||
// Redis TTL: 2 minutes
|
||||
connection
|
||||
} = octokitOptions.throttle || {};
|
||||
if (!enabled) {
|
||||
return {};
|
||||
}
|
||||
const common = { timeout };
|
||||
if (typeof connection !== "undefined") {
|
||||
common.connection = connection;
|
||||
}
|
||||
if (groups.global == null) {
|
||||
createGroups(Bottleneck, common);
|
||||
}
|
||||
const state = Object.assign(
|
||||
{
|
||||
clustering: connection != null,
|
||||
triggersNotification,
|
||||
fallbackSecondaryRateRetryAfter: 60,
|
||||
retryAfterBaseValue: 1e3,
|
||||
retryLimiter: new Bottleneck(),
|
||||
id,
|
||||
...groups
|
||||
},
|
||||
octokitOptions.throttle
|
||||
);
|
||||
if (typeof state.onSecondaryRateLimit !== "function" || typeof state.onRateLimit !== "function") {
|
||||
throw new Error(`octokit/plugin-throttling error:
|
||||
You must pass the onSecondaryRateLimit and onRateLimit error handlers.
|
||||
See https://octokit.github.io/rest.js/#throttling
|
||||
|
||||
const octokit = new Octokit({
|
||||
throttle: {
|
||||
onSecondaryRateLimit: (retryAfter, options) => {/* ... */},
|
||||
onRateLimit: (retryAfter, options) => {/* ... */}
|
||||
}
|
||||
})
|
||||
`);
|
||||
}
|
||||
const events = {};
|
||||
const emitter = new Bottleneck.Events(events);
|
||||
events.on("secondary-limit", state.onSecondaryRateLimit);
|
||||
events.on("rate-limit", state.onRateLimit);
|
||||
events.on(
|
||||
"error",
|
||||
(e) => octokit.log.warn("Error in throttling-plugin limit handler", e)
|
||||
);
|
||||
state.retryLimiter.on("failed", async function(error, info) {
|
||||
const [state2, request, options] = info.args;
|
||||
const { pathname } = new URL(options.url, "http://github.test");
|
||||
const shouldRetryGraphQL = pathname.startsWith("/graphql") && error.status !== 401;
|
||||
if (!(shouldRetryGraphQL || error.status === 403 || error.status === 429)) {
|
||||
return;
|
||||
}
|
||||
const retryCount = ~~request.retryCount;
|
||||
request.retryCount = retryCount;
|
||||
options.request.retryCount = retryCount;
|
||||
const { wantRetry, retryAfter = 0 } = await (async function() {
|
||||
if (/\bsecondary rate\b/i.test(error.message)) {
|
||||
const retryAfter2 = Number(error.response.headers["retry-after"]) || state2.fallbackSecondaryRateRetryAfter;
|
||||
const wantRetry2 = await emitter.trigger(
|
||||
"secondary-limit",
|
||||
retryAfter2,
|
||||
options,
|
||||
octokit,
|
||||
retryCount
|
||||
);
|
||||
return { wantRetry: wantRetry2, retryAfter: retryAfter2 };
|
||||
}
|
||||
if (error.response.headers != null && error.response.headers["x-ratelimit-remaining"] === "0" || (error.response.data?.errors ?? []).some(
|
||||
(error2) => error2.type === "RATE_LIMITED"
|
||||
)) {
|
||||
const rateLimitReset = new Date(
|
||||
~~error.response.headers["x-ratelimit-reset"] * 1e3
|
||||
).getTime();
|
||||
const retryAfter2 = Math.max(
|
||||
// Add one second so we retry _after_ the reset time
|
||||
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#exceeding-the-rate-limit
|
||||
Math.ceil((rateLimitReset - Date.now()) / 1e3) + 1,
|
||||
0
|
||||
);
|
||||
const wantRetry2 = await emitter.trigger(
|
||||
"rate-limit",
|
||||
retryAfter2,
|
||||
options,
|
||||
octokit,
|
||||
retryCount
|
||||
);
|
||||
return { wantRetry: wantRetry2, retryAfter: retryAfter2 };
|
||||
}
|
||||
return {};
|
||||
})();
|
||||
if (wantRetry) {
|
||||
request.retryCount++;
|
||||
return retryAfter * state2.retryAfterBaseValue;
|
||||
}
|
||||
});
|
||||
octokit.hook.wrap("request", wrapRequest.bind(null, state));
|
||||
return {};
|
||||
}
|
||||
throttling.VERSION = VERSION;
|
||||
throttling.triggersNotification = triggersNotification;
|
||||
export {
|
||||
throttling
|
||||
};
|
||||
7
node_modules/@octokit/plugin-throttling/dist-bundle/index.js.map
generated
vendored
Normal file
7
node_modules/@octokit/plugin-throttling/dist-bundle/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
24
node_modules/@octokit/plugin-throttling/dist-src/generated/triggers-notification-paths.js
generated
vendored
Normal file
24
node_modules/@octokit/plugin-throttling/dist-src/generated/triggers-notification-paths.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
var triggers_notification_paths_default = [
|
||||
"/orgs/{org}/invitations",
|
||||
"/orgs/{org}/invitations/{invitation_id}",
|
||||
"/orgs/{org}/teams/{team_slug}/discussions",
|
||||
"/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments",
|
||||
"/repos/{owner}/{repo}/collaborators/{username}",
|
||||
"/repos/{owner}/{repo}/commits/{commit_sha}/comments",
|
||||
"/repos/{owner}/{repo}/issues",
|
||||
"/repos/{owner}/{repo}/issues/{issue_number}/comments",
|
||||
"/repos/{owner}/{repo}/issues/{issue_number}/sub_issue",
|
||||
"/repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority",
|
||||
"/repos/{owner}/{repo}/pulls",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/comments",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/merge",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers",
|
||||
"/repos/{owner}/{repo}/pulls/{pull_number}/reviews",
|
||||
"/repos/{owner}/{repo}/releases",
|
||||
"/teams/{team_id}/discussions",
|
||||
"/teams/{team_id}/discussions/{discussion_number}/comments"
|
||||
];
|
||||
export {
|
||||
triggers_notification_paths_default as default
|
||||
};
|
||||
148
node_modules/@octokit/plugin-throttling/dist-src/index.js
generated
vendored
Normal file
148
node_modules/@octokit/plugin-throttling/dist-src/index.js
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
import BottleneckLight from "bottleneck/light.js";
|
||||
import { VERSION } from "./version.js";
|
||||
import { wrapRequest } from "./wrap-request.js";
|
||||
import triggersNotificationPaths from "./generated/triggers-notification-paths.js";
|
||||
import { routeMatcher } from "./route-matcher.js";
|
||||
const regex = routeMatcher(triggersNotificationPaths);
|
||||
const triggersNotification = regex.test.bind(regex);
|
||||
const groups = {};
|
||||
const createGroups = function(Bottleneck, common) {
|
||||
groups.global = new Bottleneck.Group({
|
||||
id: "octokit-global",
|
||||
maxConcurrent: 10,
|
||||
...common
|
||||
});
|
||||
groups.auth = new Bottleneck.Group({
|
||||
id: "octokit-auth",
|
||||
maxConcurrent: 1,
|
||||
...common
|
||||
});
|
||||
groups.search = new Bottleneck.Group({
|
||||
id: "octokit-search",
|
||||
maxConcurrent: 1,
|
||||
minTime: 2e3,
|
||||
...common
|
||||
});
|
||||
groups.write = new Bottleneck.Group({
|
||||
id: "octokit-write",
|
||||
maxConcurrent: 1,
|
||||
minTime: 1e3,
|
||||
...common
|
||||
});
|
||||
groups.notifications = new Bottleneck.Group({
|
||||
id: "octokit-notifications",
|
||||
maxConcurrent: 1,
|
||||
minTime: 3e3,
|
||||
...common
|
||||
});
|
||||
};
|
||||
function throttling(octokit, octokitOptions) {
|
||||
const {
|
||||
enabled = true,
|
||||
Bottleneck = BottleneckLight,
|
||||
id = "no-id",
|
||||
timeout = 1e3 * 60 * 2,
|
||||
// Redis TTL: 2 minutes
|
||||
connection
|
||||
} = octokitOptions.throttle || {};
|
||||
if (!enabled) {
|
||||
return {};
|
||||
}
|
||||
const common = { timeout };
|
||||
if (typeof connection !== "undefined") {
|
||||
common.connection = connection;
|
||||
}
|
||||
if (groups.global == null) {
|
||||
createGroups(Bottleneck, common);
|
||||
}
|
||||
const state = Object.assign(
|
||||
{
|
||||
clustering: connection != null,
|
||||
triggersNotification,
|
||||
fallbackSecondaryRateRetryAfter: 60,
|
||||
retryAfterBaseValue: 1e3,
|
||||
retryLimiter: new Bottleneck(),
|
||||
id,
|
||||
...groups
|
||||
},
|
||||
octokitOptions.throttle
|
||||
);
|
||||
if (typeof state.onSecondaryRateLimit !== "function" || typeof state.onRateLimit !== "function") {
|
||||
throw new Error(`octokit/plugin-throttling error:
|
||||
You must pass the onSecondaryRateLimit and onRateLimit error handlers.
|
||||
See https://octokit.github.io/rest.js/#throttling
|
||||
|
||||
const octokit = new Octokit({
|
||||
throttle: {
|
||||
onSecondaryRateLimit: (retryAfter, options) => {/* ... */},
|
||||
onRateLimit: (retryAfter, options) => {/* ... */}
|
||||
}
|
||||
})
|
||||
`);
|
||||
}
|
||||
const events = {};
|
||||
const emitter = new Bottleneck.Events(events);
|
||||
events.on("secondary-limit", state.onSecondaryRateLimit);
|
||||
events.on("rate-limit", state.onRateLimit);
|
||||
events.on(
|
||||
"error",
|
||||
(e) => octokit.log.warn("Error in throttling-plugin limit handler", e)
|
||||
);
|
||||
state.retryLimiter.on("failed", async function(error, info) {
|
||||
const [state2, request, options] = info.args;
|
||||
const { pathname } = new URL(options.url, "http://github.test");
|
||||
const shouldRetryGraphQL = pathname.startsWith("/graphql") && error.status !== 401;
|
||||
if (!(shouldRetryGraphQL || error.status === 403 || error.status === 429)) {
|
||||
return;
|
||||
}
|
||||
const retryCount = ~~request.retryCount;
|
||||
request.retryCount = retryCount;
|
||||
options.request.retryCount = retryCount;
|
||||
const { wantRetry, retryAfter = 0 } = await (async function() {
|
||||
if (/\bsecondary rate\b/i.test(error.message)) {
|
||||
const retryAfter2 = Number(error.response.headers["retry-after"]) || state2.fallbackSecondaryRateRetryAfter;
|
||||
const wantRetry2 = await emitter.trigger(
|
||||
"secondary-limit",
|
||||
retryAfter2,
|
||||
options,
|
||||
octokit,
|
||||
retryCount
|
||||
);
|
||||
return { wantRetry: wantRetry2, retryAfter: retryAfter2 };
|
||||
}
|
||||
if (error.response.headers != null && error.response.headers["x-ratelimit-remaining"] === "0" || (error.response.data?.errors ?? []).some(
|
||||
(error2) => error2.type === "RATE_LIMITED"
|
||||
)) {
|
||||
const rateLimitReset = new Date(
|
||||
~~error.response.headers["x-ratelimit-reset"] * 1e3
|
||||
).getTime();
|
||||
const retryAfter2 = Math.max(
|
||||
// Add one second so we retry _after_ the reset time
|
||||
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#exceeding-the-rate-limit
|
||||
Math.ceil((rateLimitReset - Date.now()) / 1e3) + 1,
|
||||
0
|
||||
);
|
||||
const wantRetry2 = await emitter.trigger(
|
||||
"rate-limit",
|
||||
retryAfter2,
|
||||
options,
|
||||
octokit,
|
||||
retryCount
|
||||
);
|
||||
return { wantRetry: wantRetry2, retryAfter: retryAfter2 };
|
||||
}
|
||||
return {};
|
||||
})();
|
||||
if (wantRetry) {
|
||||
request.retryCount++;
|
||||
return retryAfter * state2.retryAfterBaseValue;
|
||||
}
|
||||
});
|
||||
octokit.hook.wrap("request", wrapRequest.bind(null, state));
|
||||
return {};
|
||||
}
|
||||
throttling.VERSION = VERSION;
|
||||
throttling.triggersNotification = triggersNotification;
|
||||
export {
|
||||
throttling
|
||||
};
|
||||
10
node_modules/@octokit/plugin-throttling/dist-src/route-matcher.js
generated
vendored
Normal file
10
node_modules/@octokit/plugin-throttling/dist-src/route-matcher.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
function routeMatcher(paths) {
|
||||
const regexes = paths.map(
|
||||
(path) => path.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
|
||||
);
|
||||
const regex = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
|
||||
return new RegExp(regex, "i");
|
||||
}
|
||||
export {
|
||||
routeMatcher
|
||||
};
|
||||
4
node_modules/@octokit/plugin-throttling/dist-src/version.js
generated
vendored
Normal file
4
node_modules/@octokit/plugin-throttling/dist-src/version.js
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
const VERSION = "11.0.3";
|
||||
export {
|
||||
VERSION
|
||||
};
|
||||
47
node_modules/@octokit/plugin-throttling/dist-src/wrap-request.js
generated
vendored
Normal file
47
node_modules/@octokit/plugin-throttling/dist-src/wrap-request.js
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
const noop = () => Promise.resolve();
|
||||
function wrapRequest(state, request, options) {
|
||||
return state.retryLimiter.schedule(doRequest, state, request, options);
|
||||
}
|
||||
async function doRequest(state, request, options) {
|
||||
const { pathname } = new URL(options.url, "http://github.test");
|
||||
const isAuth = isAuthRequest(options.method, pathname);
|
||||
const isWrite = !isAuth && options.method !== "GET" && options.method !== "HEAD";
|
||||
const isSearch = options.method === "GET" && pathname.startsWith("/search/");
|
||||
const isGraphQL = pathname.startsWith("/graphql");
|
||||
const retryCount = ~~request.retryCount;
|
||||
const jobOptions = retryCount > 0 ? { priority: 0, weight: 0 } : {};
|
||||
if (state.clustering) {
|
||||
jobOptions.expiration = 1e3 * 60;
|
||||
}
|
||||
if (isWrite || isGraphQL) {
|
||||
await state.write.key(state.id).schedule(jobOptions, noop);
|
||||
}
|
||||
if (isWrite && state.triggersNotification(pathname)) {
|
||||
await state.notifications.key(state.id).schedule(jobOptions, noop);
|
||||
}
|
||||
if (isSearch) {
|
||||
await state.search.key(state.id).schedule(jobOptions, noop);
|
||||
}
|
||||
const req = (isAuth ? state.auth : state.global).key(state.id).schedule(jobOptions, request, options);
|
||||
if (isGraphQL) {
|
||||
const res = await req;
|
||||
if (res.data.errors != null && res.data.errors.some((error) => error.type === "RATE_LIMITED")) {
|
||||
const error = Object.assign(new Error("GraphQL Rate Limit Exceeded"), {
|
||||
response: res,
|
||||
data: res.data
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return req;
|
||||
}
|
||||
function isAuthRequest(method, pathname) {
|
||||
return method === "PATCH" && // https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-a-scoped-access-token
|
||||
/^\/applications\/[^/]+\/token\/scoped$/.test(pathname) || method === "POST" && // https://docs.github.com/en/rest/apps/oauth-applications?apiVersion=2022-11-28#reset-a-token
|
||||
(/^\/applications\/[^/]+\/token$/.test(pathname) || // https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app
|
||||
/^\/app\/installations\/[^/]+\/access_tokens$/.test(pathname) || // https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
|
||||
pathname === "/login/oauth/access_token");
|
||||
}
|
||||
export {
|
||||
wrapRequest
|
||||
};
|
||||
2
node_modules/@octokit/plugin-throttling/dist-types/generated/triggers-notification-paths.d.ts
generated
vendored
Normal file
2
node_modules/@octokit/plugin-throttling/dist-types/generated/triggers-notification-paths.d.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const _default: string[];
|
||||
export default _default;
|
||||
18
node_modules/@octokit/plugin-throttling/dist-types/index.d.ts
generated
vendored
Normal file
18
node_modules/@octokit/plugin-throttling/dist-types/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Octokit, OctokitOptions } from "@octokit/core";
|
||||
import type { ThrottlingOptions } from "./types.js";
|
||||
export declare function throttling(octokit: Octokit, octokitOptions: OctokitOptions): {};
|
||||
export declare namespace throttling {
|
||||
var VERSION: string;
|
||||
var triggersNotification: (string: string) => boolean;
|
||||
}
|
||||
declare module "@octokit/core" {
|
||||
interface OctokitOptions {
|
||||
throttle?: ThrottlingOptions;
|
||||
}
|
||||
}
|
||||
declare module "@octokit/types" {
|
||||
interface OctokitResponse<T, S extends number = number> {
|
||||
retryCount: number;
|
||||
}
|
||||
}
|
||||
export type { ThrottlingOptions };
|
||||
1
node_modules/@octokit/plugin-throttling/dist-types/route-matcher.d.ts
generated
vendored
Normal file
1
node_modules/@octokit/plugin-throttling/dist-types/route-matcher.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function routeMatcher(paths: string[]): RegExp;
|
||||
47
node_modules/@octokit/plugin-throttling/dist-types/types.d.ts
generated
vendored
Normal file
47
node_modules/@octokit/plugin-throttling/dist-types/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { Octokit } from "@octokit/core";
|
||||
import type { EndpointDefaults } from "@octokit/types";
|
||||
import type Bottleneck from "bottleneck";
|
||||
type LimitHandler = (retryAfter: number, options: Required<EndpointDefaults>, octokit: Octokit, retryCount: number) => void;
|
||||
export type SecondaryLimitHandler = {
|
||||
onSecondaryRateLimit: LimitHandler;
|
||||
};
|
||||
export type ThrottlingOptionsBase = {
|
||||
enabled?: boolean;
|
||||
Bottleneck?: typeof Bottleneck;
|
||||
id?: string;
|
||||
timeout?: number;
|
||||
connection?: Bottleneck.RedisConnection | Bottleneck.IORedisConnection;
|
||||
/**
|
||||
* @deprecated use `fallbackSecondaryRateRetryAfter`
|
||||
*/
|
||||
minimalSecondaryRateRetryAfter?: number;
|
||||
fallbackSecondaryRateRetryAfter?: number;
|
||||
retryAfterBaseValue?: number;
|
||||
write?: Bottleneck.Group;
|
||||
search?: Bottleneck.Group;
|
||||
notifications?: Bottleneck.Group;
|
||||
onRateLimit: LimitHandler;
|
||||
};
|
||||
export type ThrottlingOptions = (ThrottlingOptionsBase & SecondaryLimitHandler) | (Partial<ThrottlingOptionsBase & SecondaryLimitHandler> & {
|
||||
enabled: false;
|
||||
});
|
||||
export type Groups = {
|
||||
global?: Bottleneck.Group;
|
||||
auth?: Bottleneck.Group;
|
||||
write?: Bottleneck.Group;
|
||||
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;
|
||||
export type CreateGroupsCommon = {
|
||||
connection?: Bottleneck.RedisConnection | Bottleneck.IORedisConnection;
|
||||
timeout: number;
|
||||
};
|
||||
export {};
|
||||
1
node_modules/@octokit/plugin-throttling/dist-types/version.d.ts
generated
vendored
Normal file
1
node_modules/@octokit/plugin-throttling/dist-types/version.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare const VERSION = "11.0.3";
|
||||
5
node_modules/@octokit/plugin-throttling/dist-types/wrap-request.d.ts
generated
vendored
Normal file
5
node_modules/@octokit/plugin-throttling/dist-types/wrap-request.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { EndpointDefaults, OctokitResponse } from "@octokit/types";
|
||||
import type { State } from "./types.js";
|
||||
export declare function wrapRequest(state: State, request: ((options: Required<EndpointDefaults>) => Promise<OctokitResponse<any>>) & {
|
||||
retryCount: number;
|
||||
}, options: Required<EndpointDefaults>): Promise<OctokitResponse<any, number>>;
|
||||
52
node_modules/@octokit/plugin-throttling/package.json
generated
vendored
Normal file
52
node_modules/@octokit/plugin-throttling/package.json
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@octokit/plugin-throttling",
|
||||
"version": "11.0.3",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"type": "module",
|
||||
"description": "Octokit plugin for GitHub's recommended request throttling",
|
||||
"repository": "github:octokit/plugin-throttling.js",
|
||||
"author": "Simon Grondin (http://github.com/SGrondin)",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^16.0.0",
|
||||
"bottleneck": "^2.15.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/auth-app": "^8.1.2",
|
||||
"@octokit/core": "^7.0.6",
|
||||
"@octokit/request-error": "^7.0.2",
|
||||
"@octokit/tsconfig": "^4.0.0",
|
||||
"@types/node": "^24.0.0",
|
||||
"@vitest/coverage-v8": "^3.0.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"github-openapi-graphql-query": "^5.0.0",
|
||||
"glob": "^11.0.0",
|
||||
"npm-run-all2": "^8.0.0",
|
||||
"prettier": "3.5.3",
|
||||
"semantic-release-plugin-update-version-in-files": "^2.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
},
|
||||
"files": [
|
||||
"dist-*/**",
|
||||
"bin/**"
|
||||
],
|
||||
"types": "dist-types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist-types/index.d.ts",
|
||||
"import": "./dist-bundle/index.js",
|
||||
"default": "./dist-bundle/index.js"
|
||||
}
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
Reference in New Issue
Block a user