# `Zvex.Document`
[🔗](https://github.com/edlontech/zvex/blob/main/lib/zvex/document.ex#L1)

Pure Elixir document struct with type-tagged fields for zvec collections.

Fields are stored as `%{"field_name" => {type_atom, value}}` to preserve
type information through NIF marshaling boundaries.

## Example

    alias Zvex.Document
    alias Zvex.Vector

    doc =
      Document.new()
      |> Document.put_pk("doc-1")
      |> Document.put("title", "Hello world")
      |> Document.put("embedding", Vector.from_list([1.0, 2.0, 3.0], :fp32))

    Document.to_map(doc)
    #=> %{"title" => "Hello world", "embedding" => <<...>>}

# `t`

```elixir
@type t() :: %Zvex.Document{
  fields: %{required(String.t()) =&gt; {atom(), term()}},
  pk: String.t() | nil
}
```

A typed document for insertion into a zvec collection.

- `:fields` — map of `"field_name" => {type_atom, value}` tuples preserving
  type information through NIF boundaries.
- `:pk` — the primary key value (a string), or `nil` if not yet set.

# `clear`

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

Returns a new empty document, discarding all fields and the primary key.

# `deserialize`

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

Deserializes a binary produced by `serialize/1` back into a document.

# `deserialize!`

```elixir
@spec deserialize!(binary()) :: t()
```

Like `deserialize/1` but raises on error.

# `detail_string`

```elixir
@spec detail_string(t()) :: {:ok, String.t()} | {:error, Zvex.Error.t()}
```

Returns a human-readable string describing the document's fields and types.

# `detail_string!`

```elixir
@spec detail_string!(t()) :: String.t()
```

Like `detail_string/1` but raises on error.

# `empty?`

```elixir
@spec empty?(t()) :: boolean()
```

Returns `true` if the document has no fields and no primary key set.

# `field_null?`

```elixir
@spec field_null?(t(), String.t()) :: boolean()
```

Returns `true` if the field exists and its value is `nil`.

# `fields`

```elixir
@spec fields(t()) :: [String.t()]
```

Returns the list of field names present in the document.

# `from_map`

```elixir
@spec from_map(map(), Zvex.Collection.Schema.t()) :: t()
```

Builds a document from a plain map using the schema for type resolution.

Keys in `map` that don't match a schema field are silently ignored.
The primary key field is used to set both the `:pk` and the field entry.
Vector values can be given as plain lists — they are automatically packed
into the schema's vector type.

# `from_native_map`

```elixir
@spec from_native_map(map()) :: t()
```

Reconstructs a document from the internal NIF map format.

# `has_field?`

```elixir
@spec has_field?(t(), String.t()) :: boolean()
```

Returns `true` if the document contains a field named `field`.

# `memory_usage`

```elixir
@spec memory_usage(t()) :: {:ok, non_neg_integer()} | {:error, Zvex.Error.t()}
```

Returns the estimated memory usage of the document in bytes.

# `memory_usage!`

```elixir
@spec memory_usage!(t()) :: non_neg_integer()
```

Like `memory_usage/1` but raises on error.

# `merge`

```elixir
@spec merge(t(), t()) :: t()
```

Merges two documents. Fields from `doc2` overwrite those in `doc1`.
The primary key is taken from `doc2` if set, otherwise from `doc1`.

# `new`

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

Creates an empty document with no fields and no primary key.

# `put`

```elixir
@spec put(t(), String.t(), term()) :: t()
```

Sets a field value on the document.

The type is inferred automatically from the value:

- `Zvex.Vector` — uses the vector's type
- `boolean` — `:bool`
- `binary/string` — `:string`
- `integer` — `:int64`
- `float` — `:double`

Use the 4-arity `put/4` to specify an explicit type atom.

# `put`

```elixir
@spec put(t(), String.t(), term(), atom()) :: t()
```

Sets a field with an explicit `type` atom, bypassing automatic type inference.

# `put_null`

```elixir
@spec put_null(t(), String.t()) :: t()
```

Sets a field to null (type `:null`, value `nil`).

# `put_pk`

```elixir
@spec put_pk(t(), String.t()) :: t()
```

Sets the primary key for this document. Must be a string.

# `remove_field`

```elixir
@spec remove_field(t(), String.t()) :: t()
```

Removes a field from the document by name. No-op if the field does not exist.

# `serialize`

```elixir
@spec serialize(t()) :: {:ok, binary()} | {:error, Zvex.Error.t()}
```

Serializes the document to a compact binary representation via the native layer.

# `serialize!`

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

Like `serialize/1` but raises on error.

# `to_map`

```elixir
@spec to_map(t()) :: map()
```

Converts the document to a plain map, stripping type information from field values.

# `to_native_map`

```elixir
@spec to_native_map(t()) :: map()
```

Converts the document to the internal map format expected by the NIF layer.

# `to_native_maps`

```elixir
@spec to_native_maps(t() | [t()]) :: [map()]
```

Converts one or more documents to a list of NIF-ready maps.

# `validate`

```elixir
@spec validate(t(), Zvex.Collection.Schema.t()) :: :ok | {:error, Zvex.Error.t()}
```

Validates the document against a schema.

Checks that the primary key is set (if required), all non-nullable fields
are present, field types match, and vector dimensions are correct.

---

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