Skip to main content

Collection

A Collection stores JSON documents keyed by _id (string). It's the API you'll use most: insert, query, update, delete, aggregate.

Document IDs (_id)

  • If you don't provide _id, LioranDB generates one.
  • _id cannot start with the reserved prefix \u0000__meta__: (used for internal metadata).

Schema validation (optional)

setSchema(schema, version)

setSchema(schema: ZodSchema<T>, version: number): void
  • Validates writes.
  • Stores the current schema version in documents as __v.
import { z } from "zod";

users.setSchema(
z.object({
_id: z.string().optional(),
email: z.string().email(),
__v: z.number().optional(),
}),
1
);

addMigration({ from, to, migrate })

addMigration(migration: { from: number; to: number; migrate: (doc: any) => T }): void
  • Upgrades documents on read (from older __v to the current version).
  • Great for renames/defaults when you don't want to rewrite the whole collection immediately.
users.setSchema(UserV2, 2);
users.addMigration({
from: 1,
to: 2,
migrate: (doc) => ({ ...doc, plan: doc.tier ?? "free" }),
});

CRUD (every common function with a tiny example)

All writes are queued per collection to keep ordering consistent.

insertOne(doc)

const inserted = await users.insertOne({ email: "a@b.com", plan: "free" });
console.log(inserted);
Sandbox output (example)
{ _id: "...", email: "a@b.com", plan: "free", __v: 1 }

insertMany(docs, options?)

await users.insertMany([{ email: "a@b.com" }, { email: "c@d.com" }], { chunkSize: 500 });
Sandbox output (example)
(no output)

find(query?, options?)

const list = await users.find(
{ plan: { $in: ["free", "pro"] } },
{ limit: 2, offset: 0, projection: ["email", "plan"] }
);
console.log(list);
Sandbox output (example)
[ { email: "a@b.com", plan: "free" }, { email: "c@d.com", plan: "pro" } ]

findOne(query?, options?)

const one = await users.findOne({ email: "a@b.com" });
console.log(one?.email);
Sandbox output (example)
a@b.com

updateOne(filter, update, options?)

const updated = await users.updateOne(
{ email: "a@b.com" },
{ $set: { plan: "pro" }, $inc: { "stats.logins": 1 } }
);
console.log(updated);
Sandbox output (example)
{ _id: "...", email: "a@b.com", plan: "pro", stats: { logins: 1 }, __v: 1 }

Upsert

const doc = await users.updateOne(
{ email: "new@x.com" },
{ $set: { email: "new@x.com", plan: "free" } },
{ upsert: true }
);
console.log(doc);
Sandbox output (example)
{ _id: "...", email: "new@x.com", plan: "free", __v: 1 }

updateMany(filter, update)

const changed = await users.updateMany({ plan: "free" }, { $set: { plan: "pro" } });
console.log(changed.length);
Sandbox output (example)
2

deleteOne(filter)

console.log(await users.deleteOne({ email: "a@b.com" }));
Sandbox output (example)
true

deleteMany(filter)

console.log(await users.deleteMany({ plan: "free" }));
Sandbox output (example)
3

count() / countDocuments(filter?)

console.log(await users.count());
console.log(await users.countDocuments({ plan: "pro" }));
Sandbox output (example)
10
4

Indexes (collection-level)

You can create indexes directly from a Collection as well:

createIndex(field, options?)

createIndex(field: string, options?: { unique?: boolean }): Promise<void>

Example: unique index (email)

await users.createIndex("email", { unique: true });
await users.insertOne({ email: "a@b.com", plan: "free" });
Sandbox output (example)
(no output)

explain(query?, options?)

explain(query?: any, options?: FindOptions): Promise<any>

Example: check whether an index is used

await users.createIndex("plan");
const plan = await users.explain({ plan: "pro" }, { limit: 10 });
console.log(plan.indexUsed);
Sandbox output (example)
plan

Streaming inserts

For large datasets, streamed inserts avoid building a huge array in memory:

insertManyStream(docs, options?)

insertManyStream(docs: Iterable<any> | AsyncIterable<any>, options?: { chunkSize?: number }): Promise<number>

Example: insert 100k docs from a generator

function* generateDocs(count: number) {
for (let i = 0; i < count; i++) yield { n: i };
}

const inserted = await users.insertManyStream(generateDocs(100_000), { chunkSize: 1000 });
console.log(inserted);
Sandbox output (example)
100000

Aggregation

aggregate(pipeline)

Supported stages: $match, $project, $skip, $limit, $group.

const out = await users.aggregate([{ $group: { _id: "plan", count: { $sum: 1 } } }]);
console.log(out);
Sandbox output (example)
[ { _id: "free", count: 7 }, { _id: "pro", count: 3 } ]

Maintenance

compact()

Compaction rebuilds the collection to reduce fragmentation and then rebuilds indexes.

await users.compact();
Sandbox output (example)
(no output)

End-to-end example (CRUD)

import { LioranManager } from "@liorandb/core";

const manager = new LioranManager({ rootPath: "./.liorandb" });
const db = await manager.db("app");
const items = db.collection<{ _id?: string; sku: string; qty: number }>("items");

await items.insertMany([{ sku: "A", qty: 10 }, { sku: "B", qty: 2 }]);
await items.updateOne({ sku: "B" }, { $inc: { qty: 1 } });

console.log(await items.find({ qty: { $gte: 3 } }));
await manager.close();
Sandbox output (example)
[ { _id: "...", sku: "A", qty: 10, __v: 1 }, { _id: "...", sku: "B", qty: 3, __v: 1 } ]