feat(catalog): persist idempotent bulk operations and retryable deliveries

This commit is contained in:
2026-09-13 18:05:03 +02:00
parent 76e46d295c
commit 13665e0c3c
53 changed files with 1316 additions and 76 deletions
@@ -0,0 +1,51 @@
import { expect, it, vi } from "vitest";
import { dispatchEffects } from "./dispatcher";
it("settles only after successful delivery and preserves the claim token", async () => {
const effect = {
id: "a",
token: "lease1",
topic: "catalog.refresh" as const,
attempts: 1,
};
const repo = {
claim: vi.fn().mockResolvedValueOnce(effect).mockResolvedValue(null),
complete: vi.fn(),
fail: vi.fn(),
};
const deliver = vi.fn();
await dispatchEffects(repo, deliver);
expect(deliver).toHaveBeenCalledWith(effect);
expect(repo.complete).toHaveBeenCalledWith(effect);
expect(repo.fail).not.toHaveBeenCalled();
});
it("persists failures and continues to other pending work", async () => {
const effect = {
id: "a",
token: "lease1",
topic: "catalog.refresh" as const,
attempts: 1,
};
const repo = {
claim: vi.fn().mockResolvedValueOnce(effect).mockResolvedValue(null),
complete: vi.fn(),
fail: vi.fn(),
};
await dispatchEffects(repo, vi.fn().mockRejectedValue(Error("private")));
expect(repo.fail).toHaveBeenCalledWith(effect);
expect(repo.complete).not.toHaveBeenCalled();
});
it("bounds work per tick instead of draining indefinitely", async () => {
const repo = {
claim: vi.fn().mockResolvedValue({
id: "a",
token: "t",
topic: "catalog.refresh",
attempts: 1,
}),
complete: vi.fn(),
fail: vi.fn(),
};
await dispatchEffects(repo, async () => {});
expect(repo.claim).toHaveBeenCalledTimes(20);
});