@powersync/service-module-mongodb 0.12.9 → 0.12.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @powersync/service-module-mongodb
2
2
 
3
+ ## 0.12.11
4
+
5
+ ### Patch Changes
6
+
7
+ - d889219: Fix memory leaks when retrying replication after errors.
8
+ - Updated dependencies [b364581]
9
+ - Updated dependencies [d889219]
10
+ - Updated dependencies [0ace0d3]
11
+ - Updated dependencies [7eb7957]
12
+ - Updated dependencies [b364581]
13
+ - @powersync/service-core@1.16.2
14
+
15
+ ## 0.12.10
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [c6bdb4f]
20
+ - @powersync/service-core@1.16.1
21
+
3
22
  ## 0.12.9
4
23
 
5
24
  ### Patch Changes
@@ -9,9 +9,7 @@ export declare class ChangeStreamReplicationJob extends replication.AbstractRepl
9
9
  constructor(options: ChangeStreamReplicationJobOptions);
10
10
  cleanUp(): Promise<void>;
11
11
  keepAlive(): Promise<void>;
12
- private get slotName();
13
12
  replicate(): Promise<void>;
14
- replicateLoop(): Promise<void>;
15
13
  replicateOnce(): Promise<void>;
16
14
  getReplicationLagMillis(): Promise<number | undefined>;
17
15
  }
@@ -16,36 +16,32 @@ export class ChangeStreamReplicationJob extends replication.AbstractReplicationJ
16
16
  async keepAlive() {
17
17
  // Nothing needed here
18
18
  }
19
- get slotName() {
20
- return this.options.storage.slot_name;
21
- }
22
19
  async replicate() {
23
20
  try {
24
- await this.replicateLoop();
21
+ await this.replicateOnce();
25
22
  }
26
23
  catch (e) {
27
- // Fatal exception
28
- container.reporter.captureException(e, {
29
- metadata: {}
30
- });
31
- this.logger.error(`Replication failed`, e);
24
+ if (!this.abortController.signal.aborted) {
25
+ container.reporter.captureException(e, {
26
+ metadata: {}
27
+ });
28
+ this.logger.error(`Replication error`, e);
29
+ if (e.cause != null) {
30
+ // Without this additional log, the cause may not be visible in the logs.
31
+ this.logger.error(`cause`, e.cause);
32
+ }
33
+ this.rateLimiter.reportError(e);
34
+ }
32
35
  if (e instanceof ChangeStreamInvalidatedError) {
33
36
  // This stops replication and restarts with a new instance
34
37
  await this.options.storage.factory.restartReplication(this.storage.group_id);
35
38
  }
39
+ // No need to rethrow - the error is already logged, and retry behavior is the same on error
36
40
  }
37
41
  finally {
38
42
  this.abortController.abort();
39
43
  }
40
44
  }
41
- async replicateLoop() {
42
- while (!this.isStopped) {
43
- await this.replicateOnce();
44
- if (!this.isStopped) {
45
- await new Promise((resolve) => setTimeout(resolve, 5000));
46
- }
47
- }
48
- }
49
45
  async replicateOnce() {
50
46
  // New connections on every iteration (every error with retry),
51
47
  // otherwise we risk repeating errors related to the connection,
@@ -66,27 +62,6 @@ export class ChangeStreamReplicationJob extends replication.AbstractReplicationJ
66
62
  this.lastStream = stream;
67
63
  await stream.replicate();
68
64
  }
