Agora

Comparison

How @adonis-agora/durable compares to plain @adonisjs/queue jobs, Temporal, Inngest, and BullMQ — what the suspend-model library approach buys you, and when a dedicated orchestration cluster or a managed platform is the better call.

Durable execution is a crowded space, and the honest answer to "which one?" is it depends on your architecture. This page positions @adonis-agora/durable against the tools people most often weigh it against — starting with the one every AdonisJS app already has, @adonisjs/queue — as fairly as we can manage.

The short version: @adonis-agora/durable is a library, not a platform. It lives inside your AdonisJS app as a provider, persists state to the SQL database you already run through Lucid, and dispatches steps over the queue you already have — or over that same database, with no broker at all. There is no extra server to deploy, no vendor to sign up with, and no new language runtime — the trade being that you own scaling and operations, the way you already do for the rest of your app.

At a glance

@adonis-agora/durable@adonisjs/queueTemporalInngestBullMQ
ShapeAdonisJS library (provider)AdonisJS job queueOrchestration cluster + SDKsManaged platform (or self-host)Queue library
Extra infranone — your DB + optional queueits adapter (Redis or a database)Temporal server + its DBInngest server / cloudRedis
State lives inyour Postgres/MySQL/SQLitejob payloads in the adapterTemporal's persistenceInngest's storeRedis
Workflow modelreplay + suspend, plain async codeindependent jobs (no replay)replay, plain codestep memoization over HTTPjobs + flows (no replay)
Waits (sleep/signal)zero-compute suspenddelayed jobs onlyzero-computezero-computedelayed jobs only
Sagas (compensation)built-in — { compensate } on any step—manual try/catch patternmanual—
Event triggersonEvent + eventBatch (debounce/batch), @OnEvent / @OnDiagnosticdispatch by handschedules; signal-with-start by handcore feature (debounce, batch)—
Per-key mutexsingleton (FIFO per key)—one run per workflow idconcurrency key = 1Pro only (groups)
Durable entitiesbuilt-in (engine.registerEntity)————
Cross-languageTS + Python workers, one workflow; wire-compatible with NestJS (nestjs-durable)JS/TS onlyper-language SDKsHTTP (any language)JS/TS only
DI integrationAdonisJS container (@inject())AdonisJS containermanualframework-agnosticmanual
Dashboardbuilt in, plus a Telescope view, OTel and metricsadapter toolingTemporal WebInngest dashboardthird-party (Taskforce, Bull Board)
Scale ceilingyour DB's write throughputits adapter's throughputvery high (dedicated cluster)managed for youRedis throughput

vs plain @adonisjs/queue jobs

This is the comparison that matters most in practice, because @adonisjs/queue is what an AdonisJS app reaches for first — and this library runs happily on top of it as its cross-process transport.

A queue gives you jobs: a class under app/jobs/ with an execute(), dispatched to a worker. What it does not give you is a durable function: there is no replay, so a multi-step process must be modeled as a chain of independent jobs passing state through their payloads. The "what happened so far" lives implicitly across jobs; a crash between them leaves you reconstructing progress; waiting on a human approval or a payment callback means inventing a persistence scheme for the half-finished flow.

@adonis-agora/durable keeps the queue underneath for what it's great at — moving step invocations between processes — and adds the durable layer on top: the whole flow is one readable run(ctx, input), every step is checkpointed, waits are await ctx.sleep(...) / await ctx.waitForSignal(...) that survive deploys, and one dashboard shows the end-to-end run. The authoring convention is a deliberate parallel — app/jobs → app/workflows, make:job → make:workflow, Job.dispatch() → Workflow.dispatch(input) — so moving a job over is mechanical (see From a queue job to a durable workflow).

Choose a plain job for fire-and-forget background work — a resize, an email, a webhook delivery — where each job is independent and "workflow" would be overkill. Choose @adonis-agora/durable the moment a process has steps that must not re-run, waits, or state that outlives a deploy.

vs Temporal

