A robust, modular, local-first collaborative state engine for JavaScript & TypeScript applications.
Syncraft enables web and Node.js applications to support multi-user collaboration with zero central lock-in. Replicas can modify shared state concurrently, operate completely offline, persist mutations to local storage (IndexedDB), and automatically synchronize with peer replicas via WebSockets upon reconnection.
- Key Highlights
- Core Architecture & Diagrams
- Monorepo Package Matrix
- Setup & Installation
- CRDT & Convergence Model
- Offline-First Workflow
- Quick Start Guide
- Example Applications
- Assumptions & Invariants
- AI Planning & 10-Phase Roadmap
- Publishing & CI/CD
- Technical Limitations & Guarantees
- License
- 🔄 Deterministic Convergence: Resolves concurrent conflicts mathematically with zero state forks across arbitrary network replay permutations.
- 🕒 Lamport Logical Clocks: Guarantees causal ordering without relying on physical wall clocks or server NTP synchronization.
- 📴 Offline-First & Auto-Flush: Local operations update the UI immediately, persist to IndexedDB, queue offline, and automatically flush in batches upon reconnecting.
- 🌳 Safe Immutable Path Navigation: Deep property manipulation (
doc.tags.0), copy-on-write immutability, and built-in prototype pollution protection (__proto__,constructor,prototype). - ⚛️ React 18 Concurrent Rendering: Custom hooks built with
useSyncExternalStorefor tearing-free reactive state binding and granular slice subscriptions. - 🛡️ Tombstone & Ancestor Pruning: Automatically prunes empty intermediate container objects on key deletion, guaranteeing identical structural tree equivalence across replicas.
graph TD
subgraph UI ["User Interface Layer"]
ReactApp["React Application / UI Components"]
Hooks["@bhoomash/syncraft-react (useSyncStore, useSyncState, useConnectionStatus)"]
ReactApp --> Hooks
end
subgraph ClientLayer ["Client Orchestration Layer (@bhoomash/syncraft-client)"]
SyncClient["SyncClient Manager"]
PendingQueue["Pending Operation Queue"]
ReconnectMgr["Exponential Backoff & Reconnect"]
Hooks --> SyncClient
SyncClient --> PendingQueue
SyncClient --> ReconnectMgr
end
subgraph CoreLayer ["Core CRDT Engine (@bhoomash/syncraft-core)"]
Clock["Lamport Logical Clock"]
ConflictResolver["Deterministic LWW Resolver (Total Order)"]
PathEngine["Immutable Safe Path Engine"]
OpLog["Operation Log & Deduplication Set"]
SyncClient --> Clock
SyncClient --> ConflictResolver
ConflictResolver --> PathEngine
ConflictResolver --> OpLog
end
subgraph StorageLayer ["Storage Layer (@bhoomash/syncraft-storage)"]
StorageAdapter["IStorageAdapter Interface"]
IDBAdapter["IndexedDB Storage Adapter (Browser)"]
MemAdapter["Memory Storage Adapter (Node / Tests)"]
StorageAdapter --> IDBAdapter
StorageAdapter --> MemAdapter
SyncClient --> StorageAdapter
end
subgraph ServerLayer ["Relay Transport Layer (@bhoomash/syncraft-server)"]
WSServer["WebSocket Server Relay"]
RoomMgr["Room & Client Manager"]
MsgValidator["Protocol & Schema Validator"]
WSServer --> RoomMgr
WSServer --> MsgValidator
end
SyncClient <== "WebSocket Protocol (JSON envelopes)" ==> WSServer
┌──────────────────────────────────────────────────────────┐
│ React Application │
│ (useSyncStore, useSyncState) │
└─────────────────────────────┬────────────────────────────┘
│
┌─────────────────────────────▼────────────────────────────┐
│ @bhoomash/syncraft-client │
│ - Orchestrates engine, local persistence, & networking │
│ - Manages pending operation queue & reconnect backoff │
└───────────────┬───────────────────────────┬──────────────┘
│ │
┌───────────────▼───────────┐ ┌───────────▼──────────────┐
│ @bhoomash/syncraft-core │ │@bhoomash/syncraft-storage│
│ - Lamport Logical Clock │ │ - IndexedDB Persistence │
│ - Operation Generator │ │ - In-Memory Fallback │
│ - Deterministic LWW CRDT │ │ - Pending Queue Store │
│ - Path Engine (immutable)│ │ - Snapshots & Metadata │
│ - Zero External Deps │ │ - Zero CRDT Logic │
└───────────────────────────┘ └──────────────────────────┘
│
│ WebSocket Protocol (Relay Only)
▼
┌──────────────────────────────────────────────────────────┐
│ @bhoomash/syncraft-server │
│ - WebSocket Relay & Room Isolation │
│ - Message Validation & Operation Broadcasting │
│ - (Relay only: NOT a CRDT conflict authority) │
└──────────────────────────────────────────────────────────┘
| Package | Version | Responsibility | Dependencies | NPM Tarball |
|---|---|---|---|---|
@bhoomash/syncraft-core |
0.1.0 |
CRDT state engine, Lamport logical clock, operation evaluation, path engine, immutability. | Zero (Pure JS) | 7.3 kB |
@bhoomash/syncraft-storage |
0.1.0 |
Pluggable local persistence (IndexedDB for browsers, in-memory for Node/tests). | Zero | 4.3 kB |
@bhoomash/syncraft-client |
0.1.0 |
WebSocket client, room manager, reconnect backoff, offline sync queue. | @bhoomash/syncraft-core, @bhoomash/syncraft-storage, ws |
4.8 kB |
@bhoomash/syncraft-server |
0.1.0 |
Node.js WebSocket relay server, client tracking, room broadcast. | @bhoomash/syncraft-core, @bhoomash/syncraft-storage, ws |
3.8 kB |
@bhoomash/syncraft-react |
0.1.0 |
React 18 hooks (useSyncStore, useSyncState, useConnectionStatus, useSyncRoom). |
@bhoomash/syncraft-client, @bhoomash/syncraft-core, react |
2.5 kB |
- Node.js:
v18.0.0or higher (tested on Node 18, 20, and 22 LTS) - Package Manager:
npm(v9+) orpnpm(v8+)
# 1. Clone the repository
git clone https://github.com/bhoomash/synccraft.git
cd synccraft
# 2. Install workspace dependencies
npm install
# 3. Run full automated test suite (62 tests across 12 suites)
npm test
# 4. Verify package bundling and tarball generation
npm run publish:dry-runSyncraft employs an Operation-based Last-Write-Wins Map/Document CRDT:
{
operationId: "node-A-42-9a8f12c", // Globally unique operation ID
nodeId: "node-A", // Originating replica ID
logicalClock: 42, // Lamport scalar timestamp
operationType: "SET", // 'SET' | 'DELETE'
path: "document.theme", // Canonical dot-separated path
value: "dark", // Value payload (undefined for DELETE)
wallTime: 1740000000000 // Informational physical timestamp
}-
Local Mutation:
$L_{\text{local}} \leftarrow L_{\text{local}} + 1$ -
Remote Operation Received:
$L_{\text{local}} \leftarrow \max(L_{\text{local}}, L_{\text{op}}) + 1$
Given two operations
Even when operations are received out of order across different network partitions:
sequenceDiagram
autonumber
participant UI as User / UI
participant Client as @bhoomash/syncraft-client
participant Core as @bhoomash/syncraft-core
participant Storage as @bhoomash/syncraft-storage (IndexedDB)
participant Server as @bhoomash/syncraft-server (WS Relay)
Note over Client,Server: Client is Offline (Airplane Mode)
UI->>Client: store.set("doc.title", "Draft 1")
Client->>Core: Apply local SET
Core-->>Client: New immutable state snapshot
Client->>Storage: Persist op to pendingQueue
Client-->>UI: Immediate state notification (zero lag)
Note over Client,Server: Connection Restored
Client->>Server: Connect & JOIN_ROOM
Server-->>Client: ACK join
Client->>Storage: Read pending operations
Storage-->>Client: [Op_1, Op_2]
Client->>Server: BATCH_OPERATIONS / SYNC_REQUEST
Server-->>Client: OPERATION broadcast & ACK
Client->>Core: Apply remote operations (deterministic LWW)
Client->>Storage: Mark pending ops acknowledged
Client-->>UI: Converged state updated
npm install @bhoomash/syncraft-coreimport { createSyncStore } from '@bhoomash/syncraft-core';
// Initialize a standalone CRDT store
const store = createSyncStore({ nodeId: 'node-alpha' });
// Subscribe to state changes
const unsubscribe = store.subscribe((state) => {
console.log('Current state:', state);
});
// Mutate local state
store.set('project.title', 'Syncraft');
store.set('project.settings.theme', 'dark');
console.log(store.get('project.title')); // "Syncraft"
// Hydrate remote operation
store.applyRemoteOperation({
operationId: 'node-beta-1-abc',
nodeId: 'node-beta',
logicalClock: 2,
operationType: 'SET',
path: 'project.settings.notifications',
value: true,
});
console.log(store.getState());
unsubscribe();npm install @bhoomash/syncraft-client @bhoomash/syncraft-core @bhoomash/syncraft-storageimport { createSyncClient } from '@bhoomash/syncraft-client';
import { IndexedDBAdapter } from '@bhoomash/syncraft-storage';
const client = createSyncClient({
url: 'ws://localhost:8080',
roomId: 'workspace-room-1',
nodeId: 'user-alice',
storage: new IndexedDBAdapter({ dbName: 'my-app-db' }),
autoConnect: true,
});
// Synchronous UI updates + background persistence and sync
await client.set('doc.title', 'Collaborative Doc');
await client.set('doc.tags.0', 'crdt');
client.onStatusChange((status) => {
console.log('Sync status:', status); // 'connecting' | 'connected' | 'reconnecting' | 'disconnected'
});npm install @bhoomash/syncraft-serverimport { createSyncServer } from '@bhoomash/syncraft-server';
const server = createSyncServer({
port: 8080,
heartbeatInterval: 30000,
});
console.log(`Relay server running on ws://localhost:${server.getPort()}`);npm install @bhoomash/syncraft-react @bhoomash/syncraft-client @bhoomash/syncraft-coreimport React from 'react';
import { createSyncClient } from '@bhoomash/syncraft-client';
import { SyncProvider, useSyncState, useConnectionStatus, useSyncClient } from '@bhoomash/syncraft-react';
const client = createSyncClient({
url: 'ws://localhost:8080',
roomId: 'collab-room-1',
autoConnect: true,
});
function TitleEditor() {
const client = useSyncClient();
const [title, setTitle] = useSyncState(client.getStore(), 'document.title');
const status = useConnectionStatus(client);
return (
<div>
<p>Status: <strong>{status}</strong></p>
<input
type="text"
value={title || ''}
onChange={(e) => setTitle(e.target.value)}
placeholder="Type a collaborative title..."
/>
</div>
);
}
export function App() {
return (
<SyncProvider client={client}>
<TitleEditor />
</SyncProvider>
);
}Syncraft includes three fully working reference implementations in the examples/ directory:
examples/vanilla/: Standalone HTML5 + pure JavaScript collaborative notes app featuring live connection indicators and local IndexedDB caching.examples/react/: Real-time collaborative todo list application powered by@bhoomash/syncraft-reactwith granular path binding.examples/collaborative-editor/: Multi-user shared document editor with offline editing recovery.
- Logical Clocks over Physical Time: Physical clocks (
Date.now()) are subject to drift, skew, and leap seconds. All conflict resolution relies solely on monotonic Lamport clocks with deterministic node ID tie-breaking. - Asynchronous & Partitioned Networks: Messages can be arbitrarily delayed, dropped, reordered, or duplicated. All operations are idempotent under total order replay.
- Untrusted Server Relay: The WebSocket server acts strictly as an authenticated packet relay; each client independently verifies and maintains CRDT mathematical invariants.
- Path-Level Granularity: Conflict resolution operates at the field/path level (e.g.
doc.authoranddoc.titleupdate concurrently without collision). - Pure & Isolated Core:
@bhoomash/syncraft-corehas zero external dependencies, zero side-effects, and executes identically across Node.js, browsers, and mobile runtimes.
| Phase | Milestone | Scope & Deliverables | Status |
|---|---|---|---|
| Phase 1 | Monorepo & Package Boundaries | Workspace configuration, package manifests, ESM exports, Vitest test runner, architecture specs, and README. | Complete |
| Phase 2 | @bhoomash/syncraft-core Engine | Lamport clock, unique operation IDs, SET/DELETE ops, path manipulation, deterministic conflict resolution (LWW + tie-breaking), state reconstruction, deduplication. | Complete |
| Phase 3 | CRDT Convergence Testing | Multi-replica out-of-order, duplicate, concurrent, and network partition convergence test suites (38 tests). | Complete |
| Phase 4 | @bhoomash/syncraft-storage | Pluggable storage abstraction, IndexedDB engine (browser), in-memory adapter (testing/Node), pending queue management. | Complete |
| Phase 5 | @bhoomash/syncraft-client | Client connection manager, room join/leave, offline queue flush, backoff reconnection, state synchronization. | Complete |
| Phase 6 | @bhoomash/syncraft-server | Node.js WebSocket relay server, room isolation, message validation, client tracking, ACK broadcasting. | Complete |
| Phase 7 | Offline Sync & Reconnection | End-to-end integration tests for Client A ↔ Server ↔ Client B, offline mutation recovery, and convergence. | Complete |
| Phase 8 | @bhoomash/syncraft-react | React hooks (useSyncStore, useSyncState, useConnectionStatus, useSyncRoom, SyncProvider). |
Complete |
| Phase 9 | Example Applications | Vanilla JS, React, and Collaborative Text/Document Editor examples (examples/). |
Complete |
| Phase 10 | Documentation & Polish | Full API docs, CRDT architecture guide, npm publishing configuration, benchmarks, and CI setup. | Complete |
Syncraft includes ready-to-use npm publishing scripts and GitHub Actions continuous integration:
# Verify all package bundles (dry run)
npm run publish:dry-run
# Publish all workspaces to the public NPM registry under @bhoomash scope
npm run publish:allContinuous integration runs on every push and pull request via .github/workflows/ci.yml across Node 18, 20, and 22 LTS.
- Guarantees:
- Eventual state convergence across all replicas receiving the same valid set of operations.
- Strict idempotency: duplicate delivery will never corrupt or alter state.
- Total logical ordering independent of physical clock drift.
- Limitations:
- Last-Write-Wins (LWW) resolves conflicting field assignments at the path level; it does not perform character-by-character Operational Transformation (OT) on primitive strings without a sequence CRDT (e.g., RGA or Fugue, planned for future extension).
- Tombstones and operation histories grow over time and benefit from periodic snapshot compaction.
MIT © 2026 Bhoomash and Syncraft Contributors