opinionated-machine
Version:
Very opinionated DI framework for fastify, built on top of awilix
128 lines (127 loc) • 5.31 kB
TypeScript
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;
}