Skip to content

Collection API

A Collection<T> is returned by db.collection<T>(name). All methods return Promises, even on Node.js where the underlying Rust calls are synchronous.

insert(doc)

Inserts one document and returns its generated _id.

ts
insert(doc: Omit<T, '_id'>): Promise<string>
ts
const id = await users.insert({ name: 'Alice', age: 30 })
// id = '01HWZZQ0000000000000000000'  (ULID)

The _id field in the input is ignored — TalaDB always generates a new ULID.

insertMany(docs)

Inserts multiple documents in a single write transaction and returns an array of generated IDs in insertion order.

ts
insertMany(docs: Omit<T, '_id'>[]): Promise<string[]>
ts
const ids = await users.insertMany([
  { name: 'Bob', age: 25 },
  { name: 'Carol', age: 35 },
])

subscribeAggregate(pipeline, callback, onError?)

Subscribe to a live aggregation. The callback receives a snapshot immediately and again after every write that could affect the result.

ts
subscribeAggregate<R>(
  pipeline: AggregatePipeline<T>,
  callback: (docs: R[]) => void,
  onError?: (error: unknown) => void,
): () => void

Use this rather than aggregate whenever the result is rendered. aggregate runs once and returns a dead snapshot — a page built on it sits frozen while rows land underneath it.

ts
const unsub = products.subscribeAggregate(
  [{ $match: { category: 'kitchen' } }, { $sort: { price: 1 } }, { $limit: 20 }],
  (page) => render(page),
);

find(filter?)

Returns all documents matching the filter. If no filter is provided, returns all documents in the collection.

ts
find(filter?: Filter<T>): Promise<T[]>
ts
const all   = await users.find()
const young = await users.find({ age: { $lt: 30 } })

Documents are returned in ULID insertion order (ascending). For sorting, pagination, or field projection, see Sorting, pagination and projection below.

Sorting, pagination and projection

find() returns documents in ULID insertion order and has no sort, skip, limit or fields. To sort, page, or project, use aggregate:

ts
const page2 = await products.aggregate([
  { $match: { category: 'kitchen' } },
  { $sort:  { price: 1 } },
  { $skip:  100 },
  { $limit: 100 },
]);

aggregate() returns a snapshot, not a live result

It runs once. If rows land afterwards — a write elsewhere in the app, a migration — the result you already have does not update. For a page that stays live, subscribe instead:

ts
const unsub = products.subscribeAggregate(
  [{ $sort: { price: 1 } }, { $skip: 100 }, { $limit: 100 }],
  (page) => render(page),
);

In React, useAggregate does this for you.

Ordering caveat for derived ids

A document inserted with a derived _id (a hash of some external key, via deriveDocId) has no chronological ULID prefix, so it does not come back in insertion order from an unsorted find(). Pass an explicit $sort when reading such a collection.

findOne(filter)

Returns the first document matching the filter, or null if no document matches.

ts
findOne(filter: Filter<T>): Promise<T | null>
ts
const alice = await users.findOne({ email: 'alice@example.com' })
if (alice) {
  console.log(alice.name)
}

updateOne(filter, update)

Updates the first document matching the filter. Returns true if a document was found and updated, false if no document matched.

ts
updateOne(filter: Filter<T>, update: Update<T>): Promise<boolean>
ts
const updated = await users.updateOne(
  { email: 'alice@example.com' },
  { $set: { age: 31 }, $inc: { loginCount: 1 } },
)

updateMany(filter, update)

Updates all documents matching the filter. Returns the number of documents updated.

ts
updateMany(filter: Filter<T>, update: Update<T>): Promise<number>
ts
const count = await users.updateMany(
  { role: 'trial' },
  { $set: { role: 'user', upgradedAt: Date.now() } },
)

deleteOne(filter)

Deletes the first document matching the filter. Returns true if a document was deleted, false if none matched.

ts
deleteOne(filter: Filter<T>): Promise<boolean>
ts
const deleted = await users.deleteOne({ _id: id })

deleteMany(filter)

Deletes all documents matching the filter. Returns the number of documents deleted.

ts
deleteMany(filter: Filter<T>): Promise<number>
ts
const removed = await users.deleteMany({ active: false })

count(filter?)

Returns the number of documents matching the filter. If no filter is provided, returns the total document count.

