DBOS control operations for Elixir, with built-in idempotency semantics for mutation endpoints.
- Provides a small control API surface for DBOS workflows in Elixir.
- Works with DBOS
2.11.xsystem schema expectations. - Adds request-level idempotency for mutation operations with replay support.
- Includes a migration installer for idempotency storage.
- Health check:
ExDbos.Control.health/1 - Mutation controls:
ExDbos.Control.cancel_workflow/4ExDbos.Control.resume_workflow/4ExDbos.Control.fork_workflow/5
- Idempotency reservation/replay for mutation endpoints
- Periodic TTL cleanup for idempotency records
Add the dependency:
def deps do
[
{:ex_dbos, git: "https://github.com/taeyun16/ex_dbos.git", tag: "v0.1.0"}
]
endex_dbos does not install DBOS runtime components for you.
It assumes DBOS system schema/tables already exist in the target database.
As of February 7, 2026, DBOS "launch/init" workflows are officially documented for: Python, TypeScript, Go, and Java.
If you run DBOS workers in one of those runtimes and control them from Elixir via ex_dbos,
the standard setup is:
- Install the runtime SDK in the worker service.
- Configure system database access (typically PostgreSQL).
- Add DBOS launch/init code in the worker service startup path.
- Ensure DBOS system schema/tables are created before production rollout.
Runtime-specific entry points:
- Python
- Install:
pip install dbos - Follow "Integrating DBOS" guide: Python guide
- Install:
- TypeScript
- Install SDK:
npm install @dbos-inc/dbos-sdk@latest - Install CLI:
npm install -g @dbos-inc/dbos-cloud@latest - Schema/setup docs: TypeScript guide
- CLI reference (
dbos schema): TypeScript CLI
- Install SDK:
- Go
- Install SDK:
go get github.com/dbos-inc/dbos-transact-golang - Integrating guide: Go guide
- Install SDK:
- Java
- Add
dev.dbos:transactdependency in Gradle or Maven - Integrating guide: Java guide
- Add
General DBOS quickstart index: DBOS Quickstart
The official "Add DBOS To Your App" flow for TypeScript is:
- Register workflows.
- Configure DBOS with
DBOS.setConfig. - Launch DBOS with
DBOS.launch. - Start your app server/workers.
import { DBOS } from "@dbos-inc/dbos-sdk";
async function stepOne() {
DBOS.logger.info("step one");
}
async function workflowFunction() {
await DBOS.runStep(() => stepOne(), { name: "stepOne" });
}
const workflow = DBOS.registerWorkflow(workflowFunction);
async function main() {
DBOS.setConfig({
name: "my-dbos-worker",
systemDatabaseUrl: process.env.DBOS_SYSTEM_DATABASE_URL
});
await DBOS.launch();
await workflow();
}Install the migration template, then migrate:
mix ex_dbos.install
mix ecto.migrateCreate a client and execute control operations:
client =
ExDbos.Client.new(
repo: MyApp.Repo,
system_schema: "dbos",
idempotency_schema: "public",
idempotency_table: "control_api_idempotency"
)
:ok = ExDbos.bootstrap(client)
{:ok, %{"status" => "ok"}} = ExDbos.Control.health(client)
{:ok, payload} =
ExDbos.Control.cancel_workflow(
client,
"workflow-123",
"req-20260206-1",
ttl_days: 7,
cleanup_interval_seconds: 300
)
# payload includes idempotency metadata
_replayed? = payload["idempotency_replayed"]This repository includes a ready-to-run local workflow example with:
- PostgreSQL system database
- TypeScript DBOS worker (
DBOS.setConfig+DBOS.launch) - Elixir smoke check using
ex_dbos
Run:
docker compose up -d db dbos-worker
docker compose --profile check run --rm exdbos-checkGuide: docs/docker-compose-workflow.md
Use bootstrap checks during application startup to fail fast when DBOS prerequisites are missing:
client =
ExDbos.Client.new(
repo: MyApp.Repo,
system_schema: "dbos",
idempotency_schema: "public",
idempotency_table: "control_api_idempotency"
)
:ok = ExDbos.bootstrap!(client)Default checks:
- DB connection health (
SELECT 1) - DBOS system tables in
system_schema - idempotency table in
idempotency_schema
Optional flags:
check_idempotency_table: falseto skip idempotency table existence checkrequired_system_tables: [...]to override the expected DBOS system table set
| Function | Purpose | Idempotency |
|---|---|---|
health(client) |
Validate DB connectivity (SELECT 1) |
Not used |
cancel_workflow(client, workflow_id, request_key, opts) |
Mark workflow as cancelled when non-terminal | request_key required |
resume_workflow(client, workflow_id, request_key, opts) |
Re-enqueue workflow when non-terminal | request_key required |
fork_workflow(client, workflow_id, params, request_key, opts) |
Create a forked workflow and optionally copy historical state | request_key required |
Mutation endpoints (cancel, resume, fork) use idempotency with this model:
- New request key: operation executes and response is stored.
- Replayed request key (same action/workflow): stored success payload is returned.
- Reused key with different action/workflow: returns conflict (
409). - Replayed key whose previous request failed: returns conflict (
409) with failure context.
Useful options:
ttl_days(default7): expiration window for idempotency rows.cleanup_interval_seconds(default300): cleanup throttle interval per table.
- Docs index:
docs/README.md - Bootstrap guide:
docs/bootstrap.md - Quickstart:
docs/quickstart.md - Docker Compose workflow:
docs/docker-compose-workflow.md - Control API details:
docs/control-api.md - Idempotency details:
docs/idempotency.md - Troubleshooting:
docs/troubleshooting.md
- Contribution guide:
CONTRIBUTING.md