@push.rocks/smartmongo 2.0.14 → 2.1.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 (86) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/congodb/congodb.plugins.d.ts +10 -0
  3. package/dist_ts/congodb/congodb.plugins.js +14 -0
  4. package/dist_ts/congodb/engine/AggregationEngine.d.ts +66 -0
  5. package/dist_ts/congodb/engine/AggregationEngine.js +189 -0
  6. package/dist_ts/congodb/engine/IndexEngine.d.ts +77 -0
  7. package/dist_ts/congodb/engine/IndexEngine.js +376 -0
  8. package/dist_ts/congodb/engine/QueryEngine.d.ts +54 -0
  9. package/dist_ts/congodb/engine/QueryEngine.js +271 -0
  10. package/dist_ts/congodb/engine/TransactionEngine.d.ts +85 -0
  11. package/dist_ts/congodb/engine/TransactionEngine.js +287 -0
  12. package/dist_ts/congodb/engine/UpdateEngine.d.ts +47 -0
  13. package/dist_ts/congodb/engine/UpdateEngine.js +461 -0
  14. package/dist_ts/congodb/errors/CongoErrors.d.ts +100 -0
  15. package/dist_ts/congodb/errors/CongoErrors.js +155 -0
  16. package/dist_ts/congodb/index.d.ts +19 -0
  17. package/dist_ts/congodb/index.js +26 -0
  18. package/dist_ts/congodb/server/CommandRouter.d.ts +51 -0
  19. package/dist_ts/congodb/server/CommandRouter.js +132 -0
  20. package/dist_ts/congodb/server/CongoServer.d.ts +95 -0
  21. package/dist_ts/congodb/server/CongoServer.js +227 -0
  22. package/dist_ts/congodb/server/WireProtocol.d.ts +117 -0
  23. package/dist_ts/congodb/server/WireProtocol.js +298 -0
  24. package/dist_ts/congodb/server/handlers/AdminHandler.d.ts +100 -0
  25. package/dist_ts/congodb/server/handlers/AdminHandler.js +568 -0
  26. package/dist_ts/congodb/server/handlers/AggregateHandler.d.ts +31 -0
  27. package/dist_ts/congodb/server/handlers/AggregateHandler.js +277 -0
  28. package/dist_ts/congodb/server/handlers/DeleteHandler.d.ts +8 -0
  29. package/dist_ts/congodb/server/handlers/DeleteHandler.js +83 -0
  30. package/dist_ts/congodb/server/handlers/FindHandler.d.ts +31 -0
  31. package/dist_ts/congodb/server/handlers/FindHandler.js +261 -0
  32. package/dist_ts/congodb/server/handlers/HelloHandler.d.ts +11 -0
  33. package/dist_ts/congodb/server/handlers/HelloHandler.js +62 -0
  34. package/dist_ts/congodb/server/handlers/IndexHandler.d.ts +20 -0
  35. package/dist_ts/congodb/server/handlers/IndexHandler.js +183 -0
  36. package/dist_ts/congodb/server/handlers/InsertHandler.d.ts +8 -0
  37. package/dist_ts/congodb/server/handlers/InsertHandler.js +76 -0
  38. package/dist_ts/congodb/server/handlers/UpdateHandler.d.ts +24 -0
  39. package/dist_ts/congodb/server/handlers/UpdateHandler.js +270 -0
  40. package/dist_ts/congodb/server/handlers/index.d.ts +8 -0
  41. package/dist_ts/congodb/server/handlers/index.js +10 -0
  42. package/dist_ts/congodb/server/index.d.ts +6 -0
  43. package/dist_ts/congodb/server/index.js +7 -0
  44. package/dist_ts/congodb/storage/FileStorageAdapter.d.ts +61 -0
  45. package/dist_ts/congodb/storage/FileStorageAdapter.js +396 -0
  46. package/dist_ts/congodb/storage/IStorageAdapter.d.ts +140 -0
  47. package/dist_ts/congodb/storage/IStorageAdapter.js +2 -0
  48. package/dist_ts/congodb/storage/MemoryStorageAdapter.d.ts +66 -0
  49. package/dist_ts/congodb/storage/MemoryStorageAdapter.js +367 -0
  50. package/dist_ts/congodb/storage/OpLog.d.ts +93 -0
  51. package/dist_ts/congodb/storage/OpLog.js +221 -0
  52. package/dist_ts/congodb/types/interfaces.d.ts +363 -0
  53. package/dist_ts/congodb/types/interfaces.js +2 -0
  54. package/dist_ts/index.d.ts +1 -0
  55. package/dist_ts/index.js +8 -6
  56. package/npmextra.json +17 -7
  57. package/package.json +20 -12
  58. package/readme.hints.md +79 -0
  59. package/ts/00_commitinfo_data.ts +1 -1
  60. package/ts/congodb/congodb.plugins.ts +17 -0
  61. package/ts/congodb/engine/AggregationEngine.ts +283 -0
  62. package/ts/congodb/engine/IndexEngine.ts +479 -0
  63. package/ts/congodb/engine/QueryEngine.ts +301 -0
  64. package/ts/congodb/engine/TransactionEngine.ts +351 -0
  65. package/ts/congodb/engine/UpdateEngine.ts +506 -0
  66. package/ts/congodb/errors/CongoErrors.ts +181 -0
  67. package/ts/congodb/index.ts +37 -0
  68. package/ts/congodb/server/CommandRouter.ts +180 -0
  69. package/ts/congodb/server/CongoServer.ts +298 -0
  70. package/ts/congodb/server/WireProtocol.ts +416 -0
  71. package/ts/congodb/server/handlers/AdminHandler.ts +614 -0
  72. package/ts/congodb/server/handlers/AggregateHandler.ts +342 -0
  73. package/ts/congodb/server/handlers/DeleteHandler.ts +100 -0
  74. package/ts/congodb/server/handlers/FindHandler.ts +301 -0
  75. package/ts/congodb/server/handlers/HelloHandler.ts +78 -0
  76. package/ts/congodb/server/handlers/IndexHandler.ts +207 -0
  77. package/ts/congodb/server/handlers/InsertHandler.ts +91 -0
  78. package/ts/congodb/server/handlers/UpdateHandler.ts +315 -0
  79. package/ts/congodb/server/handlers/index.ts +10 -0
  80. package/ts/congodb/server/index.ts +10 -0
  81. package/ts/congodb/storage/FileStorageAdapter.ts +479 -0
  82. package/ts/congodb/storage/IStorageAdapter.ts +202 -0
  83. package/ts/congodb/storage/MemoryStorageAdapter.ts +443 -0
  84. package/ts/congodb/storage/OpLog.ts +282 -0
  85. package/ts/congodb/types/interfaces.ts +433 -0
  86. package/ts/index.ts +3 -0
