@push.rocks/smartmongo 3.0.0 β 4.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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/tsmdb/engine/IndexEngine.d.ts +23 -3
- package/dist_ts/tsmdb/engine/IndexEngine.js +357 -55
- package/dist_ts/tsmdb/engine/QueryPlanner.d.ts +64 -0
- package/dist_ts/tsmdb/engine/QueryPlanner.js +308 -0
- package/dist_ts/tsmdb/engine/SessionEngine.d.ts +117 -0
- package/dist_ts/tsmdb/engine/SessionEngine.js +232 -0
- package/dist_ts/tsmdb/index.d.ts +7 -0
- package/dist_ts/tsmdb/index.js +6 -1
- package/dist_ts/tsmdb/server/CommandRouter.d.ts +36 -0
- package/dist_ts/tsmdb/server/CommandRouter.js +91 -1
- package/dist_ts/tsmdb/server/TsmdbServer.js +3 -1
- package/dist_ts/tsmdb/server/handlers/AdminHandler.js +106 -6
- package/dist_ts/tsmdb/server/handlers/DeleteHandler.js +15 -3
- package/dist_ts/tsmdb/server/handlers/FindHandler.js +44 -14
- package/dist_ts/tsmdb/server/handlers/InsertHandler.js +4 -1
- package/dist_ts/tsmdb/server/handlers/UpdateHandler.js +31 -5
- package/dist_ts/tsmdb/storage/FileStorageAdapter.d.ts +25 -1
- package/dist_ts/tsmdb/storage/FileStorageAdapter.js +75 -6
- package/dist_ts/tsmdb/storage/IStorageAdapter.d.ts +5 -0
- package/dist_ts/tsmdb/storage/MemoryStorageAdapter.d.ts +1 -0
- package/dist_ts/tsmdb/storage/MemoryStorageAdapter.js +12 -1
- package/dist_ts/tsmdb/storage/WAL.d.ts +117 -0
- package/dist_ts/tsmdb/storage/WAL.js +286 -0
- package/dist_ts/tsmdb/utils/checksum.d.ts +30 -0
- package/dist_ts/tsmdb/utils/checksum.js +77 -0
- package/dist_ts/tsmdb/utils/index.d.ts +1 -0
- package/dist_ts/tsmdb/utils/index.js +2 -0
- package/package.json +2 -2
- package/readme.md +140 -17
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/tsmdb/engine/IndexEngine.ts +375 -56
- package/ts/tsmdb/engine/QueryPlanner.ts +393 -0
- package/ts/tsmdb/engine/SessionEngine.ts +292 -0
- package/ts/tsmdb/index.ts +9 -0
- package/ts/tsmdb/server/CommandRouter.ts +109 -0
- package/ts/tsmdb/server/TsmdbServer.ts +3 -0
- package/ts/tsmdb/server/handlers/AdminHandler.ts +110 -5
- package/ts/tsmdb/server/handlers/DeleteHandler.ts +17 -2
- package/ts/tsmdb/server/handlers/FindHandler.ts +42 -13
- package/ts/tsmdb/server/handlers/InsertHandler.ts +6 -0
- package/ts/tsmdb/server/handlers/UpdateHandler.ts +33 -4
- package/ts/tsmdb/storage/FileStorageAdapter.ts +88 -5
- package/ts/tsmdb/storage/IStorageAdapter.ts +6 -0
- package/ts/tsmdb/storage/MemoryStorageAdapter.ts +12 -0
- package/ts/tsmdb/storage/WAL.ts +375 -0
- package/ts/tsmdb/utils/checksum.ts +88 -0
- package/ts/tsmdb/utils/index.ts +1 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import * as plugins from '../tsmdb.plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Write-Ahead Log (WAL) for durability and crash recovery
|
|
4
|
+
*
|
|
5
|
+
* The WAL ensures durability by writing operations to a log file before
|
|
6
|
+
* they are applied to the main storage. On crash recovery, uncommitted
|
|
7
|
+
* operations can be replayed to restore the database to a consistent state.
|
|
8
|
+
*/
|
|
9
|
+
export class WAL {
|
|
10
|
+
walPath;
|
|
11
|
+
currentLsn = 0;
|
|
12
|
+
lastCheckpointLsn = 0;
|
|
13
|
+
entries = [];
|
|
14
|
+
isInitialized = false;
|
|
15
|
+
fs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
|
|
16
|
+
// In-memory uncommitted entries per transaction
|
|
17
|
+
uncommittedTxns = new Map();
|
|
18
|
+
// Checkpoint interval (number of entries between checkpoints)
|
|
19
|
+
checkpointInterval = 1000;
|
|
20
|
+
constructor(walPath, options) {
|
|
21
|
+
this.walPath = walPath;
|
|
22
|
+
if (options?.checkpointInterval) {
|
|
23
|
+
this.checkpointInterval = options.checkpointInterval;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Initialize the WAL, loading existing entries and recovering if needed
|
|
28
|
+
*/
|
|
29
|
+
async initialize() {
|
|
30
|
+
if (this.isInitialized) {
|
|
31
|
+
return { recoveredEntries: [] };
|
|
32
|
+
}
|
|
33
|
+
// Ensure WAL directory exists
|
|
34
|
+
const walDir = this.walPath.substring(0, this.walPath.lastIndexOf('/'));
|
|
35
|
+
if (walDir) {
|
|
36
|
+
await this.fs.directory(walDir).recursive().create();
|
|
37
|
+
}
|
|
38
|
+
// Try to load existing WAL
|
|
39
|
+
const exists = await this.fs.file(this.walPath).exists();
|
|
40
|
+
if (exists) {
|
|
41
|
+
const content = await this.fs.file(this.walPath).encoding('utf8').read();
|
|
42
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
try {
|
|
45
|
+
const entry = JSON.parse(line);
|
|
46
|
+
// Verify checksum
|
|
47
|
+
if (this.verifyChecksum(entry)) {
|
|
48
|
+
this.entries.push(entry);
|
|
49
|
+
if (entry.lsn > this.currentLsn) {
|
|
50
|
+
this.currentLsn = entry.lsn;
|
|
51
|
+
}
|
|
52
|
+
if (entry.operation === 'checkpoint') {
|
|
53
|
+
this.lastCheckpointLsn = entry.lsn;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Skip corrupted entries
|
|
59
|
+
console.warn('Skipping corrupted WAL entry');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
this.isInitialized = true;
|
|
64
|
+
// Return entries after last checkpoint that need recovery
|
|
65
|
+
const recoveredEntries = this.entries.filter(e => e.lsn > this.lastCheckpointLsn &&
|
|
66
|
+
(e.operation === 'insert' || e.operation === 'update' || e.operation === 'delete'));
|
|
67
|
+
return { recoveredEntries };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Log an insert operation
|
|
71
|
+
*/
|
|
72
|
+
async logInsert(dbName, collName, doc, txnId) {
|
|
73
|
+
return this.appendEntry({
|
|
74
|
+
operation: 'insert',
|
|
75
|
+
dbName,
|
|
76
|
+
collName,
|
|
77
|
+
documentId: doc._id.toHexString(),
|
|
78
|
+
data: this.serializeDocument(doc),
|
|
79
|
+
txnId,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Log an update operation
|
|
84
|
+
*/
|
|
85
|
+
async logUpdate(dbName, collName, oldDoc, newDoc, txnId) {
|
|
86
|
+
return this.appendEntry({
|
|
87
|
+
operation: 'update',
|
|
88
|
+
dbName,
|
|
89
|
+
collName,
|
|
90
|
+
documentId: oldDoc._id.toHexString(),
|
|
91
|
+
data: this.serializeDocument(newDoc),
|
|
92
|
+
previousData: this.serializeDocument(oldDoc),
|
|
93
|
+
txnId,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Log a delete operation
|
|
98
|
+
*/
|
|
99
|
+
async logDelete(dbName, collName, doc, txnId) {
|
|
100
|
+
return this.appendEntry({
|
|
101
|
+
operation: 'delete',
|
|
102
|
+
dbName,
|
|
103
|
+
collName,
|
|
104
|
+
documentId: doc._id.toHexString(),
|
|
105
|
+
previousData: this.serializeDocument(doc),
|
|
106
|
+
txnId,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Log transaction begin
|
|
111
|
+
*/
|
|
112
|
+
async logBeginTransaction(txnId) {
|
|
113
|
+
this.uncommittedTxns.set(txnId, []);
|
|
114
|
+
return this.appendEntry({
|
|
115
|
+
operation: 'begin',
|
|
116
|
+
dbName: '',
|
|
117
|
+
collName: '',
|
|
118
|
+
documentId: '',
|
|
119
|
+
txnId,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Log transaction commit
|
|
124
|
+
*/
|
|
125
|
+
async logCommitTransaction(txnId) {
|
|
126
|
+
this.uncommittedTxns.delete(txnId);
|
|
127
|
+
return this.appendEntry({
|
|
128
|
+
operation: 'commit',
|
|
129
|
+
dbName: '',
|
|
130
|
+
collName: '',
|
|
131
|
+
documentId: '',
|
|
132
|
+
txnId,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Log transaction abort
|
|
137
|
+
*/
|
|
138
|
+
async logAbortTransaction(txnId) {
|
|
139
|
+
this.uncommittedTxns.delete(txnId);
|
|
140
|
+
return this.appendEntry({
|
|
141
|
+
operation: 'abort',
|
|
142
|
+
dbName: '',
|
|
143
|
+
collName: '',
|
|
144
|
+
documentId: '',
|
|
145
|
+
txnId,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get entries to roll back for an aborted transaction
|
|
150
|
+
*/
|
|
151
|
+
getTransactionEntries(txnId) {
|
|
152
|
+
return this.entries.filter(e => e.txnId === txnId);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Create a checkpoint - marks a consistent point in the log
|
|
156
|
+
*/
|
|
157
|
+
async checkpoint() {
|
|
158
|
+
const lsn = await this.appendEntry({
|
|
159
|
+
operation: 'checkpoint',
|
|
160
|
+
dbName: '',
|
|
161
|
+
collName: '',
|
|
162
|
+
documentId: '',
|
|
163
|
+
});
|
|
164
|
+
this.lastCheckpointLsn = lsn;
|
|
165
|
+
// Truncate old entries (keep only entries after checkpoint)
|
|
166
|
+
await this.truncate();
|
|
167
|
+
return lsn;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Truncate the WAL file, removing entries before the last checkpoint
|
|
171
|
+
*/
|
|
172
|
+
async truncate() {
|
|
173
|
+
// Keep entries after last checkpoint
|
|
174
|
+
const newEntries = this.entries.filter(e => e.lsn >= this.lastCheckpointLsn);
|
|
175
|
+
this.entries = newEntries;
|
|
176
|
+
// Rewrite the WAL file
|
|
177
|
+
const lines = this.entries.map(e => JSON.stringify(e)).join('\n');
|
|
178
|
+
await this.fs.file(this.walPath).encoding('utf8').write(lines);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get current LSN
|
|
182
|
+
*/
|
|
183
|
+
getCurrentLsn() {
|
|
184
|
+
return this.currentLsn;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get entries after a specific LSN (for recovery)
|
|
188
|
+
*/
|
|
189
|
+
getEntriesAfter(lsn) {
|
|
190
|
+
return this.entries.filter(e => e.lsn > lsn);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Close the WAL
|
|
194
|
+
*/
|
|
195
|
+
async close() {
|
|
196
|
+
if (this.isInitialized) {
|
|
197
|
+
// Final checkpoint before close
|
|
198
|
+
await this.checkpoint();
|
|
199
|
+
}
|
|
200
|
+
this.isInitialized = false;
|
|
201
|
+
}
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// Private Methods
|
|
204
|
+
// ============================================================================
|
|
205
|
+
async appendEntry(partial) {
|
|
206
|
+
await this.initialize();
|
|
207
|
+
this.currentLsn++;
|
|
208
|
+
const entry = {
|
|
209
|
+
...partial,
|
|
210
|
+
lsn: this.currentLsn,
|
|
211
|
+
timestamp: Date.now(),
|
|
212
|
+
checksum: 0, // Will be calculated
|
|
213
|
+
};
|
|
214
|
+
// Calculate checksum
|
|
215
|
+
entry.checksum = this.calculateChecksum(entry);
|
|
216
|
+
// Track in transaction if applicable
|
|
217
|
+
if (partial.txnId && this.uncommittedTxns.has(partial.txnId)) {
|
|
218
|
+
this.uncommittedTxns.get(partial.txnId).push(entry);
|
|
219
|
+
}
|
|
220
|
+
// Add to in-memory log
|
|
221
|
+
this.entries.push(entry);
|
|
222
|
+
// Append to file (append mode for durability)
|
|
223
|
+
await this.fs.file(this.walPath).encoding('utf8').append(JSON.stringify(entry) + '\n');
|
|
224
|
+
// Check if we need a checkpoint
|
|
225
|
+
if (this.entries.length - this.lastCheckpointLsn >= this.checkpointInterval) {
|
|
226
|
+
await this.checkpoint();
|
|
227
|
+
}
|
|
228
|
+
return entry.lsn;
|
|
229
|
+
}
|
|
230
|
+
serializeDocument(doc) {
|
|
231
|
+
// Serialize document to BSON and encode as base64
|
|
232
|
+
const bsonData = plugins.bson.serialize(doc);
|
|
233
|
+
return Buffer.from(bsonData).toString('base64');
|
|
234
|
+
}
|
|
235
|
+
deserializeDocument(data) {
|
|
236
|
+
// Decode base64 and deserialize from BSON
|
|
237
|
+
const buffer = Buffer.from(data, 'base64');
|
|
238
|
+
return plugins.bson.deserialize(buffer);
|
|
239
|
+
}
|
|
240
|
+
calculateChecksum(entry) {
|
|
241
|
+
// Simple CRC32-like checksum
|
|
242
|
+
const str = JSON.stringify({
|
|
243
|
+
lsn: entry.lsn,
|
|
244
|
+
timestamp: entry.timestamp,
|
|
245
|
+
operation: entry.operation,
|
|
246
|
+
dbName: entry.dbName,
|
|
247
|
+
collName: entry.collName,
|
|
248
|
+
documentId: entry.documentId,
|
|
249
|
+
data: entry.data,
|
|
250
|
+
previousData: entry.previousData,
|
|
251
|
+
txnId: entry.txnId,
|
|
252
|
+
});
|
|
253
|
+
let crc = 0xFFFFFFFF;
|
|
254
|
+
for (let i = 0; i < str.length; i++) {
|
|
255
|
+
crc ^= str.charCodeAt(i);
|
|
256
|
+
for (let j = 0; j < 8; j++) {
|
|
257
|
+
crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return (~crc) >>> 0;
|
|
261
|
+
}
|
|
262
|
+
verifyChecksum(entry) {
|
|
263
|
+
const savedChecksum = entry.checksum;
|
|
264
|
+
entry.checksum = 0;
|
|
265
|
+
const calculatedChecksum = this.calculateChecksum(entry);
|
|
266
|
+
entry.checksum = savedChecksum;
|
|
267
|
+
return calculatedChecksum === savedChecksum;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Recover document from WAL entry
|
|
271
|
+
*/
|
|
272
|
+
recoverDocument(entry) {
|
|
273
|
+
if (!entry.data)
|
|
274
|
+
return null;
|
|
275
|
+
return this.deserializeDocument(entry.data);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Recover previous document state from WAL entry (for rollback)
|
|
279
|
+
*/
|
|
280
|
+
recoverPreviousDocument(entry) {
|
|
281
|
+
if (!entry.previousData)
|
|
282
|
+
return null;
|
|
283
|
+
return this.deserializeDocument(entry.previousData);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV0FMLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvdHNtZGIvc3RvcmFnZS9XQUwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQTJDL0M7Ozs7OztHQU1HO0FBQ0gsTUFBTSxPQUFPLEdBQUc7SUFDTixPQUFPLENBQVM7SUFDaEIsVUFBVSxHQUFXLENBQUMsQ0FBQztJQUN2QixpQkFBaUIsR0FBVyxDQUFDLENBQUM7SUFDOUIsT0FBTyxHQUFnQixFQUFFLENBQUM7SUFDMUIsYUFBYSxHQUFZLEtBQUssQ0FBQztJQUMvQixFQUFFLEdBQUcsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsbUJBQW1CLEVBQUUsQ0FBQyxDQUFDO0lBRXBGLGdEQUFnRDtJQUN4QyxlQUFlLEdBQTZCLElBQUksR0FBRyxFQUFFLENBQUM7SUFFOUQsOERBQThEO0lBQ3RELGtCQUFrQixHQUFXLElBQUksQ0FBQztJQUUxQyxZQUFZLE9BQWUsRUFBRSxPQUF5QztRQUNwRSxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxDQUFDO1lBQ2hDLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxPQUFPLENBQUMsa0JBQWtCLENBQUM7UUFDdkQsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxVQUFVO1FBQ2QsSUFBSSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7WUFDdkIsT0FBTyxFQUFFLGdCQUFnQixFQUFFLEVBQUUsRUFBRSxDQUFDO1FBQ2xDLENBQUM7UUFFRCw4QkFBOEI7UUFDOUIsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLEdBQUcs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
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRC32 checksum utilities for data integrity
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Calculate CRC32 checksum for a string
|
|
6
|
+
*/
|
|
7
|
+
export declare function calculateCRC32(data: string): number;
|
|
8
|
+
/**
|
|
9
|
+
* Calculate CRC32 checksum for a Buffer
|
|
10
|
+
*/
|
|
11
|
+
export declare function calculateCRC32Buffer(data: Buffer): number;
|
|
12
|
+
/**
|
|
13
|
+
* Calculate checksum for a document (serialized as JSON)
|
|
14
|
+
*/
|
|
15
|
+
export declare function calculateDocumentChecksum(doc: Record<string, any>): number;
|
|
16
|
+
/**
|
|
17
|
+
* Add checksum to a document
|
|
18
|
+
*/
|
|
19
|
+
export declare function addChecksum<T extends Record<string, any>>(doc: T): T & {
|
|
20
|
+
_checksum: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Verify checksum of a document
|
|
24
|
+
* Returns true if checksum is valid or if document has no checksum
|
|
25
|
+
*/
|
|
26
|
+
export declare function verifyChecksum(doc: Record<string, any>): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Remove checksum from a document
|
|
29
|
+
*/
|
|
30
|
+
export declare function removeChecksum<T extends Record<string, any>>(doc: T): Omit<T, '_checksum'>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRC32 checksum utilities for data integrity
|
|
3
|
+
*/
|
|
4
|
+
// CRC32 lookup table
|
|
5
|
+
const CRC32_TABLE = [];
|
|
6
|
+
// Initialize the CRC32 table
|
|
7
|
+
function initCRC32Table() {
|
|
8
|
+
if (CRC32_TABLE.length > 0)
|
|
9
|
+
return;
|
|
10
|
+
for (let i = 0; i < 256; i++) {
|
|
11
|
+
let crc = i;
|
|
12
|
+
for (let j = 0; j < 8; j++) {
|
|
13
|
+
crc = (crc & 1) ? (0xEDB88320 ^ (crc >>> 1)) : (crc >>> 1);
|
|
14
|
+
}
|
|
15
|
+
CRC32_TABLE[i] = crc >>> 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Calculate CRC32 checksum for a string
|
|
20
|
+
*/
|
|
21
|
+
export function calculateCRC32(data) {
|
|
22
|
+
initCRC32Table();
|
|
23
|
+
let crc = 0xFFFFFFFF;
|
|
24
|
+
for (let i = 0; i < data.length; i++) {
|
|
25
|
+
const byte = data.charCodeAt(i) & 0xFF;
|
|
26
|
+
crc = CRC32_TABLE[(crc ^ byte) & 0xFF] ^ (crc >>> 8);
|
|
27
|
+
}
|
|
28
|
+
return (~crc) >>> 0;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Calculate CRC32 checksum for a Buffer
|
|
32
|
+
*/
|
|
33
|
+
export function calculateCRC32Buffer(data) {
|
|
34
|
+
initCRC32Table();
|
|
35
|
+
let crc = 0xFFFFFFFF;
|
|
36
|
+
for (let i = 0; i < data.length; i++) {
|
|
37
|
+
crc = CRC32_TABLE[(crc ^ data[i]) & 0xFF] ^ (crc >>> 8);
|
|
38
|
+
}
|
|
39
|
+
return (~crc) >>> 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Calculate checksum for a document (serialized as JSON)
|
|
43
|
+
*/
|
|
44
|
+
export function calculateDocumentChecksum(doc) {
|
|
45
|
+
// Exclude _checksum field from calculation
|
|
46
|
+
const { _checksum, ...docWithoutChecksum } = doc;
|
|
47
|
+
const json = JSON.stringify(docWithoutChecksum);
|
|
48
|
+
return calculateCRC32(json);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Add checksum to a document
|
|
52
|
+
*/
|
|
53
|
+
export function addChecksum(doc) {
|
|
54
|
+
const checksum = calculateDocumentChecksum(doc);
|
|
55
|
+
return { ...doc, _checksum: checksum };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Verify checksum of a document
|
|
59
|
+
* Returns true if checksum is valid or if document has no checksum
|
|
60
|
+
*/
|
|
61
|
+
export function verifyChecksum(doc) {
|
|
62
|
+
if (!('_checksum' in doc)) {
|
|
63
|
+
// No checksum to verify
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
const storedChecksum = doc._checksum;
|
|
67
|
+
const calculatedChecksum = calculateDocumentChecksum(doc);
|
|
68
|
+
return storedChecksum === calculatedChecksum;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove checksum from a document
|
|
72
|
+
*/
|
|
73
|
+
export function removeChecksum(doc) {
|
|
74
|
+
const { _checksum, ...docWithoutChecksum } = doc;
|
|
75
|
+
return docWithoutChecksum;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2tzdW0uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy90c21kYi91dGlscy9jaGVja3N1bS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILHFCQUFxQjtBQUNyQixNQUFNLFdBQVcsR0FBYSxFQUFFLENBQUM7QUFFakMsNkJBQTZCO0FBQzdCLFNBQVMsY0FBYztJQUNyQixJQUFJLFdBQVcsQ0FBQyxNQUFNLEdBQUcsQ0FBQztRQUFFLE9BQU87SUFFbkMsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEdBQUcsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQzdCLElBQUksR0FBRyxHQUFHLENBQUMsQ0FBQztRQUNaLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUMzQixHQUFHLEdBQUcsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQzdELENBQUM7UUFDRCxXQUFXLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxLQUFLLENBQUMsQ0FBQztJQUM3QixDQUFDO0FBQ0gsQ0FBQztBQUVEOztHQUVHO0FBQ0gsTUFBTSxVQUFVLGNBQWMsQ0FBQyxJQUFZO0lBQ3pDLGNBQWMsRUFBRSxDQUFDO0lBRWpCLElBQUksR0FBRyxHQUFHLFVBQVUsQ0FBQztJQUNyQixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ3JDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDO1FBQ3ZDLEdBQUcsR0FBRyxXQUFXLENBQUMsQ0FBQyxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDdkQsQ0FBQztJQUNELE9BQU8sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUN0QixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsb0JBQW9CLENBQUMsSUFBWTtJQUMvQyxjQUFjLEVBQUUsQ0FBQztJQUVqQixJQUFJLEdBQUcsR0FBRyxVQUFVLENBQUM7SUFDckIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUNyQyxHQUFHLEdBQUcsV0FBVyxDQUFDLENBQUMsR0FBRyxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDO0lBQzFELENBQUM7SUFDRCxPQUFPLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDdEIsQ0FBQztBQUVEOztHQUVHO0FBQ0gsTUFBTSxVQUFVLHlCQUF5QixDQUFDLEdBQXdCO0lBQ2hFLDJDQUEyQztJQUMzQyxNQUFNLEVBQUUsU0FBUyxFQUFFLEdBQUcsa0JBQWtCLEVBQUUsR0FBRyxHQUFHLENBQUM7SUFDakQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO0lBQ2hELE9BQU8sY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQzlCLENBQUM7QUFFRDs7R0FFRztBQUNILE1BQU0sVUFBVSxXQUFXLENBQWdDLEdBQU07SUFDL0QsTUFBTSxRQUFRLEdBQUcseUJBQXlCLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDaEQsT0FBTyxFQUFFLEdBQUcsR0FBRyxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsQ0FBQztBQUN6QyxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLGNBQWMsQ0FBQyxHQUF3QjtJQUNyRCxJQUFJLENBQUMsQ0FBQyxXQUFXLElBQUksR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUMxQix3QkFBd0I7UUFDeEIsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsTUFBTSxjQUFjLEdBQUcsR0FBRyxDQUFDLFNBQVMsQ0FBQztJQUNyQyxNQUFNLGtCQUFrQixHQUFHLHlCQUF5QixDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBRTFELE9BQU8sY0FBYyxLQUFLLGtCQUFrQixDQUFDO0FBQy9DLENBQUM7QUFFRDs7R0FFRztBQUNILE1BQU0sVUFBVSxjQUFjLENBQWdDLEdBQU07SUFDbEUsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLGtCQUFrQixFQUFFLEdBQUcsR0FBRyxDQUFDO0lBQ2pELE9BQU8sa0JBQTBDLENBQUM7QUFDcEQsQ0FBQyJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './checksum.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartmongo",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A module for creating and managing a local MongoDB instance for testing purposes.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"author": "Lossless GmbH",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"test": "(tstest test
|
|
12
|
+
"test": "(tstest test/. --verbose --logfile --timeout 60)",
|
|
13
13
|
"build": "(tsbuild --web)",
|
|
14
14
|
"buildDocs": "tsdoc"
|
|
15
15
|
},
|
package/readme.md
CHANGED
|
@@ -134,8 +134,8 @@ await server.start();
|
|
|
134
134
|
console.log(server.getConnectionUri()); // mongodb://127.0.0.1:27017
|
|
135
135
|
|
|
136
136
|
// Server properties
|
|
137
|
-
console.log(server.running);
|
|
138
|
-
console.log(server.getUptime());
|
|
137
|
+
console.log(server.running); // true
|
|
138
|
+
console.log(server.getUptime()); // seconds
|
|
139
139
|
console.log(server.getConnectionCount()); // active connections
|
|
140
140
|
|
|
141
141
|
await server.stop();
|
|
@@ -279,7 +279,7 @@ console.log(result.deletedCount); // 1
|
|
|
279
279
|
|
|
280
280
|
### Storage Adapters
|
|
281
281
|
|
|
282
|
-
TsmDB supports pluggable storage:
|
|
282
|
+
TsmDB supports pluggable storage with data integrity features:
|
|
283
283
|
|
|
284
284
|
```typescript
|
|
285
285
|
// In-memory (default) - fast, data lost on stop
|
|
@@ -292,13 +292,118 @@ const server = new tsmdb.TsmdbServer({
|
|
|
292
292
|
persistIntervalMs: 30000 // Save every 30 seconds
|
|
293
293
|
});
|
|
294
294
|
|
|
295
|
-
// File-based - persistent storage
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
// File-based - persistent storage with optional checksums
|
|
296
|
+
import { FileStorageAdapter } from '@push.rocks/smartmongo/tsmdb';
|
|
297
|
+
|
|
298
|
+
const adapter = new FileStorageAdapter('./data/tsmdb', {
|
|
299
|
+
enableChecksums: true, // CRC32 checksums for data integrity
|
|
300
|
+
strictChecksums: false // Log warnings vs throw on mismatch
|
|
299
301
|
});
|
|
300
302
|
```
|
|
301
303
|
|
|
304
|
+
## β‘ Performance & Reliability Features
|
|
305
|
+
|
|
306
|
+
TsmDB includes enterprise-grade features for robustness:
|
|
307
|
+
|
|
308
|
+
### π Index-Accelerated Queries
|
|
309
|
+
|
|
310
|
+
Indexes are automatically used to accelerate queries. Instead of scanning all documents, TsmDB uses:
|
|
311
|
+
|
|
312
|
+
- **Hash indexes** for equality queries (`$eq`, `$in`)
|
|
313
|
+
- **B-tree indexes** for range queries (`$gt`, `$gte`, `$lt`, `$lte`)
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Create an index
|
|
317
|
+
await collection.createIndex({ email: 1 });
|
|
318
|
+
await collection.createIndex({ age: 1 });
|
|
319
|
+
|
|
320
|
+
// These queries will use the index (fast!)
|
|
321
|
+
await collection.findOne({ email: 'alice@example.com' }); // Uses hash lookup
|
|
322
|
+
await collection.find({ age: { $gte: 18, $lt: 65 } }); // Uses B-tree range scan
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### π Query Planner
|
|
326
|
+
|
|
327
|
+
TsmDB includes a query planner that analyzes queries and selects optimal execution strategies:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { tsmdb } from '@push.rocks/smartmongo';
|
|
331
|
+
|
|
332
|
+
// For debugging, you can access the query planner
|
|
333
|
+
const planner = new tsmdb.QueryPlanner(indexEngine);
|
|
334
|
+
const plan = planner.createPlan(filter);
|
|
335
|
+
|
|
336
|
+
console.log(plan);
|
|
337
|
+
// {
|
|
338
|
+
// type: 'IXSCAN', // or 'IXSCAN_RANGE', 'COLLSCAN'
|
|
339
|
+
// indexName: 'email_1',
|
|
340
|
+
// estimatedCost: 1,
|
|
341
|
+
// selectivity: 0.001
|
|
342
|
+
// }
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### π Write-Ahead Logging (WAL)
|
|
346
|
+
|
|
347
|
+
For durability, TsmDB supports write-ahead logging:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { tsmdb } from '@push.rocks/smartmongo';
|
|
351
|
+
|
|
352
|
+
const wal = new tsmdb.WAL('./data/wal.log');
|
|
353
|
+
await wal.initialize();
|
|
354
|
+
|
|
355
|
+
// WAL entries include:
|
|
356
|
+
// - LSN (Log Sequence Number)
|
|
357
|
+
// - Timestamp
|
|
358
|
+
// - Operation type (insert, update, delete, checkpoint)
|
|
359
|
+
// - Document data (BSON serialized)
|
|
360
|
+
// - CRC32 checksum
|
|
361
|
+
|
|
362
|
+
// Recovery support
|
|
363
|
+
const entries = await wal.getEntriesAfter(lastCheckpointLsn);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### π Session Management
|
|
367
|
+
|
|
368
|
+
TsmDB tracks client sessions with automatic timeout and transaction linking:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// Sessions are automatically managed when using the MongoDB driver
|
|
372
|
+
const session = client.startSession();
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
session.startTransaction();
|
|
376
|
+
await collection.insertOne({ name: 'Alice' }, { session });
|
|
377
|
+
await collection.updateOne({ name: 'Bob' }, { $inc: { balance: 100 } }, { session });
|
|
378
|
+
await session.commitTransaction();
|
|
379
|
+
} catch (error) {
|
|
380
|
+
await session.abortTransaction();
|
|
381
|
+
} finally {
|
|
382
|
+
session.endSession();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Session features:
|
|
386
|
+
// - Automatic session timeout (30 minutes default)
|
|
387
|
+
// - Transaction auto-abort on session expiry
|
|
388
|
+
// - Session activity tracking
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### β
Data Integrity Checksums
|
|
392
|
+
|
|
393
|
+
File-based storage supports CRC32 checksums to detect corruption:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import { FileStorageAdapter } from '@push.rocks/smartmongo/tsmdb';
|
|
397
|
+
|
|
398
|
+
const adapter = new FileStorageAdapter('./data', {
|
|
399
|
+
enableChecksums: true,
|
|
400
|
+
strictChecksums: true // Throw error on corruption (vs warning)
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Documents are checksummed on write, verified on read
|
|
404
|
+
// Checksums are automatically stripped before returning to client
|
|
405
|
+
```
|
|
406
|
+
|
|
302
407
|
### π Supported Wire Protocol Commands
|
|
303
408
|
|
|
304
409
|
| Category | Commands |
|
|
@@ -307,7 +412,9 @@ const server = new tsmdb.TsmdbServer({
|
|
|
307
412
|
| **CRUD** | `find`, `insert`, `update`, `delete`, `findAndModify`, `getMore`, `killCursors` |
|
|
308
413
|
| **Aggregation** | `aggregate`, `count`, `distinct` |
|
|
309
414
|
| **Indexes** | `createIndexes`, `dropIndexes`, `listIndexes` |
|
|
310
|
-
| **
|
|
415
|
+
| **Transactions** | `startTransaction`, `commitTransaction`, `abortTransaction` |
|
|
416
|
+
| **Sessions** | `startSession`, `endSessions` |
|
|
417
|
+
| **Admin** | `ping`, `listDatabases`, `listCollections`, `drop`, `dropDatabase`, `create`, `serverStatus`, `buildInfo`, `dbStats`, `collStats` |
|
|
311
418
|
|
|
312
419
|
TsmDB supports MongoDB wire protocol versions 0-21, compatible with MongoDB 3.6 through 7.0 drivers.
|
|
313
420
|
|
|
@@ -317,7 +424,7 @@ TsmDB supports MongoDB wire protocol versions 0-21, compatible with MongoDB 3.6
|
|
|
317
424
|
|
|
318
425
|
```typescript
|
|
319
426
|
import { tsmdb } from '@push.rocks/smartmongo';
|
|
320
|
-
import { MongoClient } from 'mongodb';
|
|
427
|
+
import { MongoClient, Db } from 'mongodb';
|
|
321
428
|
|
|
322
429
|
let server: tsmdb.TsmdbServer;
|
|
323
430
|
let client: MongoClient;
|
|
@@ -421,21 +528,37 @@ export default tap.start();
|
|
|
421
528
|
βΌ
|
|
422
529
|
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
423
530
|
β Engines β
|
|
424
|
-
β βββββββββββββ
|
|
425
|
-
β β
|
|
426
|
-
β β
|
|
427
|
-
β βββββββββββββ
|
|
531
|
+
β βββββββββββ ββββββββββ βββββββββββββ βββββββββ βββββββββ β
|
|
532
|
+
β β Query β β Update β βAggregationβ β Index β βSessionβ β
|
|
533
|
+
β β Planner β β Engine β β Engine β βEngine β βEngine β β
|
|
534
|
+
β βββββββββββ ββββββββββ βββββββββββββ βββββββββ βββββββββ β
|
|
535
|
+
β ββββββββββββββββββββββββ β
|
|
536
|
+
β β Transaction Engine β β
|
|
537
|
+
β ββββββββββββββββββββββββ β
|
|
428
538
|
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
|
|
429
539
|
β
|
|
430
540
|
βΌ
|
|
431
541
|
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
432
|
-
β Storage
|
|
433
|
-
β
|
|
434
|
-
β
|
|
435
|
-
β
|
|
542
|
+
β Storage Layer β
|
|
543
|
+
β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββ β
|
|
544
|
+
β β MemoryStorage β β FileStorage β β WAL β β
|
|
545
|
+
β β β β (+ Checksums) β β β β
|
|
546
|
+
β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββ β
|
|
436
547
|
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
437
548
|
```
|
|
438
549
|
|
|
550
|
+
### Key Components
|
|
551
|
+
|
|
552
|
+
| Component | Description |
|
|
553
|
+
|-----------|-------------|
|
|
554
|
+
| **WireProtocol** | Parses MongoDB OP_MSG binary protocol |
|
|
555
|
+
| **CommandRouter** | Routes commands to appropriate handlers |
|
|
556
|
+
| **QueryPlanner** | Analyzes queries and selects execution strategy |
|
|
557
|
+
| **IndexEngine** | Manages B-tree and hash indexes |
|
|
558
|
+
| **SessionEngine** | Tracks client sessions and timeouts |
|
|
559
|
+
| **TransactionEngine** | Handles ACID transaction semantics |
|
|
560
|
+
| **WAL** | Write-ahead logging for durability |
|
|
561
|
+
|
|
439
562
|
## License and Legal Information
|
|
440
563
|
|
|
441
564
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|