Hotdry.

Article

TinyCld 本地优先 Workspace 数据架构与多端同步机制

解析开源 Workspace 套件 TinyCld 的本地优先架构设计:SQLite 单文件存储、CRDTs 实时协作、SSE 多端同步与标准协议桥接的工程实践。

2026-05-27web

本地优先 Workspace 的架构回归

当 Google Workspace 和 Microsoft 365 将数据锁死在云端时,开发者对「数据自主权」的诉求正在催生新一代开源替代方案。TinyCld 作为一套完整的 Workspace 套件(Mail/Calendar/Contacts/Drive/Text/Calc),其技术架构选择了一条「本地优先、多端同步」的路径 —— 数据物理存储在自托管服务器的 SQLite 文件中,但通过 CRDTs(无冲突复制数据类型)和 Server-Sent Events 实现实时协作,同时完整兼容 IMAP、CalDAV、CardDAV、WebDAV 等开放协议。

这种架构的核心价值在于:用户拥有数据的物理控制权,却能获得与商业云服务相当的使用体验。本文将从数据层、同步机制、模块化设计三个维度,拆解 TinyCld 如何实现本地优先与多端协同的平衡。

技术栈选型:Expo + PocketBase + Go 的分层逻辑

TinyCld 的技术栈呈现清晰的三层结构:

表现层:Expo Router 统一支撑 Web、iOS、Android 三端,通过同一套代码库实现跨平台渲染。这种选择避免了为移动端单独维护 Native 代码的成本,同时利用 Expo 的 EAS 构建服务实现 OTA 更新。

数据层:PocketBase 作为核心后端,提供 SQLite 数据库、实时订阅、文件存储、认证管理等基础设施。关键设计在于「单文件数据库」—— 整个应用状态收敛于一个 SQLite 文件,简化了备份、迁移和审计流程。

扩展层:Go 语言编写服务端扩展模块,处理邮件协议(IMAP/SMTP)、日历同步(CalDAV)、文件存储(WebDAV)等计算密集型任务。Go 的静态编译特性使最终产物保持单二进制文件部署。

这种分层并非简单的技术堆砌,而是围绕「可自托管」这一核心约束的刻意设计:PocketBase 的嵌入式架构消除了数据库运维负担,Expo 的跨平台能力降低了多端维护成本,Go 的高性能保障了协议兼容的实时性。

本地优先的数据层:SQLite + CRDTs 的协作模型

TinyCld 的 Text(文档)和 Calc(表格)模块采用 CRDTs 实现多人实时协作,这是本地优先架构的关键技术决策。

传统协作方案依赖中央服务器作为唯一数据源,客户端仅作为视图层。CRDTs 则允许每个客户端维护本地副本,通过数学上可合并的数据结构保证最终一致性。TinyCld 的文档编辑器支持「富文本格式、表格、评论」的实时协作,其底层数据模型必须具备以下特性:

  • 交换律:操作顺序不影响最终状态
  • 结合律:操作分组方式不影响合并结果
  • 幂等性:重复应用相同操作不会导致状态漂移

在存储层面,PocketBase 的 SQLite 作为「真相源」持久化 CRDTs 的序列化状态,而客户端通过 TanStack DB 实现响应式查询。这种设计使离线编辑成为可能 —— 用户在网络中断期间的操作被本地缓存,恢复连接后通过 CRDTs 的合并算法自动同步,无需手动冲突解决。

值得注意的是,Text 模块支持 .docx 和 Markdown 的「round-trip」转换,Calc 模块保留 CSV/.xlsx 的格式导入导出。这种格式兼容性降低了从商业套件迁移的摩擦,也体现了本地优先架构对「数据可移植性」的承诺。

多端同步机制:SSE 推送与标准协议桥接

实时同步是 Workspace 套件的核心体验。TinyCld 采用 Server-Sent Events(SSE)作为跨端推送通道,替代了传统的轮询或 WebSocket 方案。

SSE 的优势在于:基于 HTTP/1.1 的持久连接无需协议升级,与现有基础设施(负载均衡、CDN)兼容性好;自动重连和事件 ID 追踪机制简化了断线恢复逻辑。TinyCld 的「Live data」特性通过 useOrgLiveQuery Hook 封装,开发者无需关心连接管理,数据变更会自动触发 UI 更新。

