UNPKG

@hotmeshio/hotmesh

Version:

Permanent-Memory Workflows & AI Agents

134 lines (133 loc) 5.75 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorHandler = void 0; const utils_1 = require("../../../modules/utils"); const config_1 = require("../config"); const stream_1 = require("../../../types/stream"); class ErrorHandler { shouldRetry(input, output, retryPolicy) { const tryCount = input.metadata.try || 0; // Priority 1: Use structured retry policy (from stream columns or config) if (retryPolicy) { const maxAttempts = retryPolicy.maximumAttempts || 3; const backoffCoeff = retryPolicy.backoffCoefficient || 10; const maxInterval = typeof retryPolicy.maximumInterval === 'string' ? parseInt(retryPolicy.maximumInterval) : (retryPolicy.maximumInterval || 120); // Check if we can retry (next attempt would be attempt #tryCount+2, must be <= maxAttempts) // tryCount=0 is 1st attempt, tryCount=1 is 2nd attempt, etc. // So after tryCount, we've made (tryCount + 1) attempts // We can retry if (tryCount + 1) < maxAttempts if ((tryCount + 1) < maxAttempts) { // Exponential backoff: min(coefficient^(try+1), maxInterval) // First retry (after try=0): coefficient^1 // Second retry (after try=1): coefficient^2, etc. const backoffSeconds = Math.min(Math.pow(backoffCoeff, tryCount + 1), maxInterval); return [true, backoffSeconds * 1000]; // Convert to milliseconds } return [false, 0]; } // Priority 2: Use message-level policies (existing behavior) const policies = input.policies?.retry; const errorCode = output.code.toString(); const policy = policies?.[errorCode]; const maxRetries = policy?.[0]; const cappedTryCount = Math.min(tryCount, config_1.HMSH_MAX_RETRIES); if (maxRetries > cappedTryCount) { // 10ms, 100ms, or 1000ms delays between system retries return [true, Math.pow(10, cappedTryCount + 1)]; } return [false, 0]; } structureUnhandledError(input, err) { const error = {}; if (typeof err.message === 'string') { error.message = err.message; } else { error.message = config_1.HMSH_STATUS_UNKNOWN; } if (typeof err.stack === 'string') { error.stack = err.stack; } if (typeof err.name === 'string') { error.name = err.name; } return { status: 'error', code: config_1.HMSH_CODE_UNKNOWN, metadata: { ...input.metadata, guid: (0, utils_1.guid)() }, data: error, }; } structureUnacknowledgedError(input) { const message = 'stream message max delivery count exceeded'; const code = config_1.HMSH_CODE_UNACKED; const data = { message, code }; const output = { metadata: { ...input.metadata, guid: (0, utils_1.guid)() }, status: stream_1.StreamStatus.ERROR, code, data, }; //send unacknowleded errors to the engine (it has no topic) delete output.metadata.topic; return output; } structureError(input, output) { const message = output.data?.message ? output.data?.message.toString() : config_1.HMSH_STATUS_UNKNOWN; const statusCode = output.code || output.data?.code; const code = isNaN(statusCode) ? config_1.HMSH_CODE_UNKNOWN : parseInt(statusCode.toString()); const stack = output.data?.stack ? output.data?.stack.toString() : undefined; const data = { message, code, stack }; if (typeof output.data?.error === 'object') { data.error = { ...output.data.error }; } return { status: stream_1.StreamStatus.ERROR, code, stack, metadata: { ...input.metadata, guid: (0, utils_1.guid)() }, data, }; } async handleRetry(input, output, publishMessage, retryPolicy) { const [shouldRetry, timeout] = this.shouldRetry(input, output, retryPolicy); if (shouldRetry) { // Only sleep if no retryPolicy (legacy behavior for backward compatibility) // With retryPolicy, use visibility timeout instead of in-memory sleep if (!retryPolicy) { await (0, utils_1.sleepFor)(timeout); } // Create new message with incremented try count const newMessage = { data: input.data, metadata: { ...input.metadata, try: (input.metadata.try || 0) + 1 }, policies: input.policies, }; // Propagate retry config to new message (for immutable pattern) if (input._streamRetryConfig) { newMessage._streamRetryConfig = input._streamRetryConfig; } // Add visibility delay for production-ready retry with retryPolicy if (retryPolicy && timeout > 0) { newMessage._visibilityDelayMs = timeout; } // Track retry attempt count in database const currentAttempt = input._retryAttempt || 0; newMessage._retryAttempt = currentAttempt + 1; return (await publishMessage(input.metadata.topic, newMessage)); } else { const structuredError = this.structureError(input, output); return (await publishMessage(null, structuredError)); } } } exports.ErrorHandler = ErrorHandler;