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 } })

Without a sort, the order depends on how the filter is answered. A filter that uses no index returns documents in ULID insertion order (ascending). A filter answered from an index returns them in index order: by the indexed value, then _id. For a numeric field holding both kinds of numbers, all integers come before all floating-point values. A string containing a NUL character can also come before a shorter string it extends ('a\0b' before 'a') when the shorter one's _id starts with byte 0xFF. That includes two-sided ranges such as { n: { $gte: 10, $lt: 20 } } on a field that has never held an array; before 0.13 they came back in an unspecified order that differed between runs. Other combined index filters ($or, several indexed equalities) return an unspecified order. Pass an explicit sort when order matters, and especially when taking a limited page: which documents an unsorted limit returns follows the same order. 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.