feat: add sync checkpoint restore for last successful sync

Capture rolling online DB snapshots after push/drain/reconcile and expose SyncPage 数据恢复 plus checkpoint/restore APIs; mark Z12h and restore done in coop docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-06 11:49:00 +08:00
parent 75622a728e
commit 680d2b8cde
11 changed files with 860 additions and 16 deletions

View File

@@ -1063,6 +1063,55 @@ export async function reconcileSyncChannel(session: Session, id: string) {
};
}
export type SyncCheckpointSlot = {
synced_at?: string;
source?: string;
table_count?: number;
row_count?: number;
};
export type SyncCheckpointMeta = {
channel_id?: string;
latest?: SyncCheckpointSlot | null;
previous?: SyncCheckpointSlot | null;
};
export async function getSyncCheckpointMeta(session: Session, id: string) {
const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/checkpoint`, {
headers: { Authorization: `Bearer ${session.accessToken}` },
});
const data = await readJson(res);
throwIfBad(res, data, "get checkpoint failed");
return data as SyncCheckpointMeta;
}
export async function restoreSyncChannel(
session: Session,
id: string,
body: { which?: "latest" | "previous"; confirm: boolean }
) {
const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/restore`, {
method: "POST",
headers: {
Authorization: `Bearer ${session.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const data = await readJson(res);
throwIfBad(res, data, "restore failed");
return data as {
ok?: boolean;
which?: string;
synced_at?: string;
source?: string;
tables?: number;
upserted?: number;
deleted?: number;
restored_at?: string;
};
}
/** 外部源 C → 写入本地 B再经 outbox 同步到线上 A */
export async function ingestSyncRows(
session: Session,