Database, Collections, and Cursors
Learn how to work with databases, collections, and cursors in LioranDB using the TypeScript/JavaScript driver. Understand how to organize data, access collections, query documents, and efficiently iterate through results.
Handles on this page
DbCollection<TSchema>FindCursor<TSchema>AggregationCursor<TResult>
Db
The public Db surface is:
databaseNamecollection(name)createCollection(name)listCollections()dropCollection(name)dropDatabase()query(collection, filter?, options?)
databaseName
The selected database for this handle.
collection(name)
Creates a collection handle.
const db = client.db("default");
const products = db.collection("products");
createCollection(name)
Creates the collection and returns:
type CreateCollectionResult = {
readonly collection: string;
};
listCollections()
Returns a readonly array of collection names.
dropCollection(name)
Drops one collection by name.
dropDatabase()
Drops the current database handle's database.
query(collection, filter?, options?)
Database-level query entrypoint that returns a FindCursor.
const rows = await db.query(
"products",
{inStock: true},
{sort: {price: 1}, limit: 10},
).toArray();
Collection<TSchema>
This is the main typed document handle.
Typed collections
- TypeScript
- JavaScript
type Product = {
_id?: string;
sku: string;
title: string;
price: number;
inStock: boolean;
docNumber: number;
category: string;
};
const products = client.db("default").collection<Product>("products");
const products = client.db("default").collection("products");
collectionName
Returns the collection name string.
Data model types
The real exported document-related types are:
type Document = Record<string, unknown>;
type OptionalUnlessRequiredId<TSchema> = TSchema extends { _id: unknown }
? TSchema
: Omit<TSchema, "_id"> & { _id?: unknown };
type WithId<TSchema> = Omit<TSchema, "_id"> & {
_id: unknown;
};
Practical meaning:
- if your schema does not require
_id, insert methods allow it to be omitted - result rows usually come back as normal documents with
_idincluded when present
Filters in detail
The exported filter type is:
type Filter<TSchema = Document> = Document & {
readonly [K in Extract<keyof TSchema, string>]?: unknown;
};
This is intentionally broad. The driver accepts:
- normal field equality filters
- nested operator objects
- server-specific query shapes like
$text
Examples:
{inStock: true}
{price: {$gte: 10}}
{batchTag: "docs", docNumber: {$gte: 4}}
{
$text: {
$search: "marker",
$field: "title",
},
}
The type system does not try to fully describe every query operator. The final validation boundary is the server.
Query options in detail
The real exported type is:
interface FindOptions<TSchema = Document> {
readonly limit?: number;
readonly skip?: number;
readonly projection?: Projection<TSchema>;
readonly sort?: Sort;
readonly cursor?: {
readonly token: string;
readonly direction: "next" | "previous";
};
}
limit
Maximum number of rows to return.
await products.find({inStock: true}, {limit: 5}).toArray();
skip
Number of matching rows to skip before returning data.
await products.find(
{inStock: true},
{sort: {sku: 1}, skip: 10, limit: 10},
).toArray();
projection
The real exported type is:
type Projection<TSchema = Document> =
| readonly (Extract<keyof TSchema, string> | string)[]
| Readonly<Record<string, 0 | 1 | boolean>>;
That means these forms are valid:
{projection: ["sku", "title"]}
{projection: {sku: 1, title: 1, _id: 0}}
Examples:
const brief = await products.find(
{},
{projection: ["sku", "title"]},
).toArray();
const shaped = await products.find(
{},
{projection: {sku: 1, title: 1, docNumber: 1}},
).toArray();
sort
The real exported sort types are:
type SortDirection = 1 | -1 | "asc" | "desc" | "ascending" | "descending";
type Sort =
| Readonly<Record<string, SortDirection>>
| readonly (readonly [string, SortDirection])[]
| readonly {field: string; direction: "asc" | "desc"}[];
All of these are accepted:
{sort: {price: 1}}
{sort: {price: "desc", sku: "asc"}}
{sort: [["price", -1], ["sku", 1]]}
{sort: [{field: "price", direction: "desc"}]}
cursor
The token-based page-resume shape:
{
cursor: {
token: "opaque-server-token",
direction: "next",
},
}
Most application code will not set this directly. It is primarily for advanced paging flows.
Write methods
insertOne(document, options?)
Inserts one document.
const result = await products.insertOne(
{
sku: "bk-001",
title: "Blue Notebook",
price: 14,
inStock: true,
docNumber: 1,
category: "paper",
},
{idempotencyKey: "products-insert-one-001"},
);
console.log(result.insertedId);
insertMany(documents, options?)
Inserts multiple documents and returns:
type InsertManyResult = {
readonly insertedIds: readonly unknown[];
};
updateOne(filter, update, options?)
Updates one matching document.
The real options type is:
interface UpdateOptions {
readonly idempotencyKey?: string;
readonly upsert?: boolean;
}
Example:
await products.updateOne(
{sku: "bk-001"},
{$set: {inStock: false}},
{upsert: false, idempotencyKey: "products-update-one-001"},
);
updateMany(filter, update, options?)
Updates all matching documents.
await products.updateMany(
{category: "tools"},
{$set: {inStock: true}},
{upsert: false},
);
deleteOne(filter, options?)
Deletes one matching document.
deleteMany(filter, options?)
Deletes every matching document.
Read methods
find(filter?, options?)
Returns a FindCursor<TSchema>.
const docs = await products
.find({inStock: true}, {sort: {price: 1}, limit: 5})
.toArray();
findOne(filter?, options?)
Returns the first matching document or null.
findManyByIds(ids)
Looks up a readonly list of ids and returns a readonly array where each entry is:
- the matching document
- or
nullif that id was not found
const rows = await products.findManyByIds([
"id-1",
"id-2",
"missing-id",
]);
The returned array preserves positional alignment with the input ids.
countDocuments(filter?)
Counts matching documents and returns a number.
The docs test currently verifies:
countDocuments()on all inserted test rowscountDocuments(filter)on filtered subsets
Example:
const total = await products.countDocuments();
const inStock = await products.countDocuments({inStock: true});
Representative output:
42
Real query patterns verified by the docs test
Equality query
await products.find({sku: "bk-001"}).toArray();
Range query
await products.find(
{docNumber: {$gte: 4}},
{sort: {docNumber: 1}},
).toArray();
await products.find(
{price: {$gte: 20}},
{sort: {price: 1}},
).toArray();
Sorted and paged query
await products.find(
{inStock: true},
{sort: {docNumber: 1}, skip: 1, limit: 3},
).toArray();
Projected query
await products.find(
{inStock: true},
{projection: {docNumber: 1, title: 1}},
).toArray();
Text query
await products.find({
$text: {
$search: "Marker",
$field: "title",
},
}).toArray();
Index methods
createIndex(fieldOrDefinition, options?)
Creates a secondary index.
The real option type is:
interface CreateIndexOptions {
readonly name?: string;
readonly unique?: boolean;
readonly sparse?: boolean;
readonly partialFilter?: Document;
}
The real definition type is:
interface CreateIndexDefinition extends CreateIndexOptions {
readonly field?: string;
readonly fields?: readonly (
| string
| {
readonly field: string;
readonly direction?: SortDirection | "Asc" | "Desc";
}
)[];
}
Examples:
await products.createIndex("sku", {
name: "sku_idx",
unique: true,
});
await products.createIndex({
name: "category_price_idx",
fields: [
{field: "category", direction: "asc"},
{field: "price", direction: "desc"},
],
});
await products.createIndex({
name: "active_price_idx",
fields: ["price"],
partialFilter: {active: true},
});
The docs test verifies that created secondary indexes can be:
- listed
- queried against
- dropped
createTextIndex(field, options?)
Creates a text index.
The real text-index option type is:
interface TextIndexOptions {
readonly normalize?: boolean;
readonly stopwords?: readonly string[];
}
Example:
await products.createTextIndex("title", {
normalize: true,
stopwords: ["set"],
});
listIndexes()
Returns readonly CollectionIndexDefinition[].
The real returned definition shape includes:
namefieldsuniquesparsepartialFilterbuildStatebuildProgressisTextimplicit
dropIndex(name, options?)
Drops an index by name and returns:
type DropIndexResult = {
readonly name: string;
};
Aggregation
aggregate(pipeline, options?)
Returns an AggregationCursor<TResult>.
The currently exported options type is intentionally minimal:
interface AggregateOptions {
readonly allowDiskUse?: never;
}
That means the driver does not currently expose rich aggregation options. The main control point is the pipeline itself.
Current server-compatible aggregation stages verified by the docs test:
$match$group$projectfor simple field inclusion$skip$limit
Example:
const results = await products.aggregate([
{$match: {inStock: true}},
{$group: {_id: "$category", count: {$sum: 1}}},
]).toArray();
Caveats backed by current testing:
- aggregation
$sortis not documented as supported here - grouped row ordering should not be assumed stable
drop(options?)
Drops the collection and returns:
{
readonly collection: string;
}
FindCursor<TSchema>
FindCursor is:
- chainable
- async iterable
- lazily paged
- lifecycle-bound to the client
Query-shaping methods
filter(filter)limit(limit)skip(skip)sort(sort)project(projection)
These methods return a new cursor definition.
Important source-backed rule:
- once execution starts, changing the definition throws
CursorInitializedError
Execution methods
next()tryNext()hasNext()toArray()
Iteration helpers
forEach(iterator)map(mapper)
Lifecycle helpers
rewind()clone()close()
next()
Returns the next document or null.
tryNext()
Currently behaves the same as next().
hasNext()
Returns whether another row is available.
toArray()
Drains the remaining cursor contents and returns a readonly array.
If you already consumed some rows with next(), toArray() returns only the
remaining rows.
forEach()
Runs a sync or async callback for each row.
map()
Transforms rows and returns a readonly array of mapped values.
rewind()
Resets local consumption state so the cursor can be read again from the same definition.
clone()
Creates a fresh cursor with the same definition.
Use this when you want multiple independent consumers.
Mutation rule
Once execution has started, query-shaping methods are no longer allowed.
Representative runtime error:
Cursor options cannot be changed after execution has started. Clone the cursor first if you need a variant.
AggregationCursor<TResult>
The aggregation cursor supports:
next()tryNext()hasNext()toArray()forEach()map()rewind()clone()close()
Important implementation detail from the real source:
FindCursorpages incrementallyAggregationCursorfetches the full aggregation result set when first loaded
So for large flows:
- use
find()when you want more streaming-like pagination - use
aggregate()when you want a derived result set and can tolerate full-result materialization
Cursor cancellation
The real cursor operation type is:
interface CursorOperationOptions {
readonly signal?: AbortSignal;
}
That signal can be passed to methods like:
await cursor.next({signal});
await cursor.toArray({signal});
await aggregateCursor.toArray({signal});