Stability: Experimental — see Stability Tiers.
The sync-coordinator package provides a production-ready, standalone backend server for the Quereus Sync protocol. It serves as both a reference implementation and a deployable service for multi-master CRDT replication.
- Production Ready: Built with Fastify for high performance; suitable for direct deployment
- Transport Flexibility: Supports both HTTP polling and WebSocket real-time push
- Extensible Service Layer: Hook-based architecture for validation, authentication, and custom logic
- Configurable: Data directory, CORS, auth mode, logging verbosity all configurable
- Developer-Friendly: Clear separation of concerns; easy to understand and extend
┌─────────────────────────────────────────────────────────────────────────────┐
│ sync-coordinator │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ CLI Entry Point (bin/sync-coordinator.js) │ │
│ │ • Parses command line args (Commander) │ │
│ │ • Loads configuration from file/env/args │ │
│ │ • Bootstraps and starts the server │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Configuration Layer │ │
│ │ • Data directory path │ │
│ │ • CORS settings (origins, credentials) │ │
│ │ • Auth mode (none, token-whitelist, custom hook) │ │
│ │ • Debug logging namespaces │ │
│ │ • Sync settings (batch size, tombstone TTL) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ CoordinatorService (Service Layer) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Validation Hooks │ │ │
│ │ │ • onAuthenticate(request) → Promise<AuthResult> │ │ │
│ │ │ • onAuthorize(client, operation) → Promise<boolean> │ │ │
│ │ │ • onBeforeApplyChanges(client, changes) → Promise<ValidationResult> │ │
│ │ │ • onAfterApplyChanges(client, changes, result) → void │ │ │
│ │ │ • onClientConnect(client) → Promise<boolean> │ │ │
│ │ │ • onClientDisconnect(client) → void │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Core Operations │ │ │
│ │ │ • handleSync(client, sinceHLC) → ChangeSet[] │ │ │
│ │ │ • handleApplyChanges(client, changes) → ApplyResult │ │ │
│ │ │ • handleSnapshot(client) → AsyncIterable<SnapshotChunk> │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Client Sessions (for WebSocket) │ │ │
│ │ │ • Map<connectionId, ClientSession> │ │ │
│ │ │ • Tracks siteId, lastSyncHLC, auth state │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴────────────────────────┐ │
│ ▼ ▼ │
│ ┌───────────────────────┐ ┌───────────────────────────┐ │
│ │ HTTP Routes │ │ WebSocket Handler │ │
│ │ (Fastify) │ │ (Fastify WebSocket) │ │
│ │ │ │ │ │
│ │ POST /sync/changes │ │ • Connection handshake │ │
│ │ GET /sync/changes │ │ • Bidirectional messages │ │
│ │ GET /sync/snapshot │ │ • Push notifications │ │
│ │ GET /sync/status │ │ • Automatic reconnect │ │
│ │ POST /sync/apply │ │ (client-side) │ │
│ └───────────────────────┘ └───────────────────────────┘ │
│ │ │ │
│ └───────────────────────┬────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ SyncManager │ │
│ │ (from quereus-sync) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ LevelDB Store │ │
│ │ (from quereus-store) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
The CoordinatorService is the heart of the sync-coordinator. It wraps the SyncManager from quereus-sync and adds:
- Validation Hooks: Extensible callbacks for custom business logic
- Client Session Management: Tracks connected clients and their state
- Broadcast Capabilities: Notifies connected WebSocket clients of changes
Hooks allow customization without modifying core code. All hooks are optional; defaults allow all operations.
interface CoordinatorHooks {
/**
* Authenticate an incoming request/connection.
* Called before any sync operation.
* Return client identity or throw to reject.
*/
onAuthenticate?(context: AuthContext): Promise<ClientIdentity>;
/**
* Authorize a specific operation for a client.
* Called after authentication, before executing the operation.
*/
onAuthorize?(client: ClientIdentity, operation: SyncOperation): Promise<boolean>;
/**
* Validate changes before applying them.
* Can modify, filter, or reject changes.
*/
onBeforeApplyChanges?(
client: ClientIdentity,
changes: ChangeSet[]
): Promise<{ approved: ChangeSet[]; rejected: RejectedChange[] }>;
/**
* Called after changes are successfully applied.
* Useful for logging, metrics, or triggering side effects.
*/
onAfterApplyChanges?(
client: ClientIdentity,
changes: ChangeSet[],
result: ApplyResult
): void;
/**
* Called when a WebSocket client connects.
* Return false to reject the connection.
*/
onClientConnect?(client: ClientIdentity, socket: WebSocket): Promise<boolean>;
/**
* Called when a WebSocket client disconnects.
*/
onClientDisconnect?(client: ClientIdentity): void;
}For WebSocket connections, the coordinator maintains client sessions:
interface ClientSession {
connectionId: string; // Unique connection identifier
siteId: SiteId; // Client's replica site ID
identity: ClientIdentity; // Authenticated identity
lastSyncHLC: HLC | undefined; // Last HLC client synced to
connectedAt: number; // Timestamp
socket: WebSocket; // The WebSocket connection
}Sessions are created on WebSocket handshake and destroyed on disconnect. HTTP requests are stateless but still go through authentication.
All endpoints are prefixed with /sync (configurable).
| Method | Path | Description |
|---|---|---|
GET |
/status |
Health check; returns coordinator status and stats |
GET |
/changes |
Get changes since ?sinceHLC=... for requesting client |
POST |
/changes |
Apply changes from client; body is ChangeSet[] |
GET |
/snapshot |
Stream full snapshot for initial sync |
POST |
/snapshot |
Resume snapshot from checkpoint |
| Header | Description |
|---|---|
Authorization |
Auth token (when auth mode requires it) |
X-Site-Id |
Client's site ID (required for all sync operations) |
X-Since-HLC |
Alternative to query param for GET /changes |
All responses are JSON with consistent structure:
// Success
{ "ok": true, "data": <result> }
// Error
{ "ok": false, "error": { "code": "...", "message": "..." } }WebSocket connections provide real-time bidirectional sync.
- Client connects to
/sync/ws - Client sends
{ type: "handshake", siteId: "...", token?: "...", protocolVersion: <int> } - Server checks
protocolVersionbefore authenticating. If it is absent or not equal to the server'sPROTOCOL_VERSION, the server replies{ type: "error", code: "PROTOCOL_VERSION_MISMATCH", fatal: true }and closes the socket with code4003— the store is never touched. Otherwise it authenticates and responds{ type: "handshake_ack", serverSiteId: "...", protocolVersion: <int> }. - Connection is established; client is added to session registry
The version check is strict integer equality: a peer that predates versioning
(sends no protocolVersion) is treated as incompatible, not silently accepted.
See sync-protocol.md § Protocol version for the rationale.
Client → Server:
| { type: "handshake"; siteId: string; token?: string; protocolVersion: number }
| { type: "get_changes"; sinceHLC?: HLC }
| { type: "apply_changes"; changes: ChangeSet[] }
| { type: "get_snapshot" }
| { type: "resume_snapshot"; checkpoint: SerializedSnapshotCheckpoint }
| { type: "ping" }SerializedSnapshotCheckpoint is the JSON-safe form of SnapshotCheckpoint
(base64 siteId and hlc); the raw checkpoint holds a Uint8Array and a
bigint, which JSON.stringify cannot emit. Senders build it with
serializeSnapshotCheckpoint; the coordinator decodes it with
deserializeSnapshotCheckpoint before handing it to the service.
Server → Client:
| { type: "handshake_ack"; serverSiteId: string; protocolVersion: number }
| { type: "changes"; changeSets: ChangeSet[] }
| { type: "apply_result"; result: ApplyResult }
| { type: "snapshot_chunk"; chunk: SnapshotChunk }
| { type: "snapshot_complete" } // Signals successful end of snapshot stream
| { type: "push_changes"; changeSets: ChangeSet[] } // Server pushes new changes
| { type: "error"; code: string; message: string; fatal?: boolean }
| { type: "pong" }When any client applies changes, the coordinator broadcasts to all other connected clients:
{ type: "push_changes", changeSets: [...] }Clients can immediately apply these or request a full sync.
Configuration is loaded from (in priority order):
- Command-line arguments
- Environment variables
- Configuration file (
sync-coordinator.jsonor--configpath) - Defaults
interface CoordinatorConfig {
// Server
host: string; // Default: "0.0.0.0"
port: number; // Default: 3000
basePath: string; // Default: "/sync"
// Data storage
dataDir: string; // Default: "./.data"
// CORS
cors: {
origin: string | string[] | boolean; // Default: true (all origins)
credentials: boolean; // Default: true
};
// Authentication
auth: {
mode: "none" | "token-whitelist" | "custom";
tokens?: string[]; // For token-whitelist mode
};
// Sync settings (passed to SyncManager)
sync: {
retentionHorizonMs: number; // Default: 30 days (ms)
batchSize: number; // Default: 1000
};
// Logging
logging: {
level: "debug" | "info" | "warn" | "error";
namespaces: string; // debug-style namespace filter
};
}| Variable | Description |
|---|---|
SYNC_HOST |
Server host |
SYNC_PORT |
Server port |
SYNC_DATA_DIR |
Data directory path |
SYNC_CORS_ORIGIN |
CORS origin(s), comma-separated |
SYNC_AUTH_MODE |
Authentication mode |
SYNC_AUTH_TOKENS |
Comma-separated token whitelist |
SYNC_RETENTION_HORIZON_MS |
Retention horizon in ms (changes older than this are not guaranteed deliverable; default 30 days) |
SYNC_BATCH_SIZE |
Maximum changes per sync batch |
DEBUG |
Debug logging namespaces |
# Start with defaults
npx sync-coordinator
# Start with custom config
npx sync-coordinator --config ./my-config.json
# Start with CLI overrides
npx sync-coordinator --port 8080 --data-dir ./data --auth-mode none
# Full options
npx sync-coordinator \
--host 0.0.0.0 \
--port 3000 \
--data-dir ./data \
--cors-origin "http://localhost:5173,https://myapp.com" \
--auth-mode token-whitelist \
--auth-tokens "token1,token2" \
--log-level debugThe coordinator uses the debug library (same as Quereus core) for configurable logging:
# Enable all coordinator logs
DEBUG=sync-coordinator:*
# Enable only WebSocket and error logs
DEBUG=sync-coordinator:ws,sync-coordinator:*:error
# Enable underlying sync manager logs too
DEBUG=sync-coordinator:*,quereus:sync:*| Namespace | Description |
|---|---|
sync-coordinator:server |
Server lifecycle events |
sync-coordinator:http |
HTTP request/response logging |
sync-coordinator:ws |
WebSocket connection events |
sync-coordinator:service |
Service layer operations |
sync-coordinator:auth |
Authentication events |
For custom deployments, import and extend the service:
import { createCoordinatorServer, loadConfig, type CoordinatorHooks } from 'sync-coordinator';
const hooks: CoordinatorHooks = {
async onAuthenticate(ctx) {
// Custom auth: verify JWT, check database, etc.
const user = await verifyToken(ctx.token);
return { userId: user.id, siteId: ctx.siteId! };
},
async onAuthorize(client, operation) {
// Check permissions
return hasPermission(client.userId, operation);
},
async onBeforeApplyChanges(client, changes) {
// Validate business rules
return validateChanges(changes);
}
};
const config = loadConfig({
overrides: {
port: 3000,
dataDir: './data',
cors: { origin: ['https://myapp.com'] }
}
});
const server = await createCoordinatorServer({ config, hooks });
await server.start();packages/sync-coordinator/
├── src/
│ ├── bin/
│ │ └── sync-coordinator.ts # CLI entry point
│ ├── config/
│ │ ├── index.ts # Config exports
│ │ ├── types.ts # Config type definitions
│ │ └── loader.ts # Config loading logic
│ ├── service/
│ │ ├── index.ts # Service exports
│ │ ├── types.ts # Hook and session types
│ │ └── coordinator-service.ts # Main service class
│ ├── server/
│ │ ├── index.ts # Server exports
│ │ ├── routes.ts # HTTP route definitions
│ │ ├── websocket.ts # WebSocket handler
│ │ └── create-server.ts # Fastify server factory
│ ├── common/
│ │ └── logger.ts # Debug logger setup
│ └── index.ts # Public API exports
├── test/
│ ├── service.spec.ts # Service layer tests
│ ├── routes.spec.ts # HTTP route tests
│ └── websocket.spec.ts # WebSocket tests
├── package.json
├── tsconfig.json
└── README.md
-
Always enable authentication in production: The default
auth: "none"is for development only. -
Use HTTPS in production: The coordinator doesn't handle TLS directly; deploy behind a reverse proxy (nginx, Caddy) or load balancer.
-
Validate client siteIds: The
onAuthenticatehook should verify that the claimed siteId matches the authenticated user's registered devices. -
Rate limiting: Implement rate limiting in the
onAuthorizehook or at the reverse proxy level. -
Input validation: The coordinator validates sync protocol messages but application-level validation should be done in
onBeforeApplyChanges.
- HTTP polling: Suitable for low-frequency sync (minutes). Higher latency, simpler client implementation.
- WebSocket: Recommended for real-time applications. Sub-second sync latency, persistent connections.
- Snapshot streaming: Memory-efficient chunked transfer for large datasets.
- LevelDB backend: Fast local storage; suitable for single-node deployments. For multi-node, use a shared database backend (requires custom KVStore implementation).
- Package structure and build setup
- Configuration system with CLI, env, and file support
- CoordinatorService with hook infrastructure
- HTTP routes (Fastify)
- WebSocket handler with session management
- CORS middleware
- Token-whitelist authentication mode
- CLI entry point with Commander
- Unit tests for service and config
- Integration tests for HTTP and WebSocket
- Metrics endpoint (Prometheus format)
- Admin API for runtime configuration
- Client SDK helpers for common patterns
- Docker image and Helm chart