@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,479 @@
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
+ * File-based storage adapter for CongoDB
7
+ * Stores data in JSON files on disk for persistence
8
+ */
9
+ export class FileStorageAdapter implements IStorageAdapter {
10
+ private basePath: string;
11
+ private opLogCounter = 0;
12
+ private initialized = false;
13
+ private fs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
14
+
15
+ constructor(basePath: string) {
16
+ this.basePath = basePath;
17
+ }
18
+
19
+ // ============================================================================
20
+ // Helper Methods
21
+ // ============================================================================
22
+
23
+ private getDbPath(dbName: string): string {
24
+ return plugins.smartpath.join(this.basePath, dbName);
25
+ }
26
+
27
+ private getCollectionPath(dbName: string, collName: string): string {
28
+ return plugins.smartpath.join(this.basePath, dbName, `${collName}.json`);
29
+ }
30
+
31
+ private getIndexPath(dbName: string, collName: string): string {
32
+ return plugins.smartpath.join(this.basePath, dbName, `${collName}.indexes.json`);
33
+ }
34
+
35
+ private getOpLogPath(): string {
36
+ return plugins.smartpath.join(this.basePath, '_oplog.json');
37
+ }
38
+
39
+ private getMetaPath(): string {
40
+ return plugins.smartpath.join(this.basePath, '_meta.json');
41
+ }
42
+
43
+ private async readJsonFile<T>(filePath: string, defaultValue: T): Promise<T> {
44
+ try {
45
+ const exists = await this.fs.file(filePath).exists();
46
+ if (!exists) return defaultValue;
47
+ const content = await this.fs.file(filePath).encoding('utf8').read();
48
+ return JSON.parse(content as string);
49
+ } catch {
50
+ return defaultValue;
51
+ }
52
+ }
53
+
54
+ private async writeJsonFile(filePath: string, data: any): Promise<void> {
55
+ const dir = filePath.substring(0, filePath.lastIndexOf('/'));
56
+ await this.fs.directory(dir).recursive().create();
57
+ await this.fs.file(filePath).encoding('utf8').write(JSON.stringify(data, null, 2));
58
+ }
59
+
60
+ private restoreObjectIds(doc: any): IStoredDocument {
61
+ if (doc._id) {
62
+ if (typeof doc._id === 'string') {
63
+ doc._id = new plugins.bson.ObjectId(doc._id);
64
+ } else if (typeof doc._id === 'object' && doc._id.$oid) {
65
+ doc._id = new plugins.bson.ObjectId(doc._id.$oid);
66
+ }
67
+ }
68
+ return doc;
69
+ }
70
+
71
+ // ============================================================================
72
+ // Initialization
73
+ // ============================================================================
74
+
75
+ async initialize(): Promise<void> {
76
+ if (this.initialized) return;
77
+
78
+ await this.fs.directory(this.basePath).recursive().create();
79
+
80
+ // Load metadata
81
+ const meta = await this.readJsonFile(this.getMetaPath(), { opLogCounter: 0 });
82
+ this.opLogCounter = meta.opLogCounter || 0;
83
+
84
+ this.initialized = true;
85
+ }
86
+
87
+ async close(): Promise<void> {
88
+ // Save metadata
89
+ await this.writeJsonFile(this.getMetaPath(), { opLogCounter: this.opLogCounter });
90
+ this.initialized = false;
91
+ }
92
+
93
+ // ============================================================================
94
+ // Database Operations
95
+ // ============================================================================
96
+
97
+ async listDatabases(): Promise<string[]> {
98
+ await this.initialize();
99
+ try {
100
+ const entries = await this.fs.directory(this.basePath).list();
101
+ return entries
102
+ .filter(entry => entry.isDirectory && !entry.name.startsWith('_'))
103
+ .map(entry => entry.name);
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ async createDatabase(dbName: string): Promise<void> {
110
+ await this.initialize();
111
+ const dbPath = this.getDbPath(dbName);
112
+ await this.fs.directory(dbPath).recursive().create();
113
+ }
114
+
115
+ async dropDatabase(dbName: string): Promise<boolean> {
116
+ await this.initialize();
117
+ const dbPath = this.getDbPath(dbName);
118
+ try {
119
+ const exists = await this.fs.directory(dbPath).exists();
120
+ if (exists) {
121
+ await this.fs.directory(dbPath).recursive().delete();
122
+ return true;
123
+ }
124
+ return false;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ async databaseExists(dbName: string): Promise<boolean> {
131
+ await this.initialize();
132
+ const dbPath = this.getDbPath(dbName);
133
+ return this.fs.directory(dbPath).exists();
134
+ }
135
+
136
+ // ============================================================================
137
+ // Collection Operations
138
+ // ============================================================================
139
+
140
+ async listCollections(dbName: string): Promise<string[]> {
141
+ await this.initialize();
142
+ const dbPath = this.getDbPath(dbName);
143
+ try {
144
+ const entries = await this.fs.directory(dbPath).list();
145
+ return entries
146
+ .filter(entry => entry.isFile && entry.name.endsWith('.json') && !entry.name.endsWith('.indexes.json'))
147
+ .map(entry => entry.name.replace('.json', ''));
148
+ } catch {
149
+ return [];
150
+ }
151
+ }
152
+
153
+ async createCollection(dbName: string, collName: string): Promise<void> {
154
+ await this.createDatabase(dbName);
155
+ const collPath = this.getCollectionPath(dbName, collName);
156
+ const exists = await this.fs.file(collPath).exists();
157
+ if (!exists) {
158
+ await this.writeJsonFile(collPath, []);
159
+ // Create default _id index
160
+ await this.writeJsonFile(this.getIndexPath(dbName, collName), [
161
+ { name: '_id_', key: { _id: 1 }, unique: true }
162
+ ]);
163
+ }
164
+ }
165
+
166
+ async dropCollection(dbName: string, collName: string): Promise<boolean> {
167
+ await this.initialize();
168
+ const collPath = this.getCollectionPath(dbName, collName);
169
+ const indexPath = this.getIndexPath(dbName, collName);
170
+ try {
171
+ const exists = await this.fs.file(collPath).exists();
172
+ if (exists) {
173
+ await this.fs.file(collPath).delete();
174
+ try {
175
+ await this.fs.file(indexPath).delete();
176
+ } catch {}
177
+ return true;
178
+ }
179
+ return false;
180
+ } catch {
181
+ return false;
182
+ }
183
+ }
184
+
185
+ async collectionExists(dbName: string, collName: string): Promise<boolean> {
186
+ await this.initialize();
187
+ const collPath = this.getCollectionPath(dbName, collName);
188
+ return this.fs.file(collPath).exists();
189
+ }
190
+
191
+ async renameCollection(dbName: string, oldName: string, newName: string): Promise<void> {
192
+ await this.initialize();
193
+ const oldPath = this.getCollectionPath(dbName, oldName);
194
+ const newPath = this.getCollectionPath(dbName, newName);
195
+ const oldIndexPath = this.getIndexPath(dbName, oldName);
196
+ const newIndexPath = this.getIndexPath(dbName, newName);
197
+
198
+ const exists = await this.fs.file(oldPath).exists();
199
+ if (!exists) {
200
+ throw new Error(`Collection ${oldName} not found`);
201
+ }
202
+
203
+ // Read, write to new, delete old
204
+ const docs = await this.readJsonFile<any[]>(oldPath, []);
205
+ await this.writeJsonFile(newPath, docs);
206
+ await this.fs.file(oldPath).delete();
207
+
208
+ // Handle indexes
209
+ const indexes = await this.readJsonFile<any[]>(oldIndexPath, []);
210
+ await this.writeJsonFile(newIndexPath, indexes);
211
+ try {
212
+ await this.fs.file(oldIndexPath).delete();
213
+ } catch {}
214
+ }
215
+
216
+ // ============================================================================
217
+ // Document Operations
218
+ // ============================================================================
219
+
220
+ async insertOne(dbName: string, collName: string, doc: Document): Promise<IStoredDocument> {
221
+ await this.createCollection(dbName, collName);
222
+ const collPath = this.getCollectionPath(dbName, collName);
223
+ const docs = await this.readJsonFile<any[]>(collPath, []);
224
+
225
+ const storedDoc: IStoredDocument = {
226
+ ...doc,
227
+ _id: doc._id ? (doc._id instanceof plugins.bson.ObjectId ? doc._id : new plugins.bson.ObjectId(doc._id)) : new plugins.bson.ObjectId(),
228
+ };
229
+
230
+ // Check for duplicate
231
+ const idStr = storedDoc._id.toHexString();
232
+ if (docs.some(d => d._id === idStr || (d._id && d._id.toString() === idStr))) {
233
+ throw new Error(`Duplicate key error: _id ${idStr}`);
234
+ }
235
+
236
+ docs.push(storedDoc);
237
+ await this.writeJsonFile(collPath, docs);
238
+ return storedDoc;
239
+ }
240
+
241
+ async insertMany(dbName: string, collName: string, docsToInsert: Document[]): Promise<IStoredDocument[]> {
242
+ await this.createCollection(dbName, collName);
243
+ const collPath = this.getCollectionPath(dbName, collName);
244
+ const docs = await this.readJsonFile<any[]>(collPath, []);
245
+
246
+ const results: IStoredDocument[] = [];
247
+ const existingIds = new Set(docs.map(d => d._id?.toString?.() || d._id));
248
+
249
+ for (const doc of docsToInsert) {
250
+ const storedDoc: IStoredDocument = {
251
+ ...doc,
252
+ _id: doc._id ? (doc._id instanceof plugins.bson.ObjectId ? doc._id : new plugins.bson.ObjectId(doc._id)) : new plugins.bson.ObjectId(),
253
+ };
254
+
255
+ const idStr = storedDoc._id.toHexString();
256
+ if (existingIds.has(idStr)) {
257
+ throw new Error(`Duplicate key error: _id ${idStr}`);
258
+ }
259
+
260
+ existingIds.add(idStr);
261
+ docs.push(storedDoc);
262
+ results.push(storedDoc);
263
+ }
264
+
265
+ await this.writeJsonFile(collPath, docs);
266
+ return results;
267
+ }
268
+
269
+ async findAll(dbName: string, collName: string): Promise<IStoredDocument[]> {
270
+ await this.createCollection(dbName, collName);
271
+ const collPath = this.getCollectionPath(dbName, collName);
272
+ const docs = await this.readJsonFile<any[]>(collPath, []);
273
+ return docs.map(doc => this.restoreObjectIds(doc));
274
+ }
275
+
276
+ async findById(dbName: string, collName: string, id: plugins.bson.ObjectId): Promise<IStoredDocument | null> {
277
+ const docs = await this.findAll(dbName, collName);
278
+ const idStr = id.toHexString();
279
+ return docs.find(d => d._id.toHexString() === idStr) || null;
280
+ }
281
+
282
+ async updateById(dbName: string, collName: string, id: plugins.bson.ObjectId, doc: IStoredDocument): Promise<boolean> {
283
+ const collPath = this.getCollectionPath(dbName, collName);
284
+ const docs = await this.readJsonFile<any[]>(collPath, []);
285
+ const idStr = id.toHexString();
286
+
287
+ const idx = docs.findIndex(d => {
288
+ const docId = d._id?.toHexString?.() || d._id?.toString?.() || d._id;
289
+ return docId === idStr;
290
+ });
291
+
292
+ if (idx === -1) return false;
293
+
294
+ docs[idx] = doc;
295
+ await this.writeJsonFile(collPath, docs);
296
+ return true;
297
+ }
298
+
299
+ async deleteById(dbName: string, collName: string, id: plugins.bson.ObjectId): Promise<boolean> {
300
+ const collPath = this.getCollectionPath(dbName, collName);
301
+ const docs = await this.readJsonFile<any[]>(collPath, []);
302
+ const idStr = id.toHexString();
303
+
304
+ const idx = docs.findIndex(d => {
305
+ const docId = d._id?.toHexString?.() || d._id?.toString?.() || d._id;
306
+ return docId === idStr;
307
+ });
308
+
309
+ if (idx === -1) return false;
310
+
311
+ docs.splice(idx, 1);
312
+ await this.writeJsonFile(collPath, docs);
313
+ return true;
314
+ }
315
+
316
+ async deleteByIds(dbName: string, collName: string, ids: plugins.bson.ObjectId[]): Promise<number> {
317
+ const collPath = this.getCollectionPath(dbName, collName);
318
+ const docs = await this.readJsonFile<any[]>(collPath, []);
319
+ const idStrs = new Set(ids.map(id => id.toHexString()));
320
+
321
+ const originalLength = docs.length;
322
+ const filtered = docs.filter(d => {
323
+ const docId = d._id?.toHexString?.() || d._id?.toString?.() || d._id;
324
+ return !idStrs.has(docId);
325
+ });
326
+
327
+ await this.writeJsonFile(collPath, filtered);
328
+ return originalLength - filtered.length;
329
+ }
330
+
331
+ async count(dbName: string, collName: string): Promise<number> {
332
+ const collPath = this.getCollectionPath(dbName, collName);
333
+ const docs = await this.readJsonFile<any[]>(collPath, []);
334
+ return docs.length;
335
+ }
336
+
337
+ // ============================================================================
338
+ // Index Operations
339
+ // ============================================================================
340
+
341
+ async saveIndex(
342
+ dbName: string,
343
+ collName: string,
344
+ indexName: string,
345
+ indexSpec: { key: Record<string, any>; unique?: boolean; sparse?: boolean; expireAfterSeconds?: number }
346
+ ): Promise<void> {
347
+ await this.createCollection(dbName, collName);
348
+ const indexPath = this.getIndexPath(dbName, collName);
349
+ const indexes = await this.readJsonFile<any[]>(indexPath, [
350
+ { name: '_id_', key: { _id: 1 }, unique: true }
351
+ ]);
352
+
353
+ const existingIdx = indexes.findIndex(i => i.name === indexName);
354
+ if (existingIdx >= 0) {
355
+ indexes[existingIdx] = { name: indexName, ...indexSpec };
356
+ } else {
357
+ indexes.push({ name: indexName, ...indexSpec });
358
+ }
359
+
360
+ await this.writeJsonFile(indexPath, indexes);
361
+ }
362
+
363
+ async getIndexes(dbName: string, collName: string): Promise<Array<{
364
+ name: string;
365
+ key: Record<string, any>;
366
+ unique?: boolean;
367
+ sparse?: boolean;
368
+ expireAfterSeconds?: number;
369
+ }>> {
370
+ const indexPath = this.getIndexPath(dbName, collName);
371
+ return this.readJsonFile(indexPath, [{ name: '_id_', key: { _id: 1 }, unique: true }]);
372
+ }
373
+
374
+ async dropIndex(dbName: string, collName: string, indexName: string): Promise<boolean> {
375
+ if (indexName === '_id_') {
376
+ throw new Error('Cannot drop _id index');
377
+ }
378
+
379
+ const indexPath = this.getIndexPath(dbName, collName);
380
+ const indexes = await this.readJsonFile<any[]>(indexPath, []);
381
+
382
+ const idx = indexes.findIndex(i => i.name === indexName);
383
+ if (idx >= 0) {
384
+ indexes.splice(idx, 1);
385
+ await this.writeJsonFile(indexPath, indexes);
386
+ return true;
387
+ }
388
+ return false;
389
+ }
390
+
391
+ // ============================================================================
392
+ // OpLog Operations
393
+ // ============================================================================
394
+
395
+ async appendOpLog(entry: IOpLogEntry): Promise<void> {
396
+ const opLogPath = this.getOpLogPath();
397
+ const opLog = await this.readJsonFile<IOpLogEntry[]>(opLogPath, []);
398
+ opLog.push(entry);
399
+
400
+ // Trim oplog if it gets too large
401
+ if (opLog.length > 10000) {
402
+ opLog.splice(0, opLog.length - 10000);
403
+ }
404
+
405
+ await this.writeJsonFile(opLogPath, opLog);
406
+ }
407
+
408
+ async getOpLogAfter(ts: plugins.bson.Timestamp, limit: number = 1000): Promise<IOpLogEntry[]> {
409
+ const opLogPath = this.getOpLogPath();
410
+ const opLog = await this.readJsonFile<any[]>(opLogPath, []);
411
+ const tsValue = ts.toNumber();
412
+
413
+ const entries = opLog.filter(e => {
414
+ const entryTs = e.ts.toNumber ? e.ts.toNumber() : (e.ts.t * 4294967296 + e.ts.i);
415
+ return entryTs > tsValue;
416
+ });
417
+
418
+ return entries.slice(0, limit);
419
+ }
420
+
421
+ async getLatestOpLogTimestamp(): Promise<plugins.bson.Timestamp | null> {
422
+ const opLogPath = this.getOpLogPath();
423
+ const opLog = await this.readJsonFile<any[]>(opLogPath, []);
424
+ if (opLog.length === 0) return null;
425
+
426
+ const last = opLog[opLog.length - 1];
427
+ if (last.ts instanceof plugins.bson.Timestamp) {
428
+ return last.ts;
429
+ }
430
+ return new plugins.bson.Timestamp({ t: last.ts.t, i: last.ts.i });
431
+ }
432
+
433
+ generateTimestamp(): plugins.bson.Timestamp {
434
+ this.opLogCounter++;
435
+ return new plugins.bson.Timestamp({ t: Math.floor(Date.now() / 1000), i: this.opLogCounter });
436
+ }
437
+
438
+ // ============================================================================
439
+ // Transaction Support
440
+ // ============================================================================
441
+
442
+ async createSnapshot(dbName: string, collName: string): Promise<IStoredDocument[]> {
443
+ const docs = await this.findAll(dbName, collName);
444
+ return docs.map(doc => JSON.parse(JSON.stringify(doc)));
445
+ }
446
+
447
+ async hasConflicts(
448
+ dbName: string,
449
+ collName: string,
450
+ ids: plugins.bson.ObjectId[],
451
+ snapshotTime: plugins.bson.Timestamp
452
+ ): Promise<boolean> {
453
+ const opLogPath = this.getOpLogPath();
454
+ const opLog = await this.readJsonFile<any[]>(opLogPath, []);
455
+ const ns = `${dbName}.${collName}`;
456
+ const snapshotTs = snapshotTime.toNumber();
457
+ const modifiedIds = new Set<string>();
458
+
459
+ for (const entry of opLog) {
460
+ const entryTs = entry.ts.toNumber ? entry.ts.toNumber() : (entry.ts.t * 4294967296 + entry.ts.i);
461
+ if (entryTs > snapshotTs && entry.ns === ns) {
462
+ if (entry.o._id) {
463
+ modifiedIds.add(entry.o._id.toString());
464
+ }
465
+ if (entry.o2?._id) {
466
+ modifiedIds.add(entry.o2._id.toString());
467
+ }
468
+ }
469
+ }
470
+
471
+ for (const id of ids) {
472
+ if (modifiedIds.has(id.toString())) {
473
+ return true;
474
+ }
475
+ }
476
+
477
+ return false;
478
+ }
479
+ }
@@ -0,0 +1,202 @@
1
+ import type * as plugins from '../congodb.plugins.js';
2
+ import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js';
3
+
4
+ /**
5
+ * Storage adapter interface for CongoDB
6
+ * Implementations can provide different storage backends (memory, file, etc.)
7
+ */
8
+ export interface IStorageAdapter {
9
+ /**
10
+ * Initialize the storage adapter
11
+ */
12
+ initialize(): Promise<void>;
13
+
14
+ /**
15
+ * Close the storage adapter and release resources
16
+ */
17
+ close(): Promise<void>;
18
+
19
+ // ============================================================================
20
+ // Database Operations
21
+ // ============================================================================
22
+
23
+ /**
24
+ * List all database names
25
+ */
26
+ listDatabases(): Promise<string[]>;
27
+
28
+ /**
29
+ * Create a database (typically lazy - just marks it as existing)
30
+ */
31
+ createDatabase(dbName: string): Promise<void>;
32
+
33
+ /**
34
+ * Drop a database and all its collections
35
+ */
36
+ dropDatabase(dbName: string): Promise<boolean>;
37
+
38
+ /**
39
+ * Check if a database exists
40
+ */
41
+ databaseExists(dbName: string): Promise<boolean>;
42
+
43
+ // ============================================================================
44
+ // Collection Operations
45
+ // ============================================================================
46
+
47
+ /**
48
+ * List all collection names in a database
49
+ */
50
+ listCollections(dbName: string): Promise<string[]>;
51
+
52
+ /**
53
+ * Create a collection
54
+ */
55
+ createCollection(dbName: string, collName: string): Promise<void>;
56
+
57
+ /**
58
+ * Drop a collection
59
+ */
60
+ dropCollection(dbName: string, collName: string): Promise<boolean>;
61
+
62
+ /**
63
+ * Check if a collection exists
64
+ */
65
+ collectionExists(dbName: string, collName: string): Promise<boolean>;
66
+
67
+ /**
68
+ * Rename a collection
69
+ */
70
+ renameCollection(dbName: string, oldName: string, newName: string): Promise<void>;
71
+
72
+ // ============================================================================
73
+ // Document Operations
74
+ // ============================================================================
75
+
76
+ /**
77
+ * Insert a single document
78
+ * @returns The inserted document with _id
79
+ */
80
+ insertOne(dbName: string, collName: string, doc: Document): Promise<IStoredDocument>;
81
+
82
+ /**
83
+ * Insert multiple documents
84
+ * @returns Array of inserted documents with _ids
85
+ */
86
+ insertMany(dbName: string, collName: string, docs: Document[]): Promise<IStoredDocument[]>;
87
+
88
+ /**
89
+ * Find all documents in a collection
90
+ */
91
+ findAll(dbName: string, collName: string): Promise<IStoredDocument[]>;
92
+
93
+ /**
94
+ * Find a document by _id
95
+ */
96
+ findById(dbName: string, collName: string, id: plugins.bson.ObjectId): Promise<IStoredDocument | null>;
97
+
98
+ /**
99
+ * Update a document by _id
100
+ * @returns true if document was updated
101
+ */
102
+ updateById(dbName: string, collName: string, id: plugins.bson.ObjectId, doc: IStoredDocument): Promise<boolean>;
103
+
104
+ /**
105
+ * Delete a document by _id
106
+ * @returns true if document was deleted
107
+ */
108
+ deleteById(dbName: string, collName: string, id: plugins.bson.ObjectId): Promise<boolean>;
109
+
110
+ /**
111
+ * Delete multiple documents by _id
112
+ * @returns Number of deleted documents
113
+ */
114
+ deleteByIds(dbName: string, collName: string, ids: plugins.bson.ObjectId[]): Promise<number>;
115
+
116
+ /**
117
+ * Get the count of documents in a collection
118
+ */
119
+ count(dbName: string, collName: string): Promise<number>;
120
+
121
+ // ============================================================================
122
+ // Index Operations
123
+ // ============================================================================
124
+
125
+ /**
126
+ * Store index metadata
127
+ */
128
+ saveIndex(
129
+ dbName: string,
130
+ collName: string,
131
+ indexName: string,
132
+ indexSpec: { key: Record<string, any>; unique?: boolean; sparse?: boolean; expireAfterSeconds?: number }
133
+ ): Promise<void>;
134
+
135
+ /**
136
+ * Get all index metadata for a collection
137
+ */
138
+ getIndexes(dbName: string, collName: string): Promise<Array<{
139
+ name: string;
140
+ key: Record<string, any>;
141
+ unique?: boolean;
142
+ sparse?: boolean;
143
+ expireAfterSeconds?: number;
144
+ }>>;
145
+
146
+ /**
147
+ * Drop an index
148
+ */
149
+ dropIndex(dbName: string, collName: string, indexName: string): Promise<boolean>;
150
+
151
+ // ============================================================================
152
+ // OpLog Operations (for change streams)
153
+ // ============================================================================
154
+
155
+ /**
156
+ * Append an operation to the oplog
157
+ */
158
+ appendOpLog(entry: IOpLogEntry): Promise<void>;
159
+
160
+ /**
161
+ * Get oplog entries after a timestamp
162
+ */
163
+ getOpLogAfter(ts: plugins.bson.Timestamp, limit?: number): Promise<IOpLogEntry[]>;
164
+
165
+ /**
166
+ * Get the latest oplog timestamp
167
+ */
168
+ getLatestOpLogTimestamp(): Promise<plugins.bson.Timestamp | null>;
169
+
170
+ // ============================================================================
171
+ // Transaction Support
172
+ // ============================================================================
173
+
174
+ /**
175
+ * Create a snapshot of current data for transaction isolation
176
+ */
177
+ createSnapshot(dbName: string, collName: string): Promise<IStoredDocument[]>;
178
+
179
+ /**
180
+ * Check if any documents have been modified since the snapshot
181
+ */
182
+ hasConflicts(
183
+ dbName: string,
184
+ collName: string,
185
+ ids: plugins.bson.ObjectId[],
186
+ snapshotTime: plugins.bson.Timestamp
187
+ ): Promise<boolean>;
188
+
189
+ // ============================================================================
190
+ // Persistence (optional, for MemoryStorageAdapter with file backup)
191
+ // ============================================================================
192
+
193
+ /**
194
+ * Persist current state to disk (if supported)
195
+ */
196
+ persist?(): Promise<void>;
197
+
198
+ /**
199
+ * Load state from disk (if supported)
200
+ */
201
+ restore?(): Promise<void>;
202
+ }