Documentation/titan/Resilience
In this section

Resilience

Titan ships a small set of resilience primitives in @omnitron-dev/titan/utils. They are functions and classes, not decorators — designed to be composed inside your service methods at the call boundary.

PrimitiveWhat it doesPage
computeBackoffCompute delay between attemptsshared in Retry
retry()Re-attempt a failed operationRetry
CircuitBreaker classStop calling a backend after failuresCircuit Breaker
withTimeout()Abandon a call that takes too longTimeout
FailureTrackerSliding-window error countingsource: utils/failure-tracker.ts
ResilientHandleSelf-healing cached handle to a flaky external resource (auto-recreate on fatal error)source: utils/resilience.ts

There is also a @Retry decorator in @omnitron-dev/titan/decorators for simple fixed-delay retry on a method — but it is intentionally minimal (no exponential backoff, no classifier). For sophisticated resilience, use the primitives from utils/.

When you need each

  • Retry — for transient failures: network errors, 503s, timeouts. Use isOperationalError(e) to classify.

  • Circuit breaker — when retrying a failing dependency makes the problem worse. Open the circuit; let it cool down.

  • Timeout — for any external call. A call without a timeout can hang forever, taking a request slot with it.

  • FailureTracker — when you need a sliding-window error count for custom logic (e.g. dynamic throttling based on recent error rate).

These compose. A typical "outbound call" pattern:

flowchart LR
  Call[Outbound call]
  Call --> T{withTimeout}
  T --> CB{CircuitBreaker}
  CB -- closed --> R{retry}
  R --> Op[the actual operation]
  CB -- open --> Fail1[fail fast]
  T -. deadline elapsed .-> Fail2[TimeoutError]
  Op -- success --> Result[result]
  Op -- transient err --> R
  R -- exhausted --> Fail3[last error]
  • Timeout outermost — even retries shouldn't extend the deadline.

  • Circuit breaker next — if open, fail fast (no retries).

  • Retry — re-attempt on transient failures.

Compose the primitives directly (as in the example below). Note that ResilientHandle is not a retry+breaker+timeout bundle — it is a self-healing cache for a single external resource (a Redis client, the esbuild sidecar, a gRPC channel) that recreates the instance via a factory when a call throws a fatal error. See utils/resilience.ts for its API.

The minimal example

import { retry, withTimeout, CircuitBreaker } from '@omnitron-dev/titan/utils';

@Service({ name: 'Payments' })
class PaymentsService {
  private readonly stripeBreaker = new CircuitBreaker({
    failureThreshold: 5,
    resetTimeout:     60_000,   // ms in Open before a probe
  });

  @Public()
  async charge(req: ChargeRequest) {
    // withTimeout takes a Promise (not a thunk) and a number of ms
    // (or { timeout, errorMessage }).
    return withTimeout(
      this.stripeBreaker.execute(() =>
        retry(() => this.stripe.charge(req), { maxRetries: 3 })
      ),
      5_000,
    );
  }
}

Read on

  • Retry — backoff strategies, the retry() function, the @Retry decorator.

  • Circuit Breaker — CircuitBreaker class semantics.

  • Timeout — withTimeout(), abort signals.