@aiao/rxdb-plugin-history 0.0.26 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/HistoryManager.d.ts +8 -0
- package/dist/HistoryManager.d.ts.map +1 -1
- package/dist/VersionManager.d.ts +16 -6
- package/dist/VersionManager.d.ts.map +1 -1
- package/dist/create-branch.d.ts +6 -0
- package/dist/create-branch.d.ts.map +1 -1
- package/dist/find-switch-branch-step.d.ts +49 -0
- package/dist/find-switch-branch-step.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +343 -258
- package/dist/merge-branch.d.ts.map +1 -1
- package/dist/remove-branch.d.ts.map +1 -1
- package/dist/restore-entity.d.ts.map +1 -1
- package/dist/scope-selection.d.ts +3 -2
- package/dist/scope-selection.d.ts.map +1 -1
- package/dist/switch-branch-actions.d.ts +72 -1
- package/dist/switch-branch-actions.d.ts.map +1 -1
- package/dist/undo-redo-apply.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/HistoryManager.ts +42 -12
- package/src/VersionManager.ts +180 -14
- package/src/create-branch.ts +24 -0
- package/src/find-switch-branch-step.ts +76 -40
- package/src/index.ts +15 -0
- package/src/merge-branch.ts +41 -2
- package/src/remove-branch.ts +12 -1
- package/src/restore-entity.ts +29 -2
- package/src/scope-selection.ts +3 -2
- package/src/switch-branch-actions.ts +85 -4
- package/src/undo-redo-apply.ts +13 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-branch.d.ts","sourceRoot":"","sources":["../src/merge-branch.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"merge-branch.d.ts","sourceRoot":"","sources":["../src/merge-branch.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,kBAAkB,EAClB,iBAAiB,EAIlB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIrD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,GACvB,SAAS,cAAc,EACvB,gBAAgB,MAAM,EACtB,iBAAiB,MAAM,EACvB,UAAU,kBAAkB,KAC3B,OAAO,CAAC,iBAAiB,CA2J3B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remove-branch.d.ts","sourceRoot":"","sources":["../src/remove-branch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"remove-branch.d.ts","sourceRoot":"","sources":["../src/remove-branch.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GAAU,SAAS,cAAc,EAAE,UAAU,MAAM,kBA4E5E,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restore-entity.d.ts","sourceRoot":"","sources":["../src/restore-entity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"restore-entity.d.ts","sourceRoot":"","sources":["../src/restore-entity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,UAAU,EAEV,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,CAAC,SAAS,UAAU,EACvD,EAAE,EAAE,cAAc,EAClB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EACvB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CA0F1B"}
|
|
@@ -37,8 +37,9 @@ export declare function selectScopedHistories(histories: HistoryItem[], scope: H
|
|
|
37
37
|
*
|
|
38
38
|
* @remarks
|
|
39
39
|
* 拿到本错误说明选中的历史项里有事务同时改了作用域内外的数据。作用域**不会**替你决定
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* 是牺牲原子性还是越权撤销:要完整撤销这个事务,改用不带参数的
|
|
41
|
+
* `rxdb.versionManager.history()`(database 作用域)重新取一次历史再 undo。
|
|
42
|
+
* `VersionManager.historyManager` 是实现细节,不是绕开本错误的出口。
|
|
42
43
|
*/
|
|
43
44
|
export declare class RxDBCrossScopeTransactionError extends RxDBError {
|
|
44
45
|
/** 发起 undo / redo 的作用域 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-selection.d.ts","sourceRoot":"","sources":["../src/scope-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9E,sBAAsB;AACtB,KAAK,YAAY,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,UAAU,GAAG,WAAW,CAAC,CAAC;AAE1E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAIlF;AAED,+CAA+C;AAC/C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAEtF;AAED,2CAA2C;AAC3C,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAEvF;AAED,wCAAwC;AACxC,MAAM,WAAW,sBAAsB;IACrC,kCAAkC;IAClC,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,gCAAgC;IAChC,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,WAAW,EAAE,EACxB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,GACX,sBAAsB,CAcxB;AAuBD
|
|
1
|
+
{"version":3,"file":"scope-selection.d.ts","sourceRoot":"","sources":["../src/scope-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9E,sBAAsB;AACtB,KAAK,YAAY,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,UAAU,GAAG,WAAW,CAAC,CAAC;AAE1E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAIlF;AAED,+CAA+C;AAC/C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAEtF;AAED,2CAA2C;AAC3C,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAEvF;AAED,wCAAwC;AACxC,MAAM,WAAW,sBAAsB;IACrC,kCAAkC;IAClC,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,gCAAgC;IAChC,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,WAAW,EAAE,EACxB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,GACX,sBAAsB,CAcxB;AAuBD;;;;;;;;GAQG;AACH,qBAAa,8BAA+B,SAAQ,SAAS;IAEzD,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,EAAE,YAAY;IAC5B,oBAAoB;IACpB,QAAQ,CAAC,SAAS,EAAE,SAAS,WAAW,EAAE;;IAH1C,0BAA0B;IACjB,KAAK,EAAE,YAAY;IAC5B,oBAAoB;IACX,SAAS,EAAE,SAAS,WAAW,EAAE;CAS7C"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RxDBChange, SwitchVersionActions } from '@aiao/rxdb';
|
|
1
|
+
import { EntityStaticType, RxDBBranch, RxDBChange, SwitchVersionActions } from '@aiao/rxdb';
|
|
2
2
|
import { VersionManager } from './VersionManager.js';
|
|
3
3
|
/**
|
|
4
4
|
* 计算切换分支所需的操作
|
|
@@ -7,10 +7,81 @@ import { VersionManager } from './VersionManager.js';
|
|
|
7
7
|
* @returns 返回切换分支所需的操作序列
|
|
8
8
|
*/
|
|
9
9
|
export declare const switch_branch_actions: (version: VersionManager, branchId: string) => Promise<SwitchVersionActions<import('@aiao/rxdb').EntityType>>;
|
|
10
|
+
/**
|
|
11
|
+
* 算切换分支所需操作要用到的最小仓库能力。
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* 与 {@link BranchChangeReader} 同一个理由写成结构化接口:本地仓库与事务执行器给出的
|
|
15
|
+
* `IRepository` 都接得住,两边的 `find` 入参本就是同一个类型。
|
|
16
|
+
*/
|
|
17
|
+
export interface SwitchBranchActionReaders {
|
|
18
|
+
/** 读分支行 */
|
|
19
|
+
readonly branchRepository: {
|
|
20
|
+
/**
|
|
21
|
+
* 按条件查分支
|
|
22
|
+
*
|
|
23
|
+
* @param options - 查询选项
|
|
24
|
+
* @returns 命中的分支行
|
|
25
|
+
*/
|
|
26
|
+
find(options: EntityStaticType<typeof RxDBBranch, 'findOptions'>): Promise<RxDBBranch[]>;
|
|
27
|
+
};
|
|
28
|
+
/** 读变更行;见 {@link BranchChangeReader} */
|
|
29
|
+
readonly changeRepository: BranchChangeReader;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 用给定的仓库算「从当前 active 分支切到 `branchId`」要落的增删改——
|
|
33
|
+
* {@link switch_branch_actions} 的本体。
|
|
34
|
+
*
|
|
35
|
+
* @param readers - 见 {@link SwitchBranchActionReaders}
|
|
36
|
+
* @param branchId - 要切换到的分支 id
|
|
37
|
+
* @returns 切换分支所需的操作序列
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* 拆出来是给 `@aiao/rxdb-plugin-working-tree` 的物化屏障用的:屏障要先把来源分支的投影撤掉,
|
|
41
|
+
* 再铺目标分支的快照,而它跑在 `switchBranch` 的 `prepare` 里,手上只有事务执行器的仓库——
|
|
42
|
+
* 绑在适配器上的本地仓库会在同一条连接上等屏障自己那笔事务,死锁。另写一份同义计算,
|
|
43
|
+
* 迟早会在「回滚标记算不算」上与普通切换分叉。
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export declare const compute_switch_branch_actions: (readers: SwitchBranchActionReaders, branchId: string) => Promise<SwitchVersionActions>;
|
|
10
48
|
/**
|
|
11
49
|
* 计算切换版本所需的操作
|
|
12
50
|
* @param changes 需要计算的 changes
|
|
13
51
|
* @param options
|
|
14
52
|
*/
|
|
15
53
|
export declare const get_switch_version_actions: (changes: RxDBChange[], isPatch: boolean, actions?: SwitchVersionActions) => SwitchVersionActions;
|
|
54
|
+
/**
|
|
55
|
+
* 读一条分支 tip 所需要的最小仓库能力。
|
|
56
|
+
*
|
|
57
|
+
* @remarks
|
|
58
|
+
* 写成结构化的最小接口而不是直接用本地仓库类型,是为了让 {@link get_branch_max_change}
|
|
59
|
+
* 同时接得住事务执行器给出的 `IRepository<typeof RxDBChange>`——`enable-migration.ts`
|
|
60
|
+
* 跑在事务里,拿不到本地仓库。两边的 `find` 入参本就是同一个类型
|
|
61
|
+
* (`RxDBChangeStaticTypes['findOptions']`),所以这不是放宽,只是把已有的共同点写出来。
|
|
62
|
+
*/
|
|
63
|
+
export interface BranchChangeReader {
|
|
64
|
+
/**
|
|
65
|
+
* 按条件查变更
|
|
66
|
+
*
|
|
67
|
+
* @param options - 查询选项
|
|
68
|
+
* @returns 命中的变更行
|
|
69
|
+
*/
|
|
70
|
+
find(options: EntityStaticType<typeof RxDBChange, 'findOptions'>): Promise<RxDBChange[]>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 读一条分支当前的 tip:它自己那些未被回滚的变更里 id 最大的那条。
|
|
74
|
+
*
|
|
75
|
+
* @param changeRepository - 见 {@link BranchChangeReader}
|
|
76
|
+
* @param branchId - 分支 id
|
|
77
|
+
* @returns tip 变更行;该分支一条自有变更都没有时为 `undefined`(调用方据此退回分叉点)
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* 导出是给 `enable-migration.ts` 复用的:「一条分支现在停在哪」这一问必须全局只有一个
|
|
81
|
+
* 答案(research.md R11)。迁移期另写一条同义查询,迟早会在「回滚标记算不算」
|
|
82
|
+
* 或「排序列是哪一个」上与切换路径分叉,表现为迁移放行了一条 `switchBranch` 走不通的分支。
|
|
83
|
+
*
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
export declare const get_branch_max_change: (changeRepository: BranchChangeReader, branchId: string) => Promise<RxDBChange | undefined>;
|
|
16
87
|
//# sourceMappingURL=switch-branch-actions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switch-branch-actions.d.ts","sourceRoot":"","sources":["../src/switch-branch-actions.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"switch-branch-actions.d.ts","sourceRoot":"","sources":["../src/switch-branch-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAEhB,UAAU,EACV,UAAU,EAEV,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAU,SAAS,cAAc,EAAE,UAAU,MAAM,mEACN,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,WAAW;IACX,QAAQ,CAAC,gBAAgB,EAAE;QACzB;;;;;WAKG;QACH,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,OAAO,UAAU,EAAE,aAAa,CAAC,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;KAC1F,CAAC;IAEF,wCAAwC;IACxC,QAAQ,CAAC,gBAAgB,EAAE,kBAAkB,CAAC;CAC/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,6BAA6B,GACxC,SAAS,yBAAyB,EAClC,UAAU,MAAM,KACf,OAAO,CAAC,oBAAoB,CAmF9B,CAAC;AAkBF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GACrC,SAAS,UAAU,EAAE,EACrB,SAAS,OAAO,EAChB,UAAS,oBAIR,KACA,oBA4FF,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,OAAO,UAAU,EAAE,aAAa,CAAC,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;CAC1F;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,kBAAkB,EACpC,UAAU,MAAM,KACf,OAAO,CAAC,UAAU,GAAG,SAAS,CAahC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"undo-redo-apply.d.ts","sourceRoot":"","sources":["../src/undo-redo-apply.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"undo-redo-apply.d.ts","sourceRoot":"","sources":["../src/undo-redo-apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,WAAW,EAEhB,KAAK,IAAI,EACT,KAAK,oBAAoB,EAEzB,UAAU,EAEV,QAAQ,EACR,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EAGvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAkB,OAAO,EAAE,MAAM,MAAM,CAAC;AAGhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAEvF,KAAK,uBAAuB,GAAG,WAAW,CAAC,OAAO,QAAQ,CAAC,GAAG;IAC5D,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,OAAO,QAAQ,EAAE,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;CAC3G,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,SAAS,oBAAoB,KAAG,uBACA,CAAC;AAE5E;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,qBAAqB,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACxD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACjC,mBAAmB,IAAI,IAAI,CAAC;IAC5B,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC1D,2BAA2B,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IACzD,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1F,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAC5C,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAAC;CAC1D;AAED,sCAAsC;AACtC,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,iBAAiB,EACvB,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC;IAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IAAC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAoD3E;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,iBAAiB,EACvB,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,SAAS,EAAE,WAAW,EAAE,EACxB,WAAW,CAAC,EAAE,iBAAiB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAwEf;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEhF"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiao/rxdb-plugin-history",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"description": "History, undo/redo and branch management for Aiao RxDB.",
|
|
5
|
-
"homepage": "https://rxdb.
|
|
5
|
+
"homepage": "https://rxdb.aiao.io/docs/plugins",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/aiao-io/rxdb/issues"
|
|
8
8
|
},
|
|
@@ -54,7 +54,9 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"rxjs": "^7.8.2",
|
|
57
|
-
"@aiao/
|
|
58
|
-
|
|
57
|
+
"@aiao/utils": "^0.0.28"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@aiao/rxdb": "^0.0.28"
|
|
59
61
|
}
|
|
60
62
|
}
|
package/src/HistoryManager.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
declareTrustedWrite,
|
|
2
3
|
EntityType,
|
|
3
4
|
HistoryItem,
|
|
4
5
|
HistoryScope,
|
|
@@ -7,7 +8,9 @@ import {
|
|
|
7
8
|
REPOSITORY_SYNC_COMPLETE_EVENT,
|
|
8
9
|
RxDB,
|
|
9
10
|
RxDBBranch,
|
|
10
|
-
RxDBChange
|
|
11
|
+
RxDBChange,
|
|
12
|
+
SKIP_BRANCH_SWITCH_PREPARE,
|
|
13
|
+
TrustedWriteIntent
|
|
11
14
|
} from '@aiao/rxdb';
|
|
12
15
|
import {
|
|
13
16
|
BehaviorSubject,
|
|
@@ -17,6 +20,7 @@ import {
|
|
|
17
20
|
filter,
|
|
18
21
|
firstValueFrom,
|
|
19
22
|
map,
|
|
23
|
+
NEVER,
|
|
20
24
|
Observable,
|
|
21
25
|
ReplaySubject,
|
|
22
26
|
shareReplay,
|
|
@@ -166,12 +170,21 @@ export class HistoryManager {
|
|
|
166
170
|
|
|
167
171
|
const branchRepository = rxdb.entityManager.getRepository(RxDBBranch);
|
|
168
172
|
const changeRepository = rxdb.entityManager.getRepository(RxDBChange);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
// 连上之后才查:仓库活查询会经适配器的就绪门把实例连起来,构造期直接订阅等于 `init()` 之后插件
|
|
174
|
+
// 自己把库连上了,「连接前先恢复」之类必须对着未连接实例做的操作就永远拿不到空目标。
|
|
175
|
+
// 断开时交 NEVER:丢掉挂在已断适配器上的查询,undo session 停在最后一个分支上,重连再查。
|
|
176
|
+
const current_branch$ = this.rxdb.connected$.pipe(
|
|
177
|
+
switchMap(connected =>
|
|
178
|
+
connected ?
|
|
179
|
+
branchRepository.findOne({
|
|
180
|
+
where: {
|
|
181
|
+
combinator: 'and',
|
|
182
|
+
rules: [{ field: 'activated', operator: '=', value: true }]
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
: NEVER
|
|
186
|
+
)
|
|
187
|
+
);
|
|
175
188
|
|
|
176
189
|
this.#subscriptions.push(
|
|
177
190
|
current_branch$.pipe(takeUntil(this.#destroy$)).subscribe({
|
|
@@ -297,7 +310,11 @@ export class HistoryManager {
|
|
|
297
310
|
const baseRules = [
|
|
298
311
|
{ field: 'branchId' as const, operator: '=' as const, value: branch!.id },
|
|
299
312
|
{ field: 'revertChangeId' as const, operator: '=' as const, value: null },
|
|
300
|
-
{ field: 'remoteId' as const, operator: '=' as const, value: null }
|
|
313
|
+
{ field: 'remoteId' as const, operator: '=' as const, value: null },
|
|
314
|
+
// 被拒变更(rejectedAt 非空)的 remoteId 也一直是 null,但它永远不会再被
|
|
315
|
+
// 推送,必须和 remoteId = null 一起排除,否则无关的被拒变更一有风吹草动
|
|
316
|
+
// 就会白触发一次 pushableCount 重算
|
|
317
|
+
{ field: 'rejectedAt' as const, operator: '=' as const, value: null }
|
|
301
318
|
];
|
|
302
319
|
return changeRepository
|
|
303
320
|
.count({
|
|
@@ -497,13 +514,18 @@ export class HistoryManager {
|
|
|
497
514
|
/**
|
|
498
515
|
* 使 redo 栈失效。全部 trigger id ≤ {@link #redoInvalidationFloor} 视为迟到通知,跳过。
|
|
499
516
|
*
|
|
517
|
+
* @remarks
|
|
518
|
+
* 这里**不查** `isUndoRedoInProgress` / `isInvalidatingRedo`:undo、redo 与本方法三个入口
|
|
519
|
+
* 全部排进 {@link HistoryManager.#runSerialized},两个标志又只在同一个序列化任务内部置起
|
|
520
|
+
* 再复位,因此轮到本任务时它们必然已复位——查了也是一条走不到的分支。下面那句
|
|
521
|
+
* `isInvalidatingRedo = true` 留着是给 {@link HistoryManager.isExecutingUndoRedo} 读的,
|
|
522
|
+
* 真正生效的守卫在调用方 `VersionManager` 的 `if (!historyManager.isExecutingUndoRedo())`,
|
|
523
|
+
* 它连 `syncDepth` 一起看,覆盖比这里严。
|
|
524
|
+
*
|
|
500
525
|
* @internal
|
|
501
526
|
*/
|
|
502
527
|
async invalidateRedoStack(triggerChangeIds?: readonly number[]): Promise<void> {
|
|
503
528
|
return this.#runSerialized(async () => {
|
|
504
|
-
if (this.isUndoRedoInProgress || this.isInvalidatingRedo) {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
529
|
if (triggerChangeIds?.length && triggerChangeIds.every(id => id <= this.#redoInvalidationFloor)) {
|
|
508
530
|
return;
|
|
509
531
|
}
|
|
@@ -530,10 +552,18 @@ export class HistoryManager {
|
|
|
530
552
|
});
|
|
531
553
|
});
|
|
532
554
|
|
|
555
|
+
// 作废 redo 栈同样只重写投影(行 4)。这里与下面 undo-redo-apply 调的是同一个原语,
|
|
556
|
+
// 两者的结论相反,区别只在这条声明里。
|
|
557
|
+
declareTrustedWrite(adapter, {
|
|
558
|
+
file: 'HistoryManager.ts',
|
|
559
|
+
symbol: 'invalidateRedoStack',
|
|
560
|
+
intent: TrustedWriteIntent.redo_invalidation
|
|
561
|
+
});
|
|
533
562
|
// 不传 branchId:这里只想把 actions 套用在**当前**分支上。自己先查再传会留下一个
|
|
534
563
|
// 采样窗口——本方法是 detached 任务(变更通知还会被批处理 / 跨进程延迟),窗口内的一次
|
|
535
564
|
// 真实 switchBranch 会让这条调用把 activated 与全部触发器倒回旧分支。
|
|
536
|
-
|
|
565
|
+
// 同上:分支不换,没有前置条件可校验。
|
|
566
|
+
await adapter.switchBranch({ actions, prepare: SKIP_BRANCH_SWITCH_PREPARE });
|
|
537
567
|
|
|
538
568
|
this.clearRedoStack();
|
|
539
569
|
} catch (error) {
|
package/src/VersionManager.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
declareTrustedWrite,
|
|
2
3
|
ENTITY_LOCAL_CREATE_EVENT,
|
|
3
4
|
EntityLocalCreatedEvent,
|
|
4
5
|
EntityType,
|
|
@@ -18,9 +19,14 @@ import {
|
|
|
18
19
|
SwitchBranchBeginEvent,
|
|
19
20
|
SwitchBranchCommitEvent,
|
|
20
21
|
SwitchBranchRollbackEvent,
|
|
22
|
+
takeDeclaredWrite,
|
|
21
23
|
TRANSACTION_BEGIN,
|
|
22
24
|
TRANSACTION_COMMIT,
|
|
23
|
-
TRANSACTION_ROLLBACK
|
|
25
|
+
TRANSACTION_ROLLBACK,
|
|
26
|
+
TrustedWriteIntent,
|
|
27
|
+
type RxDBAdapterLocalBase,
|
|
28
|
+
type RxDBBranchSwitchPreconditions,
|
|
29
|
+
type TransactionExecutor
|
|
24
30
|
} from '@aiao/rxdb';
|
|
25
31
|
import { create_branch } from './create-branch.js';
|
|
26
32
|
import { isIgnorableDetachedVersionEventError } from './detached-event-error.js';
|
|
@@ -127,8 +133,9 @@ export class VersionManager {
|
|
|
127
133
|
* `HistoryManager` 已经拆掉了订阅,这里重建一个而不是复用 —— 复用等于把新纪元的
|
|
128
134
|
* 变更喂给一条已 complete 的流。
|
|
129
135
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
136
|
+
* 不发任何适配器读写,也不会把实例连起来(分支流等 `connected$` 为真才查),因此可以
|
|
137
|
+
* (也必须)早于引导链跑:`HistoryManager` 的分支流要赶在第一条 `rxdb_change` 事件之前
|
|
138
|
+
* 订阅上,装晚了那一批变更就不进历史。
|
|
132
139
|
*/
|
|
133
140
|
init() {
|
|
134
141
|
if (this.#historyManagerDestroyed) {
|
|
@@ -256,43 +263,92 @@ export class VersionManager {
|
|
|
256
263
|
* 切换到指定分支
|
|
257
264
|
*
|
|
258
265
|
* @param branchId - 目标分支 ID
|
|
266
|
+
* @param preconditions - 【可选】切换前要成立的条件;不传时行为与以往逐字节一致
|
|
259
267
|
*
|
|
260
268
|
* @remarks
|
|
261
269
|
* 切换分支后会自动清空 redo 栈,因为 redo 历史在新分支中不再有效。
|
|
262
270
|
* 如果目标分支与当前分支相同,则直接返回,避免不必要的操作。
|
|
271
|
+
*
|
|
272
|
+
* 第二形参是**纯扩展**:既有调用点一个都不用改,不传即不表态。做成默认开启的话,
|
|
273
|
+
* `switchBranch(id)` 会在任何有未提交改动的库上开始抛错——而历史子系统自己就在调它
|
|
274
|
+
* (undo/redo 回放、redo 失效),那些路径上工作树恒非空,默认开启等于让 undo 在有改动时不可用。
|
|
275
|
+
*
|
|
276
|
+
* 条件由**能力插件**校验,本方法一个字段都不读:判据是
|
|
277
|
+
* `@aiao/rxdb-plugin-working-tree` 贡献的那几张表,这里既不认识也不该认识它们。
|
|
278
|
+
* 反方向(本包 import 那个插件)是条依赖环,nx 的图插件会把 `run-many` 当场拒掉。
|
|
263
279
|
*/
|
|
264
|
-
async switchBranch(branchId: string): Promise<void> {
|
|
280
|
+
async switchBranch(branchId: string, preconditions?: RxDBBranchSwitchPreconditions): Promise<void> {
|
|
265
281
|
const currentBranch = await this.getCurrentBranch();
|
|
266
282
|
// 若切换到相同分支则直接返回,避免不必要操作
|
|
267
283
|
if (currentBranch?.id === branchId) {
|
|
268
284
|
return;
|
|
269
285
|
}
|
|
270
286
|
let switchCommitted = false;
|
|
287
|
+
// 声明挂在**适配器实例**上、取用即清除;下面的 catch 要靠它原路清掉。
|
|
288
|
+
let declaredAdapter: RxDBAdapterLocalBase | undefined;
|
|
289
|
+
// 适配器是否真的调了 prepare。见下方「没调用就抛」那一段。
|
|
290
|
+
let prepared = false;
|
|
271
291
|
try {
|
|
272
292
|
this.rxdb.dispatchEvent(new SwitchBranchBeginEvent(branchId));
|
|
293
|
+
// 问一句这次切换要不要整个交给某个能力贡献方(见 RxDBSystemContribution.takeOverBranchSwitch)。
|
|
294
|
+
// 排在 `getLocalRepositories()` **之前**:接管方自己解析适配器、自己开事务,这里先取一个
|
|
295
|
+
// 出来只是让它在接管路径上白白闲置一个纪元引用。
|
|
296
|
+
if (await this.#take_over_branch_switch(currentBranch?.id ?? null, branchId, preconditions)) {
|
|
297
|
+
// 接管方已经把 active 切过去了(它自己的事务已提交),此后与普通路径同一个状态。
|
|
298
|
+
switchCommitted = true;
|
|
299
|
+
this.#settle_switched(branchId);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
273
302
|
const { adapter } = await this.getLocalRepositories();
|
|
274
303
|
const actions = await switch_branch_actions(this, branchId);
|
|
304
|
+
// 切分支重写的是实体表的**投影**,不是用户的编辑:矩阵行 4 要求它不产生工作树单元。
|
|
305
|
+
// 不声明的话挂载点只看见「有人在调 switchBranch」,与 undo/redo(行 6,必须产生单元)
|
|
306
|
+
// 完全同形,切一次分支就会把整批物化写记成一批未提交变更。
|
|
307
|
+
declareTrustedWrite(adapter, {
|
|
308
|
+
file: 'VersionManager.ts',
|
|
309
|
+
symbol: 'switchBranch',
|
|
310
|
+
intent: TrustedWriteIntent.branch_materialization
|
|
311
|
+
});
|
|
312
|
+
declaredAdapter = adapter;
|
|
275
313
|
const result = await adapter.switchBranch({
|
|
276
314
|
branchId: branchId,
|
|
277
|
-
actions
|
|
315
|
+
actions,
|
|
316
|
+
// 前置校验跑在切换事务**内部**(见 #prepare_branch_switch)。`targetBranchId` 用
|
|
317
|
+
// 适配器解析出来的那一个而不是这里的 `branchId`:省略分支的调用点上只有它是对的。
|
|
318
|
+
prepare: async ({ executor, targetBranchId }) => {
|
|
319
|
+
prepared = true;
|
|
320
|
+
await this.#prepare_branch_switch(executor, currentBranch?.id ?? null, targetBranchId, preconditions);
|
|
321
|
+
}
|
|
278
322
|
});
|
|
279
323
|
// 适配器的 switchBranch 内部包了事务(见各适配器 version/switch_branch.ts),
|
|
280
324
|
// 走到这里说明已提交。此后任何失败都不该再发 Rollback —— 分支确实切过去了。
|
|
281
325
|
switchCommitted = true;
|
|
282
326
|
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
327
|
+
this.#settle_switched(branchId);
|
|
328
|
+
// 适配器吞掉 `prepare` 不会以任何别的方式显形:分支照切、actions 照套、事件照发,
|
|
329
|
+
// 只是这一次切换没验过目标分支的提交图、没判过调用方提的条件、也没推进激活代际。
|
|
330
|
+
// 上面那几步记账照做完 —— 分支确实切过去了,把内存视图留在旧分支只是再坏一件事;
|
|
331
|
+
// 抛出只为一件事:不让这条契约违背被当成一次正常切换吞掉。
|
|
332
|
+
if (!prepared) {
|
|
333
|
+
throw new RxDBError(
|
|
334
|
+
`适配器 ${adapter.constructor.name} 的 switchBranch 没有调用 options.prepare:` +
|
|
335
|
+
`分支已经切到 ${branchId},但这次切换一条前置条件都没校验过。` +
|
|
336
|
+
'适配器必须在解析出目标分支之后、动第一行之前 await 它(见 rxdb-adapter.ts › SwitchBranchOptions.prepare)。'
|
|
337
|
+
);
|
|
338
|
+
}
|
|
292
339
|
return result;
|
|
293
340
|
} catch (error) {
|
|
341
|
+
// 前置校验现在在事务内判,被拒是一条**日常**路径(工作树不干净就该拒),而那条路径上
|
|
342
|
+
// 一个写原语都没跑过——声明还原封不动挂在适配器上。留着的话,这个适配器的下一次
|
|
343
|
+
// mergeChanges 会把它取走,那次合并于是按 projection_rewrite 判定:一个工作树单元都不产生,
|
|
344
|
+
// 拉回来的远端改动凭空消失。
|
|
345
|
+
if (declaredAdapter) takeDeclaredWrite(declaredAdapter);
|
|
294
346
|
if (!switchCommitted) {
|
|
295
347
|
this.rxdb.dispatchEvent(new SwitchBranchRollbackEvent(branchId));
|
|
348
|
+
// 诊断落盘排在**回滚之后**、重新抛出之前:判定失败发生在那次注定回滚的事务里,
|
|
349
|
+
// 写在里面的标记会跟着一起消失(见 RxDBSystemContribution.settleBranchSwitchFailure)。
|
|
350
|
+
// 只在没提交的那条路径上跑——切换已经成立之后的失败不是「没切成」。
|
|
351
|
+
await this.#settle_branch_switch_failure(currentBranch?.id ?? null, branchId, error);
|
|
296
352
|
}
|
|
297
353
|
throw error;
|
|
298
354
|
}
|
|
@@ -440,6 +496,116 @@ export class VersionManager {
|
|
|
440
496
|
return getCurrentBranch(this.rxdb);
|
|
441
497
|
}
|
|
442
498
|
|
|
499
|
+
/**
|
|
500
|
+
* 逐个问贡献方:这次切换能不能发生;能的话,把该在同一个事务里做掉的事做掉。
|
|
501
|
+
*
|
|
502
|
+
* @param executor - **切换事务本身**的执行器,由适配器经 `SwitchBranchOptions.prepare` 交过来
|
|
503
|
+
* @param currentBranchId - 当前分支 id;一条 active 分支都没有时为 `null`
|
|
504
|
+
* @param targetBranchId - 适配器解析之后的目标分支 id
|
|
505
|
+
* @param preconditions - 调用方提出的条件;没提出时是 `undefined`
|
|
506
|
+
*
|
|
507
|
+
* @remarks
|
|
508
|
+
* **跑在切换事务内部。** 它以前自己开一个只读事务、在 `adapter.switchBranch()` **之前**跑完:
|
|
509
|
+
* 那个事务提交到切换事务开始之间是一段没有任何东西守着的窗口,窗口里的一次写能让刚判过的
|
|
510
|
+
* 「工作树干净」重新变脏,而切换照样完成。搬进来之后,「校验通过」与「切换完成」不再是
|
|
511
|
+
* 两件可以分开发生的事——同一个事务,要么一起成立,要么一起没发生。
|
|
512
|
+
*
|
|
513
|
+
* 代价是顺序反了过来:`switch_branch_actions()` 现在排在校验**之前**(它算出来的重放指令
|
|
514
|
+
* 得先交给适配器才谈得上开事务),目标分支的链已知损坏时会白算一遍。那一遍只读不写,
|
|
515
|
+
* 被拒时一行都还没动——拿一次白算换掉上面那段窗口。
|
|
516
|
+
*
|
|
517
|
+
* **每一次真正发生的切换上都跑,与调用方提没提条件无关。** 目标分支的提交图可达损坏
|
|
518
|
+
* 与有没有 `preconditions` 无关(SC-013:三条入口各自返回 `commit_graph_corrupted`);
|
|
519
|
+
* 只在带选项时跑的话,日常那条不带选项的切换会一路切进一份重放不出来的历史。
|
|
520
|
+
*
|
|
521
|
+
* 「真正发生」是字面意思:A→A 的调用在 {@link VersionManager.switchBranch} 里就早返回了
|
|
522
|
+
* (同文件上方的 `currentBranch?.id === branchId`),根本走不到这里。那条路径上没有要防的
|
|
523
|
+
* 东西——分支没换,目标分支的历史也没有被重放,工作树连一行都不会动。把早返回去掉好让
|
|
524
|
+
* 守卫「真的无条件」是反向的:切到当前分支会因为它自己的历史损坏而失败,而这次切换
|
|
525
|
+
* 本来什么都不做。
|
|
526
|
+
*
|
|
527
|
+
* 串行而非 `Promise.all`:`executor` 是并发度为 1 的队列,并行发起只会让读取顺序取决于
|
|
528
|
+
* 各贡献方内部 await 的排布。一个贡献方都没有时这里是一次空循环——没装能力插件的库上,
|
|
529
|
+
* 这条扩展不多开事务、也不多发一条查询。
|
|
530
|
+
*/
|
|
531
|
+
/**
|
|
532
|
+
* 切换成立之后那几步与库无关的记账。
|
|
533
|
+
*
|
|
534
|
+
* @param branchId - 已经切过去的分支 id
|
|
535
|
+
*
|
|
536
|
+
* @remarks
|
|
537
|
+
* 抽出来只为一件事:**接管路径与普通路径必须记同一笔账**。两处各抄一遍的那天,
|
|
538
|
+
* 接管路径上漏掉的 `setUndoBranch()` 不会以任何形式报错——它只是让切换后紧接着的
|
|
539
|
+
* 一次 `undo()` 撤到上一条分支的 session 上去。
|
|
540
|
+
*
|
|
541
|
+
* 三步的顺序是:redo 栈先清(它在新分支上不再有效),undo 视图再切(按分支存,
|
|
542
|
+
* 等 `current_branch$` 补发会让紧接着的 `undo()` 撞上中途换 session 的竞态),
|
|
543
|
+
* 事件最后发(订阅者醒来时这两样都该已经是新分支的)。
|
|
544
|
+
*/
|
|
545
|
+
#settle_switched(branchId: string): void {
|
|
546
|
+
this.historyManager.clearRedoStack();
|
|
547
|
+
this.historyManager.setUndoBranch(branchId);
|
|
548
|
+
this.rxdb.dispatchEvent(new SwitchBranchCommitEvent(branchId));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* 问各贡献方要不要接管这次切换,第一个答应的就停。
|
|
553
|
+
*
|
|
554
|
+
* @param currentBranchId - 当前分支 id
|
|
555
|
+
* @param targetBranchId - 目标分支 id
|
|
556
|
+
* @param preconditions - 调用方提出的前置条件
|
|
557
|
+
* @returns 有人接管了就是 `true`,此时 active 已经切过去了
|
|
558
|
+
*
|
|
559
|
+
* @remarks
|
|
560
|
+
* 问到第一个 `'switched'` 为止:两个贡献方都接管等于 active 被切两次,而第二次看到的
|
|
561
|
+
* 现场已经是第一次的结果。串行的理由与 {@link VersionManager.#prepare_branch_switch} 同源。
|
|
562
|
+
*/
|
|
563
|
+
async #take_over_branch_switch(
|
|
564
|
+
currentBranchId: string | null,
|
|
565
|
+
targetBranchId: string,
|
|
566
|
+
preconditions: RxDBBranchSwitchPreconditions | undefined
|
|
567
|
+
): Promise<boolean> {
|
|
568
|
+
for (const contribution of this.rxdb.systemContributions) {
|
|
569
|
+
const verdict = await contribution.takeOverBranchSwitch({ currentBranchId, targetBranchId, preconditions });
|
|
570
|
+
if (verdict === 'switched') return true;
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* 切换事务回滚之后,让各贡献方落下自己的失败诊断。
|
|
577
|
+
*
|
|
578
|
+
* @param currentBranchId - 仍然 active 的那条分支
|
|
579
|
+
* @param targetBranchId - 没切成的目标分支
|
|
580
|
+
* @param error - 让这次切换失败的那个错误
|
|
581
|
+
*
|
|
582
|
+
* @remarks
|
|
583
|
+
* 调用点在 `catch` 里,紧接着就要把 `error` 重新抛出去——所以贡献方那一侧带着一条
|
|
584
|
+
* 「不得抛出」的硬契约(见 {@link RxDBSystemContribution.settleBranchSwitchFailure})。
|
|
585
|
+
* 这里**不**加 try/catch 替它兜:兜住就等于宣布这个契约可以不遵守,而真正被兜掉的那个
|
|
586
|
+
* 异常从此没有任何一处会显形。
|
|
587
|
+
*/
|
|
588
|
+
async #settle_branch_switch_failure(
|
|
589
|
+
currentBranchId: string | null,
|
|
590
|
+
targetBranchId: string,
|
|
591
|
+
error: unknown
|
|
592
|
+
): Promise<void> {
|
|
593
|
+
for (const contribution of this.rxdb.systemContributions) {
|
|
594
|
+
await contribution.settleBranchSwitchFailure({ currentBranchId, targetBranchId, error });
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async #prepare_branch_switch(
|
|
599
|
+
executor: TransactionExecutor,
|
|
600
|
+
currentBranchId: string | null,
|
|
601
|
+
targetBranchId: string,
|
|
602
|
+
preconditions: RxDBBranchSwitchPreconditions | undefined
|
|
603
|
+
): Promise<void> {
|
|
604
|
+
for (const contribution of this.rxdb.systemContributions) {
|
|
605
|
+
await contribution.prepareBranchSwitch({ executor, currentBranchId, targetBranchId, preconditions });
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
443
609
|
#runDetachedEventTask(task: Promise<void>, label: string) {
|
|
444
610
|
void task.catch(error => {
|
|
445
611
|
if (isIgnorableDetachedVersionEventError(error)) {
|
package/src/create-branch.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assertUsableBranchId,
|
|
2
3
|
type LocalRxDBBranchRepository,
|
|
3
4
|
type LocalRxDBChangeRepository,
|
|
4
5
|
resolve_current_branch,
|
|
@@ -22,8 +23,18 @@ import { VersionManager } from './VersionManager.js';
|
|
|
22
23
|
*
|
|
23
24
|
* 远端 `branchExists` 那一趟**留在事务外**:它是网络往返,放进事务会让并发度 1 的
|
|
24
25
|
* 写队列被一次 RTT 堵住。它本来也只是尽力而为的预检,真正的互斥由本地主键约束兜底。
|
|
26
|
+
*
|
|
27
|
+
* **一条分支未必只有一行。** 贡献系统能力的插件可以在同一个事务里追写自己那几行
|
|
28
|
+
* ({@link RxDBSystemContribution.writeBranchRows})——装了 `@aiao/rxdb-plugin-working-tree`
|
|
29
|
+
* 时是 `CommitBranchRef` 与 `WorkingTreeState` 两行。同属一个事务,正是为了不让
|
|
30
|
+
* 「建了分支却没有对应视图」这种半成品状态存在:那样一条分支与一条正常的老分支
|
|
31
|
+
* 在形状上分辨不出来,只会在下一次用到它时抛一句读不出主语的错。
|
|
25
32
|
*/
|
|
26
33
|
export const create_branch = async (version: VersionManager, branchId: string, fromChangeId?: number) => {
|
|
34
|
+
// 挡在所有 I/O 之前。排在查重之后的话,一条从第一个字符起就不可用的 id 会先跑完本地
|
|
35
|
+
// 查重、再跑一趟远端 RTT 才被拒——而这两趟的答案与结论无关。
|
|
36
|
+
assertUsableBranchId(branchId);
|
|
37
|
+
|
|
27
38
|
const { branchRepository: queuedBranchRepository, adapter } = await version.getLocalRepositories();
|
|
28
39
|
// 检查分支 ID 是否存在(本地)
|
|
29
40
|
//
|
|
@@ -107,11 +118,24 @@ export const create_branch = async (version: VersionManager, branchId: string, f
|
|
|
107
118
|
const branch = version.rxdb.entityManager.instantiate(RxDBBranch);
|
|
108
119
|
branch.id = branchId;
|
|
109
120
|
branch.activated = false;
|
|
121
|
+
// 冗余列与 `activated` 必须同写(`system/branch.ts` 的唯一索引就架在它上面)。
|
|
122
|
+
// 漏写一处,那一行就绕过唯一约束,而 schema 那一半的保护正好在这种漏写上失效。
|
|
123
|
+
branch.activeKey = null;
|
|
110
124
|
branch.local = true;
|
|
111
125
|
branch.remote = false;
|
|
112
126
|
branch.fromChangeId = fromChange?.id ?? null;
|
|
113
127
|
branch.parentId = fromBranch.id;
|
|
114
128
|
await branchRepository.create(branch);
|
|
129
|
+
|
|
130
|
+
// 贡献方的分支级行写在**这一个**事务里,不另开一个:分支行与贡献行分处两个事务的话,
|
|
131
|
+
// 中间失败留下的是一条「分支在、贡献行不在」的记录,而这种半条分支与一条正常的老分支
|
|
132
|
+
// 在形状上分辨不出来。抛错就让整条 `create_branch` 回滚,这是对的。
|
|
133
|
+
//
|
|
134
|
+
// 串行而非 `Promise.all`:`executor` 是一条并发度为 1 的队列,并行发起只会让
|
|
135
|
+
// 写入顺序取决于各贡献方内部 await 的排布,出问题时复现不出来。
|
|
136
|
+
for (const contribution of version.rxdb.systemContributions) {
|
|
137
|
+
await contribution.writeBranchRows(version.rxdb.entityManager, { executor, branchId });
|
|
138
|
+
}
|
|
115
139
|
return branch;
|
|
116
140
|
});
|
|
117
141
|
|