import * as plugins from '../tsmdb.plugins.js'; import type { IStorageAdapter } from '../storage/IStorageAdapter.js'; import type { IParsedCommand } from './WireProtocol.js'; import type { TsmdbServer } from './TsmdbServer.js'; // Import handlers import { HelloHandler } from './handlers/HelloHandler.js'; import { InsertHandler } from './handlers/InsertHandler.js'; import { FindHandler } from './handlers/FindHandler.js'; import { UpdateHandler } from './handlers/UpdateHandler.js'; import { DeleteHandler } from './handlers/DeleteHandler.js'; import { AggregateHandler } from './handlers/AggregateHandler.js'; import { IndexHandler } from './handlers/IndexHandler.js'; import { AdminHandler } from './handlers/AdminHandler.js'; /** * Handler context passed to command handlers */ export interface IHandlerContext { storage: IStorageAdapter; server: TsmdbServer; database: string; command: plugins.bson.Document; documentSequences?: Map; } /** * Command handler interface */ export interface ICommandHandler { handle(context: IHandlerContext): Promise; } /** * CommandRouter - Routes incoming commands to appropriate handlers */ export class CommandRouter { private storage: IStorageAdapter; private server: TsmdbServer; private handlers: Map = new Map(); // Cursor state for getMore operations private cursors: Map = new Map(); private cursorIdCounter: bigint = BigInt(1); constructor(storage: IStorageAdapter, server: TsmdbServer) { this.storage = storage; this.server = server; this.registerHandlers(); } /** * Register all command handlers */ private registerHandlers(): void { // Create handler instances with shared state const helloHandler = new HelloHandler(); const findHandler = new FindHandler(this.cursors, () => this.cursorIdCounter++); const insertHandler = new InsertHandler(); const updateHandler = new UpdateHandler(); const deleteHandler = new DeleteHandler(); const aggregateHandler = new AggregateHandler(this.cursors, () => this.cursorIdCounter++); const indexHandler = new IndexHandler(); const adminHandler = new AdminHandler(); // Handshake commands this.handlers.set('hello', helloHandler); this.handlers.set('ismaster', helloHandler); this.handlers.set('isMaster', helloHandler); // CRUD commands this.handlers.set('find', findHandler); this.handlers.set('insert', insertHandler); this.handlers.set('update', updateHandler); this.handlers.set('delete', deleteHandler); this.handlers.set('findAndModify', updateHandler); this.handlers.set('getMore', findHandler); this.handlers.set('killCursors', findHandler); // Aggregation this.handlers.set('aggregate', aggregateHandler); this.handlers.set('count', findHandler); this.handlers.set('distinct', findHandler); // Index operations this.handlers.set('createIndexes', indexHandler); this.handlers.set('dropIndexes', indexHandler); this.handlers.set('listIndexes', indexHandler); // Admin/Database operations this.handlers.set('ping', adminHandler); this.handlers.set('listDatabases', adminHandler); this.handlers.set('listCollections', adminHandler); this.handlers.set('drop', adminHandler); this.handlers.set('dropDatabase', adminHandler); this.handlers.set('create', adminHandler); this.handlers.set('serverStatus', adminHandler); this.handlers.set('buildInfo', adminHandler); this.handlers.set('whatsmyuri', adminHandler); this.handlers.set('getLog', adminHandler); this.handlers.set('hostInfo', adminHandler); this.handlers.set('replSetGetStatus', adminHandler); this.handlers.set('isMaster', helloHandler); this.handlers.set('saslStart', adminHandler); this.handlers.set('saslContinue', adminHandler); this.handlers.set('endSessions', adminHandler); this.handlers.set('abortTransaction', adminHandler); this.handlers.set('commitTransaction', adminHandler); this.handlers.set('collStats', adminHandler); this.handlers.set('dbStats', adminHandler); this.handlers.set('connectionStatus', adminHandler); this.handlers.set('currentOp', adminHandler); this.handlers.set('collMod', adminHandler); this.handlers.set('renameCollection', adminHandler); } /** * Route a command to its handler */ async route(parsedCommand: IParsedCommand): Promise { const { commandName, command, database, documentSequences } = parsedCommand; // Create handler context const context: IHandlerContext = { storage: this.storage, server: this.server, database, command, documentSequences, }; // Find handler const handler = this.handlers.get(commandName); if (!handler) { // Unknown command return { ok: 0, errmsg: `no such command: '${commandName}'`, code: 59, codeName: 'CommandNotFound', }; } try { return await handler.handle(context); } catch (error: any) { // Handle known error types if (error.code) { return { ok: 0, errmsg: error.message, code: error.code, codeName: error.codeName || 'UnknownError', }; } // Generic error return { ok: 0, errmsg: error.message || 'Internal error', code: 1, codeName: 'InternalError', }; } } } /** * Cursor state for multi-batch queries */ export interface ICursorState { id: bigint; database: string; collection: string; documents: plugins.bson.Document[]; position: number; batchSize: number; createdAt: Date; }