LioranDB TypeScript Series #5: Cursors, Query Shaping and Full-Text Search
LioranDB TypeScript Series: Build with a developer-first document database powered by Rust and designed for TypeScript.
Calling find() doesn't simply give you an array.
It gives you a FindCursor.
That distinction becomes useful as queries grow.
Building a query
const cursor = users
.find({ active: true })
.sort({ email: 1 })
.limit(20);
Execute it:
const usersArray = await cursor.toArray();
Or iterate over it:
for await (const user of users.find({ active: true })) {
console.log(user);
}
Query shaping
FindCursor supports:
filter()
limit()
skip()
sort()
project()
For example:
const results = await users
.find({ active: true })
.project({
email: 1,
age: 1,
})
.sort({
age: -1,
})
.skip(20)
.limit(20)
.toArray();
That's enough to implement a large chunk of ordinary application querying.
Cursor execution
You aren't limited to toArray().
await cursor.next();
await cursor.tryNext();
await cursor.hasNext();
await cursor.forEach(callback);
You can also:
cursor.clone();
cursor.rewind();
await cursor.close();
Important cursor rule
Once execution begins, query-shaping methods cannot modify that cursor definition.
For example:
const cursor = users.find({ active: true });
await cursor.next();
// Don't reshape the already initialized cursor.
If you need a variation, clone the cursor before changing its definition.
Text search
First create a text index:
await products.createTextIndex("title", {
normalize: true,
stopwords: ["set"],
});
Then query it:
const matches = await products.find({
$text: {
$search: "Marker",
$field: "title",
},
}).toArray();
This keeps text search inside the same collection/query model you're already using.
FindCursor vs AggregationCursor
A FindCursor is designed around query results and incremental consumption.
Aggregation works differently:
const results = await products.aggregate([
{
$match: {
inStock: true,
},
},
{
$group: {
_id: "$category",
count: {
$sum: 1,
},
},
},
]).toArray();
In the current pre-alpha driver, AggregationCursor materializes its result set on its initial load.
That's worth remembering for large analytical workloads.
Cancellation
Execution methods can work with an AbortSignal, allowing application-level cancellation.
That matters when requests disappear, clients disconnect or an operation is no longer useful.
Resources
Driver Docs:
https://docs.liorandb.com/docs/driver/database-and-collections
Documentation: https://docs.liorandb.com
Website: https://liorandb.com
Previous: Part 4 → Typed CRUD
Next: Part 6 → Secondary & Text Indexes
Top comments (0)