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:
Julien Herr
2026-05-24 13:12:42 +02:00
parent 46af982c40
commit b3d42f6c50
9 changed files with 146 additions and 27 deletions
+7
View File
@@ -1,5 +1,12 @@
import { Context } from "hono";
/**
* Schedules a fire-and-forget background task. The HTTP edge adapts this over
* `ctx.waitUntil`; the application/domain layers depend on this plain function
* type instead of Hono's `Context`.
*/
export type BackgroundScheduler = (task: Promise<unknown>) => void;
/** Calls ctx.waitUntil() without throwing when the ExecutionContext is absent (e.g. Node tests). */
export function waitUntilSafe(c: Context, promise: Promise<unknown>): void {
try {