| ... | ... | @@ -16,7 +16,7 @@ import { |
| 16 | 16 | WsResponse, |
| 17 | 17 | } from "@t3tools/contracts"; |
| 18 | 18 | import { NodeHttpServer } from "@effect/platform-node"; |
| 19 | | import { Cause, Effect, Exit, Layer, Schema, Scope, ServiceMap, Stream, Struct } from "effect"; |
| 19 | import { Cause, Effect, Exit, Layer, Ref, Schema, Scope, ServiceMap, Stream, Struct } from "effect"; |
| 20 | 20 | import { WebSocketServer, type WebSocket } from "ws"; |
| 21 | 21 | |
| 22 | 22 | import { createLogger } from "./logger"; |
| ... | ... | @@ -171,7 +171,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E |
| 171 | 171 | const keybindingsManager = yield* Keybindings; |
| 172 | 172 | const git = yield* GitCore; |
| 173 | 173 | |
| 174 | | const clients = new Set<WebSocket>(); |
| 174 | const clients = yield* Ref.make(new Set<WebSocket>()); |
| 175 | 175 | const logger = createLogger("ws"); |
| 176 | 176 | |
| 177 | 177 | function logOutgoingPush(push: WsPush, recipients: number) { |
| ... | ... | @@ -183,22 +183,23 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E |
| 183 | 183 | }); |
| 184 | 184 | } |
| 185 | 185 | |
| 186 | | const onTerminalEvent = (event: TerminalEvent) => { |
| 187 | | const push: WsPush = { |
| 188 | | type: "push", |
| 189 | | channel: WS_CHANNELS.terminalEvent, |
| 190 | | data: event, |
| 191 | | }; |
| 192 | | const message = JSON.stringify(push); |
| 193 | | let recipients = 0; |
| 194 | | for (const client of clients) { |
| 195 | | if (client.readyState === client.OPEN) { |
| 196 | | client.send(message); |
| 197 | | recipients += 1; |
| 186 | const onTerminalEvent = (event: TerminalEvent) => |
| 187 | Effect.gen(function* () { |
| 188 | const push: WsPush = { |
| 189 | type: "push", |
| 190 | channel: WS_CHANNELS.terminalEvent, |
| 191 | data: event, |
| 192 | }; |
| 193 | const message = yield* Schema.encodeEffect(Schema.fromJsonString(WsPush))(push); |
| 194 | let recipients = 0; |
| 195 | for (const client of yield* Ref.get(clients)) { |
| 196 | if (client.readyState === client.OPEN) { |
| 197 | client.send(message); |
| 198 | recipients += 1; |
| 199 | } |
| 198 | 200 | } |
| 199 | | } |
| 200 | | logOutgoingPush(push, recipients); |
| 201 | | }; |
| 201 | logOutgoingPush(push, recipients); |
| 202 | }).pipe(Effect.runPromise); |
| 202 | 203 | |
| 203 | 204 | // HTTP server — serves static files or redirects to Vite dev server |
| 204 | 205 | const httpServer = http.createServer((req, res) => { |
| ... | ... | @@ -273,12 +274,10 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E |
| 273 | 274 | }); |
| 274 | 275 | }); |
| 275 | 276 | |
| 276 | | const closeAllClients = () => { |
| 277 | | for (const client of clients) { |
| 278 | | client.close(); |
| 279 | | } |
| 280 | | clients.clear(); |
| 281 | | }; |
| 277 | const closeAllClients = Ref.get(clients).pipe( |
| 278 | Effect.flatMap(Effect.forEach((client) => Effect.sync(() => client.close()))), |
| 279 | Effect.flatMap(() => Ref.set(clients, new Set())), |
| 280 | ); |
| 282 | 281 | |
| 283 | 282 | const listenOptions = host ? { host, port } : { port }; |
| 284 | 283 | |
| ... | ... | @@ -295,15 +294,15 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E |
| 295 | 294 | ); |
| 296 | 295 | |
| 297 | 296 | yield* Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) => |
| 298 | | Effect.sync(() => { |
| 297 | Effect.gen(function* () { |
| 299 | 298 | const push: WsPush = { |
| 300 | 299 | type: "push", |
| 301 | 300 | channel: ORCHESTRATION_WS_CHANNELS.domainEvent, |
| 302 | 301 | data: event, |
| 303 | 302 | }; |
| 304 | | const message = JSON.stringify(push); |
| 303 | const message = yield* Schema.encodeEffect(Schema.fromJsonString(WsPush))(push); |
| 305 | 304 | let recipients = 0; |
| 306 | | for (const client of clients) { |
| 305 | for (const client of yield* Ref.get(clients)) { |
| 307 | 306 | if (client.readyState === client.OPEN) { |
| 308 | 307 | client.send(message); |
| 309 | 308 | recipients += 1; |
| ... | ... | @@ -532,7 +531,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E |
| 532 | 531 | }); |
| 533 | 532 | |
| 534 | 533 | wss.on("connection", (ws) => { |
| 535 | | clients.add(ws); |
| 534 | void Effect.runPromise(Ref.update(clients, (clients) => clients.add(ws))); |
| 536 | 535 | |
| 537 | 536 | const segments = cwd.split(/[/\\]/).filter(Boolean); |
| 538 | 537 | const projectName = segments[segments.length - 1] ?? "project"; |
| ... | ... | @@ -550,11 +549,21 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E |
| 550 | 549 | }); |
| 551 | 550 | |
| 552 | 551 | ws.on("close", () => { |
| 553 | | clients.delete(ws); |
| 552 | void Effect.runPromise( |
| 553 | Ref.update(clients, (clients) => { |
| 554 | clients.delete(ws); |
| 555 | return clients; |
| 556 | }), |
| 557 | ); |
| 554 | 558 | }); |
| 555 | 559 | |
| 556 | 560 | ws.on("error", () => { |
| 557 | | clients.delete(ws); |
| 561 | void Effect.runPromise( |
| 562 | Ref.update(clients, (clients) => { |
| 563 | clients.delete(ws); |
| 564 | return clients; |
| 565 | }), |
| 566 | ); |
| 558 | 567 | }); |
| 559 | 568 | }); |
| 560 | 569 | |
| ... | ... | @@ -575,10 +584,7 @@ export const createServer = Effect.fn(function* (options: ServerOptions = {}): E |
| 575 | 584 | yield* NodeHttpServer.make(() => httpServer, listenOptions); |
| 576 | 585 | |
| 577 | 586 | yield* Effect.addFinalizer(() => |
| 578 | | Effect.all([ |
| 579 | | Effect.sync(() => closeAllClients()), |
| 580 | | closeWebSocketServer.pipe(Effect.orElseSucceed(() => undefined)), |
| 581 | | ]), |
| 587 | Effect.all([closeAllClients, closeWebSocketServer.pipe(Effect.orElseSucceed(() => undefined))]), |
| 582 | 588 | ); |
| 583 | 589 | |
| 584 | 590 | return httpServer; |