Stable ID generation
Generate IDs and UUIDs that stay stable across incremental runs — generate_id and generate_uuid for pure derivation, and IdGenerator / UuidGenerator for stateful sequences that still reproduce across runs.
The ID module (cocoindex::resources::id, re-exported from cocoindex::prelude) provides utilities for generating stable unique IDs and UUIDs that persist across incremental updates.
In an incremental pipeline, using random IDs (like Uuid::new_v4()) means every reprocessing run generates different IDs for the same data — causing unnecessary churn in your targets (deleting old rows, inserting identical ones with new IDs). CocoIndex’s ID utilities produce stable IDs: the same inputs produce the same IDs across runs, so unchanged data keeps its identity and targets only see real changes.
Choosing the right API
| API | Same dep produces… | Use when… |
|---|---|---|
generate_id(&ctx, &dep) | Same ID every time | Each unique input maps to exactly one ID |
id_gen.next_id(&ctx, &dep) | Distinct ID each call | You need multiple IDs for potentially non-distinct inputs |
The same distinction applies to generate_uuid vs UuidGenerator.
Every ID function takes the component’s Ctx first: the memo that keeps an ID stable belongs to the processing component, and in Rust the context is explicit.
generate_id / generate_uuid
Async functions that return the same ID/UUID for the same dep value. These are idempotent: calling multiple times with identical dep yields identical results.
use cocoindex::resources::id::{generate_id, generate_uuid};
// Same dep always gets the same ID; "A" twice is one ID, "B" is another.
let ids = vec![
generate_id(&ctx, &"A").await?,
generate_id(&ctx, &"B").await?,
generate_id(&ctx, &"A").await?,
];
// Same for UUIDs.
let uuids = vec![
generate_uuid(&ctx, &"A").await?,
generate_uuid(&ctx, &"B").await?,
generate_uuid(&ctx, &"A").await?,
];Parameters:
ctx— The component’sCtx.dep— Dependency value that determines the ID/UUID: anySerializevalue, passed by reference. The samedepalways produces the same result within a component.generate_id_default(&ctx)andgenerate_uuid_default(&ctx)are the forms with no dependency value.
Returns:
generate_idreturns aResult<u64>(IDs start from 1; 0 is reserved)generate_uuidreturns aResult<uuid::Uuid>
IdGenerator / UuidGenerator
Generators that return a distinct ID/UUID on each call, even when called with the same dep value. The sequence is stable across runs.
Use these when you need multiple IDs for potentially non-distinct inputs, such as splitting text into chunks where chunks may have identical content but still need unique IDs.
use cocoindex::prelude::*;
/// Embed one chunk and declare its row. The shared generator gives each
/// chunk a distinct ID, even when two chunks have the same text.
#[cocoindex::function]
async fn process_chunk(
ctx: &Ctx,
chunk: Chunk,
text: &str,
filename: &str,
id_gen: &IdGenerator,
table: &postgres::TableTarget,
) -> Result<()> {
let chunk_text = chunk.text(text);
let id = i64::try_from(id_gen.next_id(ctx, chunk_text).await?)
.map_err(|_| Error::engine("generated id does not fit in BIGINT"))?;
table.declare_row(
ctx,
&DocEmbedding {
id,
filename: filename.to_string(),
chunk_start: chunk.start.char_offset as i32,
chunk_end: chunk.end.char_offset as i32,
text: chunk_text.to_string(),
embedding: ctx.get_key(&EMBEDDER)?.embed(ctx, chunk_text).await?,
},
)
}
// One generator per file, shared by the concurrent calls of `ctx.map`.
let id_gen = IdGenerator::new();
ctx.map(chunks, async |chunk| {
process_chunk(ctx, chunk, &text, &filename, &id_gen, &table).await
})
.await?;
// Constructor dependencies split the sequences of two generators in one component.
let id_gen = IdGenerator::with_deps(&dep)?;
let id = id_gen.next_id_default(&ctx).await?;
let uuid_gen = UuidGenerator::new();
let uuid = uuid_gen.next_uuid(&ctx, &"same").await?;next_id and next_uuid take &self: the generator counts how often it has seen each dep behind a mutex, and that ordinal is part of the memo key. One generator can therefore be shared by the concurrent closures of ctx.map, as in examples/rust/text_embedding. Concurrent calls take their ordinals in the order they reach the generator, so give each call a dep that identifies its item, as the chunk text does above. A generator is not Clone: a copy would replay the same ordinals and hand out the same IDs again.
Constructor:
IdGenerator::new()/UuidGenerator::new()— Create a generator with no dependency.IdGenerator::with_deps(&deps)?/UuidGenerator::with_deps(&deps)?— Create a generator whosedeps(anySerializevalue) distinguishes it from other generators within the same processing component. Use distinctdepsvalues for different generator instances.
Methods:
async fn next_id(&self, ctx: &Ctx, dep: &D) -> Result<u64>— Generate the next unique integer ID (distinct on each call)async fn next_uuid(&self, ctx: &Ctx, dep: &D) -> Result<Uuid>— Generate the next unique UUID (distinct on each call)next_id_default(&ctx)/next_uuid_default(&ctx)— The same with no per-call dependency.