Packages

Durable background jobs for Gleam: an Oban port with Postgres, SQLite and in-memory engines, queues, cron, plugins and typed telemetry.

Current section

Files

Jump to
mule README.md
Raw

README.md

# mule

[![Package Version](https://img.shields.io/hexpm/v/mule)](https://hex.pm/packages/mule)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/mule/)

```sh
gleam add mule
```
```gleam
import gleam/erlang/process
import gleam/time/duration
import mule.{MuleSpec}
import mule/engines/in_memory
import mule/jobs/worker
import mule/jobs/worker_registry
import mule/notifiers/isolated

pub fn main() -> Nil {
  // A worker is a named function Mule runs, with a codec for its typed args.
  let say_hello =
    worker.new(
      "say_hello",
      perform: fn(_args, _job) {
        // ... do the work ...
        worker.Completed
      },
      codec: worker.nil_codec(),
    )

  // Choose a driver. Here: the in-memory engine + in-process notifier (no
  // database). The caller starts and owns the engine's backing actor.
  let assert Ok(storage) = in_memory.start()
  let notifier_name = process.new_name("mule_notifier")

  let assert Ok(instance) =
    mule.start(MuleSpec(
      engine: in_memory.new(storage.data),
      notifier: isolated.new(process.named_subject(notifier_name)),
      notifier_child: isolated.child(notifier_name),
      registry: worker_registry.new() |> worker_registry.register(say_hello),
      queues: [#("default", 10)],
      stage_interval: duration.seconds(1),
      plugins: [],
    ))

  // Enqueue work; the queue's producer wakes and runs it.
  let assert Ok(_job) = mule.insert(instance, say_hello, Nil)
  Nil
}
```

For a database-backed setup (Postgres engine, caller-owned pool) and the
cross-node notifier, see the [Examples](#examples) below.

Further documentation can be found at <https://hexdocs.pm/mule>.

## Postgres storage

The Postgres engine ships versioned database migrations. To apply them, either
call `migration.migrate/1` directly or generate migration files for an external
runner (cigogne / plain SQL):

```sh
gleam run -m mule/tasks/gen_migration -- priv/migrations
```

See [docs/postgres-storage.md](docs/postgres-storage.md) for the local Postgres
setup (published on port 5433), both migration workflows, and how to run the
engine tests.

## Examples

The [`examples/`](examples/) directory holds self-contained Gleam projects that
drive `mule` through its public API, grouped by what they show:

- **[`engines/`](examples/engines/)** — storage backends: `in_memory` (no
  database) and `postgres`.
- **[`plugins/`](examples/plugins/)** — scheduled-work plugins: `cron`,
  `pruner`, `lifeline`, and `reindexer`.
- **[`notifiers/`](examples/notifiers/)** — **cross-node** demos: two separate
  OS processes, and a job inserted on one node is run on the other. In
  `postgres` the wake-up travels through Postgres `LISTEN`/`NOTIFY`; in `pg`
  the processes are named Distributed Erlang nodes and it travels through a
  `pg` process group, never touching the database.
- **[`peers/`](examples/peers/)** — leader election: two processes elect
  exactly one leader and hand off when it leaves, through the `mule_peers`
  lease (`postgres`) or a database-free `:global` lock (`global`).
- **[`applications/`](examples/applications/)** — a full wisp + mist + lustre
  web app whose button enqueues a job.

Run most of them from their own directory with `gleam run` (the Postgres ones
need `docker compose up -d` first). The cross-node notifier demos run as two
processes against one database:

```sh
docker compose up -d
# terminal 1 — wait for it to print "ready":
cd examples/notifiers/pg && gleam run -m worker_node
# terminal 2:
cd examples/notifiers/pg && gleam run -m inserter_node
```

See [examples/README.md](examples/README.md) for the full tour.

## Development

```sh
gleam run   # Run the project
gleam test  # Run the tests
```