Files
WRNexusJS/packages/cache/test/cache.test.ts
Clintchiz 586a6db8ff
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
release: WRNexusJS 0.8.0
2026-08-02 23:18:51 +05:30

151 lines
5.4 KiB
TypeScript

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<number>({ 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<number>();
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<void>((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<number>();
let release!: (value: number) => void;
const loading = cache.getOrLoad(
"x",
() => new Promise<number>((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<string, Set<(message: unknown) => void | Promise<void>>>();
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<void>) {
const handlers = subscriptions.get(topic) ?? new Set();
handlers.add(handler);
subscriptions.set(topic, handlers);
return () => handlers.delete(handler);
},
};
const first = new TagCache<number>();
const second = new TagCache<number>();
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<any>({ 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);
});
});