@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartmongo",
3
- "version": "2.0.14",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "description": "A module for creating and managing a local MongoDB instance for testing purposes.",
6
6
  "main": "dist_ts/index.js",
@@ -8,19 +8,28 @@
8
8
  "type": "module",
9
9
  "author": "Lossless GmbH",
10
10
  "license": "MIT",
11
+ "scripts": {
12
+ "test": "(tstest test/)",
13
+ "build": "(tsbuild --web)",
14
+ "buildDocs": "tsdoc"
15
+ },
11
16
  "devDependencies": {
12
- "@git.zone/tsbuild": "^2.1.66",
13
- "@git.zone/tsbundle": "^2.0.8",
14
- "@git.zone/tsrun": "^1.2.44",
15
- "@git.zone/tstest": "^1.0.77",
16
- "@push.rocks/tapbundle": "^5.0.12",
17
- "@types/node": "^22.14.0"
17
+ "@git.zone/tsbuild": "^4.1.2",
18
+ "@git.zone/tsbundle": "^2.8.3",
19
+ "@git.zone/tsrun": "^2.0.1",
20
+ "@git.zone/tstest": "^3.1.8",
21
+ "@types/node": "^25.1.0",
22
+ "mongodb": "^7.0.0"
18
23
  },
19
24
  "dependencies": {
20
25
  "@push.rocks/mongodump": "^1.0.7",
21
26
  "@push.rocks/smartdata": "^5.0.23",
27
+ "@push.rocks/smartfs": "^1.3.1",
22
28
  "@push.rocks/smartpath": "^5.0.11",
23
29
  "@push.rocks/smartpromise": "^4.0.3",
30
+ "@push.rocks/smartrx": "^3.0.0",
31
+ "bson": "^6.10.0",
32
+ "mingo": "^7.2.0",
24
33
  "mongodb-memory-server": "^10.1.4"
25
34
  },
26
35
  "browserslist": [
@@ -52,12 +61,11 @@
52
61
  "type": "git",
53
62
  "url": "https://code.foss.global/push.rocks/smartmongo.git"
54
63
  },
64
+ "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6",
55
65
  "bugs": {
56
66
  "url": "https://code.foss.global/push.rocks/smartmongo/issues"
57
67
  },
58
- "scripts": {
59
- "test": "(tstest test/ --web)",
60
- "build": "(tsbuild --web --allowimplicitany)",
61
- "buildDocs": "tsdoc"
68
+ "pnpm": {
69
+ "overrides": {}
62
70
  }
63
- }
71
+ }
package/readme.hints.md CHANGED
@@ -8,3 +8,82 @@
8
8
  - This works in both Node.js and Deno environments
9
9
  - **Why:** Deno wraps CommonJS exports in a `default` property, so default imports are required
10
10
  - Fixed in version 2.0.13 (changed from `import * as mongoPlugin`)
