@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,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
- }