然而,Workspace 套件必须兼容既有生态。TinyCld 通过协议桥接实现与标准客户端的互操作:

功能模块 标准协议 端口 兼容客户端
邮件 IMAP/SMTP 993/465 Apple Mail、Thunderbird、Outlook
日历 CalDAV 443 Apple Calendar、GNOME Calendar、DAVx5
联系人 CardDAV 443 Apple Contacts、GNOME Contacts
文件 WebDAV 443 macOS Finder、Windows Explorer、Linux Nautilus

这种「双轨制」设计 —— 现代客户端通过 SSE 获得实时体验,传统客户端通过标准协议保持兼容性 —— 使 TinyCld 既能作为独立应用使用,也能无缝融入用户现有的工具链。

模块化架构:Package SDK 与插件机制

TinyCld 的代码组织采用「Monorepo + 插件化」策略。核心仓库(tinycld/app)提供应用壳(app shell),功能模块(Mail、Calendar、Drive 等)作为独立 npm workspace 成员存在。

每个功能包通过 manifest.ts 声明其能力边界:

  • routes:Expo Router 路由定义
  • collections:PocketBase 集合与类型定义
  • server:Go 服务端扩展
  • pb-migrations:数据库迁移脚本
  • settings:管理面板配置项

代码生成器在 npm install 阶段自动拼接各包的路由、类型定义和服务端注册逻辑。这种设计使功能扩展无需修改核心代码 —— 开发者可以创建独立仓库实现自定义包,通过 npm workspace 链接即可集成。

对于需要深度定制的场景,Go 服务端扩展机制允许在核心 HTTP 路由之外注册自定义处理器。这种「渐进式扩展」能力使 TinyCld 既能作为开箱即用的 Workspace 使用,也能作为基础平台承载垂直业务开发。

部署与迁移:从云端到自托管的平滑过渡

本地优先架构的最终检验标准是部署复杂度。TinyCld 通过单 Docker 镜像实现「15 分钟部署」:

mkdir tinycld && cd tinycld
curl -O https://raw.githubusercontent.com/tinycld/app/main/docker-compose.yml
docker compose up -d

镜像内置 Let's Encrypt 自动证书管理、健康检查和标准协议服务,无需额外的反向代理或数据库配置。对于 $5 / 月的 VPS 即可承载中小团队使用。

迁移方面,TinyCld 提供 Google Takeout 的一键导入:

  1. 从 Google Takeout 导出 Mail、Calendar、Contacts、Drive 的压缩包
  2. 拖拽 ZIP 文件至 TinyCld 导入界面
  3. Web Worker 后台解析 .mbox.ics.vcf 格式
  4. 联系人按 vCard UID 去重,日历事件按 ICAL_UID 合并

导入过程的幂等性设计允许重复执行而不产生重复数据,降低了迁移风险。

实践建议:自托管参数配置与监控要点

对于计划自托管 TinyCld 的技术团队,以下配置参数值得关注:

存储层:SQLite 的 WAL(Write-Ahead Logging)模式建议开启,可提升并发写入性能。定期备份策略应针对单数据库文件设计,PocketBase 提供内置的自动备份钩子。

同步层:SSE 连接数受限于服务器文件描述符上限,高并发场景需调整 ulimit。移动端推送依赖 Expo Push 服务,若需完全离线运行需评估替代方案。

安全层:默认配置启用「邀请制注册」,无公开账号创建入口。邮件中的外部图片通过服务端代理加载,防止追踪像素泄露用户 IP 和阅读行为。

扩展层:Text 和 Calc 模块目前标记为「Lightly battle-tested」,生产环境建议先进行数据完整性测试。自定义包的 Go 扩展需注意内存管理,避免长期运行中的 Goroutine 泄漏。

结语

TinyCld 的架构设计展示了「本地优先」理念在现代 Web 应用中的可行性:通过 CRDTs 解决协作冲突,通过 SSE 实现实时同步,通过标准协议保持生态兼容,最终收敛于单文件 SQLite 的极简部署。这种架构不仅是对数据自主权的回归,也为开发者提供了一套可审计、可扩展、可迁移的 Workspace 基础设施。

在云服务锁定日益严重的当下,TinyCld 代表了一种技术路线的可能性 —— 数据物理归属与使用体验不必二选一。


资料来源

web

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com