// Transport-agnostic dispatcher. Both @agent-desk/desktop (Electron IPC) // and @agent-desk/server (WebSocket) build their transport on top of this. // // The router holds a handler for every channel in the contract, plus an // EventEmitter that pushes server→client events. It does NOT know about // `ipcMain`, `BrowserWindow`, `ws`, or any specific transport. // // As stores are extracted from src/main/index.ts in subsequent commits, // CoreDeps and createRouter() get filled in. For now this is the skeleton // that proves the channels contract type-checks against a real handler map. import { EventEmitter } from 'events'; import type { RequestChannel, RequestArgs, RequestResult, CommandChannel, CommandArgs, PushChannel, PushArgs, } from './channels.js'; export type RequestHandler = ( ...args: RequestArgs ) => RequestResult | Promise>; export type RequestHandlers = { [K in RequestChannel]?: RequestHandler; }; export type CommandHandler = (...args: CommandArgs) => void; export type CommandHandlers = { [K in CommandChannel]?: CommandHandler; }; export interface Router { request(channel: K, ...args: RequestArgs): Promise>; command(channel: K, ...args: CommandArgs): void; /** Runtime-string dispatch for a request channel. Throws if no handler. */ dispatchRequest(channel: string, args: unknown[]): Promise; /** Runtime-string dispatch for a command channel. Throws if no handler. */ dispatchCommand(channel: string, args: unknown[]): void; on(channel: K, listener: (...args: PushArgs) => void): () => void; emit(channel: K, ...args: PushArgs): void; requestChannels: RequestChannel[]; commandChannels: CommandChannel[]; } export interface CreateRouterOptions { requestHandlers: RequestHandlers; commandHandlers: CommandHandlers; } export function createRouter(opts: CreateRouterOptions): Router { const bus = new EventEmitter(); bus.setMaxListeners(0); return { async request(channel, ...args) { const handler = opts.requestHandlers[channel] as RequestHandler | undefined; if (!handler) throw new Error(`No handler for request channel: ${channel}`); return handler(...args); }, command(channel, ...args) { const handler = opts.commandHandlers[channel] as CommandHandler | undefined; if (!handler) throw new Error(`No handler for command channel: ${channel}`); handler(...args); }, async dispatchRequest(channel, args) { const handlers = opts.requestHandlers as Record unknown) | undefined>; const handler = handlers[channel]; if (!handler) throw new Error(`No handler for request channel: ${channel}`); return handler(...args); }, dispatchCommand(channel, args) { const handlers = opts.commandHandlers as Record void) | undefined>; const handler = handlers[channel]; if (!handler) throw new Error(`No handler for command channel: ${channel}`); handler(...args); }, on(channel, listener) { bus.on(channel, listener as (...a: unknown[]) => void); return () => bus.off(channel, listener as (...a: unknown[]) => void); }, emit(channel, ...args) { bus.emit(channel, ...args); }, requestChannels: Object.keys(opts.requestHandlers) as RequestChannel[], commandChannels: Object.keys(opts.commandHandlers) as CommandChannel[], }; }