fix: add close() lifecycle methods to all adapters, fix WS client handler preservation, add Worker thread context guard
- Add close() to Redis, WS Client, WS Server, Worker Host, Worker Thread adapters for graceful teardown (cleanup subscriptions, restore handlers, clear maps) - WS Client now saves/restores original onmessage (consistent with WS Server) - WS Client dispatchEvent/addEventListener/removeEventListener are no-ops after close() - WS Server close() removes all connections and clears local listeners - Redis close() unsubscribes all channels and removes message listener - Worker Host/Thread close() restore original onmessage and clear callbacks - Worker Thread throws clear error if globalThis.postMessage is unavailable - Add double-call guard to WS Server removeConnection - Export new adapter interface types (RedisEventTarget, WebSocketClientEventTarget, etc.) - Add sideEffects: false to package.json for tree-shaking - Update architecture docs: lifecycle section, close() contract, adapter status updates - 22 new tests covering close(), handler restoration, idempotency, and context guard
This commit is contained in:
@@ -819,4 +819,90 @@ describe("createWebSocketServerEventTarget", () => {
|
||||
expect(rawWs).toBe(ws);
|
||||
});
|
||||
});
|
||||
|
||||
describe("close()", () => {
|
||||
it("removes all connections and clears local listeners", () => {
|
||||
const onDisconnection = vi.fn();
|
||||
const server = createWebSocketServerEventTarget<TestEvent>({ onDisconnection });
|
||||
const ws1 = createMockWebSocket();
|
||||
const ws2 = createMockWebSocket();
|
||||
|
||||
server.addConnection(ws1 as any);
|
||||
server.addConnection(ws2 as any);
|
||||
|
||||
server.close();
|
||||
|
||||
expect(onDisconnection).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("no longer delivers events to removed connections after close", () => {
|
||||
const server = createWebSocketServerEventTarget<TestEvent>();
|
||||
const ws = createMockWebSocket();
|
||||
|
||||
server.addConnection(ws as any);
|
||||
ws.simulateMessage(JSON.stringify({ type: "__subscribe", id: "", payload: { topic: "chat:room1" } }));
|
||||
|
||||
server.close();
|
||||
|
||||
const envelope: EventEnvelope = { type: "chat", id: "room1", payload: "hello" };
|
||||
const event = new CustomEvent("chat:room1", { detail: envelope }) as TestEvent;
|
||||
server.dispatchEvent(event);
|
||||
|
||||
expect(ws.send).not.toHaveBeenCalledWith(JSON.stringify(envelope));
|
||||
});
|
||||
|
||||
it("no longer delivers events to local listeners after close", () => {
|
||||
const server = createWebSocketServerEventTarget<TestEvent>();
|
||||
const listener = vi.fn();
|
||||
server.addEventListener("chat:room1", listener);
|
||||
|
||||
server.close();
|
||||
|
||||
const envelope: EventEnvelope = { type: "chat", id: "room1", payload: "hello" };
|
||||
const event = new CustomEvent("chat:room1", { detail: envelope }) as TestEvent;
|
||||
server.dispatchEvent(event);
|
||||
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("restores original onmessage and onclose for all connections", () => {
|
||||
const server = createWebSocketServerEventTarget<TestEvent>();
|
||||
const ws = createMockWebSocket();
|
||||
const originalOnmessage = vi.fn();
|
||||
const originalOnclose = vi.fn();
|
||||
ws.onmessage = originalOnmessage;
|
||||
ws.onclose = originalOnclose;
|
||||
|
||||
server.addConnection(ws as any);
|
||||
expect(ws.onmessage).not.toBe(originalOnmessage);
|
||||
|
||||
server.close();
|
||||
|
||||
expect(ws.onmessage).toBe(originalOnmessage);
|
||||
expect(ws.onclose).toBe(originalOnclose);
|
||||
});
|
||||
|
||||
it("does not close the WebSocket connections", () => {
|
||||
const server = createWebSocketServerEventTarget<TestEvent>();
|
||||
const ws = createMockWebSocket();
|
||||
|
||||
server.addConnection(ws as any);
|
||||
server.close();
|
||||
|
||||
expect(ws.close).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("is idempotent", () => {
|
||||
const onDisconnection = vi.fn();
|
||||
const server = createWebSocketServerEventTarget<TestEvent>({ onDisconnection });
|
||||
const ws = createMockWebSocket();
|
||||
|
||||
server.addConnection(ws as any);
|
||||
|
||||
server.close();
|
||||
server.close();
|
||||
|
||||
expect(onDisconnection).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user