# Durable background worker (Postgres jobs)

Standalone producer + worker that process inspectable durable jobs in
Postgres. Useful work: uppercase a payload and store it in `job_results`.

Source pin: Bex `0b2421de955618e334936ede506b502f1413cad1` (`type: worker`,
no HTTP readiness, Blueprint `fromDatabase` binding).

Public download:
[`durable-worker-latest.zip`](https://bex.co/examples/durable-worker-latest.zip)
([README](https://bex.co/examples/durable-worker/README.md)).

Guide: [Background workers](https://bex.co/docs/platform/background-workers)
and [Durable job workers](https://bex.co/docs/platform/durable-workers).

## Local quick start (disposable Postgres)

```bash
npm install
npm run setup          # docker postgres:16 on :55433 (owned container)
npm run migrate
npm run seed           # 10 jobs
npm run worker &       # long-running consumer
npm run produce -- --count 5
npm run inspect
npm run teardown -- --container
```

Fixture setup/seed/teardown refuse non-local hosts and database names that do
not look like disposable rehearsal names. Runtime `producer.mjs` /
`worker.mjs` accept any `DATABASE_URL` you supply (including a Bex Postgres
connection string) without those destructive guards.

## Claim and completion policy

| Step | Behavior |
| --- | --- |
| Claim | `SELECT … FOR UPDATE SKIP LOCKED` on `pending` or `running` rows whose lease (`locked_at`) expired |
| Own | Sets `locked_by` + `locked_at`, increments `attempts` |
| Complete | Succeeds only if `status=running` and `locked_by` matches; inserts `job_results` (`PRIMARY KEY job_id`) |
| Fail | Returns to `pending` until `attempts >= max_attempts`, then `failed` with `last_error` |

Database result uniqueness is **not** exactly-once for external side effects.
Design side effects to tolerate at-least-once delivery.

Default lease: 30s (`BEX_JOB_LEASE_MS`). Concurrent workers are safe because
of `SKIP LOCKED` plus ownership checks on complete/fail.

## Bex Blueprint shape

See `render.yaml`: a `type: worker` service (no `healthCheckPath`), Postgres
`fromDatabase` `connectionString`, and `plan: free` on the sample database.
A live worker Deployment does not prove queue progress — use `npm run inspect`
or SQL against `jobs` / `job_results`, and check [logs](https://bex.co/docs/platform/logging).

## Tests

```bash
npm test
```

Exercises one-worker ready path, two-worker concurrency without duplicate
results, stale-lease reclaim, retry exhaustion, duplicate `submit_key`, and
graceful SIGTERM.
