Skip to content

Queries

After opening a database with a schema, every collection exposes a typed query API.

Shipped surface

await db.users.find(); // all rows
await db.users.find({ role: 'admin' }); // equality filter
await db.users.findOne({ email: 'ali@example.com' }); // first or null
await db.users.count({ role: 'admin' }); // number
await db.users.delete({ role: 'user' }); // returns count removed
await db.users.update(
{ id: '0193…' }, // filter (must match ≤ 1)
{ role: 'admin' }, // partial patch
); // returns count updated

Each method returns its result type inferred from the schema — find() returns Doc[], findOne() returns Doc | null, the mutating methods return number.

The filter is a plain Record<string, unknown> of field-value pairs: every key is interpreted as strict equality against that field. Compound filters narrow with logical AND.

await db.users.find({ role: 'admin', active: true }); // role='admin' AND active=true

Indexed fields (declared with .index(...) / .uniqueIndex(...) in the schema) are looked up via the secondary index. Non-indexed filters fall back to a full scan — fine for small collections, expensive at scale; index the fields you query.

Reactive sibling

Every read above has a live counterpart:

const lq = db.users.live({ role: 'admin' });
const unsub = lq.subscribe((users) => render(users));
// later: unsub(); lq.dispose();

See the Reactive guide for the full subscription model (subscribe, for await, React’s useLive).

Transactions

Multi-document writes commit atomically:

await db.transaction(async (tx) => {
await tx.users.insert({ email: 'ali@example.com', role: 'admin' });
await tx.posts.insert({ authorId: '0193…', title: 'Hi' });
}); // commit on return, rollback on throw

Inside the callback, tx.users is a transaction-scoped proxy with the same surface as db.users (insert, find, findOne, count, delete). Throw to roll back; return to commit. Nested db.transaction(...) calls are rejected.

Not yet shipped

The following are on the roadmap (post-v0.x) and not callable today — listed here so you don’t reach for them and find a TypeError:

  • Comparison operators ($gt, $gte, $lt, $lte, $ne, $in, $nin, $exists, $regex). For now: pull the rows with an equality filter and narrow in JS, or add an index and use multiple count / findOne calls.
  • Fluent builder (.where(...).orderBy(...).limit(...)). For now: filter via the equality-object form and sort/slice the array client-side.
  • Aggregations (groupBy, sum, avg). For now: stream rows with live({...}) and aggregate in JS, or — if your dataset outgrows that — this is a sign you want SQLite, not NookDB.

When these land they’ll be in the changelog. The shipped surface above is stable across v0.x.