# `ExCodecs.CodecRegistry`
[🔗](https://github.com/AnimaLogica/codecs/blob/v0.2.4/lib/ex_codecs/codec_registry.ex#L1)

Shared runtime catalog of codec implementations (ETS-backed).

Populated at application start (and again if this process restarts). Entries
carry a category and interface shape: binary codecs use the top-level
registry API, while spatial entries are dispatched through
`ExCodecs.Spatial`.

## Crash behavior

The ETS table is owned by this Agent process. If the Agent **crashes**, the
table is destroyed and only the `:register` callback (built-ins) is replayed
on restart. **Runtime third-party registrations made via `register/3` or
`register/5` are lost** and must be re-registered by the caller. If the
Agent restarts while the table still exists (e.g. via a supervised
`:restart_for_type`), existing entries are preserved and only overwritten
by the callback.

## Typical use

    iex> ExCodecs.available_codecs()
    [:blosc, :blosc2, :bzip2, :crc32c, :gsplat, :lz4, :ply, :snappy, :spatial_binary, :zstd]

    iex> ExCodecs.supports?(:zstd)
    true

# `all_codecs`

```elixir
@spec all_codecs() :: [atom()]
```

Sorted list of all registered names (including unavailable).

## Arguments

None.

## Returns

A sorted `[atom()]`, including entries whose module is `nil`.

## Raises

May raise `ArgumentError` if the registry ETS table does not exist.

## Example

    iex> :ok = ExCodecs.CodecRegistry.register_unavailable(:documented_optional, :compression)
    iex> :documented_optional in ExCodecs.CodecRegistry.all_codecs()
    true
    iex> ExCodecs.CodecRegistry.unregister(:documented_optional)
    :ok

# `available_codecs`

```elixir
@spec available_codecs() :: [atom()]
```

Sorted list of codec atoms with a non-nil module.

## Arguments

None.

## Returns

A sorted `[atom()]`. Unavailable entries registered with
`register_unavailable/2` are omitted.

## Raises

May raise `ArgumentError` if the registry ETS table does not exist.

## Example

    iex> codecs = ExCodecs.CodecRegistry.available_codecs()
    iex> codecs == Enum.sort(codecs) and :zstd in codecs
    true

# `available_codecs`

```elixir
@spec available_codecs(atom()) :: [atom()]
```

Sorted available catalog names in one category.

## Arguments

  * `category` — category atom such as `:compression` or `:spatial`

## Returns

Available names with non-`nil` modules, sorted by name.

## Raises

May raise `ArgumentError` if the registry table has not started.

## Examples

    iex> ExCodecs.CodecRegistry.available_codecs(:spatial)
    [:gsplat, :ply, :spatial_binary]

# `codec_info`

```elixir
@spec codec_info(atom()) :: {:ok, ExCodecs.Codec.t()} | {:error, :unsupported_codec}
```

Returns `%ExCodecs.Codec{}` for `name`.

## Arguments

  * `name` (`atom()`) — codec registry key

## Returns

  * `{:ok, ExCodecs.Codec.t()}` — metadata for a registered codec, including
    unavailable codecs
  * `{:error, :unsupported_codec}` — no registry entry exists

## Raises

Raises `FunctionClauseError` when `name` is not an atom. May raise
`ArgumentError` if the registry ETS table does not exist.

## Example

    iex> {:ok, %ExCodecs.Codec{name: :zstd, category: :compression}} =
    ...>   ExCodecs.CodecRegistry.codec_info(:zstd)
    iex> true
    true

# `codecs_by_category`

```elixir
@spec codecs_by_category(atom()) :: [ExCodecs.Codec.t()]
```

Lists `%ExCodecs.Codec{}` for a category atom (e.g. `:compression`).

## Arguments

  * `category` (`atom()`) — category to select, such as `:compression`

## Returns

A `[ExCodecs.Codec.t()]` sorted by each struct's `name`. The list includes
unavailable entries in that category and is empty when none match.

## Raises

Raises `FunctionClauseError` when `category` is not an atom. May raise
`ArgumentError` if the registry ETS table does not exist.

## Example

    iex> codecs = ExCodecs.CodecRegistry.codecs_by_category(:compression)
    iex> Enum.all?(codecs, &match?(%ExCodecs.Codec{category: :compression}, &1))
    true
    iex> Enum.map(codecs, & &1.name) == Enum.sort(Enum.map(codecs, & &1.name))
    true

# `lookup`

```elixir
@spec lookup(atom()) ::
  {:ok, {module() | nil, atom(), ExCodecs.Codec.t()}}
  | {:error, :unsupported_codec}
```

Looks up `{module, category, info}` for `name`.

## Arguments

  * `name` (`atom()`) — codec registry key

## Returns

  * `{:ok, {module() | nil, atom(), ExCodecs.Codec.t()}}` — implementation,
    category, and metadata for a registered name
  * `{:error, :unsupported_codec}` — no entry exists for `name`

## Raises

Raises `FunctionClauseError` when `name` is not an atom. May raise
`ArgumentError` if the registry ETS table does not exist.

## Example

    iex> {:ok, {module, :compression, info}} = ExCodecs.CodecRegistry.lookup(:zstd)
    iex> {module, info.name}
    {ExCodecs.Compression.Zstd, :zstd}

# `register`

```elixir
@spec register(atom(), module(), atom()) :: :ok | {:error, term()}
```

Registers a binary-interface module that exports `encode/2` and `decode/2`.

## Arguments

  * `name` (`atom()`) — registry key used for lookups
  * `module` (`module()`) — loaded module exporting `encode/2` and `decode/2`
  * `category` (`atom()`) — grouping key, such as `:compression`

## Returns

  * `:ok` — metadata was stored, replacing any entry with the same `name`
  * `{:error, {:invalid_codec_module, module}}` — the module cannot be loaded
    or does not export both codec callbacks

## Raises

May raise `ArgumentError` if the registry ETS table does not exist. A codec's
optional public `__codec_info__/0` function is called during registration and
any exception it raises propagates.

## Examples

    iex> :ok = ExCodecs.CodecRegistry.register(
    ...>   :zstd,
    ...>   ExCodecs.Compression.Zstd,
    ...>   :compression
    ...> )
    iex> ExCodecs.CodecRegistry.supports?(:zstd)
    true

# `register`

```elixir
@spec register(atom(), module(), atom(), :binary | :spatial) :: :ok | {:error, term()}
```

Registers a module in the shared catalog with an explicit interface.

`:binary` entries are dispatched by `ExCodecs.encode/3` / `decode/3`.
`:spatial` entries are discoverable from the same catalog but dispatched by
`ExCodecs.Spatial`.

## Arguments

  * `name` — catalog key
  * `module` — module exporting `encode/2` and `decode/2`
  * `category` — grouping atom such as `:compression` or `:spatial`
  * `interface` — `:binary` or `:spatial`

## Returns

`:ok` on registration or `{:error, {:invalid_codec_module, module}}`.

## Raises

May raise `ArgumentError` if the registry table has not started, or propagate
an exception from a module's optional `__codec_info__/0`.

## Examples

    iex> :ok = ExCodecs.CodecRegistry.register(
    ...>   :documented_ply,
    ...>   ExCodecs.Spatial.Codec.PLY,
    ...>   :spatial,
    ...>   :spatial
    ...> )
    iex> {:ok, info} = ExCodecs.CodecRegistry.codec_info(:documented_ply)
    iex> {info.category, info.interface}
    {:spatial, :spatial}
    iex> ExCodecs.CodecRegistry.unregister(:documented_ply)
    :ok

# `register`

```elixir
@spec register(atom(), module(), atom(), :binary | :spatial, keyword()) ::
  :ok | {:error, term()}
```

Registers a module with an explicit interface and optional metadata overrides.

Builds the stored `%ExCodecs.Codec{}` from `name`, `module`, `category`, and
`interface`, then merges capability fields from the module's optional
`c:ExCodecs.Codec.__codec_info__/0` (when exported). Each key in `metadata`
overrides the corresponding field from `__codec_info__/0`.

## Arguments

  * `name` (`atom()`) — catalog key (always stored; not taken from metadata)
  * `module` (`module()`) — module exporting `encode/2` and `decode/2`
  * `category` (`atom()`) — grouping atom such as `:compression` or `:spatial`
  * `interface` (`:binary | :spatial`) — public API shape for the entry
  * `metadata` (`keyword()`) — optional overrides for capability fields:

    * `:native?` (`boolean()`) — NIF-backed implementation
    * `:streaming?` (`boolean()`) — incremental API available
    * `:configurable?` (`boolean()`) — meaningful codec-specific options
    * `:version` (`String.t() | nil`) — backend version string

    Unknown keys are ignored. Omitted keys keep the value from
    `__codec_info__/0`, or `nil` when that function is not exported.

## Returns

  * `:ok` — metadata was stored, replacing any entry with the same `name`
  * `{:error, {:invalid_codec_module, module}}` — the module cannot be loaded
    or does not export both codec callbacks

## Raises

May raise `ArgumentError` if the registry ETS table does not exist. A codec's
optional `__codec_info__/0` is called during registration and any exception
it raises propagates.

## Persistence

This entry is stored in an ETS table owned by the registry Agent. If the
Agent crashes, runtime registrations are **not replayed** — only the
`:register` callback supplied at `start_link/1` runs on restart. Re-register
third-party codecs from your own supervision tree if you need crash
survivability.

## Examples

    iex> :ok = ExCodecs.CodecRegistry.register(
    ...>   :documented_spatial_binary,
    ...>   ExCodecs.Spatial.Codec.Binary,
    ...>   :spatial,
    ...>   :spatial,
    ...>   native?: false,
    ...>   streaming?: false,
    ...>   configurable?: false,
    ...>   version: "EXCP 1"
    ...> )
    iex> {:ok, info} = ExCodecs.CodecRegistry.codec_info(:documented_spatial_binary)
    iex> {info.interface, info.version, info.native?}
    {:spatial, "EXCP 1", false}
    iex> ExCodecs.CodecRegistry.unregister(:documented_spatial_binary)
    :ok

# `register_unavailable`

```elixir
@spec register_unavailable(atom(), atom()) :: :ok
```

Registers a known codec with `module: nil` (unavailable).

## Arguments

  * `name` (`atom()`) — known codec's registry key
  * `category` (`atom()`) — grouping key, such as `:compression`

## Returns

Always `:ok` after storing a `%ExCodecs.Codec{module: nil}` entry.

## Raises

May raise `ArgumentError` if the registry ETS table does not exist.

## Example

    iex> :ok = ExCodecs.CodecRegistry.register_unavailable(:example_optional, :compression)
    iex> {:ok, info} = ExCodecs.CodecRegistry.codec_info(:example_optional)
    iex> {info.module, ExCodecs.CodecRegistry.supports?(:example_optional)}
    {nil, false}
    iex> ExCodecs.CodecRegistry.unregister(:example_optional)
    :ok

# `register_unavailable`

```elixir
@spec register_unavailable(atom(), atom(), :binary | :spatial) :: :ok
```

Registers a known codec as unavailable with an explicit interface.

Stores `%ExCodecs.Codec{module: nil}` with capability fields set to `false`
and `version: nil`. Use when a codec name is part of the catalog but its
implementation module cannot be loaded (for example a NIF-backed codec when
the native library failed to load).

## Arguments

  * `name` (`atom()`) — known codec's registry key
  * `category` (`atom()`) — grouping key, such as `:compression` or `:spatial`
  * `interface` (`:binary | :spatial`) — public API shape for the entry

## Returns

Always `:ok` after storing the unavailable entry.

## Raises

May raise `ArgumentError` if the registry ETS table does not exist.

## Example

    iex> :ok = ExCodecs.CodecRegistry.register_unavailable(
    ...>   :example_spatial_optional,
    ...>   :spatial,
    ...>   :spatial
    ...> )
    iex> {:ok, info} = ExCodecs.CodecRegistry.codec_info(:example_spatial_optional)
    iex> {info.module, info.interface, ExCodecs.CodecRegistry.supports?(:example_spatial_optional)}
    {nil, :spatial, false}
    iex> ExCodecs.CodecRegistry.unregister(:example_spatial_optional)
    :ok

# `start_link`

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

Starts the registry Agent and optional re-registration callback.

## Arguments

  * `opts` (`keyword()`) — accepts `:register`, a zero-arity function invoked
    after the named ETS table is created or cleared; it defaults to
    `fn -> :ok end`

## Returns

  * `{:ok, pid()}` — the named registry Agent started and initialization ran
  * `{:error, {:already_started, pid()}}` — the registry is already running
  * `{:error, reason}` — Agent startup or the registration callback failed

## Raises

With a valid keyword list, startup failures are returned by `Agent`. Passing
a non-keyword value can raise `FunctionClauseError` while reading options.

## Example

A supervision tree normally starts the registry and supplies the callback:

    children = [
      {ExCodecs.CodecRegistry, register: &MyApp.Codecs.register_all/0}
    ]

    Supervisor.start_link(children, strategy: :one_for_one)

# `supports?`

```elixir
@spec supports?(atom()) :: boolean()
```

Returns `true` if registered and `module != nil`.

## Arguments

  * `name` (`atom()`) — registry key to test

## Returns

`true` only when `name` is registered with a non-`nil` module; otherwise
`false`.

## Raises

Raises `FunctionClauseError` when `name` is not an atom. May raise
`ArgumentError` if the registry ETS table does not exist.

## Example

    iex> ExCodecs.CodecRegistry.supports?(:zstd)
    true
    iex> ExCodecs.CodecRegistry.supports?(:unknown_codec)
    false

# `unregister`

```elixir
@spec unregister(atom()) :: :ok
```

Deletes a registry entry (tests / hot reload).

## Arguments

  * `name` (`atom()`) — registry key to remove

## Returns

Always `:ok`, whether or not an entry existed.

## Raises

Raises `FunctionClauseError` when `name` is not an atom. May raise
`ArgumentError` if the registry ETS table does not exist.

## Example

    iex> :ok = ExCodecs.CodecRegistry.register_unavailable(:temporary_codec, :compression)
    iex> :ok = ExCodecs.CodecRegistry.unregister(:temporary_codec)
    iex> ExCodecs.CodecRegistry.lookup(:temporary_codec)
    {:error, :unsupported_codec}

---

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