@push.rocks/smartdb 1.0.1 → 2.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.
Files changed (110) hide show
  1. package/.smartconfig.json +7 -4
  2. package/dist_rust/rustdb_linux_amd64 +0 -0
  3. package/dist_rust/rustdb_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +3 -3
  5. package/dist_ts/ts_local/classes.localsmartdb.d.ts +5 -5
  6. package/dist_ts/ts_local/classes.localsmartdb.js +5 -6
  7. package/dist_ts/ts_local/plugins.d.ts +1 -2
  8. package/dist_ts/ts_local/plugins.js +3 -3
  9. package/dist_ts/ts_smartdb/index.d.ts +1 -24
  10. package/dist_ts/ts_smartdb/index.js +4 -29
  11. package/dist_ts/ts_smartdb/plugins.d.ts +2 -10
  12. package/dist_ts/ts_smartdb/plugins.js +3 -13
  13. package/dist_ts/ts_smartdb/rust-db-bridge.d.ts +43 -0
  14. package/dist_ts/ts_smartdb/rust-db-bridge.js +98 -0
  15. package/dist_ts/ts_smartdb/server/SmartdbServer.d.ts +8 -37
  16. package/dist_ts/ts_smartdb/server/SmartdbServer.js +49 -204
  17. package/dist_ts/ts_smartdb/server/index.d.ts +0 -4
  18. package/dist_ts/ts_smartdb/server/index.js +1 -5
  19. package/license +3 -1
  20. package/package.json +9 -12
  21. package/readme.md +84 -171
  22. package/ts/00_commitinfo_data.ts +2 -2
  23. package/ts/ts_local/classes.localsmartdb.ts +5 -6
  24. package/ts/ts_local/plugins.ts +1 -3
  25. package/ts/ts_smartdb/index.ts +3 -41
  26. package/ts/ts_smartdb/plugins.ts +2 -15
  27. package/ts/ts_smartdb/rust-db-bridge.ts +138 -0
  28. package/ts/ts_smartdb/server/SmartdbServer.ts +53 -248
  29. package/ts/ts_smartdb/server/index.ts +0 -7
  30. package/dist_ts/ts_smartdb/engine/AggregationEngine.d.ts +0 -66
  31. package/dist_ts/ts_smartdb/engine/AggregationEngine.js +0 -189
  32. package/dist_ts/ts_smartdb/engine/IndexEngine.d.ts +0 -97
  33. package/dist_ts/ts_smartdb/engine/IndexEngine.js +0 -678
  34. package/dist_ts/ts_smartdb/engine/QueryEngine.d.ts +0 -54
  35. package/dist_ts/ts_smartdb/engine/QueryEngine.js +0 -271
  36. package/dist_ts/ts_smartdb/engine/QueryPlanner.d.ts +0 -64
  37. package/dist_ts/ts_smartdb/engine/QueryPlanner.js +0 -308
  38. package/dist_ts/ts_smartdb/engine/SessionEngine.d.ts +0 -117
  39. package/dist_ts/ts_smartdb/engine/SessionEngine.js +0 -232
  40. package/dist_ts/ts_smartdb/engine/TransactionEngine.d.ts +0 -85
  41. package/dist_ts/ts_smartdb/engine/TransactionEngine.js +0 -287
  42. package/dist_ts/ts_smartdb/engine/UpdateEngine.d.ts +0 -47
  43. package/dist_ts/ts_smartdb/engine/UpdateEngine.js +0 -461
  44. package/dist_ts/ts_smartdb/errors/SmartdbErrors.d.ts +0 -100
  45. package/dist_ts/ts_smartdb/errors/SmartdbErrors.js +0 -155
  46. package/dist_ts/ts_smartdb/server/CommandRouter.d.ts +0 -87
  47. package/dist_ts/ts_smartdb/server/CommandRouter.js +0 -222
  48. package/dist_ts/ts_smartdb/server/WireProtocol.d.ts +0 -117
  49. package/dist_ts/ts_smartdb/server/WireProtocol.js +0 -298
  50. package/dist_ts/ts_smartdb/server/handlers/AdminHandler.d.ts +0 -100
  51. package/dist_ts/ts_smartdb/server/handlers/AdminHandler.js +0 -668
  52. package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.d.ts +0 -31
  53. package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.js +0 -277
  54. package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.d.ts +0 -8
  55. package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.js +0 -95
  56. package/dist_ts/ts_smartdb/server/handlers/FindHandler.d.ts +0 -31
  57. package/dist_ts/ts_smartdb/server/handlers/FindHandler.js +0 -291
  58. package/dist_ts/ts_smartdb/server/handlers/HelloHandler.d.ts +0 -11
  59. package/dist_ts/ts_smartdb/server/handlers/HelloHandler.js +0 -62
  60. package/dist_ts/ts_smartdb/server/handlers/IndexHandler.d.ts +0 -20
  61. package/dist_ts/ts_smartdb/server/handlers/IndexHandler.js +0 -183
  62. package/dist_ts/ts_smartdb/server/handlers/InsertHandler.d.ts +0 -8
  63. package/dist_ts/ts_smartdb/server/handlers/InsertHandler.js +0 -79
  64. package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.d.ts +0 -24
  65. package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.js +0 -296
  66. package/dist_ts/ts_smartdb/server/handlers/index.d.ts +0 -8
  67. package/dist_ts/ts_smartdb/server/handlers/index.js +0 -10
  68. package/dist_ts/ts_smartdb/storage/FileStorageAdapter.d.ts +0 -85
  69. package/dist_ts/ts_smartdb/storage/FileStorageAdapter.js +0 -465
  70. package/dist_ts/ts_smartdb/storage/IStorageAdapter.d.ts +0 -145
  71. package/dist_ts/ts_smartdb/storage/IStorageAdapter.js +0 -2
  72. package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.d.ts +0 -67
  73. package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.js +0 -378
  74. package/dist_ts/ts_smartdb/storage/OpLog.d.ts +0 -93
  75. package/dist_ts/ts_smartdb/storage/OpLog.js +0 -221
  76. package/dist_ts/ts_smartdb/storage/WAL.d.ts +0 -117
  77. package/dist_ts/ts_smartdb/storage/WAL.js +0 -286
  78. package/dist_ts/ts_smartdb/types/interfaces.d.ts +0 -363
  79. package/dist_ts/ts_smartdb/types/interfaces.js +0 -2
  80. package/dist_ts/ts_smartdb/utils/checksum.d.ts +0 -30
  81. package/dist_ts/ts_smartdb/utils/checksum.js +0 -77
  82. package/dist_ts/ts_smartdb/utils/index.d.ts +0 -1
  83. package/dist_ts/ts_smartdb/utils/index.js +0 -2
  84. package/ts/ts_smartdb/engine/AggregationEngine.ts +0 -283
  85. package/ts/ts_smartdb/engine/IndexEngine.ts +0 -798
  86. package/ts/ts_smartdb/engine/QueryEngine.ts +0 -301
  87. package/ts/ts_smartdb/engine/QueryPlanner.ts +0 -393
  88. package/ts/ts_smartdb/engine/SessionEngine.ts +0 -292
  89. package/ts/ts_smartdb/engine/TransactionEngine.ts +0 -351
  90. package/ts/ts_smartdb/engine/UpdateEngine.ts +0 -506
  91. package/ts/ts_smartdb/errors/SmartdbErrors.ts +0 -181
  92. package/ts/ts_smartdb/server/CommandRouter.ts +0 -289
  93. package/ts/ts_smartdb/server/WireProtocol.ts +0 -416
  94. package/ts/ts_smartdb/server/handlers/AdminHandler.ts +0 -719
  95. package/ts/ts_smartdb/server/handlers/AggregateHandler.ts +0 -342
  96. package/ts/ts_smartdb/server/handlers/DeleteHandler.ts +0 -115
  97. package/ts/ts_smartdb/server/handlers/FindHandler.ts +0 -330
  98. package/ts/ts_smartdb/server/handlers/HelloHandler.ts +0 -78
  99. package/ts/ts_smartdb/server/handlers/IndexHandler.ts +0 -207
  100. package/ts/ts_smartdb/server/handlers/InsertHandler.ts +0 -97
  101. package/ts/ts_smartdb/server/handlers/UpdateHandler.ts +0 -344
  102. package/ts/ts_smartdb/server/handlers/index.ts +0 -10
  103. package/ts/ts_smartdb/storage/FileStorageAdapter.ts +0 -562
  104. package/ts/ts_smartdb/storage/IStorageAdapter.ts +0 -208
  105. package/ts/ts_smartdb/storage/MemoryStorageAdapter.ts +0 -455
  106. package/ts/ts_smartdb/storage/OpLog.ts +0 -282
  107. package/ts/ts_smartdb/storage/WAL.ts +0 -375
  108. package/ts/ts_smartdb/types/interfaces.ts +0 -433
  109. package/ts/ts_smartdb/utils/checksum.ts +0 -88
  110. package/ts/ts_smartdb/utils/index.ts +0 -1
