@push.rocks/smartdb 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/.smartconfig.json +7 -4
  2. package/dist_rust/rustdb_linux_amd64 +0 -0
  3. package/dist_rust/rustdb_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +3 -3
  5. package/dist_ts/ts_local/classes.localsmartdb.d.ts +5 -5
  6. package/dist_ts/ts_local/classes.localsmartdb.js +5 -6
  7. package/dist_ts/ts_local/plugins.d.ts +1 -2
  8. package/dist_ts/ts_local/plugins.js +3 -3
  9. package/dist_ts/ts_smartdb/index.d.ts +1 -24
  10. package/dist_ts/ts_smartdb/index.js +4 -29
  11. package/dist_ts/ts_smartdb/plugins.d.ts +2 -10
  12. package/dist_ts/ts_smartdb/plugins.js +3 -13
  13. package/dist_ts/ts_smartdb/rust-db-bridge.d.ts +43 -0
  14. package/dist_ts/ts_smartdb/rust-db-bridge.js +98 -0
  15. package/dist_ts/ts_smartdb/server/SmartdbServer.d.ts +8 -37
  16. package/dist_ts/ts_smartdb/server/SmartdbServer.js +49 -204
  17. package/dist_ts/ts_smartdb/server/index.d.ts +0 -4
  18. package/dist_ts/ts_smartdb/server/index.js +1 -5
  19. package/license +3 -1
  20. package/package.json +9 -12
  21. package/readme.md +84 -171
  22. package/ts/00_commitinfo_data.ts +2 -2
  23. package/ts/ts_local/classes.localsmartdb.ts +5 -6
  24. package/ts/ts_local/plugins.ts +1 -3
  25. package/ts/ts_smartdb/index.ts +3 -41
  26. package/ts/ts_smartdb/plugins.ts +2 -15
  27. package/ts/ts_smartdb/rust-db-bridge.ts +138 -0
  28. package/ts/ts_smartdb/server/SmartdbServer.ts +53 -248
  29. package/ts/ts_smartdb/server/index.ts +0 -7
  30. package/dist_ts/ts_smartdb/engine/AggregationEngine.d.ts +0 -66
  31. package/dist_ts/ts_smartdb/engine/AggregationEngine.js +0 -189
  32. package/dist_ts/ts_smartdb/engine/IndexEngine.d.ts +0 -97
  33. package/dist_ts/ts_smartdb/engine/IndexEngine.js +0 -678
  34. package/dist_ts/ts_smartdb/engine/QueryEngine.d.ts +0 -54
  35. package/dist_ts/ts_smartdb/engine/QueryEngine.js +0 -271
  36. package/dist_ts/ts_smartdb/engine/QueryPlanner.d.ts +0 -64
  37. package/dist_ts/ts_smartdb/engine/QueryPlanner.js +0 -308
  38. package/dist_ts/ts_smartdb/engine/SessionEngine.d.ts +0 -117
  39. package/dist_ts/ts_smartdb/engine/SessionEngine.js +0 -232
  40. package/dist_ts/ts_smartdb/engine/TransactionEngine.d.ts +0 -85
  41. package/dist_ts/ts_smartdb/engine/TransactionEngine.js +0 -287
  42. package/dist_ts/ts_smartdb/engine/UpdateEngine.d.ts +0 -47
  43. package/dist_ts/ts_smartdb/engine/UpdateEngine.js +0 -461
  44. package/dist_ts/ts_smartdb/errors/SmartdbErrors.d.ts +0 -100
  45. package/dist_ts/ts_smartdb/errors/SmartdbErrors.js +0 -155
  46. package/dist_ts/ts_smartdb/server/CommandRouter.d.ts +0 -87
  47. package/dist_ts/ts_smartdb/server/CommandRouter.js +0 -222
  48. package/dist_ts/ts_smartdb/server/WireProtocol.d.ts +0 -117
  49. package/dist_ts/ts_smartdb/server/WireProtocol.js +0 -298
  50. package/dist_ts/ts_smartdb/server/handlers/AdminHandler.d.ts +0 -100
  51. package/dist_ts/ts_smartdb/server/handlers/AdminHandler.js +0 -668
  52. package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.d.ts +0 -31
  53. package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.js +0 -277
  54. package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.d.ts +0 -8
  55. package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.js +0 -95
  56. package/dist_ts/ts_smartdb/server/handlers/FindHandler.d.ts +0 -31
  57. package/dist_ts/ts_smartdb/server/handlers/FindHandler.js +0 -291
  58. package/dist_ts/ts_smartdb/server/handlers/HelloHandler.d.ts +0 -11
  59. package/dist_ts/ts_smartdb/server/handlers/HelloHandler.js +0 -62
  60. package/dist_ts/ts_smartdb/server/handlers/IndexHandler.d.ts +0 -20
  61. package/dist_ts/ts_smartdb/server/handlers/IndexHandler.js +0 -183
  62. package/dist_ts/ts_smartdb/server/handlers/InsertHandler.d.ts +0 -8
  63. package/dist_ts/ts_smartdb/server/handlers/InsertHandler.js +0 -79
  64. package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.d.ts +0 -24
  65. package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.js +0 -296
  66. package/dist_ts/ts_smartdb/server/handlers/index.d.ts +0 -8
  67. package/dist_ts/ts_smartdb/server/handlers/index.js +0 -10
  68. package/dist_ts/ts_smartdb/storage/FileStorageAdapter.d.ts +0 -85
  69. package/dist_ts/ts_smartdb/storage/FileStorageAdapter.js +0 -465
  70. package/dist_ts/ts_smartdb/storage/IStorageAdapter.d.ts +0 -145
  71. package/dist_ts/ts_smartdb/storage/IStorageAdapter.js +0 -2
  72. package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.d.ts +0 -67
  73. package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.js +0 -378
  74. package/dist_ts/ts_smartdb/storage/OpLog.d.ts +0 -93
  75. package/dist_ts/ts_smartdb/storage/OpLog.js +0 -221
  76. package/dist_ts/ts_smartdb/storage/WAL.d.ts +0 -117
  77. package/dist_ts/ts_smartdb/storage/WAL.js +0 -286
  78. package/dist_ts/ts_smartdb/types/interfaces.d.ts +0 -363
  79. package/dist_ts/ts_smartdb/types/interfaces.js +0 -2
  80. package/dist_ts/ts_smartdb/utils/checksum.d.ts +0 -30
  81. package/dist_ts/ts_smartdb/utils/checksum.js +0 -77
  82. package/dist_ts/ts_smartdb/utils/index.d.ts +0 -1
  83. package/dist_ts/ts_smartdb/utils/index.js +0 -2
  84. package/ts/ts_smartdb/engine/AggregationEngine.ts +0 -283
  85. package/ts/ts_smartdb/engine/IndexEngine.ts +0 -798
  86. package/ts/ts_smartdb/engine/QueryEngine.ts +0 -301
  87. package/ts/ts_smartdb/engine/QueryPlanner.ts +0 -393
  88. package/ts/ts_smartdb/engine/SessionEngine.ts +0 -292
  89. package/ts/ts_smartdb/engine/TransactionEngine.ts +0 -351
  90. package/ts/ts_smartdb/engine/UpdateEngine.ts +0 -506
  91. package/ts/ts_smartdb/errors/SmartdbErrors.ts +0 -181
  92. package/ts/ts_smartdb/server/CommandRouter.ts +0 -289
  93. package/ts/ts_smartdb/server/WireProtocol.ts +0 -416
  94. package/ts/ts_smartdb/server/handlers/AdminHandler.ts +0 -719
  95. package/ts/ts_smartdb/server/handlers/AggregateHandler.ts +0 -342
  96. package/ts/ts_smartdb/server/handlers/DeleteHandler.ts +0 -115
  97. package/ts/ts_smartdb/server/handlers/FindHandler.ts +0 -330
  98. package/ts/ts_smartdb/server/handlers/HelloHandler.ts +0 -78
  99. package/ts/ts_smartdb/server/handlers/IndexHandler.ts +0 -207
  100. package/ts/ts_smartdb/server/handlers/InsertHandler.ts +0 -97
  101. package/ts/ts_smartdb/server/handlers/UpdateHandler.ts +0 -344
  102. package/ts/ts_smartdb/server/handlers/index.ts +0 -10
  103. package/ts/ts_smartdb/storage/FileStorageAdapter.ts +0 -562
  104. package/ts/ts_smartdb/storage/IStorageAdapter.ts +0 -208
  105. package/ts/ts_smartdb/storage/MemoryStorageAdapter.ts +0 -455
  106. package/ts/ts_smartdb/storage/OpLog.ts +0 -282
  107. package/ts/ts_smartdb/storage/WAL.ts +0 -375
  108. package/ts/ts_smartdb/types/interfaces.ts +0 -433
  109. package/ts/ts_smartdb/utils/checksum.ts +0 -88
  110. package/ts/ts_smartdb/utils/index.ts +0 -1
