Skip to content

Querying

Document-store renders the latest state of each document into your database. You query the database directly using its native capabilities.

How rendering works

When you add() or edit() a document, the store:

  1. Computes the new rendered state (all edits applied)
  2. Writes it to a type-specific table/collection in your database

For SQLite, each type gets a <type> table. For MongoDB, a collection per type.

Query your database directly

Use your database's native query tools. You know your query patterns — use the right indexes, joins, and features for your use case.

SurrealDB

typescript
const db = store.storage.db;

// All bookmarks, newest first
const [bookmarks] = await db.query(
    'SELECT doc FROM bookmark ORDER BY doc.updatedAt DESC'
);

// Filter by field
const [matches] = await db.query(
    'SELECT doc FROM bookmark WHERE doc.url = $url',
    { url: 'https://example.com' }
);

// Relations and graph traversal
const [tree] = await db.query(
    'SELECT doc, ->contains->bookmark.doc AS bookmarks FROM folder WHERE hash = $hash',
    { hash: folderHashAsArrayBuffer }
);

Create indexes for your query patterns:

surql
DEFINE INDEX updated_idx ON bookmark FIELDS doc.updatedAt;
DEFINE INDEX uid_idx ON bookmark FIELDS doc.uid;
DEFINE INDEX title_search ON bookmark FIELDS doc.title SEARCH ANALYZER simple BM25;

SQLite

typescript
const db = new Database('./data/app.db');

// All bookmarks, newest first
const bookmarks = db.prepare(
    'SELECT * FROM bookmark ORDER BY updatedAt DESC'
).all();

// Search by field
const matches = db.prepare(
    `SELECT * FROM bookmark WHERE json_extract(doc, '$.url') LIKE ?`
).all('%example.com%');

MongoDB

typescript
const collection = mongoDb.collection('bookmark');

const bookmarks = await collection
    .find({ uid: myUid })
    .sort({ updatedAt: -1 })
    .limit(20)
    .toArray();

Rendered document format

Each rendered document contains:

  • All your app fields (url, title, etc.)
  • hash, uid, parent, share, write — system fields, always present when set
  • createdAt, updatedAt — timestamps, on by default and renameable per type via registerType({ render: { ... } })
typescript
store.registerType('bookmark');  // defaults render createdAt/updatedAt

// Rendered document looks like:
{
    hash: Buffer,         // always
    createdAt: 1710000000,
    updatedAt: 1710001000,
    uid: Buffer,
    url: 'https://...',
    title: 'Example',
}

Why not a query abstraction?

Document-store supports SQLite, MongoDB, and SurrealDB. Each has fundamentally different query capabilities — indexes, full-text search, aggregation pipelines, JSON operators, geospatial queries. A wrapper that covers all of that across backends would be a massive surface area to maintain, and would always be a worse version of the real thing.

The persistence layer for writes is simple — insert and update rendered documents. That's easy to abstract. Reads are where databases diverge, and where your app needs full control.