import { describe, expect, test } from "bun:test"; import { CacheCoordinator, connectCacheInvalidation, RequestCache, TagCache, responseCache, } from "../src/index.ts"; describe("@wrnexus/cache", () => { test("separates request, data, component, and page cache lifetimes", async () => { const coordinator = new CacheCoordinator({ ttlMs: 100, eventLimit: 10 }); coordinator.data.set("user:1", { id: 1 }, { tags: ["users"] }); coordinator.component.set("avatar:1", "html", { tags: ["users"] }); coordinator.page.set("/users", "document", { tags: ["users"] }); expect(coordinator.inspect().layers.data[0]?.key).toBe("user:1"); expect(coordinator.invalidateTags(["users"])).toBe(3); expect( coordinator.inspect().recentEvents.some((event) => event.operation === "invalidate"), ).toBe(true); const request = new RequestCache(); let calls = 0; const [first, second] = await Promise.all([ request.getOrLoad("permissions", async () => ++calls), request.getOrLoad("permissions", async () => ++calls), ]); expect([first, second, calls]).toEqual([1, 1, 1]); }); test("supports fresh, stale, and tag invalidation", async () => { let now = 0; const cache = new TagCache({ ttlMs: 10, staleWhileRevalidateMs: 10, clock: () => now }); cache.set("a", 1, { tags: ["users"] }); expect(cache.lookup("a").state).toBe("fresh"); now = 11; expect(cache.lookup("a").state).toBe("stale"); expect(cache.invalidateTag("users")).toBe(1); expect(cache.lookup("a").state).toBe("miss"); }); test("deduplicates concurrent loaders", async () => { const cache = new TagCache(); let calls = 0; const loader = async () => ++calls; const [a, b] = await Promise.all([cache.getOrLoad("x", loader), cache.getOrLoad("x", loader)]); expect(a).toBe(1); expect(b).toBe(1); expect(calls).toBe(1); }); test("provides exclusive per-key locks", async () => { const cache = new TagCache(); const order: string[] = []; let release!: () => void; const first = cache.withLock("catalog", async () => { order.push("first:start"); await new Promise((resolve) => (release = resolve)); order.push("first:end"); }); await Promise.resolve(); const second = cache.withLock("catalog", () => order.push("second")); await Promise.resolve(); expect(order).toEqual(["first:start"]); release(); await Promise.all([first, second]); expect(order).toEqual(["first:start", "first:end", "second"]); }); test("invalidation during a load prevents stale work from repopulating the cache", async () => { const cache = new TagCache(); let release!: (value: number) => void; const loading = cache.getOrLoad( "x", () => new Promise((resolve) => (release = resolve)), ); await Promise.resolve(); cache.clear(); release(7); expect(await loading).toBe(7); expect(cache.lookup("x").state).toBe("miss"); }); test("propagates tag, key, and clear invalidations across instances", async () => { const subscriptions = new Map void | Promise>>(); const bus = { async publish(topic: string, message: unknown) { await Promise.all( [...(subscriptions.get(topic) ?? [])].map((handler) => Promise.resolve(handler(message))), ); }, subscribe(topic: string, handler: (message: unknown) => void | Promise) { const handlers = subscriptions.get(topic) ?? new Set(); handlers.add(handler); subscriptions.set(topic, handlers); return () => handlers.delete(handler); }, }; const first = new TagCache(); const second = new TagCache(); const firstChannel = connectCacheInvalidation(first, bus, { namespace: "catalog", instanceId: "one", }); const secondChannel = connectCacheInvalidation(second, bus, { namespace: "catalog", instanceId: "two", }); for (const cache of [first, second]) cache.set("product:1", 1, { tags: ["products"] }); expect(await firstChannel.invalidateTag("products")).toBe(1); expect(second.lookup("product:1").state).toBe("miss"); first.set("one", 1); second.set("one", 1); await secondChannel.delete("one"); expect(first.lookup("one").state).toBe("miss"); first.set("all", 1); second.set("all", 1); await firstChannel.clear(); expect(second.size).toBe(0); firstChannel.close(); firstChannel.close(); await expect(firstChannel.invalidateTag("products")).rejects.toThrow( "WRN-CACHE-INVALIDATION-CLOSED", ); secondChannel.close(); }); test("response middleware does not call next twice for stale entries", async () => { let now = 0; const cache = new TagCache({ ttlMs: 1, staleWhileRevalidateMs: 100, clock: () => now }); const middleware = responseCache({ cache, ttlMs: 1, staleWhileRevalidateMs: 100 }); let calls = 0; const ctx = { req: new Request("https://example.com/a"), url: new URL("https://example.com/a"), } as any; await middleware(ctx, async () => { calls++; return new Response("one"); }); now = 2; const stale = await middleware(ctx, async () => { calls++; return new Response("two"); }); expect(await stale.text()).toBe("one"); expect(calls).toBe(1); }); });