@push.rocks/smartdb 1.0.1 → 2.1.1

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 (126) hide show
  1. package/.smartconfig.json +18 -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/index.d.ts +1 -0
  6. package/dist_ts/ts_local/classes.localsmartdb.d.ts +5 -5
  7. package/dist_ts/ts_local/classes.localsmartdb.js +7 -9
  8. package/dist_ts/ts_local/plugins.d.ts +1 -2
  9. package/dist_ts/ts_local/plugins.js +3 -3
  10. package/dist_ts/ts_smartdb/index.d.ts +2 -24
  11. package/dist_ts/ts_smartdb/index.js +4 -29
  12. package/dist_ts/ts_smartdb/plugins.d.ts +2 -10
  13. package/dist_ts/ts_smartdb/plugins.js +3 -13
  14. package/dist_ts/ts_smartdb/rust-db-bridge.d.ts +122 -0
  15. package/dist_ts/ts_smartdb/rust-db-bridge.js +113 -0
  16. package/dist_ts/ts_smartdb/server/SmartdbServer.d.ts +39 -37
  17. package/dist_ts/ts_smartdb/server/SmartdbServer.js +87 -206
  18. package/dist_ts/ts_smartdb/server/index.d.ts +0 -4
  19. package/dist_ts/ts_smartdb/server/index.js +1 -5
  20. package/dist_ts_debugserver/bundled.d.ts +4 -0
  21. package/dist_ts_debugserver/bundled.js +12 -0
  22. package/dist_ts_debugserver/classes.debugserver.d.ts +36 -0
  23. package/dist_ts_debugserver/classes.debugserver.js +95 -0
  24. package/dist_ts_debugserver/index.d.ts +2 -0
  25. package/dist_ts_debugserver/index.js +2 -0
  26. package/dist_ts_debugserver/plugins.d.ts +2 -0
  27. package/dist_ts_debugserver/plugins.js +3 -0
  28. package/dist_ts_debugui/index.d.ts +2 -0
  29. package/dist_ts_debugui/index.js +2 -0
  30. package/dist_ts_debugui/plugins.d.ts +1 -0
  31. package/dist_ts_debugui/plugins.js +2 -0
  32. package/dist_ts_debugui/smartdb-debugui.d.ts +62 -0
  33. package/dist_ts_debugui/smartdb-debugui.js +1132 -0
  34. package/license +3 -1
  35. package/package.json +14 -13
  36. package/readme.md +209 -177
  37. package/ts/00_commitinfo_data.ts +2 -2
  38. package/ts/index.ts +11 -0
  39. package/ts/ts_local/classes.localsmartdb.ts +5 -6
  40. package/ts/ts_local/plugins.ts +1 -3
  41. package/ts/ts_smartdb/index.ts +14 -41
  42. package/ts/ts_smartdb/plugins.ts +2 -15
  43. package/ts/ts_smartdb/rust-db-bridge.ts +262 -0
  44. package/ts/ts_smartdb/server/SmartdbServer.ts +115 -246
  45. package/ts/ts_smartdb/server/index.ts +0 -7
  46. package/dist_ts/ts_smartdb/engine/AggregationEngine.d.ts +0 -66
  47. package/dist_ts/ts_smartdb/engine/AggregationEngine.js +0 -189
  48. package/dist_ts/ts_smartdb/engine/IndexEngine.d.ts +0 -97
  49. package/dist_ts/ts_smartdb/engine/IndexEngine.js +0 -678
  50. package/dist_ts/ts_smartdb/engine/QueryEngine.d.ts +0 -54
  51. package/dist_ts/ts_smartdb/engine/QueryEngine.js +0 -271
  52. package/dist_ts/ts_smartdb/engine/QueryPlanner.d.ts +0 -64
  53. package/dist_ts/ts_smartdb/engine/QueryPlanner.js +0 -308
  54. package/dist_ts/ts_smartdb/engine/SessionEngine.d.ts +0 -117
  55. package/dist_ts/ts_smartdb/engine/SessionEngine.js +0 -232
  56. package/dist_ts/ts_smartdb/engine/TransactionEngine.d.ts +0 -85
  57. package/dist_ts/ts_smartdb/engine/TransactionEngine.js +0 -287
  58. package/dist_ts/ts_smartdb/engine/UpdateEngine.d.ts +0 -47
  59. package/dist_ts/ts_smartdb/engine/UpdateEngine.js +0 -461
  60. package/dist_ts/ts_smartdb/errors/SmartdbErrors.d.ts +0 -100
  61. package/dist_ts/ts_smartdb/errors/SmartdbErrors.js +0 -155
  62. package/dist_ts/ts_smartdb/server/CommandRouter.d.ts +0 -87
  63. package/dist_ts/ts_smartdb/server/CommandRouter.js +0 -222
  64. package/dist_ts/ts_smartdb/server/WireProtocol.d.ts +0 -117
  65. package/dist_ts/ts_smartdb/server/WireProtocol.js +0 -298
  66. package/dist_ts/ts_smartdb/server/handlers/AdminHandler.d.ts +0 -100
  67. package/dist_ts/ts_smartdb/server/handlers/AdminHandler.js +0 -668
  68. package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.d.ts +0 -31
  69. package/dist_ts/ts_smartdb/server/handlers/AggregateHandler.js +0 -277
  70. package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.d.ts +0 -8
  71. package/dist_ts/ts_smartdb/server/handlers/DeleteHandler.js +0 -95
  72. package/dist_ts/ts_smartdb/server/handlers/FindHandler.d.ts +0 -31
  73. package/dist_ts/ts_smartdb/server/handlers/FindHandler.js +0 -291
  74. package/dist_ts/ts_smartdb/server/handlers/HelloHandler.d.ts +0 -11
  75. package/dist_ts/ts_smartdb/server/handlers/HelloHandler.js +0 -62
  76. package/dist_ts/ts_smartdb/server/handlers/IndexHandler.d.ts +0 -20
  77. package/dist_ts/ts_smartdb/server/handlers/IndexHandler.js +0 -183
  78. package/dist_ts/ts_smartdb/server/handlers/InsertHandler.d.ts +0 -8
  79. package/dist_ts/ts_smartdb/server/handlers/InsertHandler.js +0 -79
  80. package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.d.ts +0 -24
  81. package/dist_ts/ts_smartdb/server/handlers/UpdateHandler.js +0 -296
  82. package/dist_ts/ts_smartdb/server/handlers/index.d.ts +0 -8
  83. package/dist_ts/ts_smartdb/server/handlers/index.js +0 -10
  84. package/dist_ts/ts_smartdb/storage/FileStorageAdapter.d.ts +0 -85
  85. package/dist_ts/ts_smartdb/storage/FileStorageAdapter.js +0 -465
  86. package/dist_ts/ts_smartdb/storage/IStorageAdapter.d.ts +0 -145
  87. package/dist_ts/ts_smartdb/storage/IStorageAdapter.js +0 -2
  88. package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.d.ts +0 -67
  89. package/dist_ts/ts_smartdb/storage/MemoryStorageAdapter.js +0 -378
  90. package/dist_ts/ts_smartdb/storage/OpLog.d.ts +0 -93
  91. package/dist_ts/ts_smartdb/storage/OpLog.js +0 -221
  92. package/dist_ts/ts_smartdb/storage/WAL.d.ts +0 -117
  93. package/dist_ts/ts_smartdb/storage/WAL.js +0 -286
  94. package/dist_ts/ts_smartdb/types/interfaces.d.ts +0 -363
  95. package/dist_ts/ts_smartdb/types/interfaces.js +0 -2
  96. package/dist_ts/ts_smartdb/utils/checksum.d.ts +0 -30
  97. package/dist_ts/ts_smartdb/utils/checksum.js +0 -77
  98. package/dist_ts/ts_smartdb/utils/index.d.ts +0 -1
  99. package/dist_ts/ts_smartdb/utils/index.js +0 -2
  100. package/ts/ts_smartdb/engine/AggregationEngine.ts +0 -283
  101. package/ts/ts_smartdb/engine/IndexEngine.ts +0 -798
  102. package/ts/ts_smartdb/engine/QueryEngine.ts +0 -301
  103. package/ts/ts_smartdb/engine/QueryPlanner.ts +0 -393
  104. package/ts/ts_smartdb/engine/SessionEngine.ts +0 -292
  105. package/ts/ts_smartdb/engine/TransactionEngine.ts +0 -351
  106. package/ts/ts_smartdb/engine/UpdateEngine.ts +0 -506
  107. package/ts/ts_smartdb/errors/SmartdbErrors.ts +0 -181
  108. package/ts/ts_smartdb/server/CommandRouter.ts +0 -289
  109. package/ts/ts_smartdb/server/WireProtocol.ts +0 -416
  110. package/ts/ts_smartdb/server/handlers/AdminHandler.ts +0 -719
  111. package/ts/ts_smartdb/server/handlers/AggregateHandler.ts +0 -342
  112. package/ts/ts_smartdb/server/handlers/DeleteHandler.ts +0 -115
  113. package/ts/ts_smartdb/server/handlers/FindHandler.ts +0 -330
  114. package/ts/ts_smartdb/server/handlers/HelloHandler.ts +0 -78
  115. package/ts/ts_smartdb/server/handlers/IndexHandler.ts +0 -207
  116. package/ts/ts_smartdb/server/handlers/InsertHandler.ts +0 -97
  117. package/ts/ts_smartdb/server/handlers/UpdateHandler.ts +0 -344
  118. package/ts/ts_smartdb/server/handlers/index.ts +0 -10
  119. package/ts/ts_smartdb/storage/FileStorageAdapter.ts +0 -562
  120. package/ts/ts_smartdb/storage/IStorageAdapter.ts +0 -208
  121. package/ts/ts_smartdb/storage/MemoryStorageAdapter.ts +0 -455
  122. package/ts/ts_smartdb/storage/OpLog.ts +0 -282
  123. package/ts/ts_smartdb/storage/WAL.ts +0 -375
  124. package/ts/ts_smartdb/types/interfaces.ts +0 -433
  125. package/ts/ts_smartdb/utils/checksum.ts +0 -88
  126. 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
- }