import { describe, expect, test } from "bun:test"; import { createAcknowledgement, createRealtimeHistory, createRealtimeMessage, realtimeSseResponse, } from "../src/index.ts"; describe("realtime replay and acknowledgements", () => { test("bounds room history and resumes after a client acknowledgement", () => { const history = createRealtimeHistory({ limitPerRoom: 2 }); const entries = [1, 2, 3].map((value) => history.publish( "chat", createRealtimeMessage({ type: "message", room: "chat", data: { value } }), ), ); expect(history.replay("chat").map((entry) => entry.message.data)).toEqual([ { value: 2 }, { value: 3 }, ]); history.acknowledge("chat", "client-1", entries[1]!.sequence); expect(history.resume("chat", "client-1")).toHaveLength(1); expect(history.snapshot()).toMatchObject({ rooms: 1, messages: 2, acknowledgements: 1 }); }); test("acknowledgements are monotonic and reject impossible values", () => { const history = createRealtimeHistory(); const entry = history.publish("chat", createRealtimeMessage({ type: "message", data: {} })); history.acknowledge("chat", "client", entry.sequence); history.acknowledge("chat", "client", 0); expect(history.acknowledged("chat", "client")).toBe(entry.sequence); expect(() => history.acknowledge("chat", "client", entry.sequence + 1)).toThrow(); expect(createAcknowledgement("chat", entry.sequence, "client").type).toBe("ack"); }); test("encodes sequenced events as SSE", async () => { const message = createRealtimeMessage({ type: "notice", room: "chat", data: { ok: true } }); const response = realtimeSseResponse( new ReadableStream({ start(controller) { controller.enqueue({ sequence: 7, message }); controller.close(); }, }), ); expect(response.headers.get("content-type")).toContain("text/event-stream"); expect(await response.text()).toContain("id: 7\nevent: notice\ndata:"); }); });