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