@@ -1,289 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import type { IStorageAdapter } from '../storage/IStorageAdapter.js';
3
- import type { IParsedCommand } from './WireProtocol.js';
4
- import type { SmartdbServer } from './SmartdbServer.js';
5
- import { IndexEngine } from '../engine/IndexEngine.js';
6
- import { TransactionEngine } from '../engine/TransactionEngine.js';
7
- import { SessionEngine } from '../engine/SessionEngine.js';
8
-
9
- // Import handlers
10
- import { HelloHandler } from './handlers/HelloHandler.js';
11
- import { InsertHandler } from './handlers/InsertHandler.js';
12
- import { FindHandler } from './handlers/FindHandler.js';
13
- import { UpdateHandler } from './handlers/UpdateHandler.js';
14
- import { DeleteHandler } from './handlers/DeleteHandler.js';
15
- import { AggregateHandler } from './handlers/AggregateHandler.js';
16
- import { IndexHandler } from './handlers/IndexHandler.js';
17
- import { AdminHandler } from './handlers/AdminHandler.js';
18
-
19
- /**
20
- * Handler context passed to command handlers
21
- */
22
- export interface IHandlerContext {
23
- storage: IStorageAdapter;
24
- server: SmartdbServer;
25
- database: string;
26
- command: plugins.bson.Document;
27
- documentSequences?: Map<string, plugins.bson.Document[]>;
28
- /** Get or create an IndexEngine for a collection */
29
- getIndexEngine: (collName: string) => IndexEngine;
30
- /** Transaction engine instance */
31
- transactionEngine: TransactionEngine;
32
- /** Current transaction ID (if in a transaction) */
33
- txnId?: string;
34
- /** Session ID (from lsid) */
35
- sessionId?: string;
36
- /** Session engine instance */
37
- sessionEngine: SessionEngine;
38
- }
39
-
40
- /**
41
- * Command handler interface
42
- */
43
- export interface ICommandHandler {
44
- handle(context: IHandlerContext): Promise<plugins.bson.Document>;
45
- }
46
-
47
- /**
48
- * CommandRouter - Routes incoming commands to appropriate handlers
49
- */
50
- export class CommandRouter {
51
- private storage: IStorageAdapter;
52
- private server: SmartdbServer;
53
- private handlers: Map<string, ICommandHandler> = new Map();
54
-
55
- // Cursor state for getMore operations
56
- private cursors: Map<bigint, ICursorState> = new Map();
57
- private cursorIdCounter: bigint = BigInt(1);
58
-
59
- // Index engine cache: db.collection -> IndexEngine
60
- private indexEngines: Map<string, IndexEngine> = new Map();
61
-
62
- // Transaction engine (shared across all handlers)
63
- private transactionEngine: TransactionEngine;
64
-
65
- // Session engine (shared across all handlers)
66
- private sessionEngine: SessionEngine;
67
-
68
- constructor(storage: IStorageAdapter, server: SmartdbServer) {
69
- this.storage = storage;
70
- this.server = server;
71
- this.transactionEngine = new TransactionEngine(storage);
72
- this.sessionEngine = new SessionEngine();
73
- // Link session engine to transaction engine for auto-abort on session expiry
74
- this.sessionEngine.setTransactionEngine(this.transactionEngine);
75
- this.registerHandlers();
76
- }
77
-
78
- /**
79
- * Get or create an IndexEngine for a database.collection
80
- */
81
- getIndexEngine(dbName: string, collName: string): IndexEngine {
82
- const key = `${dbName}.${collName}`;
83
- let engine = this.indexEngines.get(key);
84
- if (!engine) {
85
- engine = new IndexEngine(dbName, collName, this.storage);
86
- this.indexEngines.set(key, engine);
87
- }
88
- return engine;
89
- }
90
-
91
- /**
92
- * Clear index engine cache for a collection (used when collection is dropped)
93
- */
94
- clearIndexEngineCache(dbName: string, collName?: string): void {
95
- if (collName) {
96
- this.indexEngines.delete(`${dbName}.${collName}`);
97
- } else {
98
- // Clear all engines for the database
99
- for (const key of this.indexEngines.keys()) {
100
- if (key.startsWith(`${dbName}.`)) {
101
- this.indexEngines.delete(key);
102
- }
103
- }
104
- }
105
- }
106
-
107
- /**
108
- * Register all command handlers
109
- */
110
- private registerHandlers(): void {
111
- // Create handler instances with shared state
112
- const helloHandler = new HelloHandler();
113
- const findHandler = new FindHandler(this.cursors, () => this.cursorIdCounter++);
114
- const insertHandler = new InsertHandler();
115
- const updateHandler = new UpdateHandler();
116
- const deleteHandler = new DeleteHandler();
117
- const aggregateHandler = new AggregateHandler(this.cursors, () => this.cursorIdCounter++);
118
- const indexHandler = new IndexHandler();
119
- const adminHandler = new AdminHandler();
120
-
121
- // Handshake commands
122
- this.handlers.set('hello', helloHandler);
123
- this.handlers.set('ismaster', helloHandler);
124
- this.handlers.set('isMaster', helloHandler);
125
-
126
- // CRUD commands
127
- this.handlers.set('find', findHandler);
128
- this.handlers.set('insert', insertHandler);
129
- this.handlers.set('update', updateHandler);
130
- this.handlers.set('delete', deleteHandler);
131
- this.handlers.set('findAndModify', updateHandler);
132
- this.handlers.set('getMore', findHandler);
133
- this.handlers.set('killCursors', findHandler);
134
-
135
- // Aggregation
136
- this.handlers.set('aggregate', aggregateHandler);
137
- this.handlers.set('count', findHandler);
138
- this.handlers.set('distinct', findHandler);
139
-
140
- // Index operations
141
- this.handlers.set('createIndexes', indexHandler);
142
- this.handlers.set('dropIndexes', indexHandler);
143
- this.handlers.set('listIndexes', indexHandler);
144
-
145
- // Admin/Database operations
146
- this.handlers.set('ping', adminHandler);
147
- this.handlers.set('listDatabases', adminHandler);
148
- this.handlers.set('listCollections', adminHandler);
149
- this.handlers.set('drop', adminHandler);
150
- this.handlers.set('dropDatabase', adminHandler);
151
- this.handlers.set('create', adminHandler);
152
- this.handlers.set('serverStatus', adminHandler);
153
- this.handlers.set('buildInfo', adminHandler);
154
- this.handlers.set('whatsmyuri', adminHandler);
155
- this.handlers.set('getLog', adminHandler);
156
- this.handlers.set('hostInfo', adminHandler);
157
- this.handlers.set('replSetGetStatus', adminHandler);
158
- this.handlers.set('isMaster', helloHandler);
159
- this.handlers.set('saslStart', adminHandler);
160
- this.handlers.set('saslContinue', adminHandler);
161
- this.handlers.set('endSessions', adminHandler);
162
- this.handlers.set('abortTransaction', adminHandler);
163
- this.handlers.set('commitTransaction', adminHandler);
164
- this.handlers.set('collStats', adminHandler);
165
- this.handlers.set('dbStats', adminHandler);
166
- this.handlers.set('connectionStatus', adminHandler);
167
- this.handlers.set('currentOp', adminHandler);
168
- this.handlers.set('collMod', adminHandler);
169
- this.handlers.set('renameCollection', adminHandler);
170
- }
171
-
172
- /**
173
- * Route a command to its handler
174
- */
175
- async route(parsedCommand: IParsedCommand): Promise<plugins.bson.Document> {
176
- const { commandName, command, database, documentSequences } = parsedCommand;
177
-
178
- // Extract session ID from lsid using SessionEngine helper
179
- let sessionId = SessionEngine.extractSessionId(command.lsid);
180
- let txnId: string | undefined;
181
-
182
- // If we have a session ID, register/touch the session
183
- if (sessionId) {
184
- this.sessionEngine.getOrCreateSession(sessionId);
185
- }
186
-
187
- // Check if this starts a new transaction
188
- if (command.startTransaction && sessionId) {
189
- txnId = this.transactionEngine.startTransaction(sessionId);
190
- this.sessionEngine.startTransaction(sessionId, txnId, command.txnNumber);
191
- } else if (sessionId && this.sessionEngine.isInTransaction(sessionId)) {
192
- // Continue existing transaction
193
- txnId = this.sessionEngine.getTransactionId(sessionId);
194
- // Verify transaction is still active
195
- if (txnId && !this.transactionEngine.isActive(txnId)) {
196
- this.sessionEngine.endTransaction(sessionId);
197
- txnId = undefined;
198
- }
199
- }
200
-
201
- // Create handler context
202
- const context: IHandlerContext = {
203
- storage: this.storage,
204
- server: this.server,
205
- database,
206
- command,
207
- documentSequences,
208
- getIndexEngine: (collName: string) => this.getIndexEngine(database, collName),
209
- transactionEngine: this.transactionEngine,
210
- sessionEngine: this.sessionEngine,
211
- txnId,
212
- sessionId,
213
- };
214
-
215
- // Find handler
216
- const handler = this.handlers.get(commandName);
217
-
218
- if (!handler) {
219
- // Unknown command
220
- return {
221
- ok: 0,
222
- errmsg: `no such command: '${commandName}'`,
223
- code: 59,
224
- codeName: 'CommandNotFound',
225
- };
226
- }
227
-
228
- try {
229
- return await handler.handle(context);
230
- } catch (error: any) {
231
- // Handle known error types
232
- if (error.code) {
233
- return {
234
- ok: 0,
235
- errmsg: error.message,
236
- code: error.code,
237
- codeName: error.codeName || 'UnknownError',
238
- };
239
- }
240
-
241
- // Generic error
242
- return {
243
- ok: 0,
244
- errmsg: error.message || 'Internal error',
245
- code: 1,
246
- codeName: 'InternalError',
247
- };
248
- }
249
- }
250
-
251
- /**
252
- * Close the command router and cleanup resources
253
- */
254
- close(): void {
255
- // Close session engine (stops cleanup interval, clears sessions)
256
- this.sessionEngine.close();
257
- // Clear cursors
258
- this.cursors.clear();
259
- // Clear index engine cache
260
- this.indexEngines.clear();
261
- }
262
-
263
- /**
264
- * Get session engine (for administrative purposes)
265
- */
266
- getSessionEngine(): SessionEngine {
267
- return this.sessionEngine;
268
- }
269
-
270
- /**
271
- * Get transaction engine (for administrative purposes)
272
- */
273
- getTransactionEngine(): TransactionEngine {
274
- return this.transactionEngine;
275
- }
276
- }
277
-
278
- /**
279
- * Cursor state for multi-batch queries
280
- */
281
- export interface ICursorState {
282
- id: bigint;
283
- database: string;
284
- collection: string;
285
- documents: plugins.bson.Document[];
286
- position: number;
287
- batchSize: number;
288
- createdAt: Date;
289
- }
@@ -1,416 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
-
3
- /**
4
- * MongoDB Wire Protocol Implementation
5
- * Handles parsing and encoding of MongoDB wire protocol messages (OP_MSG primarily)
6
- *
7
- * Wire Protocol Message Format:
8
- * - Header (16 bytes): messageLength (4), requestID (4), responseTo (4), opCode (4)
9
- * - OP_MSG: flagBits (4), sections[], optional checksum (4)
10
- *
11
- * References:
12
- * - https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/
13
- */
14
-
15
- // OpCodes
16
- export const OP_REPLY = 1; // Legacy reply
17
- export const OP_UPDATE = 2001; // Legacy update
18
- export const OP_INSERT = 2002; // Legacy insert
19
- export const OP_QUERY = 2004; // Legacy query (still used for initial handshake)
20
- export const OP_GET_MORE = 2005; // Legacy getMore
21
- export const OP_DELETE = 2006; // Legacy delete
22
- export const OP_KILL_CURSORS = 2007; // Legacy kill cursors
23
- export const OP_COMPRESSED = 2012; // Compressed message
24
- export const OP_MSG = 2013; // Modern protocol (MongoDB 3.6+)
25
-
26
- // OP_MSG Section Types
27
- export const SECTION_BODY = 0; // Single BSON document
28
- export const SECTION_DOCUMENT_SEQUENCE = 1; // Document sequence for bulk operations
29
-
30
- // OP_MSG Flag Bits
31
- export const MSG_FLAG_CHECKSUM_PRESENT = 1 << 0;
32
- export const MSG_FLAG_MORE_TO_COME = 1 << 1;
33
- export const MSG_FLAG_EXHAUST_ALLOWED = 1 << 16;
34
-
35
- /**
36
- * Parsed message header
37
- */
38
- export interface IMessageHeader {
39
- messageLength: number;
40
- requestID: number;
41
- responseTo: number;
42
- opCode: number;
43
- }
44
-
45
- /**
46
- * Parsed OP_MSG message
47
- */
48
- export interface IOpMsgMessage {
49
- header: IMessageHeader;
50
- flagBits: number;
51
- sections: IOpMsgSection[];
52
- checksum?: number;
53
- }
54
-
55
- /**
56
- * OP_MSG section (either body or document sequence)
57
- */
58
- export interface IOpMsgSection {
59
- type: number;
60
- payload: plugins.bson.Document;
61
- sequenceIdentifier?: string;
62
- documents?: plugins.bson.Document[];
63
- }
64
-
65
- /**
66
- * Parsed OP_QUERY message (legacy, but used for initial handshake)
67
- */
68
- export interface IOpQueryMessage {
69
- header: IMessageHeader;
70
- flags: number;
71
- fullCollectionName: string;
72
- numberToSkip: number;
73
- numberToReturn: number;
74
- query: plugins.bson.Document;
75
- returnFieldsSelector?: plugins.bson.Document;
76
- }
77
-
78
- /**
79
- * Parsed command from any message type
80
- */
81
- export interface IParsedCommand {
82
- commandName: string;
83
- command: plugins.bson.Document;
84
- database: string;
85
- requestID: number;
86
- opCode: number;
87
- documentSequences?: Map<string, plugins.bson.Document[]>;
88
- }
89
-
90
- /**
91
- * Wire Protocol parser and encoder
92
- */
93
- export class WireProtocol {
94
- /**
95
- * Parse a complete message from a buffer
96
- * Returns the parsed command and the number of bytes consumed
97
- */
98
- static parseMessage(buffer: Buffer): { command: IParsedCommand; bytesConsumed: number } | null {
99
- if (buffer.length < 16) {
100
- return null; // Not enough data for header
101
- }
102
-
103
- const header = this.parseHeader(buffer);
104
-
105
- if (buffer.length < header.messageLength) {
106
- return null; // Not enough data for complete message
107
- }
108
-
109
- const messageBuffer = buffer.subarray(0, header.messageLength);
110
-
111
- switch (header.opCode) {
112
- case OP_MSG:
113
- return this.parseOpMsg(messageBuffer, header);
114
- case OP_QUERY:
115
- return this.parseOpQuery(messageBuffer, header);
116
- default:
117
- throw new Error(`Unsupported opCode: ${header.opCode}`);
118
- }
119
- }
120
-
121
- /**
122
- * Parse message header (16 bytes)
123
- */
124
- private static parseHeader(buffer: Buffer): IMessageHeader {
125
- return {
126
- messageLength: buffer.readInt32LE(0),
127
- requestID: buffer.readInt32LE(4),
128
- responseTo: buffer.readInt32LE(8),
129
- opCode: buffer.readInt32LE(12),
130
- };
131
- }
132
-
133
- /**
134
- * Parse OP_MSG message
135
- */
136
- private static parseOpMsg(buffer: Buffer, header: IMessageHeader): { command: IParsedCommand; bytesConsumed: number } {
137
- let offset = 16; // Skip header
138
-
139
- const flagBits = buffer.readUInt32LE(offset);
140
- offset += 4;
141
-
142
- const sections: IOpMsgSection[] = [];
143
- const documentSequences = new Map<string, plugins.bson.Document[]>();
144
-
145
- // Parse sections until we reach the end (or checksum)
146
- const messageEnd = (flagBits & MSG_FLAG_CHECKSUM_PRESENT)
147
- ? header.messageLength - 4
148
- : header.messageLength;
149
-
150
- while (offset < messageEnd) {
151
- const sectionType = buffer.readUInt8(offset);
152
- offset += 1;
153
-
154
- if (sectionType === SECTION_BODY) {
155
- // Single BSON document
156
- const docSize = buffer.readInt32LE(offset);
157
- const docBuffer = buffer.subarray(offset, offset + docSize);
158
- const doc = plugins.bson.deserialize(docBuffer);
159
- sections.push({ type: SECTION_BODY, payload: doc });
160
- offset += docSize;
161
- } else if (sectionType === SECTION_DOCUMENT_SEQUENCE) {
162
- // Document sequence
163
- const sectionSize = buffer.readInt32LE(offset);
164
- const sectionEnd = offset + sectionSize;
165
- offset += 4;
166
-
167
- // Read sequence identifier (C string)
168
- let identifierEnd = offset;
169
- while (buffer[identifierEnd] !== 0 && identifierEnd < sectionEnd) {
170
- identifierEnd++;
171
- }
172
- const identifier = buffer.subarray(offset, identifierEnd).toString('utf8');
173
- offset = identifierEnd + 1; // Skip null terminator
174
-
175
- // Read documents
176
- const documents: plugins.bson.Document[] = [];
177
- while (offset < sectionEnd) {
178
- const docSize = buffer.readInt32LE(offset);
179
- const docBuffer = buffer.subarray(offset, offset + docSize);
180
- documents.push(plugins.bson.deserialize(docBuffer));
181
- offset += docSize;
182
- }
183
-
184
- sections.push({
185
- type: SECTION_DOCUMENT_SEQUENCE,
186
- payload: {},
187
- sequenceIdentifier: identifier,
188
- documents
189
- });
190
- documentSequences.set(identifier, documents);
191
- } else {
192
- throw new Error(`Unknown section type: ${sectionType}`);
193
- }
194
- }
195
-
196
- // The first section body contains the command
197
- const commandSection = sections.find(s => s.type === SECTION_BODY);
198
- if (!commandSection) {
199
- throw new Error('OP_MSG missing command body section');
200
- }
201
-
202
- const command = commandSection.payload;
203
- const commandName = Object.keys(command)[0];
204
- const database = command.$db || 'admin';
205
-
206
- return {
207
- command: {
208
- commandName,
209
- command,
210
- database,
211
- requestID: header.requestID,
212
- opCode: header.opCode,
213
- documentSequences: documentSequences.size > 0 ? documentSequences : undefined,
214
- },
215
- bytesConsumed: header.messageLength,
216
- };
217
- }
218
-
219
- /**
220
- * Parse OP_QUERY message (legacy, used for initial handshake)
221
- */
222
- private static parseOpQuery(buffer: Buffer, header: IMessageHeader): { command: IParsedCommand; bytesConsumed: number } {
223
- let offset = 16; // Skip header
224
-
225
- const flags = buffer.readInt32LE(offset);
226
- offset += 4;
227
-
228
- // Read full collection name (C string)
229
- let nameEnd = offset;
230
- while (buffer[nameEnd] !== 0 && nameEnd < buffer.length) {
231
- nameEnd++;
232
- }
233
- const fullCollectionName = buffer.subarray(offset, nameEnd).toString('utf8');
234
- offset = nameEnd + 1;
235
-
236
- const numberToSkip = buffer.readInt32LE(offset);
237
- offset += 4;
238
-
239
- const numberToReturn = buffer.readInt32LE(offset);
240
- offset += 4;
241
-
242
- // Read query document
243
- const querySize = buffer.readInt32LE(offset);
244
- const queryBuffer = buffer.subarray(offset, offset + querySize);
245
- const query = plugins.bson.deserialize(queryBuffer);
246
- offset += querySize;
247
-
248
- // Extract database from collection name (format: "dbname.$cmd" or "dbname.collection")
249
- const parts = fullCollectionName.split('.');
250
- const database = parts[0];
251
-
252
- // For OP_QUERY to .$cmd, the query IS the command
253
- let commandName = 'find';
254
- let command = query;
255
-
256
- if (parts[1] === '$cmd') {
257
- // This is a command
258
- commandName = Object.keys(query)[0];
259
- // Handle special commands like isMaster, hello
260
- if (commandName === 'isMaster' || commandName === 'ismaster') {
261
- commandName = 'hello';
262
- }
263
- }
264
-
265
- return {
266
- command: {
267
- commandName,
268
- command,
269
- database,
270
- requestID: header.requestID,
271
- opCode: header.opCode,
272
- },
273
- bytesConsumed: header.messageLength,
274
- };
275
- }
276
-
277
- /**
278
- * Encode a response as OP_MSG
279
- */
280
- static encodeOpMsgResponse(
281
- responseTo: number,
282
- response: plugins.bson.Document,
283
- requestID: number = Math.floor(Math.random() * 0x7FFFFFFF)
284
- ): Buffer {
285
- // Add $db if not present (optional in response)
286
- const responseDoc = { ...response };
287
-
288
- // Serialize the response document
289
- const bodyBson = plugins.bson.serialize(responseDoc);
290
-
291
- // Calculate message length
292
- // Header (16) + flagBits (4) + section type (1) + body BSON
293
- const messageLength = 16 + 4 + 1 + bodyBson.length;
294
-
295
- const buffer = Buffer.alloc(messageLength);
296
- let offset = 0;
297
-
298
- // Write header
299
- buffer.writeInt32LE(messageLength, offset); // messageLength
300
- offset += 4;
301
- buffer.writeInt32LE(requestID, offset); // requestID
302
- offset += 4;
303
- buffer.writeInt32LE(responseTo, offset); // responseTo
304
- offset += 4;
305
- buffer.writeInt32LE(OP_MSG, offset); // opCode
306
- offset += 4;
307
-
308
- // Write flagBits (0 = no flags)
309
- buffer.writeUInt32LE(0, offset);
310
- offset += 4;
311
-
312
- // Write section type 0 (body)
313
- buffer.writeUInt8(SECTION_BODY, offset);
314
- offset += 1;
315
-
316
- // Write body BSON
317
- Buffer.from(bodyBson).copy(buffer, offset);
318
-
319
- return buffer;
320
- }
321
-
322
- /**
323
- * Encode a response as OP_REPLY (legacy, for OP_QUERY responses)
324
- */
325
- static encodeOpReplyResponse(
326
- responseTo: number,
327
- documents: plugins.bson.Document[],
328
- requestID: number = Math.floor(Math.random() * 0x7FFFFFFF),
329
- cursorId: bigint = BigInt(0)
330
- ): Buffer {
331
- // Serialize all documents
332
- const docBuffers = documents.map(doc => plugins.bson.serialize(doc));
333
- const totalDocsSize = docBuffers.reduce((sum, buf) => sum + buf.length, 0);
334
-
335
- // Message format:
336
- // Header (16) + responseFlags (4) + cursorID (8) + startingFrom (4) + numberReturned (4) + documents
337
- const messageLength = 16 + 4 + 8 + 4 + 4 + totalDocsSize;
338
-
339
- const buffer = Buffer.alloc(messageLength);
340
- let offset = 0;
341
-
342
- // Write header
343
- buffer.writeInt32LE(messageLength, offset); // messageLength
344
- offset += 4;
345
- buffer.writeInt32LE(requestID, offset); // requestID
346
- offset += 4;
347
- buffer.writeInt32LE(responseTo, offset); // responseTo
348
- offset += 4;
349
- buffer.writeInt32LE(OP_REPLY, offset); // opCode
350
- offset += 4;
351
-
352
- // Write OP_REPLY fields
353
- buffer.writeInt32LE(0, offset); // responseFlags (0 = no errors)
354
- offset += 4;
355
- buffer.writeBigInt64LE(cursorId, offset); // cursorID
356
- offset += 8;
357
- buffer.writeInt32LE(0, offset); // startingFrom
358
- offset += 4;
359
- buffer.writeInt32LE(documents.length, offset); // numberReturned
360
- offset += 4;
361
-
362
- // Write documents
363
- for (const docBuffer of docBuffers) {
364
- Buffer.from(docBuffer).copy(buffer, offset);
365
- offset += docBuffer.length;
366
- }
367
-
368
- return buffer;
369
- }
370
-
371
- /**
372
- * Encode an error response
373
- */
374
- static encodeErrorResponse(
375
- responseTo: number,
376
- errorCode: number,
377
- errorMessage: string,
378
- commandName?: string
379
- ): Buffer {
380
- const response: plugins.bson.Document = {
381
- ok: 0,
382
- errmsg: errorMessage,
383
- code: errorCode,
384
- codeName: this.getErrorCodeName(errorCode),
385
- };
386
-
387
- return this.encodeOpMsgResponse(responseTo, response);
388
- }
389
-
390
- /**
391
- * Get error code name from error code
392
- */
393
- private static getErrorCodeName(code: number): string {
394
- const errorNames: Record<number, string> = {
395
- 0: 'OK',
396
- 1: 'InternalError',
397
- 2: 'BadValue',
398
- 11000: 'DuplicateKey',
399
- 11001: 'DuplicateKeyValue',
400
- 13: 'Unauthorized',
401
- 26: 'NamespaceNotFound',
402
- 27: 'IndexNotFound',
403
- 48: 'NamespaceExists',
404
- 59: 'CommandNotFound',
405
- 66: 'ImmutableField',
406
- 73: 'InvalidNamespace',
407
- 85: 'IndexOptionsConflict',
408
- 112: 'WriteConflict',
409
- 121: 'DocumentValidationFailure',
410
- 211: 'KeyNotFound',
411
- 251: 'NoSuchTransaction',
412
- };
413
-
414
- return errorNames[code] || 'UnknownError';
415
- }
416
- }