Offline Sync Engine
2026.01.20An offline sync engine for Local-First architecture, optimized for low-bandwidth environments.
Chinese version | English
A complete offline sync solution designed with a Local-First architecture. Applications can run entirely offline, using local storage as the primary data source, while automatically synchronizing with the server in the background. Optimized for unstable network conditions—such as 2G/3G networks in Africa—with support for data compression, resumable transfers, and intelligent conflict resolution.
✨ Features
Core Capabilities
- 🌐 Full Offline Support - Built on IndexedDB local storage, fully functional offline
- 🔄 Automatic Sync - Automatically synchronizes when network is detected
- ⚡ Incremental Sync - Transmits only changed data, conserving bandwidth
- 🗜️ Outbox Pattern - Intercepts write operations, queues locally, ensures reliable sync
- 🧠 Intelligent Conflict Resolution - Last-Write-Wins (LWW) + Vector Clocks
- 📱 Cross-Platform - Built on RxDB, supports Web and mobile platforms
Advanced Features
- 📦 Data Compression - MessagePack + DEFLATE, reduces data size by 40–60%
- 📤 Resumable Transfers - Full TUS protocol implementation, supports large file uploads
- ⚡ Performance Optimization - Batch operations, indexed optimizations, query caching
- 🔌 Real-Time Push - WebSocket server-side notification push
- 🛡️ Type Safety - End-to-end TypeScript support
📐 Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ Client Application │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ UI Layer (React) │ │
│ └──────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────┐ │
│ │ Offline SDK (@offline-sync/sdk) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │ │
│ │ │ Storage │ │ Network │ │ Outbox │ │ Sync │ │ │
│ │ │ Layer │ │ Manager │ │ (Queue) │ │ Manager│ │ │
│ │ │ (RxDB) │ │ │ │ │ │ │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └───┬────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌───▼────────────▼────────────▼────────────▼───────┐ │ │
│ │ │ IndexedDB (Browser Local Storage) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────┘
│ HTTPS (compressed transfer)
▼
┌─────────────────────────────────────────────────────────────┐
│ Sync Gateway Server │
│ ┌──────────────┐ ┌──────────┐ ┌─────────┐ ┌────────┐ │
│ │ Gateway │ │ Applicator│ │ Arbiter │ │ TUS │ │
│ │ (Routing) │ │(Apply Ops)│ │(Conflict│ │(Resume)│ │
│ │ │ │ │ │ Resolution)│ │ │
│ └──────┬───────┘ └────┬─────┘ └────┬─────┘ └────┬───┘ │
│ │ │ │ │ │
│ ┌──────▼──────────────▼─────────────▼─────────────▼───┐ │
│ │ CouchDB (Primary Database) │ │
│ │ - todos, products, customers, orders │ │
│ │ - _changes feed for incremental sync │ │
│ │ - Mango Query support │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
🚀 Quick Start
Installation
# Clone the repository
git clone https://github.com/iannil/offline-sync-engine.git
cd offline-sync-engine
# Install dependencies
pnpm install
Run the Development Server
# Start the server (port 3000)
pnpm dev:server
# Start the client demo (port 5173)
pnpm dev:client
Build
# Build the SDK
pnpm --filter @offline-sync/sdk build
# Build the server
pnpm --filter @offline-sync/server build
# Build the demo app
pnpm --filter @offline-sync/client-demo build
💻 Usage Examples
Basic SDK Usage
import { OfflineClient } from '@offline-sync/sdk';
// Initialize the client
const client = new OfflineClient({
database: { name: 'my-app' },
sync: {
enabled: true,
url: 'https://api.example.com/sync',
interval: 30000, // Sync every 30 seconds
enableCompression: true,
},
});
// Wait for the client to be ready
await client.initialize();
// Get the database
const db = client.getDatabase();
// Create a to-do (offline + automatic sync)
const todo = await db.todos.insert({
id: 'todo-1',
text: 'Learn the offline sync engine',
completed: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
// Trigger sync manually
await client.getSyncManager().triggerSync();
// Listen to sync state changes
client.getSyncManager().onStateChange((state) => {
console.log('Syncing:', state.isSyncing);
console.log('Pending items:', state.pendingCount);
});
TUS Resumable Uploads
import { createTusUpload } from '@offline-sync/sdk/storage';
// Create a file upload
const uploader = createTusUpload({
endpoint: 'https://api.example.com/api/tus',
data: file,
metadata: {
filename: file.name,
type: file.type,
},
chunkSize: 5 * 1024 * 1024, // 5MB chunks
onProgress: (sent, total) => {
console.log(`Progress: ${(sent / total * 100).toFixed(1)}%`);
},
});
// Start upload
const uploadUrl = await uploader.start();
// Pause upload
uploader.pause();
// Resume upload (supports resumable transfer)
await uploader.resume();
Server API
# Push local operations to server
curl -X POST https://api.example.com/api/sync/push \
-H "Content-Type: application/msgpack+deflate" \
-H "Accept: application/msgpack+deflate" \
--data-binary '@payload.bin'
# Pull server changes
curl "https://api.example.com/api/sync/pull?since=1234567890" \
-H "Accept: application/msgpack+deflate"
# TUS create upload
curl -X POST https://api.example.com/api/tus \
-H "Tus-Resumable: 1.0.0" \
-H "Upload-Length: 1024000" \
-H "Upload-Metadata: filename dGVzdC5qcGc="
📦 Package Structure
offline-sync-engine/
├── packages/
│ ├── sdk/ # Client SDK
│ │ ├── src/
│ │ │ ├── storage/ # Storage module
│ │ │ ├── network/ # Network manager
│ │ │ ├── outbox/ # Offline queue
│ │ │ ├── sync/ # Sync manager
│ │ │ └── client/ # Client entrypoint
│ │ └── package.json
│ │
│ ├── server/ # Sync gateway server
│ │ ├── src/
│ │ │ ├── gateway/ # Sync gateway
│ │ │ ├── applier/ # Operation applier
│ │ │ ├── arbiter/ # Conflict arbiter
│ │ │ ├── database/ # Database layer
│ │ │ └── tus/ # TUS protocol
│ │ └── package.json
│ │
│ └── client-demo/ # Demo application
│ ├── src/
│ │ ├── components/
│ │ └── db/
│ └── package.json
│
├── docs/ # Documentation
├── pnpm-workspace.yaml # Monorepo configuration
└── package.json
🔧 Configuration
SDK Configuration
interface OfflineClientConfig {
// Database configuration
database: {
name: string; // Database name
};
// Sync configuration
sync?: {
enabled: boolean; // Enable sync
url: string; // Sync server URL
interval?: number; // Sync interval (milliseconds)
batchSize?: number; // Batch size
enableCompression?: boolean; // Enable compression
enableWebSocket?: boolean; // Enable WebSocket
};
// Outbox configuration
outbox?: {
maxRetries?: number; // Maximum retry attempts
initialDelay?: number; // Initial retry delay (milliseconds)
maxDelay?: number; // Maximum retry delay (milliseconds)
};
}
Server Configuration
# Environment variables
COUCHDB_URL=http://localhost:5984
COUCHDB_USERNAME=admin
COUCHDB_PASSWORD=password
COUCHDB_DB_PREFIX=offline-sync
PORT=3000
HOST=0.0.0.0
📚 API Documentation
SDK Exports
// Client
import { OfflineClient } from '@offline-sync/sdk/client';
// Storage
import {
createDatabase,
getDatabase,
todoSchema,
productSchema,
} from '@offline-sync/sdk/storage';
// Query
import {
findAll,
findById,
findWhere,
paginate,
count,
QueryBuilder,
} from '@offline-sync/sdk/storage';
// Compression
import {
CompressionService,
compress,
decompress,
} from '@offline-sync/sdk/storage';
// TUS Protocol
import {
createTusUpload,
uploadFile,
TusUploader,
} from '@offline-sync/sdk/storage';
// Testing
import {
benchmarkWrite,
benchmarkRead,
benchmarkQuery,
testCapacity,
} from '@offline-sync/sdk/testing';
// Types
import type { Todo, Product, OutboxAction, NetworkStatus } from '@offline-sync/sdk';
Server Endpoints
| Endpoint | Method | Description |
|---|---|---|
/health | GET | Health check |
/api/sync/push | POST | Push local operations |
/api/sync/pull | GET | Pull remote changes |
/api/sync/:collection | GET | Get collection data |
/api/sync/:collection/:id | GET | Get single document |
/api/applier/apply | POST | Apply single operation |
/api/applier/batch | POST | Batch apply operations |
/api/arbiter/check | POST | Conflict detection |
/api/arbiter/resolve | POST | LWW conflict resolution |
/api/arbiter/resolve/merge | POST | Field-level merge |
/api/tus | POST | Create upload |
/api/tus/:id | PATCH | Upload chunk |
/api/stream | WS | Real-time push |
🧪 Development
Environment Requirements
- Node.js >= 18
- pnpm >= 8
- CouchDB >= 3.0 (optional, for production)
Development Commands
# Install dependencies
pnpm install
# Start development servers
pnpm dev:server # server
pnpm dev:client # client
# Run tests
pnpm test
# Lint and format code
pnpm lint
pnpm format
Local CouchDB Development
# Start CouchDB with Docker
docker run -d \
--name couchdb \
-p 5984:5984 \
-e COUCHDB_USER=admin \
-e COUCHDB_PASSWORD=password \
couchdb:3
Code Standards
- Write code in TypeScript
- Follow ESLint rules
- Add unit tests for new features
- Update relevant documentation
🔗 Technology Stack
| Category | Technology |
|---|---|
| Frontend Framework | React + TypeScript |
| Local Database | RxDB + Dexie (IndexedDB) |
| Backend Framework | Fastify (Node.js) |
| Primary Database | CouchDB |
| Data Serialization | MessagePack |
| Data Compression | DEFLATE (pako) |
| Resumable Transfers | TUS Protocol v1.0.0 |
| Real-Time Communication | WebSocket |
| Package Manager | pnpm workspaces |
| Build Tools | tsup (library) + Vite (application) |
| Testing Framework | Vitest |
📄 Acknowledgments
This project is built upon the following excellent open-source projects:
- RxDB - JavaScript NoSQL database
- Fastify - High-performance Node.js web framework
- Nano - CouchDB client
- MessagePack - Efficient binary serialization
- TUS Protocol - Resumable upload protocol
- Pako - zlib interface