DEV Community

Cover image for LioranDB TypeScript Series #5: Cursors, Query Shaping and Full-Text Search
Swaraj Puppalwar
Swaraj Puppalwar

Posted on

LioranDB TypeScript Series #5: Cursors, Query Shaping and Full-Text Search

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);
Enter fullscreen mode Exit fullscreen mode

Execute it:

const usersArray = await cursor.toArray();
Enter fullscreen mode Exit fullscreen mode

Or iterate over it:

for await (const user of users.find({ active: true })) {
  console.log(user);
}
Enter fullscreen mode Exit fullscreen mode

Query shaping

FindCursor supports:

filter()
limit()
skip()
sort()
project()
Enter fullscreen mode Exit fullscreen mode

For example:

const results = await users
  .find({ active: true })
  .project({
    email: 1,
    age: 1,
  })
  .sort({
    age: -1,
  })
  .skip(20)
  .limit(20)
  .toArray();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

You can also:

cursor.clone();
cursor.rewind();
await cursor.close();
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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"],
});
Enter fullscreen mode Exit fullscreen mode

Then query it:

const matches = await products.find({
  $text: {
    $search: "Marker",
    $field: "title",
  },
}).toArray();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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



Enter fullscreen mode Exit fullscreen mode

Top comments (0)