# `Yog.Builder.Live`
[🔗](https://github.com/code-shoily/yog_ex/blob/v0.98.5/lib/yog/builder/live.ex#L1)

A live builder for incremental graph construction with label-to-ID registry.

Unlike the static `Yog.Builder.Labeled` which follows a "Build-Freeze-Analyze" pattern,
`Live` provides a **Transaction-style API** that tracks pending changes.
This allows efficient synchronization of an existing `Graph` with new labeled edges
in O(ΔE) time, where ΔE is the number of new edges since last sync.

## Use Cases

- **REPL environments**: Incrementally build and analyze graphs
- **UI editors**: Add nodes/edges interactively without rebuilding
- **Streaming data**: Ingest new relationships as they arrive
- **Large graphs**: Avoid O(E) rebuild for single-edge updates

## Guarantees

- **ID Stability:** Once a label is mapped to a `NodeId`, that mapping is immutable
- **Idempotency:** Calling `sync/2` with no pending changes is effectively free
- **Opaque Integration:** Uses the same ID generation as static builders

## Important: Managing the Pending Queue

The `Live` builder queues changes in memory until `sync/2` is called. In streaming
scenarios, if you add edges continuously without syncing, the pending queue will
grow unbounded and consume memory.

**Best Practice:** Sync periodically based on your workload:

    # For high-frequency streaming (e.g., Kafka consumer)
    # Sync every N messages or every T seconds
    {builder, graph} =
      if Yog.Builder.Live.pending_count(builder) > 1000 do
        Yog.Builder.Live.sync(builder, graph)
      else
        {builder, graph}
      end

    # For batch processing
    # Build up a batch, then sync once
    builder = Enum.reduce(batch, builder, fn {from, to, weight}, b ->
      Yog.Builder.Live.add_edge(b, from, to, weight)
    end)
    {builder, graph} = Yog.Builder.Live.sync(builder, graph)

## Recovery

If you need to discard pending changes without applying them:
- Use `purge_pending/1` to abandon changes
- Use `checkpoint/1` to keep registry but clear pending

## Multigraphs

The same builder can sync to multigraphs (`Yog.Multi.Graph`) via `sync_multi/2`.
All operations (add/remove) work identically; the only difference is that
`add_edge` creates parallel edges rather than replacing existing ones.

    builder = Yog.Builder.Live.new() |> Yog.Builder.Live.add_edge("A", "B", 10)
    {builder, multi} = Yog.Builder.Live.sync_multi(builder, Yog.Multi.directed())

    # Add a parallel edge
    builder = Yog.Builder.Live.add_edge(builder, "A", "B", 20)
    {_builder, multi} = Yog.Builder.Live.sync_multi(builder, multi)
    # multi now has TWO edges between A and B

## Limitations

- **Memory:** Pending changes are stored in memory until synced
- **No Persistence:** The pending queue is lost if the process crashes
- **Single-threaded:** Not designed for concurrent updates from multiple actors

## Example Usage

    # Initial setup - build base graph
    builder = Yog.Builder.Live.new() |> Yog.Builder.Live.add_edge("A", "B", 10)
    {builder, graph} = Yog.Builder.Live.sync(builder, Yog.directed())

    # Incremental update - add new edge efficiently
    builder = Yog.Builder.Live.add_edge(builder, "B", "C", 5)
    {builder, graph} = Yog.Builder.Live.sync(builder, graph)  # O(1) for just this edge!

    # Use with algorithms - get IDs from registry
    {:ok, a_id} = Yog.Builder.Live.get_id(builder, "A")
    {:ok, c_id} = Yog.Builder.Live.get_id(builder, "C")

# `label`

```elixir
@type label() :: term()
```

Any type can be used as a label

# `t`

```elixir
@type t() :: %Yog.Builder.Live{
  next_id: integer(),
  pending: [transition()],
  registry: %{required(label()) =&gt; Yog.node_id()}
}
```

Live builder struct

# `transition`

```elixir
@type transition() ::
  {:add_node, Yog.node_id(), label()}
  | {:add_edge, Yog.node_id(), Yog.node_id(), term()}
  | {:remove_edge, Yog.node_id(), Yog.node_id()}
  | {:remove_node, Yog.node_id()}
```

A pending transition

# `add_edge`

```elixir
@spec add_edge(t(), label(), label(), term()) :: t()
```

Adds an edge between two labeled nodes with a weight.

The change is queued until `sync/2` is called.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    iex> Yog.Builder.Live.pending_count(builder) > 0
    true

# `add_node`

```elixir
@spec add_node(t(), label()) :: t()
```

Adds a node with the given label.

The change is queued until `sync/2` is called.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_node("A")
    iex> Yog.Builder.Live.all_labels(builder)
    ["A"]

# `add_simple_edge`

```elixir
@spec add_simple_edge(t(), label(), label()) :: t()
```

Adds a simple edge with weight 1 between two labeled nodes.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_simple_edge("A", "B")
    iex> is_struct(builder, Yog.Builder.Live)
    true

# `add_unweighted_edge`

```elixir
@spec add_unweighted_edge(t(), label(), label()) :: t()
```

Adds an unweighted edge (weight = nil) between two labeled nodes.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_unweighted_edge("A", "B")
    iex> is_struct(builder, Yog.Builder.Live)
    true

# `all_labels`

```elixir
@spec all_labels(t()) :: [label()]
```

Returns all labels that have been registered.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    iex> labels = Yog.Builder.Live.all_labels(builder)
    iex> Enum.sort(labels)
    ["A", "B"]

# `checkpoint`

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

Creates a checkpoint by clearing pending changes while preserving the registry.

Similar to `purge_pending/1` but conceptually marks a save point.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    iex> builder = Yog.Builder.Live.checkpoint(builder)
    iex> Yog.Builder.Live.pending_count(builder)
    0

# `directed`

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

Creates a new live builder for directed graphs.

## Examples

    iex> builder = Yog.Builder.Live.directed()
    iex> is_struct(builder, Yog.Builder.Live)
    true

# `from_labeled`

```elixir
@spec from_labeled(Yog.Builder.Labeled.t()) :: t()
```

Creates a live builder from an existing labeled builder.

This is useful for transitioning from static to incremental building.

## Examples

    iex> labeled = Yog.Builder.Labeled.directed()
    ...> |> Yog.Builder.Labeled.add_edge("A", "B", 5)
    iex> builder = Yog.Builder.Live.from_labeled(labeled)
    iex> is_struct(builder, Yog.Builder.Live)
    true

# `get_id`

```elixir
@spec get_id(t(), label()) :: {:ok, Yog.node_id()} | {:error, nil}
```

Looks up the internal node ID for a given label.

Returns `{:ok, id}` if the label exists in the registry,
`{:error, nil}` otherwise.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    ...> |> Yog.Builder.Live.sync(Yog.directed())
    ...> |> elem(0)
    iex> Yog.Builder.Live.get_id(builder, "A")
    {:ok, 0}

# `get_label`

```elixir
@spec get_label(t(), Yog.node_id()) :: {:ok, label()} | {:error, nil}
```

Looks up the label for a given internal node ID.

Returns `{:ok, label}` if the ID exists in the registry,
`{:error, nil}` otherwise.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_node("A")
    iex> {:ok, id} = Yog.Builder.Live.get_id(builder, "A")
    iex> Yog.Builder.Live.get_label(builder, id)
    {:ok, "A"}

    iex> builder = Yog.Builder.Live.new()
    iex> Yog.Builder.Live.get_label(builder, 999)
    {:error, nil}

# `has_label?`

```elixir
@spec has_label?(t(), label()) :: boolean()
```

Checks if a label has been registered in the builder.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    iex> Yog.Builder.Live.has_label?(builder, "A")
    true
    iex> Yog.Builder.Live.has_label?(builder, "Z")
    false

# `new`

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

Creates a new live builder with the specified graph type.

## Examples

    iex> builder = Yog.Builder.Live.new()
    iex> is_struct(builder, Yog.Builder.Live)
    true

# `node_count`

```elixir
@spec node_count(t()) :: integer()
```

Returns the number of registered nodes.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    iex> Yog.Builder.Live.node_count(builder)
    2

# `pending_count`

```elixir
@spec pending_count(t()) :: integer()
```

Returns the number of pending changes.

Use this to monitor queue growth and trigger syncs when needed.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    iex> Yog.Builder.Live.pending_count(builder) > 0
    true

# `purge_pending`

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

Discards all pending changes without applying them.

The registry (label-to-ID mappings) is preserved.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    iex> builder = Yog.Builder.Live.purge_pending(builder)
    iex> Yog.Builder.Live.pending_count(builder)
    0

# `remove_edge`

```elixir
@spec remove_edge(t(), label(), label()) :: t()
```

Removes an edge between two labeled nodes.

The change is queued until `sync/2` is called.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    ...> |> Yog.Builder.Live.remove_edge("A", "B")
    iex> is_struct(builder, Yog.Builder.Live)
    true

# `remove_node`

```elixir
@spec remove_node(t(), label()) :: t()
```

Removes a node by its label.

Also removes all edges connected to this node.
The change is queued until `sync/2` is called.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    ...> |> Yog.Builder.Live.remove_node("A")
    iex> is_struct(builder, Yog.Builder.Live)
    true

# `sync`

```elixir
@spec sync(t(), Yog.graph()) :: {t(), Yog.graph()}
```

Applies all pending changes to a simple graph.

Returns `{builder, updated_graph}` where the builder has cleared its pending queue.
This is an O(ΔE) operation where ΔE is the number of pending edges.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    iex> {_builder, graph} = Yog.Builder.Live.sync(builder, Yog.directed())
    iex> length(Yog.all_nodes(graph))
    2

# `sync_multi`

```elixir
@spec sync_multi(t(), Yog.Multi.Graph.t()) :: {t(), Yog.Multi.Graph.t()}
```

Applies all pending changes to a multigraph.

Returns `{builder, updated_multigraph}` where the builder has cleared its pending
queue. Unlike `sync/2`, this creates parallel edges when the same pair of nodes
is connected multiple times.

## Examples

    iex> builder = Yog.Builder.Live.new()
    ...> |> Yog.Builder.Live.add_edge("A", "B", 10)
    ...> |> Yog.Builder.Live.add_edge("A", "B", 20)
    iex> {_builder, multi} = Yog.Builder.Live.sync_multi(builder, Yog.Multi.directed())
    iex> Yog.Multi.size(multi)
    2

# `undirected`

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

Creates a new live builder for undirected graphs.

## Examples

    iex> builder = Yog.Builder.Live.undirected()
    iex> is_struct(builder, Yog.Builder.Live)
    true

---

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