2026-02-01 14:34:07 +00:00
|
|
|
import * as plugins from '../tsmdb.plugins.js';
|
2026-01-31 11:33:11 +00:00
|
|
|
import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
|
|
|
|
|
import type { IParsedCommand } from './WireProtocol.js';
|
2026-02-01 14:34:07 +00:00
|
|
|
import type { TsmdbServer } from './TsmdbServer.js';
|
2026-01-31 11:33:11 +00:00
|
|
|
|
|
|
|
|
// 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;
|
2026-02-01 14:34:07 +00:00
|
|
|
server: TsmdbServer;
|
2026-01-31 11:33:11 +00:00
|
|
|
database: string;
|
|
|
|
|
command: plugins.bson.Document;
|
|
|
|
|
documentSequences?: Map<string, plugins.bson.Document[]>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Command handler interface
|
|
|
|
|
*/
|
|
|
|
|
export interface ICommandHandler {
|
|
|
|
|
handle(context: IHandlerContext): Promise<plugins.bson.Document>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* CommandRouter - Routes incoming commands to appropriate handlers
|
|
|
|
|
*/
|
|
|
|
|
export class CommandRouter {
|
|
|
|
|
private storage: IStorageAdapter;
|
2026-02-01 14:34:07 +00:00
|
|
|
private server: TsmdbServer;
|
2026-01-31 11:33:11 +00:00
|
|
|
private handlers: Map<string, ICommandHandler> = new Map();
|
|
|
|
|
|
|
|
|
|
// Cursor state for getMore operations
|
|
|
|
|
private cursors: Map<bigint, ICursorState> = new Map();
|
|
|
|
|
private cursorIdCounter: bigint = BigInt(1);
|
|
|
|
|
|
2026-02-01 14:34:07 +00:00
|
|
|
constructor(storage: IStorageAdapter, server: TsmdbServer) {
|
2026-01-31 11:33:11 +00:00
|
|
|
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<plugins.bson.Document> {
|
|
|
|
|
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;
|
|
|
|
|
}
|