@aiao/rxdb-plugin-sync 0.0.0-stage → 0.0.26
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/LICENSE +21 -0
- package/README.md +81 -2
- package/dist/SyncManager.d.ts +412 -0
- package/dist/SyncManager.d.ts.map +1 -0
- package/dist/branch-change-id.d.ts +31 -0
- package/dist/branch-change-id.d.ts.map +1 -0
- package/dist/branch-utils.d.ts +8 -0
- package/dist/branch-utils.d.ts.map +1 -0
- package/dist/bulk-sync.d.ts +117 -0
- package/dist/bulk-sync.d.ts.map +1 -0
- package/dist/cascade-blocking.d.ts +38 -0
- package/dist/cascade-blocking.d.ts.map +1 -0
- package/dist/check-repository-updates.d.ts +47 -0
- package/dist/check-repository-updates.d.ts.map +1 -0
- package/dist/cleanup-expired.d.ts +56 -0
- package/dist/cleanup-expired.d.ts.map +1 -0
- package/dist/dependency-graph.d.ts +128 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/get-all-repository-sync-status.d.ts +58 -0
- package/dist/get-all-repository-sync-status.d.ts.map +1 -0
- package/dist/get-repository-sync-status.d.ts +68 -0
- package/dist/get-repository-sync-status.d.ts.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3273 -0
- package/dist/plugin.d.ts +70 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/pull-ancestor-changes.d.ts +41 -0
- package/dist/pull-ancestor-changes.d.ts.map +1 -0
- package/dist/pull-batch.d.ts +16 -0
- package/dist/pull-batch.d.ts.map +1 -0
- package/dist/pull-conflict-utils.d.ts +43 -0
- package/dist/pull-conflict-utils.d.ts.map +1 -0
- package/dist/pull-repository.d.ts +36 -0
- package/dist/pull-repository.d.ts.map +1 -0
- package/dist/pull.d.ts +16 -0
- package/dist/pull.d.ts.map +1 -0
- package/dist/push-branch.d.ts +22 -0
- package/dist/push-branch.d.ts.map +1 -0
- package/dist/push-repository.d.ts +63 -0
- package/dist/push-repository.d.ts.map +1 -0
- package/dist/push.d.ts +14 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/query-cache-outbox.d.ts +120 -0
- package/dist/query-cache-outbox.d.ts.map +1 -0
- package/dist/sync-branches.d.ts +35 -0
- package/dist/sync-branches.d.ts.map +1 -0
- package/dist/sync-listeners.d.ts +20 -0
- package/dist/sync-listeners.d.ts.map +1 -0
- package/dist/sync-manager.utils.d.ts +36 -0
- package/dist/sync-manager.utils.d.ts.map +1 -0
- package/dist/sync-repository.d.ts +63 -0
- package/dist/sync-repository.d.ts.map +1 -0
- package/dist/topological-sort.d.ts +126 -0
- package/dist/topological-sort.d.ts.map +1 -0
- package/dist/version.utils.d.ts +3 -0
- package/dist/version.utils.d.ts.map +1 -0
- package/package.json +58 -3
- package/src/SyncManager.ts +662 -0
- package/src/branch-change-id.ts +70 -0
- package/src/branch-utils.ts +37 -0
- package/src/bulk-sync.ts +338 -0
- package/src/cascade-blocking.ts +61 -0
- package/src/check-repository-updates.ts +143 -0
- package/src/cleanup-expired.ts +271 -0
- package/src/dependency-graph.ts +324 -0
- package/src/get-all-repository-sync-status.ts +115 -0
- package/src/get-repository-sync-status.ts +237 -0
- package/src/index.ts +53 -0
- package/src/plugin.ts +124 -0
- package/src/pull-ancestor-changes.ts +61 -0
- package/src/pull-batch.ts +410 -0
- package/src/pull-conflict-utils.ts +280 -0
- package/src/pull-repository.ts +701 -0
- package/src/pull.ts +204 -0
- package/src/push-branch.ts +61 -0
- package/src/push-repository.ts +1002 -0
- package/src/push.ts +104 -0
- package/src/query-cache-outbox.ts +866 -0
- package/src/sync-branches.ts +156 -0
- package/src/sync-listeners.ts +289 -0
- package/src/sync-manager.utils.ts +48 -0
- package/src/sync-repository.ts +302 -0
- package/src/topological-sort.ts +246 -0
- package/src/version.utils.ts +20 -0
package/src/pull.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findCurrentSyncRecord,
|
|
3
|
+
getEntityMetadata,
|
|
4
|
+
getSyncCapability,
|
|
5
|
+
getSyncType,
|
|
6
|
+
isRepositorySyncEnabled,
|
|
7
|
+
PullOptions,
|
|
8
|
+
PullRepositoryResult,
|
|
9
|
+
PullResult,
|
|
10
|
+
RxDBError,
|
|
11
|
+
RxDBPartialSyncError,
|
|
12
|
+
SyncBeginEvent,
|
|
13
|
+
SyncCompleteEvent,
|
|
14
|
+
SyncErrorEvent
|
|
15
|
+
} from '@aiao/rxdb';
|
|
16
|
+
import { BulkSyncOptions } from './bulk-sync.js';
|
|
17
|
+
import { pullBatch } from './pull-batch.js';
|
|
18
|
+
import { pullRepository } from './pull-repository.js';
|
|
19
|
+
import type { SyncManager } from './SyncManager.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Pull 功能实现
|
|
23
|
+
*
|
|
24
|
+
* 从远程拉取变更并应用到本地实体表
|
|
25
|
+
*
|
|
26
|
+
* 默认使用 pullBatch 单次 HTTP 请求拉取所有实体的变更。
|
|
27
|
+
* 当指定 repositoryFilter 时,降级为 bulkSync 逐实体拉取。
|
|
28
|
+
*
|
|
29
|
+
* @param sm - SyncManager 实例
|
|
30
|
+
* @param options - Pull 选项
|
|
31
|
+
* @returns Pull 结果
|
|
32
|
+
*/
|
|
33
|
+
export async function pull(sm: SyncManager, options?: PullOptions): Promise<PullResult> {
|
|
34
|
+
const rxdb = sm.rxdb;
|
|
35
|
+
|
|
36
|
+
// 验证远程适配器配置
|
|
37
|
+
const remoteAdapterName = rxdb.config.sync?.remote?.adapter;
|
|
38
|
+
if (!remoteAdapterName) {
|
|
39
|
+
throw new RxDBError('Remote adapter not configured.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 触发同步开始事件
|
|
43
|
+
rxdb.dispatchEvent(new SyncBeginEvent('pull'));
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
let pullResult: PullResult;
|
|
47
|
+
|
|
48
|
+
if (!options?.repositoryFilter?.length) {
|
|
49
|
+
// 无过滤时使用批量拉取(单次 HTTP 请求)
|
|
50
|
+
pullResult = await pullBatch(sm, {
|
|
51
|
+
limit: options?.limit,
|
|
52
|
+
fetchAll: options?.fetchAll,
|
|
53
|
+
conflictResolver: options?.conflictResolver
|
|
54
|
+
});
|
|
55
|
+
// pullBatch 对 syncType === 'filter' 的仓库直接 continue(过滤条件是逐仓库的,
|
|
56
|
+
// 拼不进一次批量请求)。不补这一趟,pull() 就会对 filter 仓库一条不拉却报告
|
|
57
|
+
// `hasMore:false, failures:[]` —— isCompletePull 判真、pullableCount 归零,
|
|
58
|
+
// 用户看到的是「已全部同步」。
|
|
59
|
+
await pullFilterRepositories(sm, options, pullResult);
|
|
60
|
+
} else {
|
|
61
|
+
// 指定仓库过滤时降级为逐仓库拉取
|
|
62
|
+
pullResult = await pullWithBulkSync(sm, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 触发同步完成事件
|
|
66
|
+
rxdb.dispatchEvent(new SyncCompleteEvent('pull', pullResult));
|
|
67
|
+
|
|
68
|
+
return pullResult;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// 触发同步错误事件
|
|
71
|
+
rxdb.dispatchEvent(new SyncErrorEvent('pull', error as Error));
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 补拉 `syncType === 'filter'` 的仓库,并把结果并进批量拉取的聚合里。
|
|
78
|
+
*
|
|
79
|
+
* @param sm - SyncManager 实例
|
|
80
|
+
* @param options - 调用方传给 `pull()` 的选项,`limit` / `fetchAll` 原样透传
|
|
81
|
+
* @param aggregated - `pullBatch` 的结果,**就地**累加
|
|
82
|
+
*
|
|
83
|
+
* @remarks
|
|
84
|
+
* `includeRelated: false`:批量那一趟已经覆盖了所有非 filter 仓库,再级联一次只会重复拉取。
|
|
85
|
+
* 单个仓库失败不中断其余仓库,失败结构化进 `failures` —— 与 `pullWithBulkSync` 同口径。
|
|
86
|
+
*/
|
|
87
|
+
async function pullFilterRepositories(sm: SyncManager, options: PullOptions | undefined, aggregated: PullResult) {
|
|
88
|
+
const rxdb = sm.rxdb;
|
|
89
|
+
|
|
90
|
+
for (const EntityClass of rxdb.config.entities) {
|
|
91
|
+
const metadata = getEntityMetadata(EntityClass);
|
|
92
|
+
const syncType = getSyncType(metadata, rxdb.config.sync);
|
|
93
|
+
if (syncType !== 'filter' || !getSyncCapability(syncType).pull) continue;
|
|
94
|
+
|
|
95
|
+
const repoSync = await findCurrentSyncRecord(sm.rxdb, metadata.namespace, metadata.name);
|
|
96
|
+
if (!isRepositorySyncEnabled(repoSync)) continue;
|
|
97
|
+
|
|
98
|
+
let result: PullRepositoryResult;
|
|
99
|
+
try {
|
|
100
|
+
result = await pullRepository(sm, metadata.namespace, metadata.name, {
|
|
101
|
+
limit: options?.limit,
|
|
102
|
+
fetchAll: options?.fetchAll,
|
|
103
|
+
conflictResolver: options?.conflictResolver,
|
|
104
|
+
includeRelated: false
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
108
|
+
// 失败前已提交的进度挂在 RxDBPartialSyncError.result 上,同样要并进聚合
|
|
109
|
+
if (normalized instanceof RxDBPartialSyncError) {
|
|
110
|
+
accumulatePullProgress(aggregated, normalized.result as PullRepositoryResult);
|
|
111
|
+
}
|
|
112
|
+
aggregated.failures.push({
|
|
113
|
+
repository: { namespace: metadata.namespace, entity: metadata.name },
|
|
114
|
+
error: normalized instanceof RxDBPartialSyncError ? normalized.cause : normalized
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
accumulatePullProgress(aggregated, result);
|
|
120
|
+
aggregated.failures.push(...result.failures);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** 把一个仓库的拉取进度就地累加进聚合结果(不含 failures)。 */
|
|
125
|
+
function accumulatePullProgress(aggregated: PullResult, result: PullRepositoryResult): void {
|
|
126
|
+
aggregated.pulled += result.pulled;
|
|
127
|
+
aggregated.compacted += result.compacted;
|
|
128
|
+
aggregated.applied += result.applied;
|
|
129
|
+
aggregated.conflictsResolved += result.conflictsResolved;
|
|
130
|
+
aggregated.conflictsDeferred += result.conflictsDeferred;
|
|
131
|
+
aggregated.hasMore ||= result.hasMore;
|
|
132
|
+
aggregated.persistedProgress ||= result.persistedProgress;
|
|
133
|
+
aggregated.historyInvalidated ||= result.historyInvalidated;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 降级方案:使用 bulkSync 逐仓库拉取(用于指定 repositoryFilter 的场景)
|
|
138
|
+
*/
|
|
139
|
+
async function pullWithBulkSync(sm: SyncManager, options: PullOptions): Promise<PullResult> {
|
|
140
|
+
const bulkOptions: BulkSyncOptions = {
|
|
141
|
+
operation: 'pull',
|
|
142
|
+
repositories: options.repositoryFilter?.map(item => {
|
|
143
|
+
if (typeof item === 'string') {
|
|
144
|
+
return { namespace: 'public', entity: item };
|
|
145
|
+
}
|
|
146
|
+
return item;
|
|
147
|
+
}),
|
|
148
|
+
// 此前这里漏传,逐仓库拉取时 limit/fetchAll 静默失效
|
|
149
|
+
pull: { limit: options.limit, fetchAll: options.fetchAll, conflictResolver: options.conflictResolver }
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const bulkResult = await sm.bulkSync(bulkOptions);
|
|
153
|
+
|
|
154
|
+
const aggregated: PullResult = {
|
|
155
|
+
pulled: 0,
|
|
156
|
+
compacted: 0,
|
|
157
|
+
applied: 0,
|
|
158
|
+
hasMore: false,
|
|
159
|
+
conflictsResolved: 0,
|
|
160
|
+
conflictsDeferred: 0,
|
|
161
|
+
persistedProgress: false,
|
|
162
|
+
historyInvalidated: false,
|
|
163
|
+
failures: []
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
for (const result of bulkResult.results) {
|
|
167
|
+
// 失败仓库自己已提交的进度也要累加 —— 它藏在 `RxDBPartialSyncError.result`
|
|
168
|
+
// 里,被 bulkSync 原样挂在 `result.result` 上,只看 success 会把这段进度丢掉。
|
|
169
|
+
const pullResult = result.result?.pullResult;
|
|
170
|
+
if (pullResult) {
|
|
171
|
+
aggregated.pulled += pullResult.pulled;
|
|
172
|
+
aggregated.compacted += pullResult.compacted;
|
|
173
|
+
aggregated.applied += pullResult.applied;
|
|
174
|
+
aggregated.conflictsResolved += pullResult.conflictsResolved;
|
|
175
|
+
aggregated.conflictsDeferred += pullResult.conflictsDeferred;
|
|
176
|
+
aggregated.hasMore ||= pullResult.hasMore;
|
|
177
|
+
aggregated.persistedProgress ||= pullResult.persistedProgress;
|
|
178
|
+
aggregated.historyInvalidated ||= pullResult.historyInvalidated;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (result.success) continue;
|
|
182
|
+
|
|
183
|
+
// 此前失败的仓库被静默跳过,调用方拿到「成功」的聚合结果
|
|
184
|
+
// 只留第一个失败会让后面几个仓库的失败无处可查,全部结构化保留
|
|
185
|
+
const failure =
|
|
186
|
+
result.error ??
|
|
187
|
+
new RxDBError(`Repository pull failed: ${result.repository.namespace}:${result.repository.entity}`);
|
|
188
|
+
aggregated.failures.push({
|
|
189
|
+
repository: result.repository,
|
|
190
|
+
// 嵌套的 partial error 解包成根因,避免调用方拿到「错误里套错误」
|
|
191
|
+
error: failure instanceof RxDBPartialSyncError ? failure.cause : failure
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const [firstFailure] = aggregated.failures;
|
|
196
|
+
if (firstFailure) {
|
|
197
|
+
// 单个失败且零进度时包装只会多剥一层,原始错误更有用(对齐 pull-batch.ts 的约定);
|
|
198
|
+
// 但只要有进度、或有多于一个失败,就必须包装 —— 否则进度和其余失败都无处可查。
|
|
199
|
+
if (!aggregated.persistedProgress && aggregated.failures.length === 1) throw firstFailure.error;
|
|
200
|
+
throw new RxDBPartialSyncError<PullResult>(aggregated, firstFailure.error);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return aggregated;
|
|
204
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { toRemoteFromChangeId } from './branch-change-id.js';
|
|
2
|
+
import type { SyncManager } from './SyncManager.js';
|
|
3
|
+
|
|
4
|
+
export interface PushBranchResult {
|
|
5
|
+
synced: number;
|
|
6
|
+
skipped: string[];
|
|
7
|
+
/**
|
|
8
|
+
* 分叉点变更还没有 `remoteId`,本次只能带 `fromChangeId: null` 上行。
|
|
9
|
+
*
|
|
10
|
+
* 调用方应在实体变更推送完成、分叉点拿到 `remoteId` 之后重推一次分支,
|
|
11
|
+
* 把远端那一行补全。
|
|
12
|
+
*/
|
|
13
|
+
forkPointPending: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 将当前激活分支推送到远程
|
|
18
|
+
*
|
|
19
|
+
* 规则:
|
|
20
|
+
* 1. 不推送 id='main' 的分支
|
|
21
|
+
* 2. 不修改远程分支的 activated 属性(由 SQL 函数保证)
|
|
22
|
+
* 3. `fromChangeId` 翻译成远端 id 后再上行(见 {@link toRemoteFromChangeId})
|
|
23
|
+
*/
|
|
24
|
+
export async function pushBranch(sm: SyncManager): Promise<PushBranchResult> {
|
|
25
|
+
const branch = await sm.getCurrentBranch();
|
|
26
|
+
if (!branch || branch.id === 'main') {
|
|
27
|
+
return { synced: 0, skipped: branch ? ['main'] : [], forkPointPending: false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { adapter: remoteAdapter } = await sm.getRemoteRepositories();
|
|
31
|
+
|
|
32
|
+
if (!remoteAdapter.pushBranches) {
|
|
33
|
+
return { synced: 0, skipped: [], forkPointPending: false };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { branchRepository, changeRepository } = await sm.getLocalRepositories();
|
|
37
|
+
|
|
38
|
+
// 本地 change id 对远端毫无意义,必须翻译成 remoteId 再上行。
|
|
39
|
+
// 分叉点还没推上去时只能发 null(远端此刻确实不知道这个分叉点),并把这件事报给调用方。
|
|
40
|
+
const localFromChangeId = branch.fromChangeId ?? null;
|
|
41
|
+
const remoteFromChangeId =
|
|
42
|
+
localFromChangeId === null ? null : await toRemoteFromChangeId(changeRepository, localFromChangeId);
|
|
43
|
+
const forkPointPending = localFromChangeId !== null && remoteFromChangeId === null;
|
|
44
|
+
|
|
45
|
+
const branchData: Record<string, unknown> = {
|
|
46
|
+
id: branch.id,
|
|
47
|
+
fromChangeId: remoteFromChangeId,
|
|
48
|
+
local: branch.local,
|
|
49
|
+
remote: true,
|
|
50
|
+
parentId: branch.parentId
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = await remoteAdapter.pushBranches([branchData]);
|
|
54
|
+
|
|
55
|
+
// 推送成功后更新本地分支的 remote 标记
|
|
56
|
+
if (result.synced > 0 && !branch.remote) {
|
|
57
|
+
await branchRepository.update(branch, { remote: true, updatedAt: new Date() });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { ...result, forkPointPending };
|
|
61
|
+
}
|