@powersync/service-module-mongodb 0.0.0-dev-20241001150444

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 (62) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +67 -0
  3. package/README.md +3 -0
  4. package/dist/api/MongoRouteAPIAdapter.d.ts +22 -0
  5. package/dist/api/MongoRouteAPIAdapter.js +64 -0
  6. package/dist/api/MongoRouteAPIAdapter.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +4 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/module/MongoModule.d.ts +13 -0
  11. package/dist/module/MongoModule.js +46 -0
  12. package/dist/module/MongoModule.js.map +1 -0
  13. package/dist/replication/ChangeStream.d.ts +53 -0
  14. package/dist/replication/ChangeStream.js +389 -0
  15. package/dist/replication/ChangeStream.js.map +1 -0
  16. package/dist/replication/ChangeStreamReplicationJob.d.ts +16 -0
  17. package/dist/replication/ChangeStreamReplicationJob.js +90 -0
  18. package/dist/replication/ChangeStreamReplicationJob.js.map +1 -0
  19. package/dist/replication/ChangeStreamReplicator.d.ts +13 -0
  20. package/dist/replication/ChangeStreamReplicator.js +26 -0
  21. package/dist/replication/ChangeStreamReplicator.js.map +1 -0
  22. package/dist/replication/ConnectionManagerFactory.d.ts +9 -0
  23. package/dist/replication/ConnectionManagerFactory.js +21 -0
  24. package/dist/replication/ConnectionManagerFactory.js.map +1 -0
  25. package/dist/replication/MongoErrorRateLimiter.d.ts +11 -0
  26. package/dist/replication/MongoErrorRateLimiter.js +44 -0
  27. package/dist/replication/MongoErrorRateLimiter.js.map +1 -0
  28. package/dist/replication/MongoManager.d.ts +14 -0
  29. package/dist/replication/MongoManager.js +36 -0
  30. package/dist/replication/MongoManager.js.map +1 -0
  31. package/dist/replication/MongoRelation.d.ts +9 -0
  32. package/dist/replication/MongoRelation.js +174 -0
  33. package/dist/replication/MongoRelation.js.map +1 -0
  34. package/dist/replication/replication-index.d.ts +4 -0
  35. package/dist/replication/replication-index.js +5 -0
  36. package/dist/replication/replication-index.js.map +1 -0
  37. package/dist/types/types.d.ts +51 -0
  38. package/dist/types/types.js +37 -0
  39. package/dist/types/types.js.map +1 -0
  40. package/package.json +47 -0
  41. package/src/api/MongoRouteAPIAdapter.ts +86 -0
  42. package/src/index.ts +5 -0
  43. package/src/module/MongoModule.ts +52 -0
  44. package/src/replication/ChangeStream.ts +503 -0
  45. package/src/replication/ChangeStreamReplicationJob.ts +104 -0
  46. package/src/replication/ChangeStreamReplicator.ts +36 -0
  47. package/src/replication/ConnectionManagerFactory.ts +27 -0
  48. package/src/replication/MongoErrorRateLimiter.ts +45 -0
  49. package/src/replication/MongoManager.ts +47 -0
  50. package/src/replication/MongoRelation.ts +156 -0
  51. package/src/replication/replication-index.ts +4 -0
  52. package/src/types/types.ts +65 -0
  53. package/test/src/change_stream.test.ts +306 -0
  54. package/test/src/change_stream_utils.ts +148 -0
  55. package/test/src/env.ts +7 -0
  56. package/test/src/mongo_test.test.ts +219 -0
  57. package/test/src/setup.ts +7 -0
  58. package/test/src/util.ts +52 -0
  59. package/test/tsconfig.json +28 -0
  60. package/tsconfig.json +28 -0
  61. package/tsconfig.tsbuildinfo +1 -0
  62. package/vitest.config.ts +9 -0
