Skip to content

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-sdk

This 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-storeadd(), 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:

  1. CBOR-encodes the content
  2. SHA-256 hashes it → that's the document's hash
  3. 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→hash

The 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

BackendUse case
SurrealdbPersistenceEmbedded or distributed, multi-model queries
SqlitePersistenceDesktop apps, CLI tools, simple embedded
MongoDbPersistenceServer 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

Next steps

  • Documents — types, fields, and the document lifecycle
  • Querying — find, filter, sort
  • Editing — update operators, save, delete
  • P2P Sync — sync protocol, signals, conflict resolution