# `ExDataSketch.FilterChain`
[🔗](https://github.com/thanos/ex_data_sketch/blob/main/lib/ex_data_sketch/filter_chain.ex#L1)

Capability-aware composition framework for chaining membership filters.

FilterChain composes membership filter structures into ordered query pipelines.
It enables lifecycle-tier patterns such as a hot Cuckoo filter (absorbing writes)
followed by a cold XorFilter (compacted snapshot).

## Chain Roles

Each filter type has valid chain positions:

- **Front/Middle/Terminal**: Bloom, Cuckoo, Quotient, CQF -- dynamic filters
  that support member? and put.
- **Terminal only**: XorFilter -- static, no incremental insert. Must be the
  last query stage.
- **Adjunct only**: IBLT -- reconciliation helper, not in the query path.

## Query Semantics

`member?/2` evaluates query stages in order with short-circuit semantics:
a definite "no" from any stage returns `false` immediately. Adjuncts are
never queried.

## Insert Semantics

`put/2` forwards to all query stages that support `:put`, skipping static
stages (XorFilter). Returns `{:ok, chain}` or `{:error, :full}` if a
Cuckoo stage is full.

## Delete Semantics

`delete/2` checks that ALL query stages support `:delete`. If any stage
lacks delete support (e.g., Bloom), raises `UnsupportedOperationError`.

## Binary Format (FCN1)

FilterChain serializes each stage independently using its own `serialize/1`,
wrapped in a chain manifest with magic bytes "FCN1".

# `t`

```elixir
@type t() :: %ExDataSketch.FilterChain{adjuncts: [struct()], stages: [struct()]}
```

# `add_stage`

```elixir
@spec add_stage(
  t(),
  struct()
) :: t()
```

Adds a filter stage to the chain.

The stage is automatically classified based on its module type:
- IBLT goes to the adjuncts list (not in query path)
- XorFilter is appended as a terminal query stage
- All other filters are appended as query stages

Raises `InvalidChainCompositionError` if the composition is invalid
(e.g., adding a query stage after a XorFilter terminal).

## Examples

    iex> chain = ExDataSketch.FilterChain.new()
    iex> chain = ExDataSketch.FilterChain.add_stage(chain, ExDataSketch.Bloom.new(capacity: 100))
    iex> length(ExDataSketch.FilterChain.stages(chain))
    1

# `adjuncts`

```elixir
@spec adjuncts(t()) :: [struct()]
```

Returns the list of adjunct stages.

## Examples

    iex> ExDataSketch.FilterChain.adjuncts(ExDataSketch.FilterChain.new())
    []

# `capabilities`

Returns the set of capabilities supported by FilterChain.

# `count`

```elixir
@spec count(t()) :: non_neg_integer()
```

Returns the sum of counts across all query stages.

## Examples

    iex> ExDataSketch.FilterChain.count(ExDataSketch.FilterChain.new())
    0

# `delete`

```elixir
@spec delete(t(), term()) :: {:ok, t()}
```

Deletes an item from all query stages.

Raises `UnsupportedOperationError` if any query stage does not support
`:delete` (e.g., Bloom or XorFilter).

## Examples

    iex> chain = ExDataSketch.FilterChain.new()
    iex> cuckoo = ExDataSketch.Cuckoo.new()
    iex> {:ok, cuckoo} = ExDataSketch.Cuckoo.put(cuckoo, "hello")
    iex> chain = ExDataSketch.FilterChain.add_stage(chain, cuckoo)
    iex> {:ok, chain} = ExDataSketch.FilterChain.delete(chain, "hello")
    iex> ExDataSketch.FilterChain.member?(chain, "hello")
    false

# `deserialize`

```elixir
@spec deserialize(binary()) :: {:ok, t()} | {:error, Exception.t()}
```

Deserializes an FCN1 binary into a FilterChain.

Returns `{:ok, chain}` on success or `{:error, reason}` on failure.

## Examples

    iex> chain = ExDataSketch.FilterChain.new()
    iex> chain = ExDataSketch.FilterChain.add_stage(chain, ExDataSketch.Bloom.new(capacity: 100))
    iex> binary = ExDataSketch.FilterChain.serialize(chain)
    iex> {:ok, recovered} = ExDataSketch.FilterChain.deserialize(binary)
    iex> length(ExDataSketch.FilterChain.stages(recovered))
    1

# `member?`

```elixir
@spec member?(t(), term()) :: boolean()
```

Tests whether an item may be a member by querying all stages in order.

Short-circuits on the first `false` result. Adjuncts are not queried.
Returns `false` if the chain has no query stages.

## Examples

    iex> chain = ExDataSketch.FilterChain.new()
    iex> bloom = ExDataSketch.Bloom.new(capacity: 100) |> ExDataSketch.Bloom.put("hello")
    iex> chain = ExDataSketch.FilterChain.add_stage(chain, bloom)
    iex> ExDataSketch.FilterChain.member?(chain, "hello")
    true

# `new`

```elixir
@spec new() :: t()
```

Creates an empty FilterChain.

## Examples

    iex> chain = ExDataSketch.FilterChain.new()
    iex> ExDataSketch.FilterChain.stages(chain)
    []

# `put`

```elixir
@spec put(t(), term()) :: {:ok, t()} | {:error, :full}
```

Inserts an item into all query stages that support `:put`.

Skips static stages (XorFilter). Returns `{:ok, chain}` on success
or `{:error, :full}` if a Cuckoo stage is full.

## Examples

    iex> chain = ExDataSketch.FilterChain.new()
    iex> chain = ExDataSketch.FilterChain.add_stage(chain, ExDataSketch.Bloom.new(capacity: 100))
    iex> {:ok, chain} = ExDataSketch.FilterChain.put(chain, "hello")
    iex> ExDataSketch.FilterChain.member?(chain, "hello")
    true

# `serialize`

```elixir
@spec serialize(t()) :: binary()
```

Serializes the FilterChain to the FCN1 binary format.

Each stage is serialized independently using its own `serialize/1`,
then wrapped in a chain manifest.

## Examples

    iex> chain = ExDataSketch.FilterChain.new()
    iex> chain = ExDataSketch.FilterChain.add_stage(chain, ExDataSketch.Bloom.new(capacity: 100))
    iex> binary = ExDataSketch.FilterChain.serialize(chain)
    iex> <<"FCN1", _rest::binary>> = binary
    iex> byte_size(binary) > 0
    true

# `size_bytes`

```elixir
@spec size_bytes(t()) :: non_neg_integer()
```

# `stages`

```elixir
@spec stages(t()) :: [struct()]
```

Returns the list of query stages.

## Examples

    iex> ExDataSketch.FilterChain.stages(ExDataSketch.FilterChain.new())
    []

---

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