11
+
12
+ ## CongoDB - MongoDB Wire Protocol Server
13
+
14
+ ### Architecture
15
+ CongoDB implements the MongoDB binary wire protocol (OP_MSG, OP_QUERY) allowing official MongoDB drivers to connect directly.
16
+
17
+ ```
18
+ Official MongoClient → TCP (wire protocol) → CongoServer → Engines → Storage
19
+ (mongodb npm) OP_MSG/BSON (port)
20
+ ```
21
+
22
+ ### Module Structure
23
+ ```
24
+ ts/congodb/
25
+ ├── server/ # Wire protocol server
26
+ │ ├── CongoServer.ts # TCP server, connection handling
27
+ │ ├── WireProtocol.ts # OP_MSG/OP_QUERY parsing & encoding
28
+ │ ├── CommandRouter.ts # Route commands to handlers
29
+ │ └── handlers/ # Command implementations
30
+ │ ├── HelloHandler.ts # hello/isMaster handshake
31
+ │ ├── FindHandler.ts # find, getMore, killCursors, count, distinct
32
+ │ ├── InsertHandler.ts # insert
33
+ │ ├── UpdateHandler.ts # update, findAndModify
34
+ │ ├── DeleteHandler.ts # delete
35
+ │ ├── AggregateHandler.ts # aggregate
36
+ │ ├── IndexHandler.ts # createIndexes, dropIndexes, listIndexes
37
+ │ └── AdminHandler.ts # ping, listDatabases, listCollections, etc.
38
+
39
+ ├── engine/ # Core logic (reused)
40
+ │ ├── QueryEngine.ts # Query filtering with mingo
41
+ │ ├── UpdateEngine.ts # Update operations
42
+ │ ├── AggregationEngine.ts # Aggregation pipelines
43
+ │ ├── IndexEngine.ts # Index management
44
+ │ └── TransactionEngine.ts # Transaction support
45
+
46
+ ├── storage/ # Storage layer
47
+ │ ├── IStorageAdapter.ts # Interface
48
+ │ ├── MemoryStorageAdapter.ts
49
+ │ └── FileStorageAdapter.ts
50
+
51
+ └── types/interfaces.ts # Type definitions
52
+ ```
53
+
54
+ ### Usage Example
55
+ ```typescript
56
+ import { CongoServer } from '@push.rocks/smartmongo/congodb';
57
+ import { MongoClient } from 'mongodb';
58
+
59
+ // Start server
60
+ const server = new CongoServer({ port: 27117 });
61
+ await server.start();
62
+
63
+ // Connect with official MongoDB driver
64
+ const client = new MongoClient('mongodb://127.0.0.1:27117', {
65
+ directConnection: true
66
+ });
67
+ await client.connect();
68
+
69
+ // Use like any MongoDB instance
70
+ const db = client.db('mydb');
71
+ await db.collection('users').insertOne({ name: 'John' });
72
+ const user = await db.collection('users').findOne({ name: 'John' });
73
+
74
+ // Cleanup
75
+ await client.close();
76
+ await server.stop();
77
+ ```
78
+
79
+ ### Supported Commands
80
+ - **Handshake**: hello, isMaster
81
+ - **CRUD**: find, insert, update, delete, findAndModify, getMore, killCursors
82
+ - **Aggregation**: aggregate, count, distinct
83
+ - **Indexes**: createIndexes, dropIndexes, listIndexes
84
+ - **Admin**: ping, listDatabases, listCollections, drop, dropDatabase, create, serverStatus, buildInfo
85
+
86
+ ### Notes
87
+ - The old CongoClient/CongoDb/CongoCollection classes have been removed
88
+ - Use the official `mongodb` npm package's MongoClient instead
89
+ - Server supports MongoDB wire protocol versions 0-21 (MongoDB 3.6 through 7.0 compatible)
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartmongo',
6
- version: '2.0.14',
6
+ version: '2.1.0',
7
7
  description: 'A module for creating and managing a local MongoDB instance for testing purposes.'
8
8
  }
