# `LLMDB.Merge`
[🔗](https://github.com/agentjido/llm_db/blob/main/lib/llm_db/engine/merge.ex#L1)

Precedence-aware merging with exclude handling for LLM model data.

Provides functions to merge providers, models, and arbitrary maps with
configurable precedence rules. Handles excludes via exact match or glob patterns.

# `continue_deep_merge`

```elixir
@type continue_deep_merge() :: {module(), :continue_deep_merge}
```

# `resolver3`

```elixir
@type resolver3() :: (any(), any(), any() -&gt; any())
```

# `compile_excludes`

```elixir
@spec compile_excludes(map()) :: map()
```

Compiles exclude patterns to regex for performance.

Converts a map of %{provider => [patterns]} to %{provider => [compiled_patterns]}
where each pattern is either kept as a string (for exact match) or compiled to regex (for globs).

## Examples

    iex> result = LLMDB.Merge.compile_excludes(%{openai: ["gpt-3", "gpt-5-*"]})
    iex> [exact, pattern] = result.openai
    iex> exact
    "gpt-3"
    iex> Regex.match?(pattern, "gpt-5-pro")
    true

# `compile_pattern`

```elixir
@spec compile_pattern(String.t()) :: Regex.t()
```

Converts a glob pattern to an anchored regex.

- "*" becomes ".*"
- Escape other regex special chars
- Anchor with ^ and $

## Examples

    iex> pattern = LLMDB.Merge.compile_pattern("gpt-*")
    iex> Regex.match?(pattern, "gpt-4")
    true

    iex> pattern = LLMDB.Merge.compile_pattern("gpt-5-*-mini")
    iex> Regex.match?(pattern, "gpt-5-turbo-mini")
    true

# `deep_merge`

```elixir
@spec deep_merge(any(), any(), resolver3()) :: any()
```

Deep merges two maps with conflict resolution delegated to a 3-arity resolver.

The resolver receives the current key, left value, and right value. Returning
`continue_deep_merge()` for a map conflict recursively merges the nested maps.

# `matches_exclude?`

```elixir
@spec matches_exclude?(String.t() | nil, [String.t() | Regex.t()]) :: boolean()
```

Checks if a model_id matches any exclude pattern.

Patterns can be exact strings or compiled regexes.

## Examples

    iex> LLMDB.Merge.matches_exclude?("gpt-4", ["gpt-3", "gpt-5"])
    false

    iex> LLMDB.Merge.matches_exclude?("gpt-3", ["gpt-3", "gpt-5"])
    true

    iex> LLMDB.Merge.matches_exclude?("gpt-5-pro", [~r/^gpt-5-.*$/])
    true

    iex> LLMDB.Merge.matches_exclude?("gpt-4", [~r/^gpt-5-.*$/])
    false

# `merge`

```elixir
@spec merge(map(), map(), :higher | :lower) :: map()
```

Merges two maps with precedence rules.

- Scalar values: higher precedence wins
- Maps: deep merge recursively
- Lists: concat and de-dup by value
- Higher precedence source always wins on scalars

## Examples

    iex> LLMDB.Merge.merge(%{a: 1}, %{b: 2}, :higher)
    %{a: 1, b: 2}

    iex> LLMDB.Merge.merge(%{a: 1}, %{a: 2}, :higher)
    %{a: 2}

    iex> LLMDB.Merge.merge(%{a: 1}, %{a: 2}, :lower)
    %{a: 1}

    iex> LLMDB.Merge.merge(%{a: %{b: 1}}, %{a: %{c: 2}}, :higher)
    %{a: %{b: 1, c: 2}}

    iex> LLMDB.Merge.merge(%{a: [1, 2]}, %{a: [2, 3]}, :higher)
    %{a: [1, 2, 3]}

# `merge_list_by_id`

```elixir
@spec merge_list_by_id([map()], [map()], atom() | String.t()) :: [map()]
```

Merges two lists of maps by a shared ID key.

Keeps the base list order, overrides items with matching IDs from the override list,
and appends override-only items in their original order.

Used by `LLMDB.Pricing` to merge pricing components from provider defaults
with model-specific overrides.

## Parameters

- `base_list` - The base list of maps (order preserved)
- `override_list` - Maps that override or extend the base list
- `id_key` - The key to match on (default: `:id`). Supports both atom and string keys.

## Examples

    # Override matching items, preserve order
    iex> base = [%{id: "a", value: 1}, %{id: "b", value: 2}]
    iex> override = [%{id: "b", value: 20}]
    iex> LLMDB.Merge.merge_list_by_id(base, override)
    [%{id: "a", value: 1}, %{id: "b", value: 20}]

    # Append new items from override
    iex> base = [%{id: "a", value: 1}]
    iex> override = [%{id: "b", value: 2}, %{id: "c", value: 3}]
    iex> LLMDB.Merge.merge_list_by_id(base, override)
    [%{id: "a", value: 1}, %{id: "b", value: 2}, %{id: "c", value: 3}]

    # Pricing component merge example
    iex> defaults = [%{id: "tool.search", rate: 10.0}, %{id: "tool.code", rate: 5.0}]
    iex> overrides = [%{id: "tool.search", rate: 0.0}]  # Free search
    iex> LLMDB.Merge.merge_list_by_id(defaults, overrides)
    [%{id: "tool.search", rate: 0.0}, %{id: "tool.code", rate: 5.0}]

# `merge_models`

```elixir
@spec merge_models([map()], [map()], map()) :: [map()]
```

Merges two model lists by {provider, id} identity, applying excludes.

- Merge models by {provider, id} identity
- Apply excludes: %{provider_atom => [patterns]} where patterns can be exact strings or globs with *
- Compile glob patterns to regex once for performance
- Higher precedence (override) wins on conflicts

## Examples

    iex> base = [%{id: "gpt-4", provider: :openai}]
    iex> override = [%{id: "gpt-4", provider: :openai, capabilities: %{tools: true}}]
    iex> LLMDB.Merge.merge_models(base, override, %{})
    [%{id: "gpt-4", provider: :openai, capabilities: %{tools: true}}]

    iex> base = [%{id: "gpt-4", provider: :openai}, %{id: "gpt-3", provider: :openai}]
    iex> excludes = %{openai: ["gpt-3"]}
    iex> LLMDB.Merge.merge_models(base, [], excludes)
    [%{id: "gpt-4", provider: :openai}]

    iex> base = [%{id: "gpt-4o-mini", provider: :openai}, %{id: "gpt-5-pro", provider: :openai}]
    iex> excludes = %{openai: ["gpt-5-*"]}
    iex> LLMDB.Merge.merge_models(base, [], excludes)
    [%{id: "gpt-4o-mini", provider: :openai}]

# `merge_providers`

```elixir
@spec merge_providers([map()], [map()]) :: [map()]
```

Merges two provider lists by :id key.

Higher precedence (override) wins on conflicts.

## Examples

    iex> base = [%{id: :openai, name: "OpenAI"}]
    iex> override = [%{id: :openai, name: "OpenAI Updated"}, %{id: :anthropic, name: "Anthropic"}]
    iex> result = LLMDB.Merge.merge_providers(base, override)
    iex> Enum.sort_by(result, & &1.id)
    [%{id: :anthropic, name: "Anthropic"}, %{id: :openai, name: "OpenAI Updated"}]

# `resolver`

```elixir
@spec resolver(keyword()) :: resolver3()
```

Creates a configurable deep merge resolver function.

Returns a 3-arity function that can be passed to `deep_merge/3`
with customizable behavior for list and special key handling.

## Options

- `:union_list_keys` - List of keys whose list values should be unioned (default: [])
- `:preserve_empty_list_keys` - List of keys where empty list on right preserves left (default: [])

## Examples

    # Model merging with list unions
    resolver = Merge.resolver(union_list_keys: [:aliases, :tags])
    Merge.deep_merge(base, override, resolver)

    # Provider merging preserving exclude_models
    resolver = Merge.resolver(preserve_empty_list_keys: [:exclude_models])
    Merge.deep_merge(base, override, resolver)

---

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