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:
@@ -473,6 +473,47 @@ describe("createWorkerHostEventTarget", () => {
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("close()", () => {
|
||||
it("restores original worker.onmessage handler", () => {
|
||||
const worker = createMockWorker();
|
||||
const originalOnmessage = vi.fn();
|
||||
worker.onmessage = originalOnmessage as any;
|
||||
|
||||
const eventTarget = createWorkerHostEventTarget<TestEvent>(worker as any);
|
||||
expect(worker.onmessage).not.toBe(originalOnmessage);
|
||||
|
||||
eventTarget.close();
|
||||
expect(worker.onmessage).toBe(originalOnmessage);
|
||||
});
|
||||
|
||||
it("clears all listeners so events are no longer delivered", () => {
|
||||
const worker = createMockWorker();
|
||||
const eventTarget = createWorkerHostEventTarget<TestEvent>(worker as any);
|
||||
|
||||
const listener = vi.fn();
|
||||
eventTarget.addEventListener("topic:a", listener);
|
||||
|
||||
eventTarget.close();
|
||||
|
||||
const envelope: EventEnvelope = { type: "topic", id: "a", payload: "data" };
|
||||
worker.simulateMessage(envelope);
|
||||
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("is idempotent", () => {
|
||||
const worker = createMockWorker();
|
||||
const originalOnmessage = vi.fn();
|
||||
worker.onmessage = originalOnmessage as any;
|
||||
|
||||
const eventTarget = createWorkerHostEventTarget<TestEvent>(worker as any);
|
||||
eventTarget.close();
|
||||
eventTarget.close();
|
||||
|
||||
expect(worker.onmessage).toBe(originalOnmessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createWorkerThreadEventTarget", () => {
|
||||
@@ -769,6 +810,20 @@ describe("createWorkerThreadEventTarget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("createWorkerThreadEventTarget context guard", () => {
|
||||
it("throws if globalThis.postMessage is not available", () => {
|
||||
const originalPostMessage = (globalThis as any).postMessage;
|
||||
delete (globalThis as any).postMessage;
|
||||
try {
|
||||
expect(() => createWorkerThreadEventTarget<TestEvent>()).toThrow(
|
||||
"createWorkerThreadEventTarget must be called inside a Worker context where globalThis.postMessage is available",
|
||||
);
|
||||
} finally {
|
||||
(globalThis as any).postMessage = originalPostMessage;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("bidirectional communication (host + thread)", () => {
|
||||
it("host sends envelope that thread receives", () => {
|
||||
const worker = createMockWorker();
|
||||
|
||||
Reference in New Issue
Block a user