Skip to content

Commit fba972d

Browse files
authored
1 parent cd26d47 commit fba972d

File tree

20 files changed

+137
-165
lines changed

20 files changed

+137
-165
lines changed

ARCHITECTURE.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -349,18 +349,18 @@ export class MyService extends TypedEventEmitter<MyServiceEvents> {
349349

350350
### 3. Create Subscriptions in Router
351351

352-
Use a helper to reduce boilerplate. For global events (broadcast to all subscribers):
352+
Use `toIterable()` on the service to convert events to an async iterable. For global events (broadcast to all subscribers):
353353

354354
```typescript
355355
// src/main/trpc/routers/my-router.ts
356-
import { on } from "node:events";
357356
import { MyServiceEvent, type MyServiceEvents } from "../../services/my-service/schemas";
358357

359358
function subscribe<K extends keyof MyServiceEvents>(event: K) {
360359
return publicProcedure.subscription(async function* (opts) {
361360
const service = getService();
362-
for await (const [payload] of on(service, event, { signal: opts.signal })) {
363-
yield payload as MyServiceEvents[K];
361+
const iterable = service.toIterable(event, { signal: opts.signal });
362+
for await (const data of iterable) {
363+
yield data;
364364
}
365365
});
366366
}
@@ -382,15 +382,15 @@ export interface ShellEvents {
382382
}
383383

384384
// Router filters events to the specific session
385-
function subscribeToSession<K extends keyof ShellEvents>(event: K) {
385+
function subscribeFiltered<K extends keyof ShellEvents>(event: K) {
386386
return publicProcedure
387387
.input(sessionIdInput)
388388
.subscription(async function* (opts) {
389389
const service = getService();
390390
const targetSessionId = opts.input.sessionId;
391+
const iterable = service.toIterable(event, { signal: opts.signal });
391392

392-
for await (const [payload] of on(service, event, { signal: opts.signal })) {
393-
const data = payload as ShellEvents[K];
393+
for await (const data of iterable) {
394394
if (data.sessionId === targetSessionId) {
395395
yield data;
396396
}
@@ -399,8 +399,8 @@ function subscribeToSession<K extends keyof ShellEvents>(event: K) {
399399
}
400400

401401
export const shellRouter = router({
402-
onData: subscribeToSession(ShellEvent.Data),
403-
onExit: subscribeToSession(ShellEvent.Exit),
402+
onData: subscribeFiltered(ShellEvent.Data),
403+
onExit: subscribeFiltered(ShellEvent.Exit),
404404
});
405405
```
406406

apps/array/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@
110110
"@tiptap/react": "^3.11.0",
111111
"@tiptap/starter-kit": "^3.11.0",
112112
"@tiptap/suggestion": "^3.11.0",
113-
"@trpc/client": "^11.7.2",
114-
"@trpc/react-query": "^11.7.2",
115-
"@trpc/server": "^11.7.2",
113+
"@trpc/client": "^11.8.0",
114+
"@trpc/react-query": "^11.8.0",
115+
"@trpc/server": "^11.8.0",
116116
"@xterm/addon-fit": "^0.10.0",
117117
"@xterm/addon-serialize": "^0.13.0",
118118
"@xterm/addon-web-links": "^0.11.0",

apps/array/src/main/lib/typed-event-emitter.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EventEmitter } from "node:events";
1+
import { EventEmitter, on } from "node:events";
22