ts
count(filter?: Filter<T>): Promise<number>
ts
const total  = await users.count()
const admins = await users.count({ role: 'admin' })

createIndex(field)

Creates a secondary B-tree index on a field. The call is idempotent — creating an existing index does nothing.

ts
createIndex(field: keyof Omit<T, '_id'> & string): Promise<void>
ts
await users.createIndex('email')
await users.createIndex('age')

Index creation backfills all existing documents. For large collections this may be slow — create indexes before inserting bulk data whenever possible.

Full-text search index

Use createFtsIndex to build an inverted token index. It powers the $contains filter shown here, plus BM25-ranked searchText and hybridSearch:

ts
await posts.createFtsIndex('body')
const results = await posts.find({ body: { $contains: 'rust embedded' } })

dropIndex(field)

Removes a secondary index. Queries that relied on the index will fall back to full collection scans.

ts
dropIndex(field: keyof Omit<T, '_id'> & string): Promise<void>
ts
await users.dropIndex('age')

createCompoundIndex(fields)

Creates a compound B-tree index on a tuple of two or more fields. A compound index accelerates $and queries where every listed field is constrained with an equality ($eq) filter.

ts
createCompoundIndex(fields: (keyof Omit<T, '_id'> & string)[]): Promise<void>

The call is idempotent — creating an index that already exists is a no-op. The call backfills all existing documents automatically.

ts
// Speed up name lookups: { lastName: 'Smith', firstName: 'Alice' }
await people.createCompoundIndex(['lastName', 'firstName'])

// Three-field compound index
await events.createCompoundIndex(['year', 'month', 'day'])

When the planner uses a compound index

The query planner picks CompoundIndexEq when an $and filter contains an equality condition on every field in the compound index, in any order:

ts
// Uses the ['lastName', 'firstName'] compound index
await people.find({
  $and: [{ lastName: 'Smith' }, { firstName: 'Alice' }],
})

// Equivalent shorthand — also uses the compound index
await people.find({ lastName: 'Smith', firstName: 'Alice' })

If only a subset of the indexed fields is constrained, or a non-equality operator is used, the planner falls back to a single-field index (if one exists) or a full scan.

When to use compound indexes

A compound index is most useful when you always query a fixed set of fields together with equality — for example (lastName, firstName) for name lookups, or (tenantId, status) for multi-tenant filtered lists. For range queries or sorting, a single-field index is usually the better choice.

Throws InvalidOperation if fewer than two fields are provided.

dropCompoundIndex(fields)

Removes a compound index. Queries that used it will fall back to single-field indexes or a full scan.

ts
dropCompoundIndex(fields: (keyof Omit<T, '_id'> & string)[]): Promise<void>
ts
await people.dropCompoundIndex(['lastName', 'firstName'])

Throws IndexNotFound if no compound index exists for the given field tuple.

Vector indexing and similarity search — createVectorIndex, findNearest, metadata-filtered k-NN, and optional HNSW — have a dedicated reference:

Vector Search

BM25 keyword ranking (searchText), the $contains filter, and RAG-ready hybridSearch (BM25 + vector, fused with reciprocal rank fusion) have their own reference:

Search (Full-Text & Hybrid)

aggregate(pipeline)

Executes an aggregation pipeline against the collection. A pipeline is an ordered array of stages, each transforming the document set produced by the previous stage.

ts
aggregate(pipeline: Stage[]): Promise<Document[]>

If the first stage is $match, TalaDB consults the query planner so that any available index accelerates the initial filtering step. All subsequent stages run in memory.

Stages

$match

Filters the working document set. Accepts any standard Filter.

ts
{ $match: { status: 'active' } }
{ $match: { $and: [{ dept: 'eng' }, { level: { $gte: 3 } }] } }

When placed first in the pipeline, $match benefits from all index acceleration (single-field, compound, FTS). A $match in any later position is evaluated as a full in-memory filter.

$group

Groups documents by a key and computes per-group accumulators. The output document for each group contains _id (the group key value) plus one field per accumulator.

ts
{
  $group: {
    _id: '$fieldName',   // field to group by, or null for a single group
    outputField: { $accumulator: 'sourceField' },
    ...
  }
}
_id valueBehaviour
'$fieldName'One group per distinct value of fieldName. Documents where the field is absent are grouped under null.
nullAll documents form a single group (equivalent to SQL GROUP BY NULL).

