法不净空,觉无性也。

Offline Sync Engine

2026.01.20

Local-First 架构的离线同步引擎,针对低带宽环境优化

中文版 | English

完整的离线同步解决方案,采用 Local-First 架构设计。应用可以完全离线运行,以本地存储作为主数据源,同时在后台自动与服务器同步。针对不稳定的网络条件(如非洲地区的 2G/3G 网络)进行了优化,支持数据压缩、断点续传和智能冲突解决。

[Build Status [TypeScript [License

✨ 特性

核心能力

  • 🌐 完全离线支持 - 基于 IndexedDB 本地存储,可完全离线工作
  • 🔄 自动同步 - 检测到网络后自动同步
  • 增量同步 - 仅传输变更数据,节省带宽
  • 🗜️ Outbox 模式 - 拦截写操作,本地排队,可靠同步
  • 🧠 智能冲突解决 - Last-Write-Wins (LWW) + 向量时钟
  • 📱 跨平台 - 基于 RxDB,支持 Web 和移动端

高级特性

  • 📦 数据压缩 - MessagePack + DEFLATE,减少 40-60% 数据量
  • 📤 断点续传 - 完整的 TUS 协议实现,支持大文件上传
  • 性能优化 - 批量操作、索引优化、查询缓存
  • 🔌 实时推送 - WebSocket 服务端推送通知
  • 🛡️ 类型安全 - 端到端 TypeScript 支持

📐 架构概览

┌─────────────────────────────────────────────────────────────┐
│                        客户端应用                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │                    UI 层 (React)                       │ │
│  └──────────────────────┬─────────────────────────────────┘ │
│                         │                                   │
│  ┌──────────────────────▼─────────────────────────────────┐ │
│  │                 离线 SDK (@offline-sync/sdk)            │ │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌────────┐     │ │
│  │  │ 存储层   │  │ 网络管理 │  │ Outbox  │  │ 同步    │     │ │
│  │  │ (RxDB)  │  │  器     │  │ (队列)   │  │管理器   │     │ │
│  │  └────┬────┘  └────┬────┘  └────┬────┘  └───┬────┘     │ │
│  │       │            │            │            │         │ │
│  │  ┌───▼────────────▼────────────▼────────────▼───────┐  │ │
│  │  │           IndexedDB (浏览器本地存储)               │  │ │
│  │  └──────────────────────────────────────────────────┘  │ │
│  └────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────┘
                               │ HTTPS (压缩传输)
┌─────────────────────────────────────────────────────────────┐
│                    同步网关服务器                             │
│  ┌──────────────┐  ┌──────────┐  ┌─────────┐  ┌────────┐    │
│  │   网关        │  │ 应用器    │  │ 仲裁器   │  │   TUS  │    │
│  │  (路由)       │  │(应用操作) │  │(冲突解决) │  │(断点续传)│   │
│  └──────┬───────┘  └────┬─────┘  └────┬─────┘  └────┬───┘   │
│         │               │             │             │       │
│  ┌──────▼──────────────▼─────────────▼─────────────▼───┐    │
│  │                 CouchDB (主数据库)                    │   │
│  │  - todos, products, customers, orders                │   │
│  │  - _changes feed 用于增量同步                          │   │
│  │  - Mango Query 支持                                   │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

🚀 快速开始

安装

# 克隆仓库
git clone https://github.com/iannil/offline-sync-engine.git
cd offline-sync-engine

# 安装依赖
pnpm install

运行开发服务器

# 启动服务端 (端口 3000)
pnpm dev:server

# 启动客户端演示 (端口 5173)
pnpm dev:client

构建

# 构建 SDK
pnpm --filter @offline-sync/sdk build

# 构建服务端
pnpm --filter @offline-sync/server build

# 构建演示应用
pnpm --filter @offline-sync/client-demo build

💻 使用示例

SDK 基础用法

import { OfflineClient } from '@offline-sync/sdk';

// 初始化客户端
const client = new OfflineClient({
  database: { name: 'my-app' },
  sync: {
    enabled: true,
    url: 'https://api.example.com/sync',
    interval: 30000,  // 每 30 秒同步一次
    enableCompression: true,
  },
});

// 等待客户端就绪
await client.initialize();

// 获取数据库
const db = client.getDatabase();

// 创建待办事项 (离线 + 自动同步)
const todo = await db.todos.insert({
  id: 'todo-1',
  text: '学习离线同步引擎',
  completed: false,
  createdAt: new Date().toISOString(),
  updatedAt: new Date().toISOString(),
});

// 手动触发同步
await client.getSyncManager().triggerSync();

// 监听同步状态
client.getSyncManager().onStateChange((state) => {
  console.log('同步中:', state.isSyncing);
  console.log('待同步数量:', state.pendingCount);
});

TUS 断点续传

import { createTusUpload } from '@offline-sync/sdk/storage';

// 创建文件上传
const uploader = createTusUpload({
  endpoint: 'https://api.example.com/api/tus',
  data: file,
  metadata: {
    filename: file.name,
    type: file.type,
  },
  chunkSize: 5 * 1024 * 1024,  // 5MB 分片
  onProgress: (sent, total) => {
    console.log(`进度: ${(sent / total * 100).toFixed(1)}%`);
  },
});

// 开始上传
const uploadUrl = await uploader.start();

// 暂停上传
uploader.pause();

// 恢复上传 (支持断点续传)
await uploader.resume();

服务端 API

# 推送本地操作到服务器
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'

# 拉取服务器变更
curl "https://api.example.com/api/sync/pull?since=1234567890" \
  -H "Accept: application/msgpack+deflate"

# TUS 创建上传
curl -X POST https://api.example.com/api/tus \
  -H "Tus-Resumable: 1.0.0" \
  -H "Upload-Length: 1024000" \
  -H "Upload-Metadata: filename dGVzdC5qcGc="

📦 包结构

offline-sync-engine/
├── packages/
│   ├── sdk/              # 客户端 SDK
│   │   ├── src/
│   │   │   ├── storage/     # 存储模块
│   │   │   ├── network/     # 网络管理
│   │   │   ├── outbox/      # 离线队列
│   │   │   ├── sync/        # 同步管理
│   │   │   └── client/      # 客户端入口
│   │   └── package.json
│   │
│   ├── server/           # 同步网关服务器
│   │   ├── src/
│   │   │   ├── gateway/     # 同步网关
│   │   │   ├── applier/     # 操作应用器
│   │   │   ├── arbiter/     # 冲突仲裁器
│   │   │   ├── database/    # 数据库层
│   │   │   └── tus/         # TUS 协议
│   │   └── package.json
│   │
│   └── client-demo/       # 演示应用
│       ├── src/
│       │   ├── components/
│       │   └── db/
│       └── package.json
├── docs/                 # 文档
├── pnpm-workspace.yaml  # Monorepo 配置
└── package.json

🔧 配置

SDK 配置

interface OfflineClientConfig {
  // 数据库配置
  database: {
    name: string;              // 数据库名称
  };

  // 同步配置
  sync?: {
    enabled: boolean;         // 启用同步
    url: string;              // 同步服务器 URL
    interval?: number;        // 同步间隔 (毫秒)
    batchSize?: number;       // 批量大小
    enableCompression?: boolean;  // 启用压缩
    enableWebSocket?: boolean;    // 启用 WebSocket
  };

  // Outbox 配置
  outbox?: {
    maxRetries?: number;      // 最大重试次数
    initialDelay?: number;    // 初始重试延迟 (毫秒)
    maxDelay?: number;        // 最大重试延迟 (毫秒)
  };
}

服务端配置

# 环境变量
COUCHDB_URL=http://localhost:5984
COUCHDB_USERNAME=admin
COUCHDB_PASSWORD=password
COUCHDB_DB_PREFIX=offline-sync
PORT=3000
HOST=0.0.0.0

📚 API 文档

SDK 导出

// 客户端
import { OfflineClient } from '@offline-sync/sdk/client';

// 存储
import {
  createDatabase,
  getDatabase,
  todoSchema,
  productSchema,
} from '@offline-sync/sdk/storage';

// 查询
import {
  findAll,
  findById,
  findWhere,
  paginate,
  count,
  QueryBuilder,
} from '@offline-sync/sdk/storage';

// 压缩
import {
  CompressionService,
  compress,
  decompress,
} from '@offline-sync/sdk/storage';

// TUS 协议
import {
  createTusUpload,
  uploadFile,
  TusUploader,
} from '@offline-sync/sdk/storage';

// 测试
import {
  benchmarkWrite,
  benchmarkRead,
  benchmarkQuery,
  testCapacity,
} from '@offline-sync/sdk/testing';

// 类型
import type { Todo, Product, OutboxAction, NetworkStatus } from '@offline-sync/sdk';

服务端端点

端点方法描述
/healthGET健康检查
/api/sync/pushPOST推送本地操作
/api/sync/pullGET拉取远程变更
/api/sync/:collectionGET获取集合数据
/api/sync/:collection/:idGET获取单个文档
/api/applier/applyPOST应用单个操作
/api/applier/batchPOST批量应用操作
/api/arbiter/checkPOST冲突检测
/api/arbiter/resolvePOSTLWW 冲突解决
/api/arbiter/resolve/mergePOST字段级合并
/api/tusPOST创建上传
/api/tus/:idPATCH上传分片
/api/streamWS实时推送

🧪 开发

环境要求

  • Node.js >= 18
  • pnpm >= 8
  • CouchDB >= 3.0 (可选,用于生产环境)

开发命令

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev:server  # 服务端
pnpm dev:client  # 客户端

# 运行测试
pnpm test

# 代码检查
pnpm lint
pnpm format

本地 CouchDB 开发

# 使用 Docker 启动 CouchDB
docker run -d \
  --name couchdb \
  -p 5984:5984 \
  -e COUCHDB_USER=admin \
  -e COUCHDB_PASSWORD=password \
  couchdb:3

代码规范

  • 使用 TypeScript 编写代码
  • 遵循 ESLint 规则
  • 为新功能添加单元测试
  • 更新相关文档

🔗 技术栈

类别技术
前端框架React + TypeScript
本地数据库RxDB + Dexie (IndexedDB)
后端框架Fastify (Node.js)
主数据库CouchDB
数据序列化MessagePack
数据压缩DEFLATE (pako)
断点续传TUS 协议 v1.0.0
实时通信WebSocket
包管理器pnpm workspaces
构建工具tsup (库) + Vite (应用)
测试框架Vitest

📄 致谢

本项目构建于以下优秀的开源项目之上: