# `Milvex.Migration.Plan`

Pure diff between a `Milvex.Collection` DSL module and the live Milvus state.

`diff/4` produces a `%Plan{}` carrying a list of `%Milvex.Migration.Operation{}`
describing every change required to bring the live collection in line with the
DSL. The function is pure: no RPCs, no side effects.

Live state shape:

    %{
      schema: %Milvex.Schema{},
      indexes: [%Milvex.Milvus.Proto.Milvus.IndexDescription{}],
      collection_props: keyword()
    }

Pass `nil` when the collection does not exist; the plan will then contain a
`:create_collection` followed by one `:create_index` per entry in
`module.index_config/0` (if exported).

# `live_state`

```elixir
@type live_state() ::
  nil
  | %{
      :schema =&gt; Milvex.Schema.t(),
      optional(:indexes) =&gt; [Milvex.Milvus.Proto.Milvus.IndexDescription.t()],
      optional(:collection_props) =&gt; keyword()
    }
```

# `t`

```elixir
@type t() :: %Milvex.Migration.Plan{
  collection_name: String.t(),
  milvus_version: String.t(),
  module: module(),
  operations: [Milvex.Migration.Operation.t()],
  prefix: String.t() | nil
}
```

# `diff`

```elixir
@spec diff(module(), String.t() | nil, live_state(), String.t()) :: t()
```

Computes the diff for one (module, prefix) tuple against the live state on the
given Milvus version.

# `field_diff`

```elixir
@spec field_diff(Milvex.Schema.t(), Milvex.Schema.t()) :: [
  Milvex.Migration.Operation.t()
]
```

Field-only diff between two `%Schema{}` values.

Returns the same `%Operation{}` shapes as `diff/4` but limited to field
add/drop/alter changes. Description-only changes and function/index/KV
differences are ignored.

Used by `Milvex.Schema.Migration` to project structural field differences
into the legacy `{missing, extra, mismatches}` shape without spinning up a
full DSL module diff.

# `index_summary`

```elixir
@spec index_summary(
  Milvex.Index.t()
  | Milvex.Milvus.Proto.Milvus.IndexDescription.t()
  | map()
) :: map()
```

Returns a canonical, JSON-friendly summary of an index for use in
`:recreate_index` payloads.

Both the DSL (`%Index{}`) and the live (`%IndexDescription{}`) inputs are
normalised to the same shape:

    %{
      index_type: "HNSW",
      metric_type: "COSINE",
      params: %{M: 16, efConstruction: 256}
    }

- `index_type` and `metric_type` are upper-case strings (or `nil`).
- Structural integer params (`M`, `efConstruction`, `nlist`, `m`, `nbits`)
  are always atom-keyed and integer-typed regardless of source.
- On the live side, unknown params are dropped; the structural ones are
  parsed via `String.to_integer/1`.

---

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