bns-v2-sdk
Version:
The official BNS V2 SDK for interacting with Stacks Blockchain
98 lines (83 loc) • 2.54 kB
text/typescript
import { debug } from "./debug";
export interface RetryConfig {
maxRetries: number;
initialDelayMs: number;
maxDelayMs: number;
backoffMultiplier: number;
retryableStatusCodes: number[];
}
const DEFAULT_RETRY_CONFIG: RetryConfig = {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 10000,
backoffMultiplier: 2,
retryableStatusCodes: [429, 500, 502, 503, 504],
};
let globalRetryConfig: RetryConfig = { ...DEFAULT_RETRY_CONFIG };
/**
* Configure global retry behavior for all SDK API calls.
* Retries are enabled by default (3 retries with exponential backoff).
* Call `configureRetry({ maxRetries: 0 })` to disable.
*/
export function configureRetry(config: Partial<RetryConfig>): void {
globalRetryConfig = { ...globalRetryConfig, ...config };
debug.log("Retry configuration updated:", globalRetryConfig);
}
export function getRetryConfig(): RetryConfig {
return { ...globalRetryConfig };
}
function isRetryableError(error: unknown, config: RetryConfig): boolean {
if (error && typeof error === "object" && "response" in error) {
const status = (error as { response?: { status?: number } }).response
?.status;
if (status && config.retryableStatusCodes.includes(status)) {
return true;
}
}
if (error && typeof error === "object" && "code" in error) {
const code = (error as { code?: string }).code;
if (
code === "ECONNRESET" ||
code === "ETIMEDOUT" ||
code === "ECONNABORTED"
) {
return true;
}
}
return false;
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Wrap an async function with exponential backoff retry logic.
* Uses the global retry config by default, or accepts an override.
*/
export async function withRetry<T>(
fn: () => Promise<T>,
config?: Partial<RetryConfig>
): Promise<T> {
const cfg = config
? { ...globalRetryConfig, ...config }
: globalRetryConfig;
let lastError: unknown;
for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (attempt >= cfg.maxRetries || !isRetryableError(error, cfg)) {
throw error;
}
const delay = Math.min(
cfg.initialDelayMs * Math.pow(cfg.backoffMultiplier, attempt),
cfg.maxDelayMs
);
debug.log(
`Retry attempt ${attempt + 1}/${cfg.maxRetries} after ${delay}ms`
);
await sleep(delay);
}
}
throw lastError;
}