跳到主要内容

分支管理

RxDB 提供类似 Git 的分支管理能力,让你可以在本地创建多个数据分支,安全地进行实验性修改,然后选择性地合并或切换分支。

核心概念

  • 主分支(main):默认分支,记录稳定的生产数据
  • 开发分支:用于测试和实验的临时分支
  • 变更记录(RxDBChange):通过数据库触发器自动记录所有数据变更
  • 分支切换:在不同数据状态之间切换,基于变更历史实现状态迁移

快速开始

访问版本管理器

import { RxDB, SyncType } from '@aiao/rxdb';

// 创建 RxDB 实例后,可以通过 versionManager 访问分支管理功能
const rxdb = new RxDB({
dbName: 'myapp',
entities: [Todo],
sync: {
local: { adapter: 'sqlite' },
type: SyncType.None
}
});

// 版本管理器提供了分支操作的所有方法
const { versionManager } = rxdb;

分支管理 API

创建分支

创建一个新的数据分支,可以选择从指定的变更点(changeId)创建:

// 从当前状态创建新分支
await rxdb.versionManager.createBranch('feature-1');

// 从指定变更点创建分支(用于从历史状态创建分支)
await rxdb.versionManager.createBranch('feature-2', 12345);

切换分支

切换到不同的分支,系统会自动应用所需的数据变更:

// 切换到指定分支
await rxdb.versionManager.switchBranch('feature-1');

// 切换回主分支
await rxdb.versionManager.switchBranch('main');

切换分支时发生的操作:

  1. 关闭数据库触发器
  2. 执行当前分支的逆向变更(inversePatch)回退到共同祖先
  3. 应用目标分支的正向变更(patch)到最新状态
  4. 更新分支激活状态
  5. 重新生成触发器(使用新的分支ID)
  6. 启用触发器

删除分支

删除不再需要的分支:

// 删除分支(会清理相关的变更记录)
await rxdb.versionManager.removeBranch('feature-1');
注意

删除分支会同时删除该分支的所有变更历史,操作不可恢复。

获取当前分支

查询当前激活的分支:

const currentBranch = await rxdb.versionManager.getCurrentBranch();
console.log('当前分支:', currentBranch.id);
console.log('是否激活:', currentBranch.activated);

实际应用场景

场景 1:测试新功能

// 1. 创建测试分支
await rxdb.versionManager.createBranch('test-feature');
await rxdb.versionManager.switchBranch('test-feature');

// 2. 在测试分支上修改数据
const todo = new Todo();
todo.title = '测试任务';
await todo.save();

// 3. 测试通过,切换回主分支
await rxdb.versionManager.switchBranch('main');

// 4. 不需要测试数据,删除测试分支
await rxdb.versionManager.removeBranch('test-feature');

场景 2:离线编辑与同步

// 1. 创建离线工作分支
await rxdb.versionManager.createBranch('offline-work');
await rxdb.versionManager.switchBranch('offline-work');

// 2. 进行离线修改...
// (用户在没有网络的情况下工作)

// 3. 恢复网络后,切换回主分支
await rxdb.versionManager.switchBranch('main');

// 4. 可选:将离线分支的修改合并到主分支(未来功能)
// await rxdb.versionManager.merge('offline-work', 'main');

场景 3:数据快照与回滚

// 1. 在重要操作前创建快照分支
const changeId = await rxdb.versionManager.getCurrentChangeId();
await rxdb.versionManager.createBranch('snapshot-before-import', changeId);

// 2. 执行批量导入操作
await importLargeDataset();

// 3. 如果出现问题,切换回快照分支
if (hasErrors) {
await rxdb.versionManager.switchBranch('snapshot-before-import');
}

系统表结构

系统表 RxDBChange 会通过数据库触发器(Trigger) 记录所有数据库的数据变化(插入,修改,删除),RxDBBranch 表会记录当前分支,这样我们就可以通过历史记录来计算出任何时刻的数据,再去修改其他表的数据达到切换版本的需求。

