mongolite-ts 0.5.0 → 0.6.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.
- package/README.md +181 -0
- package/dist/changeStream.d.ts +114 -0
- package/dist/changeStream.js +338 -0
- package/dist/changeStream.js.map +1 -0
- package/dist/collection.d.ts +26 -0
- package/dist/collection.js +28 -0
- package/dist/collection.js.map +1 -1
- package/dist/cursors/findCursor.js +16 -3
- package/dist/cursors/findCursor.js.map +1 -1
- package/dist/database-manager.d.ts +1 -0
- package/dist/database-manager.js +2 -0
- package/dist/database-manager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/mongodbSync.d.ts +128 -0
- package/dist/plugins/mongodbSync.js +339 -0
- package/dist/plugins/mongodbSync.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ A MongoDB-like client that uses SQLite as its underlying persistent store. Writt
|
|
|
17
17
|
* 100% test coverage (aiming for).
|
|
18
18
|
* **Interactive Query Debugger** - Debug complex queries with `npx mongolite-debug`
|
|
19
19
|
* **JSON Safety & Data Integrity** - Comprehensive protection against malformed JSON and data corruption
|
|
20
|
+
* **Change Streams** - Real-time change tracking similar to MongoDB's `changeStream = collection.watch()`
|
|
20
21
|
|
|
21
22
|
## JSON Safety & Data Integrity
|
|
22
23
|
|
|
@@ -155,6 +156,154 @@ async function main() {
|
|
|
155
156
|
main();
|
|
156
157
|
```
|
|
157
158
|
|
|
159
|
+
## Change Streams
|
|
160
|
+
|
|
161
|
+
MongoLite supports real-time change tracking through change streams, similar to MongoDB's `collection.watch()` feature. Change streams allow you to monitor and react to data changes (inserts, updates, deletes) in real-time.
|
|
162
|
+
|
|
163
|
+
### Basic Usage
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { MongoLite } from 'mongolite-ts';
|
|
167
|
+
|
|
168
|
+
const client = new MongoLite('./mydatabase.sqlite');
|
|
169
|
+
const collection = client.collection('users');
|
|
170
|
+
|
|
171
|
+
// Create a change stream
|
|
172
|
+
const changeStream = collection.watch({
|
|
173
|
+
fullDocument: 'updateLookup', // Include full document on updates
|
|
174
|
+
fullDocumentBeforeChange: 'whenAvailable' // Include document before change
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Listen for changes
|
|
178
|
+
changeStream.on('change', (change) => {
|
|
179
|
+
console.log('Change detected:', {
|
|
180
|
+
operation: change.operationType, // 'insert', 'update', 'delete'
|
|
181
|
+
documentId: change.documentKey._id,
|
|
182
|
+
collection: change.ns.coll,
|
|
183
|
+
timestamp: change.clusterTime
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (change.fullDocument) {
|
|
187
|
+
console.log('New document state:', change.fullDocument);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (change.updateDescription) {
|
|
191
|
+
console.log('Updated fields:', change.updateDescription.updatedFields);
|
|
192
|
+
console.log('Removed fields:', change.updateDescription.removedFields);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Handle errors
|
|
197
|
+
changeStream.on('error', (error) => {
|
|
198
|
+
console.error('Change stream error:', error);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Perform operations - changes will be captured
|
|
202
|
+
await collection.insertOne({ name: 'Alice', age: 30 });
|
|
203
|
+
await collection.updateOne({ name: 'Alice' }, { $set: { age: 31 } });
|
|
204
|
+
await collection.deleteOne({ name: 'Alice' });
|
|
205
|
+
|
|
206
|
+
// Close the change stream when done
|
|
207
|
+
changeStream.close();
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Async Iteration
|
|
211
|
+
|
|
212
|
+
Change streams support async iteration for a more declarative approach:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const changeStream = collection.watch();
|
|
216
|
+
|
|
217
|
+
// Use async iteration
|
|
218
|
+
for await (const change of changeStream) {
|
|
219
|
+
console.log('Change detected:', change.operationType);
|
|
220
|
+
|
|
221
|
+
// Process the change...
|
|
222
|
+
|
|
223
|
+
// Break after processing some changes
|
|
224
|
+
if (someCondition) {
|
|
225
|
+
changeStream.close();
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Change Stream Options
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
interface ChangeStreamOptions {
|
|
235
|
+
// Filter to apply to change events
|
|
236
|
+
filter?: Filter<ChangeStreamDocument>;
|
|
237
|
+
|
|
238
|
+
// Whether to include the full document in insert and update operations
|
|
239
|
+
fullDocument?: 'default' | 'updateLookup' | 'whenAvailable' | 'required';
|
|
240
|
+
|
|
241
|
+
// Whether to include the full document before the change
|
|
242
|
+
fullDocumentBeforeChange?: 'off' | 'whenAvailable' | 'required';
|
|
243
|
+
|
|
244
|
+
// Maximum number of events to buffer
|
|
245
|
+
maxBufferSize?: number;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Example with options
|
|
249
|
+
const changeStream = collection.watch({
|
|
250
|
+
fullDocument: 'updateLookup',
|
|
251
|
+
fullDocumentBeforeChange: 'whenAvailable',
|
|
252
|
+
maxBufferSize: 500
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Change Document Structure
|
|
257
|
+
|
|
258
|
+
Each change event contains detailed information about the operation:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
interface ChangeStreamDocument {
|
|
262
|
+
_id: string; // Unique change event ID
|
|
263
|
+
operationType: 'insert' | 'update' | 'delete' | 'replace';
|
|
264
|
+
clusterTime?: Date; // Timestamp of the change
|
|
265
|
+
fullDocument?: T; // Full document (based on options)
|
|
266
|
+
fullDocumentBeforeChange?: T; // Document before change (based on options)
|
|
267
|
+
documentKey: { _id: string }; // ID of the affected document
|
|
268
|
+
ns: { // Namespace information
|
|
269
|
+
db: string; // Database name
|
|
270
|
+
coll: string; // Collection name
|
|
271
|
+
};
|
|
272
|
+
updateDescription?: { // Update details (for update operations)
|
|
273
|
+
updatedFields: Record<string, unknown>;
|
|
274
|
+
removedFields: string[];
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Implementation Details
|
|
280
|
+
|
|
281
|
+
- **SQLite Triggers**: Uses SQLite triggers to capture changes automatically
|
|
282
|
+
- **Change Log Table**: Stores change events in a dedicated `__mongolite_changes__` table
|
|
283
|
+
- **Polling**: Efficiently polls for new changes every 100ms
|
|
284
|
+
- **Cleanup**: Automatically cleans up triggers when change streams are closed
|
|
285
|
+
- **Error Handling**: Robust error handling for database operations and malformed data
|
|
286
|
+
|
|
287
|
+
### Best Practices
|
|
288
|
+
|
|
289
|
+
1. **Close Change Streams**: Always close change streams when done to free resources
|
|
290
|
+
2. **Error Handling**: Implement proper error handling for change stream events
|
|
291
|
+
3. **Buffer Management**: Consider the `maxBufferSize` option for high-volume scenarios
|
|
292
|
+
4. **Cleanup**: Call `changeStream.cleanup()` to remove triggers if needed
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Proper cleanup
|
|
296
|
+
try {
|
|
297
|
+
const changeStream = collection.watch();
|
|
298
|
+
|
|
299
|
+
// ... use change stream
|
|
300
|
+
|
|
301
|
+
} finally {
|
|
302
|
+
changeStream.close();
|
|
303
|
+
await changeStream.cleanup(); // Remove triggers if needed
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
158
307
|
## Query Debugger
|
|
159
308
|
|
|
160
309
|
MongoLite includes an interactive query debugger to help you debug complex queries and understand how MongoDB-style filters are converted to SQL.
|
|
@@ -256,6 +405,38 @@ Deletes a single document matching the filter.
|
|
|
256
405
|
* `filter`: The selection criteria for the deletion.
|
|
257
406
|
* Returns `DeleteResult`: `{ acknowledged: boolean; deletedCount: number; }`.
|
|
258
407
|
|
|
408
|
+
#### `watch(options?: ChangeStreamOptions<T>): ChangeStream<T>`
|
|
409
|
+
|
|
410
|
+
Opens a change stream to watch for changes on this collection. Returns a ChangeStream that emits events when documents are inserted, updated, or deleted.
|
|
411
|
+
|
|
412
|
+
* `options`: Optional configuration for the change stream
|
|
413
|
+
* `fullDocument`: Controls when to include the full document ('default', 'updateLookup', 'whenAvailable', 'required')
|
|
414
|
+
* `fullDocumentBeforeChange`: Controls when to include the document before change ('off', 'whenAvailable', 'required')
|
|
415
|
+
* `maxBufferSize`: Maximum number of events to buffer (default: 1000)
|
|
416
|
+
* Returns a `ChangeStream` instance that extends EventEmitter and supports async iteration.
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
// Basic change stream
|
|
420
|
+
const changeStream = collection.watch();
|
|
421
|
+
changeStream.on('change', (change) => {
|
|
422
|
+
console.log('Change detected:', change);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// With options
|
|
426
|
+
const changeStream = collection.watch({
|
|
427
|
+
fullDocument: 'updateLookup',
|
|
428
|
+
fullDocumentBeforeChange: 'whenAvailable'
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Async iteration
|
|
432
|
+
for await (const change of changeStream) {
|
|
433
|
+
console.log('Change:', change.operationType);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Always close when done
|
|
437
|
+
changeStream.close();
|
|
438
|
+
```
|
|
439
|
+
|
|
259
440
|
|
|
260
441
|
### `FindCursor<T>`
|
|
261
442
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { SQLiteDB } from './db.js';
|
|
3
|
+
import { DocumentWithId, Filter } from './types.js';
|
|
4
|
+
export type ChangeOperationType = 'insert' | 'update' | 'delete' | 'replace';
|
|
5
|
+
export interface ChangeStreamDocument<T extends DocumentWithId = DocumentWithId> {
|
|
6
|
+
_id: string;
|
|
7
|
+
operationType: ChangeOperationType;
|
|
8
|
+
clusterTime?: Date;
|
|
9
|
+
fullDocument?: T;
|
|
10
|
+
fullDocumentBeforeChange?: T;
|
|
11
|
+
documentKey: {
|
|
12
|
+
_id: string;
|
|
13
|
+
};
|
|
14
|
+
ns: {
|
|
15
|
+
db: string;
|
|
16
|
+
coll: string;
|
|
17
|
+
};
|
|
18
|
+
updateDescription?: {
|
|
19
|
+
updatedFields: Record<string, unknown>;
|
|
20
|
+
removedFields: string[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface ChangeStreamOptions<T extends DocumentWithId = DocumentWithId> {
|
|
24
|
+
/**
|
|
25
|
+
* Filter to apply to change events
|
|
26
|
+
*/
|
|
27
|
+
filter?: Filter<ChangeStreamDocument<T>>;
|
|
28
|
+
/**
|
|
29
|
+
* Whether to include the full document in insert and update operations
|
|
30
|
+
*/
|
|
31
|
+
fullDocument?: 'default' | 'updateLookup' | 'whenAvailable' | 'required';
|
|
32
|
+
/**
|
|
33
|
+
* Whether to include the full document before the change for update operations
|
|
34
|
+
*/
|
|
35
|
+
fullDocumentBeforeChange?: 'off' | 'whenAvailable' | 'required';
|
|
36
|
+
/**
|
|
37
|
+
* Resume token for resuming change streams (not implemented in this version)
|
|
38
|
+
*/
|
|
39
|
+
resumeAfter?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Maximum number of events to buffer
|
|
42
|
+
*/
|
|
43
|
+
maxBufferSize?: number;
|
|
44
|
+
}
|
|
45
|
+
export declare class ChangeStream<T extends DocumentWithId = DocumentWithId> extends EventEmitter {
|
|
46
|
+
private readonly db;
|
|
47
|
+
private readonly collectionName;
|
|
48
|
+
private readonly options;
|
|
49
|
+
private closed;
|
|
50
|
+
private pollInterval;
|
|
51
|
+
private lastProcessedId;
|
|
52
|
+
private buffer;
|
|
53
|
+
private readonly maxBufferSize;
|
|
54
|
+
constructor(db: SQLiteDB, collectionName: string, options?: ChangeStreamOptions<T>);
|
|
55
|
+
/**
|
|
56
|
+
* Sets up the change tracking infrastructure
|
|
57
|
+
*/
|
|
58
|
+
private setupChangeTracking;
|
|
59
|
+
/**
|
|
60
|
+
* Creates the change log table if it doesn't exist
|
|
61
|
+
*/
|
|
62
|
+
private ensureChangeLogTable;
|
|
63
|
+
/**
|
|
64
|
+
* Sets up triggers for tracking changes on the collection
|
|
65
|
+
*/
|
|
66
|
+
private setupTriggers;
|
|
67
|
+
/**
|
|
68
|
+
* Starts polling for new changes
|
|
69
|
+
*/
|
|
70
|
+
private startPolling;
|
|
71
|
+
/**
|
|
72
|
+
* Polls for new changes from the change log
|
|
73
|
+
*/
|
|
74
|
+
private pollForChanges;
|
|
75
|
+
/**
|
|
76
|
+
* Transforms a raw change event from the database into a ChangeStreamDocument
|
|
77
|
+
*/
|
|
78
|
+
private transformChangeEvent;
|
|
79
|
+
/**
|
|
80
|
+
* Computes the update description by comparing before and after documents
|
|
81
|
+
*/
|
|
82
|
+
private computeUpdateDescription;
|
|
83
|
+
/**
|
|
84
|
+
* Determines if the full document should be included
|
|
85
|
+
*/
|
|
86
|
+
private shouldIncludeFullDocument;
|
|
87
|
+
/**
|
|
88
|
+
* Determines if the full document before change should be included
|
|
89
|
+
*/
|
|
90
|
+
private shouldIncludeFullDocumentBefore;
|
|
91
|
+
/**
|
|
92
|
+
* Checks if a change document passes the filter
|
|
93
|
+
*/
|
|
94
|
+
private passesFilter;
|
|
95
|
+
/**
|
|
96
|
+
* Returns an async iterator for the change stream
|
|
97
|
+
*/
|
|
98
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<ChangeStreamDocument<T>>;
|
|
99
|
+
/**
|
|
100
|
+
* Closes the change stream
|
|
101
|
+
*/
|
|
102
|
+
close(): void;
|
|
103
|
+
/**
|
|
104
|
+
* Returns the next change document
|
|
105
|
+
*/
|
|
106
|
+
next(): Promise<{
|
|
107
|
+
value: ChangeStreamDocument<T>;
|
|
108
|
+
done: boolean;
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Cleanup triggers when the change stream is destroyed
|
|
112
|
+
*/
|
|
113
|
+
cleanup(): Promise<void>;
|
|
114
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export class ChangeStream extends EventEmitter {
|
|
3
|
+
constructor(db, collectionName, options = {}) {
|
|
4
|
+
super();
|
|
5
|
+
this.db = db;
|
|
6
|
+
this.collectionName = collectionName;
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.closed = false;
|
|
9
|
+
this.pollInterval = null;
|
|
10
|
+
this.lastProcessedId = 0;
|
|
11
|
+
this.buffer = [];
|
|
12
|
+
this.maxBufferSize = options.maxBufferSize || 1000;
|
|
13
|
+
this.setupChangeTracking();
|
|
14
|
+
this.startPolling();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Sets up the change tracking infrastructure
|
|
18
|
+
*/
|
|
19
|
+
async setupChangeTracking() {
|
|
20
|
+
await this.ensureChangeLogTable();
|
|
21
|
+
await this.setupTriggers();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates the change log table if it doesn't exist
|
|
25
|
+
*/
|
|
26
|
+
async ensureChangeLogTable() {
|
|
27
|
+
const createTableSQL = `
|
|
28
|
+
CREATE TABLE IF NOT EXISTS __mongolite_changes__ (
|
|
29
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
30
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
31
|
+
operation_type TEXT NOT NULL,
|
|
32
|
+
collection_name TEXT NOT NULL,
|
|
33
|
+
document_id TEXT NOT NULL,
|
|
34
|
+
full_document TEXT,
|
|
35
|
+
full_document_before TEXT,
|
|
36
|
+
updated_fields TEXT,
|
|
37
|
+
removed_fields TEXT,
|
|
38
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
39
|
+
);
|
|
40
|
+
`;
|
|
41
|
+
const createIndexSQL = `
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_mongolite_changes_collection_timestamp
|
|
43
|
+
ON __mongolite_changes__ (collection_name, timestamp);
|
|
44
|
+
`;
|
|
45
|
+
await this.db.exec(createTableSQL);
|
|
46
|
+
await this.db.exec(createIndexSQL);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Sets up triggers for tracking changes on the collection
|
|
50
|
+
*/
|
|
51
|
+
async setupTriggers() {
|
|
52
|
+
const collectionName = this.collectionName;
|
|
53
|
+
// Drop existing triggers for this collection
|
|
54
|
+
await this.db.exec(`DROP TRIGGER IF EXISTS "${collectionName}_insert_trigger"`);
|
|
55
|
+
await this.db.exec(`DROP TRIGGER IF EXISTS "${collectionName}_update_trigger"`);
|
|
56
|
+
await this.db.exec(`DROP TRIGGER IF EXISTS "${collectionName}_delete_trigger"`);
|
|
57
|
+
// Insert trigger
|
|
58
|
+
const insertTriggerSQL = `
|
|
59
|
+
CREATE TRIGGER "${collectionName}_insert_trigger"
|
|
60
|
+
AFTER INSERT ON "${collectionName}"
|
|
61
|
+
FOR EACH ROW
|
|
62
|
+
BEGIN
|
|
63
|
+
INSERT INTO __mongolite_changes__ (
|
|
64
|
+
operation_type, collection_name, document_id, full_document
|
|
65
|
+
) VALUES (
|
|
66
|
+
'insert', '${collectionName}', NEW._id, NEW.data
|
|
67
|
+
);
|
|
68
|
+
END;
|
|
69
|
+
`;
|
|
70
|
+
// Update trigger
|
|
71
|
+
const updateTriggerSQL = `
|
|
72
|
+
CREATE TRIGGER "${collectionName}_update_trigger"
|
|
73
|
+
AFTER UPDATE ON "${collectionName}"
|
|
74
|
+
FOR EACH ROW
|
|
75
|
+
BEGIN
|
|
76
|
+
INSERT INTO __mongolite_changes__ (
|
|
77
|
+
operation_type, collection_name, document_id,
|
|
78
|
+
full_document, full_document_before
|
|
79
|
+
) VALUES (
|
|
80
|
+
'update', '${collectionName}', NEW._id,
|
|
81
|
+
NEW.data, OLD.data
|
|
82
|
+
);
|
|
83
|
+
END;
|
|
84
|
+
`;
|
|
85
|
+
// Delete trigger
|
|
86
|
+
const deleteTriggerSQL = `
|
|
87
|
+
CREATE TRIGGER "${collectionName}_delete_trigger"
|
|
88
|
+
AFTER DELETE ON "${collectionName}"
|
|
89
|
+
FOR EACH ROW
|
|
90
|
+
BEGIN
|
|
91
|
+
INSERT INTO __mongolite_changes__ (
|
|
92
|
+
operation_type, collection_name, document_id, full_document_before
|
|
93
|
+
) VALUES (
|
|
94
|
+
'delete', '${collectionName}', OLD._id, OLD.data
|
|
95
|
+
);
|
|
96
|
+
END;
|
|
97
|
+
`;
|
|
98
|
+
await this.db.exec(insertTriggerSQL);
|
|
99
|
+
await this.db.exec(updateTriggerSQL);
|
|
100
|
+
await this.db.exec(deleteTriggerSQL);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Starts polling for new changes
|
|
104
|
+
*/
|
|
105
|
+
startPolling() {
|
|
106
|
+
if (this.closed)
|
|
107
|
+
return;
|
|
108
|
+
this.pollInterval = setInterval(async () => {
|
|
109
|
+
try {
|
|
110
|
+
await this.pollForChanges();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
this.emit('error', error);
|
|
114
|
+
}
|
|
115
|
+
}, 100); // Poll every 100ms
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Polls for new changes from the change log
|
|
119
|
+
*/
|
|
120
|
+
async pollForChanges() {
|
|
121
|
+
if (this.closed)
|
|
122
|
+
return;
|
|
123
|
+
const changes = await this.db.all(`SELECT * FROM __mongolite_changes__
|
|
124
|
+
WHERE collection_name = ? AND id > ?
|
|
125
|
+
ORDER BY id ASC LIMIT 100`, [this.collectionName, this.lastProcessedId]);
|
|
126
|
+
for (const change of changes) {
|
|
127
|
+
if (this.closed)
|
|
128
|
+
break;
|
|
129
|
+
const changeDoc = await this.transformChangeEvent(change);
|
|
130
|
+
if (this.passesFilter(changeDoc)) {
|
|
131
|
+
this.buffer.push(changeDoc);
|
|
132
|
+
// Emit the change event
|
|
133
|
+
this.emit('change', changeDoc);
|
|
134
|
+
// Clean buffer if it gets too large
|
|
135
|
+
if (this.buffer.length > this.maxBufferSize) {
|
|
136
|
+
this.buffer = this.buffer.slice(-this.maxBufferSize);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
this.lastProcessedId = change.id;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Transforms a raw change event from the database into a ChangeStreamDocument
|
|
144
|
+
*/
|
|
145
|
+
async transformChangeEvent(change) {
|
|
146
|
+
const fullDocument = change.full_document ? JSON.parse(change.full_document) : undefined;
|
|
147
|
+
const fullDocumentBefore = change.full_document_before
|
|
148
|
+
? JSON.parse(change.full_document_before)
|
|
149
|
+
: undefined;
|
|
150
|
+
let updateDescription;
|
|
151
|
+
if (change.operation_type === 'update' && fullDocument && fullDocumentBefore) {
|
|
152
|
+
updateDescription = this.computeUpdateDescription(fullDocumentBefore, fullDocument);
|
|
153
|
+
}
|
|
154
|
+
const changeDoc = {
|
|
155
|
+
_id: `${change.id}`, // Use the change log ID as the change stream document ID
|
|
156
|
+
operationType: change.operation_type,
|
|
157
|
+
clusterTime: new Date(change.timestamp),
|
|
158
|
+
documentKey: { _id: change.document_id },
|
|
159
|
+
ns: {
|
|
160
|
+
db: 'mongolite', // You could make this configurable
|
|
161
|
+
coll: change.collection_name,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
// Add full document based on options
|
|
165
|
+
if (this.shouldIncludeFullDocument(change.operation_type)) {
|
|
166
|
+
changeDoc.fullDocument = fullDocument
|
|
167
|
+
? { _id: change.document_id, ...fullDocument }
|
|
168
|
+
: undefined;
|
|
169
|
+
}
|
|
170
|
+
// Add full document before change based on options
|
|
171
|
+
if (this.shouldIncludeFullDocumentBefore(change.operation_type)) {
|
|
172
|
+
changeDoc.fullDocumentBeforeChange = fullDocumentBefore
|
|
173
|
+
? { _id: change.document_id, ...fullDocumentBefore }
|
|
174
|
+
: undefined;
|
|
175
|
+
}
|
|
176
|
+
if (updateDescription) {
|
|
177
|
+
changeDoc.updateDescription = updateDescription;
|
|
178
|
+
}
|
|
179
|
+
return changeDoc;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Computes the update description by comparing before and after documents
|
|
183
|
+
*/
|
|
184
|
+
computeUpdateDescription(before, after) {
|
|
185
|
+
const updatedFields = {};
|
|
186
|
+
const removedFields = [];
|
|
187
|
+
// Type guard to ensure we have objects to compare
|
|
188
|
+
if (typeof before !== 'object' ||
|
|
189
|
+
before === null ||
|
|
190
|
+
typeof after !== 'object' ||
|
|
191
|
+
after === null) {
|
|
192
|
+
return { updatedFields, removedFields };
|
|
193
|
+
}
|
|
194
|
+
// Simple comparison - this could be made more sophisticated
|
|
195
|
+
const beforeObj = before;
|
|
196
|
+
const afterObj = after;
|
|
197
|
+
const allKeys = new Set([...Object.keys(beforeObj), ...Object.keys(afterObj)]);
|
|
198
|
+
for (const key of allKeys) {
|
|
199
|
+
if (!(key in afterObj)) {
|
|
200
|
+
removedFields.push(key);
|
|
201
|
+
}
|
|
202
|
+
else if (!(key in beforeObj) ||
|
|
203
|
+
JSON.stringify(beforeObj[key]) !== JSON.stringify(afterObj[key])) {
|
|
204
|
+
updatedFields[key] = afterObj[key];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { updatedFields, removedFields };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Determines if the full document should be included
|
|
211
|
+
*/
|
|
212
|
+
shouldIncludeFullDocument(operationType) {
|
|
213
|
+
const option = this.options.fullDocument || 'default';
|
|
214
|
+
switch (option) {
|
|
215
|
+
case 'default':
|
|
216
|
+
return operationType === 'insert';
|
|
217
|
+
case 'updateLookup':
|
|
218
|
+
case 'whenAvailable':
|
|
219
|
+
case 'required':
|
|
220
|
+
return (operationType === 'insert' || operationType === 'update' || operationType === 'replace');
|
|
221
|
+
default:
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Determines if the full document before change should be included
|
|
227
|
+
*/
|
|
228
|
+
shouldIncludeFullDocumentBefore(operationType) {
|
|
229
|
+
const option = this.options.fullDocumentBeforeChange || 'off';
|
|
230
|
+
if (option === 'off')
|
|
231
|
+
return false;
|
|
232
|
+
return operationType === 'update' || operationType === 'replace' || operationType === 'delete';
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Checks if a change document passes the filter
|
|
236
|
+
*/
|
|
237
|
+
passesFilter(changeDoc) {
|
|
238
|
+
if (!this.options.filter)
|
|
239
|
+
return true;
|
|
240
|
+
// Simple filter implementation - could be enhanced
|
|
241
|
+
// For now, just check basic equality filters
|
|
242
|
+
for (const [key, value] of Object.entries(this.options.filter)) {
|
|
243
|
+
const docValue = changeDoc[key];
|
|
244
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
245
|
+
// Handle operators like $in, $eq, etc.
|
|
246
|
+
// This is a simplified implementation
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
else if (docValue !== value) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Returns an async iterator for the change stream
|
|
257
|
+
*/
|
|
258
|
+
async *[Symbol.asyncIterator]() {
|
|
259
|
+
while (!this.closed) {
|
|
260
|
+
if (this.buffer.length > 0) {
|
|
261
|
+
yield this.buffer.shift();
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Wait for next change
|
|
265
|
+
await new Promise((resolve) => {
|
|
266
|
+
const onChange = () => {
|
|
267
|
+
this.removeListener('change', onChange);
|
|
268
|
+
this.removeListener('close', onClose);
|
|
269
|
+
resolve();
|
|
270
|
+
};
|
|
271
|
+
const onClose = () => {
|
|
272
|
+
this.removeListener('change', onChange);
|
|
273
|
+
this.removeListener('close', onClose);
|
|
274
|
+
resolve();
|
|
275
|
+
};
|
|
276
|
+
this.once('change', onChange);
|
|
277
|
+
this.once('close', onClose);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Closes the change stream
|
|
284
|
+
*/
|
|
285
|
+
close() {
|
|
286
|
+
if (this.closed)
|
|
287
|
+
return;
|
|
288
|
+
this.closed = true;
|
|
289
|
+
if (this.pollInterval) {
|
|
290
|
+
clearInterval(this.pollInterval);
|
|
291
|
+
this.pollInterval = null;
|
|
292
|
+
}
|
|
293
|
+
this.emit('close');
|
|
294
|
+
this.removeAllListeners();
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Returns the next change document
|
|
298
|
+
*/
|
|
299
|
+
async next() {
|
|
300
|
+
if (this.closed) {
|
|
301
|
+
return { value: {}, done: true };
|
|
302
|
+
}
|
|
303
|
+
if (this.buffer.length > 0) {
|
|
304
|
+
return { value: this.buffer.shift(), done: false };
|
|
305
|
+
}
|
|
306
|
+
// Wait for next change
|
|
307
|
+
return new Promise((resolve) => {
|
|
308
|
+
const onChange = (changeDoc) => {
|
|
309
|
+
this.removeListener('change', onChange);
|
|
310
|
+
this.removeListener('close', onClose);
|
|
311
|
+
resolve({ value: changeDoc, done: false });
|
|
312
|
+
};
|
|
313
|
+
const onClose = () => {
|
|
314
|
+
this.removeListener('change', onChange);
|
|
315
|
+
this.removeListener('close', onClose);
|
|
316
|
+
resolve({ value: {}, done: true });
|
|
317
|
+
};
|
|
318
|
+
this.once('change', onChange);
|
|
319
|
+
this.once('close', onClose);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Cleanup triggers when the change stream is destroyed
|
|
324
|
+
*/
|
|
325
|
+
async cleanup() {
|
|
326
|
+
const collectionName = this.collectionName;
|
|
327
|
+
try {
|
|
328
|
+
await this.db.exec(`DROP TRIGGER IF EXISTS "${collectionName}_insert_trigger"`);
|
|
329
|
+
await this.db.exec(`DROP TRIGGER IF EXISTS "${collectionName}_update_trigger"`);
|
|
330
|
+
await this.db.exec(`DROP TRIGGER IF EXISTS "${collectionName}_delete_trigger"`);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
// Ignore errors during cleanup
|
|
334
|
+
console.warn('Error during change stream cleanup:', error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=changeStream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changeStream.js","sourceRoot":"","sources":["../src/changeStream.ts"],"names":[],"mappings":"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"}
|