UNPKG

opinionated-machine

Version:

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

155 lines (154 loc) 5.43 kB
import type { SSEMessage } from '../sseTypes.js'; /** * A typed room name resolver function. * * Created via `defineRoom()`, this ensures consistent room naming * across controllers and domain services. * * @template TParams - The parameters required to construct the room name * * @example * ```typescript * const dashboardRoom: RoomNameResolver<{ dashboardId: string }> = * ({ dashboardId }) => `dashboard:${dashboardId}` * ``` */ export type RoomNameResolver<TParams> = (params: TParams) => string; /** * Options for broadcasting to rooms. */ export type RoomBroadcastOptions = { /** * Only broadcast locally (skip adapter propagation to other nodes). * Useful for testing or node-specific announcements. * @default false */ local?: boolean; }; /** * Adapter interface for cross-node room communication. * * Adapters handle the propagation of room broadcasts across multiple server nodes. * The default InMemoryAdapter is a no-op for single-node deployments. * For multi-node deployments, use RedisAdapter or implement a custom adapter. */ export type SSERoomAdapter = { /** * Connect to the underlying messaging system (e.g., Redis). * Called when the room manager is initialized. */ connect(): Promise<void>; /** * Disconnect from the underlying messaging system. * Called during graceful shutdown. */ disconnect(): Promise<void>; /** * Subscribe to messages for a specific room. * Called when any connection on this node joins a room. * * @param room - The room to subscribe to */ subscribe(room: string): Promise<void>; /** * Unsubscribe from messages for a specific room. * Called when no connections on this node are in a room anymore. * * @param room - The room to unsubscribe from */ unsubscribe(room: string): Promise<void>; /** * Publish a message to all nodes subscribed to a room. * The message will be received by all nodes (including sender) via onMessage. * * @param room - The room to publish to * @param message - The SSE message to broadcast * @param metadata - Optional opaque metadata for cross-node subscription * filtering (e.g. event scope, project id). Adapters serialize this * alongside the message; it is not delivered to SSE clients. */ publish(room: string, message: SSEMessage, metadata?: Record<string, unknown>): Promise<void>; /** * Register a handler for messages received from other nodes. * The handler should forward the message to local connections in the room. * * @param handler - Callback invoked when a message is received */ onMessage(handler: SSERoomMessageHandler): void; }; /** * Handler for messages received from the adapter (other nodes). * * @param room - The room the message was published to * @param message - The SSE message * @param sourceNodeId - Id of the node that originated the publish; used to * suppress echoing the publisher's own messages back to it * @param metadata - Optional opaque metadata attached at publish time; * `undefined` for v1 (pre-metadata) messages from older nodes */ export type SSERoomMessageHandler = (room: string, message: SSEMessage, sourceNodeId: string, metadata?: Record<string, unknown>) => void | Promise<void>; /** * Pre-delivery filter called before sending to each connection. * Returns true to deliver, false to skip. * * Used by SSESubscriptionManager to inject resolver pipeline evaluation * into the broadcaster's delivery path. * * When no filter is set, all connections receive all messages (existing behavior). */ export type PreDeliveryFilter = (connectionId: string, message: SSEMessage, metadata?: Record<string, unknown>) => Promise<boolean> | boolean; /** * Result of a single broadcast call. * * Returned per-call (rather than tracked on the broadcaster) so that concurrent * broadcasts cannot interleave their counters. */ export type BroadcastResult = { /** Local connections the message was successfully sent to. */ delivered: number; /** Local connections skipped by the pre-delivery filter. */ filtered: number; }; /** * Configuration for the SSE Room Manager. */ export type SSERoomManagerConfig = { /** * Optional adapter for cross-node communication. * If not provided, uses InMemoryAdapter (single-node only). */ adapter?: SSERoomAdapter; /** * Unique identifier for this server node. * Used to prevent echo when receiving messages from the adapter. * @default crypto.randomUUID() */ nodeId?: string; }; /** * Room operations available on SSE sessions. * Accessed via `session.rooms.join()`, `session.rooms.leave()`. */ export type SSERoomOperations = { /** * Join one or more rooms. * * @param room - Room name or array of room names to join * * @example * ```typescript * // Join a single room based on route parameter * session.rooms.join(`dashboard:${request.params.dashboardId}`) * * // Join multiple rooms * session.rooms.join(['project:123', 'team:engineering']) * ``` */ join: (room: string | string[]) => void; /** * Leave one or more rooms. * * @param room - Room name or array of room names to leave */ leave: (room: string | string[]) => void; };