Agora

Getting Started

Install @adonis-agora/resilience, wrap a flaky call with a composed policy, then register named policies in config and reach them through the container.

Getting @adonis-agora/resilience running is three steps: install the package, compose a policy, and (optionally) register named policies in config/resilience.ts so you can reach them through the container-resolved ResilienceService. You can use the policies entirely standalone — the AdonisJS surfaces are a convenience, not a requirement.


Prerequisites

  • Node.js 20.6+
  • AdonisJS 7 (@adonisjs/core ^7.3.0)
  • TypeScript 5+

Install

node ace add @adonis-agora/resilience

node ace add installs the package, runs node ace configure @adonis-agora/resilience for you (registering the provider), and publishes config/resilience.ts. To configure an already-installed package, run the configure step directly:

node ace configure @adonis-agora/resilience

This registers @adonis-agora/resilience/resilience_provider in adonisrc.ts and writes config/resilience.ts. @adonis-agora/diagnostics and @adonis-agora/context are optional — install them only if you want diagnostics emission or tenant-aware breaker keys (see Integrations).

Wrap a call with a policy

Every policy exposes execute(op). Compose several with wrap() — outermost first — and run your operation inside:

import { wrap, timeout, retry, exponential, circuitBreaker, InMemoryResilienceStore } from '@adonis-agora/resilience'

const store = new InMemoryResilienceStore()

const charge = wrap(
  timeout(2_000),                                              // give up after 2s
  retry({ attempts: 3, backoff: exponential(100) }),          // up to 3 tries, 100ms → 200ms
  circuitBreaker({ key: 'payments', store, threshold: 5, cooldownMs: 30_000 }),
)

const result = await charge.execute(() => chargeCard(order))

Your operation receives a PolicyContext ({ signal, attempt }) if you want it — wire the AbortSignal into fetch/your client to make timeouts actually cancel work:

await charge.execute(({ signal }) => fetch(url, { signal }))

Register named policies (optional)

config/resilience.ts (published by configure) is where you register reusable, named policies and pick a store. Uncomment and adapt:

config/resilience.ts
import { defineConfig, stores, wrap, timeout, retry, exponential } from '@adonis-agora/resilience'

export default defineConfig({
  // The circuit-breaker store. `memory` is in-process; see Stores for Lucid / Redis.
  default: 'memory',
  stores: {
    memory: stores.memory(),
  },

  // Emit diagnostics events on `agora:resilience:*`. Default true.
  // emit: true,

  // Named, reusable policies resolvable via `resilience.execute('payments', op)`.
  policies: {
    payments: () => wrap(timeout(2_000), retry({ attempts: 3, backoff: exponential(100) })),
  },
})

The provider binds a singleton ResilienceService built lazily from this config. Import the ready-to-use singleton from @adonis-agora/resilience/services/main into a service and run the named policy around the outbound call:

app/services/payment_gateway_service.ts
import resilience from '@adonis-agora/resilience/services/main'
import type Order from '#models/order'

export default class PaymentGatewayService {
  async charge(order: Order) {
    // the named `payments` policy wraps the call to the payment gateway
    return resilience.execute('payments', ({ signal }) =>
      fetch('https://api.stripe.com/v1/charges', {
        method: 'POST',
        body: JSON.stringify({ amount: order.total, currency: 'usd' }),
        signal, // thread the abort signal so the 2s timeout can cancel the request
      }).then((res) => res.json())
    )
  }
}

By default the service uses the in-memory store and emits diagnostics events. To share circuit state across instances, point default at a Lucid or Redis store built with the stores factory — see Stores. To turn emission off, pass emit: false.

Where to go next

On this page