69
- catch (e) {
70
- if (this.abortController.signal.aborted) {
71
- return;
72
- }
73
- this.logger.error(`Replication error`, e);
74
- if (e.cause != null) {
75
- // Without this additional log, the cause may not be visible in the logs.
76
- this.logger.error(`cause`, e.cause);
77
- }
78
- if (e instanceof ChangeStreamInvalidatedError) {
79
- throw e;
80
- }
81
- else {
82
- // Report the error if relevant, before retrying
83
- container.reporter.captureException(e, {
84
- metadata: {}
85
- });
86
- // This sets the retry delay
87
- this.rateLimiter?.reportError(e);
88
- }
89
- }
90
65
  finally {
91
66
  await connectionManager.end();
92
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ChangeStreamReplicationJob.js","sourceRoot":"","sources":["../../src/replication/ChangeStreamReplicationJob.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChangeStreamReplicationJob.js","sourceRoot":"","sources":["../../src/replication/ChangeStreamReplicationJob.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { replication } from '@powersync/service-core';
2
2
  import { ChangeStreamReplicationJob } from './ChangeStreamReplicationJob.js';
3
- import { MongoErrorRateLimiter } from './MongoErrorRateLimiter.js';
4
3
  import { MongoModule } from '../module/MongoModule.js';
5
4
  import { MongoLSN } from '../common/MongoLSN.js';
6
5
  import { timestampToDate } from './replication-utils.js';
@@ -17,7 +16,7 @@ export class ChangeStreamReplicator extends replication.AbstractReplicator {
17
16
  metrics: this.metrics,
18
17
  connectionFactory: this.connectionFactory,
19
18
  lock: options.lock,
20
- rateLimiter: new MongoErrorRateLimiter()
19
+ rateLimiter: this.rateLimiter
21
20
  });
22
21
  }
