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