33
export class TypedEventEmitter<TEvents> extends EventEmitter {
44
emit<K extends keyof TEvents & string>(
@@ -21,4 +21,13 @@ export class TypedEventEmitter<TEvents> extends EventEmitter {
2121
): this {
2222
return super.off(event, listener);
2323
}
24+
25+
async *toIterable<K extends keyof TEvents & string>(
26+
event: K,
27+
opts?: { signal?: AbortSignal },
28+
): AsyncIterable<TEvents[K]> {
29+
for await (const [payload] of on(this, event, opts)) {
30+
yield payload as TEvents[K];
31+
}
32+
}
2433
}

apps/array/src/main/services/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,3 @@
22
* Auto-import all IPC service types for declaration merging
33
* This file is auto-generated by vite-plugin-auto-services.ts
44
*/
5-
6-

apps/array/src/main/services/ui/schemas.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ export const UIServiceEvent = {
66
ClearStorage: "clear-storage",
77
} as const;
88

9+
// UI events are simple signals - payload is just a marker that the event fired
910
export interface UIServiceEvents {
10-
[UIServiceEvent.OpenSettings]: undefined;
11-
[UIServiceEvent.NewTask]: undefined;
12-
[UIServiceEvent.ResetLayout]: undefined;
13-
[UIServiceEvent.ClearStorage]: undefined;
11+
[UIServiceEvent.OpenSettings]: true;
12+
[UIServiceEvent.NewTask]: true;
13+
[UIServiceEvent.ResetLayout]: true;
14+
[UIServiceEvent.ClearStorage]: true;
1415
}

apps/array/src/main/services/ui/service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ import { UIServiceEvent, type UIServiceEvents } from "./schemas.js";
99
@injectable()
1010
export class UIService extends TypedEventEmitter<UIServiceEvents> {
1111
openSettings(): void {
12-
this.emit(UIServiceEvent.OpenSettings, undefined);
12+
this.emit(UIServiceEvent.OpenSettings, true);
1313
}
1414

1515
newTask(): void {
16-
this.emit(UIServiceEvent.NewTask, undefined);
16+
this.emit(UIServiceEvent.NewTask, true);
1717
}
1818

1919
resetLayout(): void {
20-
this.emit(UIServiceEvent.ResetLayout, undefined);
20+
this.emit(UIServiceEvent.ResetLayout, true);
2121
}
2222

2323
clearStorage(): void {
24-
this.emit(UIServiceEvent.ClearStorage, undefined);
24+
this.emit(UIServiceEvent.ClearStorage, true);
2525
}
2626
}

apps/array/src/main/services/updates/schemas.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export type UpdatesStatusPayload = {
3131
};
3232

3333
export interface UpdatesEvents {
34-
[UpdatesEvent.Ready]: undefined;
34+
[UpdatesEvent.Ready]: true;
3535
[UpdatesEvent.Status]: UpdatesStatusPayload;
36-
[UpdatesEvent.CheckFromMenu]: undefined;
36+
[UpdatesEvent.CheckFromMenu]: true;
3737
}

apps/array/src/main/services/updates/service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class UpdatesService extends TypedEventEmitter<UpdatesEvents> {
5555
}
5656

5757
triggerMenuCheck(): void {
58-
this.emit(UpdatesEvent.CheckFromMenu, undefined);
58+
this.emit(UpdatesEvent.CheckFromMenu, true);
5959
}
6060

6161
checkForUpdates(): CheckForUpdatesOutput {
@@ -120,7 +120,7 @@ export class UpdatesService extends TypedEventEmitter<UpdatesEvents> {
120120

121121
private flushPendingNotification(): void {
122122
if (this.updateReady && this.pendingNotification) {
123-
this.emit(UpdatesEvent.Ready, undefined);
123+
this.emit(UpdatesEvent.Ready, true);
124124
this.pendingNotification = false;
125125
}
126126
}

apps/array/src/main/trpc/routers/agent.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { on } from "node:events";
21
import type { ContentBlock } from "@agentclientprotocol/sdk";
32
import { container } from "../../di/container.js";
43
import { MAIN_TOKENS } from "../../di/tokens.js";
54
import {
65
AgentServiceEvent,
7-
type AgentSessionEventPayload,
86
cancelPromptInput,
97
cancelSessionInput,
108
promptInput,
@@ -64,14 +62,11 @@ export const agentRouter = router({
6462
.subscription(async function* (opts) {
6563
const service = getService();
6664
const targetSessionId = opts.input.sessionId;
67-
const options = opts.signal ? { signal: opts.signal } : undefined;
65+
const iterable = service.toIterable(AgentServiceEvent.SessionEvent, {
66+
signal: opts.signal,
67+
});
6868

69-
for await (const [payload] of on(
70-
service,
71-
AgentServiceEvent.SessionEvent,
72-
options,
73-
)) {
74-
const event = payload as AgentSessionEventPayload;
69+
for await (const event of iterable) {
7570
if (event.sessionId === targetSessionId) {
7671
yield event.payload;
7772
}

apps/array/src/main/trpc/routers/deep-link.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { on } from "node:events";
21
import { container } from "../../di/container.js";
32
import { MAIN_TOKENS } from "../../di/tokens.js";
43
import {
54
TaskLinkEvent,
6-
type TaskLinkEvents,
75
type TaskLinkService,
86
} from "../../services/task-link/service.js";
97
import { publicProcedure, router } from "../trpc.js";
@@ -18,13 +16,11 @@ export const deepLinkRouter = router({
1816
*/
1917
onOpenTask: publicProcedure.subscription(async function* (opts) {
2018
const service = getService();
21-
const options = opts.signal ? { signal: opts.signal } : undefined;
22-
for await (const [payload] of on(
23-
service,
24-
TaskLinkEvent.OpenTask,
25-
options,
26-
)) {
27-
yield payload as TaskLinkEvents[typeof TaskLinkEvent.OpenTask];
19+
const iterable = service.toIterable(TaskLinkEvent.OpenTask, {
20+
signal: opts.signal,
21+
});
22+
for await (const data of iterable) {
23+
yield data;
2824
}
2925
}),
3026

0 commit comments

Comments
 (0)