@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,282 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import type { IStorageAdapter } from './IStorageAdapter.js';
3
- import type { IOpLogEntry, Document, IResumeToken, ChangeStreamOperationType } from '../types/interfaces.js';
4
-
5
- /**
6
- * Operation Log for tracking all mutations
7
- * Used primarily for change stream support
8
- */
9
- export class OpLog {
10
- private storage: IStorageAdapter;
11
- private counter = 0;
12
- private listeners: Array<(entry: IOpLogEntry) => void> = [];
13
-
14
- constructor(storage: IStorageAdapter) {
15
- this.storage = storage;
16
- }
17
-
18
- /**
19
- * Generate a new timestamp for oplog entries
20
- */
21
- generateTimestamp(): plugins.bson.Timestamp {
22
- this.counter++;
23
- return new plugins.bson.Timestamp({ t: Math.floor(Date.now() / 1000), i: this.counter });
24
- }
25
-
26
- /**
27
- * Generate a resume token from a timestamp
28
- */
29
- generateResumeToken(ts: plugins.bson.Timestamp): IResumeToken {
30
- // Create a resume token similar to MongoDB's format
31
- // It's a base64-encoded BSON document containing the timestamp
32
- const tokenData = {
33
- _data: Buffer.from(JSON.stringify({
34
- ts: { t: ts.high, i: ts.low },
35
- version: 1,
36
- })).toString('base64'),
37
- };
38
- return tokenData;
39
- }
40
-
41
- /**
42
- * Parse a resume token to get the timestamp
43
- */
44
- parseResumeToken(token: IResumeToken): plugins.bson.Timestamp {
45
- try {
46
- const data = JSON.parse(Buffer.from(token._data, 'base64').toString('utf-8'));
47
- return new plugins.bson.Timestamp({ t: data.ts.t, i: data.ts.i });
48
- } catch {
49
- throw new Error('Invalid resume token');
50
- }
51
- }
52
-
53
- /**
54
- * Record an insert operation
55
- */
56
- async recordInsert(
57
- dbName: string,
58
- collName: string,
59
- document: Document,
60
- txnInfo?: { txnNumber?: number; lsid?: { id: plugins.bson.Binary } }
61
- ): Promise<IOpLogEntry> {
62
- const entry: IOpLogEntry = {
63
- ts: this.generateTimestamp(),
64
- op: 'i',
65
- ns: `${dbName}.${collName}`,
66
- o: document,
67
- ...txnInfo,
68
- };
69
-
70
- await this.storage.appendOpLog(entry);
71
- this.notifyListeners(entry);
72
- return entry;
73
- }
74
-
75
- /**
76
- * Record an update operation
77
- */
78
- async recordUpdate(
79
- dbName: string,
80
- collName: string,
81
- filter: Document,
82
- update: Document,
83
- txnInfo?: { txnNumber?: number; lsid?: { id: plugins.bson.Binary } }
84
- ): Promise<IOpLogEntry> {
85
- const entry: IOpLogEntry = {
86
- ts: this.generateTimestamp(),
87
- op: 'u',
88
- ns: `${dbName}.${collName}`,
89
- o: update,
90
- o2: filter,
91
- ...txnInfo,
92
- };
93
-
94
- await this.storage.appendOpLog(entry);
95
- this.notifyListeners(entry);
96
- return entry;
97
- }
98
-
99
- /**
100
- * Record a delete operation
101
- */
102
- async recordDelete(
103
- dbName: string,
104
- collName: string,
105
- filter: Document,
106
- txnInfo?: { txnNumber?: number; lsid?: { id: plugins.bson.Binary } }
107
- ): Promise<IOpLogEntry> {
108
- const entry: IOpLogEntry = {
109
- ts: this.generateTimestamp(),
110
- op: 'd',
111
- ns: `${dbName}.${collName}`,
112
- o: filter,
113
- ...txnInfo,
114
- };
115
-
116
- await this.storage.appendOpLog(entry);
117
- this.notifyListeners(entry);
118
- return entry;
119
- }
120
-
121
- /**
122
- * Record a command (drop, rename, etc.)
123
- */
124
- async recordCommand(
125
- dbName: string,
126
- command: Document
127
- ): Promise<IOpLogEntry> {
128
- const entry: IOpLogEntry = {
129
- ts: this.generateTimestamp(),
130
- op: 'c',
131
- ns: `${dbName}.$cmd`,
132
- o: command,
133
- };
134
-
135
- await this.storage.appendOpLog(entry);
136
- this.notifyListeners(entry);
137
- return entry;
138
- }
139
-
140
- /**
141
- * Get oplog entries after a timestamp
142
- */
143
- async getEntriesAfter(ts: plugins.bson.Timestamp, limit?: number): Promise<IOpLogEntry[]> {
144
- return this.storage.getOpLogAfter(ts, limit);
145
- }
146
-
147
- /**
148
- * Get the latest timestamp
149
- */
150
- async getLatestTimestamp(): Promise<plugins.bson.Timestamp | null> {
151
- return this.storage.getLatestOpLogTimestamp();
152
- }
153
-
154
- /**
155
- * Subscribe to oplog changes (for change streams)
156
- */
157
- subscribe(listener: (entry: IOpLogEntry) => void): () => void {
158
- this.listeners.push(listener);
159
- return () => {
160
- const idx = this.listeners.indexOf(listener);
161
- if (idx >= 0) {
162
- this.listeners.splice(idx, 1);
163
- }
164
- };
165
- }
166
-
167
- /**
168
- * Notify all listeners of a new entry
169
- */
170
- private notifyListeners(entry: IOpLogEntry): void {
171
- for (const listener of this.listeners) {
172
- try {
173
- listener(entry);
174
- } catch (error) {
175
- console.error('Error in oplog listener:', error);
176
- }
177
- }
178
- }
179
-
180
- /**
181
- * Convert an oplog entry to a change stream document
182
- */
183
- opLogEntryToChangeEvent(
184
- entry: IOpLogEntry,
185
- fullDocument?: Document,
186
- fullDocumentBeforeChange?: Document
187
- ): {
188
- _id: IResumeToken;
189
- operationType: ChangeStreamOperationType;
190
- fullDocument?: Document;
191
- fullDocumentBeforeChange?: Document;
192
- ns: { db: string; coll?: string };
193
- documentKey?: { _id: plugins.bson.ObjectId };
194
- updateDescription?: {
195
- updatedFields?: Document;
196
- removedFields?: string[];
197
- };
198
- clusterTime: plugins.bson.Timestamp;
199
- } {
200
- const [db, coll] = entry.ns.split('.');
201
- const resumeToken = this.generateResumeToken(entry.ts);
202
-
203
- const baseEvent = {
204
- _id: resumeToken,
205
- ns: { db, coll: coll === '$cmd' ? undefined : coll },
206
- clusterTime: entry.ts,
207
- };
208
-
209
- switch (entry.op) {
210
- case 'i':
211
- return {
212
- ...baseEvent,
213
- operationType: 'insert' as ChangeStreamOperationType,
214
- fullDocument: fullDocument || entry.o,
215
- documentKey: entry.o._id ? { _id: entry.o._id } : undefined,
216
- };
217
-
218
- case 'u':
219
- const updateEvent: any = {
220
- ...baseEvent,
221
- operationType: 'update' as ChangeStreamOperationType,
222
- documentKey: entry.o2?._id ? { _id: entry.o2._id } : undefined,
223
- };
224
-
225
- if (fullDocument) {
226
- updateEvent.fullDocument = fullDocument;
227
- }
228
- if (fullDocumentBeforeChange) {
229
- updateEvent.fullDocumentBeforeChange = fullDocumentBeforeChange;
230
- }
231
-
232
- // Parse update description
233
- if (entry.o.$set || entry.o.$unset) {
234
- updateEvent.updateDescription = {
235
- updatedFields: entry.o.$set || {},
236
- removedFields: entry.o.$unset ? Object.keys(entry.o.$unset) : [],
237
- };
238
- }
239
-
240
- return updateEvent;
241
-
242
- case 'd':
243
- return {
244
- ...baseEvent,
245
- operationType: 'delete' as ChangeStreamOperationType,
246
- documentKey: entry.o._id ? { _id: entry.o._id } : undefined,
247
- fullDocumentBeforeChange,
248
- };
249
-
250
- case 'c':
251
- if (entry.o.drop) {
252
- return {
253
- ...baseEvent,
254
- operationType: 'drop' as ChangeStreamOperationType,
255
- ns: { db, coll: entry.o.drop },
256
- };
257
- }
258
- if (entry.o.dropDatabase) {
259
- return {
260
- ...baseEvent,
261
- operationType: 'dropDatabase' as ChangeStreamOperationType,
262
- };
263
- }
264
- if (entry.o.renameCollection) {
265
- return {
266
- ...baseEvent,
267
- operationType: 'rename' as ChangeStreamOperationType,
268
- };
269
- }
270
- return {
271
- ...baseEvent,
272
- operationType: 'invalidate' as ChangeStreamOperationType,
273
- };
274
-
275
- default:
276
- return {
277
- ...baseEvent,
278
- operationType: 'invalidate' as ChangeStreamOperationType,
279
- };
280
- }
281
- }
282
- }
@@ -1,375 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import type { Document, IStoredDocument } from '../types/interfaces.js';
3
-
4
- /**
5
- * WAL entry operation types
6
- */
7
- export type TWalOperation = 'insert' | 'update' | 'delete' | 'checkpoint' | 'begin' | 'commit' | 'abort';
8
-
9
- /**
10
- * WAL entry structure
11
- */
12
- export interface IWalEntry {
13
- /** Log Sequence Number - monotonically increasing */
14
- lsn: number;
15
- /** Timestamp of the operation */
16
- timestamp: number;
17
- /** Operation type */
18
- operation: TWalOperation;
19
- /** Database name */
20
- dbName: string;
21
- /** Collection name */
22
- collName: string;
23
- /** Document ID (hex string) */
24
- documentId: string;
25
- /** Document data (BSON serialized, base64 encoded) */
26
- data?: string;
27
- /** Previous document data for updates (for rollback) */
28
- previousData?: string;
29
- /** Transaction ID if part of a transaction */
30
- txnId?: string;
31
- /** CRC32 checksum of the entry (excluding this field) */
32
- checksum: number;
33
- }
34
-
35
- /**
36
- * Checkpoint record
37
- */
38
- interface ICheckpointRecord {
39
- lsn: number;
40
- timestamp: number;
41
- lastCommittedLsn: number;
42
- }
43
-
44
- /**
45
- * Write-Ahead Log (WAL) for durability and crash recovery
46
- *
47
- * The WAL ensures durability by writing operations to a log file before
48
- * they are applied to the main storage. On crash recovery, uncommitted
49
- * operations can be replayed to restore the database to a consistent state.
50
- */
51
- export class WAL {
52
- private walPath: string;
53
- private currentLsn: number = 0;
54
- private lastCheckpointLsn: number = 0;
55
- private entries: IWalEntry[] = [];
56
- private isInitialized: boolean = false;
57
- private fs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
58
-
59
- // In-memory uncommitted entries per transaction
60
- private uncommittedTxns: Map<string, IWalEntry[]> = new Map();
61
-
62
- // Checkpoint interval (number of entries between checkpoints)
63
- private checkpointInterval: number = 1000;
64
-
65
- constructor(walPath: string, options?: { checkpointInterval?: number }) {
66
- this.walPath = walPath;
67
- if (options?.checkpointInterval) {
68
- this.checkpointInterval = options.checkpointInterval;
69
- }
70
- }
71
-
72
- /**
73
- * Initialize the WAL, loading existing entries and recovering if needed
74
- */
75
- async initialize(): Promise<{ recoveredEntries: IWalEntry[] }> {
76
- if (this.isInitialized) {
77
- return { recoveredEntries: [] };
78
- }
79
-
80
- // Ensure WAL directory exists
81
- const walDir = this.walPath.substring(0, this.walPath.lastIndexOf('/'));
82
- if (walDir) {
83
- await this.fs.directory(walDir).recursive().create();
84
- }
85
-
86
- // Try to load existing WAL
87
- const exists = await this.fs.file(this.walPath).exists();
88
- if (exists) {
89
- const content = await this.fs.file(this.walPath).encoding('utf8').read();
90
- const lines = (content as string).split('\n').filter(line => line.trim());
91
-
92
- for (const line of lines) {
93
- try {
94
- const entry = JSON.parse(line) as IWalEntry;
95
- // Verify checksum
96
- if (this.verifyChecksum(entry)) {
97
- this.entries.push(entry);
98
- if (entry.lsn > this.currentLsn) {
99
- this.currentLsn = entry.lsn;
100
- }
101
- if (entry.operation === 'checkpoint') {
102
- this.lastCheckpointLsn = entry.lsn;
103
- }
104
- }
105
- } catch {
106
- // Skip corrupted entries
107
- console.warn('Skipping corrupted WAL entry');
108
- }
109
- }
110
- }
111
-
112
- this.isInitialized = true;
113
-
114
- // Return entries after last checkpoint that need recovery
115
- const recoveredEntries = this.entries.filter(
116
- e => e.lsn > this.lastCheckpointLsn &&
117
- (e.operation === 'insert' || e.operation === 'update' || e.operation === 'delete')
118
- );
119
-
120
- return { recoveredEntries };
121
- }
122
-
123
- /**
124
- * Log an insert operation
125
- */
126
- async logInsert(dbName: string, collName: string, doc: IStoredDocument, txnId?: string): Promise<number> {
127
- return this.appendEntry({
128
- operation: 'insert',
129
- dbName,
130
- collName,
131
- documentId: doc._id.toHexString(),
132
- data: this.serializeDocument(doc),
133
- txnId,
134
- });
135
- }
136
-
137
- /**
138
- * Log an update operation
139
- */
140
- async logUpdate(
141
- dbName: string,
142
- collName: string,
143
- oldDoc: IStoredDocument,
144
- newDoc: IStoredDocument,
145
- txnId?: string
146
- ): Promise<number> {
147
- return this.appendEntry({
148
- operation: 'update',
149
- dbName,
150
- collName,
151
- documentId: oldDoc._id.toHexString(),
152
- data: this.serializeDocument(newDoc),
153
- previousData: this.serializeDocument(oldDoc),
154
- txnId,
155
- });
156
- }
157
-
158
- /**
159
- * Log a delete operation
160
- */
161
- async logDelete(dbName: string, collName: string, doc: IStoredDocument, txnId?: string): Promise<number> {
162
- return this.appendEntry({
163
- operation: 'delete',
164
- dbName,
165
- collName,
166
- documentId: doc._id.toHexString(),
167
- previousData: this.serializeDocument(doc),
168
- txnId,
169
- });
170
- }
171
-
172
- /**
173
- * Log transaction begin
174
- */
175
- async logBeginTransaction(txnId: string): Promise<number> {
176
- this.uncommittedTxns.set(txnId, []);
177
- return this.appendEntry({
178
- operation: 'begin',
179
- dbName: '',
180
- collName: '',
181
- documentId: '',
182
- txnId,
183
- });
184
- }
185
-
186
- /**
187
- * Log transaction commit
188
- */
189
- async logCommitTransaction(txnId: string): Promise<number> {
190
- this.uncommittedTxns.delete(txnId);
191
- return this.appendEntry({
192
- operation: 'commit',
193
- dbName: '',
194
- collName: '',
195
- documentId: '',
196
- txnId,
197
- });
198
- }
199
-
200
- /**
201
- * Log transaction abort
202
- */
203
- async logAbortTransaction(txnId: string): Promise<number> {
204
- this.uncommittedTxns.delete(txnId);
205
- return this.appendEntry({
206
- operation: 'abort',
207
- dbName: '',
208
- collName: '',
209
- documentId: '',
210
- txnId,
211
- });
212
- }
213
-
214
- /**
215
- * Get entries to roll back for an aborted transaction
216
- */
217
- getTransactionEntries(txnId: string): IWalEntry[] {
218
- return this.entries.filter(e => e.txnId === txnId);
219
- }
220
-
221
- /**
222
- * Create a checkpoint - marks a consistent point in the log
223
- */
224
- async checkpoint(): Promise<number> {
225
- const lsn = await this.appendEntry({
226
- operation: 'checkpoint',
227
- dbName: '',
228
- collName: '',
229
- documentId: '',
230
- });
231
- this.lastCheckpointLsn = lsn;
232
-
233
- // Truncate old entries (keep only entries after checkpoint)
234
- await this.truncate();
235
-
236
- return lsn;
237
- }
238
-
239
- /**
240
- * Truncate the WAL file, removing entries before the last checkpoint
241
- */
242
- private async truncate(): Promise<void> {
243
- // Keep entries after last checkpoint
244
- const newEntries = this.entries.filter(e => e.lsn >= this.lastCheckpointLsn);
245
- this.entries = newEntries;
246
-
247
- // Rewrite the WAL file
248
- const lines = this.entries.map(e => JSON.stringify(e)).join('\n');
249
- await this.fs.file(this.walPath).encoding('utf8').write(lines);
250
- }
251
-
252
- /**
253
- * Get current LSN
254
- */
255
- getCurrentLsn(): number {
256
- return this.currentLsn;
257
- }
258
-
259
- /**
260
- * Get entries after a specific LSN (for recovery)
261
- */
262
- getEntriesAfter(lsn: number): IWalEntry[] {
263
- return this.entries.filter(e => e.lsn > lsn);
264
- }
265
-
266
- /**
267
- * Close the WAL
268
- */
269
- async close(): Promise<void> {
270
- if (this.isInitialized) {
271
- // Final checkpoint before close
272
- await this.checkpoint();
273
- }
274
- this.isInitialized = false;
275
- }
276
-
277
- // ============================================================================
278
- // Private Methods
279
- // ============================================================================
280
-
281
- private async appendEntry(
282
- partial: Omit<IWalEntry, 'lsn' | 'timestamp' | 'checksum'>
283
- ): Promise<number> {
284
- await this.initialize();
285
-
286
- this.currentLsn++;
287
- const entry: IWalEntry = {
288
- ...partial,
289
- lsn: this.currentLsn,
290
- timestamp: Date.now(),
291
- checksum: 0, // Will be calculated
292
- };
293
-
294
- // Calculate checksum
295
- entry.checksum = this.calculateChecksum(entry);
296
-
297
- // Track in transaction if applicable
298
- if (partial.txnId && this.uncommittedTxns.has(partial.txnId)) {
299
- this.uncommittedTxns.get(partial.txnId)!.push(entry);
300
- }
301
-
302
- // Add to in-memory log
303
- this.entries.push(entry);
304
-
305
- // Append to file (append mode for durability)
306
- await this.fs.file(this.walPath).encoding('utf8').append(JSON.stringify(entry) + '\n');
307
-
308
- // Check if we need a checkpoint
309
- if (this.entries.length - this.lastCheckpointLsn >= this.checkpointInterval) {
310
- await this.checkpoint();
311
- }
312
-
313
- return entry.lsn;
314
- }
315
-
316
- private serializeDocument(doc: Document): string {
317
- // Serialize document to BSON and encode as base64
318
- const bsonData = plugins.bson.serialize(doc);
319
- return Buffer.from(bsonData).toString('base64');
320
- }
321
-
322
- private deserializeDocument(data: string): Document {
323
- // Decode base64 and deserialize from BSON
324
- const buffer = Buffer.from(data, 'base64');
325
- return plugins.bson.deserialize(buffer);
326
- }
327
-
328
- private calculateChecksum(entry: IWalEntry): number {
329
- // Simple CRC32-like checksum
330
- const str = JSON.stringify({
331
- lsn: entry.lsn,
332
- timestamp: entry.timestamp,
333
- operation: entry.operation,
334
- dbName: entry.dbName,
335
- collName: entry.collName,
336
- documentId: entry.documentId,
337
- data: entry.data,
338
- previousData: entry.previousData,
339
- txnId: entry.txnId,
340
- });
341
-
342
- let crc = 0xFFFFFFFF;
343
- for (let i = 0; i < str.length; i++) {
344
- crc ^= str.charCodeAt(i);
345
- for (let j = 0; j < 8; j++) {
346
- crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0);
347
- }
348
- }
349
- return (~crc) >>> 0;
350
- }
351
-
352
- private verifyChecksum(entry: IWalEntry): boolean {
353
- const savedChecksum = entry.checksum;
354
- entry.checksum = 0;
355
- const calculatedChecksum = this.calculateChecksum(entry);
356
- entry.checksum = savedChecksum;
357
- return calculatedChecksum === savedChecksum;
358
- }
359
-
360
- /**
361
- * Recover document from WAL entry
362
- */
363
- recoverDocument(entry: IWalEntry): IStoredDocument | null {
364
- if (!entry.data) return null;
365
- return this.deserializeDocument(entry.data) as IStoredDocument;
366
- }
367
-
368
- /**
369
- * Recover previous document state from WAL entry (for rollback)
370
- */
371
- recoverPreviousDocument(entry: IWalEntry): IStoredDocument | null {
372
- if (!entry.previousData) return null;
373
- return this.deserializeDocument(entry.previousData) as IStoredDocument;
374
- }
375
- }