返回 DeepSeek-Reasonix
operationOwner.ts
根目录 / desktop / frontend / src / app-runtime / operationOwner.ts
1 export type OperationTarget =
2 | { kind: "session"; tabId: string; sessionKey: string }
3 | { kind: "workspace"; workspaceKey: string }
4 | { kind: "application" };
5
6 export type OperationIdentity = {
7 ownerEpoch: number;
8 requestId: number;
9 target: OperationTarget;
10 navigationIntent?: number;
11 channel: string;
12 };
13
14 export type OperationTerminalStatus = "completed" | "failed" | "cancelled";
15
16 export function operationTargetsEqual(left: OperationTarget, right: OperationTarget): boolean {
17 if (left.kind !== right.kind) return false;
18 if (left.kind === "application") return true;
19 if (left.kind === "workspace" && right.kind === "workspace") {
20 return left.workspaceKey === right.workspaceKey;
21 }
22 return left.kind === "session"
23 && right.kind === "session"
24 && left.tabId === right.tabId
25 && left.sessionKey === right.sessionKey;
26 }
27
28 function freezeIdentity(identity: OperationIdentity): OperationIdentity {
29 return Object.freeze({ ...identity, target: Object.freeze({ ...identity.target }) });
30 }
31
32 export type OperationOwner = ReturnType<typeof createOperationOwner>;
33
34 /**
35 * Owns a last-request-wins interaction without retaining request payloads.
36 * Resource data may complete independently; `owns` governs current UI rights.
37 */
38 export function createOperationOwner(trackOperation: (delta: 1 | -1) => void = () => {}) {
39 let ownerEpoch = 0;
40 let requestId = 0;
41 let mounted = false;
42 const active = new Map<string, OperationIdentity>();
43 const terminalCounts: Record<OperationTerminalStatus, number> = {
44 completed: 0,
45 failed: 0,
46 cancelled: 0,
47 };
48
49 return {
50 mount(): number {
51 if (mounted) return ownerEpoch;
52 ownerEpoch += 1;
53 mounted = true;
54 active.clear();
55 return ownerEpoch;
56 },
57
58 unmount(epoch: number): void {
59 if (!mounted || epoch !== ownerEpoch) return;
60 for (const _identity of active.values()) {
61 terminalCounts.cancelled += 1;
62 trackOperation(-1);
63 }
64 active.clear();
65 mounted = false;
66 },
67
68 begin(target: OperationTarget, navigationIntent?: number, channel = "navigation"): OperationIdentity {
69 if (!mounted) throw new Error("operation owner is not mounted");
70 if (active.has(channel)) terminalCounts.cancelled += 1;
71 else trackOperation(1);
72 const identity = freezeIdentity({
73 ownerEpoch,
74 requestId: ++requestId,
75 target,
76 channel,
77 ...(navigationIntent === undefined ? {} : { navigationIntent }),
78 });
79 active.set(channel, identity);
80 return identity;
81 },
82
83 owns(identity: OperationIdentity): boolean {
84 const current = active.get(identity.channel);
85 return Boolean(
86 mounted
87 && current
88 && identity.ownerEpoch === ownerEpoch
89 && identity === current
90 && operationTargetsEqual(identity.target, current.target)
91 && identity.navigationIntent === current.navigationIntent,
92 );
93 },
94
95 finish(identity: OperationIdentity, status: OperationTerminalStatus = "completed"): boolean {
96 if (!this.owns(identity)) return false;
97 active.delete(identity.channel);
98 trackOperation(-1);
99 terminalCounts[status] += 1;
100 return true;
101 },
102
103 cancel(identity: OperationIdentity): boolean {
104 return this.finish(identity, "cancelled");
105 },
106
107 get activeCount(): number {
108 return active.size;
109 },
110
111 get diagnostics(): Readonly<Record<OperationTerminalStatus, number>> {
112 return { ...terminalCounts };
113 },
114 };
115 }
116
116 lines TYPESCRIPT