返回 DeepSeek-Reasonix
heartbeat.bridge.ts
根目录 / desktop / frontend / src / custom / features / heartbeat / heartbeat.bridge.ts
1 // Heartbeat panel bridge — typed wrappers around app heartbeat bindings.
2 // Custom components should import from here instead of calling app.* directly
3 // so that heartbeat-specific calls are scoped to this feature.
4
5 import { app } from "../../../lib/bridge";
6 import type { HeartbeatTask } from "./heartbeat.types";
7
8 interface HeartbeatConfigView {
9 revision: number;
10 etag: string;
11 tasks: HeartbeatTask[];
12 }
13
14 let loadedConfigToken: Pick<HeartbeatConfigView, "revision" | "etag"> | null = null;
15 let loadedTasks: HeartbeatTask[] = [];
16 let configQueue: Promise<void> = Promise.resolve();
17
18 function enqueueConfigOperation<T>(operation: () => Promise<T>): Promise<T> {
19 const result = configQueue.then(operation, operation);
20 configQueue = result.then(() => undefined, () => undefined);
21 return result;
22 }
23
24 async function reloadConfig(): Promise<HeartbeatTask[]> {
25 const raw = await app.HeartbeatReloadConfig();
26 const view = (raw ?? { revision: 0, etag: "", tasks: [] }) as HeartbeatConfigView;
27 loadedConfigToken = { revision: view.revision || 0, etag: view.etag || "" };
28 loadedTasks = Array.isArray(view.tasks) ? view.tasks : [];
29 return loadedTasks;
30 }
31
32 async function saveConfig(tasks: HeartbeatTask[]): Promise<HeartbeatTask[]> {
33 const view = await app.HeartbeatSaveConfig({
34 revision: loadedConfigToken?.revision || 0,
35 etag: loadedConfigToken?.etag || "",
36 tasks,
37 });
38 const saved = (view ?? { revision: 0, etag: "" }) as HeartbeatConfigView;
39 loadedConfigToken = { revision: saved.revision || 0, etag: saved.etag || "" };
40 loadedTasks = Array.isArray(saved.tasks) ? saved.tasks : tasks;
41 return loadedTasks;
42 }
43
44 export function heartbeatListTasks(): Promise<HeartbeatTask[]> {
45 return enqueueConfigOperation(reloadConfig);
46 }
47
48 export function heartbeatMutateTasks(mutate: (tasks: HeartbeatTask[]) => HeartbeatTask[]): Promise<HeartbeatTask[]> {
49 return enqueueConfigOperation(async () => {
50 if (!loadedConfigToken) await reloadConfig();
51 const current = loadedTasks.map((task) => ({ ...task }));
52 try {
53 return await saveConfig(mutate(current));
54 } catch (error) {
55 try {
56 await reloadConfig();
57 } catch {
58 // Preserve the original mutation error; the next operation will retry.
59 }
60 throw error;
61 }
62 });
63 }
64
65 export function heartbeatTriggerNow(id: string): Promise<void> {
66 return app.HeartbeatTriggerNow(id);
67 }
68
69 export function heartbeatGenerateID(): Promise<string> {
70 return app.HeartbeatGenerateID();
71 }
72
72 lines TYPESCRIPT