/** * Transaction System Errors * * Provides detailed error information for transaction failures. */ /** * Base class for all transaction errors */ export class TransactionError extends Error { constructor( message: string, public readonly context?: Record ) { super(message) this.name = 'TransactionError' Error.captureStackTrace(this, this.constructor) } } /** * Error during transaction execution */ export class TransactionExecutionError extends TransactionError { constructor( message: string, public readonly operationIndex: number, public readonly operationName: string | undefined, public override readonly cause: Error ) { super(message, { operationIndex, operationName, cause: cause.message }) this.name = 'TransactionExecutionError' } } /** * Error during transaction rollback */ export class TransactionRollbackError extends TransactionError { constructor( message: string, public readonly originalError: Error, public readonly rollbackErrors: Error[] ) { super(message, { originalError: originalError.message, rollbackErrorCount: rollbackErrors.length, rollbackErrors: rollbackErrors.map(e => e.message) }) this.name = 'TransactionRollbackError' } } /** * Error for invalid transaction state */ export class InvalidTransactionStateError extends TransactionError { constructor( currentState: string, attemptedAction: string ) { super( `Cannot ${attemptedAction}: transaction is in state '${currentState}'`, { currentState, attemptedAction } ) this.name = 'InvalidTransactionStateError' } } /** * Error for transaction timeout * * Machine-readable no-hot-retry contract: {@link retryable} and * {@link hotRetryUnsafe} are both always `true` on this class — they exist * so a caller can branch on the *shape* of the error instead of parsing * message text. Read them together: the operation may eventually succeed, * but never by looping on it immediately. * * `context` (inherited from {@link TransactionError}) carries the caller's * backoff inputs — see the field docs below. */ export class TransactionTimeoutError extends TransactionError { /** * The failed operation MAY succeed on a later attempt — once the * underlying slowness resolves (e.g. a cold page cache warms up) or the * budget is deliberately raised (`transactionBudgetFloorMs`, or a larger * `timeoutMs` override on the batch). This is a statement about eventual * retryability, not a license to retry now — see {@link hotRetryUnsafe}. */ public readonly retryable = true /** * An immediate, identical retry re-pays the FULL cost of the work that * just timed out — it does not resume partway. Looping on this error * (hot-retrying) repeats that full cost every attempt and can cascade * into a CPU/resource storm on the caller's side. Callers MUST latch: on * this error, record `{ at: Date.now(), error }`, surface one loud * failure to their own caller, and hold a cooldown window before any * re-attempt (clearing the latch only on success). Never retry this error * in a tight loop. */ public readonly hotRetryUnsafe = true constructor( timeoutMs: number, operationIndex: number, telemetry?: { /** Milliseconds elapsed in the transaction when the budget tripped. */ elapsedMs?: number /** Total number of operations in the batch that timed out. */ totalOperations?: number /** Name of the operation the batch was about to start when it tripped, if named. */ operationName?: string } ) { const progress = telemetry?.totalOperations !== undefined ? `${operationIndex}/${telemetry.totalOperations}` : String(operationIndex) const name = telemetry?.operationName ? ` ('${telemetry.operationName}')` : '' const elapsed = telemetry?.elapsedMs !== undefined ? `${telemetry.elapsedMs}ms elapsed, ` : '' super( `Transaction timed out at operation ${progress}${name} — ${elapsed}budget ${timeoutMs}ms. ` + `The batch rolled back atomically; retryable after the underlying slowness resolves or ` + `the budget is raised, but hot-retry-unsafe — latch and back off, never loop.`, { // Caller backoff inputs — all present on every instance: /** Configured budget (ms) that was exceeded. */ timeoutMs, /** Index of the operation the batch was about to start when it tripped. */ operationIndex, ...telemetry } ) this.name = 'TransactionTimeoutError' } }