Temporal is the reference implementation of durable execution, and its model — deterministic replay of plain code — is the same one this library uses. If you've written a Temporal workflow, ctx.step / ctx.sleep / signals / ctx.patched will all feel familiar; even the query/update split is deliberately Temporal-shaped. A few things Temporal leaves as hand-rolled patterns are first-class here: saga compensation is a { compensate } option on any step (undone in reverse, durably) instead of a try/catch you write yourself, and durable entities — keyed, serialized actors — have no Temporal equivalent at all.

The difference is operational. Temporal is a separate orchestration cluster: its own server, its own database, its own deployment lifecycle, workers connecting over gRPC. That buys enormous scale, multi-region namespaces, and battle-tested guarantees — and costs you a second distributed system to run (or a Temporal Cloud bill). @adonis-agora/durable inverts the trade: the engine is a provider in your app, state is rows in your existing database, and "deploying the orchestrator" is just deploying your app.

Choose Temporal when workflow orchestration is a core, high-scale concern — millions of concurrent runs, many teams, many languages — and a dedicated cluster earns its keep. Choose @adonis-agora/durable when you're an AdonisJS shop that wants durable checkout/onboarding/pipeline flows without adopting and operating a second platform.

vs Inngest

Inngest attacks the same problem from the serverless direction: your steps are memoized function invocations driven over HTTP by the Inngest server, which makes it a natural fit for Vercel/Netlify-style deployments and event-driven fan-out. It is excellent at what it does, and its managed offering removes the ops question entirely.

The differences that matter here: @adonis-agora/durable workflows are BaseWorkflow classes built by the AdonisJS container — steps are @Step methods on injectable classes, tested like any other service — and the suspend model parks a waiting run as a database row rather than re-invoking a function per step. State stays in your database (a hard requirement in regulated or air-gapped environments), and step dispatch rides your own transport (in-process, @adonisjs/queue, plain DB polling, or BullMQ) instead of inbound HTTP. Inngest's signature event ergonomics have direct analogues too: onEvent with eventBatch starts workflows from published events with the same coalescing patterns ("reindex at most once the source goes quiet", "process uploads in batches"), and @OnEvent / @OnDiagnostic bridge the Adonis emitter and diagnostics channels in without wiring.

Choose Inngest for serverless/edge architectures or when you want a managed platform with event-driven ergonomics. Choose @adonis-agora/durable for long-running AdonisJS services, container-first codebases, and data-residency constraints.

vs BullMQ

BullMQ gives you jobs and queues: retries, priorities, rate limits, delayed jobs, and flows for parent/child trees. Like a plain queue job, it has no replay, so the same limits apply: a multi-step process is a chain of handlers passing state through job data, and long waits need a persistence scheme of your own.

In AdonisJS you'd usually meet BullMQ through @adonisjs/queue, which is this library's primary cross-process transport. A dedicated transports.bullmq(...) driver also exists for a specific reason: it is byte-compatible with the NestJS nestjs-durable BullMQ transport and its Python worker, so an AdonisJS engine and NestJS or Python workers can share one Redis.

Choose plain BullMQ for independent background jobs where "workflow" would be overkill. Choose @adonis-agora/durable — on whichever transport you already run — once a process has steps that must not re-run, waits, or state that outlives a deploy.

Where @adonis-agora/durable is honestly weaker

  • Scale ceiling. The engine writes checkpoints to your SQL database. That's a feature (one source of truth, your backup story) until your run volume makes checkpoint writes a hot spot — a dedicated cluster like Temporal moves that ceiling far higher. A store-less cluster (tenant pods driving one control plane) spreads the step work, but every checkpoint still lands in the control plane's database.
  • Ecosystem maturity. Temporal has a decade of production hardening and a large community; this library is younger and AdonisJS-scoped by design.
  • Languages. Cross-language support today means TypeScript workflows with Python workers (or Python-authored workflows over the same protocol) — not the per-language SDK breadth of Temporal.

If those are your constraints, use the bigger tool. If they aren't, skipping a second platform is a real simplification — in code, in ops, and in the audit story.

On this page

Partner

Your product as the official integration for Durable, right here.

Become a partner →