import type { StateUpdatedNotification } from "../domain/events"; import type { FanoutPort } from "../ports"; interface Sink { push(value: T): void; close(): void; iterable: AsyncIterable; } function createSink(): Sink { const queue: T[] = []; const waiters: Array<(value: IteratorResult) => void> = []; let closed = false; const push = (value: T) => { if (closed) return; const waiter = waiters.shift(); if (waiter) { waiter({ value, done: false }); return; } queue.push(value); }; const close = () => { closed = true; while (waiters.length > 0) { const waiter = waiters.shift(); waiter?.({ value: undefined as never, done: true }); } }; const iterable: AsyncIterable = { [Symbol.asyncIterator]() { return { async next() { if (queue.length > 0) { return { value: queue.shift() as T, done: false }; } if (closed) { return { value: undefined as never, done: true }; } return new Promise>((resolve) => { waiters.push(resolve); }); }, }; }, }; return { push, close, iterable }; } export class EffectFanout implements FanoutPort { private readonly sinks = new Set>(); async publish(notification: StateUpdatedNotification): Promise { for (const sink of this.sinks) { sink.push(notification); } } async subscribe(): Promise> { const sink = createSink(); this.sinks.add(sink); return sink.iterable; } }