# `Zizq`
[🔗](https://github.com/zizq-labs/zizq-elixir/blob/v0.8.0/lib/zizq.ex#L4)

Official Elixir client for the [Zizq](https://zizq.io) job queue.

Zizq is a fast and durable job queue server built on an embedded
LSM database — not on Redis, and not on your RDBMS. It supports
multiple producers and multiple consumers across an entire stack,
with producers and consumers written in any language.

# `bind_all_jobs_budget`

```elixir
@spec bind_all_jobs_budget(
  Zizq.BudgetBinding.t() | keyword() | map(),
  atom(),
  keyword()
) ::
  {:ok, Zizq.BudgetChange.t()} | {:error, Zizq.Error.t()}
```

Bind every matching job to a budget, skipping over those already bound
to it.

Takes the same filters `Zizq.Filter` describes. An unfiltered call binds
every queued job on the server.

    Zizq.bind_all_jobs_budget([key: "emails", cost: 2], MyApp.Zizq, queue: "emails")

Unlike the single-job form this does not conflict — a bulk operation
cannot usefully fail on just one matching member.

Returns a `Zizq.BudgetChange`, which provides any job IDs that were in
flight and so could not be rebound.

# `bind_budget`

```elixir
@spec bind_budget(
  Zizq.Job.t() | String.t(),
  atom(),
  Zizq.BudgetBinding.t() | keyword() | map()
) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Bind one job to a budget it is not already bound to.

    Zizq.bind_budget(job, MyApp.Zizq, key: "emails", cost: 2)

Returns `%Zizq.Error{reason: :conflict}` if the job is already bound
to the budget, leaving the existing binding untouched — use
`rebind_budget/3` to replace it, or `set_budget_cost/4` to change
only what it costs.

Only queued (`:scheduled`, `:ready`) jobs may be rebound: an in-flight
job already debited tokens against its budgets, and terminal jobs are
always immutable. Either non-queued state returns
`%Zizq.Error{reason: :invalid_request}`.

Returns the updated job, whose `:budgets` reflect the change.

# `clear_all_jobs_budgets`

```elixir
@spec clear_all_jobs_budgets(keyword(), atom()) ::
  {:ok, Zizq.BudgetChange.t()} | {:error, Zizq.Error.t()}
```

Unbind *every* budget from every matching job.

Named `clear_` rather than `unbind_` so it cannot be mistaken for
`unbind_all_jobs_budget/3`, which removes one named budget. They
would otherwise differ by a single letter, and confusing them
unthrottles everything rather than one thing.

Beware: an unfiltered call strips every job on the server of any
budgets it was bound to.

# `count_jobs`

```elixir
@spec count_jobs(keyword(), atom()) ::
  {:ok, non_neg_integer()} | {:error, Zizq.Error.t()}
```

Count the jobs matching a set of filters.

    Zizq.count_jobs([queue: "emails", status: [:ready]], MyApp.Zizq)
    #=> {:ok, 1_284}

Counting is a separate endpoint rather than the length of a listing,
so a count costs one request whatever the total.

# `count_jobs!`

```elixir
@spec count_jobs!(keyword(), atom()) :: non_neg_integer()
```

Count jobs, raising on failure.

# `define_budget`

```elixir
@spec define_budget(Zizq.Budget.t() | keyword() | map(), atom(), keyword()) ::
  {:ok, Zizq.Budget.t()} | {:error, Zizq.Error.t()}
```

Define a budget.

Budgets are a Pro feature. Without a licence the server responds with
`%Zizq.Error{reason: :forbidden}`.

    Zizq.Budget.new!(
      key: "emails",
      allocation: 100,
      strategy: :time_based,
      duration: :timer.minutes(1)
    )
    |> Zizq.define_budget(MyApp.Zizq)

By default when one already exists under the key, returns
`%Zizq.Error{reason: :conflict}` and leaves the stored policy
untouched, so an application declaring its budgets on boot cannot
accidentally clobber an existing budget that has since tuned. Every
node can call this in a horizontally scaled workload and treat the
conflict as success:

    case Zizq.define_budget(budget, MyApp.Zizq) do
      {:ok, budget} -> {:ok, budget}
      {:error, %Zizq.Error{reason: :conflict}} -> :already_defined
      {:error, error} -> {:error, error}
    end

## Options

  * `:replace` — overwrite an existing policy instead of refusing.
    Never conflicts. A replace changes the policy, not the budget's
    identity, so `:created_at` survives it.

Returns `%Zizq.Error{reason: :invalid_request}` if a replacement
allocation is below a cost already set on a queued job or a cron
entry.

# `define_budget!`

```elixir
@spec define_budget!(Zizq.Budget.t() | keyword() | map(), atom(), keyword()) ::
  Zizq.Budget.t()
```

Define a budget, raising on failure.

# `delete_all_crons`

```elixir
@spec delete_all_crons(atom()) :: {:ok, non_neg_integer()} | {:error, Zizq.Error.t()}
```

Delete every cron schedule, and return how many were removed.

# `delete_all_jobs`

```elixir
@spec delete_all_jobs(keyword(), atom()) ::
  {:ok, non_neg_integer()} | {:error, Zizq.Error.t()}
```

Delete every job matching a set of filters, and return how many.

    Zizq.delete_all_jobs([queue: "emails", status: :dead], MyApp.Zizq)
    #=> {:ok, 17}

Selection uses the filters in `Zizq.Filter`.

Filters restrict what is deleted the way a `WHERE` clause does, and
are optional for the same reason: `delete_all_jobs([], client)`
empties the server, deliberately.

Counting first with the same filters is a cheap way to see what
would go:

    filters = [queue: "emails", status: :dead]
    {:ok, 17} = Zizq.count_jobs(filters, MyApp.Zizq)
    {:ok, 17} = Zizq.delete_all_jobs(filters, MyApp.Zizq)

# `delete_all_jobs!`

```elixir
@spec delete_all_jobs!(keyword(), atom()) :: non_neg_integer()
```

Delete every matching job, raising on failure.

# `delete_budget`

```elixir
@spec delete_budget(Zizq.Budget.t() | String.t(), atom()) ::
  :ok | {:error, Zizq.Error.t()}
```

Delete a budget.

Responds `%Zizq.Error{reason: :conflict}` while anything remains
bound to it.

# `delete_budget!`

```elixir
@spec delete_budget!(Zizq.Budget.t() | String.t(), atom()) :: :ok
```

Delete a budget, raising on failure.

# `delete_cron`

```elixir
@spec delete_cron(Zizq.Cron.t() | String.t(), atom()) ::
  :ok | {:error, Zizq.Error.t()}
```

Delete a cron schedule and everything all its entries.

Jobs it already enqueued are unaffected.

# `delete_cron!`

```elixir
@spec delete_cron!(Zizq.Cron.t() | String.t(), atom()) :: :ok
```

Delete a cron schedule, raising on failure.

# `delete_cron_entry`

```elixir
@spec delete_cron_entry(keyword(), atom()) :: :ok | {:error, Zizq.Error.t()}
```

Delete one cron entry, leaving the rest of its schedule alone.

    Zizq.delete_cron_entry([cron: "my_app", entry: "digest"], MyApp.Zizq)

The server-side counterpart to `Zizq.Cron.delete_entry/2`, which
changes a schedule in memory you then replace whole on the server.

# `delete_cron_entry!`

```elixir
@spec delete_cron_entry!(keyword(), atom()) :: :ok
```

Delete one cron entry, raising on failure.

# `delete_job`

```elixir
@spec delete_job(Zizq.Job.t() | String.t(), atom()) :: :ok | {:error, Zizq.Error.t()}
```

Delete a job outright.

    Zizq.delete_job(job, MyApp.Zizq)
    #=> :ok

Unlike `report_failure/3` with `kill: true`, which leaves a dead job
behind to be inspected, this removes it. A job the server no longer
holds is `{:error, %Zizq.Error{reason: :not_found}}`.

# `delete_job!`

```elixir
@spec delete_job!(Zizq.Job.t() | String.t(), atom()) :: :ok
```

Delete a job outright, raising on failure.

# `enqueue`

```elixir
@spec enqueue(Zizq.Enqueue.t() | keyword() | map(), atom()) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Enqueue a job.

Accepts a `Zizq.Enqueue` struct, or a keyword list or map of the same
fields. Only `:type` is required; see `Zizq.Enqueue` for the rest.

    Zizq.Enqueue.new!(type: "send_email", payload: %{"user_id" => 42})
    |> Zizq.enqueue(MyApp.Zizq)
    #=> {:ok, %Zizq.Job{id: "03gn…", status: :ready}}

The job comes first and the client second so that enqueues pipe,
which is how they are normally written once job modules are building
them.

Returns the job the server recorded, so its `:id` and server-assigned
defaults are available immediately. Note that the returned job carries
no `:payload` — the server omits it from enqueue responses.

Raises `ArgumentError` for an invalid enqueue, since that is a bug in
the calling code rather than a runtime condition to handle.

# `enqueue!`

```elixir
@spec enqueue!(Zizq.Enqueue.t() | keyword() | map(), atom()) :: Zizq.Job.t()
```

Enqueue a job, raising on failure.

Suits call sites where a failed enqueue should abort the surrounding
work, such as inside a transaction.

# `enqueue_all`

```elixir
@spec enqueue_all([Zizq.Enqueue.t() | keyword() | map()], atom()) ::
  {:ok, [Zizq.Job.t()]} | {:error, Zizq.Error.t()}
```

Enqueue many jobs in a single atomic bulk request.

Each element may be a `Zizq.Enqueue` struct, a keyword list, or a
map, exactly as `enqueue/2` accepts.

    users
    |> Enum.map(&Zizq.Enqueue.new!(type: "send_email", payload: %{"user_id" => &1.id}))
    |> Zizq.enqueue_all(MyApp.Zizq)
    #=> {:ok, [%Zizq.Job{}, ...]}

Jobs are returned in the order they were sent. An empty list short
circuits immediately without contacting the server.

Note that the returned jobs carry no `:payload` — the server omits it
from enqueue responses.

# `enqueue_all!`

```elixir
@spec enqueue_all!([Zizq.Enqueue.t() | keyword() | map()], atom()) :: [Zizq.Job.t()]
```

Bulk enqueue many jobs atomically, raising on failure.

# `erase_all_data`

```elixir
@spec erase_all_data(atom()) :: :ok | {:error, Zizq.Error.t()}
```

Delete every job and every cron schedule on the server.

    Zizq.erase_all_data(MyApp.Zizq)
    #=> :ok

> #### This empties the server {: .warning}
>
> Not a filtered delete — there is nothing to narrow and nothing to
> confirm. Every job in every queue and every schedule goes, and the
> call simply returns once they have.

Intended as a setup or teardown step in tests and development, where
a known-empty server between scenarios is worth more than the data.
It is one request rather than `delete_all_jobs/2` followed by
`delete_all_crons/1`.

# `erase_all_data!`

```elixir
@spec erase_all_data!(atom()) :: :ok
```

Delete every job and schedule, raising on failure.

# `get_budget`

```elixir
@spec get_budget(Zizq.Budget.t() | String.t(), atom()) ::
  {:ok, Zizq.Budget.t()} | {:error, Zizq.Error.t()}
```

Read a single budget's policy.

Returns `%Zizq.Error{reason: :not_found}` when no budget exists under
the key.

# `get_budget!`

```elixir
@spec get_budget!(Zizq.Budget.t() | String.t(), atom()) :: Zizq.Budget.t()
```

Read a budget, raising on failure.

# `get_cron`

```elixir
@spec get_cron(Zizq.Cron.t() | String.t(), atom()) ::
  {:ok, Zizq.Cron.t()} | {:error, Zizq.Error.t()}
```

Read a cron schedule and its entries.

# `get_cron!`

```elixir
@spec get_cron!(Zizq.Cron.t() | String.t(), atom()) :: Zizq.Cron.t()
```

Read a cron schedule, raising on failure.

# `get_error`

```elixir
@spec get_error(Zizq.Job.t() | String.t(), pos_integer(), atom()) ::
  {:ok, Zizq.ErrorRecord.t()} | {:error, Zizq.Error.t()}
```

Read what went wrong on one attempt.

    Zizq.get_error(job, 1, MyApp.Zizq)
    #=> {:ok, %Zizq.ErrorRecord{attempt: 1, message: "SMTP timeout"}}

Attempts count from 1. An attempt that never failed — or never
happened — is `{:error, %Zizq.Error{reason: :not_found}}`.

# `get_error!`

```elixir
@spec get_error!(Zizq.Job.t() | String.t(), pos_integer(), atom()) ::
  Zizq.ErrorRecord.t()
```

Read one attempt's error, raising on failure.

# `get_job`

```elixir
@spec get_job(Zizq.Job.t() | String.t(), atom()) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Read one job by id.

    Zizq.get_job(job.id, MyApp.Zizq)
    #=> {:ok, %Zizq.Job{status: :completed}}

Unlike the job returned by `enqueue/2`, this one carries its
`:payload`.

A job the server no longer holds is `{:error, %Zizq.Error{reason:
:not_found}}` — which a completed job becomes as soon as its
retention expires, immediately by default.

# `get_job!`

```elixir
@spec get_job!(Zizq.Job.t() | String.t(), atom()) :: Zizq.Job.t()
```

Read one job by id, raising on failure.

# `list_budgets`

```elixir
@spec list_budgets(atom()) :: {:ok, [Zizq.Budget.t()]} | {:error, Zizq.Error.t()}
```

List every budget defined on the server, with its policy.

Budgets are a Pro feature. Without a licence the server responds with
`%Zizq.Error{reason: :forbidden}`.

# `list_budgets!`

```elixir
@spec list_budgets!(atom()) :: [Zizq.Budget.t()]
```

List budgets, raising on failure.

# `list_crons`

```elixir
@spec list_crons(atom()) :: {:ok, [String.t()]} | {:error, Zizq.Error.t()}
```

List the names of every cron schedule on the server.

# `list_crons!`

```elixir
@spec list_crons!(atom()) :: [String.t()]
```

List cron schedule names, raising on failure.

# `list_errors`

```elixir
@spec list_errors(Zizq.Job.t() | String.t(), atom(), keyword()) ::
  {:ok, Zizq.ErrorPage.t()} | {:error, Zizq.Error.t()}
```

List a job's failed attempts, oldest first.

    Zizq.list_errors(job, MyApp.Zizq)
    #=> {:ok, %Zizq.ErrorPage{errors: [%Zizq.ErrorRecord{attempt: 1}, ...]}}

## Options

  * `:limit` — records per page, 1 to 200. The server decides if
    unset.
  * `:order` — `:asc` (first attempt first) or `:desc` (most recent
    first). The server defaults to `:asc`.

Pages follow with `next_page/2`, the same as a job listing.

Errors live as long as the job does, so a completed job whose
retention has expired takes its failure history with it.

# `list_errors!`

```elixir
@spec list_errors!(Zizq.Job.t() | String.t(), atom(), keyword()) :: Zizq.ErrorPage.t()
```

List a job's failed attempts, raising on failure.

# `list_jobs`

```elixir
@spec list_jobs(keyword(), atom()) ::
  {:ok, Zizq.JobPage.t()} | {:error, Zizq.Error.t()}
```

List jobs, oldest first, one page at a time.

Narrow with any of the filters in `Zizq.Filter`:

    Zizq.list_jobs([queue: "emails", status: [:ready]], MyApp.Zizq)
    #=> {:ok, %Zizq.JobPage{jobs: [%Zizq.Job{}, ...]}}

## Options

Every option `Zizq.Filter` documents, plus:

  * `:limit` — jobs per page, 1 to 2000. The server decides if
    unset.
  * `:order` — `:asc` (oldest first) or `:desc` (newest first).
    The server defaults to `:asc`.

Follow the pages with `next_page/2` and `prev_page/2`.

# `list_jobs!`

```elixir
@spec list_jobs!(keyword(), atom()) :: Zizq.JobPage.t()
```

List jobs, raising on failure.

# `list_queues`

```elixir
@spec list_queues(atom()) :: {:ok, [String.t()]} | {:error, Zizq.Error.t()}
```

List the queues the server currently holds jobs for.

    Zizq.list_queues(MyApp.Zizq)
    #=> {:ok, ["default", "emails"]}

Queues are not declared — one exists because a job named it — so
this reports what is there rather than what was configured.

# `list_queues!`

```elixir
@spec list_queues!(atom()) :: [String.t()]
```

List queues, raising on failure.

# `next_page`

```elixir
@spec next_page(page, atom()) :: {:ok, page | nil} | {:error, Zizq.Error.t()}
when page: Zizq.JobPage.t() | Zizq.ErrorPage.t()
```

Fetch the page after this one, or `nil` at the end of the listing.

    {:ok, page} = Zizq.list_jobs([queue: "emails"], MyApp.Zizq)
    {:ok, next} = Zizq.next_page(page, MyApp.Zizq)

The link carries the cursor and the original filters.

# `pause_cron`

```elixir
@spec pause_cron(Zizq.Cron.t() | String.t(), atom()) ::
  {:ok, Zizq.Cron.t()} | {:error, Zizq.Error.t()}
```

Suspend a whole cron schedule. Its entries stop firing until resumed.

# `pause_cron_entry`

```elixir
@spec pause_cron_entry(keyword(), atom()) ::
  {:ok, Zizq.CronEntry.t()} | {:error, Zizq.Error.t()}
```

Suspend one cron entry, leaving the rest of its schedule running.

    Zizq.pause_cron_entry([cron: "my_app", entry: "digest"], MyApp.Zizq)

Changes only that entry, on the server, so it is safe while an
application is running, unlike reading a schedule, amending it and
replacing it, which is last-write-wins.

Named rather than positional for clarity.

# `prev_page`

```elixir
@spec prev_page(page, atom()) :: {:ok, page | nil} | {:error, Zizq.Error.t()}
when page: Zizq.JobPage.t() | Zizq.ErrorPage.t()
```

Fetch the page before this one, or `nil` at the start of the listing.

    {:ok, prev} = Zizq.prev_page(page, MyApp.Zizq)

# `query`

```elixir
@spec query(atom()) :: Zizq.Query.t()
```

Start a composable query.

    Zizq.query(MyApp.Zizq)
    |> Zizq.Query.where(queue: "emails", status: [:ready])
    |> Enum.take(10)

Builds nothing and sends nothing until the query is run. See
`Zizq.Query`.

# `rebind_all_jobs_budget`

```elixir
@spec rebind_all_jobs_budget(
  Zizq.BudgetBinding.t() | keyword() | map(),
  atom(),
  keyword()
) ::
  {:ok, Zizq.BudgetChange.t()} | {:error, Zizq.Error.t()}
```

Bind every matching job to a budget, replacing any existing binding
to it.

The binding is replaced whole, so a `:cost` left unset returns to the
default of 1 rather than keeping what was there.

# `rebind_budget`

```elixir
@spec rebind_budget(
  Zizq.Job.t() | String.t(),
  atom(),
  Zizq.BudgetBinding.t() | keyword() | map()
) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Bind one job to a budget, replacing any existing binding to it.

Unlike `bind_budget/3` this never conflicts. The binding is replaced
whole.

# `replace_budgets`

```elixir
@spec replace_budgets(Zizq.Job.t() | String.t(), atom(), [
  Zizq.BudgetBinding.t() | keyword() | map()
]) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Replace the whole set of budgets bound to a job.

    Zizq.replace_budgets(job, MyApp.Zizq, [[key: "emails", cost: 2]])

Budgets absent from the list are unbound. Passing `[]` is the same as
`unbind_all_budgets/2`.

# `replace_cron`

```elixir
@spec replace_cron(Zizq.Cron.t(), atom()) ::
  {:ok, Zizq.Cron.t()} | {:error, Zizq.Error.t()}
```

Install a cron schedule, replacing whatever was there.

This is the call to make at application startup:

    Zizq.Cron.new("my_app",
      entries: [
        [name: "nightly_cleanup",
         expression: "0 3 * * *",
         job: MyApp.Cleanup.new(%{})],
        [name: "digest",
         expression: "*/15 * * * *",
         timezone: "Australia/Melbourne",
         job: [type: "digest", queue: "reports"]]
      ]
    )
    |> Zizq.replace_cron(MyApp.Zizq)

Or alternatively pipelined:

    Zizq.Cron.new("my_app")
    |> Zizq.Cron.put_entry(
      name: "nightly_cleanup",
      expression: "0 3 * * *",
      job: MyApp.Cleanup.new(%{})
    )
    |> Zizq.Cron.put_entry(
      name: "digest",
      expression: "*/15 * * * *",
      timezone: "Australia/Melbourne",
      job: [type: "digest", queue: "reports"]
    )
    |> Zizq.replace_cron(MyApp.Zizq)

A schedule that runs in one timezone throughout says so once, on
the group, rather than on every entry:

    Zizq.Cron.new("my_app",
      timezone: "Australia/Melbourne",
      entries: [
        [name: "nightly_cleanup",
         expression: "0 3 * * *",
         job: MyApp.Cleanup.new(%{})]
      ]
    )
    |> Zizq.replace_cron(MyApp.Zizq)

An entry specifying its own `:timezone` uses that instead. See
`Zizq.Cron` for the whole picture.

It is atomic and idempotent, so every instance of an application can
run it on boot without coordinating — none of them needs to be the
one that owns the schedule.

The group is created if it does not exist, and **entries left out
are removed**, so a `Zizq.Cron` is the whole schedule rather than an
addition to it. That is what makes running it on every boot converge
rather than accumulate.

Once configured, there is no futher integration required. Your
`Zizq.Worker` process receives jobs enqueued via the schedule just
like any other job.

Cron needs a Pro licence; without one the server responds 403, which
arrives as `%Zizq.Error{reason: :forbidden}`.

# `replace_cron!`

```elixir
@spec replace_cron!(Zizq.Cron.t(), atom()) :: Zizq.Cron.t()
```

Install a cron schedule, raising on failure.

# `report_failure`

```elixir
@spec report_failure(Zizq.Job.t() | String.t(), atom(), keyword()) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Report a job as failed.

The server decides what happens next — reschedule with backoff, or
declare the job dead once its retry limit is spent — and returns the
job as it now stands, so the new `:status` and `:attempts` are
visible immediately.

    Zizq.report_failure(job, MyApp.Zizq, message: "SMTP timeout")

## Options

  * `:message` — what went wrong. **Required.**
  * `:error_type` — an exception or error class name, e.g.
    `"Mint.TransportError"`.
  * `:backtrace` — a formatted stacktrace.
  * `:kill` — when true, declare the job dead now regardless of how
    many attempts remain.
  * `:retry_at` — a `t:DateTime.t/0` (or Unix milliseconds) to retry
    at, bypassing the backoff policy. Reschedules the job even if its
    retry limit is spent.

`:kill` and `:retry_at` are what a handler's `{:cancel, reason}` and
`{:snooze, milliseconds}` results map onto.

# `report_success`

```elixir
@spec report_success(Zizq.Job.t() | String.t(), atom()) ::
  :ok | {:error, Zizq.Error.t()}
```

Report a job as completed successfully.

Accepts a `Zizq.Job` or a job id.

    Zizq.report_success(job, MyApp.Zizq)
    #=> :ok

A job the server no longer holds in flight — already acknowledged, or
redelivered elsewhere after its visibility timeout — answers
`{:error, %Zizq.Error{reason: :not_found}}`. That is usually benign
rather than a failure: the work is done either way, and nothing is
gained by retrying.

> #### Completed jobs disappear by default {: .tip}
>
> The server's default retention for completed jobs is zero, so a job
> is purged as it completes and a later `GET /jobs/{id}` will 404.
> Set `retention: [completed: ...]` when enqueueing if you need to
> inspect it afterwards. Dead jobs are kept for seven days by
> default, so failures remain visible without doing anything.

# `report_success_all`

```elixir
@spec report_success_all([Zizq.Job.t() | String.t()], atom()) ::
  {:ok, [String.t()]} | {:error, Zizq.Error.t()}
```

Report many jobs as completed in a single request.

Returns the ids the server did not recognise, so `{:ok, []}` means
every one was acknowledged.

    Zizq.report_success_all(jobs, MyApp.Zizq)
    #=> {:ok, []}

A partial result is a success, not an error: the jobs the server did
recognise **were** completed. Only the unrecognised ids come back,
and those are typically jobs already acknowledged or redelivered
elsewhere. An empty list short-circuits without contacting the server.

# `resume_cron`

```elixir
@spec resume_cron(Zizq.Cron.t() | String.t(), atom()) ::
  {:ok, Zizq.Cron.t()} | {:error, Zizq.Error.t()}
```

Resume a suspended cron schedule.

# `resume_cron_entry`

```elixir
@spec resume_cron_entry(keyword(), atom()) ::
  {:ok, Zizq.CronEntry.t()} | {:error, Zizq.Error.t()}
```

Resume one suspended cron entry.

# `server_version`

```elixir
@spec server_version(atom()) :: {:ok, String.t()} | {:error, Zizq.Error.t()}
```

Ask the server for its version.

Useful as a liveness check — it is the cheapest endpoint the server
exposes that proves the connection works end to end.

    Zizq.server_version(MyApp.Zizq)
    #=> {:ok, "0.6.1"}

# `set_all_jobs_budget_cost`

```elixir
@spec set_all_jobs_budget_cost(String.t(), atom(), pos_integer(), keyword()) ::
  {:ok, Zizq.BudgetChange.t()} | {:error, Zizq.Error.t()}
```

Change the cost on an existing binding, across every matching job.

Jobs not bound to the budget are skipped over. The check that a cost
fits inside the budget's capacity is against the budget itself, so it
fails the whole call rather than a subset of the selection.

# `set_budget_cost`

```elixir
@spec set_budget_cost(Zizq.Job.t() | String.t(), atom(), String.t(), pos_integer()) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Change what one job's existing binding costs, leaving the binding
itself alone.

Returns `%Zizq.Error{reason: :not_found}` if the job does is not
bound to the budget.

Returns `%Zizq.Error{reason: :invalid_request}` if the new cost
exceeds the budget's `Zizq.Budget.capacity/1`, since the job could
then never be dispatched.

# `start_link`

```elixir
@spec start_link(keyword()) :: Supervisor.on_start()
```

Start a client and add it to your supervision tree.

    children = [
      {Zizq, name: MyApp.Zizq, url: "http://localhost:7890"}
    ]

The `:name` both names the supervisor and is the handle you pass to
every other function in this module. Several clients can run side by
side under different names.

## Options

* `:name` (`t:atom/0`) - Required. Name for this client instance. Also names the supervisor, and is
  the handle passed to `Zizq.enqueue/2` and friends.

* `:url` - Required. Base URL of the Zizq server, e.g. `"http://localhost:7890"`, as a
  string or a `URI`. A path is allowed and is treated as a prefix,
  for servers behind a reverse proxy.

* `:format` (`t:atom/0`) - Serialization format: `:msgpack` (default), `:json`, or a module
  implementing the `Zizq.Codec` behaviour. The default value is `:msgpack`.

* `:pool_count` (`t:pos_integer/0`) - Number of HTTP/2 connections to the server. Each is fully
  multiplexed, so one is usually enough; raise it only if a single
  connection becomes a bottleneck. The default value is `1`.

* `:connect_timeout` (`t:timeout/0`) - Milliseconds to wait for a connection to be established. The default value is `5000`.

* `:receive_timeout` (`t:timeout/0`) - Milliseconds to wait for a response. Does not apply to streaming endpoints. The default value is `15000`.

* `:stream_idle_timeout` (`t:timeout/0`) - Milliseconds a streaming connection may go without any data
  before it is treated as dead and reconnected.

  The server sends heartbeat frames on an otherwise idle stream
  specifically so this can be detected, so the timeout only has to
  exceed that interval. The default is ten times the server's own
  default of three seconds. Raise it if the server runs with a
  longer heartbeat interval, since a timeout shorter than the
  heartbeat would reconnect a perfectly healthy connection on a
  loop.

  The default value is `30000`.

* `:tls` (`t:keyword/0`) - Certificates for connecting over HTTPS.

    * `:ca` — the certificate authority to verify the server
      against. Pins verification to this CA instead of the
      system's trust store.
    * `:client_cert` and `:client_key` — the client identity to
      present for **mutual TLS**. Both are needed together.

  Each value may be the PEM contents or a path to a PEM file.

      {Zizq,
       name: MyApp.Zizq,
       url: "https://zizq.internal:7890",
       tls: [
         ca: "/etc/zizq/ca.pem",
         client_cert: "/etc/zizq/client.pem",
         client_key: "/etc/zizq/client-key.pem"
       ]}

  HTTPS works without this — connections verify against the
  system trust store by default. It is only needed to pin a
  private CA or to present a client certificate.

  Mutual TLS requires a Zizq Pro licence on the server.

  The default value is `[]`.

  * `:ca` (`t:String.t/0`)

  * `:client_cert` (`t:String.t/0`)

  * `:client_key` (`t:String.t/0`)

# `unbind_all_budgets`

```elixir
@spec unbind_all_budgets(Zizq.Job.t() | String.t(), atom()) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Unbind every budget from a job, leaving it unthrottled.

# `unbind_all_jobs_budget`

```elixir
@spec unbind_all_jobs_budget(String.t(), atom(), keyword()) ::
  {:ok, Zizq.BudgetChange.t()} | {:error, Zizq.Error.t()}
```

Unbind one budget from every matching job, leaving other bindings
unchanged.

This is how a budget is drained before it can be deleted, since
`delete_budget/2` refuses while anything still draws on it:

    Zizq.unbind_all_jobs_budget("emails", MyApp.Zizq, budgets_key: "emails")
    Zizq.delete_budget("emails", MyApp.Zizq)

See `clear_all_jobs_budgets/2` to remove every budget rather than
one named one.

# `unbind_budget`

```elixir
@spec unbind_budget(Zizq.Job.t() | String.t(), atom(), String.t()) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Unbind one budget from a job, leaving its other budgets alone.

Returns `%Zizq.Error{reason: :not_found}` the job is not bound to it.

# `update_all_jobs`

```elixir
@spec update_all_jobs(keyword(), atom()) ::
  {:ok, non_neg_integer()} | {:error, Zizq.Error.t()}
```

Change every job matching a set of filters, and return how many.

    Zizq.update_all_jobs(
      [where: [queue: "emails", status: :scheduled], apply: [ready_at: nil]],
      MyApp.Zizq
    )
    #=> {:ok, 42}

## Options

  * `:where` — which jobs to change, using the filters in
    `Zizq.Filter`. Restricts the operation the way a `WHERE` clause
    does, and is optional for the same reason: left out, every job
    is changed, deliberately.
  * `:apply` — what to change, using the options `update_job/3`
    takes, with the same merge-patch rules. An omitted option leaves
    a field alone, `nil` clears it. Required.

Named rather than positional because both halves are keyword lists
of overlapping keys — `queue:` and `priority:` mean something on
each side — so transposing them could quietly change the wrong jobs
rather than fail.

Finished jobs cannot be changed, so asking for one is an error
rather than a silent no-op: `status: :completed` or `status: :dead`
in `:where` is rejected before the request is sent.

# `update_all_jobs!`

```elixir
@spec update_all_jobs!(keyword(), atom()) :: non_neg_integer()
```

Change every matching job, raising on failure.

# `update_budget`

```elixir
@spec update_budget(Zizq.Budget.t() | String.t(), atom(), keyword() | map()) ::
  {:ok, Zizq.Budget.t()} | {:error, Zizq.Error.t()}
```

Amend named fields of a budget's policy, leaving the rest alone.

This is a deep JSON merge patch, so e.g. a burst
can be changed without restating the strategy type and duration:

    Zizq.update_budget("emails", MyApp.Zizq, burst: 5)

## Options

  * `:allocation` — tokens the bucket accommodates when full.
  * `:strategy` — `:time_based` or `:while_in_flight`.
  * `:duration` — for `:time_based`, the period, in milliseconds.
  * `:burst` — for `:time_based`, the ceiling; `nil` clears it back to
    the allocation.

`:burst` is the only field with a meaningful `nil`.

Returns `%Zizq.Error{reason: :not_found}` when no budget exists under
the key.

# `update_budget!`

```elixir
@spec update_budget!(Zizq.Budget.t() | String.t(), atom(), keyword() | map()) ::
  Zizq.Budget.t()
```

Amend a budget, raising on failure.

# `update_job`

```elixir
@spec update_job(Zizq.Job.t() | String.t(), atom(), keyword()) ::
  {:ok, Zizq.Job.t()} | {:error, Zizq.Error.t()}
```

Change a job that has not finished yet, and return it as it now stands.

    Zizq.update_job(job, MyApp.Zizq, queue: "urgent", priority: 0)

## Options

Only the options given are touched; the rest of the job is left
alone. Passing `nil` **clears** a field to the server's default,
which is why an option must be omitted rather than set to `nil` to
leave it as it is:

    # Retry with the server's default limit, whatever it now is.
    Zizq.update_job(job, MyApp.Zizq, retry_limit: nil)

  * `:queue` — move the job to another queue. Cannot be `nil`.
  * `:priority` — lower runs sooner. Cannot be `nil`.
  * `:ready_at` — a `DateTime` or Unix milliseconds. `nil` makes the
    job ready immediately.
  * `:retry_limit` — `nil` restores the server default.
  * `:backoff` — a `Zizq.Backoff` or keyword list. `nil` restores the
    server default.
  * `:retention` — a `Zizq.Retention` or keyword list, merged field
    by field, so `retention: [completed: :timer.hours(1)]` leaves
    `:dead` alone. `nil` clears the whole override.

Only jobs that have not finished can be changed: the server rejects
a completed or dead job with `%Zizq.Error{reason: :invalid_request}`.

# `update_job!`

```elixir
@spec update_job!(Zizq.Job.t() | String.t(), atom(), keyword()) :: Zizq.Job.t()
```

Change a job, raising on failure.

# `version`

```elixir
@spec version() :: String.t()
```

Returns the version of this client as a string.

## Examples

    iex> Zizq.version()
    "0.8.0"

---

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