awscdk-construct-scte-scheduler
Version:
AWS CDK Construct for scheduling SCTE-35 events using the MediaLive schedule API
801 lines (775 loc) • 31.6 kB
JavaScript
'use strict';
var client = require('@smithy/core/client');
var protocols = require('@smithy/core/protocols');
var serde = require('@smithy/core/serde');
const isStreamingPayload = (request) => request?.body instanceof ReadableStream;
const CLOCK_SKEW_ERROR_CODES = [
"AuthFailure",
"InvalidSignatureException",
"RequestExpired",
"RequestInTheFuture",
"RequestTimeTooSkewed",
"SignatureDoesNotMatch",
];
const THROTTLING_ERROR_CODES = [
"BandwidthLimitExceeded",
"EC2ThrottledException",
"LimitExceededException",
"PriorRequestNotComplete",
"ProvisionedThroughputExceededException",
"RequestLimitExceeded",
"RequestThrottled",
"RequestThrottledException",
"SlowDown",
"ThrottledException",
"Throttling",
"ThrottlingException",
"TooManyRequestsException",
"TransactionInProgressException",
];
const TRANSIENT_ERROR_CODES = ["TimeoutError", "RequestTimeout", "RequestTimeoutException"];
const TRANSIENT_ERROR_STATUS_CODES = [500, 502, 503, 504];
const NODEJS_TIMEOUT_ERROR_CODES = ["ECONNRESET", "ECONNREFUSED", "EPIPE", "ETIMEDOUT"];
const NODEJS_NETWORK_ERROR_CODES = ["EHOSTUNREACH", "ENETUNREACH", "ENOTFOUND"];
const isRetryableByTrait = (error) => error?.$retryable !== undefined;
const isClockSkewError = (error) => CLOCK_SKEW_ERROR_CODES.includes(error.name);
const isClockSkewCorrectedError = (error) => error.$metadata?.clockSkewCorrected;
const isBrowserNetworkError = (error) => {
const errorMessages = new Set([
"Failed to fetch",
"NetworkError when attempting to fetch resource",
"The Internet connection appears to be offline",
"Load failed",
"Network request failed",
]);
const isValid = error && error instanceof TypeError;
if (!isValid) {
return false;
}
return errorMessages.has(error.message);
};
const isThrottlingError = (error) => error.$metadata?.httpStatusCode === 429 ||
THROTTLING_ERROR_CODES.includes(error.name) ||
error.$retryable?.throttling == true;
const isTransientError = (error, depth = 0) => isRetryableByTrait(error) ||
isClockSkewCorrectedError(error) ||
(error.name === "InvalidSignatureException" && error.message?.includes("Signature expired")) ||
TRANSIENT_ERROR_CODES.includes(error.name) ||
NODEJS_TIMEOUT_ERROR_CODES.includes(error?.code || "") ||
NODEJS_NETWORK_ERROR_CODES.includes(error?.code || "") ||
TRANSIENT_ERROR_STATUS_CODES.includes(error.$metadata?.httpStatusCode || 0) ||
isBrowserNetworkError(error) ||
isNodeJsHttp2TransientError(error) ||
(error.cause !== undefined && depth <= 10 && isTransientError(error.cause, depth + 1));
const isServerError = (error) => {
if (error.$metadata?.httpStatusCode !== undefined) {
const statusCode = error.$metadata.httpStatusCode;
if (500 <= statusCode && statusCode <= 599 && !isTransientError(error)) {
return true;
}
return false;
}
return false;
};
function isNodeJsHttp2TransientError(error) {
return error.code === "ERR_HTTP2_STREAM_ERROR" && error.message.includes("NGHTTP2_REFUSED_STREAM");
}
const DEFAULT_RETRY_DELAY_BASE = 100;
const MAXIMUM_RETRY_DELAY = 20 * 1000;
const THROTTLING_RETRY_DELAY_BASE = 500;
const INITIAL_RETRY_TOKENS = 500;
const RETRY_COST = 5;
const TIMEOUT_RETRY_COST = 10;
const NO_RETRY_INCREMENT = 1;
const INVOCATION_ID_HEADER = "amz-sdk-invocation-id";
const REQUEST_HEADER = "amz-sdk-request";
function parseRetryAfterHeader(response, logger) {
if (!protocols.HttpResponse.isInstance(response)) {
return;
}
for (const header of Object.keys(response.headers)) {
const h = header.toLowerCase();
if (h === "retry-after") {
const retryAfter = response.headers[header];
let retryAfterSeconds = NaN;
if (retryAfter.endsWith("GMT")) {
try {
const date = serde.parseRfc7231DateTime(retryAfter);
retryAfterSeconds = (date.getTime() - Date.now()) / 1000;
}
catch (e) {
logger?.trace?.("Failed to parse retry-after header");
logger?.trace?.(e);
}
}
else if (retryAfter.match(/ GMT, ((\d+)|(\d+\.\d+))$/)) {
retryAfterSeconds = Number(retryAfter.match(/ GMT, ([\d.]+)$/)?.[1]);
}
else if (retryAfter.match(/^((\d+)|(\d+\.\d+))$/)) {
retryAfterSeconds = Number(retryAfter);
}
else if (Date.parse(retryAfter) >= Date.now()) {
retryAfterSeconds = (Date.parse(retryAfter) - Date.now()) / 1000;
}
if (isNaN(retryAfterSeconds)) {
return;
}
return new Date(Date.now() + retryAfterSeconds * 1000);
}
else if (h === "x-amz-retry-after") {
const v = response.headers[header];
const backoffMilliseconds = Number(v);
if (isNaN(backoffMilliseconds)) {
logger?.trace?.(`Failed to parse x-amz-retry-after=${v}`);
return;
}
return new Date(Date.now() + backoffMilliseconds);
}
}
}
function getRetryAfterHint(response, logger) {
return parseRetryAfterHeader(response, logger);
}
const asSdkError = (error) => {
if (error instanceof Error)
return error;
if (error instanceof Object)
return Object.assign(new Error(), error);
if (typeof error === "string")
return new Error(error);
return new Error(`AWS SDK error wrapper for ${error}`);
};
function bindRetryMiddleware(isStreamingPayload) {
return (options) => (next, context) => async (args) => {
let retryStrategy = await options.retryStrategy();
const maxAttempts = await options.maxAttempts();
if (isRetryStrategyV2(retryStrategy)) {
retryStrategy = retryStrategy;
let retryToken = await retryStrategy.acquireInitialRetryToken((context["partition_id"] ?? "") + (context.__retryLongPoll ? ":longpoll" : ""));
let lastError = new Error();
let attempts = 0;
let totalRetryDelay = 0;
const { request } = args;
const isRequest = protocols.HttpRequest.isInstance(request);
if (isRequest) {
request.headers[INVOCATION_ID_HEADER] = serde.v4();
}
while (true) {
try {
if (isRequest) {
request.headers[REQUEST_HEADER] = `attempt=${attempts + 1}; max=${maxAttempts}`;
}
const { response, output } = await next(args);
retryStrategy.recordSuccess(retryToken);
output.$metadata.attempts = attempts + 1;
output.$metadata.totalRetryDelay = totalRetryDelay;
return { response, output };
}
catch (e) {
const retryErrorInfo = getRetryErrorInfo(e, options.logger);
lastError = asSdkError(e);
if (isRequest && isStreamingPayload(request)) {
(context.logger instanceof client.NoOpLogger ? console : context.logger)?.warn("An error was encountered in a non-retryable streaming request.");
throw lastError;
}
try {
retryToken = await retryStrategy.refreshRetryTokenForRetry(retryToken, retryErrorInfo);
}
catch (refreshError) {
if (typeof refreshError.$backoff === "number") {
await cooldown(refreshError.$backoff);
}
if (!lastError.$metadata) {
lastError.$metadata = {};
}
lastError.$metadata.attempts = attempts + 1;
lastError.$metadata.totalRetryDelay = totalRetryDelay;
throw lastError;
}
attempts = retryToken.getRetryCount();
const delay = retryToken.getRetryDelay();
totalRetryDelay += delay;
await cooldown(delay);
}
}
}
else {
retryStrategy = retryStrategy;
if (retryStrategy?.mode) {
context.userAgent = [...(context.userAgent || []), ["cfg/retry-mode", retryStrategy.mode]];
}
return retryStrategy.retry(next, args);
}
};
}
const cooldown = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const isRetryStrategyV2 = (retryStrategy) => typeof retryStrategy.acquireInitialRetryToken !== "undefined" &&
typeof retryStrategy.refreshRetryTokenForRetry !== "undefined" &&
typeof retryStrategy.recordSuccess !== "undefined";
const getRetryErrorInfo = (error, logger) => {
const errorInfo = {
error,
errorType: getRetryErrorType(error),
};
const retryAfterHint = parseRetryAfterHeader(error.$response, logger);
if (retryAfterHint) {
errorInfo.retryAfterHint = retryAfterHint;
}
return errorInfo;
};
const getRetryErrorType = (error) => {
if (isThrottlingError(error))
return "THROTTLING";
if (isTransientError(error))
return "TRANSIENT";
if (isServerError(error))
return "SERVER_ERROR";
return "CLIENT_ERROR";
};
const retryMiddlewareOptions = {
name: "retryMiddleware",
tags: ["RETRY"],
step: "finalizeRequest",
priority: "high",
override: true,
};
function bindGetRetryPlugin(isStreamingPayload) {
const retryMiddleware = bindRetryMiddleware(isStreamingPayload);
return (options) => ({
applyToStack: (clientStack) => {
clientStack.add(retryMiddleware(options), retryMiddlewareOptions);
},
});
}
class DefaultRateLimiter {
static setTimeoutFn = setTimeout;
beta;
minCapacity;
minFillRate;
scaleConstant;
smooth;
enabled = false;
availableTokens = 0;
lastMaxRate = 0;
measuredTxRate = 0;
requestCount = 0;
fillRate;
lastThrottleTime;
lastTimestamp = 0;
lastTxRateBucket;
maxCapacity;
timeWindow = 0;
constructor(options) {
this.beta = options?.beta ?? 0.7;
this.minCapacity = options?.minCapacity ?? 1;
this.minFillRate = options?.minFillRate ?? 0.5;
this.scaleConstant = options?.scaleConstant ?? 0.4;
this.smooth = options?.smooth ?? 0.8;
this.lastThrottleTime = this.getCurrentTimeInSeconds();
this.lastTxRateBucket = Math.floor(this.getCurrentTimeInSeconds());
this.fillRate = this.minFillRate;
this.maxCapacity = this.minCapacity;
}
async getSendToken() {
return this.acquireTokenBucket(1);
}
updateClientSendingRate(response) {
let calculatedRate;
this.updateMeasuredRate();
const retryErrorInfo = response;
const isThrottling = retryErrorInfo?.errorType === "THROTTLING" || isThrottlingError(retryErrorInfo?.error ?? response);
if (isThrottling) {
const rateToUse = !this.enabled ? this.measuredTxRate : Math.min(this.measuredTxRate, this.fillRate);
this.lastMaxRate = rateToUse;
this.calculateTimeWindow();
this.lastThrottleTime = this.getCurrentTimeInSeconds();
calculatedRate = this.cubicThrottle(rateToUse);
this.enableTokenBucket();
}
else {
this.calculateTimeWindow();
calculatedRate = this.cubicSuccess(this.getCurrentTimeInSeconds());
}
const newRate = Math.min(calculatedRate, 2 * this.measuredTxRate);
this.updateTokenBucketRate(newRate);
}
getCurrentTimeInSeconds() {
return Date.now() / 1000;
}
async acquireTokenBucket(amount) {
if (!this.enabled) {
return;
}
this.refillTokenBucket();
while (amount > this.availableTokens) {
const delay = ((amount - this.availableTokens) / this.fillRate) * 1000;
await new Promise((resolve) => DefaultRateLimiter.setTimeoutFn(resolve, delay));
this.refillTokenBucket();
}
this.availableTokens = this.availableTokens - amount;
}
refillTokenBucket() {
const timestamp = this.getCurrentTimeInSeconds();
if (!this.lastTimestamp) {
this.lastTimestamp = timestamp;
return;
}
const fillAmount = (timestamp - this.lastTimestamp) * this.fillRate;
this.availableTokens = Math.min(this.maxCapacity, this.availableTokens + fillAmount);
this.lastTimestamp = timestamp;
}
calculateTimeWindow() {
this.timeWindow = this.getPrecise(Math.pow((this.lastMaxRate * (1 - this.beta)) / this.scaleConstant, 1 / 3));
}
cubicThrottle(rateToUse) {
return this.getPrecise(rateToUse * this.beta);
}
cubicSuccess(timestamp) {
return this.getPrecise(this.scaleConstant * Math.pow(timestamp - this.lastThrottleTime - this.timeWindow, 3) + this.lastMaxRate);
}
enableTokenBucket() {
this.enabled = true;
}
updateTokenBucketRate(newRate) {
this.refillTokenBucket();
this.fillRate = Math.max(newRate, this.minFillRate);
this.maxCapacity = Math.max(newRate, this.minCapacity);
this.availableTokens = Math.min(this.availableTokens, this.maxCapacity);
}
updateMeasuredRate() {
const t = this.getCurrentTimeInSeconds();
const timeBucket = Math.floor(t * 2) / 2;
this.requestCount++;
if (timeBucket > this.lastTxRateBucket) {
const currentRate = this.requestCount / (timeBucket - this.lastTxRateBucket);
this.measuredTxRate = this.getPrecise(currentRate * this.smooth + this.measuredTxRate * (1 - this.smooth));
this.requestCount = 0;
this.lastTxRateBucket = timeBucket;
}
}
getPrecise(num) {
return parseFloat(num.toFixed(8));
}
}
class Retry {
static v2026 = typeof process !== "undefined" && process.env?.SMITHY_NEW_RETRIES_2026 === "true";
static delay() {
return Retry.v2026 ? 50 : 100;
}
static throttlingDelay() {
return Retry.v2026 ? 1_000 : 500;
}
static cost() {
return Retry.v2026 ? 14 : 5;
}
static throttlingCost() {
return Retry.v2026 ? 5 : 10;
}
static modifiedCostType() {
return Retry.v2026 ? "THROTTLING" : "TRANSIENT";
}
}
class DefaultRetryBackoffStrategy {
x = Retry.delay();
computeNextBackoffDelay(i) {
const b = Math.random();
const r = 2;
const t_i = b * Math.min(this.x * r ** i, MAXIMUM_RETRY_DELAY);
return Math.floor(t_i);
}
setDelayBase(delay) {
this.x = delay;
}
}
class DefaultRetryToken {
delay;
count;
cost;
longPoll;
constructor(delay, count, cost, longPoll) {
this.delay = delay;
this.count = count;
this.cost = cost;
this.longPoll = longPoll;
}
getRetryCount() {
return this.count;
}
getRetryDelay() {
return Math.min(MAXIMUM_RETRY_DELAY, this.delay);
}
getRetryCost() {
return this.cost;
}
isLongPoll() {
return this.longPoll;
}
}
exports.RETRY_MODES = void 0;
(function (RETRY_MODES) {
RETRY_MODES["STANDARD"] = "standard";
RETRY_MODES["ADAPTIVE"] = "adaptive";
})(exports.RETRY_MODES || (exports.RETRY_MODES = {}));
const DEFAULT_MAX_ATTEMPTS = 3;
const DEFAULT_RETRY_MODE = exports.RETRY_MODES.STANDARD;
const refusal = {
incompatible: 1,
attempts: 2,
capacity: 3,
};
let StandardRetryStrategy$1 = class StandardRetryStrategy {
mode = exports.RETRY_MODES.STANDARD;
capacity = INITIAL_RETRY_TOKENS;
retryBackoffStrategy;
maxAttemptsProvider;
baseDelay;
constructor(arg1) {
if (typeof arg1 === "number") {
this.maxAttemptsProvider = async () => arg1;
}
else if (typeof arg1 === "function") {
this.maxAttemptsProvider = arg1;
}
else if (arg1 && typeof arg1 === "object") {
this.maxAttemptsProvider = async () => arg1.maxAttempts;
this.baseDelay = arg1.baseDelay;
this.retryBackoffStrategy = arg1.backoff;
}
this.maxAttemptsProvider ??= async () => DEFAULT_MAX_ATTEMPTS;
this.baseDelay ??= Retry.delay();
this.retryBackoffStrategy ??= new DefaultRetryBackoffStrategy();
}
async acquireInitialRetryToken(retryTokenScope) {
return new DefaultRetryToken(Retry.delay(), 0, undefined, Retry.v2026 && retryTokenScope.includes(":longpoll"));
}
async refreshRetryTokenForRetry(token, errorInfo) {
const maxAttempts = await this.getMaxAttempts();
const retryCode = this.retryCode(token, errorInfo, maxAttempts);
const shouldRetry = retryCode === 0;
const isLongPoll = token.isLongPoll?.();
if (shouldRetry || isLongPoll) {
const errorType = errorInfo.errorType;
this.retryBackoffStrategy.setDelayBase(errorType === "THROTTLING" ? Retry.throttlingDelay() : this.baseDelay);
const delayFromErrorType = this.retryBackoffStrategy.computeNextBackoffDelay(token.getRetryCount());
let retryDelay = delayFromErrorType;
if (errorInfo.retryAfterHint instanceof Date) {
retryDelay = Math.max(delayFromErrorType, Math.min(errorInfo.retryAfterHint.getTime() - Date.now(), delayFromErrorType + 5_000));
}
if (!shouldRetry) {
throw Object.assign(new Error("No retry token available"), {
$backoff: Retry.v2026 && retryCode === refusal.capacity && isLongPoll ? retryDelay : 0,
});
}
else {
const capacityCost = this.getCapacityCost(errorType);
this.capacity -= capacityCost;
return new DefaultRetryToken(retryDelay, token.getRetryCount() + 1, capacityCost, token.isLongPoll?.() ?? false);
}
}
throw new Error("No retry token available");
}
recordSuccess(token) {
this.capacity = Math.min(INITIAL_RETRY_TOKENS, this.capacity + (token.getRetryCost() ?? NO_RETRY_INCREMENT));
}
getCapacity() {
return this.capacity;
}
async maxAttempts() {
return this.maxAttemptsProvider();
}
async getMaxAttempts() {
try {
return await this.maxAttemptsProvider();
}
catch (error) {
console.warn(`Max attempts provider could not resolve. Using default of ${DEFAULT_MAX_ATTEMPTS}`);
return DEFAULT_MAX_ATTEMPTS;
}
}
retryCode(tokenToRenew, errorInfo, maxAttempts) {
const attempts = tokenToRenew.getRetryCount() + 1;
const retryableStatus = this.isRetryableError(errorInfo.errorType) ? 0 : refusal.incompatible;
const attemptStatus = attempts < maxAttempts ? 0 : refusal.attempts;
const capacityStatus = this.capacity >= this.getCapacityCost(errorInfo.errorType) ? 0 : refusal.capacity;
return retryableStatus || attemptStatus || capacityStatus;
}
getCapacityCost(errorType) {
return errorType === Retry.modifiedCostType() ? Retry.throttlingCost() : Retry.cost();
}
isRetryableError(errorType) {
return errorType === "THROTTLING" || errorType === "TRANSIENT";
}
};
let AdaptiveRetryStrategy$1 = class AdaptiveRetryStrategy {
mode = exports.RETRY_MODES.ADAPTIVE;
rateLimiter;
standardRetryStrategy;
constructor(maxAttemptsProvider, options) {
const { rateLimiter } = options ?? {};
this.rateLimiter = rateLimiter ?? new DefaultRateLimiter();
this.standardRetryStrategy = options
? new StandardRetryStrategy$1({
maxAttempts: typeof maxAttemptsProvider === "number" ? maxAttemptsProvider : 3,
...options,
})
: new StandardRetryStrategy$1(maxAttemptsProvider);
}
async acquireInitialRetryToken(retryTokenScope) {
const token = await this.standardRetryStrategy.acquireInitialRetryToken(retryTokenScope);
await this.rateLimiter.getSendToken();
return token;
}
async refreshRetryTokenForRetry(tokenToRenew, errorInfo) {
this.rateLimiter.updateClientSendingRate(errorInfo);
const token = await this.standardRetryStrategy.refreshRetryTokenForRetry(tokenToRenew, errorInfo);
await this.rateLimiter.getSendToken();
return token;
}
recordSuccess(token) {
this.rateLimiter.updateClientSendingRate({});
this.standardRetryStrategy.recordSuccess(token);
}
async maxAttemptsProvider() {
return this.standardRetryStrategy.maxAttempts();
}
};
class ConfiguredRetryStrategy extends StandardRetryStrategy$1 {
computeNextBackoffDelay;
constructor(maxAttempts, computeNextBackoffDelay = Retry.delay()) {
super(typeof maxAttempts === "function" ? maxAttempts : async () => maxAttempts);
if (typeof computeNextBackoffDelay === "number") {
this.computeNextBackoffDelay = () => computeNextBackoffDelay;
}
else {
this.computeNextBackoffDelay = computeNextBackoffDelay;
}
}
async refreshRetryTokenForRetry(tokenToRenew, errorInfo) {
const token = await super.refreshRetryTokenForRetry(tokenToRenew, errorInfo);
token.getRetryDelay = () => this.computeNextBackoffDelay(token.getRetryCount());
return token;
}
}
const getDefaultRetryQuota = (initialRetryTokens, options) => {
const MAX_CAPACITY = initialRetryTokens;
const noRetryIncrement = NO_RETRY_INCREMENT;
const retryCost = RETRY_COST;
const timeoutRetryCost = TIMEOUT_RETRY_COST;
let availableCapacity = initialRetryTokens;
const getCapacityAmount = (error) => (error.name === "TimeoutError" ? timeoutRetryCost : retryCost);
const hasRetryTokens = (error) => getCapacityAmount(error) <= availableCapacity;
const retrieveRetryTokens = (error) => {
if (!hasRetryTokens(error)) {
throw new Error("No retry token available");
}
const capacityAmount = getCapacityAmount(error);
availableCapacity -= capacityAmount;
return capacityAmount;
};
const releaseRetryTokens = (capacityReleaseAmount) => {
availableCapacity += capacityReleaseAmount ?? noRetryIncrement;
availableCapacity = Math.min(availableCapacity, MAX_CAPACITY);
};
return Object.freeze({
hasRetryTokens,
retrieveRetryTokens,
releaseRetryTokens,
});
};
const defaultDelayDecider = (delayBase, attempts) => Math.floor(Math.min(MAXIMUM_RETRY_DELAY, Math.random() * 2 ** attempts * delayBase));
const defaultRetryDecider = (error) => {
if (!error) {
return false;
}
return isRetryableByTrait(error) || isClockSkewError(error) || isThrottlingError(error) || isTransientError(error);
};
class StandardRetryStrategy {
maxAttemptsProvider;
retryDecider;
delayDecider;
retryQuota;
mode = exports.RETRY_MODES.STANDARD;
constructor(maxAttemptsProvider, options) {
this.maxAttemptsProvider = maxAttemptsProvider;
this.retryDecider = options?.retryDecider ?? defaultRetryDecider;
this.delayDecider = options?.delayDecider ?? defaultDelayDecider;
this.retryQuota = options?.retryQuota ?? getDefaultRetryQuota(INITIAL_RETRY_TOKENS);
}
shouldRetry(error, attempts, maxAttempts) {
return attempts < maxAttempts && this.retryDecider(error) && this.retryQuota.hasRetryTokens(error);
}
async getMaxAttempts() {
let maxAttempts;
try {
maxAttempts = await this.maxAttemptsProvider();
}
catch (error) {
maxAttempts = DEFAULT_MAX_ATTEMPTS;
}
return maxAttempts;
}
async retry(next, args, options) {
let retryTokenAmount;
let attempts = 0;
let totalDelay = 0;
const maxAttempts = await this.getMaxAttempts();
const { request } = args;
if (protocols.HttpRequest.isInstance(request)) {
request.headers[INVOCATION_ID_HEADER] = serde.v4();
}
while (true) {
try {
if (protocols.HttpRequest.isInstance(request)) {
request.headers[REQUEST_HEADER] = `attempt=${attempts + 1}; max=${maxAttempts}`;
}
if (options?.beforeRequest) {
await options.beforeRequest();
}
const { response, output } = await next(args);
if (options?.afterRequest) {
options.afterRequest(response);
}
this.retryQuota.releaseRetryTokens(retryTokenAmount);
output.$metadata.attempts = attempts + 1;
output.$metadata.totalRetryDelay = totalDelay;
return { response, output };
}
catch (e) {
const err = asSdkError(e);
attempts++;
if (this.shouldRetry(err, attempts, maxAttempts)) {
retryTokenAmount = this.retryQuota.retrieveRetryTokens(err);
const delayFromDecider = this.delayDecider(isThrottlingError(err) ? THROTTLING_RETRY_DELAY_BASE : DEFAULT_RETRY_DELAY_BASE, attempts);
const delayFromResponse = getDelayFromRetryAfterHeader(err.$response);
const delay = Math.max(delayFromResponse || 0, delayFromDecider);
totalDelay += delay;
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
if (!err.$metadata) {
err.$metadata = {};
}
err.$metadata.attempts = attempts;
err.$metadata.totalRetryDelay = totalDelay;
throw err;
}
}
}
}
const getDelayFromRetryAfterHeader = (response) => {
if (!protocols.HttpResponse.isInstance(response))
return;
const retryAfterHeaderName = Object.keys(response.headers).find((key) => key.toLowerCase() === "retry-after");
if (!retryAfterHeaderName)
return;
const retryAfter = response.headers[retryAfterHeaderName];
const retryAfterSeconds = Number(retryAfter);
if (!Number.isNaN(retryAfterSeconds))
return retryAfterSeconds * 1000;
const retryAfterDate = new Date(retryAfter);
return retryAfterDate.getTime() - Date.now();
};
class AdaptiveRetryStrategy extends StandardRetryStrategy {
rateLimiter;
constructor(maxAttemptsProvider, options) {
const { rateLimiter, ...superOptions } = options ?? {};
super(maxAttemptsProvider, superOptions);
this.rateLimiter = rateLimiter ?? new DefaultRateLimiter();
this.mode = exports.RETRY_MODES.ADAPTIVE;
}
async retry(next, args) {
return super.retry(next, args, {
beforeRequest: async () => {
return this.rateLimiter.getSendToken();
},
afterRequest: (response) => {
this.rateLimiter.updateClientSendingRate(response);
},
});
}
}
const resolveRetryConfig = (input) => {
const { retryStrategy, retryMode } = input;
const maxAttempts = client.normalizeProvider(input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
let controller = retryStrategy
? Promise.resolve(retryStrategy)
: undefined;
const getDefault = async () => (await client.normalizeProvider(retryMode)()) === exports.RETRY_MODES.ADAPTIVE
? new AdaptiveRetryStrategy$1(maxAttempts)
: new StandardRetryStrategy$1(maxAttempts);
return Object.assign(input, {
maxAttempts,
retryStrategy: () => (controller ??= getDefault()),
});
};
const omitRetryHeadersMiddleware = () => (next) => async (args) => {
const { request } = args;
if (protocols.HttpRequest.isInstance(request)) {
delete request.headers[INVOCATION_ID_HEADER];
delete request.headers[REQUEST_HEADER];
}
return next(args);
};
const omitRetryHeadersMiddlewareOptions = {
name: "omitRetryHeadersMiddleware",
tags: ["RETRY", "HEADERS", "OMIT_RETRY_HEADERS"],
relation: "before",
toMiddleware: "awsAuthMiddleware",
override: true,
};
const getOmitRetryHeadersPlugin = (options) => ({
applyToStack: (clientStack) => {
clientStack.addRelativeTo(omitRetryHeadersMiddleware(), omitRetryHeadersMiddlewareOptions);
},
});
const no = Symbol.for("node-only");
const ENV_MAX_ATTEMPTS = no;
const CONFIG_MAX_ATTEMPTS = no;
const NODE_MAX_ATTEMPT_CONFIG_OPTIONS = no;
const ENV_RETRY_MODE = no;
const CONFIG_RETRY_MODE = no;
const NODE_RETRY_MODE_CONFIG_OPTIONS = no;
const retryMiddleware = bindRetryMiddleware(isStreamingPayload);
const getRetryPlugin = bindGetRetryPlugin(isStreamingPayload);
exports.AdaptiveRetryStrategy = AdaptiveRetryStrategy$1;
exports.CONFIG_MAX_ATTEMPTS = CONFIG_MAX_ATTEMPTS;
exports.CONFIG_RETRY_MODE = CONFIG_RETRY_MODE;
exports.ConfiguredRetryStrategy = ConfiguredRetryStrategy;
exports.DEFAULT_MAX_ATTEMPTS = DEFAULT_MAX_ATTEMPTS;
exports.DEFAULT_RETRY_DELAY_BASE = DEFAULT_RETRY_DELAY_BASE;
exports.DEFAULT_RETRY_MODE = DEFAULT_RETRY_MODE;
exports.DefaultRateLimiter = DefaultRateLimiter;
exports.DeprecatedAdaptiveRetryStrategy = AdaptiveRetryStrategy;
exports.DeprecatedStandardRetryStrategy = StandardRetryStrategy;
exports.ENV_MAX_ATTEMPTS = ENV_MAX_ATTEMPTS;
exports.ENV_RETRY_MODE = ENV_RETRY_MODE;
exports.INITIAL_RETRY_TOKENS = INITIAL_RETRY_TOKENS;
exports.INVOCATION_ID_HEADER = INVOCATION_ID_HEADER;
exports.MAXIMUM_RETRY_DELAY = MAXIMUM_RETRY_DELAY;
exports.NODE_MAX_ATTEMPT_CONFIG_OPTIONS = NODE_MAX_ATTEMPT_CONFIG_OPTIONS;
exports.NODE_RETRY_MODE_CONFIG_OPTIONS = NODE_RETRY_MODE_CONFIG_OPTIONS;
exports.NO_RETRY_INCREMENT = NO_RETRY_INCREMENT;
exports.REQUEST_HEADER = REQUEST_HEADER;
exports.RETRY_COST = RETRY_COST;
exports.Retry = Retry;
exports.StandardRetryStrategy = StandardRetryStrategy$1;
exports.THROTTLING_RETRY_DELAY_BASE = THROTTLING_RETRY_DELAY_BASE;
exports.TIMEOUT_RETRY_COST = TIMEOUT_RETRY_COST;
exports.defaultDelayDecider = defaultDelayDecider;
exports.defaultRetryDecider = defaultRetryDecider;
exports.getOmitRetryHeadersPlugin = getOmitRetryHeadersPlugin;
exports.getRetryAfterHint = getRetryAfterHint;
exports.getRetryPlugin = getRetryPlugin;
exports.isBrowserNetworkError = isBrowserNetworkError;
exports.isClockSkewCorrectedError = isClockSkewCorrectedError;
exports.isClockSkewError = isClockSkewError;
exports.isNodeJsHttp2TransientError = isNodeJsHttp2TransientError;
exports.isRetryableByTrait = isRetryableByTrait;
exports.isServerError = isServerError;
exports.isThrottlingError = isThrottlingError;
exports.isTransientError = isTransientError;
exports.omitRetryHeadersMiddleware = omitRetryHeadersMiddleware;
exports.omitRetryHeadersMiddlewareOptions = omitRetryHeadersMiddlewareOptions;
exports.resolveRetryConfig = resolveRetryConfig;
exports.retryMiddleware = retryMiddleware;
exports.retryMiddlewareOptions = retryMiddlewareOptions;