Appearance
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 (or one syncs in from a peer), the store computes the new rendered state and hands it to your render handler, which writes it into your database — see Rendering. The table layout is whatever your handler made: real columns you designed, or a generic <type>(hash, content) table if you used a provided default.
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 (the provided handler's layout)
typescript
// db = your own Surreal connection (the one you gave createSurrealRenderHandler)
// 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 (your own table layout)
If your handler projects fields into real columns (the recommended pattern), queries are plain SQL:
typescript
const bookmarks = db.prepare(
'SELECT * FROM bookmark ORDER BY updated DESC'
).all();
const matches = db.prepare(
'SELECT * FROM bookmark WHERE url LIKE ?'
).all('%example.com%');Anything else
Postgres, DuckDB, an in-memory map — if your render handler writes it, your reads query it. The store doesn't care.
Rendered document format
Each rendered document contains:
- All your app fields (
url,title, etc.) hash,uid,parent,share,write— system fields, always present when setcreatedAt,updatedAt— timestamps, always present (rename in your render handler if your tables want other names)
typescript
store.registerType('bookmark');
// Rendered content (what your render handler receives) looks like:
{
hash: Buffer, // always
createdAt: 1710000000,
updatedAt: 1710001000,
uid: Buffer,
url: 'https://...',
title: 'Example',
}Why not a query abstraction?
Databases have 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 render-handler contract for writes is simple — four operation types. That's easy to keep portable. Reads are where databases diverge, and where your app needs full control.