Files
flynn/src/channels/registry.ts
T
William Valentin 6090508bad style: auto-fix ESLint issues (curly braces and formatting)
- Add curly braces to all if/else/for/while statements
- Fix indentation and trailing spaces
- Auto-fixed 372 linting errors using eslint --fix
- Remaining issues are warnings only (non-null assertions, explicit any types)
2026-02-11 10:30:24 -08:00

117 lines
3.5 KiB
TypeScript

/**
* Channel registry — manages adapter lifecycle and message routing.
*
* The ChannelRegistry holds all registered channel adapters and routes
* inbound messages through a single MessageHandler. Each adapter's
* onMessage callback is wired at registration time so that messages
* flow through handleInbound → messageHandler → reply.
*/
import type {
ChannelAdapter,
InboundMessage,
MessageHandler,
OutboundMessage,
} from './types.js';
export class ChannelRegistry {
private adapters: Map<string, ChannelAdapter> = new Map();
private messageHandler?: MessageHandler;
/** Register an adapter. Throws if name already registered. */
register(adapter: ChannelAdapter): void {
if (this.adapters.has(adapter.name)) {
throw new Error(`Channel adapter '${adapter.name}' is already registered`);
}
// Wire the adapter's onMessage to route through our messageHandler
adapter.onMessage((msg) => this.handleInbound(msg));
this.adapters.set(adapter.name, adapter);
}
/** Unregister an adapter by name. Calls disconnect() if connected. */
async unregister(name: string): Promise<void> {
const adapter = this.adapters.get(name);
if (!adapter) {return;}
if (adapter.status === 'connected' || adapter.status === 'connecting') {
await adapter.disconnect();
}
this.adapters.delete(name);
}
/** Get an adapter by name. */
get(name: string): ChannelAdapter | undefined {
return this.adapters.get(name);
}
/** List all registered adapters. */
list(): ChannelAdapter[] {
return Array.from(this.adapters.values());
}
/** Set the message handler that all adapters route inbound messages to. */
setMessageHandler(handler: MessageHandler): void {
this.messageHandler = handler;
}
/** Start all registered adapters. Logs errors per adapter, doesn't throw. */
async startAll(): Promise<void> {
const adapters = Array.from(this.adapters.values());
const results = await Promise.allSettled(
adapters.map((a) => a.connect()),
);
for (const [i, result] of results.entries()) {
if (result.status === 'rejected') {
console.error(
`Failed to start channel '${adapters[i].name}':`,
result.reason,
);
}
}
}
/** Stop all registered adapters. */
async stopAll(): Promise<void> {
const adapters = Array.from(this.adapters.values());
const results = await Promise.allSettled(
adapters.map((a) => a.disconnect()),
);
for (const [i, result] of results.entries()) {
if (result.status === 'rejected') {
console.error(
`Failed to stop channel '${adapters[i].name}':`,
result.reason,
);
}
}
}
/** Internal: route an inbound message to the message handler. */
private handleInbound(msg: InboundMessage): void {
if (!this.messageHandler) {
console.warn(`No message handler set, dropping message from '${msg.channel}'`);
return;
}
const adapter = this.adapters.get(msg.channel);
if (!adapter) {
console.warn(`Unknown channel '${msg.channel}' in inbound message`);
return;
}
// Create a reply function bound to this message's channel and sender
const reply = async (response: OutboundMessage): Promise<void> => {
await adapter.send(msg.senderId, response);
};
// Fire and forget — errors are logged, not propagated
this.messageHandler(msg, reply).catch((err: unknown) => {
console.error(`Error handling message from '${msg.channel}':`, err);
});
}
}