import { describe, expect, it } from "vitest"; import { CanonicalJsonEventCodec, EventDB, EventHashMismatchError, MemoryBlobStorage, composeStorageTransforms, createAesGcmTransform, createEventIdentity, createMsgPackTransform, type BaseEventData, type BlobStorageAddedEvent, } from "../src/index.js"; interface TestEvent extends BaseEventData { readonly type: "test"; readonly payload: unknown; } const codec = new CanonicalJsonEventCodec(); const identify = createEventIdentity((event) => codec.encodeCanonical(event)); describe("EventDB", () => { it("loads historical events, deduplicates observations, and sorts deterministically", async () => { const storage = new ObservableMemoryStorage(); const first: TestEvent = { version: 1, timestamp: 2, nonce: "a", type: "test", payload: 1 }; const firstId = await identify(first); await storage.set(firstId, first); const database = await EventDB.open(storage, { identify }); const observed: string[] = []; database.on("event", (event) => observed.push(event.id)); expect((await database.append(first)).id).toBe(firstId); storage.notify({ id: firstId, data: first, source: "remote" }); const earlier = await database.append({ ...first, timestamp: 1, nonce: "b" }); const tied = await database.append({ ...first, nonce: "c" }); const events = await database.events(); expect(events[0]?.id).toBe(earlier.id); expect(events.slice(1).map((event) => event.id)).toEqual([firstId, tied.id].sort()); expect(observed).toEqual([earlier.id, tied.id]); }); it("rejects a logical value stored under the wrong ID", async () => { const storage = new MemoryBlobStorage(); await storage.set("sha256:wrong", { version: 1, timestamp: 1, nonce: "a", type: "test", payload: null, }); await expect(EventDB.open(storage, { identify })).rejects.toBeInstanceOf(EventHashMismatchError); }); it("keeps event identity stable when storage encryption changes physical bytes", async () => { const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]); const transform = composeStorageTransforms( createMsgPackTransform(), createAesGcmTransform(key), ); const plainStorage = new MemoryBlobStorage(); const encryptedStorage = new MemoryBlobStorage({ transform }); const plain = await EventDB.open(plainStorage, { identify }); const encrypted = await EventDB.open(encryptedStorage, { identify }); const event: TestEvent = { version: 1, timestamp: 1, nonce: "same", type: "test", payload: "value" }; expect((await plain.append(event)).id).toBe((await encrypted.append(event)).id); }); }); class ObservableMemoryStorage extends MemoryBlobStorage { notify(event: BlobStorageAddedEvent): void { this.emitAdded(event); } }