mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
refactor: introduce domain events for feed side effects (Track E — point 5)
Light "collect + dispatch" variant: the Feed aggregate records FeedEvents (FeedCreated, EmailIngested) on the mutations that have consequences, exposed via pullEvents(). A new application dispatcher (feed-events.applyFeedEvents) maps those events to their side effects — counters (awaited) plus WebSub pings and favicon fetches handed to a BackgroundScheduler. This removes the inline, scattered side effects from the ingest hot path (email-processor) and from createFeedRecord; the aggregate is now the source of truth for "what happened". Side effects with no aggregate mutation (rejected email, feed deletion bypassing the aggregate, bulk admin ops, the cron, unsubscribes-sent) stay imperative by design — there is no aggregate event for them to ride on. BackgroundScheduler type moved to infrastructure/worker.ts (shared). CLAUDE.md updated. 355 tests pass (+4 event tests); tsc --noEmit clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -173,6 +173,43 @@ describe("Feed.removeEmails", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Feed events", () => {
|
||||
it("records FeedCreated on create and drains it once", () => {
|
||||
const feed = Feed.create(FID, createInput());
|
||||
expect(feed.pullEvents()).toEqual([{ type: "FeedCreated" }]);
|
||||
// Draining clears: a second pull is empty.
|
||||
expect(feed.pullEvents()).toEqual([]);
|
||||
});
|
||||
|
||||
it("records EmailIngested (with icon domain) on ingest", () => {
|
||||
const feed = Feed.reconstitute(
|
||||
FID,
|
||||
{ title: "T", language: "en", created_at: 0 },
|
||||
{ emails: [] },
|
||||
);
|
||||
feed.ingest(entry({ key: "k" }), {
|
||||
maxBytes: 1_000_000,
|
||||
iconDomain: "example.com",
|
||||
});
|
||||
expect(feed.pullEvents()).toEqual([
|
||||
{ type: "EmailIngested", iconDomain: "example.com" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("emits no events for editDetails / edit / removeEmails", () => {
|
||||
const feed = Feed.reconstitute(
|
||||
FID,
|
||||
{ title: "T", language: "en", created_at: 0, expires_at: 9_999_999_999 },
|
||||
{ emails: [entry({ key: "k1" })] },
|
||||
fixedClock(1000),
|
||||
);
|
||||
feed.editDetails({ title: "X" });
|
||||
feed.edit({ description: "Y" }, { recomputeExpiry: false });
|
||||
feed.removeEmails(["k1"]);
|
||||
expect(feed.pullEvents()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("FeedRepository.load / save round-trip", () => {
|
||||
it("persists a created feed and reflects later mutations", async () => {
|
||||
const repo = new FeedRepository(mockEnv().EMAIL_STORAGE);
|
||||
|
||||
Reference in New Issue
Block a user