@@ -0,0 +1,389 @@
1
+ import { container, logger } from '@powersync/lib-services-framework';
2
+ import { Metrics } from '@powersync/service-core';
3
+ import { constructAfterRecord, createCheckpoint, getMongoLsn, getMongoRelation, mongoLsnToTimestamp } from './MongoRelation.js';
4
+ export const ZERO_LSN = '0000000000000000';
5
+ export class MissingReplicationSlotError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ }
9
+ }
10
+ export class ChangeStream {
11
+ constructor(options) {
12
+ this.connection_id = 1;
13
+ this.relation_cache = new Map();
14
+ this.storage = options.storage;
15
+ this.group_id = options.storage.group_id;
16
+ this.connections = options.connections;
17
+ this.client = this.connections.client;
18
+ this.defaultDb = this.connections.db;
19
+ this.sync_rules = options.storage.getParsedSyncRules({
20
+ defaultSchema: this.defaultDb.databaseName
21
+ });
22
+ this.abort_signal = options.abort_signal;
23
+ this.abort_signal.addEventListener('abort', () => {
24
+ // TODO: Fast abort?
25
+ }, { once: true });
26
+ }
27
+ get stopped() {
28
+ return this.abort_signal.aborted;
29
+ }
30
+ async getQualifiedTableNames(batch, tablePattern) {
31
+ const schema = tablePattern.schema;
32
+ if (tablePattern.connectionTag != this.connections.connectionTag) {
33
+ return [];
34
+ }
35
+ let nameFilter;
36
+ if (tablePattern.isWildcard) {
37
+ nameFilter = new RegExp('^' + escapeRegExp(tablePattern.tablePrefix));
38
+ }
39
+ else {
40
+ nameFilter = tablePattern.name;
41
+ }
42
+ let result = [];
43
+ // Check if the collection exists
44
+ const collections = await this.client
45
+ .db(schema)
46
+ .listCollections({
47
+ name: nameFilter
48
+ }, { nameOnly: true })
49
+ .toArray();
50
+ for (let collection of collections) {
51
+ const table = await this.handleRelation(batch, {
52
+ name: collection.name,
53
+ schema,
54
+ objectId: collection.name,
55
+ replicationColumns: [{ name: '_id' }]
56
+ },
57
+ // This is done as part of the initial setup - snapshot is handled elsewhere
58
+ { snapshot: false });
59
+ result.push(table);
60
+ }
61
+ return result;
62
+ }
63
+ async initSlot() {
64
+ const status = await this.storage.getStatus();
65
+ if (status.snapshot_done && status.checkpoint_lsn) {
66
+ logger.info(`Initial replication already done`);
67
+ return { needsInitialSync: false };
68
+ }
69
+ return { needsInitialSync: true };
70
+ }
71
+ async estimatedCount(table) {
72
+ const db = this.client.db(table.schema);
73
+ const count = db.collection(table.table).estimatedDocumentCount();
74
+ return `~${count}`;
75
+ }
76
+ /**
77
+ * Start initial replication.
78
+ *
79
+ * If (partial) replication was done before on this slot, this clears the state
80
+ * and starts again from scratch.
81
+ */
82
+ async startInitialReplication() {
83
+ await this.storage.clear();
84
+ await this.initialReplication();
85
+ }
86
+ async initialReplication() {
87
+ const sourceTables = this.sync_rules.getSourceTables();
88
+ await this.client.connect();
89
+ const hello = await this.defaultDb.command({ hello: 1 });
90
+ const startTime = hello.lastWrite?.majorityOpTime?.ts;
91
+ if (hello.msg == 'isdbgrid') {
92
+ throw new Error('Sharded MongoDB Clusters are not supported yet (including MongoDB Serverless instances).');
93
+ }
94
+ else if (hello.setName == null) {
95
+ throw new Error('Standalone MongoDB instances are not supported - use a replicaset.');
96
+ }
97
+ else if (startTime == null) {
98
+ // Not known where this would happen apart from the above cases
99
+ throw new Error('MongoDB lastWrite timestamp not found.');
100
+ }
101
+ const session = await this.client.startSession({
102
+ snapshot: true
103
+ });
104
+ try {
105
+ await this.storage.startBatch({ zeroLSN: ZERO_LSN, defaultSchema: this.defaultDb.databaseName }, async (batch) => {
106
+ for (let tablePattern of sourceTables) {
107
+ const tables = await this.getQualifiedTableNames(batch, tablePattern);
108
+ for (let table of tables) {
109
+ await this.snapshotTable(batch, table, session);
110
+ await batch.markSnapshotDone([table], ZERO_LSN);
111
+ await touch();
112
+ }
113
+ }
114
+ const snapshotTime = session.clusterTime?.clusterTime ?? startTime;
115
+ if (snapshotTime != null) {
116
+ const lsn = getMongoLsn(snapshotTime);
117
+ logger.info(`Snapshot commit at ${snapshotTime.inspect()} / ${lsn}`);
118
+ // keepalive() does an auto-commit if there is data
119
+ await batch.flush();
120
+ await batch.keepalive(lsn);
121
+ }
122
+ else {
123
+ throw new Error(`No snapshot clusterTime available.`);
124
+ }
125
+ });
126
+ }
127
+ finally {
128
+ session.endSession();
129
+ }
130
+ }
131
+ getSourceNamespaceFilters() {
132
+ const sourceTables = this.sync_rules.getSourceTables();
133
+ let $inFilters = [{ db: this.defaultDb.databaseName, coll: '_powersync_checkpoints' }];
134
+ let $refilters = [];
135
+ for (let tablePattern of sourceTables) {
136
+ if (tablePattern.connectionTag != this.connections.connectionTag) {
137
+ continue;
138
+ }
139
+ if (tablePattern.isWildcard) {
140
+ $refilters.push({ db: tablePattern.schema, coll: new RegExp('^' + escapeRegExp(tablePattern.tablePrefix)) });
141
+ }
142
+ else {
143
+ $inFilters.push({
144
+ db: tablePattern.schema,
145
+ coll: tablePattern.name
146
+ });
147
+ }
148
+ }
149
+ if ($refilters.length > 0) {
150
+ return { $or: [{ ns: { $in: $inFilters } }, ...$refilters] };
151
+ }
152
+ return { ns: { $in: $inFilters } };
153
+ }
154
+ static *getQueryData(results) {
155
+ for (let row of results) {
156
+ yield constructAfterRecord(row);
157
+ }
158
+ }
159
+ async snapshotTable(batch, table, session) {
160
+ logger.info(`Replicating ${table.qualifiedName}`);
161
+ const estimatedCount = await this.estimatedCount(table);
162
+ let at = 0;
163
+ const db = this.client.db(table.schema);
164
+ const collection = db.collection(table.table);
165
+ const query = collection.find({}, { session });
166
+ const cursor = query.stream();
167
+ for await (let document of cursor) {
168
+ if (this.abort_signal.aborted) {
169
+ throw new Error(`Aborted initial replication`);
170
+ }
171
+ const record = constructAfterRecord(document);
172
+ // This auto-flushes when the batch reaches its size limit
173
+ await batch.save({
174
+ tag: 'insert',
175
+ sourceTable: table,
176
+ before: undefined,
177
+ beforeReplicaId: undefined,
178
+ after: record,
179
+ afterReplicaId: document._id
180
+ });
181
+ at += 1;
182
+ Metrics.getInstance().rows_replicated_total.add(1);
183
+ await touch();
184
+ }
185
+ await batch.flush();
186
+ }
187
+ async getRelation(batch, descriptor) {
188
+ const existing = this.relation_cache.get(descriptor.objectId);
189
+ if (existing != null) {
190
+ return existing;
191
+ }
192
+ return this.handleRelation(batch, descriptor, { snapshot: false });
193
+ }
194
+ async handleRelation(batch, descriptor, options) {
195
+ const snapshot = options.snapshot;
196
+ if (!descriptor.objectId && typeof descriptor.objectId != 'string') {
197
+ throw new Error('objectId expected');
198
+ }
199
+ const result = await this.storage.resolveTable({
200
+ group_id: this.group_id,
201
+ connection_id: this.connection_id,
202
+ connection_tag: this.connections.connectionTag,
203
+ entity_descriptor: descriptor,
204
+ sync_rules: this.sync_rules
205
+ });
206
+ this.relation_cache.set(descriptor.objectId, result.table);
207
+ // Drop conflicting tables. This includes for example renamed tables.
208
+ await batch.drop(result.dropTables);
209
+ // Snapshot if:
210
+ // 1. Snapshot is requested (false for initial snapshot, since that process handles it elsewhere)
211
+ // 2. Snapshot is not already done, AND:
212
+ // 3. The table is used in sync rules.
213
+ const shouldSnapshot = snapshot && !result.table.snapshotComplete && result.table.syncAny;
214
+ if (shouldSnapshot) {
215
+ // Truncate this table, in case a previous snapshot was interrupted.
216
+ await batch.truncate([result.table]);
217
+ await this.snapshotTable(batch, result.table);
218
+ const no_checkpoint_before_lsn = await createCheckpoint(this.client, this.defaultDb);
219
+ const [table] = await batch.markSnapshotDone([result.table], no_checkpoint_before_lsn);
220
+ return table;
221
+ }
222
+ return result.table;
223
+ }
224
+ async writeChange(batch, table, change) {
225
+ if (!table.syncAny) {
226
+ logger.debug(`Collection ${table.qualifiedName} not used in sync rules - skipping`);
227
+ return null;
228
+ }
229
+ Metrics.getInstance().rows_replicated_total.add(1);
230
+ if (change.operationType == 'insert') {
231
+ const baseRecord = constructAfterRecord(change.fullDocument);
232
+ return await batch.save({
233
+ tag: 'insert',
234
+ sourceTable: table,
235
+ before: undefined,
236
+ beforeReplicaId: undefined,
237
+ after: baseRecord,
238
+ afterReplicaId: change.documentKey._id
239
+ });
240
+ }
241
+ else if (change.operationType == 'update' || change.operationType == 'replace') {
242
+ if (change.fullDocument == null) {
243
+ // Treat as delete
244
+ return await batch.save({
245
+ tag: 'delete',
246
+ sourceTable: table,
247
+ before: undefined,
248
+ beforeReplicaId: change.documentKey._id
249
+ });
250
+ }
251
+ const after = constructAfterRecord(change.fullDocument);
252
+ return await batch.save({
253
+ tag: 'update',
254
+ sourceTable: table,
255
+ before: undefined,
256
+ beforeReplicaId: undefined,
257
+ after: after,
258
+ afterReplicaId: change.documentKey._id
259
+ });
260
+ }
261
+ else if (change.operationType == 'delete') {
262
+ return await batch.save({
263
+ tag: 'delete',
264
+ sourceTable: table,
265
+ before: undefined,
266
+ beforeReplicaId: change.documentKey._id
267
+ });
268
+ }
269
+ else {
270
+ throw new Error(`Unsupported operation: ${change.operationType}`);
271
+ }
272
+ }
273
+ async replicate() {
274
+ try {
275
+ // If anything errors here, the entire replication process is halted, and
276
+ // all connections automatically closed, including this one.
277
+ await this.initReplication();
278
+ await this.streamChanges();
279
+ }
280
+ catch (e) {
281
+ await this.storage.reportError(e);
282
+ throw e;
283
+ }
284
+ }
285
+ async initReplication() {
286
+ const result = await this.initSlot();
287
+ if (result.needsInitialSync) {
288
+ await this.startInitialReplication();
289
+ }
290
+ }
291
+ async streamChanges() {
292
+ // Auto-activate as soon as initial replication is done
293
+ await this.storage.autoActivate();
294
+ await this.storage.startBatch({ zeroLSN: ZERO_LSN, defaultSchema: this.defaultDb.databaseName }, async (batch) => {
295
+ const lastLsn = batch.lastCheckpointLsn;
296
+ const startAfter = mongoLsnToTimestamp(lastLsn) ?? undefined;
297
+ logger.info(`Resume streaming at ${startAfter?.inspect()} / ${lastLsn}`);
298
+ // TODO: Use changeStreamSplitLargeEvent
299
+ const pipeline = [
300
+ {
301
+ $match: this.getSourceNamespaceFilters()
302
+ }
303
+ ];
304
+ const stream = this.client.watch(pipeline, {
305
+ startAtOperationTime: startAfter,
306
+ showExpandedEvents: true,
307
+ useBigInt64: true,
308
+ maxAwaitTimeMS: 200,
309
+ fullDocument: 'updateLookup'
310
+ });
311
+ if (this.abort_signal.aborted) {
312
+ stream.close();
313
+ return;
314
+ }
315
+ this.abort_signal.addEventListener('abort', () => {
316
+ stream.close();
317
+ });
318
+ let waitForCheckpointLsn = null;
319
+ while (true) {
320
+ if (this.abort_signal.aborted) {
321
+ break;
322
+ }
323
+ const changeDocument = await stream.tryNext();
324
+ if (changeDocument == null || this.abort_signal.aborted) {
325
+ continue;
326
+ }
327
+ await touch();
328
+ if (startAfter != null && changeDocument.clusterTime?.lte(startAfter)) {
329
+ continue;
330
+ }
331
+ // console.log('event', changeDocument);
332
+ if ((changeDocument.operationType == 'insert' ||
333
+ changeDocument.operationType == 'update' ||
334
+ changeDocument.operationType == 'replace') &&
335
+ changeDocument.ns.coll == '_powersync_checkpoints') {
336
+ const lsn = getMongoLsn(changeDocument.clusterTime);
337
+ if (waitForCheckpointLsn != null && lsn >= waitForCheckpointLsn) {
338
+ waitForCheckpointLsn = null;
339
+ }
340
+ await batch.flush();
341
+ await batch.keepalive(lsn);
342
+ }
343
+ else if (changeDocument.operationType == 'insert' ||
344
+ changeDocument.operationType == 'update' ||
345
+ changeDocument.operationType == 'replace' ||
346
+ changeDocument.operationType == 'delete') {
347
+ if (waitForCheckpointLsn == null) {
348
+ waitForCheckpointLsn = await createCheckpoint(this.client, this.defaultDb);
349
+ }
350
+ const rel = getMongoRelation(changeDocument.ns);
351
+ const table = await this.getRelation(batch, rel);
352
+ if (table.syncAny) {
353
+ await this.writeChange(batch, table, changeDocument);
354
+ }
355
+ }
356
+ else if (changeDocument.operationType == 'drop') {
357
+ const rel = getMongoRelation(changeDocument.ns);
358
+ const table = await this.getRelation(batch, rel);
359
+ if (table.syncAny) {
360
+ await batch.drop([table]);
361
+ this.relation_cache.delete(table.objectId);
362
+ }
363
+ }
364
+ else if (changeDocument.operationType == 'rename') {
365
+ const relFrom = getMongoRelation(changeDocument.ns);
366
+ const relTo = getMongoRelation(changeDocument.to);
367
+ const tableFrom = await this.getRelation(batch, relFrom);
368
+ if (tableFrom.syncAny) {
369
+ await batch.drop([tableFrom]);
370
+ this.relation_cache.delete(tableFrom.objectId);
371
+ }
372
+ // Here we do need to snapshot the new table
373
+ await this.handleRelation(batch, relTo, { snapshot: true });
374
+ }
375
+ }
376
+ });
377
+ }
378
+ }
379
+ async function touch() {
380
+ // FIXME: The hosted Kubernetes probe does not actually check the timestamp on this.
381
+ // FIXME: We need a timeout of around 5+ minutes in Kubernetes if we do start checking the timestamp,
382
+ // or reduce PING_INTERVAL here.
383
+ return container.probes.touch();
384
+ }
385
+ function escapeRegExp(string) {
386
+ // https://stackoverflow.com/a/3561711/214837
387
+ return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
388
+ }
389
+ //# sourceMappingURL=ChangeStream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChangeStream.js","sourceRoot":"","sources":["../../src/replication/ChangeStream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { replication } from '@powersync/service-core';
2
+ import { ConnectionManagerFactory } from './ConnectionManagerFactory.js';
3
+ export interface ChangeStreamReplicationJobOptions extends replication.AbstractReplicationJobOptions {
4
+ connectionFactory: ConnectionManagerFactory;
5
+ }
6
+ export declare class ChangeStreamReplicationJob extends replication.AbstractReplicationJob {
7
+ private connectionFactory;
8
+ private readonly connectionManager;
9
+ constructor(options: ChangeStreamReplicationJobOptions);
10
+ cleanUp(): Promise<void>;
11
+ keepAlive(): Promise<void>;
12
+ private get slotName();
13
+ replicate(): Promise<void>;
14
+ replicateLoop(): Promise<void>;
15
+ replicateOnce(): Promise<void>;
16
+ }
@@ -0,0 +1,90 @@
1
+ import { container } from '@powersync/lib-services-framework';
2
+ import { MissingReplicationSlotError, ChangeStream } from './ChangeStream.js';
3
+ import { replication } from '@powersync/service-core';
4
+ import * as mongo from 'mongodb';
5
+ export class ChangeStreamReplicationJob extends replication.AbstractReplicationJob {
6
+ constructor(options) {
7
+ super(options);
8
+ this.connectionFactory = options.connectionFactory;
9
+ this.connectionManager = this.connectionFactory.create();
10
+ }
11
+ async cleanUp() {
12
+ // TODO: Implement?
13
+ }
14
+ async keepAlive() {
15
+ // TODO: Implement?
16
+ }
17
+ get slotName() {
18
+ return this.options.storage.slot_name;
19
+ }
20
+ async replicate() {
21
+ try {
22
+ await this.replicateLoop();
23
+ }
24
+ catch (e) {
25
+ // Fatal exception
26
+ container.reporter.captureException(e, {
27
+ metadata: {}
28
+ });
29
+ this.logger.error(`Replication failed`, e);
30
+ if (e instanceof MissingReplicationSlotError) {
31
+ // This stops replication on this slot, and creates a new slot
32
+ await this.options.storage.factory.slotRemoved(this.slotName);
33
+ }
34
+ }
35
+ finally {
36
+ this.abortController.abort();
37
+ }
38
+ }
39
+ async replicateLoop() {
40
+ while (!this.isStopped) {
41
+ await this.replicateOnce();
42
+ if (!this.isStopped) {
43
+ await new Promise((resolve) => setTimeout(resolve, 5000));
44
+ }
45
+ }
46
+ }
47
+ async replicateOnce() {
48
+ // New connections on every iteration (every error with retry),
49
+ // otherwise we risk repeating errors related to the connection,
50
+ // such as caused by cached PG schemas.
51
+ const connectionManager = this.connectionFactory.create();
52
+ try {
53
+ await this.rateLimiter?.waitUntilAllowed({ signal: this.abortController.signal });
54
+ if (this.isStopped) {
55
+ return;
56
+ }
57
+ const stream = new ChangeStream({
58
+ abort_signal: this.abortController.signal,
59
+ storage: this.options.storage,
60
+ connections: connectionManager
61
+ });
62
+ await stream.replicate();
63
+ }
64
+ catch (e) {
65
+ if (this.abortController.signal.aborted) {
66
+ return;
67
+ }
68
+ this.logger.error(`Replication error`, e);
69
+ if (e.cause != null) {
70
+ // Without this additional log, the cause may not be visible in the logs.
71
+ this.logger.error(`cause`, e.cause);
72
+ }
73
+ if (e instanceof mongo.MongoError && e.hasErrorLabel('NonResumableChangeStreamError')) {
74
+ throw new MissingReplicationSlotError(e.message);
75
+ }
76
+ else {
77
+ // Report the error if relevant, before retrying
78
+ container.reporter.captureException(e, {
79
+ metadata: {}
80
+ });
81
+ // This sets the retry delay
82
+ this.rateLimiter?.reportError(e);
83
+ }
84
+ }
85
+ finally {
86
+ await connectionManager.end();
87
+ }
88
+ }
89
+ }
90
+ //# sourceMappingURL=ChangeStreamReplicationJob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChangeStreamReplicationJob.js","sourceRoot":"","sources":["../../src/replication/ChangeStreamReplicationJob.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { storage, replication } from '@powersync/service-core';
2
+ import { ChangeStreamReplicationJob } from './ChangeStreamReplicationJob.js';
3
+ import { ConnectionManagerFactory } from './ConnectionManagerFactory.js';
4
+ export interface WalStreamReplicatorOptions extends replication.AbstractReplicatorOptions {
5
+ connectionFactory: ConnectionManagerFactory;
6
+ }
7
+ export declare class ChangeStreamReplicator extends replication.AbstractReplicator<ChangeStreamReplicationJob> {
8
+ private readonly connectionFactory;
9
+ constructor(options: WalStreamReplicatorOptions);
10
+ createJob(options: replication.CreateJobOptions): ChangeStreamReplicationJob;
11
+ cleanUp(syncRulesStorage: storage.SyncRulesBucketStorage): Promise<void>;
12
+ stop(): Promise<void>;
13
+ }
@@ -0,0 +1,26 @@
1
+ import { replication } from '@powersync/service-core';
2
+ import { ChangeStreamReplicationJob } from './ChangeStreamReplicationJob.js';
3
+ import { MongoErrorRateLimiter } from './MongoErrorRateLimiter.js';
4
+ export class ChangeStreamReplicator extends replication.AbstractReplicator {
5
+ constructor(options) {
6
+ super(options);
7
+ this.connectionFactory = options.connectionFactory;
8
+ }
9
+ createJob(options) {
10
+ return new ChangeStreamReplicationJob({
11
+ id: this.createJobId(options.storage.group_id),
12
+ storage: options.storage,
13
+ connectionFactory: this.connectionFactory,
14
+ lock: options.lock,
15
+ rateLimiter: new MongoErrorRateLimiter()
16
+ });
17
+ }
18
+ async cleanUp(syncRulesStorage) {
19
+ // TODO: Implement anything?
20
+ }
21
+ async stop() {
22
+ await super.stop();
23
+ await this.connectionFactory.shutdown();
24
+ }
25
+ }
26
+ //# sourceMappingURL=ChangeStreamReplicator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChangeStreamReplicator.js","sourceRoot":"","sources":["../../src/replication/ChangeStreamReplicator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAMnE,MAAM,OAAO,sBAAuB,SAAQ,WAAW,CAAC,kBAA8C;IAGpG,YAAY,OAAmC;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IACrD,CAAC;IAED,SAAS,CAAC,OAAqC;QAC7C,OAAO,IAAI,0BAA0B,CAAC;YACpC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;YAC9C,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;YACzC,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,WAAW,EAAE,IAAI,qBAAqB,EAAE;SACzC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,gBAAgD;QAC5D,4BAA4B;IAC9B,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC;IAC1C,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import { NormalizedMongoConnectionConfig } from '../types/types.js';
2
+ import { MongoManager } from './MongoManager.js';
3
+ export declare class ConnectionManagerFactory {
4
+ private readonly connectionManagers;
5
+ private readonly dbConnectionConfig;
6
+ constructor(dbConnectionConfig: NormalizedMongoConnectionConfig);
7
+ create(): MongoManager;
8
+ shutdown(): Promise<void>;
9
+ }
@@ -0,0 +1,21 @@
1
+ import { logger } from '@powersync/lib-services-framework';
2
+ import { MongoManager } from './MongoManager.js';
3
+ export class ConnectionManagerFactory {
4
+ constructor(dbConnectionConfig) {
5
+ this.dbConnectionConfig = dbConnectionConfig;
6
+ this.connectionManagers = [];
7
+ }
8
+ create() {
9
+ const manager = new MongoManager(this.dbConnectionConfig);
10
+ this.connectionManagers.push(manager);
11
+ return manager;
12
+ }
13
+ async shutdown() {
14
+ logger.info('Shutting down MongoDB connection Managers...');
15
+ for (const manager of this.connectionManagers) {
16
+ await manager.end();
17
+ }
18
+ logger.info('MongoDB connection Managers shutdown completed.');
19
+ }
20
+ }
21
+ //# sourceMappingURL=ConnectionManagerFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConnectionManagerFactory.js","sourceRoot":"","sources":["../../src/replication/ConnectionManagerFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,OAAO,wBAAwB;IAInC,YAAY,kBAAmD;QAC7D,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAC7C,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC/B,CAAC;IAED,MAAM;QACJ,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC1D,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;QAC5D,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,kBAAkB,EAAE;YAC7C,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC;SACrB;QACD,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IACjE,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { ErrorRateLimiter } from '@powersync/service-core';
3
+ export declare class MongoErrorRateLimiter implements ErrorRateLimiter {
4
+ nextAllowed: number;
5
+ waitUntilAllowed(options?: {
6
+ signal?: AbortSignal | undefined;
7
+ } | undefined): Promise<void>;
8
+ mayPing(): boolean;
9
+ reportError(e: any): void;
10
+ private setDelay;
11
+ }