Appearance
Getting Started
Document Store is a content-addressed document database that syncs peer-to-peer. Every document gets a hash based on its content. Edits create new versions, forming a chain back to the original. This makes sync simple — you either have a hash or you don't.
Install
sh
npm install @wishcore/wish-sdkThis includes document-store and its dependencies.
Quick example
A complete app that stores notes and syncs them between peers:
typescript
import { RpcApp, SyncProtocol } from '@wishcore/wish-sdk';
import { DocumentStore, SurrealdbPersistence } from 'document-store';
// 1. Create database and store
const storage = new SurrealdbPersistence('mem://');
await storage.ready();
const store = new DocumentStore({ storage });
// 2. Register document types
store.registerType('note');
// 3. Set up P2P sync
const sync = new SyncProtocol({
store,
onSync: () => console.log('Synced documents from peer')
});
// 4. Connect to wish-core (auto-detects Unix socket)
const app = new RpcApp({
name: 'MyApp',
protocols: { myapp: sync }
});
await app.whenReady;
// 5. Write through document-store
const uid = app.identities[0].uid;
const [errors, hash] = await store.add('note', {
uid,
title: 'Hello',
content: 'First note',
share: { self: true }
});
// 6. Read directly from the database
const db = store.storage.db;
const [notes] = await db.query('SELECT doc FROM note');That's a working P2P app. Documents sync automatically when peers connect.
SQLite works too
If you prefer SQLite, swap SurrealdbPersistence for SqlitePersistence:
typescript
import { SqlitePersistence } from 'document-store';
import Database from 'better-sqlite3';
const db = new Database('./data/app.db');
const store = new DocumentStore({ storage: new SqlitePersistence(db) });Then read with db.prepare('SELECT * FROM note ORDER BY ...').all().
Architecture: writes vs reads
All writes go through document-store — add(), edit(), save(), delete(). The store handles hashing, versioning, validation, signatures, and rendering the latest state into your database.
All reads go directly to your database. Document-store renders each document type into a table (SQLite) or collection (MongoDB). Query them with your database's native tools — SQL queries, indexes, joins, whatever you need.
Writes: App → DocumentStore → [hash, validate, sign, render] → Database
Reads: App → Database (direct)This split is intentional. Writing a full query abstraction across SQLite, MongoDB, and SurrealDB is a massive undertaking — every index, lookup pattern, and aggregation works differently. The persistence layer for writes is simple (insert/update rendered docs). But reads need the full power of your database, and you know your query patterns better than any abstraction can guess.
How it works
Content addressing
When you add a document, the store:
- CBOR-encodes the content
- SHA-256 hashes it → that's the document's
hash - Stores both the rendered document (for queries) and the raw CBOR (for sync)
Version chain
Edits don't modify the original. They create new documents with prev and root pointers:
[original] ← [edit 1] ← [edit 2] ← [edit 3]
hash prev→hash prev→edit1 prev→edit2
root→hash root→hash root→hashThe store maintains a rendered view — the latest state with all edits applied. You query the rendered view; sync transfers the full chain.
Sync
The SyncProtocol from wish-sdk handles everything:
- When a peer connects, they exchange document hashes and timestamps
- Missing documents are fetched and injected into the local store
- When you add or edit locally, call
sync.notifyPeers()to push changes
Storage backends
| Backend | Use case |
|---|---|
SurrealdbPersistence | Embedded or distributed, multi-model queries |
SqlitePersistence | Desktop apps, CLI tools, simple embedded |
MongoDbPersistence | Server deployments, large datasets |
SurrealDB with the embedded SurrealKV engine is the recommended default — no server needed, file-backed, with a powerful query language:
typescript
import { SurrealdbPersistence } from 'document-store';
// File-backed (SurrealKV embedded engine — use absolute paths)
const storage = new SurrealdbPersistence('surrealkv:///absolute/path/to/data');
await storage.ready();
// In-memory (testing)
const storage = new SurrealdbPersistence('mem://');
await storage.ready();Prerequisites
Document Store runs standalone for local storage. For P2P sync, you need:
- A running wish-core instance (Wish SDK docs)
- An identity granted to your app via the Wish Dashboard