# `Honker.Scheduler`

Time-trigger task registry. Thin wrapper over `honker_scheduler_*`
SQL functions, plus a blocking `run/3` loop with leader election via
the `honker-scheduler` advisory lock.

    :ok = Honker.Scheduler.add(db,
      name: "hourly-health",
      queue: "health",
      schedule: "0 * * * *",
      payload: %{}
    )

    stop = :atomics.new(1, [])
    :ok = :atomics.put(stop, 1, 0)
    Task.async(fn ->
      Honker.Scheduler.run(db, "worker-1", fn -> :atomics.get(stop, 1) == 1 end)
    end)

# `add`

Register a recurring scheduled task. Idempotent by `:name`.

Options (all required except `:priority` and `:expires_s`):

  * `:name`       — unique task name
  * `:queue`      — queue the payload is enqueued onto each fire
  * `:schedule`   — canonical schedule expression:
                    5-field cron, 6-field cron, or `@every <n><unit>`
  * `:cron`       — backward-compatible alias for `:schedule`
  * `:payload`    — any JSON-encodable term
  * `:priority`   — integer (default 0)
  * `:expires_s`  — seconds; the fired job expires this many seconds
                    after its scheduled fire time. Default `nil`.
  * `:max_attempts` — attempt budget for each fired job. Default `3`.

# `list`

Return every registered schedule with current state. Each entry is a
map with: name, queue, cron_expr, payload, priority, expires_s,
next_fire_at, enabled, max_attempts.

# `pause`

Pause a registered schedule. Returns `{:ok, true}` if a row was paused.

# `remove`

Unregister a task by name. Returns `{:ok, count}`.

# `resume`

Resume a paused schedule. Returns `{:ok, true}` if a row was resumed.

# `run`

Run the scheduler loop with leader election. Blocks until `stop_fun`
returns `true`. Only the process holding the `honker-scheduler` lock
fires ticks; standbys poll for the lock to expire.

Lock TTL is 60 seconds; we refresh every 20 seconds. If a refresh
returns `false` (TTL expired, someone else took it), we exit the
leader loop *before* firing another tick so we never double-fire
with the new leader.

On any tick error we release the lock before returning the error so
a standby can take over immediately.

# `soonest`

Soonest unix timestamp across all tasks, or 0 if none.

# `tick`

Fire any tasks whose boundaries are due. Returns
`{:ok, [%{name, queue, fire_at, job_id}, ...]}` — a list of maps with
string keys as decoded from the JSON returned by the extension.

# `update`

Mutate fields in place. `opts` is a keyword list with any of:
`cron:` / `schedule:`, `payload:`, `priority:`, `expires_s:`,
`max_attempts:`. Omit a key to leave its field alone. `payload: nil`
writes JSON null; `max_attempts: nil` resets to default 3.
Cron change recomputes `next_fire_at` from now. Returns
`{:ok, true}` iff a row was updated.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
