Skip to main content

lib/dust.ex

defmodule Dust do
  @moduledoc "Dust SDK — reactive global map client."

  @cloud_url "wss://dustlayer.io/ws/sync"

  @doc """
  Returns the WebSocket URL for Dust's hosted cloud service at dustlayer.io.

  Use as the `:url` option when starting the supervisor against cloud:

      {Dust, stores: ["acme/site"], url: Dust.cloud_url(), token: token, cache: ...}
  """
  def cloud_url, do: @cloud_url

  defmacro __using__(opts) do
    quote do
      use Dust.Instance, unquote(opts)
    end
  end

  def child_spec(opts) do
    %{
      id: __MODULE__,
      start: {Dust.Supervisor, :start_link, [opts]},
      type: :supervisor
    }
  end

  defdelegate get(store, path), to: Dust.SyncEngine
  defdelegate get_many(store, paths), to: Dust.SyncEngine
  defdelegate entry(store, path), to: Dust.SyncEngine
  defdelegate put(store, path, value), to: Dust.SyncEngine
  defdelegate put(store, path, value, opts), to: Dust.SyncEngine
  defdelegate delete(store, path), to: Dust.SyncEngine
  defdelegate delete(store, path, opts), to: Dust.SyncEngine
  defdelegate merge(store, path, map), to: Dust.SyncEngine
  defdelegate merge(store, path, map, opts), to: Dust.SyncEngine
  defdelegate increment(store, path, delta \\ 1), to: Dust.SyncEngine
  defdelegate increment(store, path, delta, opts), to: Dust.SyncEngine
  defdelegate add(store, path, member), to: Dust.SyncEngine
  defdelegate add(store, path, member, opts), to: Dust.SyncEngine
  defdelegate remove(store, path, member), to: Dust.SyncEngine
  defdelegate remove(store, path, member, opts), to: Dust.SyncEngine
  defdelegate put_file(store, path, source_path), to: Dust.SyncEngine
  defdelegate put_file(store, path, source_path, opts), to: Dust.SyncEngine
  defdelegate on(store, pattern, callback, opts \\ []), to: Dust.SyncEngine
  defdelegate watch(store, pattern, callback, opts \\ []), to: Dust.SyncEngine, as: :on
  defdelegate off(store, ref), to: Dust.SyncEngine
  defdelegate unsubscribe(store, ref), to: Dust.SyncEngine, as: :off
  defdelegate enum(store, pattern), to: Dust.SyncEngine
  defdelegate enum(store, pattern, opts), to: Dust.SyncEngine
  defdelegate range(store, from, to, opts \\ []), to: Dust.SyncEngine
  defdelegate status(store), to: Dust.SyncEngine
  defdelegate lease(store, key, opts \\ []), to: Dust.SyncEngine
  defdelegate renew(store, lease, opts \\ []), to: Dust.SyncEngine
  defdelegate release(store, lease), to: Dust.SyncEngine

  @doc """
  Coordinated distributed cache-fill — compute `fun` once across the fleet and
  share the result.

  Returns `{:ok, %Dust.Flight{}}` or `{:error, reason}`.

  `fun` receives the held `%Dust.Lease{}` (or `nil` on the degraded
  `:run_local` path) and MUST return:

    * `{:publish, value}` — store `value` at `key` and return it. `value` must
      be a small pointer (put the bytes in S3/your DB). Publish a *definitive*
      negative (e.g. a genuinely empty result) so the freshness window holds
      and you don't recompute — classification is domain-specific (an empty
      page may be definitive; a 404 that will appear later is transient).
    * `{:abort, reason}` — release the lease, publish nothing, return
      `{:error, reason}`. Use this for *transient* failures so they aren't
      cached. **Prefer `{:abort, _}` over raising** for expected/transient
      errors: it releases the lease *immediately* (waiters re-elect at once),
      whereas a raised `fun` only frees the lease when `lease_ttl` expires
      (the heartbeat dies with the caller). Inside an Oban worker especially,
      abort cleanly rather than raise.

  Options:

    * `:fresh?` — `nil` (default) = presence mode (key exists ⇒ fresh; for
      done-forever results). A `(value -> boolean)` predicate = freshness mode
      (the value carries its own timestamp; refill when the predicate says so).
    * `:lease_ttl` — max in-flight fill time before the lease is stealable
      (default 30s; the lease is heartbeat-renewed while `fun` runs).
    * `:wait_timeout` — max a follower waits for the winner (default
      `lease_ttl + 5s`).
    * `:on_unavailable` — `:run_local` (default; run `fun` uncoordinated, never
      block — possible duplicate work, `coordinated?: false`) or `:error`.
    * `:lock_key` — the lease key (default a reserved sibling of `key`).

  **At-least-once, not exactly-once.** `fun` must be idempotent.
  """
  defdelegate single_flight(store, key, fun, opts \\ []), to: Dust.SingleFlight, as: :run
end