Skip to main content

Collections

db.collection(name) returns a Collection<T> wrapper that calls server endpoints.

If you pass a schema + schema version (db.collection(name, schema, schemaVersion)), the driver can also:

  • parse documents through schema.parse(...), and
  • auto-manage a __v field and client-side read migrations.

See Schemas & migrations for the full workflow.

Related page: Databases (collection creation/rename/drop, indexes, transactions, credentials).

CRUD

  • insertOne(doc): Promise<T>
  • insertMany(docs): Promise<T[]>
  • insertManyStream(docs, options?): Promise<number>
  • find(filter = {}): Promise<T[]>
  • findOne(filter = {}): Promise<T | null>
  • updateOne(filter, update, options?): Promise<T | null>
  • updateMany(filter, update): Promise<{ updated; docs }>
  • deleteOne(filter): Promise<T | null>
  • deleteMany(filter): Promise<{ deleted: number }
  • count(filter = {}): Promise<number>
  • countDocuments(filter = {}): Promise<number>
  • aggregate(pipeline?): Promise<{ results }>
  • stats(): Promise<...>
  • compact(): Promise<...>

Examples (tiny per method)

insertOne(doc)

import { LioranClient } from "@liorandb/driver";

const client = new LioranClient("http://localhost:4000");
await client.login("admin", "admin");

const db = await client.db("default");
const users = db.collection<{ userId: string; role: string }>("users");

await users.insertOne({ userId: "u1", role: "user" });
console.log(await users.count({ role: "user" }));

insertMany(docs)

await users.insertMany([
{ userId: "u2", role: "user" },
{ userId: "u3", role: "admin" },
]);

insertManyStream(docs, options?) (NDJSON streaming)

For large inserts, you can stream documents using NDJSON. This avoids building a giant array in memory.

insertManyStream(...) accepts an Iterable or AsyncIterable and returns the number of inserted documents (as reported by the server response).

async function* docs() {
for (let i = 0; i < 10_000; i++) {
yield { userId: `u${i}`, role: "user", createdAt: Date.now() };
}
}

const inserted = await users.insertManyStream(docs(), { chunkSize: 1000 });
console.log({ inserted });

find(filter?, options?)

find() and findOne() accept an optional options object:

  • limit?: number
  • offset?: number
  • projection?: string[]
  • sort?: Record<string, 1 | -1>
  • sortBy?: string (convenience for a single-field sort)
  • sortDir?: "asc" | "desc" | 1 | -1 | "1" | "-1"
console.log(await users.find({ role: "user" }, { limit: 10, offset: 0 }));
console.log(await users.find({ role: "user" }, { sortBy: "userId", sortDir: "desc" }));

Embedding options inside the query (__options)

If it is more convenient for your codebase, you can embed options inside the filter object under a reserved __options key:

await users.find({
role: "user",
__options: { limit: 10, sortBy: "userId", sortDir: -1 },
});

If you pass both embedded __options and an explicit options argument, the explicit argument wins.

findOne(filter?, options?)

console.log(await users.findOne({ userId: "u1" }));

updateOne(filter, update, options?)

Supports update operators like $set, $inc, $unset. Use { upsert: true } to insert when missing.

console.log(
await users.updateOne(
{ userId: "u1" },
{ $set: { role: "admin" } },
{ upsert: true }
)
);

updateMany(filter, update)

const out = await users.updateMany({ role: "user" }, { $set: { active: true } });
console.log(out.updated);

deleteOne(filter) / deleteMany(filter)

console.log(await users.deleteOne({ userId: "u3" }));
console.log(await users.deleteMany({ active: false }));

count(filter?) / countDocuments(filter?)

console.log(await users.count({ role: "admin" }));
console.log(await users.countDocuments({ role: "admin" }));

Aggregation

aggregate(pipeline?)

const out = await users.aggregate([{ $group: { _id: "role", count: { $sum: 1 } } }]);
console.log(out);

Indexes

listIndexes() / createIndex(field, options?) / dropIndex(field)

console.log(await users.listIndexes());
console.log(await users.createIndex("userId", { unique: true }));
console.log(await users.dropIndex("userId"));

rebuildIndex(field) / rebuildIndexes()

console.log(await users.rebuildIndex("role"));
console.log(await users.rebuildIndexes());

explain(filter?, options?)

console.log(await users.explain({ role: "admin" }, { limit: 5 }));

Text indexes: createTextIndex(field, options?) / dropTextIndex(field)

Text indexes are used by the $text query operator (see below).

await users.createTextIndex("title", { normalize: true, stopwords: ["a", "the"] });
await users.dropTextIndex("title");

rebuildTextIndex(field)

await users.rebuildTextIndex("title");

Text search ($text)

Use $text at the top level of your filter. This requires at least one text index.

await users.find({ $text: { $search: "hello world" } });
await users.find({ $text: { $search: "hello world", $fields: ["title", "body"] } });

Dates and timestamps

The driver talks to the server over JSON. If you pass a JavaScript Date into a document, it will serialize as an ISO string.

Recommended pattern:

await users.insertOne({ createdAt: Date.now() }); // epoch ms number

Maintenance

stats() / compact()

console.log(await users.stats());
console.log(await users.compact());

Collection options

The server can store per-collection options. The driver exposes:

  • getOptions()
  • setDateOption(date) (convenience for the common date option)
console.log(await users.getOptions());
await users.setDateOption({ mode: "epoch_ms" });

Server-side collection doc migrations

Separate from the driver’s client-side addMigration(...), the server can maintain its own collection doc-migration configuration:

  • getDocMigrations()
  • setDocMigrations(config | null)
  • testDocMigration(doc)