23
22
  async cleanUp(syncRulesStorage) {
@@ -1 +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;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAMzD,MAAM,OAAO,sBAAuB,SAAQ,WAAW,CAAC,kBAA8C;IACnF,iBAAiB,CAA2B;IAE7D,YAAY,OAAsC;QAChD,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,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,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;IAED,KAAK,CAAC,cAAc;QAClB,OAAO,MAAM,WAAW,CAAC,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IACrF,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,uBAAuB,EAAE,CAAC;QAClD,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YAChB,OAAO,GAAG,CAAC;QACb,CAAC;QAED,qEAAqE;QACrE,8CAA8C;QAC9C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,yBAAyB,EAAE,CAAC;QAC/D,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,oDAAoD;QACpD,MAAM,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC;QACxC,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YAChB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;CACF"}
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;AAG7E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAMzD,MAAM,OAAO,sBAAuB,SAAQ,WAAW,CAAC,kBAA8C;IACnF,iBAAiB,CAA2B;IAE7D,YAAY,OAAsC;QAChD,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,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;YACzC,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,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;IAED,KAAK,CAAC,cAAc;QAClB,OAAO,MAAM,WAAW,CAAC,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IACrF,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,uBAAuB,EAAE,CAAC;QAClD,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YAChB,OAAO,GAAG,CAAC;QACb,CAAC;QAED,qEAAqE;QACrE,8CAA8C;QAC9C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,yBAAyB,EAAE,CAAC;QAC/D,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,oDAAoD;QACpD,MAAM,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC;QACxC,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YAChB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;CACF"}
@@ -1,20 +1,24 @@
1
1
  import { logger } from '@powersync/lib-services-framework';
2
2
  import { MongoManager } from './MongoManager.js';
3
3
  export class ConnectionManagerFactory {
4
- connectionManagers;
4
+ connectionManagers = new Set();
5
5
  dbConnectionConfig;
6
6
  constructor(dbConnectionConfig) {
7
7
  this.dbConnectionConfig = dbConnectionConfig;
8
- this.connectionManagers = [];
9
8
  }
10
9
  create() {
11
10
  const manager = new MongoManager(this.dbConnectionConfig);
12
- this.connectionManagers.push(manager);
11
+ this.connectionManagers.add(manager);
12
+ manager.registerListener({
13
+ onEnded: () => {
14
+ this.connectionManagers.delete(manager);
15
+ }
16
+ });
13
17
  return manager;
14
18
  }
15
19
  async shutdown() {
16
20
  logger.info('Shutting down MongoDB connection Managers...');
17
- for (const manager of this.connectionManagers) {
21
+ for (const manager of [...this.connectionManagers]) {
18
22
  await manager.end();
19
23
  }
20
24
  logger.info('MongoDB connection Managers shutdown completed.');
@@ -1 +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;IAClB,kBAAkB,CAAiB;IACpC,kBAAkB,CAAkC;IAEpE,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,CAAC;YAC9C,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC;QACtB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IACjE,CAAC;CACF"}
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;IAClB,kBAAkB,GAAG,IAAI,GAAG,EAAgB,CAAC;IAC9C,kBAAkB,CAAkC;IAEpE,YAAY,kBAAmD;QAC7D,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IAC/C,CAAC;IAED,MAAM;QACJ,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC1D,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAErC,OAAO,CAAC,gBAAgB,CAAC;YACvB,OAAO,EAAE,GAAG,EAAE;gBACZ,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC1C,CAAC;SACF,CAAC,CAAC;QACH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;QAC5D,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACnD,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC;QACtB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IACjE,CAAC;CACF"}
@@ -1,9 +1,10 @@
1
1
  import { setTimeout } from 'timers/promises';
2
+ import { ChangeStreamInvalidatedError } from './ChangeStream.js';
2
3
  export class MongoErrorRateLimiter {
3
4
  nextAllowed = Date.now();
4
5
  async waitUntilAllowed(options) {
5
6
  const delay = Math.max(0, this.nextAllowed - Date.now());
6
- // Minimum delay between connections, even without errors
7
+ // Minimum delay between connections, even without errors (for the next attempt)
7
8
  this.setDelay(500);
8
9
  await setTimeout(delay, undefined, { signal: options?.signal });
9
10
  }
@@ -13,9 +14,13 @@ export class MongoErrorRateLimiter {
13
14
  reportError(e) {
14
15
  // FIXME: Check mongodb-specific requirements
15
16
  const message = e.message ?? '';
16
- if (message.includes('password authentication failed')) {
17
- // Wait 15 minutes, to avoid triggering Supabase's fail2ban
18
- this.setDelay(900_000);
17
+ if (e instanceof ChangeStreamInvalidatedError) {
18
+ // Short delay
19
+ this.setDelay(2_000);
20
+ }
21
+ else if (message.includes('Authentication failed')) {
22
+ // Wait 2 minutes, to avoid triggering too many authentication attempts
23
+ this.setDelay(120_000);
19
24
  }
20
25
  else if (message.includes('ENOTFOUND')) {
21
26
  // DNS lookup issue - incorrect URI or deleted instance
@@ -1 +1 @@
1
- {"version":3,"file":"MongoErrorRateLimiter.js","sourceRoot":"","sources":["../../src/replication/MongoErrorRateLimiter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,OAAO,qBAAqB;IAChC,WAAW,GAAW,IAAI,CAAC,GAAG,EAAE,CAAC;IAEjC,KAAK,CAAC,gBAAgB,CAAC,OAA0D;QAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACzD,yDAAyD;QACzD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACnB,MAAM,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC;IACxC,CAAC;IAED,WAAW,CAAC,CAAM;QAChB,6CAA6C;QAC7C,MAAM,OAAO,GAAI,CAAC,CAAC,OAAkB,IAAI,EAAE,CAAC;QAC5C,IAAI,OAAO,CAAC,QAAQ,CAAC,gCAAgC,CAAC,EAAE,CAAC;YACvD,2DAA2D;YAC3D,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACzC,uDAAuD;YACvD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAC5C,+BAA+B;YAC/B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAEO,QAAQ,CAAC,KAAa;QAC5B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;IACpE,CAAC;CACF"}
1
+ {"version":3,"file":"MongoErrorRateLimiter.js","sourceRoot":"","sources":["../../src/replication/MongoErrorRateLimiter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,OAAO,qBAAqB;IAChC,WAAW,GAAW,IAAI,CAAC,GAAG,EAAE,CAAC;IAEjC,KAAK,CAAC,gBAAgB,CAAC,OAA0D;QAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACzD,gFAAgF;QAChF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACnB,MAAM,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC;IACxC,CAAC;IAED,WAAW,CAAC,CAAM;QAChB,6CAA6C;QAC7C,MAAM,OAAO,GAAI,CAAC,CAAC,OAAkB,IAAI,EAAE,CAAC;QAC5C,IAAI,CAAC,YAAY,4BAA4B,EAAE,CAAC;YAC9C,cAAc;YACd,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC;YACrD,uEAAuE;YACvE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACzC,uDAAuD;YACvD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAC5C,+BAA+B;YAC/B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAEO,QAAQ,CAAC,KAAa;QAC5B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;IACpE,CAAC;CACF"}
@@ -1,14 +1,17 @@
1
1
  import { mongo } from '@powersync/lib-service-mongodb';
2
2
  import { NormalizedMongoConnectionConfig } from '../types/types.js';
3
+ import { BaseObserver } from '@powersync/lib-services-framework';
4
+ export interface MongoManagerListener {
5
+ onEnded(): void;
6
+ }
3
7
  /**
4
8
  * Manage a MongoDB source database connection.
5
9
  */
6
- export declare class MongoManager {
10
+ export declare class MongoManager extends BaseObserver<MongoManagerListener> {
7
11
  options: NormalizedMongoConnectionConfig;
8
12
  readonly client: mongo.MongoClient;
9
13
  readonly db: mongo.Db;
10
14
  constructor(options: NormalizedMongoConnectionConfig, overrides?: mongo.MongoClientOptions);
11
15
  get connectionTag(): string;
12
16
  end(): Promise<void>;
13
- destroy(): Promise<void>;
14
17
  }
@@ -1,13 +1,15 @@
1
1
  import { mongo } from '@powersync/lib-service-mongodb';
2
2
  import { BSON_DESERIALIZE_DATA_OPTIONS, POWERSYNC_VERSION } from '@powersync/service-core';
3
+ import { BaseObserver } from '@powersync/lib-services-framework';
3
4
  /**
4
5
  * Manage a MongoDB source database connection.
5
6
  */
6
- export class MongoManager {
7
+ export class MongoManager extends BaseObserver {
7
8
  options;
8
9
  client;
9
10
  db;
10
11
  constructor(options, overrides) {
12
+ super();
11
13
  this.options = options;
12
14
  // The pool is lazy - no connections are opened until a query is performed.
13
15
  this.client = new mongo.MongoClient(options.uri, {
@@ -46,9 +48,9 @@ export class MongoManager {
46
48
  }
47
49
  async end() {
48
50
  await this.client.close();
49
- }
50
- async destroy() {
51
- // TODO: Implement?
51
+ this.iterateListeners((listener) => {
52
+ listener.onEnded?.();
53
+ });
52
54
  }
53
55
  }
54
56
  //# sourceMappingURL=MongoManager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MongoManager.js","sourceRoot":"","sources":["../../src/replication/MongoManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAGvD,OAAO,EAAE,6BAA6B,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE3F;;GAEG;AACH,MAAM,OAAO,YAAY;IAKd;IAJO,MAAM,CAAoB;IAC1B,EAAE,CAAW;IAE7B,YACS,OAAwC,EAC/C,SAAoC;QAD7B,YAAO,GAAP,OAAO,CAAiC;QAG/C,2EAA2E;QAC3E,IAAI,CAAC,MAAM,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE;YAC/C,IAAI,EAAE;gBACJ,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;aAC3B;YAED,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,iCAAiC;YACjC,gBAAgB,EAAE,KAAK;YACvB,0CAA0C;YAC1C,eAAe,EAAE,MAAM;YACvB,6CAA6C;YAC7C,wBAAwB,EAAE,MAAM;YAEhC,sBAAsB;YACtB,OAAO,EAAE,aAAa,iBAAiB,EAAE;YACzC,sFAAsF;YACtF,UAAU,EAAE;gBACV,4CAA4C;gBAC5C,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,iBAAiB;aAC3B;YAED,8BAA8B;YAC9B,2CAA2C;YAC3C,yFAAyF;YACzF,WAAW,EAAE,CAAC;YAEd,aAAa,EAAE,CAAC;YAChB,aAAa,EAAE,MAAM;YAErB,GAAG,6BAA6B;YAEhC,GAAG,SAAS;SACb,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,IAAW,aAAa;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,GAAG;QACP,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,OAAO;QACX,mBAAmB;IACrB,CAAC;CACF"}
1
+ {"version":3,"file":"MongoManager.js","sourceRoot":"","sources":["../../src/replication/MongoManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAGvD,OAAO,EAAE,6BAA6B,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAMjE;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,YAAkC;IAKzD;IAJO,MAAM,CAAoB;IAC1B,EAAE,CAAW;IAE7B,YACS,OAAwC,EAC/C,SAAoC;QAEpC,KAAK,EAAE,CAAC;QAHD,YAAO,GAAP,OAAO,CAAiC;QAI/C,2EAA2E;QAC3E,IAAI,CAAC,MAAM,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE;YAC/C,IAAI,EAAE;gBACJ,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;aAC3B;YAED,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,iCAAiC;YACjC,gBAAgB,EAAE,KAAK;YACvB,0CAA0C;YAC1C,eAAe,EAAE,MAAM;YACvB,6CAA6C;YAC7C,wBAAwB,EAAE,MAAM;YAEhC,sBAAsB;YACtB,OAAO,EAAE,aAAa,iBAAiB,EAAE;YACzC,sFAAsF;YACtF,UAAU,EAAE;gBACV,4CAA4C;gBAC5C,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,iBAAiB;aAC3B;YAED,8BAA8B;YAC9B,2CAA2C;YAC3C,yFAAyF;YACzF,WAAW,EAAE,CAAC;YAEd,aAAa,EAAE,CAAC;YAChB,aAAa,EAAE,MAAM;YAErB,GAAG,6BAA6B;YAEhC,GAAG,SAAS;SACb,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,IAAW,aAAa;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,GAAG;QACP,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,gBAAgB,CAAC,CAAC,QAAQ,EAAE,EAAE;YACjC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@powersync/service-module-mongodb",
3
3
  "repository": "https://github.com/powersync-ja/powersync-service",
4
4
  "types": "dist/index.d.ts",
5
- "version": "0.12.9",
5
+ "version": "0.12.11",
6
6
  "main": "dist/index.js",
7
7
  "license": "FSL-1.1-ALv2",
8
8
  "type": "module",
@@ -27,15 +27,15 @@
27
27
  "uuid": "^11.1.0",
28
28
  "@powersync/lib-service-mongodb": "0.6.11",
29
29
  "@powersync/lib-services-framework": "0.7.9",
30
- "@powersync/service-core": "1.16.0",
30
+ "@powersync/service-core": "1.16.2",
31
31
  "@powersync/service-jsonbig": "0.17.12",
32
32
  "@powersync/service-sync-rules": "0.29.6",
33
33
  "@powersync/service-types": "0.13.1"
34
34
  },
35
35
  "devDependencies": {
36
- "@powersync/service-core-tests": "0.12.9",
37
- "@powersync/service-module-mongodb-storage": "0.12.9",
38
- "@powersync/service-module-postgres-storage": "0.10.9"
36
+ "@powersync/service-core-tests": "0.12.11",
37
+ "@powersync/service-module-mongodb-storage": "0.12.11",
38
+ "@powersync/service-module-postgres-storage": "0.10.11"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b",
@@ -27,39 +27,35 @@ export class ChangeStreamReplicationJob extends replication.AbstractReplicationJ
27
27
  // Nothing needed here
28
28
  }
29
29
 
30
- private get slotName() {
31
- return this.options.storage.slot_name;
32
- }
33
-
34
30
  async replicate() {
35
31
  try {
36
- await this.replicateLoop();
32
+ await this.replicateOnce();
37
33
  } catch (e) {
38
- // Fatal exception
39
- container.reporter.captureException(e, {
40
- metadata: {}
41
- });
42
- this.logger.error(`Replication failed`, e);
34
+ if (!this.abortController.signal.aborted) {
35
+ container.reporter.captureException(e, {
36
+ metadata: {}
37
+ });
38
+
39
+ this.logger.error(`Replication error`, e);
40
+ if (e.cause != null) {
41
+ // Without this additional log, the cause may not be visible in the logs.
42
+ this.logger.error(`cause`, e.cause);
43
+ }
44
+
45
+ this.rateLimiter.reportError(e);
46
+ }
43
47
 
44
48
  if (e instanceof ChangeStreamInvalidatedError) {
45
49
  // This stops replication and restarts with a new instance
46
50
  await this.options.storage.factory.restartReplication(this.storage.group_id);
47
51
  }
52
+
53
+ // No need to rethrow - the error is already logged, and retry behavior is the same on error
48
54
  } finally {
49
55
  this.abortController.abort();
50
56
  }
51
57
  }
52
58
 
53
- async replicateLoop() {
54
- while (!this.isStopped) {
55
- await this.replicateOnce();
56
-
57
- if (!this.isStopped) {
58
- await new Promise((resolve) => setTimeout(resolve, 5000));
59
- }
60
- }
61
- }
62
-
63
59
  async replicateOnce() {
64
60
  // New connections on every iteration (every error with retry),
65
61
  // otherwise we risk repeating errors related to the connection,
@@ -79,25 +75,6 @@ export class ChangeStreamReplicationJob extends replication.AbstractReplicationJ
79
75
  });
80
76
  this.lastStream = stream;
81
77
  await stream.replicate();
82
- } catch (e) {
83
- if (this.abortController.signal.aborted) {
84
- return;
85
- }
86
- this.logger.error(`Replication error`, e);
87
- if (e.cause != null) {
88
- // Without this additional log, the cause may not be visible in the logs.
89
- this.logger.error(`cause`, e.cause);
90
- }
91
- if (e instanceof ChangeStreamInvalidatedError) {
92
- throw e;
93
- } else {
94
- // Report the error if relevant, before retrying
95
- container.reporter.captureException(e, {
96
- metadata: {}
97
- });
98
- // This sets the retry delay
99
- this.rateLimiter?.reportError(e);
100
- }
101
78
  } finally {
102
79
  await connectionManager.end();
103
80
  }
@@ -25,7 +25,7 @@ export class ChangeStreamReplicator extends replication.AbstractReplicator<Chang
25
25
  metrics: this.metrics,
26
26
  connectionFactory: this.connectionFactory,
27
27
  lock: options.lock,
28
- rateLimiter: new MongoErrorRateLimiter()
28
+ rateLimiter: this.rateLimiter
29
29
  });
30
30
  }
31
31
 
@@ -3,23 +3,28 @@ import { NormalizedMongoConnectionConfig } from '../types/types.js';
3
3
  import { MongoManager } from './MongoManager.js';
4
4
 
5
5
  export class ConnectionManagerFactory {
6
- private readonly connectionManagers: MongoManager[];
6
+ private readonly connectionManagers = new Set<MongoManager>();
7
7
  public readonly dbConnectionConfig: NormalizedMongoConnectionConfig;
8
8
 
9
9
  constructor(dbConnectionConfig: NormalizedMongoConnectionConfig) {
10
10
  this.dbConnectionConfig = dbConnectionConfig;
11
- this.connectionManagers = [];
12
11
  }
13
12
 
14
13
  create() {
15
14
  const manager = new MongoManager(this.dbConnectionConfig);
16
- this.connectionManagers.push(manager);
15
+ this.connectionManagers.add(manager);
16
+
17
+ manager.registerListener({
18
+ onEnded: () => {
19
+ this.connectionManagers.delete(manager);
20
+ }
21
+ });
17
22
  return manager;
18
23
  }
19
24
 
20
25
  async shutdown() {
21
26
  logger.info('Shutting down MongoDB connection Managers...');
22
- for (const manager of this.connectionManagers) {
27
+ for (const manager of [...this.connectionManagers]) {
23
28
  await manager.end();
24
29
  }
25
30
  logger.info('MongoDB connection Managers shutdown completed.');
@@ -1,12 +1,13 @@
1
1
  import { ErrorRateLimiter } from '@powersync/service-core';
2
2
  import { setTimeout } from 'timers/promises';
3
+ import { ChangeStreamInvalidatedError } from './ChangeStream.js';
3
4
 
4
5
  export class MongoErrorRateLimiter implements ErrorRateLimiter {
5
6
  nextAllowed: number = Date.now();
6
7
 
7
8
  async waitUntilAllowed(options?: { signal?: AbortSignal | undefined } | undefined): Promise<void> {
8
9
  const delay = Math.max(0, this.nextAllowed - Date.now());
9
- // Minimum delay between connections, even without errors
10
+ // Minimum delay between connections, even without errors (for the next attempt)
10
11
  this.setDelay(500);
11
12
  await setTimeout(delay, undefined, { signal: options?.signal });
12
13
  }
@@ -18,9 +19,12 @@ export class MongoErrorRateLimiter implements ErrorRateLimiter {
18
19
  reportError(e: any): void {
19
20
  // FIXME: Check mongodb-specific requirements
20
21
  const message = (e.message as string) ?? '';
21
- if (message.includes('password authentication failed')) {
22
- // Wait 15 minutes, to avoid triggering Supabase's fail2ban
23
- this.setDelay(900_000);
22
+ if (e instanceof ChangeStreamInvalidatedError) {
23
+ // Short delay
24
+ this.setDelay(2_000);
25
+ } else if (message.includes('Authentication failed')) {
26
+ // Wait 2 minutes, to avoid triggering too many authentication attempts
27
+ this.setDelay(120_000);
24
28
  } else if (message.includes('ENOTFOUND')) {
25
29
  // DNS lookup issue - incorrect URI or deleted instance
26
30
  this.setDelay(120_000);
@@ -2,11 +2,16 @@ import { mongo } from '@powersync/lib-service-mongodb';
2
2
 
3
3
  import { NormalizedMongoConnectionConfig } from '../types/types.js';
4
4
  import { BSON_DESERIALIZE_DATA_OPTIONS, POWERSYNC_VERSION } from '@powersync/service-core';
5
+ import { BaseObserver } from '@powersync/lib-services-framework';
6
+
7
+ export interface MongoManagerListener {
8
+ onEnded(): void;
9
+ }
5
10
 
6
11
  /**
7
12
  * Manage a MongoDB source database connection.
8
13
  */
9
- export class MongoManager {
14
+ export class MongoManager extends BaseObserver<MongoManagerListener> {
10
15
  public readonly client: mongo.MongoClient;
11
16
  public readonly db: mongo.Db;
12
17
 
@@ -14,6 +19,7 @@ export class MongoManager {
14
19
  public options: NormalizedMongoConnectionConfig,
15
20
  overrides?: mongo.MongoClientOptions
16
21
  ) {
22
+ super();
17
23
  // The pool is lazy - no connections are opened until a query is performed.
18
24
  this.client = new mongo.MongoClient(options.uri, {
19
25
  auth: {
@@ -59,9 +65,8 @@ export class MongoManager {
59
65
 
60
66
  async end(): Promise<void> {
61
67
  await this.client.close();
62
- }
63
-
64
- async destroy() {
65
- // TODO: Implement?
68
+ this.iterateListeners((listener) => {
69
+ listener.onEnded?.();
70
+ });
66
71
  }
67
72
  }
@@ -57,11 +57,18 @@ export class ChangeStreamTestContext {
57
57
  initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
58
58
  }
59
59
 
60
- async dispose() {
60
+ /**
61
+ * Abort snapshot and/or replication, without actively closing connections.
62
+ */
63
+ abort() {
61
64
  this.abortController.abort();
65
+ }
66
+
67
+ async dispose() {
68
+ this.abort();
62
69
  await this.streamPromise?.catch((e) => e);
63
- await this.connectionManager.destroy();
64
70
  await this.factory[Symbol.asyncDispose]();
71
+ await this.connectionManager.end();
65
72
  }
66
73
 
67
74
  async [Symbol.asyncDispose]() {