字段说明

RxDBBranch 表

  • id: 分支唯一标识符
  • activated: 分支是否激活(同时只有一个分支激活)
  • local: 是否为本地分支
  • remote: 是否已同步到远程

RxDBChange 表

  • id: 变更记录唯一标识符(自增)
  • branchId: 所属分支ID
  • namespace: 命名空间
  • entity: 实体名称
  • entityId: 实体ID
  • type: 变更类型(insertupdatedelete
  • patch: 正向变更数据(JSON Patch 格式)
  • inversePatch: 逆向变更数据(用于回滚)
  • revertChangedAt: 撤销时间(用于 undo/redo)
  • revertChangeId: 撤销时的变更ID
  • transactionId: 事务ID(同一事务的多个变更共享)
  • createdAt: 创建时间
  • updatedAt: 更新时间

分支合并(规划中)

你可以随时/实时 pull、push,如果遇到数据冲突需要像 Git 一样合并冲突。

直接合并

将源分支的所有变更直接应用到目标分支:

// 将 feature-1 分支合并到 main 分支
// await rxdb.versionManager.merge('feature-1', 'main');

压缩合并

将多个提交压缩成一个提交再合并:

// 压缩合并,保持历史简洁
// await rxdb.versionManager.merge('feature-1', 'main', { squash: true });

分支删除

合并后可以删除源分支:

// 合并并删除源分支
// await rxdb.versionManager.merge('feature-1', 'main', { deleteBranch: true });

最佳实践

1. 合理命名分支

使用描述性的分支名称:

// ✅ 好的命名
await rxdb.versionManager.createBranch('feature-user-profile');
await rxdb.versionManager.createBranch('bugfix-todo-delete');
await rxdb.versionManager.createBranch('experiment-new-ui');

// ❌ 不好的命名
await rxdb.versionManager.createBranch('test');
await rxdb.versionManager.createBranch('branch1');

2. 及时清理分支

定期清理不再需要的分支:

// 列出所有分支并清理
const branches = await rxdb.versionManager.listBranches();
for (const branch of branches) {
if (branch.id !== 'main' && !branch.activated) {
// 检查是否需要保留
const shouldKeep = await checkBranchStatus(branch);
if (!shouldKeep) {
await rxdb.versionManager.removeBranch(branch.id);
}
}
}

3. 使用快照保护重要操作

在执行危险操作前创建快照:

// 重要操作前的保护机制
async function safeOperation(operation: () => Promise<void>) {
const timestamp = Date.now();
const snapshotBranch = `snapshot-${timestamp}`;

try {
// 创建快照
await rxdb.versionManager.createBranch(snapshotBranch);

// 执行操作
await operation();

// 操作成功,删除快照
await rxdb.versionManager.removeBranch(snapshotBranch);
} catch (error) {
// 操作失败,回滚到快照
await rxdb.versionManager.switchBranch(snapshotBranch);
throw error;
}
}

4. 分支隔离实验

使用分支进行 A/B 测试或实验性功能:

// 创建实验分支
await rxdb.versionManager.createBranch('experiment-a');
await rxdb.versionManager.createBranch('experiment-b');

// 在不同分支测试不同方案
await rxdb.versionManager.switchBranch('experiment-a');
await testFeatureA();

await rxdb.versionManager.switchBranch('experiment-b');
await testFeatureB();

// 根据结果选择保留哪个分支
await rxdb.versionManager.switchBranch('main');
if (resultsA.isBetter()) {
// 未来支持:await rxdb.versionManager.merge('experiment-a', 'main');
}

注意事项

  1. 分支切换是数据库级别的操作:切换分支会影响所有实体的数据
  2. 触发器管理:分支切换时会自动管理触发器的启用/禁用
  3. 性能考虑:大量变更的分支切换可能需要较长时间
  4. 数据一致性:确保在分支切换前保存所有待处理的数据
  5. 并发控制:避免在分支切换过程中进行其他数据操作

相关文档