UNPKG

aws-delivlib

Version:

A fabulous library for defining continuous pipelines for building, testing and releasing code libraries.

237 lines (226 loc) • 9.57 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var BottleneckLight = _interopDefault(require('bottleneck/light')); const VERSION = "5.2.3"; const noop = () => Promise.resolve(); // @ts-expect-error function wrapRequest(state, request, options) { return state.retryLimiter.schedule(doRequest, state, request, options); } // @ts-expect-error async function doRequest(state, request, options) { 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/"); const isGraphQL = pathname.startsWith("/graphql"); const retryCount = ~~request.retryCount; const jobOptions = retryCount > 0 ? { priority: 0, weight: 0 } : {}; if (state.clustering) { // Remove a job from Redis if it has not completed or failed within 60s // Examples: Node process terminated, client disconnected, etc. // @ts-expect-error jobOptions.expiration = 1000 * 60; } // Guarantee at least 1000ms between writes // GraphQL can also trigger writes if (isWrite || isGraphQL) { await state.write.key(state.id).schedule(jobOptions, noop); } // Guarantee at least 3000ms between requests that trigger notifications if (isWrite && state.triggersNotification(pathname)) { await state.notifications.key(state.id).schedule(jobOptions, noop); } // Guarantee at least 2000ms between search requests if (isSearch) { await state.search.key(state.id).schedule(jobOptions, noop); } const req = state.global.key(state.id).schedule(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")) { const error = Object.assign(new Error("GraphQL Rate Limit Exceeded"), { response: res, data: res.data }); throw error; } } return req; } var triggersNotificationPaths = ["/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}/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"]; function routeMatcher(paths) { // EXAMPLE. For the following paths: /* [ "/orgs/{org}/invitations", "/repos/{owner}/{repo}/collaborators/{username}" ] */ const regexes = paths.map(path => path.split("/").map(c => c.startsWith("{") ? "(?:.+?)" : c).join("/")); // 'regexes' would contain: /* [ '/orgs/(?:.+?)/invitations', '/repos/(?:.+?)/(?:.+?)/collaborators/(?:.+?)' ] */ const regex = `^(?:${regexes.map(r => `(?:${r})`).join("|")})[^/]*$`; // 'regex' would contain: /* ^(?:(?:\/orgs\/(?:.+?)\/invitations)|(?:\/repos\/(?:.+?)\/(?:.+?)\/collaborators\/(?:.+?)))[^\/]*$ It may look scary, but paste it into https://www.debuggex.com/ and it will make a lot more sense! */ return new RegExp(regex, "i"); } // @ts-expect-error // Workaround to allow tests to directly access the triggersNotification function. const regex = routeMatcher(triggersNotificationPaths); const triggersNotification = regex.test.bind(regex); const groups = {}; // @ts-expect-error const createGroups = function (Bottleneck, common) { groups.global = new Bottleneck.Group({ id: "octokit-global", maxConcurrent: 10, ...common }); groups.search = new Bottleneck.Group({ id: "octokit-search", maxConcurrent: 1, minTime: 2000, ...common }); groups.write = new Bottleneck.Group({ id: "octokit-write", maxConcurrent: 1, minTime: 1000, ...common }); groups.notifications = new Bottleneck.Group({ id: "octokit-notifications", maxConcurrent: 1, minTime: 3000, ...common }); }; function throttling(octokit, octokitOptions) { const { enabled = true, Bottleneck = BottleneckLight, id = "no-id", timeout = 1000 * 60 * 2, // Redis TTL: 2 minutes connection } = octokitOptions.throttle || {}; if (!enabled) { return {}; } const common = { connection, timeout }; if (groups.global == null) { createGroups(Bottleneck, common); } if (octokitOptions.throttle && octokitOptions.throttle.minimalSecondaryRateRetryAfter) { octokit.log.warn("[@octokit/plugin-throttling] `options.throttle.minimalSecondaryRateRetryAfter` is deprecated, please use `options.throttle.fallbackSecondaryRateRetryAfter` instead"); octokitOptions.throttle.fallbackSecondaryRateRetryAfter = octokitOptions.throttle.minimalSecondaryRateRetryAfter; delete octokitOptions.throttle.minimalSecondaryRateRetryAfter; } if (octokitOptions.throttle && octokitOptions.throttle.onAbuseLimit) { octokit.log.warn("[@octokit/plugin-throttling] `onAbuseLimit()` is deprecated and will be removed in a future release of `@octokit/plugin-throttling`, please use the `onSecondaryRateLimit` handler instead"); // @ts-ignore types don't allow for both properties to be set octokitOptions.throttle.onSecondaryRateLimit = octokitOptions.throttle.onAbuseLimit; // @ts-ignore delete octokitOptions.throttle.onAbuseLimit; } const state = Object.assign({ clustering: connection != null, triggersNotification, fallbackSecondaryRateRetryAfter: 60, retryAfterBaseValue: 1000, 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); // @ts-expect-error events.on("secondary-limit", state.onSecondaryRateLimit); // @ts-expect-error events.on("rate-limit", state.onRateLimit); // @ts-expect-error events.on("error", e => 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 { pathname } = new URL(options.url, "http://github.test"); const shouldRetryGraphQL = pathname.startsWith("/graphql") && error.status !== 401; if (!(shouldRetryGraphQL || error.status === 403)) { return; } const retryCount = ~~request.retryCount; request.retryCount = retryCount; // backward compatibility options.request.retryCount = retryCount; const { wantRetry, retryAfter = 0 } = await async function () { if (/\bsecondary rate\b/i.test(error.message)) { // The user has hit the secondary rate limit. (REST and GraphQL) // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits // The Retry-After header can sometimes be blank when hitting a secondary rate limit, // but is always present after 2-3s, so make sure to set `retryAfter` to at least 5s by default. const retryAfter = Number(error.response.headers["retry-after"]) || state.fallbackSecondaryRateRetryAfter; const wantRetry = await emitter.trigger("secondary-limit", retryAfter, options, octokit, retryCount); return { wantRetry, retryAfter }; } if (error.response.headers != null && error.response.headers["x-ratelimit-remaining"] === "0") { // The user has used all their allowed calls for the current time period (REST and GraphQL) // https://docs.github.com/en/rest/reference/rate-limit (REST) // https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit (GraphQL) const rateLimitReset = new Date(~~error.response.headers["x-ratelimit-reset"] * 1000).getTime(); const retryAfter = Math.max(Math.ceil((rateLimitReset - Date.now()) / 1000), 0); const wantRetry = await emitter.trigger("rate-limit", retryAfter, options, octokit, retryCount); return { wantRetry, retryAfter }; } return {}; }(); if (wantRetry) { request.retryCount++; return retryAfter * state.retryAfterBaseValue; } }); octokit.hook.wrap("request", wrapRequest.bind(null, state)); return {}; } throttling.VERSION = VERSION; throttling.triggersNotification = triggersNotification; exports.throttling = throttling; //# sourceMappingURL=index.js.map