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
JavaScript
;
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