This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Durex is a lightweight, embeddable durable task queue and workflow engine for Go — "Temporal for the rest of us". It provides persistent background job execution, automatic retries, workflow orchestration (sequences, fan-out, fan-in), and a web dashboard, with no external infrastructure required beyond a database.
make all # fmt + lint + test + build
make test # go test with race detector and coverage
make test-short # Quick tests (skips slow ones)
make lint # go vet + staticcheck
make fmt # gofmt
make build # go build ./...
make cli # Build the CLI tool
make coverage # Generate HTML coverage report
make bench # Run benchmarksRun a single test:
go test -run TestName ./...
go test -run TestName -v -race .The CLI tool lives in cmd/durex/ and is a separate Go module (to keep the main library dependency-free of DB drivers).
Command(command.go): Interface for task handlers. Optional interfaces:Recoverable(saga compensation),Expirable(deadline handling),Defaulter(default spec).Spec(spec.go): Blueprint for creating a command instance — name, payload data, delay, retries, timeout, cron/period, sequence chain, etc.Instance(instance.go): The persisted, running state of a command.Get/Set/GetString/GetBoolaccess typed data from JSON payload.Result(result.go): What a handler returns —Empty(),Repeat(),Retry(),Next(spec),Spawn(specs...),SpawnThen(specs, continuation).Executor(executor.go): Worker pool engine. Polls storage, dispatches commands to handlers, manages goroutine lifecycle.Registry(registry.go): Maps command names to handlers.
PENDING → STARTED → COMPLETED
↓
error + retries → re-PENDING (with backoff delay)
↓
error + no retries → FAILED → Recoverable.OnRecover()
PENDING → EXPIRED (if deadline passed before execution)
COMPLETED → REPEATING → PENDING (for periodic/cron commands)
- Sequences: Linear chain — set
Spec.Sequence = []string{"step2", "step3"}and callinstance.ContinueSequence(data)from the result. - Fan-out:
durex.Spawn(spec1, spec2, ...)— spawns parallel child commands. - Fan-in:
durex.SpawnThen([]Spec{...}, continuationSpec)— spawns parallel commands, then triggers continuation when all complete. Uses an internal__durex_barriercommand (barrier.go) that tracks child IDs. - Saga: Return
durex.Spawn(compensationSpec)fromOnRecover().
Storage interface (storage.go) has three implementations in storage/:
memory.go— in-memory, for testssqlite.go— SQLite, schema auto-migrated on openpostgres.go— PostgreSQL withFOR UPDATE SKIP LOCKEDfor multi-instance safety
// Simple function
exec.HandleFunc("send-email", func(ctx context.Context, inst durex.Instance) (durex.Result, error) { ... })
// Type-safe generic
durex.HandleTyped[MyPayload](exec, "send-email", func(ctx context.Context, inst durex.Instance, p MyPayload) (durex.Result, error) { ... })
// Struct (implements Command interface)
exec.Register(&MyCommand{})- Workers run as goroutines; panics are recovered and the worker continues.
barrier.goimplements fan-in by tracking child command IDs directly, checking completion on each child's callback.- Deep copy of
DataandMetadatauses JSON round-trip (instance.go:deepCopyMap) to handle nested maps/slices correctly. - PostgreSQL uses row-level locking for distributed multi-instance safety; SQLite is single-instance only.
- The web dashboard (
dashboard.go) serves an embedded SPA; the CLI (cmd/durex/) queries storage directly.
Main library (go.mod): only prometheus/client_golang and robfig/cron/v3.
CLI (cmd/durex/go.mod): adds SQLite and PostgreSQL drivers — kept separate to avoid pulling DB drivers into library consumers.