UNPKG

opinionated-machine

Version:

Very opinionated DI framework for fastify, built on top of awilix

128 lines (127 loc) 5.31 kB
import type { z } from 'zod'; import type { SSEEventDefinition } from '../defineEvent.js'; import type { SSEMessage } from '../sseTypes.js'; import type { SSERoomManager } from './SSERoomManager.js'; import type { BroadcastResult, PreDeliveryFilter, RoomBroadcastOptions } from './types.js'; /** * Shared, non-generic room broadcaster that can be registered once in DI * and used by multiple controllers and domain services. * * Controllers register their `sendEvent` callback via `registerSender()`. * Domain services receive the broadcaster directly from the DI container. * * Requires `sseRoomManager` to be registered in the DI container. * * @example * ```typescript * // In your DI module's resolveDependencies() * sseRoomManager: asValue(new SSERoomManager()), * sseRoomBroadcaster: asSingletonClass(SSERoomBroadcaster), * * // In a domain service * class MetricsService { * private readonly sseRoomBroadcaster: SSERoomBroadcaster * constructor(deps: { sseRoomBroadcaster: SSERoomBroadcaster }) { * this.sseRoomBroadcaster = deps.sseRoomBroadcaster * } * } * ``` */ export declare class SSERoomBroadcaster { private readonly _roomManager; private readonly senders; private readonly dedupCache; private preDeliveryFilter?; constructor(deps: { sseRoomManager: SSERoomManager; }); /** * Set a pre-delivery filter that runs before sending to each connection. * Used by SSESubscriptionManager for resolver pipeline evaluation. * Only one filter can be active at a time — calling this method twice throws, * to prevent silently replacing an already-installed filter (which is almost * always a wiring bug, e.g. registering two SSESubscriptionManagers). */ setPreDeliveryFilter(filter: PreDeliveryFilter): void; /** * Public getter for the underlying room manager. * Used by the route builder for `session.rooms`. */ get roomManager(): SSERoomManager; /** * Register a sender callback (typically from a controller's sendEvent). * When broadcasting, each registered sender is tried — the first to return `true` wins. */ registerSender(sendFn: (connId: string, msg: SSEMessage) => Promise<boolean>): void; /** * Broadcast a type-safe event to all connections in one or more rooms. * * Domain services use this method with `defineEvent()`-based event definitions * for compile-time data validation. * * @param room - Room name or array of room names * @param event - Event definition created by `defineEvent()` * @param data - Event data (must match the schema from the event definition) * @param options - Broadcast options (local, id, retry) * @returns Number of local connections the message was successfully delivered to. * For separate `delivered`/`filtered` counts, call {@link broadcastMessage}. */ broadcastToRoom<T extends z.ZodType>(room: string | string[], event: SSEEventDefinition<string, T>, data: z.input<T>, options?: RoomBroadcastOptions & { id?: string; retry?: number; metadata?: Record<string, unknown>; }): Promise<number>; /** * Lower-level broadcast API — sends a raw SSEMessage to all connections in one or more rooms. * * The controller's typed `broadcastToRoom()` delegates here after constructing the message. * * @param room - Room name or array of room names * @param message - The SSE message to broadcast * @param options - Broadcast options (local) * @returns `delivered` is the number of local connections the message was sent to; * `filtered` is the number of local connections skipped by the pre-delivery * filter. Returning both per-call (rather than tracking on the broadcaster) * avoids races between concurrent broadcasts. */ broadcastMessage(room: string | string[], message: SSEMessage, options?: RoomBroadcastOptions & { metadata?: Record<string, unknown>; }): Promise<BroadcastResult>; /** * Get all connection IDs in a room. * * @param room - The room to query * @returns Array of connection IDs */ getConnectionsInRoom(room: string): string[]; /** * Get the number of connections in a room. * * @param room - The room to query * @returns Number of connections */ getConnectionCountInRoom(room: string): number; /** * Clean up dedup cache for a disconnected connection. * Called by the controller when a connection is unregistered. */ cleanupConnection(connectionId: string): void; /** * Try each registered sender until one succeeds (only the owning controller can send). */ private sendToConnection; /** * Handle broadcasts from other nodes (via adapter). * Deduplicates messages per-connection based on message ID. */ private handleRemoteBroadcast; /** * Check if a message has already been delivered to a connection (deduplication). * Returns true if the message is a duplicate and should be skipped. */ private isDuplicateMessage; /** * Collect unique connection IDs from multiple rooms. */ private collectRoomConnections; }