@@ -0,0 +1,17 @@
1
+ // @push.rocks scope
2
+ import * as smartfs from '@push.rocks/smartfs';
3
+ import * as smartpath from '@push.rocks/smartpath';
4
+ import * as smartpromise from '@push.rocks/smartpromise';
5
+ import * as smartrx from '@push.rocks/smartrx';
6
+
7
+ export { smartfs, smartpath, smartpromise, smartrx };
8
+
9
+ // thirdparty
10
+ import * as bson from 'bson';
11
+ import * as mingo from 'mingo';
12
+
13
+ export { bson, mingo };
14
+
15
+ // Re-export commonly used mingo classes
16
+ export { Query } from 'mingo';
17
+ export { Aggregator } from 'mingo';
@@ -0,0 +1,283 @@
1
+ import * as plugins from '../congodb.plugins.js';
2
+ import type { Document, IStoredDocument, IAggregateOptions } from '../types/interfaces.js';
3
+
4
+ // Import mingo Aggregator
5
+ import { Aggregator } from 'mingo';
6
+
7
+ /**
8
+ * Aggregation engine using mingo for MongoDB-compatible aggregation pipeline execution
9
+ */
10
+ export class AggregationEngine {
11
+ /**
12
+ * Execute an aggregation pipeline on a collection of documents
13
+ */
14
+ static aggregate(
15
+ documents: IStoredDocument[],
16
+ pipeline: Document[],
17
+ options?: IAggregateOptions
18
+ ): Document[] {
19
+ if (!pipeline || pipeline.length === 0) {
20
+ return documents;
21
+ }
22
+
23
+ // Create mingo aggregator with the pipeline
24
+ const aggregator = new Aggregator(pipeline, {
25
+ collation: options?.collation as any,
26
+ });
27
+
28
+ // Run the aggregation
29
+ const result = aggregator.run(documents);
30
+
31
+ return Array.isArray(result) ? result : [];
32
+ }
33
+
34
+ /**
35
+ * Execute aggregation and return an iterator for lazy evaluation
36
+ */
37
+ static *aggregateIterator(
38
+ documents: IStoredDocument[],
39
+ pipeline: Document[],
40
+ options?: IAggregateOptions
41
+ ): Generator<Document> {
42
+ const aggregator = new Aggregator(pipeline, {
43
+ collation: options?.collation as any,
44
+ });
45
+
46
+ // Get the cursor from mingo
47
+ const cursor = aggregator.stream(documents);
48
+
49
+ for (const doc of cursor) {
50
+ yield doc;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Execute a $lookup stage manually (for cross-collection lookups)
56
+ * This is used when the lookup references another collection in the same database
57
+ */
58
+ static executeLookup(
59
+ documents: IStoredDocument[],
60
+ lookupSpec: {
61
+ from: string;
62
+ localField: string;
63
+ foreignField: string;
64
+ as: string;
65
+ },
66
+ foreignCollection: IStoredDocument[]
67
+ ): Document[] {
68
+ const { localField, foreignField, as } = lookupSpec;
69
+
70
+ return documents.map(doc => {
71
+ const localValue = this.getNestedValue(doc, localField);
72
+ const matches = foreignCollection.filter(foreignDoc => {
73
+ const foreignValue = this.getNestedValue(foreignDoc, foreignField);
74
+ return this.valuesMatch(localValue, foreignValue);
75
+ });
76
+
77
+ return {
78
+ ...doc,
79
+ [as]: matches,
80
+ };
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Execute a $graphLookup stage manually
86
+ */
87
+ static executeGraphLookup(
88
+ documents: IStoredDocument[],
89
+ graphLookupSpec: {
90
+ from: string;
91
+ startWith: string | Document;
92
+ connectFromField: string;
93
+ connectToField: string;
94
+ as: string;
95
+ maxDepth?: number;
96
+ depthField?: string;
97
+ restrictSearchWithMatch?: Document;
98
+ },
99
+ foreignCollection: IStoredDocument[]
100
+ ): Document[] {
101
+ const {
102
+ startWith,
103
+ connectFromField,
104
+ connectToField,
105
+ as,
106
+ maxDepth = 10,
107
+ depthField,
108
+ restrictSearchWithMatch,
109
+ } = graphLookupSpec;
110
+
111
+ return documents.map(doc => {
112
+ const startValue = typeof startWith === 'string' && startWith.startsWith('$')
113
+ ? this.getNestedValue(doc, startWith.slice(1))
114
+ : startWith;
115
+
116
+ const results: Document[] = [];
117
+ const visited = new Set<string>();
118
+ const queue: Array<{ value: any; depth: number }> = [];
119
+
120
+ // Initialize with start value(s)
121
+ const startValues = Array.isArray(startValue) ? startValue : [startValue];
122
+ for (const val of startValues) {
123
+ queue.push({ value: val, depth: 0 });
124
+ }
125
+
126
+ while (queue.length > 0) {
127
+ const { value, depth } = queue.shift()!;
128
+ if (depth > maxDepth) continue;
129
+
130
+ const valueKey = JSON.stringify(value);
131
+ if (visited.has(valueKey)) continue;
132
+ visited.add(valueKey);
133
+
134
+ // Find matching documents
135
+ for (const foreignDoc of foreignCollection) {
136
+ const foreignValue = this.getNestedValue(foreignDoc, connectToField);
137
+
138
+ if (this.valuesMatch(value, foreignValue)) {
139
+ // Check restrictSearchWithMatch
140
+ if (restrictSearchWithMatch) {
141
+ const matchQuery = new plugins.mingo.Query(restrictSearchWithMatch);
142
+ if (!matchQuery.test(foreignDoc)) continue;
143
+ }
144
+
145
+ const resultDoc = depthField
146
+ ? { ...foreignDoc, [depthField]: depth }
147
+ : { ...foreignDoc };
148
+
149
+ // Avoid duplicates in results
150
+ const docKey = foreignDoc._id.toHexString();
151
+ if (!results.some(r => r._id?.toHexString?.() === docKey)) {
152
+ results.push(resultDoc);
153
+
154
+ // Add connected values to queue
155
+ const nextValue = this.getNestedValue(foreignDoc, connectFromField);
156
+ if (nextValue !== undefined) {
157
+ const nextValues = Array.isArray(nextValue) ? nextValue : [nextValue];
158
+ for (const nv of nextValues) {
159
+ queue.push({ value: nv, depth: depth + 1 });
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ return {
168
+ ...doc,
169
+ [as]: results,
170
+ };
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Execute a $facet stage manually
176
+ */
177
+ static executeFacet(
178
+ documents: IStoredDocument[],
179
+ facetSpec: Record<string, Document[]>
180
+ ): Document {
181
+ const result: Document = {};
182
+
183
+ for (const [facetName, pipeline] of Object.entries(facetSpec)) {
184
+ result[facetName] = this.aggregate(documents, pipeline);
185
+ }
186
+
187
+ return result;
188
+ }
189
+
190
+ /**
191
+ * Execute a $unionWith stage
192
+ */
193
+ static executeUnionWith(
194
+ documents: IStoredDocument[],
195
+ otherDocuments: IStoredDocument[],
196
+ pipeline?: Document[]
197
+ ): Document[] {
198
+ let unionDocs: Document[] = otherDocuments;
199
+ if (pipeline && pipeline.length > 0) {
200
+ unionDocs = this.aggregate(otherDocuments, pipeline);
201
+ }
202
+ return [...documents, ...unionDocs];
203
+ }
204
+
205
+ /**
206
+ * Execute a $merge stage (output to another collection)
207
+ * Returns the documents that would be inserted/updated
208
+ */
209
+ static prepareMerge(
210
+ documents: Document[],
211
+ mergeSpec: {
212
+ into: string;
213
+ on?: string | string[];
214
+ whenMatched?: 'replace' | 'keepExisting' | 'merge' | 'fail' | Document[];
215
+ whenNotMatched?: 'insert' | 'discard' | 'fail';
216
+ }
217
+ ): {
218
+ toInsert: Document[];
219
+ toUpdate: Array<{ filter: Document; update: Document }>;
220
+ onField: string | string[];
221
+ whenMatched: string | Document[];
222
+ whenNotMatched: string;
223
+ } {
224
+ const onField = mergeSpec.on || '_id';
225
+ const whenMatched = mergeSpec.whenMatched || 'merge';
226
+ const whenNotMatched = mergeSpec.whenNotMatched || 'insert';
227
+
228
+ return {
229
+ toInsert: [],
230
+ toUpdate: [],
231
+ onField,
232
+ whenMatched,
233
+ whenNotMatched,
234
+ };
235
+ }
236
+
237
+ // ============================================================================
238
+ // Helper Methods
239
+ // ============================================================================
240
+
241
+ private static getNestedValue(obj: any, path: string): any {
242
+ const parts = path.split('.');
243
+ let current = obj;
244
+
245
+ for (const part of parts) {
246
+ if (current === null || current === undefined) {
247
+ return undefined;
248
+ }
249
+ current = current[part];
250
+ }
251
+
252
+ return current;
253
+ }
254
+
255
+ private static valuesMatch(a: any, b: any): boolean {
256
+ if (a === b) return true;
257
+
258
+ // Handle ObjectId comparison
259
+ if (a instanceof plugins.bson.ObjectId && b instanceof plugins.bson.ObjectId) {
260
+ return a.equals(b);
261
+ }
262
+
263
+ // Handle array contains check
264
+ if (Array.isArray(a)) {
265
+ return a.some(item => this.valuesMatch(item, b));
266
+ }
267
+ if (Array.isArray(b)) {
268
+ return b.some(item => this.valuesMatch(a, item));
269
+ }
270
+
271
+ // Handle Date comparison
272
+ if (a instanceof Date && b instanceof Date) {
273
+ return a.getTime() === b.getTime();
274
+ }
275
+
276
+ // Handle object comparison
277
+ if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
278
+ return JSON.stringify(a) === JSON.stringify(b);
279
+ }
280
+
281
+ return false;
282
+ }
283
+ }