@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,443 @@
1
+ import * as plugins from '../congodb.plugins.js';
2
+ import type { IStorageAdapter } from './IStorageAdapter.js';
3
+ import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js';
4
+
5
+ /**
6
+ * In-memory storage adapter for CongoDB
7
+ * Optionally supports persistence to a file
8
+ */
9
+ export class MemoryStorageAdapter implements IStorageAdapter {
10
+ // Database -> Collection -> Documents
11
+ private databases: Map<string, Map<string, Map<string, IStoredDocument>>> = new Map();
12
+
13
+ // Database -> Collection -> Indexes
14
+ private indexes: Map<string, Map<string, Array<{
15
+ name: string;
16
+ key: Record<string, any>;
17
+ unique?: boolean;
18
+ sparse?: boolean;
19
+ expireAfterSeconds?: number;
20
+ }>>> = new Map();
21
+
22
+ // OpLog entries
23
+ private opLog: IOpLogEntry[] = [];
24
+ private opLogCounter = 0;
25
+
26
+ // Persistence settings
27
+ private persistPath?: string;
28
+ private persistInterval?: ReturnType<typeof setInterval>;
29
+ private fs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
30
+
31
+ constructor(options?: { persistPath?: string; persistIntervalMs?: number }) {
32
+ this.persistPath = options?.persistPath;
33
+ if (this.persistPath && options?.persistIntervalMs) {
34
+ this.persistInterval = setInterval(() => {
35
+ this.persist().catch(console.error);
36
+ }, options.persistIntervalMs);
37
+ }
38
+ }
39
+
40
+ async initialize(): Promise<void> {
41
+ if (this.persistPath) {
42
+ await this.restore();
43
+ }
44
+ }
45
+
46
+ async close(): Promise<void> {
47
+ if (this.persistInterval) {
48
+ clearInterval(this.persistInterval);
49
+ }
50
+ if (this.persistPath) {
51
+ await this.persist();
52
+ }
53
+ }
54
+
55
+ // ============================================================================
56
+ // Database Operations
57
+ // ============================================================================
58
+
59
+ async listDatabases(): Promise<string[]> {
60
+ return Array.from(this.databases.keys());
61
+ }
62
+
63
+ async createDatabase(dbName: string): Promise<void> {
64
+ if (!this.databases.has(dbName)) {
65
+ this.databases.set(dbName, new Map());
66
+ this.indexes.set(dbName, new Map());
67
+ }
68
+ }
69
+
70
+ async dropDatabase(dbName: string): Promise<boolean> {
71
+ const existed = this.databases.has(dbName);
72
+ this.databases.delete(dbName);
73
+ this.indexes.delete(dbName);
74
+ return existed;
75
+ }
76
+
77
+ async databaseExists(dbName: string): Promise<boolean> {
78
+ return this.databases.has(dbName);
79
+ }
80
+
81
+ // ============================================================================
82
+ // Collection Operations
83
+ // ============================================================================
84
+
85
+ async listCollections(dbName: string): Promise<string[]> {
86
+ const db = this.databases.get(dbName);
87
+ return db ? Array.from(db.keys()) : [];
88
+ }
89
+
90
+ async createCollection(dbName: string, collName: string): Promise<void> {
91
+ await this.createDatabase(dbName);
92
+ const db = this.databases.get(dbName)!;
93
+ if (!db.has(collName)) {
94
+ db.set(collName, new Map());
95
+ // Initialize default _id index
96
+ const dbIndexes = this.indexes.get(dbName)!;
97
+ dbIndexes.set(collName, [{ name: '_id_', key: { _id: 1 }, unique: true }]);
98
+ }
99
+ }
100
+
101
+ async dropCollection(dbName: string, collName: string): Promise<boolean> {
102
+ const db = this.databases.get(dbName);
103
+ if (!db) return false;
104
+ const existed = db.has(collName);
105
+ db.delete(collName);
106
+ const dbIndexes = this.indexes.get(dbName);
107
+ if (dbIndexes) {
108
+ dbIndexes.delete(collName);
109
+ }
110
+ return existed;
111
+ }
112
+
113
+ async collectionExists(dbName: string, collName: string): Promise<boolean> {
114
+ const db = this.databases.get(dbName);
115
+ return db ? db.has(collName) : false;
116
+ }
117
+
118
+ async renameCollection(dbName: string, oldName: string, newName: string): Promise<void> {
119
+ const db = this.databases.get(dbName);
120
+ if (!db || !db.has(oldName)) {
121
+ throw new Error(`Collection ${oldName} not found`);
122
+ }
123
+ const collection = db.get(oldName)!;
124
+ db.set(newName, collection);
125
+ db.delete(oldName);
126
+
127
+ // Also rename indexes
128
+ const dbIndexes = this.indexes.get(dbName);
129
+ if (dbIndexes && dbIndexes.has(oldName)) {
130
+ const collIndexes = dbIndexes.get(oldName)!;
131
+ dbIndexes.set(newName, collIndexes);
132
+ dbIndexes.delete(oldName);
133
+ }
134
+ }
135
+
136
+ // ============================================================================
137
+ // Document Operations
138
+ // ============================================================================
139
+
140
+ private getCollection(dbName: string, collName: string): Map<string, IStoredDocument> {
141
+ const db = this.databases.get(dbName);
142
+ if (!db) {
143
+ throw new Error(`Database ${dbName} not found`);
144
+ }
145
+ const collection = db.get(collName);
146
+ if (!collection) {
147
+ throw new Error(`Collection ${collName} not found`);
148
+ }
149
+ return collection;
150
+ }
151
+
152
+ private ensureCollection(dbName: string, collName: string): Map<string, IStoredDocument> {
153
+ if (!this.databases.has(dbName)) {
154
+ this.databases.set(dbName, new Map());
155
+ this.indexes.set(dbName, new Map());
156
+ }
157
+ const db = this.databases.get(dbName)!;
158
+ if (!db.has(collName)) {
159
+ db.set(collName, new Map());
160
+ const dbIndexes = this.indexes.get(dbName)!;
161
+ dbIndexes.set(collName, [{ name: '_id_', key: { _id: 1 }, unique: true }]);
162
+ }
163
+ return db.get(collName)!;
164
+ }
165
+
166
+ async insertOne(dbName: string, collName: string, doc: Document): Promise<IStoredDocument> {
167
+ const collection = this.ensureCollection(dbName, collName);
168
+ const storedDoc: IStoredDocument = {
169
+ ...doc,
170
+ _id: doc._id instanceof plugins.bson.ObjectId ? doc._id : new plugins.bson.ObjectId(doc._id),
171
+ };
172
+
173
+ if (!storedDoc._id) {
174
+ storedDoc._id = new plugins.bson.ObjectId();
175
+ }
176
+
177
+ const idStr = storedDoc._id.toHexString();
178
+ if (collection.has(idStr)) {
179
+ throw new Error(`Duplicate key error: _id ${idStr}`);
180
+ }
181
+
182
+ collection.set(idStr, storedDoc);
183
+ return storedDoc;
184
+ }
185
+
186
+ async insertMany(dbName: string, collName: string, docs: Document[]): Promise<IStoredDocument[]> {
187
+ const results: IStoredDocument[] = [];
188
+ for (const doc of docs) {
189
+ results.push(await this.insertOne(dbName, collName, doc));
190
+ }
191
+ return results;
192
+ }
193
+
194
+ async findAll(dbName: string, collName: string): Promise<IStoredDocument[]> {
195
+ const collection = this.ensureCollection(dbName, collName);
196
+ return Array.from(collection.values());
197
+ }
198
+
199
+ async findById(dbName: string, collName: string, id: plugins.bson.ObjectId): Promise<IStoredDocument | null> {
200
+ const collection = this.ensureCollection(dbName, collName);
201
+ return collection.get(id.toHexString()) || null;
202
+ }
203
+
204
+ async updateById(dbName: string, collName: string, id: plugins.bson.ObjectId, doc: IStoredDocument): Promise<boolean> {
205
+ const collection = this.ensureCollection(dbName, collName);
206
+ const idStr = id.toHexString();
207
+ if (!collection.has(idStr)) {
208
+ return false;
209
+ }
210
+ collection.set(idStr, doc);
211
+ return true;
212
+ }
213
+
214
+ async deleteById(dbName: string, collName: string, id: plugins.bson.ObjectId): Promise<boolean> {
215
+ const collection = this.ensureCollection(dbName, collName);
216
+ return collection.delete(id.toHexString());
217
+ }
218
+
219
+ async deleteByIds(dbName: string, collName: string, ids: plugins.bson.ObjectId[]): Promise<number> {
220
+ let count = 0;
221
+ for (const id of ids) {
222
+ if (await this.deleteById(dbName, collName, id)) {
223
+ count++;
224
+ }
225
+ }
226
+ return count;
227
+ }
228
+
229
+ async count(dbName: string, collName: string): Promise<number> {
230
+ const collection = this.ensureCollection(dbName, collName);
231
+ return collection.size;
232
+ }
233
+
234
+ // ============================================================================
235
+ // Index Operations
236
+ // ============================================================================
237
+
238
+ async saveIndex(
239
+ dbName: string,
240
+ collName: string,
241
+ indexName: string,
242
+ indexSpec: { key: Record<string, any>; unique?: boolean; sparse?: boolean; expireAfterSeconds?: number }
243
+ ): Promise<void> {
244
+ await this.createCollection(dbName, collName);
245
+ const dbIndexes = this.indexes.get(dbName)!;
246
+ let collIndexes = dbIndexes.get(collName);
247
+ if (!collIndexes) {
248
+ collIndexes = [{ name: '_id_', key: { _id: 1 }, unique: true }];
249
+ dbIndexes.set(collName, collIndexes);
250
+ }
251
+
252
+ // Check if index already exists
253
+ const existingIndex = collIndexes.findIndex(i => i.name === indexName);
254
+ if (existingIndex >= 0) {
255
+ collIndexes[existingIndex] = { name: indexName, ...indexSpec };
256
+ } else {
257
+ collIndexes.push({ name: indexName, ...indexSpec });
258
+ }
259
+ }
260
+
261
+ async getIndexes(dbName: string, collName: string): Promise<Array<{
262
+ name: string;
263
+ key: Record<string, any>;
264
+ unique?: boolean;
265
+ sparse?: boolean;
266
+ expireAfterSeconds?: number;
267
+ }>> {
268
+ const dbIndexes = this.indexes.get(dbName);
269
+ if (!dbIndexes) return [{ name: '_id_', key: { _id: 1 }, unique: true }];
270
+ const collIndexes = dbIndexes.get(collName);
271
+ return collIndexes || [{ name: '_id_', key: { _id: 1 }, unique: true }];
272
+ }
273
+
274
+ async dropIndex(dbName: string, collName: string, indexName: string): Promise<boolean> {
275
+ if (indexName === '_id_') {
276
+ throw new Error('Cannot drop _id index');
277
+ }
278
+ const dbIndexes = this.indexes.get(dbName);
279
+ if (!dbIndexes) return false;
280
+ const collIndexes = dbIndexes.get(collName);
281
+ if (!collIndexes) return false;
282
+
283
+ const idx = collIndexes.findIndex(i => i.name === indexName);
284
+ if (idx >= 0) {
285
+ collIndexes.splice(idx, 1);
286
+ return true;
287
+ }
288
+ return false;
289
+ }
290
+
291
+ // ============================================================================
292
+ // OpLog Operations
293
+ // ============================================================================
294
+
295
+ async appendOpLog(entry: IOpLogEntry): Promise<void> {
296
+ this.opLog.push(entry);
297
+ // Trim oplog if it gets too large (keep last 10000 entries)
298
+ if (this.opLog.length > 10000) {
299
+ this.opLog = this.opLog.slice(-10000);
300
+ }
301
+ }
302
+
303
+ async getOpLogAfter(ts: plugins.bson.Timestamp, limit: number = 1000): Promise<IOpLogEntry[]> {
304
+ const tsValue = ts.toNumber();
305
+ const entries = this.opLog.filter(e => e.ts.toNumber() > tsValue);
306
+ return entries.slice(0, limit);
307
+ }
308
+
309
+ async getLatestOpLogTimestamp(): Promise<plugins.bson.Timestamp | null> {
310
+ if (this.opLog.length === 0) return null;
311
+ return this.opLog[this.opLog.length - 1].ts;
312
+ }
313
+
314
+ /**
315
+ * Generate a new timestamp for oplog entries
316
+ */
317
+ generateTimestamp(): plugins.bson.Timestamp {
318
+ this.opLogCounter++;
319
+ return new plugins.bson.Timestamp({ t: Math.floor(Date.now() / 1000), i: this.opLogCounter });
320
+ }
321
+
322
+ // ============================================================================
323
+ // Transaction Support
324
+ // ============================================================================
325
+
326
+ async createSnapshot(dbName: string, collName: string): Promise<IStoredDocument[]> {
327
+ const docs = await this.findAll(dbName, collName);
328
+ // Deep clone the documents for snapshot isolation
329
+ return docs.map(doc => JSON.parse(JSON.stringify(doc)));
330
+ }
331
+
332
+ async hasConflicts(
333
+ dbName: string,
334
+ collName: string,
335
+ ids: plugins.bson.ObjectId[],
336
+ snapshotTime: plugins.bson.Timestamp
337
+ ): Promise<boolean> {
338
+ // Check if any of the given document IDs have been modified after snapshotTime
339
+ const ns = `${dbName}.${collName}`;
340
+ const modifiedIds = new Set<string>();
341
+
342
+ for (const entry of this.opLog) {
343
+ if (entry.ts.greaterThan(snapshotTime) && entry.ns === ns) {
344
+ if (entry.o._id) {
345
+ modifiedIds.add(entry.o._id.toString());
346
+ }
347
+ if (entry.o2?._id) {
348
+ modifiedIds.add(entry.o2._id.toString());
349
+ }
350
+ }
351
+ }
352
+
353
+ for (const id of ids) {
354
+ if (modifiedIds.has(id.toString())) {
355
+ return true;
356
+ }
357
+ }
358
+
359
+ return false;
360
+ }
361
+
362
+ // ============================================================================
363
+ // Persistence
364
+ // ============================================================================
365
+
366
+ async persist(): Promise<void> {
367
+ if (!this.persistPath) return;
368
+
369
+ const data = {
370
+ databases: {} as Record<string, Record<string, IStoredDocument[]>>,
371
+ indexes: {} as Record<string, Record<string, any[]>>,
372
+ opLogCounter: this.opLogCounter,
373
+ };
374
+
375
+ for (const [dbName, collections] of this.databases) {
376
+ data.databases[dbName] = {};
377
+ for (const [collName, docs] of collections) {
378
+ data.databases[dbName][collName] = Array.from(docs.values());
379
+ }
380
+ }
381
+
382
+ for (const [dbName, collIndexes] of this.indexes) {
383
+ data.indexes[dbName] = {};
384
+ for (const [collName, indexes] of collIndexes) {
385
+ data.indexes[dbName][collName] = indexes;
386
+ }
387
+ }
388
+
389
+ // Ensure parent directory exists
390
+ const dir = this.persistPath.substring(0, this.persistPath.lastIndexOf('/'));
391
+ if (dir) {
392
+ await this.fs.directory(dir).recursive().create();
393
+ }
394
+ await this.fs.file(this.persistPath).encoding('utf8').write(JSON.stringify(data, null, 2));
395
+ }
396
+
397
+ async restore(): Promise<void> {
398
+ if (!this.persistPath) return;
399
+
400
+ try {
401
+ const exists = await this.fs.file(this.persistPath).exists();
402
+ if (!exists) return;
403
+
404
+ const content = await this.fs.file(this.persistPath).encoding('utf8').read();
405
+ const data = JSON.parse(content as string);
406
+
407
+ this.databases.clear();
408
+ this.indexes.clear();
409
+
410
+ for (const [dbName, collections] of Object.entries(data.databases || {})) {
411
+ const dbMap = new Map<string, Map<string, IStoredDocument>>();
412
+ this.databases.set(dbName, dbMap);
413
+
414
+ for (const [collName, docs] of Object.entries(collections as Record<string, any[]>)) {
415
+ const collMap = new Map<string, IStoredDocument>();
416
+ for (const doc of docs) {
417
+ // Restore ObjectId
418
+ if (doc._id && typeof doc._id === 'string') {
419
+ doc._id = new plugins.bson.ObjectId(doc._id);
420
+ } else if (doc._id && typeof doc._id === 'object' && doc._id.$oid) {
421
+ doc._id = new plugins.bson.ObjectId(doc._id.$oid);
422
+ }
423
+ collMap.set(doc._id.toHexString(), doc);
424
+ }
425
+ dbMap.set(collName, collMap);
426
+ }
427
+ }
428
+
429
+ for (const [dbName, collIndexes] of Object.entries(data.indexes || {})) {
430
+ const indexMap = new Map<string, any[]>();
431
+ this.indexes.set(dbName, indexMap);
432
+ for (const [collName, indexes] of Object.entries(collIndexes as Record<string, any[]>)) {
433
+ indexMap.set(collName, indexes);
434
+ }
435
+ }
436
+
437
+ this.opLogCounter = data.opLogCounter || 0;
438
+ } catch (error) {
439
+ // If restore fails, start fresh
440
+ console.warn('Failed to restore from persistence:', error);
441
+ }
442
+ }
443
+ }