@push.rocks/smartmongo 2.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/index.js +3 -3
- package/dist_ts/tsmdb/engine/AggregationEngine.js +189 -0
- package/dist_ts/{congodb → tsmdb}/engine/IndexEngine.d.ts +23 -3
- package/dist_ts/tsmdb/engine/IndexEngine.js +678 -0
- package/dist_ts/tsmdb/engine/QueryEngine.js +271 -0
- package/dist_ts/tsmdb/engine/QueryPlanner.d.ts +64 -0
- package/dist_ts/tsmdb/engine/QueryPlanner.js +308 -0
- package/dist_ts/tsmdb/engine/SessionEngine.d.ts +117 -0
- package/dist_ts/tsmdb/engine/SessionEngine.js +232 -0
- package/dist_ts/{congodb → tsmdb}/engine/TransactionEngine.d.ts +1 -1
- package/dist_ts/tsmdb/engine/TransactionEngine.js +287 -0
- package/dist_ts/tsmdb/engine/UpdateEngine.js +461 -0
- package/dist_ts/{congodb/errors/CongoErrors.d.ts → tsmdb/errors/TsmdbErrors.d.ts} +16 -16
- package/dist_ts/tsmdb/errors/TsmdbErrors.js +155 -0
- package/dist_ts/{congodb → tsmdb}/index.d.ts +11 -4
- package/dist_ts/tsmdb/index.js +31 -0
- package/dist_ts/tsmdb/server/CommandRouter.d.ts +87 -0
- package/dist_ts/tsmdb/server/CommandRouter.js +222 -0
- package/dist_ts/{congodb/server/CongoServer.d.ts → tsmdb/server/TsmdbServer.d.ts} +6 -6
- package/dist_ts/tsmdb/server/TsmdbServer.js +229 -0
- package/dist_ts/{congodb → tsmdb}/server/WireProtocol.d.ts +1 -1
- package/dist_ts/tsmdb/server/WireProtocol.js +298 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/AdminHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/AdminHandler.js +668 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/AggregateHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/AggregateHandler.js +277 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/DeleteHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/DeleteHandler.js +95 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/FindHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/FindHandler.js +291 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/HelloHandler.d.ts +1 -1
- package/dist_ts/{congodb → tsmdb}/server/handlers/HelloHandler.js +2 -2
- package/dist_ts/{congodb → tsmdb}/server/handlers/IndexHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/IndexHandler.js +183 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/InsertHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/InsertHandler.js +79 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/UpdateHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/UpdateHandler.js +296 -0
- package/dist_ts/tsmdb/server/handlers/index.js +10 -0
- package/dist_ts/{congodb → tsmdb}/server/index.d.ts +2 -2
- package/dist_ts/tsmdb/server/index.js +7 -0
- package/dist_ts/{congodb → tsmdb}/storage/FileStorageAdapter.d.ts +27 -3
- package/dist_ts/tsmdb/storage/FileStorageAdapter.js +465 -0
- package/dist_ts/{congodb → tsmdb}/storage/IStorageAdapter.d.ts +7 -2
- package/dist_ts/{congodb → tsmdb}/storage/IStorageAdapter.js +1 -1
- package/dist_ts/{congodb → tsmdb}/storage/MemoryStorageAdapter.d.ts +3 -2
- package/dist_ts/tsmdb/storage/MemoryStorageAdapter.js +378 -0
- package/dist_ts/{congodb → tsmdb}/storage/OpLog.d.ts +1 -1
- package/dist_ts/tsmdb/storage/OpLog.js +221 -0
- package/dist_ts/tsmdb/storage/WAL.d.ts +117 -0
- package/dist_ts/tsmdb/storage/WAL.js +286 -0
- package/dist_ts/tsmdb/tsmdb.plugins.js +14 -0
- package/dist_ts/{congodb → tsmdb}/types/interfaces.d.ts +3 -3
- package/dist_ts/{congodb → tsmdb}/types/interfaces.js +1 -1
- package/dist_ts/tsmdb/utils/checksum.d.ts +30 -0
- package/dist_ts/tsmdb/utils/checksum.js +77 -0
- package/dist_ts/tsmdb/utils/index.d.ts +1 -0
- package/dist_ts/tsmdb/utils/index.js +2 -0
- package/package.json +1 -1
- package/readme.hints.md +7 -12
- package/readme.md +25 -25
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +2 -2
- package/ts/{congodb → tsmdb}/engine/AggregationEngine.ts +1 -1
- package/ts/tsmdb/engine/IndexEngine.ts +798 -0
- package/ts/{congodb → tsmdb}/engine/QueryEngine.ts +1 -1
- package/ts/tsmdb/engine/QueryPlanner.ts +393 -0
- package/ts/tsmdb/engine/SessionEngine.ts +292 -0
- package/ts/{congodb → tsmdb}/engine/TransactionEngine.ts +12 -12
- package/ts/{congodb → tsmdb}/engine/UpdateEngine.ts +1 -1
- package/ts/{congodb/errors/CongoErrors.ts → tsmdb/errors/TsmdbErrors.ts} +34 -34
- package/ts/{congodb → tsmdb}/index.ts +16 -7
- package/ts/{congodb → tsmdb}/server/CommandRouter.ts +114 -5
- package/ts/{congodb/server/CongoServer.ts → tsmdb/server/TsmdbServer.ts} +11 -8
- package/ts/{congodb → tsmdb}/server/WireProtocol.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/AdminHandler.ts +116 -11
- package/ts/{congodb → tsmdb}/server/handlers/AggregateHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/DeleteHandler.ts +18 -3
- package/ts/{congodb → tsmdb}/server/handlers/FindHandler.ts +43 -14
- package/ts/{congodb → tsmdb}/server/handlers/HelloHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/IndexHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/InsertHandler.ts +7 -1
- package/ts/{congodb → tsmdb}/server/handlers/UpdateHandler.ts +34 -5
- package/ts/{congodb → tsmdb}/server/index.ts +2 -2
- package/ts/{congodb → tsmdb}/storage/FileStorageAdapter.ts +90 -7
- package/ts/{congodb → tsmdb}/storage/IStorageAdapter.ts +8 -2
- package/ts/{congodb → tsmdb}/storage/MemoryStorageAdapter.ts +14 -2
- package/ts/{congodb → tsmdb}/storage/OpLog.ts +1 -1
- package/ts/tsmdb/storage/WAL.ts +375 -0
- package/ts/{congodb → tsmdb}/types/interfaces.ts +3 -3
- package/ts/tsmdb/utils/checksum.ts +88 -0
- package/ts/tsmdb/utils/index.ts +1 -0
- package/dist_ts/congodb/congodb.plugins.js +0 -14
- package/dist_ts/congodb/engine/AggregationEngine.js +0 -189
- package/dist_ts/congodb/engine/IndexEngine.js +0 -376
- package/dist_ts/congodb/engine/QueryEngine.js +0 -271
- package/dist_ts/congodb/engine/TransactionEngine.js +0 -287
- package/dist_ts/congodb/engine/UpdateEngine.js +0 -461
- package/dist_ts/congodb/errors/CongoErrors.js +0 -155
- package/dist_ts/congodb/index.js +0 -26
- package/dist_ts/congodb/server/CommandRouter.d.ts +0 -51
- package/dist_ts/congodb/server/CommandRouter.js +0 -132
- package/dist_ts/congodb/server/CongoServer.js +0 -227
- package/dist_ts/congodb/server/WireProtocol.js +0 -298
- package/dist_ts/congodb/server/handlers/AdminHandler.js +0 -568
- package/dist_ts/congodb/server/handlers/AggregateHandler.js +0 -277
- package/dist_ts/congodb/server/handlers/DeleteHandler.js +0 -83
- package/dist_ts/congodb/server/handlers/FindHandler.js +0 -261
- package/dist_ts/congodb/server/handlers/IndexHandler.js +0 -183
- package/dist_ts/congodb/server/handlers/InsertHandler.js +0 -76
- package/dist_ts/congodb/server/handlers/UpdateHandler.js +0 -270
- package/dist_ts/congodb/server/handlers/index.js +0 -10
- package/dist_ts/congodb/server/index.js +0 -7
- package/dist_ts/congodb/storage/FileStorageAdapter.js +0 -396
- package/dist_ts/congodb/storage/MemoryStorageAdapter.js +0 -367
- package/dist_ts/congodb/storage/OpLog.js +0 -221
- package/ts/congodb/engine/IndexEngine.ts +0 -479
- /package/dist_ts/{congodb → tsmdb}/engine/AggregationEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/engine/QueryEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/engine/UpdateEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/server/handlers/index.d.ts +0 -0
- /package/dist_ts/{congodb/congodb.plugins.d.ts → tsmdb/tsmdb.plugins.d.ts} +0 -0
- /package/ts/{congodb → tsmdb}/server/handlers/index.ts +0 -0
- /package/ts/{congodb/congodb.plugins.ts → tsmdb/tsmdb.plugins.ts} +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
import * as plugins from '../tsmdb.plugins.js';
|
|
3
|
+
import { WireProtocol, OP_QUERY } from './WireProtocol.js';
|
|
4
|
+
import { CommandRouter } from './CommandRouter.js';
|
|
5
|
+
import { MemoryStorageAdapter } from '../storage/MemoryStorageAdapter.js';
|
|
6
|
+
import { FileStorageAdapter } from '../storage/FileStorageAdapter.js';
|
|
7
|
+
/**
|
|
8
|
+
* TsmdbServer - MongoDB Wire Protocol compatible server
|
|
9
|
+
*
|
|
10
|
+
* This server implements the MongoDB wire protocol (OP_MSG) to allow
|
|
11
|
+
* official MongoDB drivers to connect and perform operations.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { TsmdbServer } from '@push.rocks/smartmongo/tsmdb';
|
|
16
|
+
* import { MongoClient } from 'mongodb';
|
|
17
|
+
*
|
|
18
|
+
* const server = new TsmdbServer({ port: 27017 });
|
|
19
|
+
* await server.start();
|
|
20
|
+
*
|
|
21
|
+
* const client = new MongoClient('mongodb://127.0.0.1:27017');
|
|
22
|
+
* await client.connect();
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class TsmdbServer {
|
|
26
|
+
options;
|
|
27
|
+
server = null;
|
|
28
|
+
storage;
|
|
29
|
+
commandRouter;
|
|
30
|
+
connections = new Map();
|
|
31
|
+
connectionIdCounter = 0;
|
|
32
|
+
isRunning = false;
|
|
33
|
+
startTime = new Date();
|
|
34
|
+
constructor(options = {}) {
|
|
35
|
+
this.options = {
|
|
36
|
+
port: options.port ?? 27017,
|
|
37
|
+
host: options.host ?? '127.0.0.1',
|
|
38
|
+
storage: options.storage ?? 'memory',
|
|
39
|
+
storagePath: options.storagePath ?? './data',
|
|
40
|
+
persistPath: options.persistPath ?? '',
|
|
41
|
+
persistIntervalMs: options.persistIntervalMs ?? 60000,
|
|
42
|
+
};
|
|
43
|
+
// Create storage adapter
|
|
44
|
+
if (this.options.storage === 'file') {
|
|
45
|
+
this.storage = new FileStorageAdapter(this.options.storagePath);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.storage = new MemoryStorageAdapter({
|
|
49
|
+
persistPath: this.options.persistPath || undefined,
|
|
50
|
+
persistIntervalMs: this.options.persistPath ? this.options.persistIntervalMs : undefined,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Create command router
|
|
54
|
+
this.commandRouter = new CommandRouter(this.storage, this);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the storage adapter (for testing/debugging)
|
|
58
|
+
*/
|
|
59
|
+
getStorage() {
|
|
60
|
+
return this.storage;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get server uptime in seconds
|
|
64
|
+
*/
|
|
65
|
+
getUptime() {
|
|
66
|
+
return Math.floor((Date.now() - this.startTime.getTime()) / 1000);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get current connection count
|
|
70
|
+
*/
|
|
71
|
+
getConnectionCount() {
|
|
72
|
+
return this.connections.size;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Start the server
|
|
76
|
+
*/
|
|
77
|
+
async start() {
|
|
78
|
+
if (this.isRunning) {
|
|
79
|
+
throw new Error('Server is already running');
|
|
80
|
+
}
|
|
81
|
+
// Initialize storage
|
|
82
|
+
await this.storage.initialize();
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
this.server = net.createServer((socket) => {
|
|
85
|
+
this.handleConnection(socket);
|
|
86
|
+
});
|
|
87
|
+
this.server.on('error', (err) => {
|
|
88
|
+
if (!this.isRunning) {
|
|
89
|
+
reject(err);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.error('Server error:', err);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
this.server.listen(this.options.port, this.options.host, () => {
|
|
96
|
+
this.isRunning = true;
|
|
97
|
+
this.startTime = new Date();
|
|
98
|
+
resolve();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Stop the server
|
|
104
|
+
*/
|
|
105
|
+
async stop() {
|
|
106
|
+
if (!this.isRunning || !this.server) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// Close all connections
|
|
110
|
+
for (const conn of this.connections.values()) {
|
|
111
|
+
conn.socket.destroy();
|
|
112
|
+
}
|
|
113
|
+
this.connections.clear();
|
|
114
|
+
// Close command router (cleans up session engine, cursors, etc.)
|
|
115
|
+
this.commandRouter.close();
|
|
116
|
+
// Close storage
|
|
117
|
+
await this.storage.close();
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
this.server.close(() => {
|
|
120
|
+
this.isRunning = false;
|
|
121
|
+
this.server = null;
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Handle a new client connection
|
|
128
|
+
*/
|
|
129
|
+
handleConnection(socket) {
|
|
130
|
+
const connectionId = ++this.connectionIdCounter;
|
|
131
|
+
const state = {
|
|
132
|
+
id: connectionId,
|
|
133
|
+
socket,
|
|
134
|
+
buffer: Buffer.alloc(0),
|
|
135
|
+
authenticated: true, // No auth required for now
|
|
136
|
+
database: 'test',
|
|
137
|
+
};
|
|
138
|
+
this.connections.set(connectionId, state);
|
|
139
|
+
socket.on('data', (data) => {
|
|
140
|
+
this.handleData(state, Buffer.isBuffer(data) ? data : Buffer.from(data));
|
|
141
|
+
});
|
|
142
|
+
socket.on('close', () => {
|
|
143
|
+
this.connections.delete(connectionId);
|
|
144
|
+
});
|
|
145
|
+
socket.on('error', (err) => {
|
|
146
|
+
// Connection errors are expected when clients disconnect
|
|
147
|
+
this.connections.delete(connectionId);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Handle incoming data from a client
|
|
152
|
+
*/
|
|
153
|
+
handleData(state, data) {
|
|
154
|
+
// Append new data to buffer
|
|
155
|
+
state.buffer = Buffer.concat([state.buffer, data]);
|
|
156
|
+
// Process messages from buffer
|
|
157
|
+
this.processMessages(state);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Process complete messages from the buffer
|
|
161
|
+
*/
|
|
162
|
+
async processMessages(state) {
|
|
163
|
+
while (state.buffer.length >= 16) {
|
|
164
|
+
try {
|
|
165
|
+
const result = WireProtocol.parseMessage(state.buffer);
|
|
166
|
+
if (!result) {
|
|
167
|
+
// Not enough data for a complete message
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
const { command, bytesConsumed } = result;
|
|
171
|
+
// Remove processed bytes from buffer
|
|
172
|
+
state.buffer = state.buffer.subarray(bytesConsumed);
|
|
173
|
+
// Process the command
|
|
174
|
+
const response = await this.commandRouter.route(command);
|
|
175
|
+
// Encode and send response
|
|
176
|
+
let responseBuffer;
|
|
177
|
+
if (command.opCode === OP_QUERY) {
|
|
178
|
+
// Legacy OP_QUERY gets OP_REPLY response
|
|
179
|
+
responseBuffer = WireProtocol.encodeOpReplyResponse(command.requestID, [response]);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// OP_MSG gets OP_MSG response
|
|
183
|
+
responseBuffer = WireProtocol.encodeOpMsgResponse(command.requestID, response);
|
|
184
|
+
}
|
|
185
|
+
if (!state.socket.destroyed) {
|
|
186
|
+
state.socket.write(responseBuffer);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
// Send error response
|
|
191
|
+
const errorResponse = WireProtocol.encodeErrorResponse(0, // We don't have the requestID at this point
|
|
192
|
+
1, error.message || 'Internal error');
|
|
193
|
+
if (!state.socket.destroyed) {
|
|
194
|
+
state.socket.write(errorResponse);
|
|
195
|
+
}
|
|
196
|
+
// Clear buffer on parse errors to avoid infinite loops
|
|
197
|
+
if (error.message?.includes('opCode') || error.message?.includes('section')) {
|
|
198
|
+
state.buffer = Buffer.alloc(0);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get the connection URI for this server
|
|
206
|
+
*/
|
|
207
|
+
getConnectionUri() {
|
|
208
|
+
return `mongodb://${this.options.host}:${this.options.port}`;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if the server is running
|
|
212
|
+
*/
|
|
213
|
+
get running() {
|
|
214
|
+
return this.isRunning;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get the port the server is listening on
|
|
218
|
+
*/
|
|
219
|
+
get port() {
|
|
220
|
+
return this.options.port;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get the host the server is bound to
|
|
224
|
+
*/
|
|
225
|
+
get host() {
|
|
226
|
+
return this.options.host;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVHNtZGJTZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy90c21kYi9zZXJ2ZXIvVHNtZGJTZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEdBQUcsTUFBTSxLQUFLLENBQUM7QUFDM0IsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUMvQyxPQUFPLEVBQUUsWUFBWSxFQUFFLFFBQVEsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQzNELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNuRCxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUMxRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQWdDdEU7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsTUFBTSxPQUFPLFdBQVc7SUFDZCxPQUFPLENBQWdDO0lBQ3ZDLE1BQU0sR0FBc0IsSUFBSSxDQUFDO0lBQ2pDLE9BQU8sQ0FBa0I7SUFDekIsYUFBYSxDQUFnQjtJQUM3QixXQUFXLEdBQWtDLElBQUksR0FBRyxFQUFFLENBQUM7SUFDdkQsbUJBQW1CLEdBQUcsQ0FBQyxDQUFDO0lBQ3hCLFNBQVMsR0FBRyxLQUFLLENBQUM7SUFDbEIsU0FBUyxHQUFTLElBQUksSUFBSSxFQUFFLENBQUM7SUFFckMsWUFBWSxVQUErQixFQUFFO1FBQzNDLElBQUksQ0FBQyxPQUFPLEdBQUc7WUFDYixJQUFJLEVBQUUsT0FBTyxDQUFDLElBQUksSUFBSSxLQUFLO1lBQzNCLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSSxJQUFJLFdBQVc7WUFDakMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPLElBQUksUUFBUTtZQUNwQyxXQUFXLEVBQUUsT0FBTyxDQUFDLFdBQVcsSUFBSSxRQUFRO1lBQzVDLFdBQVcsRUFBRSxPQUFPLENBQUMsV0FBVyxJQUFJLEVBQUU7WUFDdEMsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLGlCQUFpQixJQUFJLEtBQUs7U0FDdEQsQ0FBQztRQUVGLHlCQUF5QjtRQUN6QixJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQ3BDLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQ2xFLENBQUM7YUFBTSxDQUFDO1lBQ04sSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLG9CQUFvQixDQUFDO2dCQUN0QyxXQUFXLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLElBQUksU0FBUztnQkFDbEQsaUJBQWlCLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLFNBQVM7YUFDekYsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUVELHdCQUF3QjtRQUN4QixJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksYUFBYSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVEOztPQUVHO0lBQ0gsVUFBVTtRQUNSLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQztJQUN0QixDQUFDO0lBRUQ7O09BRUc7SUFDSCxTQUFTO1FBQ1AsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxFQUFFLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQztJQUNwRSxDQUFDO0lBRUQ7O09BRUc7SUFDSCxrQkFBa0I7UUFDaEIsT0FBTyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQztJQUMvQixDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsS0FBSztRQUNULElBQUksSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMsMkJBQTJCLENBQUMsQ0FBQztRQUMvQyxDQUFDO1FBRUQscUJBQXFCO1FBQ3JCLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUVoQyxPQUFPLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ3JDLElBQUksQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFO2dCQUN4QyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDaEMsQ0FBQyxDQUFDLENBQUM7WUFFSCxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRTtnQkFDOUIsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztvQkFDcEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUNkLENBQUM7cUJBQU0sQ0FBQztvQkFDTixPQUFPLENBQUMsS0FBSyxDQUFDLGVBQWUsRUFBRSxHQUFHLENBQUMsQ0FBQztnQkFDdEMsQ0FBQztZQUNILENBQUMsQ0FBQyxDQUFDO1lBRUgsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFO2dCQUM1RCxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQztnQkFDdEIsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO2dCQUM1QixPQUFPLEVBQUUsQ0FBQztZQUNaLENBQUMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsSUFBSTtRQUNSLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3BDLE9BQU87UUFDVCxDQUFDO1FBRUQsd0JBQXdCO1FBQ3hCLEtBQUssTUFBTSxJQUFJLElBQUksSUFBSSxDQUFDLFdBQVcsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDO1lBQzdDLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDeEIsQ0FBQztRQUNELElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFekIsaUVBQWlFO1FBQ2pFLElBQUksQ0FBQyxhQUFhLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFM0IsZ0JBQWdCO1FBQ2hCLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUUzQixPQUFPLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUU7WUFDN0IsSUFBSSxDQUFDLE1BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFO2dCQUN0QixJQUFJLENBQUMsU0FBUyxHQUFHLEtBQUssQ0FBQztnQkFDdkIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7Z0JBQ25CLE9BQU8sRUFBRSxDQUFDO1lBQ1osQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNLLGdCQUFnQixDQUFDLE1BQWtCO1FBQ3pDLE1BQU0sWUFBWSxHQUFHLEVBQUUsSUFBSSxDQUFDLG1CQUFtQixDQUFDO1FBRWhELE1BQU0sS0FBSyxHQUFxQjtZQUM5QixFQUFFLEVBQUUsWUFBWTtZQUNoQixNQUFNO1lBQ04sTUFBTSxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1lBQ3ZCLGFBQWEsRUFBRSxJQUFJLEVBQUUsMkJBQTJCO1lBQ2hELFFBQVEsRUFBRSxNQUFNO1NBQ2pCLENBQUM7UUFFRixJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxZQUFZLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFFMUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsRUFBRTtZQUN6QixJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUMzRSxDQUFDLENBQUMsQ0FBQztRQUVILE1BQU0sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRTtZQUN0QixJQUFJLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUN4QyxDQUFDLENBQUMsQ0FBQztRQUVILE1BQU0sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsR0FBRyxFQUFFLEVBQUU7WUFDekIseURBQXlEO1lBQ3pELElBQUksQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ3hDLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVEOztPQUVHO0lBQ0ssVUFBVSxDQUFDLEtBQXVCLEVBQUUsSUFBWTtRQUN0RCw0QkFBNEI7UUFDNUIsS0FBSyxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBRW5ELCtCQUErQjtRQUMvQixJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzlCLENBQUM7SUFFRDs7T0FFRztJQUNLLEtBQUssQ0FBQyxlQUFlLENBQUMsS0FBdUI7UUFDbkQsT0FBTyxLQUFLLENBQUMsTUFBTSxDQUFDLE1BQU0sSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUNqQyxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBRXZELElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztvQkFDWix5Q0FBeUM7b0JBQ3pDLE1BQU07Z0JBQ1IsQ0FBQztnQkFFRCxNQUFNLEVBQUUsT0FBTyxFQUFFLGFBQWEsRUFBRSxHQUFHLE1BQU0sQ0FBQztnQkFFMUMscUNBQXFDO2dCQUNyQyxLQUFLLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFDO2dCQUVwRCxzQkFBc0I7Z0JBQ3RCLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7Z0JBRXpELDJCQUEyQjtnQkFDM0IsSUFBSSxjQUFzQixDQUFDO2dCQUMzQixJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssUUFBUSxFQUFFLENBQUM7b0JBQ2hDLHlDQUF5QztvQkFDekMsY0FBYyxHQUFHLFlBQVksQ0FBQyxxQkFBcUIsQ0FDakQsT0FBTyxDQUFDLFNBQVMsRUFDakIsQ0FBQyxRQUFRLENBQUMsQ0FDWCxDQUFDO2dCQUNKLENBQUM7cUJBQU0sQ0FBQztvQkFDTiw4QkFBOEI7b0JBQzlCLGNBQWMsR0FBRyxZQUFZLENBQUMsbUJBQW1CLENBQy9DLE9BQU8sQ0FBQyxTQUFTLEVBQ2pCLFFBQVEsQ0FDVCxDQUFDO2dCQUNKLENBQUM7Z0JBRUQsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFLENBQUM7b0JBQzVCLEtBQUssQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxDQUFDO2dCQUNyQyxDQUFDO1lBQ0gsQ0FBQztZQUFDLE9BQU8sS0FBVSxFQUFFLENBQUM7Z0JBQ3BCLHNCQUFzQjtnQkFDdEIsTUFBTSxhQUFhLEdBQUcsWUFBWSxDQUFDLG1CQUFtQixDQUNwRCxDQUFDLEVBQUUsNENBQTRDO2dCQUMvQyxDQUFDLEVBQ0QsS0FBSyxDQUFDLE9BQU8sSUFBSSxnQkFBZ0IsQ0FDbEMsQ0FBQztnQkFFRixJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQztvQkFDNUIsS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUM7Z0JBQ3BDLENBQUM7Z0JBRUQsdURBQXVEO2dCQUN2RCxJQUFJLEtBQUssQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEtBQUssQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7b0JBQzVFLEtBQUssQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDakMsQ0FBQztnQkFDRCxNQUFNO1lBQ1IsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxnQkFBZ0I7UUFDZCxPQUFPLGFBQWEsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUMvRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJLE9BQU87UUFDVCxPQUFPLElBQUksQ0FBQyxTQUFTLENBQUM7SUFDeEIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsSUFBSSxJQUFJO1FBQ04sT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQztJQUMzQixDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJLElBQUk7UUFDTixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO0lBQzNCLENBQUM7Q0FDRiJ9
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import * as plugins from '../tsmdb.plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* MongoDB Wire Protocol Implementation
|
|
4
|
+
* Handles parsing and encoding of MongoDB wire protocol messages (OP_MSG primarily)
|
|
5
|
+
*
|
|
6
|
+
* Wire Protocol Message Format:
|
|
7
|
+
* - Header (16 bytes): messageLength (4), requestID (4), responseTo (4), opCode (4)
|
|
8
|
+
* - OP_MSG: flagBits (4), sections[], optional checksum (4)
|
|
9
|
+
*
|
|
10
|
+
* References:
|
|
11
|
+
* - https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/
|
|
12
|
+
*/
|
|
13
|
+
// OpCodes
|
|
14
|
+
export const OP_REPLY = 1; // Legacy reply
|
|
15
|
+
export const OP_UPDATE = 2001; // Legacy update
|
|
16
|
+
export const OP_INSERT = 2002; // Legacy insert
|
|
17
|
+
export const OP_QUERY = 2004; // Legacy query (still used for initial handshake)
|
|
18
|
+
export const OP_GET_MORE = 2005; // Legacy getMore
|
|
19
|
+
export const OP_DELETE = 2006; // Legacy delete
|
|
20
|
+
export const OP_KILL_CURSORS = 2007; // Legacy kill cursors
|
|
21
|
+
export const OP_COMPRESSED = 2012; // Compressed message
|
|
22
|
+
export const OP_MSG = 2013; // Modern protocol (MongoDB 3.6+)
|
|
23
|
+
// OP_MSG Section Types
|
|
24
|
+
export const SECTION_BODY = 0; // Single BSON document
|
|
25
|
+
export const SECTION_DOCUMENT_SEQUENCE = 1; // Document sequence for bulk operations
|
|
26
|
+
// OP_MSG Flag Bits
|
|
27
|
+
export const MSG_FLAG_CHECKSUM_PRESENT = 1 << 0;
|
|
28
|
+
export const MSG_FLAG_MORE_TO_COME = 1 << 1;
|
|
29
|
+
export const MSG_FLAG_EXHAUST_ALLOWED = 1 << 16;
|
|
30
|
+
/**
|
|
31
|
+
* Wire Protocol parser and encoder
|
|
32
|
+
*/
|
|
33
|
+
export class WireProtocol {
|
|
34
|
+
/**
|
|
35
|
+
* Parse a complete message from a buffer
|
|
36
|
+
* Returns the parsed command and the number of bytes consumed
|
|
37
|
+
*/
|
|
38
|
+
static parseMessage(buffer) {
|
|
39
|
+
if (buffer.length < 16) {
|
|
40
|
+
return null; // Not enough data for header
|
|
41
|
+
}
|
|
42
|
+
const header = this.parseHeader(buffer);
|
|
43
|
+
if (buffer.length < header.messageLength) {
|
|
44
|
+
return null; // Not enough data for complete message
|
|
45
|
+
}
|
|
46
|
+
const messageBuffer = buffer.subarray(0, header.messageLength);
|
|
47
|
+
switch (header.opCode) {
|
|
48
|
+
case OP_MSG:
|
|
49
|
+
return this.parseOpMsg(messageBuffer, header);
|
|
50
|
+
case OP_QUERY:
|
|
51
|
+
return this.parseOpQuery(messageBuffer, header);
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`Unsupported opCode: ${header.opCode}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse message header (16 bytes)
|
|
58
|
+
*/
|
|
59
|
+
static parseHeader(buffer) {
|
|
60
|
+
return {
|
|
61
|
+
messageLength: buffer.readInt32LE(0),
|
|
62
|
+
requestID: buffer.readInt32LE(4),
|
|
63
|
+
responseTo: buffer.readInt32LE(8),
|
|
64
|
+
opCode: buffer.readInt32LE(12),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse OP_MSG message
|
|
69
|
+
*/
|
|
70
|
+
static parseOpMsg(buffer, header) {
|
|
71
|
+
let offset = 16; // Skip header
|
|
72
|
+
const flagBits = buffer.readUInt32LE(offset);
|
|
73
|
+
offset += 4;
|
|
74
|
+
const sections = [];
|
|
75
|
+
const documentSequences = new Map();
|
|
76
|
+
// Parse sections until we reach the end (or checksum)
|
|
77
|
+
const messageEnd = (flagBits & MSG_FLAG_CHECKSUM_PRESENT)
|
|
78
|
+
? header.messageLength - 4
|
|
79
|
+
: header.messageLength;
|
|
80
|
+
while (offset < messageEnd) {
|
|
81
|
+
const sectionType = buffer.readUInt8(offset);
|
|
82
|
+
offset += 1;
|
|
83
|
+
if (sectionType === SECTION_BODY) {
|
|
84
|
+
// Single BSON document
|
|
85
|
+
const docSize = buffer.readInt32LE(offset);
|
|
86
|
+
const docBuffer = buffer.subarray(offset, offset + docSize);
|
|
87
|
+
const doc = plugins.bson.deserialize(docBuffer);
|
|
88
|
+
sections.push({ type: SECTION_BODY, payload: doc });
|
|
89
|
+
offset += docSize;
|
|
90
|
+
}
|
|
91
|
+
else if (sectionType === SECTION_DOCUMENT_SEQUENCE) {
|
|
92
|
+
// Document sequence
|
|
93
|
+
const sectionSize = buffer.readInt32LE(offset);
|
|
94
|
+
const sectionEnd = offset + sectionSize;
|
|
95
|
+
offset += 4;
|
|
96
|
+
// Read sequence identifier (C string)
|
|
97
|
+
let identifierEnd = offset;
|
|
98
|
+
while (buffer[identifierEnd] !== 0 && identifierEnd < sectionEnd) {
|
|
99
|
+
identifierEnd++;
|
|
100
|
+
}
|
|
101
|
+
const identifier = buffer.subarray(offset, identifierEnd).toString('utf8');
|
|
102
|
+
offset = identifierEnd + 1; // Skip null terminator
|
|
103
|
+
// Read documents
|
|
104
|
+
const documents = [];
|
|
105
|
+
while (offset < sectionEnd) {
|
|
106
|
+
const docSize = buffer.readInt32LE(offset);
|
|
107
|
+
const docBuffer = buffer.subarray(offset, offset + docSize);
|
|
108
|
+
documents.push(plugins.bson.deserialize(docBuffer));
|
|
109
|
+
offset += docSize;
|
|
110
|
+
}
|
|
111
|
+
sections.push({
|
|
112
|
+
type: SECTION_DOCUMENT_SEQUENCE,
|
|
113
|
+
payload: {},
|
|
114
|
+
sequenceIdentifier: identifier,
|
|
115
|
+
documents
|
|
116
|
+
});
|
|
117
|
+
documentSequences.set(identifier, documents);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
throw new Error(`Unknown section type: ${sectionType}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// The first section body contains the command
|
|
124
|
+
const commandSection = sections.find(s => s.type === SECTION_BODY);
|
|
125
|
+
if (!commandSection) {
|
|
126
|
+
throw new Error('OP_MSG missing command body section');
|
|
127
|
+
}
|
|
128
|
+
const command = commandSection.payload;
|
|
129
|
+
const commandName = Object.keys(command)[0];
|
|
130
|
+
const database = command.$db || 'admin';
|
|
131
|
+
return {
|
|
132
|
+
command: {
|
|
133
|
+
commandName,
|
|
134
|
+
command,
|
|
135
|
+
database,
|
|
136
|
+
requestID: header.requestID,
|
|
137
|
+
opCode: header.opCode,
|
|
138
|
+
documentSequences: documentSequences.size > 0 ? documentSequences : undefined,
|
|
139
|
+
},
|
|
140
|
+
bytesConsumed: header.messageLength,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Parse OP_QUERY message (legacy, used for initial handshake)
|
|
145
|
+
*/
|
|
146
|
+
static parseOpQuery(buffer, header) {
|
|
147
|
+
let offset = 16; // Skip header
|
|
148
|
+
const flags = buffer.readInt32LE(offset);
|
|
149
|
+
offset += 4;
|
|
150
|
+
// Read full collection name (C string)
|
|
151
|
+
let nameEnd = offset;
|
|
152
|
+
while (buffer[nameEnd] !== 0 && nameEnd < buffer.length) {
|
|
153
|
+
nameEnd++;
|
|
154
|
+
}
|
|
155
|
+
const fullCollectionName = buffer.subarray(offset, nameEnd).toString('utf8');
|
|
156
|
+
offset = nameEnd + 1;
|
|
157
|
+
const numberToSkip = buffer.readInt32LE(offset);
|
|
158
|
+
offset += 4;
|
|
159
|
+
const numberToReturn = buffer.readInt32LE(offset);
|
|
160
|
+
offset += 4;
|
|
161
|
+
// Read query document
|
|
162
|
+
const querySize = buffer.readInt32LE(offset);
|
|
163
|
+
const queryBuffer = buffer.subarray(offset, offset + querySize);
|
|
164
|
+
const query = plugins.bson.deserialize(queryBuffer);
|
|
165
|
+
offset += querySize;
|
|
166
|
+
// Extract database from collection name (format: "dbname.$cmd" or "dbname.collection")
|
|
167
|
+
const parts = fullCollectionName.split('.');
|
|
168
|
+
const database = parts[0];
|
|
169
|
+
// For OP_QUERY to .$cmd, the query IS the command
|
|
170
|
+
let commandName = 'find';
|
|
171
|
+
let command = query;
|
|
172
|
+
if (parts[1] === '$cmd') {
|
|
173
|
+
// This is a command
|
|
174
|
+
commandName = Object.keys(query)[0];
|
|
175
|
+
// Handle special commands like isMaster, hello
|
|
176
|
+
if (commandName === 'isMaster' || commandName === 'ismaster') {
|
|
177
|
+
commandName = 'hello';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
command: {
|
|
182
|
+
commandName,
|
|
183
|
+
command,
|
|
184
|
+
database,
|
|
185
|
+
requestID: header.requestID,
|
|
186
|
+
opCode: header.opCode,
|
|
187
|
+
},
|
|
188
|
+
bytesConsumed: header.messageLength,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Encode a response as OP_MSG
|
|
193
|
+
*/
|
|
194
|
+
static encodeOpMsgResponse(responseTo, response, requestID = Math.floor(Math.random() * 0x7FFFFFFF)) {
|
|
195
|
+
// Add $db if not present (optional in response)
|
|
196
|
+
const responseDoc = { ...response };
|
|
197
|
+
// Serialize the response document
|
|
198
|
+
const bodyBson = plugins.bson.serialize(responseDoc);
|
|
199
|
+
// Calculate message length
|
|
200
|
+
// Header (16) + flagBits (4) + section type (1) + body BSON
|
|
201
|
+
const messageLength = 16 + 4 + 1 + bodyBson.length;
|
|
202
|
+
const buffer = Buffer.alloc(messageLength);
|
|
203
|
+
let offset = 0;
|
|
204
|
+
// Write header
|
|
205
|
+
buffer.writeInt32LE(messageLength, offset); // messageLength
|
|
206
|
+
offset += 4;
|
|
207
|
+
buffer.writeInt32LE(requestID, offset); // requestID
|
|
208
|
+
offset += 4;
|
|
209
|
+
buffer.writeInt32LE(responseTo, offset); // responseTo
|
|
210
|
+
offset += 4;
|
|
211
|
+
buffer.writeInt32LE(OP_MSG, offset); // opCode
|
|
212
|
+
offset += 4;
|
|
213
|
+
// Write flagBits (0 = no flags)
|
|
214
|
+
buffer.writeUInt32LE(0, offset);
|
|
215
|
+
offset += 4;
|
|
216
|
+
// Write section type 0 (body)
|
|
217
|
+
buffer.writeUInt8(SECTION_BODY, offset);
|
|
218
|
+
offset += 1;
|
|
219
|
+
// Write body BSON
|
|
220
|
+
Buffer.from(bodyBson).copy(buffer, offset);
|
|
221
|
+
return buffer;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Encode a response as OP_REPLY (legacy, for OP_QUERY responses)
|
|
225
|
+
*/
|
|
226
|
+
static encodeOpReplyResponse(responseTo, documents, requestID = Math.floor(Math.random() * 0x7FFFFFFF), cursorId = BigInt(0)) {
|
|
227
|
+
// Serialize all documents
|
|
228
|
+
const docBuffers = documents.map(doc => plugins.bson.serialize(doc));
|
|
229
|
+
const totalDocsSize = docBuffers.reduce((sum, buf) => sum + buf.length, 0);
|
|
230
|
+
// Message format:
|
|
231
|
+
// Header (16) + responseFlags (4) + cursorID (8) + startingFrom (4) + numberReturned (4) + documents
|
|
232
|
+
const messageLength = 16 + 4 + 8 + 4 + 4 + totalDocsSize;
|
|
233
|
+
const buffer = Buffer.alloc(messageLength);
|
|
234
|
+
let offset = 0;
|
|
235
|
+
// Write header
|
|
236
|
+
buffer.writeInt32LE(messageLength, offset); // messageLength
|
|
237
|
+
offset += 4;
|
|
238
|
+
buffer.writeInt32LE(requestID, offset); // requestID
|
|
239
|
+
offset += 4;
|
|
240
|
+
buffer.writeInt32LE(responseTo, offset); // responseTo
|
|
241
|
+
offset += 4;
|
|
242
|
+
buffer.writeInt32LE(OP_REPLY, offset); // opCode
|
|
243
|
+
offset += 4;
|
|
244
|
+
// Write OP_REPLY fields
|
|
245
|
+
buffer.writeInt32LE(0, offset); // responseFlags (0 = no errors)
|
|
246
|
+
offset += 4;
|
|
247
|
+
buffer.writeBigInt64LE(cursorId, offset); // cursorID
|
|
248
|
+
offset += 8;
|
|
249
|
+
buffer.writeInt32LE(0, offset); // startingFrom
|
|
250
|
+
offset += 4;
|
|
251
|
+
buffer.writeInt32LE(documents.length, offset); // numberReturned
|
|
252
|
+
offset += 4;
|
|
253
|
+
// Write documents
|
|
254
|
+
for (const docBuffer of docBuffers) {
|
|
255
|
+
Buffer.from(docBuffer).copy(buffer, offset);
|
|
256
|
+
offset += docBuffer.length;
|
|
257
|
+
}
|
|
258
|
+
return buffer;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Encode an error response
|
|
262
|
+
*/
|
|
263
|
+
static encodeErrorResponse(responseTo, errorCode, errorMessage, commandName) {
|
|
264
|
+
const response = {
|
|
265
|
+
ok: 0,
|
|
266
|
+
errmsg: errorMessage,
|
|
267
|
+
code: errorCode,
|
|
268
|
+
codeName: this.getErrorCodeName(errorCode),
|
|
269
|
+
};
|
|
270
|
+
return this.encodeOpMsgResponse(responseTo, response);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get error code name from error code
|
|
274
|
+
*/
|
|
275
|
+
static getErrorCodeName(code) {
|
|
276
|
+
const errorNames = {
|
|
277
|
+
0: 'OK',
|
|
278
|
+
1: 'InternalError',
|
|
279
|
+
2: 'BadValue',
|
|
280
|
+
11000: 'DuplicateKey',
|
|
281
|
+
11001: 'DuplicateKeyValue',
|
|
282
|
+
13: 'Unauthorized',
|
|
283
|
+
26: 'NamespaceNotFound',
|
|
284
|
+
27: 'IndexNotFound',
|
|
285
|
+
48: 'NamespaceExists',
|
|
286
|
+
59: 'CommandNotFound',
|
|
287
|
+
66: 'ImmutableField',
|
|
288
|
+
73: 'InvalidNamespace',
|
|
289
|
+
85: 'IndexOptionsConflict',
|
|
290
|
+
112: 'WriteConflict',
|
|
291
|
+
121: 'DocumentValidationFailure',
|
|
292
|
+
211: 'KeyNotFound',
|
|
293
|
+
251: 'NoSuchTransaction',
|
|
294
|
+
};
|
|
295
|
+
return errorNames[code] || 'UnknownError';
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV2lyZVByb3RvY29sLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvdHNtZGIvc2VydmVyL1dpcmVQcm90b2NvbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBRS9DOzs7Ozs7Ozs7O0dBVUc7QUFFSCxVQUFVO0FBQ1YsTUFBTSxDQUFDLE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQyxDQUFRLGVBQWU7QUFDakQsTUFBTSxDQUFDLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxDQUFJLGdCQUFnQjtBQUNsRCxNQUFNLENBQUMsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLENBQUksZ0JBQWdCO0FBQ2xELE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsQ0FBSyxrREFBa0Q7QUFDcEYsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxDQUFFLGlCQUFpQjtBQUNuRCxNQUFNLENBQUMsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLENBQUksZ0JBQWdCO0FBQ2xELE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsQ0FBQyxzQkFBc0I7QUFDM0QsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxDQUFHLHFCQUFxQjtBQUMxRCxNQUFNLENBQUMsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLENBQU8saUNBQWlDO0FBRW5FLHVCQUF1QjtBQUN2QixNQUFNLENBQUMsTUFBTSxZQUFZLEdBQUcsQ0FBQyxDQUFDLENBQVcsdUJBQXVCO0FBQ2hFLE1BQU0sQ0FBQyxNQUFNLHlCQUF5QixHQUFHLENBQUMsQ0FBQyxDQUFDLHdDQUF3QztBQUVwRixtQkFBbUI7QUFDbkIsTUFBTSxDQUFDLE1BQU0seUJBQXlCLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUNoRCxNQUFNLENBQUMsTUFBTSxxQkFBcUIsR0FBRyxDQUFDLElB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
|