Skip to main content

lib/dust/activity_buffer.ex

defmodule Dust.ActivityBuffer do
  @moduledoc """
  ETS-backed circular buffer for recent Dust operations.

  Stores the last 100 events per store for dashboard display.
  Append is a direct ETS write — no GenServer serialization on the hot path.
  """

  @max_entries 100

  def start_link(opts) do
    name = Keyword.fetch!(opts, :name)

    case :ets.whereis(name) do
      :undefined -> :ets.new(name, [:named_table, :public, :set])
      _ref -> :ok
    end

    :ignore
  end

  def child_spec(opts) do
    %{
      id: {__MODULE__, opts[:name]},
      start: {__MODULE__, :start_link, [opts]},
      type: :worker,
      restart: :temporary
    }
  end

  def append(name, store, attrs) do
    entry =
      Map.merge(attrs, %{
        timestamp: DateTime.utc_now(),
        store: store
      })

    # Get and increment the per-store index
    idx = :ets.update_counter(name, {:idx, store}, {2, 1}, {{:idx, store}, 0})
    slot = rem(idx - 1, @max_entries)

    :ets.insert(name, {{store, slot}, entry})
    :ok
  end

  def recent(name, store, limit \\ @max_entries) do
    # Read up to @max_entries slots for this store
    entries =
      for slot <- 0..(@max_entries - 1),
          [{_key, entry}] <- [:ets.lookup(name, {store, slot})],
          do: entry

    entries
    |> Enum.sort_by(& &1.seq, :desc)
    |> Enum.take(limit)
  end
end