# In-memory LiveMap

> **CocoIndex v1, Rust SDK (experimental).** This page documents the CocoIndex **v1** Rust SDK (the `cocoindex` crate). The Rust SDK is experimental: any release may change or remove its API, and it is not yet published on crates.io. The Python SDK pages are at https://cocoindex.io/docs/.
>
> Source: https://cocoindex.io/docs/rust/common_resources/live_map/ · Rust docs index: https://cocoindex.io/docs/rust/llms.txt · Rust SDK tour: https://github.com/cocoindex-io/cocoindex/blob/main/rust/sdk/SHOWCASE.md · Rust examples: https://github.com/cocoindex-io/cocoindex/tree/main/examples/rust

`LiveMap<K, V>` (`cocoindex::resources::live_map`, also `cocoindex::LiveMap`) is an in-memory, keyed collection that sits **between** a producing part of your pipeline and a consuming part. The producing side **declares** `(key, value)` entries; the consuming side reads the map as a [`LiveMapView`](/docs/rust/advanced_topics/live_component/#livemapfeed-and-livemapview) via `ctx.mount_each_live`, getting one [processing component](/docs/rust/programming_guide/processing_component/) per entry that stays in sync as entries appear, change, and disappear.

Think of it as a connector whose "external system" is an in-process map: entries are declared as [target states](/docs/rust/programming_guide/target_state/) (so they participate in CocoIndex's declarative change detection and ownership) and that same map is simultaneously exposed as a live source for downstream components. It lets you split a pipeline into a producer half and a consumer half that are decoupled through one shared, incrementally-maintained collection.

It is designed for [live mode](/docs/rust/programming_guide/live_mode/): the producer and consumer run concurrently, and the consumer reacts as the producer updates the map.

## Example

```rust
use std::sync::{Arc, Mutex};

use cocoindex::{Ctx, LiveComponent, LiveComponentOperator, LiveMap, Result, async_trait};
use tokio::sync::Notify;

/// Declares every entry of `source`; re-runs its full pass on `trigger`.
struct Producer {
    lm: LiveMap<String, String>,
    source: Arc<Mutex<Vec<(String, String)>>>,
    trigger: Arc<Notify>,
}

#[async_trait]
impl LiveComponent for Producer {
    async fn process(&self, ctx: Ctx) -> Result<()> {
        let entries = self.source.lock().unwrap().clone();
        for (key, value) in entries {
            self.lm.declare_entry(&ctx, key, value)?;
        }
        Ok(())
    }

    async fn process_live(&self, operator: LiveComponentOperator) -> Result<()> {
        operator.update_full().await?;
        operator.mark_ready().await;
        loop {
            self.trigger.notified().await;
            operator.update_full().await?;
        }
    }
}

/// Consumes the map: one child component per entry.
struct Consumer {
    lm: LiveMap<String, String>,
    seen: Arc<Mutex<Vec<String>>>,
}

#[async_trait]
impl LiveComponent for Consumer {
    async fn process(&self, ctx: Ctx) -> Result<()> {
        let seen = self.seen.clone();
        ctx.mount_each_live(&"each", self.lm.clone(), move |_ctx, value: String| {
            let seen = seen.clone();
            async move {
                seen.lock().unwrap().push(value);
                Ok(())
            }
        })
        .await
    }

    async fn process_live(&self, operator: LiveComponentOperator) -> Result<()> {
        operator.update_full().await?;
        operator.mark_ready().await;
        std::future::pending::<()>().await;
        Ok(())
    }
}

// In the app body:
let lm: LiveMap<String, String> = LiveMap::create(&ctx).await?;
ctx.mount_live(&"producer", Producer { lm: lm.clone(), source, trigger }).await?;
ctx.mount_live(&"consumer", Consumer { lm, seen }).await?;
```

The producer and the consumer are [live components](/docs/rust/advanced_topics/live_component/) here, so both keep running in live mode. A plain component can declare entries too: `declare_entry` only needs a `Ctx`.

## Creating a LiveMap

```rust
let lm: LiveMap<String, String> = LiveMap::create(&ctx).await?;
```

`create` is an async factory and must be called from **inside the app's component tree** (it mounts a backing target).

`K` is any `LiveMapKey`: `String`, `i64`, `u64` or a [`StableKey`](/docs/rust/programming_guide/processing_component/#component-path), so a composite key works too. `V` must be `Serialize + DeserializeOwned + Clone + PartialEq + Send + Sync + 'static` (the `LiveMapValue` bound, implemented automatically for every such type). Values are compared with `==`; the serialization bound is what makes an entry a target state. A `LiveMap` is cheap to clone, and all clones share one map.

## Producing entries

```rust
lm.declare_entry(&ctx, "a".to_string(), "alpha".to_string())?;
```

Call `declare_entry` from inside any component. The entry is a target state **owned by the declaring component**, which gives it normal declarative semantics:

- Declaring a key makes it present, or updates its value.
- The consumer is notified **only when the value actually changes** (compared with `==`) — re-declaring the same value is a no-op for downstream.
- An entry is removed when the component that declared it **stops declaring it** (on a re-run) or disappears. There is no explicit delete verb; deletion follows target-state ownership, exactly like other CocoIndex targets.

Multiple components may declare into the same map, as long as each key has a single owner (two components declaring the same key is a conflict, the same as for any target).

## Consuming entries

A `LiveMap` is a [`LiveMapView`](/docs/rust/advanced_topics/live_component/#livemapfeed-and-livemapview), so pass it straight to `ctx.mount_each_live` — it behaves just like a live source:

```rust
ctx.mount_each_live(&"each", self.lm.clone(), move |_ctx, value: String| {
    let seen = seen.clone();
    async move {
        seen.lock().unwrap().push(value);
        Ok(())
    }
})
.await
```

`ctx.mount_each_live(&key, feed, process_fn)` takes the map as its feed: `LiveMap<K, V>` implements `LiveMapView<K, V>` and `LiveMapFeed<K, V>`, so it is a live feed like a watched directory. `lm.scan().await?` (from the `LiveMapView` trait) returns the current entries as `Vec<(K, V)>`.

`mount_each_live` mounts one processing component per entry (keyed by the entry key), scans the current entries first, then reacts to incremental changes — re-mounting a component when its value changes and removing it when its entry is deleted. The processor receives the entry **value**.

A LiveMap supports **one active consumer** at a time; a second concurrent `watch` fails with an `Error`. A consumer that finished, failed or was cancelled releases the map, so it can be watched again.

## Semantics

- **Live-mode first.** LiveMap is built for [live mode](/docs/rust/programming_guide/live_mode/): the consumer subscribes and reacts as producers update the map. In catch-up mode (a plain update), the consumer scans whatever entries happen to exist when it runs, which can race producers running concurrently in the same session — so for predictable one-shot results, order the producer ahead of the consumer (wait for the producer's mount handle to be ready before consuming).
- **In-memory and per-session.** The map lives in process memory and is rebuilt from its producers each time the app starts; its contents do not persist across restarts.
- **Empty in a preview.** A preview (`app.preview(..)`, `cocoindex update --preview`) applies no target actions, so it does not fill the map either: its consumers see an empty map, and the preview lists no downstream actions for them. Preview the producers' own targets instead.

## When to use it

Reach for a LiveMap when you want to decouple a producing stage from a consuming stage through a shared, incrementally-maintained collection. For example: one part of your pipeline watches a stream, extracts entities, and declares them into a map keyed by entity ID; another part builds an index or enrichment for each entity, reacting automatically as entities are added, updated, or removed.
