UNPKG

axiodb

Version:

The Pure JavaScript Alternative to SQLite. Embedded NoSQL database for Node.js with MongoDB-style queries, zero native dependencies, built-in InMemoryCache, and web GUI. Perfect for desktop apps, CLI tools, and embedded systems. No compilation, no platfor

228 lines 9.63 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MessageValidator = exports.MessageBuffer = exports.MessageFramer = void 0; const command_types_1 = require("../types/command.types"); const keys_1 = require("./keys"); /** * Message Framing - Handles encoding/decoding of TCP messages * Protocol: [4-byte length (uint32 BE)][JSON payload] */ class MessageFramer { /** * Encode message to binary format: [length][JSON] */ static encode(message) { const json = JSON.stringify(message); const jsonBuffer = Buffer.from(json, keys_1.ENCODING); if (jsonBuffer.length > keys_1.MAX_MESSAGE_SIZE) { throw new Error(keys_1.ErrorMessage.MESSAGE_TOO_LARGE); } const lengthBuffer = Buffer.allocUnsafe(keys_1.MESSAGE_LENGTH_BYTES); lengthBuffer.writeUInt32BE(jsonBuffer.length, 0); return Buffer.concat([lengthBuffer, jsonBuffer]); } /** * Decode buffer to message object * Note: Caller must ensure buffer has complete message */ static decode(buffer) { if (buffer.length < keys_1.MESSAGE_LENGTH_BYTES) { throw new Error(keys_1.ErrorMessage.INVALID_MESSAGE_FORMAT); } const length = buffer.readUInt32BE(0); if (length > keys_1.MAX_MESSAGE_SIZE) { throw new Error(keys_1.ErrorMessage.MESSAGE_TOO_LARGE); } if (buffer.length < keys_1.MESSAGE_LENGTH_BYTES + length) { throw new Error(keys_1.ErrorMessage.INVALID_MESSAGE_FORMAT); } const jsonBuffer = buffer.slice(keys_1.MESSAGE_LENGTH_BYTES, keys_1.MESSAGE_LENGTH_BYTES + length); const json = jsonBuffer.toString(keys_1.ENCODING); try { return JSON.parse(json); } catch (error) { throw new Error(keys_1.ErrorMessage.INVALID_MESSAGE_FORMAT); } } } exports.MessageFramer = MessageFramer; /** * Message Buffer - Accumulates incoming data and extracts complete messages */ class MessageBuffer { constructor() { this.buffer = Buffer.alloc(0); } /** * Add chunk to buffer and extract complete messages */ addChunk(chunk) { this.buffer = Buffer.concat([this.buffer, chunk]); const messages = []; while (this.buffer.length >= keys_1.MESSAGE_LENGTH_BYTES) { const length = this.buffer.readUInt32BE(0); // Detect if we're receiving HTTP data instead of binary protocol if (this.buffer.length >= 4) { const firstBytes = this.buffer.toString('utf8', 0, 4); if (firstBytes.match(/^(HTTP|GET |POST|PUT |DELE|HEAD|OPTI)/)) { this.buffer = Buffer.alloc(0); throw new Error('Invalid protocol: Received HTTP data on TCP port. Check connection string.'); } } if (length > keys_1.MAX_MESSAGE_SIZE) { this.buffer = Buffer.alloc(0); throw new Error(keys_1.ErrorMessage.MESSAGE_TOO_LARGE); } if (this.buffer.length < keys_1.MESSAGE_LENGTH_BYTES + length) { break; // Wait for more data } const messageBuffer = this.buffer.slice(0, keys_1.MESSAGE_LENGTH_BYTES + length); const message = MessageFramer.decode(messageBuffer); messages.push(message); this.buffer = this.buffer.slice(keys_1.MESSAGE_LENGTH_BYTES + length); } return messages; } /** * Clear buffer (useful for error recovery) */ clear() { this.buffer = Buffer.alloc(0); } /** * Get current buffer size */ get size() { return this.buffer.length; } } exports.MessageBuffer = MessageBuffer; /** * Message Validator - Validates request structure and parameters */ class MessageValidator { /** * Validate TCPRequest structure */ static validateRequest(request) { if (!request || typeof request !== 'object') { throw new Error(keys_1.ErrorMessage.INVALID_MESSAGE_FORMAT); } if (!request.id || typeof request.id !== 'string') { throw new Error(keys_1.ErrorMessage.INVALID_CORRELATION_ID); } if (!request.command || !Object.values(command_types_1.CommandType).includes(request.command)) { throw new Error(keys_1.ErrorMessage.UNKNOWN_COMMAND); } if (!request.params || typeof request.params !== 'object') { request.params = {}; } return request; } /** * Validate command-specific parameters */ static validateParams(command, params) { switch (command) { case command_types_1.CommandType.CREATE_DB: case command_types_1.CommandType.DELETE_DB: case command_types_1.CommandType.DB_EXISTS: if (!params.dbName) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName`); } break; case command_types_1.CommandType.CREATE_COLLECTION: case command_types_1.CommandType.DELETE_COLLECTION: case command_types_1.CommandType.COLLECTION_EXISTS: case command_types_1.CommandType.GET_COLLECTION_INFO: if (!params.dbName || !params.collectionName) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName`); } break; case command_types_1.CommandType.INSERT_DOCUMENT: if (!params.dbName || !params.collectionName || !params.data) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName, data`); } break; case command_types_1.CommandType.INSERT_MANY_DOCUMENTS: if (!params.dbName || !params.collectionName || !params.documents) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName, documents`); } break; case command_types_1.CommandType.QUERY_DOCUMENTS: if (!params.dbName || !params.collectionName) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName`); } break; case command_types_1.CommandType.QUERY_BY_ID: case command_types_1.CommandType.UPDATE_DOCUMENT_BY_ID: case command_types_1.CommandType.DELETE_DOCUMENT_BY_ID: if (!params.dbName || !params.collectionName || !params.id) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName, id`); } break; case command_types_1.CommandType.UPDATE_DOCUMENTS_BY_QUERY: if (!params.dbName || !params.collectionName || !params.query || !params.updateData) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName, query, updateData`); } break; case command_types_1.CommandType.DELETE_DOCUMENTS_BY_QUERY: if (!params.dbName || !params.collectionName || !params.query) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName, query`); } break; case command_types_1.CommandType.AGGREGATE: if (!params.dbName || !params.collectionName || !params.pipeline) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName, pipeline`); } break; case command_types_1.CommandType.TOTAL_DOCUMENTS: if (!params.dbName || !params.collectionName) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName`); } break; case command_types_1.CommandType.CREATE_INDEX: if (!params.dbName || !params.collectionName || !params.fieldNames) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName, fieldNames`); } break; case command_types_1.CommandType.DROP_INDEX: if (!params.dbName || !params.collectionName || !params.indexName) { throw new Error(`${keys_1.ErrorMessage.MISSING_REQUIRED_PARAMS}: dbName, collectionName, indexName`); } break; case command_types_1.CommandType.PING: case command_types_1.CommandType.DISCONNECT: case command_types_1.CommandType.GET_INSTANCE_INFO: // No required params break; default: throw new Error(keys_1.ErrorMessage.UNKNOWN_COMMAND); } } /** * Create error response */ static createErrorResponse(requestId, statusCode, message, error) { return { id: requestId, statusCode, message, error, }; } /** * Create success response */ static createSuccessResponse(requestId, message, data) { return { id: requestId, statusCode: keys_1.StatusCode.OK, message, data, }; } } exports.MessageValidator = MessageValidator; //# sourceMappingURL=protocol.js.map