@web_of_trust/adapter-yjs
v0.1.2
Published
Yjs CRDT adapter for Web of Trust — pure JavaScript replication and personal document management
Downloads
132
Readme
@web_of_trust/adapter-yjs
Default CRDT adapter for Web of Trust — pure JavaScript, no WASM required.
Implements the ReplicationAdapter and personal document interfaces from @web_of_trust/core using Yjs. Chosen as the default CRDT after benchmarking showed 76x faster initialisation on Android compared to the Automerge/WASM alternative.
Installation
pnpm add @web_of_trust/adapter-yjsRequires @web_of_trust/core as a peer dependency.
Key Features
- Pure JavaScript — no WASM, no worker, no WASM bundle (69 KB vs 1.7 MB)
- YjsPersonalDocManager — personal data (profile, contacts, attestations, group keys) stored in a
Y.Docwith proxy-based mutation API - YjsReplicationAdapter — encrypted shared spaces backed by
Y.Doc, drop-in replacement for the Automerge adapter - YjsPersonalSyncAdapter — multi-device sync for the personal document via the Relay
- Built-in garbage collection —
ydoc.gc = true; no history-stripping hack needed - CRDT-agnostic persistence — serialises to
Uint8ArrayviaY.encodeStateAsUpdate(), stored in CompactStore (IndexedDB) and Vault
API Overview
Personal Document
import {
initYjsPersonalDoc,
getYjsPersonalDoc,
changeYjsPersonalDoc,
onYjsPersonalDocChange,
flushYjsPersonalDoc,
} from '@web_of_trust/adapter-yjs'
// Initialise (loads from CompactStore / Vault on first call)
await initYjsPersonalDoc({ identity, compactStore, vaultClient })
// Read
const doc = getYjsPersonalDoc()
const contact = doc.contacts['did:key:z6Mk...']
// Mutate (proxy-based — plain assignment works)
changeYjsPersonalDoc((doc) => {
doc.profile.name = 'Alice'
doc.contacts['did:key:z6Mk...'] = { did: '...', name: 'Bob', ... }
})
// Subscribe to changes
const unsub = onYjsPersonalDocChange(() => {
const latest = getYjsPersonalDoc()
// re-render
})
// Persist immediately (normally automatic)
await flushYjsPersonalDoc()Replication Adapter (Shared Spaces)
import { YjsReplicationAdapter } from '@web_of_trust/adapter-yjs'
const replication = new YjsReplicationAdapter({
identity, // WotIdentity
messaging, // MessagingAdapter
groupKeyService, // GroupKeyService
metadataStorage, // SpaceMetadataStorage (optional)
compactStore, // YjsCompactStore (optional, IDB-backed)
vaultUrl, // string (optional)
})
// Open a space (creates Y.Doc if new, restores if known)
const handle = await replication.openSpace<{ notes: string }>(spaceInfo)
// Read current state
const doc = handle.getDoc()
// Mutate
await handle.transact((doc) => {
doc.notes = 'Hello from Alice'
})
// React to remote updates
handle.onRemoteUpdate(() => {
console.log('Remote change received:', handle.getDoc())
})
// Close when done
handle.close()Personal Sync (Multi-Device)
import { YjsPersonalSyncAdapter } from '@web_of_trust/adapter-yjs'
const sync = new YjsPersonalSyncAdapter({ identity, messaging, compactStore })
await sync.start()
// Encrypted Y.Doc updates are now forwarded to / from other devices via the RelayHow to Run
# Build (watch mode during development)
pnpm dev
# Build once
pnpm build
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watchCRDT Switch
The demo app selects adapters via the VITE_CRDT environment variable:
# Default — uses adapter-yjs
pnpm dev:demo
# Switch to Automerge
VITE_CRDT=automerge pnpm dev:demoBoth adapters pass the same 7 end-to-end Playwright tests.
