Where events go.
Two adapters ship: Pixelex.Store.Postgres (the real one) and
Pixelex.Store.ETS (tests, and a dev server that should not need a database
to boot). ClickHouse via ecto_ch and DuckDB via duckdbex fit this
behaviour without changes to anything above it, but neither is written —
columnar storage earns its operational cost somewhere past the point where
partitioned Postgres stops coping, and most apps never get there.
The contract
insert_events/1 receives a batch and must be idempotent on event.id. The
ingest buffer may replay a batch after a transient failure, and every event
carries a UUIDv7 assigned once at creation, so a replay is a duplicate key
rather than a duplicate row.
Summary
Callbacks
Processes the adapter needs running, if any.
Write a batch. Must be idempotent on event.id.
Create whatever the adapter needs. Called by mix pixelex.setup; a no-op for stateless adapters.
Functions
Child specs for the configured adapter, or [] when it needs none.
Write a batch through the configured adapter.
Callbacks
@callback children() :: [Supervisor.child_spec() | {module(), term()} | module()]
Processes the adapter needs running, if any.
@callback insert_events([Pixelex.Event.t()]) :: {:ok, non_neg_integer()} | {:error, term()}
Write a batch. Must be idempotent on event.id.
Returns the number of rows actually written — which is less than the batch size when a replay collided with rows already stored, and that is success, not failure.
@callback setup() :: :ok | {:error, term()}
Create whatever the adapter needs. Called by mix pixelex.setup; a no-op for stateless adapters.
Functions
@spec children() :: list()
Child specs for the configured adapter, or [] when it needs none.
@spec insert_events([Pixelex.Event.t()]) :: {:ok, non_neg_integer()} | {:error, term()}
Write a batch through the configured adapter.