Accumulators

AccumulatorDescriptionExample
$sumSum of numeric values{ total: { $sum: '$amount' } }
$avgArithmetic mean of numeric values. Returns null if no numeric values exist.{ avg: { $avg: '$score' } }
$minMinimum value{ lowest: { $min: 'price' } }
$maxMaximum value{ highest: { $max: 'price' } }
$countNumber of documents in the group{ n: { $count: {} } }
$pushCollect all field values into an array (duplicates kept){ names: { $push: 'name' } }
$addToSetCollect unique field values into an array{ tags: { $addToSet: 'tag' } }
$firstFirst value of the field in the group{ first: { $first: 'name' } }
$lastLast value of the field in the group{ last: { $last: 'name' } }

$first and $last reflect the document order entering $group. Pair with a preceding $sort to make the semantics explicit.

ts
// Sum and count per department
{
  $group: {
    _id: '$dept',
    totalSalary: { $sum: 'salary' },
    headcount:   { $count: {} },
    avgSalary:   { $avg: 'salary' },
  }
}

// Single-group totals
{
  $group: {
    _id: null,
    revenue: { $sum: 'amount' },
    maxOrder: { $max: 'amount' },
  }
}

$sort

Sorts the working document set. Object key order defines sort priority.

ts
{ $sort: { createdAt: -1 } }
{ $sort: { dept: 1, salary: -1 } }

$skip

Skips the first N documents.

ts
{ $skip: 10 }

$limit

Keeps only the first N documents.

ts
{ $limit: 5 }

$project

Retains only the listed fields in each document. All other fields are removed.

ts
{ $project: { _id: 1, name: 1, email: 1 } }

Pipeline examples

Aggregation with $match index acceleration

ts
// Count and total revenue per product, for 'eu' region orders only
const results = await orders.aggregate([
  { $match: { region: 'eu' } },          // uses index if one exists on 'region'
  {
    $group: {
      _id: '$product',
      revenue: { $sum: 'amount' },
      count:   { $count: {} },
    },
  },
  { $sort: { revenue: -1 } },
  { $limit: 10 },
])

Leaderboard — top 5 users by score

ts
const top5 = await scores.aggregate([
  { $match: { active: true } },
  { $sort:  { score: -1 } },
  { $limit: 5 },
  { $project: { _id: 1, username: 1, score: 1 } },
])

Daily revenue summary (full pipeline)

ts
const summary = await transactions.aggregate([
  { $match: { $and: [{ status: 'settled' }, { amount: { $gt: 0 } }] } },
  {
    $group: {
      _id: '$date',
      total:  { $sum: 'amount' },
      count:  { $count: {} },
      max:    { $max: 'amount' },
    },
  },
  { $sort:   { _id: 1 } },
  { $skip:   0 },
  { $limit:  30 },
  { $project: { _id: 1, total: 1, count: 1, max: 1 } },
])

Unique tag collection across all posts

ts
const [result] = await posts.aggregate([
  { $group: { _id: null, allTags: { $addToSet: 'tag' } } },
])
console.log(result.allTags)  // deduplicated array of all tags

First and last event per session

ts
const sessions = await events.aggregate([
  { $sort: { ts: 1 } },
  {
    $group: {
      _id:   '$sessionId',
      start: { $first: 'ts' },
      end:   { $last:  'ts' },
    },
  },
])

watch(filter?)

Pushes a fresh snapshot of matching documents to callback after every write to the collection. Returns an unsubscribe function. (watch()/WatchHandle is the Rust-core equivalent, pulled with next().)

ts
subscribe(
  filter: Filter<T>,
  callback: (docs: T[]) => void,
  onError?: (error: unknown) => void,
): () => void   // call it to unsubscribe
ts
// The callback fires immediately with the current snapshot, and again after
// every write that affects the filter.
const stop = users.subscribe({ role: 'admin' }, (admins) => {
  render(admins)
})

stop() // unsubscribe

// Async iterator
for await (const snapshot of handle) {
  console.log('Admins:', snapshot)
}

See Live Queries for full details.

Snapshot export / import

Database-level, not collection-level — and only on the raw @taladb/web handle, not on the object openDB() returns. See Snapshot export/import in Features.