'use strict'; const sleep = (seconds) => { return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); }; const waiterServiceDefaults = { minDelay: 2, maxDelay: 120, }; exports.WaiterState = void 0; (function (WaiterState) { WaiterState["ABORTED"] = "ABORTED"; WaiterState["FAILURE"] = "FAILURE"; WaiterState["SUCCESS"] = "SUCCESS"; WaiterState["RETRY"] = "RETRY"; WaiterState["TIMEOUT"] = "TIMEOUT"; })(exports.WaiterState || (exports.WaiterState = {})); const checkExceptions = (result) => { if (result.state === exports.WaiterState.ABORTED) { const abortError = new Error(`${JSON.stringify({ ...result, reason: "Request was aborted", })}`); abortError.name = "AbortError"; throw abortError; } else if (result.state === exports.WaiterState.TIMEOUT) { const timeoutError = new Error(`${JSON.stringify({ ...result, reason: "Waiter has timed out", })}`); timeoutError.name = "TimeoutError"; throw timeoutError; } else if (result.state !== exports.WaiterState.SUCCESS) { throw new Error(`${JSON.stringify(result)}`); } return result; }; const exponentialBackoffWithJitter = (minDelay, maxDelay, attemptCeiling, attempt) => { if (attempt > attemptCeiling) return maxDelay; const delay = minDelay * 2 ** (attempt - 1); return randomInRange(minDelay, delay); }; const randomInRange = (min, max) => min + Math.random() * (max - min); const runPolling = async ({ minDelay, maxDelay, maxWaitTime, abortController, client, abortSignal }, input, acceptorChecks) => { const observedResponses = {}; const { state, reason } = await acceptorChecks(client, input); if (reason) { const message = createMessageFromResponse(reason); observedResponses[message] |= 0; observedResponses[message] += 1; } if (state !== exports.WaiterState.RETRY) { return { state, reason, observedResponses }; } let currentAttempt = 1; const waitUntil = Date.now() + maxWaitTime * 1000; const attemptCeiling = Math.log(maxDelay / minDelay) / Math.log(2) + 1; while (true) { if (abortController?.signal?.aborted || abortSignal?.aborted) { const message = "AbortController signal aborted."; observedResponses[message] |= 0; observedResponses[message] += 1; return { state: exports.WaiterState.ABORTED, observedResponses }; } const delay = exponentialBackoffWithJitter(minDelay, maxDelay, attemptCeiling, currentAttempt); if (Date.now() + delay * 1000 > waitUntil) { return { state: exports.WaiterState.TIMEOUT, observedResponses }; } await sleep(delay); const { state, reason } = await acceptorChecks(client, input); if (reason) { const message = createMessageFromResponse(reason); observedResponses[message] |= 0; observedResponses[message] += 1; } if (state !== exports.WaiterState.RETRY) { return { state, reason, observedResponses }; } currentAttempt += 1; } }; const createMessageFromResponse = (reason) => { if (reason?.$responseBodyText) { return `Deserialization error for body: ${reason.$responseBodyText}`; } if (reason?.$metadata?.httpStatusCode) { if (reason.$response || reason.message) { return `${reason.$response.statusCode ?? reason.$metadata.httpStatusCode ?? "Unknown"}: ${reason.message}`; } return `${reason.$metadata.httpStatusCode}: OK`; } return String(reason?.message ?? JSON.stringify(reason) ?? "Unknown"); }; const validateWaiterOptions = (options) => { if (options.maxWaitTime <= 0) { throw new Error(`WaiterConfiguration.maxWaitTime must be greater than 0`); } else if (options.minDelay <= 0) { throw new Error(`WaiterConfiguration.minDelay must be greater than 0`); } else if (options.maxDelay <= 0) { throw new Error(`WaiterConfiguration.maxDelay must be greater than 0`); } else if (options.maxWaitTime <= options.minDelay) { throw new Error(`WaiterConfiguration.maxWaitTime [${options.maxWaitTime}] must be greater than WaiterConfiguration.minDelay [${options.minDelay}] for this waiter`); } else if (options.maxDelay < options.minDelay) { throw new Error(`WaiterConfiguration.maxDelay [${options.maxDelay}] must be greater than WaiterConfiguration.minDelay [${options.minDelay}] for this waiter`); } }; const abortTimeout = (abortSignal) => { let onAbort; const promise = new Promise((resolve) => { onAbort = () => resolve({ state: exports.WaiterState.ABORTED }); if (typeof abortSignal.addEventListener === "function") { abortSignal.addEventListener("abort", onAbort); } else { abortSignal.onabort = onAbort; } }); return { clearListener() { if (typeof abortSignal.removeEventListener === "function") { abortSignal.removeEventListener("abort", onAbort); } }, aborted: promise, }; }; const createWaiter = async (options, input, acceptorChecks) => { const params = { ...waiterServiceDefaults, ...options, }; validateWaiterOptions(params); const exitConditions = [runPolling(params, input, acceptorChecks)]; const finalize = []; if (options.abortSignal) { const { aborted, clearListener } = abortTimeout(options.abortSignal); finalize.push(clearListener); exitConditions.push(aborted); } if (options.abortController?.signal) { const { aborted, clearListener } = abortTimeout(options.abortController.signal); finalize.push(clearListener); exitConditions.push(aborted); } return Promise.race(exitConditions).then((result) => { for (const fn of finalize) { fn(); } return result; }); }; exports.checkExceptions = checkExceptions; exports.createWaiter = createWaiter; exports.waiterServiceDefaults = waiterServiceDefaults;