Skip to content

Architecture.zh_CN

Doug Blank edited this page Oct 5, 2026 · 1 revision

🌐 English · Deutsch · Español · Français

架构

Gramps Connect 是一个 React 前端(app/),通过 REST API 与 gramps-web-api 后端通信——它没有分叉或取代 gramps-web-api,而是同一个服务器的另一个客户端。有两个机制让这个前端既快速又能协作:本地优先的缓存,以及构建在 gramps-web-api 已有端点之上的轻量级实时同步轮询。

本地优先缓存

一个 WASM 版本的 SQLite 在浏览器内运行,把服务器数据镜像到本地表中,并持久化到 OPFS(浏览器自己的私有文件系统),因此再次访问时可以完全跳过网络。数据通过 gramps-web-api 快速的、把工作下推到 SQL 的 /api/<type>/query/ 端点获取——GOQL 条件和 Gramplet 的 where= 子句最终也编译到这些端点——而不是逐个对象地分页读取完整的 REST 响应。

回报就是概览中描述的效果:一旦您查看过家谱的某一部分,再次浏览、排序和搜索这部分时都是瞬间完成的,即使家谱有数万人也是如此;而在一个普通的、没有索引的 REST 后端上,这类搜索可能要花一分多钟。

这也是为什么固定使用 admin/admin 的桌面版和真正的服务器部署可以共享完全相同的前端代码而无需任何特殊处理:缓存只关心它连接的是一个形如 gramps-web-api 的 /api/,而不关心由谁托管。

一个值得了解的陷阱:浏览器端缓存以每个视图固定的文件名为键,而不是以后端 URL 或家谱 ID 为键。切换所连接的后端,或者在同一后端上重新导入/重新创建家谱,可能会让浏览器配置文件继续提供过时的缓存行,且不会自动失效——唯一执行的检查是模式(schema)兼容性,而不是数据身份。如果切换后端后数据看起来过时,请手动清除:DevTools → Application → Storage → clear site data(或专门清除 OPFS)。

实时同步

客户端以较短的间隔轮询 gramps-web-api 已有的 GET /api/transactions/history/ 端点(它本来就提供的对象编辑审计/撤销日志,并非为此新增)。对于该端点报告为已更改的每个对象,Gramps Connect 都会重新获取,并只修补本地缓存中的那一行。

这刻意保持简单:不需要 WebSocket 式的持久连接,也不需要 Postgres 特有的变更数据捕获,只需一个定时执行的普通带认证 GET——因此它适用于任何 gramps-web-api 后端,而不仅仅是基于 Postgres 的后端。“别人更正了一个日期,您的屏幕会自动更新”(见概览)正是这样实现的;这也是开发测试环境无需真正的 Postgres 实例就能测试同步的原因——即使是纯 SQLite 的环境也支持,因为这只是一次轮询。

实时同步尚未实现的功能:没有在线状态层(谁正在查看或编辑什么),也没有面向用户的历史浏览器——见路线图与已知限制。

仓库结构

  • app/——生产环境的 React 客户端:全部十种对象类型视图(成员、家庭、事件、地点、文库、来源、引用、媒体、摘录、标签——见数据模型与编辑)、where_expr/GOQL 筛选、持久化到 OPFS 的 WASM SQLite 缓存,以及实时同步,底层是基于 useSyncExternalStore 的存储层(app/src/store/),并使用 @tanstack/react-virtual 实现滚动。
  • dev-fixtures/——在本地运行 app/ 所需的真实 gramps-web-api 后端;它们不属于发布的产品,只是让本地开发无需手动配置服务器。三种变体(layer2-local-cache/api-fixture、api-fixture-example 和 layer3-sync/api-fixture)及各自用途见开发。
  • packages/gramps-date/——Gramps Date 模型、历法转换和按区域设置显示日期功能的 TypeScript 移植版,供 app/ 使用,这样它就能渲染或构建 Gramps Date 结构,而无需为表格中的每一行都缓慢地往返调用 Gramps 自己的 Python 日期显示器。它支持五种历法的转换(公历、儒略历、法国共和历、伊斯兰历、瑞典历——希伯来历和波斯历可以正确显示,但输入时还无法校验,因为它们的 SDN 转换需要更多机制)、结构化的日期输入与校验,以及可插拔区域设置的显示(registerLocale();目前只提供英语)。它是把 Gramps 核心自己的 gramps/gen/lib/date.py/gcalendar.py/_datedisplay.py 从 Python 翻译成 TypeScript,并在自己的测试套件中与真正的 Python 实现交叉核对——完整的来源和许可说明见它自己的 README(它是并入这个 AGPL-3.0-or-later 项目的 GPL-2.0-or-later 代码,与 gramps-web 自己的 gcalendar.js 移植做法相同)。
  • standalone/——基于 PyInstaller 的 gramps-connect-desktop 构建:一个 Python 启动器(launcher.py),把 app/ 构建好的前端与 gramps-web-api 和 SQLite 打包成一个原生窗口(或回退到浏览器)应用。见安装。
  • deploy/——容器化的多用户部署(app/ + gramps-web-api + Postgres + Caddy + Redis/Celery)。见部署。
  • gramplet_examples/ 和 gramplet-store/——示例 Gramplet,以及应用内 Gramplet 商店目录的源内容。见 Gramplet。

根目录下的 npm 工作区(packages/*、app)把 app/ 和 packages/gramps-date 作为真正的工作区依赖联系在一起。app/ 所依赖的快速 /query/ 端点位于 gramps-web-api 本身(一个独立的仓库,原地扩展且向后兼容),通过 GOQL 自己的实现 gramps-object-query-language 提供——而不在本仓库中。

插件在浏览器中运行 Python

Gramplet——应用的插件——在 Pyodide(编译为 WebAssembly 的 CPython)下直接在浏览器标签页中运行,使用本地构建的 gramps.gen.lib wheel(Gramps 自己的数据模型,因此 Gramplet 的 Python 代码看到的是真正的 Person/Family/... 对象,而不是重新实现的版本)。不在服务器上执行,也不需要在您的电脑上安装任何东西——这些 wheel 如何构建见开发,沙箱 API(people()、filter()、db、row()、html() 等)如何组成见 Gramplet。

Clone this wiki locally