跳到主要内容

rxdb-plugin-history

Implements: US-025 核心插件化外移

历史、撤销重做与分支。装上它,rxdb.versionManager 才存在。

核心 @aiao/rxdb 保留的是原语:三张系统表(RxDBBranch / RxDBChange / RxDBSync)、变更编解码、冲突模型、同步资格判定,以及 getCurrentBranch()。这些东西并不专属历史子系统——变更日志触发器直接写 branchId,RxDBChange.branch 会生成真实的 REFERENCES rxdb$rxdb_branch(id) 外键,整条响应式增量链路都跑在 rxdb_change 上。搬进本包的是消费者:读历史、撤销重做、建/切/合/删分支。

推拉同步的调度不在这里,它住在 @aiao/rxdb-plugin-sync:rxdb.syncManager.push() / .pull() / .sync()。那个插件 inject: ['plugin:history'],反向没有依赖——本包不认识同步包。

安装​

pnpm add @aiao/rxdb @aiao/rxdb-plugin-history

使用​

import { RxDB, SyncType } from '@aiao/rxdb';
import { rxDBPluginHistory } from '@aiao/rxdb-plugin-history';

const rxdb = new RxDB({
dbName: 'notes',
entities: [Note],
sync: `{ type: SyncType.Full, local: { adapter: 'wa-sqlite' }, remote: { adapter: 'supabase' } }`
});

// 传工厂函数本身,不要调用它;必须早于 connect()——connect() 内部就会调 init()
rxdb.use(rxDBPluginHistory).adapter('wa-sqlite' /* … */);

await rxdb.connect('wa-sqlite');

// 撤销重做
await rxdb.versionManager.history().undo();
await rxdb.versionManager.history(Note).redo(2);

// 分支
await rxdb.versionManager.createBranch('feature-a');
await rxdb.versionManager.switchBranch('feature-a');
await rxdb.versionManager.mergeBranch('feature-a');

use() 的时机是有意义的:use() 在 init() 之前调用则插件在 init() 时装上,之后调用则立即装上,而 connect() 内部会调 init()。写在 connect() 后面,第一批变更已经错过了历史订阅。

主要能力​

分组入口
历史history(scope?) → histories$ / undoHistories$ / redoHistories$ / count$ / undo() / redo()
分支createBranch() / switchBranch() / mergeBranch() / removeBranch()
计数pushableCount$ / pullableCount$
恢复restoreEntity()

history() 的作用域按入参解析:无参 = 整库,传实体类 = 该仓储,传实例 = 该行。撤销一个跨作用域事务时抛 RxDBCrossScopeTransactionError,而不是只撤一半——半个事务比失败难查得多。

没装插件时​

rxdb.versionManager 不存在(读到 undefined),核心不给一个什么都不做的空壳。这对下游是可见的契约而非退化路径:

  • DevTools:DevToolsRxDB.versionManager 是可选成员,连接器据实报「分支能力不可用」,不会把它谎报成「一个分支都没有」。
  • 同步状态枢纽:SyncStateHub 留在核心,但可推送计数由本插件经 bindPushableCount() 接上。没装插件,那个读数就是无人维护——这正是实情。待拉数归 @aiao/rxdb-plugin-sync:它接住远端适配器发出的 requestPullableRefresh()。
  • 类型:versionManager 由本包的 declare module '@aiao/rxdb' 声明。只消费、不负责安装的模块(框架组件、共享 UI)应写 import type {} from '@aiao/rxdb-plugin-history';——它把类型声明拉进编译单元,同时在 emit 时整句擦除,不产生运行时依赖。

连接纪元​

插件声明 lifecycle: 'scoped',三处宿主改动全部登记在 install(scope) 收到的作用域上,disconnectAll() 时逆序释放,宿主不调用 destroy():VersionManager 实例本身(连同 destroy())、rxdb.versionManager 槽位、syncState.bindPushableCount() 订阅。重新 connect() 装的是一个全新的管理器,而不是复活上一纪元那个——后者指向的事件总线已经拆了。

本包不声明 inject,所以它在 init() 里就装完;@aiao/rxdb-plugin-sync 声明了 inject: ['plugin:history'],要等本包装好才轮到它,那个槽位得 await connect() 之后才在。

插件不声明 inject:VersionManager.init() 只挂监听与订阅,一条适配器读写都不发。声明 adapter:local 会把安装推到引导链之后,而分支流必须在第一条 rxdb_change 事件到达之前就订阅上。

@aiao/rxdb-plugin-history —— 历史、撤销重做与分支。

Remarks​

核心 @aiao/rxdb 只保留原语:三张系统表(RxDBBranch / RxDBChange / RxDBSync)、变更编解码、冲突模型与同步资格判定。原因是这些东西并不属于历史子系统 —— 变更日志触发器直接写 branchId,RxDBChange.branch 会生成真实的 REFERENCES rxdb$rxdb_branch(id) 外键,整条响应式增量链路都跑在 rxdb_change 上。

搬进本包的是消费者:读历史、撤销重做、建/切/合/删分支。

推拉同步的调度不在这里。US-025 阶段 D 之后它住在 @aiao/rxdb-plugin-sync: rxdb.syncManager.push() / .pull() / .sync()。那个插件 inject: ['plugin:history'], 反向则没有依赖——本包不认识同步包。

Example​

import { rxDBPluginHistory } from '@aiao/rxdb-plugin-history';

rxdb.use(rxDBPluginHistory);
await rxdb.connect();

// 撤销重做走作用域 API:无参 = 整库,传实体类 = 该仓储,传实例 = 该行
await rxdb.versionManager.history().undo();

Classes​

ClassDescription
PushInFlightRegistry「哪些变更此刻正在飞往远端」的进程内登记处。
RxDBCrossScopeTransactionError作用域 undo / redo 撞上跨作用域事务
RxDBPluginHistory历史 / 撤销重做 / 分支插件。

Interfaces​

InterfaceDescription
PushInFlightSession一次 push 调用期间持有的认领句柄。
SwitchBranchActionReaders算切换分支所需操作要用到的最小仓库能力。
SwitchBranchStep分支切换步骤
SyncHistoryBridge同步插件可见的历史侧能力。
VersionManager版本管理器

Type Aliases​

Type AliasDescription
RxDBPluginHistoryOptions本插件当前不接受任何选项

Variables​

VariableDescription
rxDBPluginHistory历史插件工厂。

Functions​

FunctionDescription
compute_switch_branch_actions用给定的仓库算「从当前 active 分支切到 branchId」要落的增删改—— switch_branch_actions 的本体。
find_branch_path_to_root沿 parentId 从一条分支走到它所在树的根,返回途经的全部分支(含自己,根在最后)。
find_switch_branch_step算出树上两个节点之间的切换路径
get_branch_max_change读一条分支当前的 tip:它自己那些未被回滚的变更里 id 最大的那条。
isIgnorableDetachedVersionEventError判断游离事件任务的错误是否可忽略