@@ -1,562 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import type { IStorageAdapter } from './IStorageAdapter.js';
3
- import type { IStoredDocument, IOpLogEntry, Document } from '../types/interfaces.js';
4
- import { calculateDocumentChecksum, verifyChecksum } from '../utils/checksum.js';
5
-
6
- /**
7
- * File storage adapter options
8
- */
9
- export interface IFileStorageAdapterOptions {
10
- /** Enable checksum verification for data integrity */
11
- enableChecksums?: boolean;
12
- /** Throw error on checksum mismatch (default: false, just log warning) */
13
- strictChecksums?: boolean;
14
- }
15
-
16
- /**
17
- * File-based storage adapter for SmartDB
18
- * Stores data in JSON files on disk for persistence
19
- */
20
- export class FileStorageAdapter implements IStorageAdapter {
21
- private basePath: string;
22
- private opLogCounter = 0;
23
- private initialized = false;
24
- private fs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
25
- private enableChecksums: boolean;
26
- private strictChecksums: boolean;
27
-
28
- constructor(basePath: string, options?: IFileStorageAdapterOptions) {
29
- this.basePath = basePath;
30
- this.enableChecksums = options?.enableChecksums ?? false;
31
- this.strictChecksums = options?.strictChecksums ?? false;
32
- }
33
-
34
- // ============================================================================
35
- // Helper Methods
36
- // ============================================================================
37
-
38
- private getDbPath(dbName: string): string {
39
- return plugins.smartpath.join(this.basePath, dbName);
40
- }
41
-
42
- private getCollectionPath(dbName: string, collName: string): string {
43
- return plugins.smartpath.join(this.basePath, dbName, `${collName}.json`);
44
- }
45
-
46
- private getIndexPath(dbName: string, collName: string): string {
47
- return plugins.smartpath.join(this.basePath, dbName, `${collName}.indexes.json`);
48
- }
49
-
50
- private getOpLogPath(): string {
51
- return plugins.smartpath.join(this.basePath, '_oplog.json');
52
- }
53
-
54
- private getMetaPath(): string {
55
- return plugins.smartpath.join(this.basePath, '_meta.json');
56
- }
57
-
58
- private async readJsonFile<T>(filePath: string, defaultValue: T): Promise<T> {
59
- try {
60
- const exists = await this.fs.file(filePath).exists();
61
- if (!exists) return defaultValue;
62
- const content = await this.fs.file(filePath).encoding('utf8').read();
63
- return JSON.parse(content as string);
64
- } catch {
65
- return defaultValue;
66
- }
67
- }
68
-
69
- private async writeJsonFile(filePath: string, data: any): Promise<void> {
70
- const dir = filePath.substring(0, filePath.lastIndexOf('/'));
71
- await this.fs.directory(dir).recursive().create();
72
- await this.fs.file(filePath).encoding('utf8').write(JSON.stringify(data, null, 2));
73
- }
74
-
75
- private restoreObjectIds(doc: any): IStoredDocument {
76
- if (doc._id) {
77
- if (typeof doc._id === 'string') {
78
- doc._id = new plugins.bson.ObjectId(doc._id);
79
- } else if (typeof doc._id === 'object' && doc._id.$oid) {
80
- doc._id = new plugins.bson.ObjectId(doc._id.$oid);
81
- }
82
- }
83
- return doc;
84
- }
85
-
86
- /**
87
- * Verify document checksum and handle errors
88
- */
89
- private verifyDocumentChecksum(doc: any): boolean {
90
- if (!this.enableChecksums || !doc._checksum) {
91
- return true;
92
- }
93
-
94
- const isValid = verifyChecksum(doc);
95
- if (!isValid) {
96
- const errorMsg = `Checksum mismatch for document ${doc._id}`;
97
- if (this.strictChecksums) {
98
- throw new Error(errorMsg);
99
- } else {
100
- console.warn(`WARNING: ${errorMsg}`);
101
- }
102
- }
103
- return isValid;
104
- }
105
-
106
- /**
107
- * Add checksum to document before storing
108
- */
109
- private prepareDocumentForStorage(doc: any): any {
110
- if (!this.enableChecksums) {
111
- return doc;
112
- }
113
- const checksum = calculateDocumentChecksum(doc);
114
- return { ...doc, _checksum: checksum };
115
- }
116
-
117
- /**
118
- * Remove internal checksum field before returning to user
119
- */
120
- private cleanDocumentForReturn(doc: any): IStoredDocument {
121
- const { _checksum, ...cleanDoc } = doc;
122
- return this.restoreObjectIds(cleanDoc);
123
- }
124
-
125
- // ============================================================================
126
- // Initialization
127
- // ============================================================================
128
-
129
- async initialize(): Promise<void> {
130
- if (this.initialized) return;
131
-
132
- await this.fs.directory(this.basePath).recursive().create();
133
-
134
- // Load metadata
135
- const meta = await this.readJsonFile(this.getMetaPath(), { opLogCounter: 0 });
136
- this.opLogCounter = meta.opLogCounter || 0;
137
-
138
- this.initialized = true;
139
- }
140
-
141
- async close(): Promise<void> {
142
- // Save metadata
143
- await this.writeJsonFile(this.getMetaPath(), { opLogCounter: this.opLogCounter });
144
- this.initialized = false;
145
- }
146
-
147
- // ============================================================================
148
- // Database Operations
149
- // ============================================================================
150
-
151
- async listDatabases(): Promise<string[]> {
152
- await this.initialize();
153
- try {
154
- const entries = await this.fs.directory(this.basePath).list();
155
- return entries
156
- .filter(entry => entry.isDirectory && !entry.name.startsWith('_'))
157
- .map(entry => entry.name);
158
- } catch {
159
- return [];
160
- }
161
- }
162
-
163
- async createDatabase(dbName: string): Promise<void> {
164
- await this.initialize();
165
- const dbPath = this.getDbPath(dbName);
166
- await this.fs.directory(dbPath).recursive().create();
167
- }
168
-
169
- async dropDatabase(dbName: string): Promise<boolean> {
170
- await this.initialize();
171
- const dbPath = this.getDbPath(dbName);
172
- try {
173
- const exists = await this.fs.directory(dbPath).exists();
174
- if (exists) {
175
- await this.fs.directory(dbPath).recursive().delete();
176
- return true;
177
- }
178
- return false;
179
- } catch {
180
- return false;
181
- }
182
- }
183
-
184
- async databaseExists(dbName: string): Promise<boolean> {
185
- await this.initialize();
186
- const dbPath = this.getDbPath(dbName);
187
- return this.fs.directory(dbPath).exists();
188
- }
189
-
190
- // ============================================================================
191
- // Collection Operations
192
- // ============================================================================
193
-
194
- async listCollections(dbName: string): Promise<string[]> {
195
- await this.initialize();
196
- const dbPath = this.getDbPath(dbName);
197
- try {
198
- const entries = await this.fs.directory(dbPath).list();
199
- return entries
200
- .filter(entry => entry.isFile && entry.name.endsWith('.json') && !entry.name.endsWith('.indexes.json'))
201
- .map(entry => entry.name.replace('.json', ''));
202
- } catch {
203
- return [];
204
- }
205
- }
206
-
207
- async createCollection(dbName: string, collName: string): Promise<void> {
208
- await this.createDatabase(dbName);
209
- const collPath = this.getCollectionPath(dbName, collName);
210
- const exists = await this.fs.file(collPath).exists();
211
- if (!exists) {
212
- await this.writeJsonFile(collPath, []);
213
- // Create default _id index
214
- await this.writeJsonFile(this.getIndexPath(dbName, collName), [
215
- { name: '_id_', key: { _id: 1 }, unique: true }
216
- ]);
217
- }
218
- }
219
-
220
- async dropCollection(dbName: string, collName: string): Promise<boolean> {
221
- await this.initialize();
222
- const collPath = this.getCollectionPath(dbName, collName);
223
- const indexPath = this.getIndexPath(dbName, collName);
224
- try {
225
- const exists = await this.fs.file(collPath).exists();
226
- if (exists) {
227
- await this.fs.file(collPath).delete();
228
- try {
229
- await this.fs.file(indexPath).delete();
230
- } catch {}
231
- return true;
232
- }
233
- return false;
234
- } catch {
235
- return false;
236
- }
237
- }
238
-
239
- async collectionExists(dbName: string, collName: string): Promise<boolean> {
240
- await this.initialize();
241
- const collPath = this.getCollectionPath(dbName, collName);
242
- return this.fs.file(collPath).exists();
243
- }
244
-
245
- async renameCollection(dbName: string, oldName: string, newName: string): Promise<void> {
246
- await this.initialize();
247
- const oldPath = this.getCollectionPath(dbName, oldName);
248
- const newPath = this.getCollectionPath(dbName, newName);
249
- const oldIndexPath = this.getIndexPath(dbName, oldName);
250
- const newIndexPath = this.getIndexPath(dbName, newName);
251
-
252
- const exists = await this.fs.file(oldPath).exists();
253
- if (!exists) {
254
- throw new Error(`Collection ${oldName} not found`);
255
- }
256
-
257
- // Read, write to new, delete old
258
- const docs = await this.readJsonFile<any[]>(oldPath, []);
259
- await this.writeJsonFile(newPath, docs);
260
- await this.fs.file(oldPath).delete();
261
-
262
- // Handle indexes
263
- const indexes = await this.readJsonFile<any[]>(oldIndexPath, []);
264
- await this.writeJsonFile(newIndexPath, indexes);
265
- try {
266
- await this.fs.file(oldIndexPath).delete();
267
- } catch {}
268
- }
269
-
270
- // ============================================================================
271
- // Document Operations
272
- // ============================================================================
273
-
274
- async insertOne(dbName: string, collName: string, doc: Document): Promise<IStoredDocument> {
275
- await this.createCollection(dbName, collName);
276
- const collPath = this.getCollectionPath(dbName, collName);
277
- const docs = await this.readJsonFile<any[]>(collPath, []);
278
-
279
- const storedDoc: IStoredDocument = {
280
- ...doc,
281
- _id: doc._id ? (doc._id instanceof plugins.bson.ObjectId ? doc._id : new plugins.bson.ObjectId(doc._id)) : new plugins.bson.ObjectId(),
282
- };
283
-
284
- // Check for duplicate
285
- const idStr = storedDoc._id.toHexString();
286
- if (docs.some(d => d._id === idStr || (d._id && d._id.toString() === idStr))) {
287
- throw new Error(`Duplicate key error: _id ${idStr}`);
288
- }
289
-
290
- // Add checksum if enabled
291
- const docToStore = this.prepareDocumentForStorage(storedDoc);
292
- docs.push(docToStore);
293
- await this.writeJsonFile(collPath, docs);
294
- return storedDoc;
295
- }
296
-
297
- async insertMany(dbName: string, collName: string, docsToInsert: Document[]): Promise<IStoredDocument[]> {
298
- await this.createCollection(dbName, collName);
299
- const collPath = this.getCollectionPath(dbName, collName);
300
- const docs = await this.readJsonFile<any[]>(collPath, []);
301
-
302
- const results: IStoredDocument[] = [];
303
- const existingIds = new Set(docs.map(d => d._id?.toString?.() || d._id));
304
-
305
- for (const doc of docsToInsert) {
306
- const storedDoc: IStoredDocument = {
307
- ...doc,
308
- _id: doc._id ? (doc._id instanceof plugins.bson.ObjectId ? doc._id : new plugins.bson.ObjectId(doc._id)) : new plugins.bson.ObjectId(),
309
- };
310
-
311
- const idStr = storedDoc._id.toHexString();
312
- if (existingIds.has(idStr)) {
313
- throw new Error(`Duplicate key error: _id ${idStr}`);
314
- }
315
-
316
- existingIds.add(idStr);
317
- // Add checksum if enabled
318
- const docToStore = this.prepareDocumentForStorage(storedDoc);
319
- docs.push(docToStore);
320
- results.push(storedDoc);
321
- }
322
-
323
- await this.writeJsonFile(collPath, docs);
324
- return results;
325
- }
326
-
327
- async findAll(dbName: string, collName: string): Promise<IStoredDocument[]> {
328
- await this.createCollection(dbName, collName);
329
- const collPath = this.getCollectionPath(dbName, collName);
330
- const docs = await this.readJsonFile<any[]>(collPath, []);
331
- return docs.map(doc => {
332
- // Verify checksum if enabled
333
- this.verifyDocumentChecksum(doc);
334
- // Clean and return document without internal checksum field
335
- return this.cleanDocumentForReturn(doc);
336
- });
337
- }
338
-
339
- async findByIds(dbName: string, collName: string, ids: Set<string>): Promise<IStoredDocument[]> {
340
- await this.createCollection(dbName, collName);
341
- const collPath = this.getCollectionPath(dbName, collName);
342
- const docs = await this.readJsonFile<any[]>(collPath, []);
343
- const results: IStoredDocument[] = [];
344
- for (const doc of docs) {
345
- // Verify checksum if enabled
346
- this.verifyDocumentChecksum(doc);
347
- // Clean and restore document
348
- const cleaned = this.cleanDocumentForReturn(doc);
349
- if (ids.has(cleaned._id.toHexString())) {
350
- results.push(cleaned);
351
- }
352
- }
353
- return results;
354
- }
355
-
356
- async findById(dbName: string, collName: string, id: plugins.bson.ObjectId): Promise<IStoredDocument | null> {
357
- // Use findAll which already handles checksum verification
358
- const docs = await this.findAll(dbName, collName);
359
- const idStr = id.toHexString();
360
- return docs.find(d => d._id.toHexString() === idStr) || null;
361
- }
362
-
363
- async updateById(dbName: string, collName: string, id: plugins.bson.ObjectId, doc: IStoredDocument): Promise<boolean> {
364
- const collPath = this.getCollectionPath(dbName, collName);
365
- const docs = await this.readJsonFile<any[]>(collPath, []);
366
- const idStr = id.toHexString();
367
-
368
- const idx = docs.findIndex(d => {
369
- const docId = d._id?.toHexString?.() || d._id?.toString?.() || d._id;
370
- return docId === idStr;
371
- });
372
-
373
- if (idx === -1) return false;
374
-
375
- // Add checksum if enabled
376
- const docToStore = this.prepareDocumentForStorage(doc);
377
- docs[idx] = docToStore;
378
- await this.writeJsonFile(collPath, docs);
379
- return true;
380
- }
381
-
382
- async deleteById(dbName: string, collName: string, id: plugins.bson.ObjectId): Promise<boolean> {
383
- const collPath = this.getCollectionPath(dbName, collName);
384
- const docs = await this.readJsonFile<any[]>(collPath, []);
385
- const idStr = id.toHexString();
386
-
387
- const idx = docs.findIndex(d => {
388
- const docId = d._id?.toHexString?.() || d._id?.toString?.() || d._id;
389
- return docId === idStr;
390
- });
391
-
392
- if (idx === -1) return false;
393
-
394
- docs.splice(idx, 1);
395
- await this.writeJsonFile(collPath, docs);
396
- return true;
397
- }
398
-
399
- async deleteByIds(dbName: string, collName: string, ids: plugins.bson.ObjectId[]): Promise<number> {
400
- const collPath = this.getCollectionPath(dbName, collName);
401
- const docs = await this.readJsonFile<any[]>(collPath, []);
402
- const idStrs = new Set(ids.map(id => id.toHexString()));
403
-
404
- const originalLength = docs.length;
405
- const filtered = docs.filter(d => {
406
- const docId = d._id?.toHexString?.() || d._id?.toString?.() || d._id;
407
- return !idStrs.has(docId);
408
- });
409
-
410
- await this.writeJsonFile(collPath, filtered);
411
- return originalLength - filtered.length;
412
- }
413
-
414
- async count(dbName: string, collName: string): Promise<number> {
415
- const collPath = this.getCollectionPath(dbName, collName);
416
- const docs = await this.readJsonFile<any[]>(collPath, []);
417
- return docs.length;
418
- }
419
-
420
- // ============================================================================
421
- // Index Operations
422
- // ============================================================================
423
-
424
- async saveIndex(
425
- dbName: string,
426
- collName: string,
427
- indexName: string,
428
- indexSpec: { key: Record<string, any>; unique?: boolean; sparse?: boolean; expireAfterSeconds?: number }
429
- ): Promise<void> {
430
- await this.createCollection(dbName, collName);
431
- const indexPath = this.getIndexPath(dbName, collName);
432
- const indexes = await this.readJsonFile<any[]>(indexPath, [
433
- { name: '_id_', key: { _id: 1 }, unique: true }
434
- ]);
435
-
436
- const existingIdx = indexes.findIndex(i => i.name === indexName);
437
- if (existingIdx >= 0) {
438
- indexes[existingIdx] = { name: indexName, ...indexSpec };
439
- } else {
440
- indexes.push({ name: indexName, ...indexSpec });
441
- }
442
-
443
- await this.writeJsonFile(indexPath, indexes);
444
- }
445
-
446
- async getIndexes(dbName: string, collName: string): Promise<Array<{
447
- name: string;
448
- key: Record<string, any>;
449
- unique?: boolean;
450
- sparse?: boolean;
451
- expireAfterSeconds?: number;
452
- }>> {
453
- const indexPath = this.getIndexPath(dbName, collName);
454
- return this.readJsonFile(indexPath, [{ name: '_id_', key: { _id: 1 }, unique: true }]);
455
- }
456
-
457
- async dropIndex(dbName: string, collName: string, indexName: string): Promise<boolean> {
458
- if (indexName === '_id_') {
459
- throw new Error('Cannot drop _id index');
460
- }
461
-
462
- const indexPath = this.getIndexPath(dbName, collName);
463
- const indexes = await this.readJsonFile<any[]>(indexPath, []);
464
-
465
- const idx = indexes.findIndex(i => i.name === indexName);
466
- if (idx >= 0) {
467
- indexes.splice(idx, 1);
468
- await this.writeJsonFile(indexPath, indexes);
469
- return true;
470
- }
471
- return false;
472
- }
473
-
474
- // ============================================================================
475
- // OpLog Operations
476
- // ============================================================================
477
-
478
- async appendOpLog(entry: IOpLogEntry): Promise<void> {
479
- const opLogPath = this.getOpLogPath();
480
- const opLog = await this.readJsonFile<IOpLogEntry[]>(opLogPath, []);
481
- opLog.push(entry);
482
-
483
- // Trim oplog if it gets too large
484
- if (opLog.length > 10000) {
485
- opLog.splice(0, opLog.length - 10000);
486
- }
487
-
488
- await this.writeJsonFile(opLogPath, opLog);
489
- }
490
-
491
- async getOpLogAfter(ts: plugins.bson.Timestamp, limit: number = 1000): Promise<IOpLogEntry[]> {
492
- const opLogPath = this.getOpLogPath();
493
- const opLog = await this.readJsonFile<any[]>(opLogPath, []);
494
- const tsValue = ts.toNumber();
495
-
496
- const entries = opLog.filter(e => {
497
- const entryTs = e.ts.toNumber ? e.ts.toNumber() : (e.ts.t * 4294967296 + e.ts.i);
498
- return entryTs > tsValue;
499
- });
500
-
501
- return entries.slice(0, limit);
502
- }
503
-
504
- async getLatestOpLogTimestamp(): Promise<plugins.bson.Timestamp | null> {
505
- const opLogPath = this.getOpLogPath();
506
- const opLog = await this.readJsonFile<any[]>(opLogPath, []);
507
- if (opLog.length === 0) return null;
508
-
509
- const last = opLog[opLog.length - 1];
510
- if (last.ts instanceof plugins.bson.Timestamp) {
511
- return last.ts;
512
- }
513
- return new plugins.bson.Timestamp({ t: last.ts.t, i: last.ts.i });
514
- }
515
-
516
- generateTimestamp(): plugins.bson.Timestamp {
517
- this.opLogCounter++;
518
- return new plugins.bson.Timestamp({ t: Math.floor(Date.now() / 1000), i: this.opLogCounter });
519
- }
520
-
521
- // ============================================================================
522
- // Transaction Support
523
- // ============================================================================
524
-
525
- async createSnapshot(dbName: string, collName: string): Promise<IStoredDocument[]> {
526
- const docs = await this.findAll(dbName, collName);
527
- return docs.map(doc => JSON.parse(JSON.stringify(doc)));
528
- }
529
-
530
- async hasConflicts(
531
- dbName: string,
532
- collName: string,
533
- ids: plugins.bson.ObjectId[],
534
- snapshotTime: plugins.bson.Timestamp
535
- ): Promise<boolean> {
536
- const opLogPath = this.getOpLogPath();
537
- const opLog = await this.readJsonFile<any[]>(opLogPath, []);
538
- const ns = `${dbName}.${collName}`;
539
- const snapshotTs = snapshotTime.toNumber();
540
- const modifiedIds = new Set<string>();
541
-
542
- for (const entry of opLog) {
543
- const entryTs = entry.ts.toNumber ? entry.ts.toNumber() : (entry.ts.t * 4294967296 + entry.ts.i);
544
- if (entryTs > snapshotTs && entry.ns === ns) {
545
- if (entry.o._id) {
546
- modifiedIds.add(entry.o._id.toString());
547
- }
548
- if (entry.o2?._id) {
549
- modifiedIds.add(entry.o2._id.toString());
550
- }
551
- }
552
- }
553
-
554
- for (const id of ids) {
555
- if (modifiedIds.has(id.toString())) {
556
- return true;
557
- }
558
- }
559
-
560
- return false;
561
- }
562
- }