@@ -0,0 +1,301 @@
1
+ import * as plugins from '../../congodb.plugins.js';
2
+ import type { ICommandHandler, IHandlerContext, ICursorState } from '../CommandRouter.js';
3
+ import { QueryEngine } from '../../engine/QueryEngine.js';
4
+
5
+ /**
6
+ * FindHandler - Handles find, getMore, killCursors, count, distinct commands
7
+ */
8
+ export class FindHandler implements ICommandHandler {
9
+ private cursors: Map<bigint, ICursorState>;
10
+ private nextCursorId: () => bigint;
11
+
12
+ constructor(
13
+ cursors: Map<bigint, ICursorState>,
14
+ nextCursorId: () => bigint
15
+ ) {
16
+ this.cursors = cursors;
17
+ this.nextCursorId = nextCursorId;
18
+ }
19
+
20
+ async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
21
+ const { command } = context;
22
+
23
+ // Determine which operation to perform
24
+ if (command.find) {
25
+ return this.handleFind(context);
26
+ } else if (command.getMore !== undefined) {
27
+ return this.handleGetMore(context);
28
+ } else if (command.killCursors) {
29
+ return this.handleKillCursors(context);
30
+ } else if (command.count) {
31
+ return this.handleCount(context);
32
+ } else if (command.distinct) {
33
+ return this.handleDistinct(context);
34
+ }
35
+
36
+ return {
37
+ ok: 0,
38
+ errmsg: 'Unknown find-related command',
39
+ code: 59,
40
+ codeName: 'CommandNotFound',
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Handle find command
46
+ */
47
+ private async handleFind(context: IHandlerContext): Promise<plugins.bson.Document> {
48
+ const { storage, database, command } = context;
49
+
50
+ const collection = command.find;
51
+ const filter = command.filter || {};
52
+ const projection = command.projection;
53
+ const sort = command.sort;
54
+ const skip = command.skip || 0;
55
+ const limit = command.limit || 0;
56
+ const batchSize = command.batchSize || 101;
57
+ const singleBatch = command.singleBatch || false;
58
+
59
+ // Ensure collection exists
60
+ const exists = await storage.collectionExists(database, collection);
61
+ if (!exists) {
62
+ // Return empty cursor for non-existent collection
63
+ return {
64
+ ok: 1,
65
+ cursor: {
66
+ id: plugins.bson.Long.fromNumber(0),
67
+ ns: `${database}.${collection}`,
68
+ firstBatch: [],
69
+ },
70
+ };
71
+ }
72
+
73
+ // Get all documents
74
+ let documents = await storage.findAll(database, collection);
75
+
76
+ // Apply filter
77
+ documents = QueryEngine.filter(documents, filter);
78
+
79
+ // Apply sort
80
+ if (sort) {
81
+ documents = QueryEngine.sort(documents, sort);
82
+ }
83
+
84
+ // Apply skip
85
+ if (skip > 0) {
86
+ documents = documents.slice(skip);
87
+ }
88
+
89
+ // Apply limit
90
+ if (limit > 0) {
91
+ documents = documents.slice(0, limit);
92
+ }
93
+
94
+ // Apply projection
95
+ if (projection) {
96
+ documents = QueryEngine.project(documents, projection) as any[];
97
+ }
98
+
99
+ // Determine how many documents to return in first batch
100
+ const effectiveBatchSize = Math.min(batchSize, documents.length);
101
+ const firstBatch = documents.slice(0, effectiveBatchSize);
102
+ const remaining = documents.slice(effectiveBatchSize);
103
+
104
+ // Create cursor if there are more documents
105
+ let cursorId = BigInt(0);
106
+ if (remaining.length > 0 && !singleBatch) {
107
+ cursorId = this.nextCursorId();
108
+ this.cursors.set(cursorId, {
109
+ id: cursorId,
110
+ database,
111
+ collection,
112
+ documents: remaining,
113
+ position: 0,
114
+ batchSize,
115
+ createdAt: new Date(),
116
+ });
117
+ }
118
+
119
+ return {
120
+ ok: 1,
121
+ cursor: {
122
+ id: plugins.bson.Long.fromBigInt(cursorId),
123
+ ns: `${database}.${collection}`,
124
+ firstBatch,
125
+ },
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Handle getMore command
131
+ */
132
+ private async handleGetMore(context: IHandlerContext): Promise<plugins.bson.Document> {
133
+ const { database, command } = context;
134
+
135
+ const cursorIdInput = command.getMore;
136
+ const collection = command.collection;
137
+ const batchSize = command.batchSize || 101;
138
+
139
+ // Convert cursorId to bigint
140
+ let cursorId: bigint;
141
+ if (typeof cursorIdInput === 'bigint') {
142
+ cursorId = cursorIdInput;
143
+ } else if (cursorIdInput instanceof plugins.bson.Long) {
144
+ cursorId = cursorIdInput.toBigInt();
145
+ } else {
146
+ cursorId = BigInt(cursorIdInput);
147
+ }
148
+
149
+ const cursor = this.cursors.get(cursorId);
150
+ if (!cursor) {
151
+ return {
152
+ ok: 0,
153
+ errmsg: `cursor id ${cursorId} not found`,
154
+ code: 43,
155
+ codeName: 'CursorNotFound',
156
+ };
157
+ }
158
+
159
+ // Verify namespace
160
+ if (cursor.database !== database || cursor.collection !== collection) {
161
+ return {
162
+ ok: 0,
163
+ errmsg: 'cursor namespace mismatch',
164
+ code: 43,
165
+ codeName: 'CursorNotFound',
166
+ };
167
+ }
168
+
169
+ // Get next batch
170
+ const start = cursor.position;
171
+ const end = Math.min(start + batchSize, cursor.documents.length);
172
+ const nextBatch = cursor.documents.slice(start, end);
173
+ cursor.position = end;
174
+
175
+ // Check if cursor is exhausted
176
+ let returnCursorId = cursorId;
177
+ if (cursor.position >= cursor.documents.length) {
178
+ this.cursors.delete(cursorId);
179
+ returnCursorId = BigInt(0);
180
+ }
181
+
182
+ return {
183
+ ok: 1,
184
+ cursor: {
185
+ id: plugins.bson.Long.fromBigInt(returnCursorId),
186
+ ns: `${database}.${collection}`,
187
+ nextBatch,
188
+ },
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Handle killCursors command
194
+ */
195
+ private async handleKillCursors(context: IHandlerContext): Promise<plugins.bson.Document> {
196
+ const { command } = context;
197
+
198
+ const collection = command.killCursors;
199
+ const cursorIds = command.cursors || [];
200
+
201
+ const cursorsKilled: plugins.bson.Long[] = [];
202
+ const cursorsNotFound: plugins.bson.Long[] = [];
203
+ const cursorsUnknown: plugins.bson.Long[] = [];
204
+
205
+ for (const idInput of cursorIds) {
206
+ let cursorId: bigint;
207
+ if (typeof idInput === 'bigint') {
208
+ cursorId = idInput;
209
+ } else if (idInput instanceof plugins.bson.Long) {
210
+ cursorId = idInput.toBigInt();
211
+ } else {
212
+ cursorId = BigInt(idInput);
213
+ }
214
+
215
+ if (this.cursors.has(cursorId)) {
216
+ this.cursors.delete(cursorId);
217
+ cursorsKilled.push(plugins.bson.Long.fromBigInt(cursorId));
218
+ } else {
219
+ cursorsNotFound.push(plugins.bson.Long.fromBigInt(cursorId));
220
+ }
221
+ }
222
+
223
+ return {
224
+ ok: 1,
225
+ cursorsKilled,
226
+ cursorsNotFound,
227
+ cursorsUnknown,
228
+ cursorsAlive: [],
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Handle count command
234
+ */
235
+ private async handleCount(context: IHandlerContext): Promise<plugins.bson.Document> {
236
+ const { storage, database, command } = context;
237
+
238
+ const collection = command.count;
239
+ const query = command.query || {};
240
+ const skip = command.skip || 0;
241
+ const limit = command.limit || 0;
242
+
243
+ // Check if collection exists
244
+ const exists = await storage.collectionExists(database, collection);
245
+ if (!exists) {
246
+ return { ok: 1, n: 0 };
247
+ }
248
+
249
+ // Get all documents
250
+ let documents = await storage.findAll(database, collection);
251
+
252
+ // Apply filter
253
+ documents = QueryEngine.filter(documents, query);
254
+
255
+ // Apply skip
256
+ if (skip > 0) {
257
+ documents = documents.slice(skip);
258
+ }
259
+
260
+ // Apply limit
261
+ if (limit > 0) {
262
+ documents = documents.slice(0, limit);
263
+ }
264
+
265
+ return { ok: 1, n: documents.length };
266
+ }
267
+
268
+ /**
269
+ * Handle distinct command
270
+ */
271
+ private async handleDistinct(context: IHandlerContext): Promise<plugins.bson.Document> {
272
+ const { storage, database, command } = context;
273
+
274
+ const collection = command.distinct;
275
+ const key = command.key;
276
+ const query = command.query || {};
277
+
278
+ if (!key) {
279
+ return {
280
+ ok: 0,
281
+ errmsg: 'distinct requires a key',
282
+ code: 2,
283
+ codeName: 'BadValue',
284
+ };
285
+ }
286
+
287
+ // Check if collection exists
288
+ const exists = await storage.collectionExists(database, collection);
289
+ if (!exists) {
290
+ return { ok: 1, values: [] };
291
+ }
292
+
293
+ // Get all documents
294
+ const documents = await storage.findAll(database, collection);
295
+
296
+ // Get distinct values
297
+ const values = QueryEngine.distinct(documents, key, query);
298
+
299
+ return { ok: 1, values };
300
+ }
301
+ }
@@ -0,0 +1,78 @@
1
+ import * as plugins from '../../congodb.plugins.js';
2
+ import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
3
+
4
+ /**
5
+ * HelloHandler - Handles hello/isMaster handshake commands
6
+ *
7
+ * This is the first command sent by MongoDB drivers to establish a connection.
8
+ * It returns server capabilities and configuration.
9
+ */
10
+ export class HelloHandler implements ICommandHandler {
11
+ async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
12
+ const { command, server } = context;
13
+
14
+ // Build response with server capabilities
15
+ const response: plugins.bson.Document = {
16
+ ismaster: true,
17
+ ok: 1,
18
+
19
+ // Maximum sizes
20
+ maxBsonObjectSize: 16777216, // 16 MB
21
+ maxMessageSizeBytes: 48000000, // 48 MB
22
+ maxWriteBatchSize: 100000, // 100k documents per batch
23
+
24
+ // Timestamps
25
+ localTime: new Date(),
26
+
27
+ // Session support
28
+ logicalSessionTimeoutMinutes: 30,
29
+
30
+ // Connection info
31
+ connectionId: 1,
32
+
33
+ // Wire protocol versions (support MongoDB 3.6 through 7.0)
34
+ minWireVersion: 0,
35
+ maxWireVersion: 21,
36
+
37
+ // Server mode
38
+ readOnly: false,
39
+
40
+ // Topology info (standalone mode)
41
+ isWritablePrimary: true,
42
+
43
+ // Additional info
44
+ topologyVersion: {
45
+ processId: new plugins.bson.ObjectId(),
46
+ counter: plugins.bson.Long.fromNumber(0),
47
+ },
48
+ };
49
+
50
+ // Handle hello-specific fields
51
+ if (command.hello || command.hello === 1) {
52
+ response.helloOk = true;
53
+ }
54
+
55
+ // Handle client metadata
56
+ if (command.client) {
57
+ // Client is providing metadata about itself
58
+ // We just acknowledge it - no need to do anything special
59
+ }
60
+
61
+ // Handle SASL mechanisms query
62
+ if (command.saslSupportedMechs) {
63
+ response.saslSupportedMechs = [
64
+ // We don't actually support auth, but the driver needs to see this
65
+ ];
66
+ }
67
+
68
+ // Compression support (none for now)
69
+ if (command.compression) {
70
+ response.compression = [];
71
+ }
72
+
73
+ // Server version info
74
+ response.version = '7.0.0';
75
+
76
+ return response;
77
+ }
78
+ }
@@ -0,0 +1,207 @@
1
+ import * as plugins from '../../congodb.plugins.js';
2
+ import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
3
+ import { IndexEngine } from '../../engine/IndexEngine.js';
4
+
5
+ // Cache of index engines per collection
6
+ const indexEngines: Map<string, IndexEngine> = new Map();
7
+
8
+ /**
9
+ * Get or create an IndexEngine for a collection
10
+ */
11
+ function getIndexEngine(storage: any, database: string, collection: string): IndexEngine {
12
+ const key = `${database}.${collection}`;
13
+ let engine = indexEngines.get(key);
14
+
15
+ if (!engine) {
16
+ engine = new IndexEngine(database, collection, storage);
17
+ indexEngines.set(key, engine);
18
+ }
19
+
20
+ return engine;
21
+ }
22
+
23
+ /**
24
+ * IndexHandler - Handles createIndexes, dropIndexes, listIndexes commands
25
+ */
26
+ export class IndexHandler implements ICommandHandler {
27
+ async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
28
+ const { command } = context;
29
+
30
+ if (command.createIndexes) {
31
+ return this.handleCreateIndexes(context);
32
+ } else if (command.dropIndexes) {
33
+ return this.handleDropIndexes(context);
34
+ } else if (command.listIndexes) {
35
+ return this.handleListIndexes(context);
36
+ }
37
+
38
+ return {
39
+ ok: 0,
40
+ errmsg: 'Unknown index command',
41
+ code: 59,
42
+ codeName: 'CommandNotFound',
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Handle createIndexes command
48
+ */
49
+ private async handleCreateIndexes(context: IHandlerContext): Promise<plugins.bson.Document> {
50
+ const { storage, database, command } = context;
51
+
52
+ const collection = command.createIndexes;
53
+ const indexes = command.indexes || [];
54
+
55
+ if (!Array.isArray(indexes)) {
56
+ return {
57
+ ok: 0,
58
+ errmsg: 'indexes must be an array',
59
+ code: 2,
60
+ codeName: 'BadValue',
61
+ };
62
+ }
63
+
64
+ // Ensure collection exists
65
+ await storage.createCollection(database, collection);
66
+
67
+ const indexEngine = getIndexEngine(storage, database, collection);
68
+ const createdNames: string[] = [];
69
+ let numIndexesBefore = 0;
70
+ let numIndexesAfter = 0;
71
+
72
+ try {
73
+ const existingIndexes = await indexEngine.listIndexes();
74
+ numIndexesBefore = existingIndexes.length;
75
+
76
+ for (const indexSpec of indexes) {
77
+ const key = indexSpec.key;
78
+ const options = {
79
+ name: indexSpec.name,
80
+ unique: indexSpec.unique,
81
+ sparse: indexSpec.sparse,
82
+ expireAfterSeconds: indexSpec.expireAfterSeconds,
83
+ background: indexSpec.background,
84
+ partialFilterExpression: indexSpec.partialFilterExpression,
85
+ };
86
+
87
+ const name = await indexEngine.createIndex(key, options);
88
+ createdNames.push(name);
89
+ }
90
+
91
+ const finalIndexes = await indexEngine.listIndexes();
92
+ numIndexesAfter = finalIndexes.length;
93
+ } catch (error: any) {
94
+ return {
95
+ ok: 0,
96
+ errmsg: error.message || 'Failed to create index',
97
+ code: error.code || 1,
98
+ codeName: error.codeName || 'InternalError',
99
+ };
100
+ }
101
+
102
+ return {
103
+ ok: 1,
104
+ numIndexesBefore,
105
+ numIndexesAfter,
106
+ createdCollectionAutomatically: false,
107
+ commitQuorum: 'votingMembers',
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Handle dropIndexes command
113
+ */
114
+ private async handleDropIndexes(context: IHandlerContext): Promise<plugins.bson.Document> {
115
+ const { storage, database, command } = context;
116
+
117
+ const collection = command.dropIndexes;
118
+ const indexName = command.index;
119
+
120
+ // Check if collection exists
121
+ const exists = await storage.collectionExists(database, collection);
122
+ if (!exists) {
123
+ return {
124
+ ok: 0,
125
+ errmsg: `ns not found ${database}.${collection}`,
126
+ code: 26,
127
+ codeName: 'NamespaceNotFound',
128
+ };
129
+ }
130
+
131
+ const indexEngine = getIndexEngine(storage, database, collection);
132
+
133
+ try {
134
+ if (indexName === '*') {
135
+ // Drop all indexes except _id
136
+ await indexEngine.dropAllIndexes();
137
+ } else if (typeof indexName === 'string') {
138
+ // Drop specific index by name
139
+ await indexEngine.dropIndex(indexName);
140
+ } else if (typeof indexName === 'object') {
141
+ // Drop index by key specification
142
+ const indexes = await indexEngine.listIndexes();
143
+ const keyStr = JSON.stringify(indexName);
144
+
145
+ for (const idx of indexes) {
146
+ if (JSON.stringify(idx.key) === keyStr) {
147
+ await indexEngine.dropIndex(idx.name);
148
+ break;
149
+ }
150
+ }
151
+ }
152
+
153
+ return { ok: 1, nIndexesWas: 1 };
154
+ } catch (error: any) {
155
+ return {
156
+ ok: 0,
157
+ errmsg: error.message || 'Failed to drop index',
158
+ code: error.code || 27,
159
+ codeName: error.codeName || 'IndexNotFound',
160
+ };
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Handle listIndexes command
166
+ */
167
+ private async handleListIndexes(context: IHandlerContext): Promise<plugins.bson.Document> {
168
+ const { storage, database, command } = context;
169
+
170
+ const collection = command.listIndexes;
171
+ const cursor = command.cursor || {};
172
+ const batchSize = cursor.batchSize || 101;
173
+
174
+ // Check if collection exists
175
+ const exists = await storage.collectionExists(database, collection);
176
+ if (!exists) {
177
+ return {
178
+ ok: 0,
179
+ errmsg: `ns not found ${database}.${collection}`,
180
+ code: 26,
181
+ codeName: 'NamespaceNotFound',
182
+ };
183
+ }
184
+
185
+ const indexEngine = getIndexEngine(storage, database, collection);
186
+ const indexes = await indexEngine.listIndexes();
187
+
188
+ // Format indexes for response
189
+ const indexDocs = indexes.map(idx => ({
190
+ v: idx.v || 2,
191
+ key: idx.key,
192
+ name: idx.name,
193
+ ...(idx.unique ? { unique: idx.unique } : {}),
194
+ ...(idx.sparse ? { sparse: idx.sparse } : {}),
195
+ ...(idx.expireAfterSeconds !== undefined ? { expireAfterSeconds: idx.expireAfterSeconds } : {}),
196
+ }));
197
+
198
+ return {
199
+ ok: 1,
200
+ cursor: {
201
+ id: plugins.bson.Long.fromNumber(0),
202
+ ns: `${database}.${collection}`,
203
+ firstBatch: indexDocs,
204
+ },
205
+ };
206
+ }
207
+ }
@@ -0,0 +1,91 @@
1
+ import * as plugins from '../../congodb.plugins.js';
2
+ import type { ICommandHandler, IHandlerContext } from '../CommandRouter.js';
3
+
4
+ /**
5
+ * InsertHandler - Handles insert commands
6
+ */
7
+ export class InsertHandler implements ICommandHandler {
8
+ async handle(context: IHandlerContext): Promise<plugins.bson.Document> {
9
+ const { storage, database, command, documentSequences } = context;
10
+
11
+ const collection = command.insert;
12
+ if (typeof collection !== 'string') {
13
+ return {
14
+ ok: 0,
15
+ errmsg: 'insert command requires a collection name',
16
+ code: 2,
17
+ codeName: 'BadValue',
18
+ };
19
+ }
20
+
21
+ // Get documents from command or document sequences
22
+ let documents: plugins.bson.Document[] = command.documents || [];
23
+
24
+ // Check for OP_MSG document sequences (for bulk inserts)
25
+ if (documentSequences && documentSequences.has('documents')) {
26
+ documents = documentSequences.get('documents')!;
27
+ }
28
+
29
+ if (!Array.isArray(documents) || documents.length === 0) {
30
+ return {
31
+ ok: 0,
32
+ errmsg: 'insert command requires documents array',
33
+ code: 2,
34
+ codeName: 'BadValue',
35
+ };
36
+ }
37
+
38
+ const ordered = command.ordered !== false;
39
+ const writeErrors: plugins.bson.Document[] = [];
40
+ let insertedCount = 0;
41
+
42
+ // Ensure collection exists
43
+ await storage.createCollection(database, collection);
44
+
45
+ // Insert documents
46
+ for (let i = 0; i < documents.length; i++) {
47
+ const doc = documents[i];
48
+
49
+ try {
50
+ // Ensure _id exists
51
+ if (!doc._id) {
52
+ doc._id = new plugins.bson.ObjectId();
53
+ }
54
+
55
+ await storage.insertOne(database, collection, doc);
56
+ insertedCount++;
57
+ } catch (error: any) {
58
+ const writeError: plugins.bson.Document = {
59
+ index: i,
60
+ code: error.code || 11000,
61
+ errmsg: error.message || 'Insert failed',
62
+ };
63
+
64
+ // Check for duplicate key error
65
+ if (error.message?.includes('Duplicate key')) {
66
+ writeError.code = 11000;
67
+ writeError.keyPattern = { _id: 1 };
68
+ writeError.keyValue = { _id: doc._id };
69
+ }
70
+
71
+ writeErrors.push(writeError);
72
+
73
+ if (ordered) {
74
+ // Stop on first error for ordered inserts
75
+ break;
76
+ }
77
+ }
78
+ }
79
+
80
+ const response: plugins.bson.Document = {
81
+ ok: 1,
82
+ n: insertedCount,
83
+ };
84
+
85
+ if (writeErrors.length > 0) {
86
+ response.writeErrors = writeErrors;
87
+ }
88
+
89
+ return response;
90
+ }
91
+ }