UNPKG

@noves/noves-sdk

Version:
1,310 lines (1,305 loc) 214 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // node_modules/tsup/assets/cjs_shims.js var init_cjs_shims = __esm({ "node_modules/tsup/assets/cjs_shims.js"() { "use strict"; } }); // src/errors/ErrorTypes.ts var ErrorType, ERROR_MESSAGES, ERROR_STATUS_CODES; var init_ErrorTypes = __esm({ "src/errors/ErrorTypes.ts"() { "use strict"; init_cjs_shims(); ErrorType = /* @__PURE__ */ ((ErrorType2) => { ErrorType2["UNAUTHORIZED"] = "UNAUTHORIZED"; ErrorType2["INVALID_API_KEY"] = "INVALID_API_KEY"; ErrorType2["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED"; ErrorType2["JOB_NOT_FOUND"] = "JOB_NOT_FOUND"; ErrorType2["JOB_PROCESSING"] = "JOB_PROCESSING"; ErrorType2["JOB_NOT_READY"] = "JOB_NOT_READY"; ErrorType2["INVALID_REQUEST"] = "INVALID_REQUEST"; ErrorType2["INVALID_RESPONSE_FORMAT"] = "INVALID_RESPONSE_FORMAT"; ErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR"; ErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR"; ErrorType2["VALIDATION_ERROR"] = "VALIDATION_ERROR"; return ErrorType2; })(ErrorType || {}); ERROR_MESSAGES = { ["UNAUTHORIZED" /* UNAUTHORIZED */]: "Unauthorized", ["INVALID_API_KEY" /* INVALID_API_KEY */]: "Invalid API Key", ["RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */]: "Rate limit exceeded", ["JOB_NOT_FOUND" /* JOB_NOT_FOUND */]: "Job does not exist", ["JOB_PROCESSING" /* JOB_PROCESSING */]: "Job is still processing. Please try again in a few moments.", ["JOB_NOT_READY" /* JOB_NOT_READY */]: "Job not ready yet", ["INVALID_REQUEST" /* INVALID_REQUEST */]: "Invalid request", ["INVALID_RESPONSE_FORMAT" /* INVALID_RESPONSE_FORMAT */]: "Invalid response format from API", ["NETWORK_ERROR" /* NETWORK_ERROR */]: "Network error occurred", ["UNKNOWN_ERROR" /* UNKNOWN_ERROR */]: "An unknown error occurred", ["VALIDATION_ERROR" /* VALIDATION_ERROR */]: "Validation error occurred" }; ERROR_STATUS_CODES = { ["UNAUTHORIZED" /* UNAUTHORIZED */]: 401, ["INVALID_API_KEY" /* INVALID_API_KEY */]: 401, ["RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */]: 429, ["JOB_NOT_FOUND" /* JOB_NOT_FOUND */]: 404, ["JOB_PROCESSING" /* JOB_PROCESSING */]: 425, ["JOB_NOT_READY" /* JOB_NOT_READY */]: 425, ["INVALID_REQUEST" /* INVALID_REQUEST */]: 400, ["INVALID_RESPONSE_FORMAT" /* INVALID_RESPONSE_FORMAT */]: 500, ["NETWORK_ERROR" /* NETWORK_ERROR */]: 500, ["UNKNOWN_ERROR" /* UNKNOWN_ERROR */]: 500, ["VALIDATION_ERROR" /* VALIDATION_ERROR */]: 400 }; } }); // src/errors/TransactionError.ts var TransactionError; var init_TransactionError = __esm({ "src/errors/TransactionError.ts"() { "use strict"; init_cjs_shims(); init_ErrorTypes(); TransactionError = class extends Error { errors; errorType; httpStatusCode; details; retryMetadata; /** * Creates an instance of TransactionError. * @param {Record<string, string[]>} errors - The validation errors. * @param {ErrorType} [errorType=ErrorType.UNKNOWN_ERROR] - The type of error. * @param {number} [httpStatusCode] - The HTTP status code associated with the error. * @param {any} [details] - Additional error details. */ constructor(errors, errorType = "UNKNOWN_ERROR" /* UNKNOWN_ERROR */, httpStatusCode, details) { const customMessage = errorType === "VALIDATION_ERROR" /* VALIDATION_ERROR */ && errors.message && errors.message.length > 0 ? errors.message[0] : void 0; super(customMessage || ERROR_MESSAGES[errorType] || "Transaction validation error"); this.name = "TransactionError"; this.errors = errors; this.errorType = errorType; this.httpStatusCode = httpStatusCode; this.details = details; } /** * Check if this error is of a specific type. * @param {ErrorType} type - The error type to check. * @returns {boolean} True if the error matches the specified type. */ isErrorType(type) { return this.errorType === type; } /** * Check if this error indicates a job not found. * @returns {boolean} True if the error indicates a job not found. */ isJobNotFound() { return this.errorType === "JOB_NOT_FOUND" /* JOB_NOT_FOUND */; } /** * Check if this error indicates a job is still processing. * @returns {boolean} True if the error indicates a job is processing. */ isJobProcessing() { return this.errorType === "JOB_PROCESSING" /* JOB_PROCESSING */ || this.errorType === "JOB_NOT_READY" /* JOB_NOT_READY */; } /** * Check if this error indicates rate limiting. * @returns {boolean} True if the error indicates rate limiting. */ isRateLimited() { return this.errorType === "RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */; } /** * Check if this error indicates authorization issues. * @returns {boolean} True if the error indicates authorization issues. */ isUnauthorized() { return this.errorType === "UNAUTHORIZED" /* UNAUTHORIZED */ || this.errorType === "INVALID_API_KEY" /* INVALID_API_KEY */; } /** * Check if this error has retry metadata attached. * @returns {boolean} True if retry metadata is available. */ hasRetryMetadata() { return this.retryMetadata !== void 0; } /** * Get the number of retry attempts made before this final failure. * @returns {number} Number of attempts, or 0 if no retry metadata. */ getRetryAttempts() { return this.retryMetadata?.attempts || 0; } /** * Get the total duration of all retry attempts. * @returns {number} Total duration in milliseconds, or 0 if no retry metadata. */ getRetryDuration() { return this.retryMetadata?.totalDuration || 0; } /** * Get the recent performance assessment at time of failure. * @returns {string} Performance level or 'unknown' if no retry metadata. */ getPerformanceLevel() { return this.retryMetadata?.recentPerformance || "unknown"; } /** * Get a summary of the error pattern that led to this failure. * @returns {string[]} Array of error types in chronological order. */ getErrorPattern() { return this.retryMetadata?.errorHistory.map((e) => e.errorType) || []; } /** * Check if this error resulted from exhausted retries. * @returns {boolean} True if this error is the result of failed retry attempts. */ isRetryExhausted() { return this.hasRetryMetadata() && this.getRetryAttempts() > 1; } /** * Get a human-readable summary of retry information. * @returns {string} Formatted summary of retry attempts and performance. */ getRetrySummary() { if (!this.hasRetryMetadata()) { return "No retry attempts made"; } const attempts = this.getRetryAttempts(); const duration = this.getRetryDuration(); const performance = this.getPerformanceLevel(); const pattern = this.getErrorPattern(); return `${attempts} retry attempts over ${duration}ms (performance: ${performance}, pattern: ${pattern.join(" \u2192 ")})`; } }; } }); // src/utils/apiUtils.ts function mergeRetryConfig(baseConfig, userConfig) { if (arguments.length === 1 && baseConfig) { userConfig = baseConfig; baseConfig = DEFAULT_RETRY_CONFIG; } const base = baseConfig || DEFAULT_RETRY_CONFIG; if (!userConfig) { return { ...base }; } return { ...base, ...userConfig, // Ensure arrays are properly merged, not replaced retryableErrors: userConfig.retryableErrors || base.retryableErrors, retryableStatusCodes: userConfig.retryableStatusCodes || base.retryableStatusCodes }; } function extractRetryAfterFromError(error) { if (!error) return void 0; if (error instanceof TransactionError && error.details?.retryAfter) { return error.details.retryAfter; } if (error.response?.headers?.["retry-after"]) { return parseInt(error.response.headers["retry-after"], 10); } return void 0; } function combineAbortSignals(...signals) { if (signals.length === 1) return signals[0]; const controller = new AbortController(); for (const signal of signals) { if (signal.aborted) { controller.abort(signal.reason); return controller.signal; } signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true }); } return controller.signal; } function buildRequestSignal(fetchConfig, perRequestSignal) { const signals = []; let timeoutId; let timeoutController; if (fetchConfig?.timeout) { timeoutController = new AbortController(); timeoutId = setTimeout(() => timeoutController.abort(), fetchConfig.timeout); signals.push(timeoutController.signal); } if (fetchConfig?.signal) { signals.push(fetchConfig.signal); } if (perRequestSignal) { signals.push(perRequestSignal); } const cleanup = () => { if (timeoutId) clearTimeout(timeoutId); }; if (signals.length === 0) { return { signal: void 0, cleanup }; } if (signals.length === 1) { return { signal: signals[0], cleanup }; } return { signal: combineAbortSignals(...signals), cleanup }; } async function processResponse(response) { if (response.status === 429) { const retryAfter = response.headers.get("retry-after"); const error = new Error("Rate limit exceeded"); error.response = { status: 429, headers: { "retry-after": retryAfter } }; throw error; } if (response.status === 401) { return { succeeded: false, response: { message: "Unauthorized" }, httpStatusCode: 401, errorType: "UNAUTHORIZED" }; } if (response.status === 425) { const responseData2 = await response.json(); return { succeeded: false, response: { message: "Job not ready yet", detail: responseData2.detail }, httpStatusCode: 425, errorType: "JOB_NOT_READY" }; } let responseData; try { responseData = await response.json(); } catch (error) { return { succeeded: false, response: { message: "Invalid response format" }, httpStatusCode: response.status, errorType: "INVALID_RESPONSE_FORMAT" }; } if (!response.ok) { if ([425, 429, 500, 502, 503, 504].includes(response.status)) { const error = new Error(`HTTP ${response.status}: ${response.statusText}`); error.response = { status: response.status, data: responseData }; throw error; } return { succeeded: false, response: responseData, httpStatusCode: response.status }; } return { succeeded: true, response: responseData, httpStatusCode: response.status }; } function createTranslateClient(ecosystem, apiKey, retryConfig, fetchConfig) { const config = mergeRetryConfig(retryConfig); return async function request(endpoint, method = "GET", options = {}) { const operationId = `translate-${ecosystem}-${endpoint}`; try { return await retryEngine.executeWithRetry( async () => { if (fetchConfig?.signal?.aborted) { throw new DOMException("The operation was aborted.", "AbortError"); } const url = `${TRANSLATE_URL}/${ecosystem}/${endpoint}`; const { signal, cleanup } = buildRequestSignal(fetchConfig, options.signal); try { const response = await fetch(url, { ...options, method, signal, headers: { ...fetchConfig?.headers, ...options.headers, "apiKey": apiKey, "Content-Type": "application/json" } }); return processResponse(response); } catch (error) { if (error.name === "AbortError") { if (fetchConfig?.signal?.aborted) { throw error; } if (fetchConfig?.timeout) { const timeoutError = new Error(`Request timed out after ${fetchConfig.timeout}ms`); timeoutError.code = "NETWORK_ERROR"; throw timeoutError; } throw error; } throw error; } finally { cleanup(); } }, config, operationId ); } catch (error) { if (error.name === "AbortError") { throw error; } return { succeeded: false, response: { message: error.message || "Request failed after retries" }, httpStatusCode: error.response?.status || 500 }; } }; } function createForesightClient(apiKey, retryConfig, fetchConfig) { const config = mergeRetryConfig(retryConfig); return async function request(endpoint, method = "GET", options = {}) { const operationId = `foresight-${endpoint}`; try { return await retryEngine.executeWithRetry( async () => { if (fetchConfig?.signal?.aborted) { throw new DOMException("The operation was aborted.", "AbortError"); } const { signal, cleanup } = buildRequestSignal(fetchConfig, options.signal); try { const response = await fetch(`${FORESIGHT_URL}/${endpoint}`, { ...options, method, signal, headers: { ...fetchConfig?.headers, ...options.headers, "apiKey": apiKey, "Content-Type": "application/json" } }); return processResponse(response); } catch (error) { if (error.name === "AbortError") { if (fetchConfig?.signal?.aborted) { throw error; } if (fetchConfig?.timeout) { const timeoutError = new Error(`Request timed out after ${fetchConfig.timeout}ms`); timeoutError.code = "NETWORK_ERROR"; throw timeoutError; } throw error; } throw error; } finally { cleanup(); } }, config, operationId ); } catch (error) { if (error.name === "AbortError") { throw error; } return { succeeded: false, response: { message: error.message || "Request failed after retries" }, httpStatusCode: error.response?.status || 500 }; } }; } function createPricingClient(ecosystem, apiKey, retryConfig, fetchConfig) { const config = mergeRetryConfig(retryConfig); return async function request(endpoint, method = "GET", options = {}) { const operationId = `pricing-${ecosystem}-${endpoint}`; try { return await retryEngine.executeWithRetry( async () => { if (fetchConfig?.signal?.aborted) { throw new DOMException("The operation was aborted.", "AbortError"); } const { signal, cleanup } = buildRequestSignal(fetchConfig, options.signal); try { const response = await fetch(`${PRICING_URL}/${ecosystem}/${endpoint}`, { ...options, method, signal, headers: { ...fetchConfig?.headers, ...options.headers, "apiKey": apiKey, "Content-Type": "application/json" } }); return processResponse(response); } catch (error) { if (error.name === "AbortError") { if (fetchConfig?.signal?.aborted) { throw error; } if (fetchConfig?.timeout) { const timeoutError = new Error(`Request timed out after ${fetchConfig.timeout}ms`); timeoutError.code = "NETWORK_ERROR"; throw timeoutError; } throw error; } throw error; } finally { cleanup(); } }, config, operationId ); } catch (error) { if (error.name === "AbortError") { throw error; } return { succeeded: false, response: { message: error.message || "Request failed after retries" }, httpStatusCode: error.response?.status || 500 }; } }; } var TRANSLATE_URL, FORESIGHT_URL, PRICING_URL, DEFAULT_RETRY_CONFIG, RETRY_PRESETS, IntelligentRetryEngine, ERROR_SPECIFIC_STRATEGIES, RetryBudgetManager, PredictiveAnalyzer, IntelligentCircuitBreaker, retryBudgetManager, predictiveAnalyzer, intelligentCircuitBreaker, retryEngine; var init_apiUtils = __esm({ "src/utils/apiUtils.ts"() { "use strict"; init_cjs_shims(); init_ErrorTypes(); init_TransactionError(); TRANSLATE_URL = "https://translate.noves.fi"; FORESIGHT_URL = "https://foresight.noves.fi"; PRICING_URL = "https://pricing.noves.fi"; DEFAULT_RETRY_CONFIG = { enabled: false, maxAttempts: 3, baseDelay: 1e3, maxDelay: 3e4, exponentialBase: 2, jitter: true, jitterFactor: 0.1, retryableErrors: [ "RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */, "NETWORK_ERROR" /* NETWORK_ERROR */, "JOB_NOT_READY" /* JOB_NOT_READY */, "UNKNOWN_ERROR" /* UNKNOWN_ERROR */ ], retryableStatusCodes: [429, 500, 502, 503, 504], respectRetryAfter: true, circuitBreakerThreshold: 5, circuitBreakerResetTime: 6e4 }; RETRY_PRESETS = { /** * Completely disables retry mechanism (default for backward compatibility) */ disabled: { ...DEFAULT_RETRY_CONFIG, enabled: false }, /** * Conservative retry strategy with minimal retries and longer delays. * Suitable for rate-limited environments or when being respectful of server resources. */ conservative: { ...DEFAULT_RETRY_CONFIG, enabled: true, maxAttempts: 2, baseDelay: 2e3, maxDelay: 15e3, circuitBreakerThreshold: 3 }, /** * Standard retry strategy with balanced settings. * Good default for most production applications. */ standard: { ...DEFAULT_RETRY_CONFIG, enabled: true }, /** * Aggressive retry strategy with more attempts and shorter delays. * Use when high availability is critical and server resources allow. */ aggressive: { ...DEFAULT_RETRY_CONFIG, enabled: true, maxAttempts: 5, baseDelay: 500, maxDelay: 2e4, circuitBreakerThreshold: 8 }, /** * Production-optimized retry strategy with circuit breaker tuned for high-traffic scenarios. * Balances resilience with resource protection. */ production: { ...DEFAULT_RETRY_CONFIG, enabled: true, maxAttempts: 3, baseDelay: 1e3, maxDelay: 1e4, circuitBreakerThreshold: 3, circuitBreakerResetTime: 3e4 }, /** * Fast-fail strategy with minimal retries for time-sensitive operations. * Only retries obvious transient errors like rate limits. */ fastFail: { ...DEFAULT_RETRY_CONFIG, enabled: true, maxAttempts: 2, baseDelay: 500, maxDelay: 2e3, retryableErrors: ["RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */, "NETWORK_ERROR" /* NETWORK_ERROR */], circuitBreakerThreshold: 2, circuitBreakerResetTime: 1e4 } }; IntelligentRetryEngine = class { adaptiveContexts = /* @__PURE__ */ new Map(); globalPerformanceMetrics = { avgResponseTime: 1e3, successRate: 0.95, errorRateByType: /* @__PURE__ */ new Map() }; /** * Execute a request function with intelligent retry logic applied. * @param requestFn Function that makes the actual request * @param config Retry configuration to use * @param operationId Unique identifier for this operation type * @returns Promise that resolves with the request result */ async executeWithRetry(requestFn, config, operationId) { if (!config.enabled) { return requestFn(); } if (!retryBudgetManager.canRetry(operationId)) { throw new Error(`Retry budget exhausted for operation: ${operationId}`); } const context = this.getOrCreateAdaptiveContext(operationId); const strategy = this.determineRetryStrategy(context, config); if (this.isCircuitOpen(context, strategy, config)) { throw new Error(`Circuit breaker is open for operation: ${operationId}`); } if (predictiveAnalyzer.shouldPreemptivelyFail(operationId, context)) { throw new Error(`Predictive analysis suggests operation will fail: ${operationId}`); } let lastError; const startTime = Date.now(); for (let attempt = 1; attempt <= strategy.maxAttempts; attempt++) { try { retryBudgetManager.consumeRetry(operationId); const result = await requestFn(); const responseTime = Date.now() - startTime; this.recordSuccess(context, responseTime); this.updatePerformanceMetrics(operationId, true, responseTime); predictiveAnalyzer.recordMetrics(operationId, true, responseTime); if (config.onSuccess) { config.onSuccess(attempt); } return result; } catch (error) { lastError = error; const errorType = this.extractErrorType(error); this.recordError(context, errorType); predictiveAnalyzer.recordMetrics(operationId, false, Date.now() - startTime, errorType); if (attempt === strategy.maxAttempts || !strategy.shouldRetry(attempt, error, context)) { this.recordFailure(context); this.updatePerformanceMetrics(operationId, false, Date.now() - startTime); if (config.onFailure) { config.onFailure(attempt, error); } throw this.enhanceErrorWithRetryInfo(error, attempt, context); } const delay = strategy.calculateDelay(attempt, strategy.baseDelay, context); if (config.onRetry) { config.onRetry(attempt, error, delay); } await this.delay(delay); } } throw lastError; } /** * Determine the optimal retry strategy based on error type and context. */ determineRetryStrategy(context, config) { if (context.errorHistory.length > 0) { const lastErrorType = context.errorHistory[context.errorHistory.length - 1].errorType; if (ERROR_SPECIFIC_STRATEGIES[lastErrorType]) { return this.adaptStrategy(ERROR_SPECIFIC_STRATEGIES[lastErrorType], context, config); } } return this.createGeneralStrategy(context, config); } /** * Adapt strategy based on recent performance and context. */ adaptStrategy(baseStrategy, context, config) { const adapted = { ...baseStrategy }; if (context.recentPerformance === "poor") { adapted.maxAttempts = Math.max(1, Math.floor(adapted.maxAttempts * 0.7)); adapted.baseDelay = Math.floor(adapted.baseDelay * 1.5); } else if (context.recentPerformance === "good" && context.successfulRecoveries > 3) { adapted.maxAttempts = Math.min(10, Math.floor(adapted.maxAttempts * 1.2)); adapted.baseDelay = Math.floor(adapted.baseDelay * 0.8); } if (config.maxAttempts !== DEFAULT_RETRY_CONFIG.maxAttempts) { adapted.maxAttempts = config.maxAttempts; } if (config.baseDelay !== DEFAULT_RETRY_CONFIG.baseDelay) { adapted.baseDelay = config.baseDelay; } return adapted; } /** * Create a general strategy when no error-specific strategy is available. */ createGeneralStrategy(context, config) { return { errorType: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */, maxAttempts: config.maxAttempts, baseDelay: config.baseDelay, maxDelay: config.maxDelay, backoffType: "exponential", jitterFactor: config.jitterFactor, shouldRetry: (attempt) => attempt <= config.maxAttempts, calculateDelay: (attempt, baseDelay) => { return Math.min(baseDelay * Math.pow(config.exponentialBase, attempt - 1), config.maxDelay); } }; } /** * Extract error type from various error formats. */ extractErrorType(error) { if (error instanceof TransactionError) { return error.errorType; } if (error.response?.status === 429) { return "RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */; } if (error.response?.status === 401) { return "UNAUTHORIZED" /* UNAUTHORIZED */; } if (error.response?.status === 425) { return "JOB_NOT_READY" /* JOB_NOT_READY */; } if (error instanceof TypeError || error.code === "NETWORK_ERROR") { return "NETWORK_ERROR" /* NETWORK_ERROR */; } return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */; } /** * Record error occurrence and update context. */ recordError(context, errorType) { context.errorHistory.push({ errorType, timestamp: Date.now() }); if (context.errorHistory.length > 50) { context.errorHistory = context.errorHistory.slice(-50); } context.lastError = errorType; this.updatePerformanceAssessment(context); } /** * Record successful operation and recovery metrics. */ recordSuccess(context, responseTime) { if (context.errorHistory.length > 0) { const lastError = context.errorHistory[context.errorHistory.length - 1]; if (!lastError.recoveryTime) { lastError.recoveryTime = Date.now() - lastError.timestamp; context.successfulRecoveries++; const recoveryTimes = context.errorHistory.filter((e) => e.recoveryTime).map((e) => e.recoveryTime); if (recoveryTimes.length > 0) { context.avgRecoveryTime = recoveryTimes.reduce((a, b) => a + b, 0) / recoveryTimes.length; } } } this.updatePerformanceAssessment(context); } /** * Update performance assessment based on recent patterns. */ updatePerformanceAssessment(context) { const recentErrors = context.errorHistory.filter( (e) => Date.now() - e.timestamp < 3e5 // Last 5 minutes ); const errorRate = recentErrors.length / Math.max(context.successfulRecoveries + recentErrors.length, 1); if (errorRate > 0.3) { context.recentPerformance = "poor"; } else if (errorRate > 0.1) { context.recentPerformance = "degraded"; } else { context.recentPerformance = "good"; } } /** * Check if circuit breaker should prevent requests. */ isCircuitOpen(context, strategy, config) { if (strategy.errorType) { return !intelligentCircuitBreaker.shouldAllowRequest( context.operationId, strategy.errorType, config ); } if (context.circuitBreakerState === "OPEN") { const now = Date.now(); if (now - context.lastFailureTime > config.circuitBreakerResetTime) { context.circuitBreakerState = "HALF_OPEN"; return false; } return true; } return false; } /** * Record failure and update circuit breaker state. */ recordFailure(context) { context.failures++; context.lastFailureTime = Date.now(); if (context.failures >= 5) { context.circuitBreakerState = "OPEN"; } } /** * Enhance error with retry metadata for debugging. */ enhanceErrorWithRetryInfo(error, attempts, context) { if (error instanceof TransactionError) { error.retryMetadata = { attempts, totalDuration: Date.now() - context.startTime, errorHistory: context.errorHistory.slice(-5), // Last 5 errors recentPerformance: context.recentPerformance }; } return error; } /** * Get or create adaptive context for operation tracking. */ getOrCreateAdaptiveContext(operationId) { if (!this.adaptiveContexts.has(operationId)) { this.adaptiveContexts.set(operationId, { operationId, startTime: Date.now(), failures: 0, lastFailureTime: 0, circuitBreakerState: "CLOSED", errorHistory: [], successfulRecoveries: 0, avgRecoveryTime: 0, recentPerformance: "good" }); } return this.adaptiveContexts.get(operationId); } /** * Update global performance metrics for adaptive decisions. */ updatePerformanceMetrics(operationId, success, responseTime) { if (success) { this.globalPerformanceMetrics.avgResponseTime = this.globalPerformanceMetrics.avgResponseTime * 0.9 + responseTime * 0.1; } this.globalPerformanceMetrics.successRate = this.globalPerformanceMetrics.successRate * 0.95 + (success ? 0.05 : 0); } /** * Delay execution for the specified number of milliseconds. */ delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } }; ERROR_SPECIFIC_STRATEGIES = { ["RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */]: { errorType: "RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */, maxAttempts: 5, baseDelay: 2e3, maxDelay: 6e4, backoffType: "exponential", jitterFactor: 0.25, // Higher jitter to spread out requests shouldRetry: (attempt, error, context) => { return attempt <= 5; }, calculateDelay: (attempt, baseDelay, context) => { const retryAfter = extractRetryAfterFromError(context.lastError); if (retryAfter) { return Math.min(retryAfter * 1e3, 6e4); } return Math.min(baseDelay * Math.pow(2, attempt - 1), 6e4); } }, ["JOB_NOT_READY" /* JOB_NOT_READY */]: { errorType: "JOB_NOT_READY" /* JOB_NOT_READY */, maxAttempts: 10, baseDelay: 3e3, maxDelay: 3e4, backoffType: "linear", // Jobs typically become ready at predictable intervals jitterFactor: 0.1, shouldRetry: (attempt, error, context) => { const timeSinceStart = Date.now() - context.startTime; return attempt <= 10 && timeSinceStart < 3e5; }, calculateDelay: (attempt, baseDelay, context) => { return Math.min(baseDelay + attempt * 1e3, 3e4); } }, ["JOB_PROCESSING" /* JOB_PROCESSING */]: { errorType: "JOB_PROCESSING" /* JOB_PROCESSING */, maxAttempts: 8, baseDelay: 5e3, maxDelay: 45e3, backoffType: "linear", jitterFactor: 0.15, shouldRetry: (attempt, error, context) => { const timeSinceStart = Date.now() - context.startTime; return attempt <= 8 && timeSinceStart < 42e4; }, calculateDelay: (attempt, baseDelay, context) => { return Math.min(baseDelay + attempt * 2e3, 45e3); } }, ["NETWORK_ERROR" /* NETWORK_ERROR */]: { errorType: "NETWORK_ERROR" /* NETWORK_ERROR */, maxAttempts: 4, baseDelay: 1e3, maxDelay: 15e3, backoffType: "exponential", jitterFactor: 0.2, shouldRetry: (attempt, error, context) => { return attempt <= 4; }, calculateDelay: (attempt, baseDelay, context) => { return Math.min(baseDelay * Math.pow(2, attempt - 1), 15e3); } }, ["INVALID_RESPONSE_FORMAT" /* INVALID_RESPONSE_FORMAT */]: { errorType: "INVALID_RESPONSE_FORMAT" /* INVALID_RESPONSE_FORMAT */, maxAttempts: 3, baseDelay: 1500, maxDelay: 1e4, backoffType: "exponential", jitterFactor: 0.1, shouldRetry: (attempt, error, context) => { return attempt <= 3; }, calculateDelay: (attempt, baseDelay, context) => { return Math.min(baseDelay * Math.pow(1.5, attempt - 1), 1e4); } }, ["UNKNOWN_ERROR" /* UNKNOWN_ERROR */]: { errorType: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */, maxAttempts: 2, baseDelay: 2e3, maxDelay: 8e3, backoffType: "exponential", jitterFactor: 0.15, shouldRetry: (attempt, error, context) => { return attempt <= 2; }, calculateDelay: (attempt, baseDelay, context) => { return Math.min(baseDelay * Math.pow(2, attempt - 1), 8e3); } }, // Non-retryable errors ["UNAUTHORIZED" /* UNAUTHORIZED */]: { errorType: "UNAUTHORIZED" /* UNAUTHORIZED */, maxAttempts: 0, // Never retry auth errors baseDelay: 0, maxDelay: 0, backoffType: "fixed", jitterFactor: 0, shouldRetry: () => false, calculateDelay: () => 0 }, ["INVALID_API_KEY" /* INVALID_API_KEY */]: { errorType: "INVALID_API_KEY" /* INVALID_API_KEY */, maxAttempts: 0, baseDelay: 0, maxDelay: 0, backoffType: "fixed", jitterFactor: 0, shouldRetry: () => false, calculateDelay: () => 0 }, ["VALIDATION_ERROR" /* VALIDATION_ERROR */]: { errorType: "VALIDATION_ERROR" /* VALIDATION_ERROR */, maxAttempts: 0, baseDelay: 0, maxDelay: 0, backoffType: "fixed", jitterFactor: 0, shouldRetry: () => false, calculateDelay: () => 0 }, ["INVALID_REQUEST" /* INVALID_REQUEST */]: { errorType: "INVALID_REQUEST" /* INVALID_REQUEST */, maxAttempts: 0, baseDelay: 0, maxDelay: 0, backoffType: "fixed", jitterFactor: 0, shouldRetry: () => false, calculateDelay: () => 0 }, ["JOB_NOT_FOUND" /* JOB_NOT_FOUND */]: { errorType: "JOB_NOT_FOUND" /* JOB_NOT_FOUND */, maxAttempts: 0, baseDelay: 0, maxDelay: 0, backoffType: "fixed", jitterFactor: 0, shouldRetry: () => false, calculateDelay: () => 0 } }; RetryBudgetManager = class { budgets = /* @__PURE__ */ new Map(); canRetry(operationId, maxRetriesPerWindow = 50, windowSizeMs = 6e4) { const budget = this.getOrCreateBudget(operationId, maxRetriesPerWindow, windowSizeMs); if (Date.now() - budget.windowStart > budget.windowSizeMs) { budget.currentRetries = 0; budget.windowStart = Date.now(); } return budget.currentRetries < budget.maxRetries; } consumeRetry(operationId) { const budget = this.budgets.get(operationId); if (budget) { budget.currentRetries++; } } getOrCreateBudget(operationId, maxRetries, windowSizeMs) { if (!this.budgets.has(operationId)) { this.budgets.set(operationId, { maxRetries, windowSizeMs, currentRetries: 0, windowStart: Date.now() }); } return this.budgets.get(operationId); } }; PredictiveAnalyzer = class { operationMetrics = /* @__PURE__ */ new Map(); shouldPreemptivelyFail(operationId, context) { const metrics = this.operationMetrics.get(operationId); if (!metrics) return false; const recentErrors = metrics.errorPatterns.filter( (e) => Date.now() - e.timestamp < 3e4 // Last 30 seconds ); if (recentErrors.length > 10) { return true; } const lastFiveErrors = context.errorHistory.slice(-5); if (lastFiveErrors.length === 5) { const errorTypes = lastFiveErrors.map((e) => e.errorType); const uniqueTypes = new Set(errorTypes); if (uniqueTypes.size === 1 && ["UNAUTHORIZED" /* UNAUTHORIZED */, "INVALID_API_KEY" /* INVALID_API_KEY */, "VALIDATION_ERROR" /* VALIDATION_ERROR */].includes(lastFiveErrors[0].errorType)) { return true; } } return false; } recordMetrics(operationId, success, responseTime, errorType) { if (!this.operationMetrics.has(operationId)) { this.operationMetrics.set(operationId, { recentLatencies: [], errorPatterns: [], successRate: 1, avgResponseTime: responseTime }); } const metrics = this.operationMetrics.get(operationId); if (success) { metrics.recentLatencies.push(responseTime); if (metrics.recentLatencies.length > 20) { metrics.recentLatencies = metrics.recentLatencies.slice(-20); } metrics.avgResponseTime = metrics.avgResponseTime * 0.9 + responseTime * 0.1; metrics.successRate = metrics.successRate * 0.95 + 0.05; } else if (errorType) { metrics.errorPatterns.push({ timestamp: Date.now(), errorType }); if (metrics.errorPatterns.length > 50) { metrics.errorPatterns = metrics.errorPatterns.slice(-50); } metrics.successRate = metrics.successRate * 0.95; } } getHealthScore(operationId) { const metrics = this.operationMetrics.get(operationId); if (!metrics) return 1; const responseTimeHealth = metrics.avgResponseTime < 5e3 ? 1 : metrics.avgResponseTime < 15e3 ? 0.7 : 0.3; return metrics.successRate * 0.7 + responseTimeHealth * 0.3; } }; IntelligentCircuitBreaker = class { circuits = /* @__PURE__ */ new Map(); shouldAllowRequest(operationId, errorType, config) { const circuitMap = this.getOrCreateCircuitMap(operationId); const circuit = this.getOrCreateCircuit(circuitMap, errorType); switch (circuit.state) { case "CLOSED": return true; case "OPEN": const timeSinceLastFailure = Date.now() - circuit.lastFailureTime; const resetTime = this.getResetTimeForErrorType(errorType, config); if (timeSinceLastFailure > resetTime) { circuit.state = "HALF_OPEN"; return true; } return false; case "HALF_OPEN": return true; } } recordSuccess(operationId, errorType) { const circuitMap = this.circuits.get(operationId); if (circuitMap) { const circuit = circuitMap.get(errorType); if (circuit) { circuit.failures = 0; circuit.state = "CLOSED"; } } } recordFailure(operationId, errorType, config) { const circuitMap = this.getOrCreateCircuitMap(operationId); const circuit = this.getOrCreateCircuit(circuitMap, errorType); circuit.failures++; circuit.lastFailureTime = Date.now(); const threshold = this.getThresholdForErrorType(errorType, config); if (circuit.failures >= threshold) { circuit.state = "OPEN"; } } getThresholdForErrorType(errorType, config) { switch (errorType) { case "RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */: return 3; case "NETWORK_ERROR" /* NETWORK_ERROR */: return 5; case "JOB_NOT_READY" /* JOB_NOT_READY */: case "JOB_PROCESSING" /* JOB_PROCESSING */: return 8; default: return config.circuitBreakerThreshold; } } getResetTimeForErrorType(errorType, config) { switch (errorType) { case "RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */: return 6e4; case "NETWORK_ERROR" /* NETWORK_ERROR */: return 3e4; case "JOB_NOT_READY" /* JOB_NOT_READY */: case "JOB_PROCESSING" /* JOB_PROCESSING */: return 12e4; default: return config.circuitBreakerResetTime; } } getOrCreateCircuitMap(operationId) { if (!this.circuits.has(operationId)) { this.circuits.set(operationId, /* @__PURE__ */ new Map()); } return this.circuits.get(operationId); } getOrCreateCircuit(circuitMap, errorType) { if (!circuitMap.has(errorType)) { circuitMap.set(errorType, { failures: 0, lastFailureTime: 0, state: "CLOSED", errorType }); } return circuitMap.get(errorType); } }; retryBudgetManager = new RetryBudgetManager(); predictiveAnalyzer = new PredictiveAnalyzer(); intelligentCircuitBreaker = new IntelligentCircuitBreaker(); retryEngine = new IntelligentRetryEngine(); } }); // src/foresight/foresight.ts var foresight_exports = {}; __export(foresight_exports, { Foresight: () => Foresight }); var Foresight; var init_foresight = __esm({ "src/foresight/foresight.ts"() { "use strict"; init_cjs_shims(); init_TransactionError(); init_apiUtils(); Foresight = class { request; /** * Create a Foresight instance. * @param {string} apiKey - The API key to authenticate requests. * @param {Partial<RetryConfig>} [retryConfig] - Optional retry configuration for enhanced resilience. * @param {FetchConfig} [fetchConfig] - Optional fetch configuration for timeouts, abort signals, and custom headers. * @throws Will throw an error if the API key is not provided. */ constructor(apiKey, retryConfig, fetchConfig) { if (!apiKey) { throw new Error("API key is required"); } this.request = createForesightClient(apiKey, retryConfig, fetchConfig); } /** * Returns a list with the names of the EVM blockchains currently supported by this API. * Use the provided chain name when calling other methods. * @returns {Promise<EVMForesightChains>} A promise that resolves to an array of chains. */ async getChains() { const result = await this.request("evm/chains"); return result.response; } /** * Takes an unsigned transaction object and returns a fully classified transaction, * including an enriched English description of the action that is about to take place, and all relevant asset transfers tagged. * * Optionally, it takes a stateOverrides object, which allows you to customize the state of the chain before the transaction is previewed. * Useful for more advanced applications. You can skip this object to preview the transaction in the "real" state of the chain. * @param {string} chain - The chain name. * @param {EVMTranslateUnsignedTransaction} unsignedTransaction - The unsigned transaction object, modeled after the standard format used by multiple EVM wallets. * @param {EVMTranslateStateOverrides} stateOverrides - OPTIONAL. The state overrides object allows you to customize the state of the chain before the transaction is previewed. * @param {string} viewAsAccountAddress - OPTIONAL The account address from which perspective the transaction will be previewed. Leave blank to use the raw 'from' of the transaction object. * @param {number} block - OPTIONAL. The block number to preview the transaction at. Leave blank to use the latest block number. * @returns {Promise<EVMForesightPreviewResponse>} A promise that resolves to the transaction preview details. * @throws {TransactionError} If there are validation errors in the request. */ async preview(chain, unsignedTransaction, stateOverrides, viewAsAccountAddress, block) { try { let endpoint = `evm/${chain}/preview`; const queryParams = new URLSearchParams(); if (block) { queryParams.append("block", block.toString()); } if (viewAsAccountAddress) { queryParams.append("viewAsAccountAddress", viewAsAccountAddress); } if (queryParams.toString()) { endpoint += `?${queryParams.toString()}`; } const body = { transaction: unsignedTransaction, stateOverrides: stateOverrides || {} }; const result = await this.request(endpoint, "POST", { body: JSON.stringify(body) }); return result.response; } catch (error) { if (error instanceof Response) { const errorResponse = await error.json(); if (errorResponse.status === 400 && errorResponse.errors) { throw new TransactionError(errorResponse.errors); } } throw error; } } /** * Takes an ERC-4337 userOp object, and returns a classified transaction previewing what will happen if the userOp is executed. * * It includes an English description plus all relevant asset transfers tagged from the perspective of the userOp's 'sender' (the user). * @param {string} chain - The chain name. * @param {EVMTranslateUserOperation} userOperation - The ERC-4337 userOp object, in exactly the same format that would be submitted to a bundler for transaction execution. * @param {number} block - OPTIONAL. The block number to preview the userOp at. Leave blank to preview the userOp in the current state of the chain. * @returns {Promise<EVMForesightPreview4337Response>} A promise that resolves to the transaction preview details. * @throws {TransactionError} If there are validation errors in the request. */ async preview4337(chain, userOperation, block) { try { let endpoint = `evm/${chain}/preview4337`; endpoint += block ? `?block=${block}` : ""; const result = await this.request(endpoint, "POST", { body: JSON.stringify({ userOp: userOperation }) }); return result.response; } catch (error) { if (error instanceof Response) { const errorResponse = await error.json(); if (errorResponse.status === 400 && errorResponse.errors) { throw new TransactionError(errorResponse.errors); } } throw error; } } /** * Returns a description of the action that will take place if the transaction executes. * * @param {string} chain - The chain name. * @param {EVMTranslateUnsignedTransaction} unsignedTransaction - The unsigned transaction object, modeled after the standard format used by multiple EVM wallets. * @returns {Promise<EVMForesightDescribeResponse>} A promise that resolves to the transaction description. * @throws {TransactionError} If there are validation errors in the request. */ async describe(chain, unsignedTransaction) { try { let endpoint = `evm/${chain}/describe`; const result = await this.request(endpoint, "POST", { body: JSON.stringify({ transaction: unsignedTransaction }) }); return result.response; } catch (error) { if (error instanceof Response) { const errorResponse = await error.json(); if (errorResponse.status === 400 && errorResponse.errors) { throw new TransactionError(errorResponse.errors); } } throw error; } } /** * Validates that the response contains all required fields. * @param {any} response - The response to validate. * @param {string[]} requiredFields - Array of required field names. * @returns {boolean} True if all required fields are present. */ validateResponse(response, requiredFields) { return requiredFields.every((field) => response && typeof response[field] === "string"); } /** * Returns a description of what will happ