@powersync/service-module-mongodb 0.0.0-dev-20260602073133 → 0.0.0-dev-20260803144812

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 (70) hide show
  1. package/CHANGELOG.md +97 -5
  2. package/dist/api/MongoRouteAPIAdapter.d.ts +4 -2
  3. package/dist/api/MongoRouteAPIAdapter.js +23 -32
  4. package/dist/api/MongoRouteAPIAdapter.js.map +1 -1
  5. package/dist/common/SentinelLSN.d.ts +37 -0
  6. package/dist/common/SentinelLSN.js +59 -0
  7. package/dist/common/SentinelLSN.js.map +1 -0
  8. package/dist/replication/ChangeStream.d.ts +16 -0
  9. package/dist/replication/ChangeStream.js +143 -64
  10. package/dist/replication/ChangeStream.js.map +1 -1
  11. package/dist/replication/ChangeStreamReplicationJob.js +1 -1
  12. package/dist/replication/ChangeStreamReplicationJob.js.map +1 -1
  13. package/dist/replication/ChangeStreamReplicator.js +1 -1
  14. package/dist/replication/ChangeStreamReplicator.js.map +1 -1
  15. package/dist/replication/MongoRelation.d.ts +36 -1
  16. package/dist/replication/MongoRelation.js +133 -18
  17. package/dist/replication/MongoRelation.js.map +1 -1
  18. package/dist/replication/MongoSnapshotter.d.ts +9 -1
  19. package/dist/replication/MongoSnapshotter.js +128 -66
  20. package/dist/replication/MongoSnapshotter.js.map +1 -1
  21. package/dist/replication/RawChangeStream.d.ts +12 -1
  22. package/dist/replication/RawChangeStream.js +23 -10
  23. package/dist/replication/RawChangeStream.js.map +1 -1
  24. package/dist/replication/checkpoints/CheckpointImplementation.d.ts +132 -0
  25. package/dist/replication/checkpoints/CheckpointImplementation.js +22 -0
  26. package/dist/replication/checkpoints/CheckpointImplementation.js.map +1 -0
  27. package/dist/replication/checkpoints/SentinelCheckpointImplementation.d.ts +55 -0
  28. package/dist/replication/checkpoints/SentinelCheckpointImplementation.js +224 -0
  29. package/dist/replication/checkpoints/SentinelCheckpointImplementation.js.map +1 -0
  30. package/dist/replication/checkpoints/TimestampCheckpointImplementation.d.ts +24 -0
  31. package/dist/replication/checkpoints/TimestampCheckpointImplementation.js +115 -0
  32. package/dist/replication/checkpoints/TimestampCheckpointImplementation.js.map +1 -0
  33. package/dist/replication/checkpoints/create-checkpoint-implementation.d.ts +6 -0
  34. package/dist/replication/checkpoints/create-checkpoint-implementation.js +10 -0
  35. package/dist/replication/checkpoints/create-checkpoint-implementation.js.map +1 -0
  36. package/dist/replication/replication-utils.d.ts +6 -0
  37. package/dist/replication/replication-utils.js +19 -2
  38. package/dist/replication/replication-utils.js.map +1 -1
  39. package/dist/types/types.d.ts +2 -2
  40. package/package.json +11 -21
  41. package/src/api/MongoRouteAPIAdapter.ts +28 -39
  42. package/src/common/SentinelLSN.ts +78 -0
  43. package/src/replication/ChangeStream.ts +159 -73
  44. package/src/replication/ChangeStreamReplicationJob.ts +1 -1
  45. package/src/replication/ChangeStreamReplicator.ts +1 -1
  46. package/src/replication/MongoRelation.ts +153 -21
  47. package/src/replication/MongoSnapshotter.ts +147 -72
  48. package/src/replication/RawChangeStream.ts +54 -32
  49. package/src/replication/checkpoints/CheckpointImplementation.ts +167 -0
  50. package/src/replication/checkpoints/SentinelCheckpointImplementation.ts +269 -0
  51. package/src/replication/checkpoints/TimestampCheckpointImplementation.ts +144 -0
  52. package/src/replication/checkpoints/create-checkpoint-implementation.ts +14 -0
  53. package/src/replication/replication-utils.ts +23 -2
  54. package/test/DOCUMENTDB_TESTING.md +115 -0
  55. package/test/src/DatabaseType.ts +25 -0
  56. package/test/src/change_stream.test.ts +107 -71
  57. package/test/src/change_stream_utils.ts +117 -29
  58. package/test/src/checkpoint_retry.test.ts +134 -0
  59. package/test/src/documentdb_helpers.test.ts +124 -0
  60. package/test/src/documentdb_mode.test.ts +1040 -0
  61. package/test/src/mongo_test.test.ts +15 -5
  62. package/test/src/raw_change_stream.test.ts +209 -125
  63. package/test/src/resume.test.ts +2 -2
  64. package/test/src/resume_token.test.ts +30 -0
  65. package/test/src/slow_tests.test.ts +4 -1
  66. package/test/src/test-timeouts.ts +23 -0
  67. package/test/src/util.ts +3 -3
  68. package/tsconfig.json +1 -4
  69. package/tsconfig.scripts.json +1 -2
  70. package/tsconfig.tsbuildinfo +1 -1
@@ -2,9 +2,9 @@ import { mongo } from '@powersync/lib-service-mongodb';
2
2
  import { storage } from '@powersync/service-core';
3
3
  import { JsonContainer } from '@powersync/service-jsonbig';
4
4
  import { CustomArray, CustomObject, CustomSqliteValue, DateTimeValue, TimeValuePrecision } from '@powersync/service-sync-rules';
5
- import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
5
+ import { ErrorCode, logger, ServiceAssertionError, ServiceError } from '@powersync/lib-services-framework';
6
6
  import { MongoLSN } from '../common/MongoLSN.js';
7
- import { CHECKPOINTS_COLLECTION } from './replication-utils.js';
7
+ import { SentinelLSN } from '../common/SentinelLSN.js';
8
8
  export function getMongoRelation(source, connectionTag) {
9
9
  return {
10
10
  connectionTag,
@@ -192,24 +192,139 @@ function filterJsonData(data, context, depth = 0) {
192
192
  * the checkpoint immediately, and not wait for batching.
193
193
  */
194
194
  export const STANDALONE_CHECKPOINT_ID = '_standalone_checkpoint';
195
- export async function createCheckpoint(client, db, id) {
195
+ /**
196
+ * Id for checkpoint records managed by the {@link SentinelCheckpointImplementation} implementation.
197
+ */
198
+ export const SENTINEL_CHECKPOINT_ID = '_sentinel_checkpoint';
199
+ /**
200
+ * Create a checkpoint by upserting a document in _powersync_checkpoints, and
201
+ * return a comparable LSN string derived from the write's operationTime.
202
+ *
203
+ * Standard MongoDB only: this requires session.operationTime, which DocumentDB
204
+ * does not provide (it throws PSYNC_S1004 when it is missing). The DocumentDB /
205
+ * sentinel path builds LSNs via {@link createSentinelCheckpointLsn} instead.
206
+ */
207
+ export async function createCheckpoint(db, id) {
208
+ const TRIES = 2;
209
+ for (let i = 0; i < TRIES; i++) {
210
+ try {
211
+ return await createCheckpointInner(db, id);
212
+ }
213
+ catch (e) {
214
+ if (i < TRIES - 1) {
215
+ logger.warn(`Failed to create checkpoint on attempt ${i + 1}`, e);
216
+ }
217
+ else {
218
+ throw e;
219
+ }
220
+ }
221
+ }
222
+ throw new ServiceAssertionError(`Unreachable code`);
223
+ }
224
+ async function createCheckpointInner(db, id) {
225
+ // We use an unique id per process, and clear documents on startup.
226
+ // This is so that we can filter events for our own process only, and ignore
227
+ // events from other processes.
228
+ // We use a command instead of a regular update to avoid auto retries on writes.
229
+ // An auto retry on the write can trigger a weird edge case where the change stream event
230
+ // has the clusterTime of the first write, while the returned operation time is for the second no-op write.
231
+ // Instead, we do manual retries, which does not have the same write de-duplication logic.
232
+ // A sentinal-based approach would be better here, but that is a much bigger change.
233
+ const update = { $inc: { i: 1 } };
234
+ const response = await db.command({
235
+ findAndModify: '_powersync_checkpoints',
236
+ query: {
237
+ _id: id
238
+ },
239
+ new: true,
240
+ upsert: true,
241
+ update
242
+ });
243
+ const time = response.operationTime;
244
+ if (time == null) {
245
+ throw new ServiceError(ErrorCode.PSYNC_S1004, `clusterTime not available for checkpoint`);
246
+ }
247
+ return new MongoLSN({ timestamp: time }).comparable;
248
+ }
249
+ /**
250
+ * Create a DocumentDB comparable LSN by advancing the shared sentinel checkpoint
251
+ * document ({@link SENTINEL_CHECKPOINT_ID}). The returned LSN encodes the
252
+ * checkpoint counter in the same 16-hex shape as a MongoDB timestamp LSN (see
253
+ * {@link SentinelLSN}), so the two formats are directly comparable. The counter is
254
+ * seeded in the epoch-seconds range (see below), so a sentinel LSN always sorts
255
+ * above any real-timestamp LSN issued in the past.
256
+ *
257
+ * This counter is intentionally global to the source database. It is used for
258
+ * storage/client checkpoint comparisons and write checkpoint heads, so it must
259
+ * not reset when a new ChangeStream instance or new sync rules start.
260
+ *
261
+ * The document is a single shared record whose `stream_id` field alternates:
262
+ * batch/keepalive bumps stamp it with the calling stream's id (so a stream can
263
+ * recognise its own private barriers), while standalone bumps (write checkpoint
264
+ * heads, snapshot markers) clear it to null. `i` advances globally regardless.
265
+ *
266
+ * @param changeStreamId
267
+ * When provided, the bump is attributed to this stream (a private barrier).
268
+ * When omitted, it is a standalone bump and stream_id is cleared to null.
269
+ */
270
+ export async function createSentinelCheckpointLsn(client, db, changeStreamId) {
196
271
  const session = client.startSession();
197
272
  try {
198
- // We use an unique id per process, and clear documents on startup.
199
- // This is so that we can filter events for our own process only, and ignore
200
- // events from other processes.
201
- await db.collection(CHECKPOINTS_COLLECTION).findOneAndUpdate({
202
- _id: id
203
- }, {
204
- $inc: { i: 1 }
205
- }, {
206
- upsert: true,
207
- returnDocument: 'after',
208
- session
209
- });
210
- const time = session.operationTime;
211
- // TODO: Use the above when we support custom write checkpoints
212
- return new MongoLSN({ timestamp: time }).comparable;
273
+ const collection = db.collection('_powersync_checkpoints');
274
+ for (let attempt = 0; attempt < 3; attempt++) {
275
+ // Common path: increment the existing counter.
276
+ const result = await collection.findOneAndUpdate({
277
+ _id: SENTINEL_CHECKPOINT_ID,
278
+ i: { $exists: true }
279
+ }, {
280
+ $inc: { i: 1 },
281
+ // Standalone bumps (no changeStreamId) must explicitly clear the
282
+ // stream_id left by a previous batch bump — this is a single shared
283
+ // document whose stream_id alternates. Write null rather than relying
284
+ // on `undefined` being serialized (which depends on the driver's
285
+ // ignoreUndefined option, and collapses to an empty $set if enabled).
286
+ $set: {
287
+ stream_id: changeStreamId ?? null
288
+ }
289
+ }, {
290
+ returnDocument: 'after',
291
+ session
292
+ });
293
+ if (result != null) {
294
+ // `i` is a bigint: the client is configured with useBigInt64, and the
295
+ // seed exceeds 2^53 so it could not be safely represented as a number.
296
+ return new SentinelLSN({ sentinel: result.i }).comparable;
297
+ }
298
+ // The counter document does not exist: first run, or a consumer deleted
299
+ // it in their source database. Seed the counter in the epoch-SECONDS range
300
+ // (seconds in the high 32 bits, mirroring a MongoDB timestamp) rather than
301
+ // starting at 1. Two properties follow:
302
+ //
303
+ // - A sentinel LSN sorts above any real-timestamp LSN issued in the past,
304
+ // because its high 32 bits are the current epoch seconds.
305
+ // - A re-created counter jumps forward instead of backward across deletion:
306
+ // the seed advances by 2^32 each wall-clock second, while checkpoints add
307
+ // 1 each, so any later re-seed exceeds the previously issued coordinate
308
+ // (keeping the LSN domain monotonic; otherwise new write checkpoint heads
309
+ // could resolve against old, higher committed LSNs).
310
+ //
311
+ // $setOnInsert cannot be combined with $inc on the same field, so this
312
+ // is a separate upsert; the loop then retries the increment. The
313
+ // $setOnInsert is a no-op if another process created the document
314
+ // concurrently.
315
+ await collection.updateOne({
316
+ _id: SENTINEL_CHECKPOINT_ID
317
+ }, {
318
+ $setOnInsert: {
319
+ i: BigInt(Math.floor(Date.now() / 1000)) << 32n,
320
+ stream_id: changeStreamId ?? null
321
+ }
322
+ }, {
323
+ upsert: true,
324
+ session
325
+ });
326
+ }
327
+ throw new ServiceError(ErrorCode.PSYNC_S1301, `Failed to increment the sentinel checkpoint counter - the checkpoint document may be getting deleted concurrently.`);
213
328
  }
214
329
  finally {
215
330
  await session.endSession();
@@ -1 +1 @@
1
- {"version":3,"file":"MongoRelation.js","sourceRoot":"","sources":["../../src/replication/MongoRelation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAEL,WAAW,EACX,YAAY,EACZ,iBAAiB,EAEjB,aAAa,EAGb,kBAAkB,EACnB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,UAAU,gBAAgB,CAC9B,MAAmC,EACnC,aAAqB;IAErB,OAAO;QACL,aAAa;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,EAAE;QACjB,qEAAqE;QACrE,QAAQ,EAAE,SAAS;QACnB,gBAAgB,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;KACK,CAAC;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAA4D;IAC7F,IAAI,MAAM,YAAY,OAAO,CAAC,WAAW,EAAE,CAAC;QAC1C,OAAO,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAwB;IAC3D,IAAI,MAAM,GAAmB,EAAE,CAAC;IAChC,KAAK,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,GAAG,qBAAqB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAS;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC;IACxB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,WAAW,EAAE,CAAC;QACtC,2EAA2E;QAC3E,uBAAuB;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,UAAU,EAAE,CAAC;YACzC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,SAAS,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxB,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;SAAM,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACrC,OAAO,IAAI,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;IACnE,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,MAAM,EAAE,CAAC;QACxC,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,UAAU,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,MAAM,IAAI,IAAI,YAAY,KAAK,CAAC,MAAM,EAAE,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,IAAI,YAAY,MAAM,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACvE,CAAC;SAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC/C,CAAC;SAAM,IAAI,IAAI,YAAY,UAAU,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,IAAI,YAAY,aAAa,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAChD,CAAC;SAAM,CAAC;QACN,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB,SAAS,cAAc,CAAC,IAAS,EAAE,OAA6B,EAAE,KAAK,GAAG,CAAC;IACzE,MAAM,UAAU,GAAG,IAAI,CAAC;IACxB,IAAI,KAAK,GAAG,WAAW,EAAE,CAAC;QACxB,kDAAkD;QAClD,MAAM,IAAI,YAAY,CAAC,SAAS,CAAC,WAAW,EAAE,iDAAiD,WAAW,EAAE,CAAC,CAAC;IAChH,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,WAAW,EAAE,CAAC;QACtC,sCAAsC;QACtC,wCAAwC;QACxC,mCAAmC;QACnC,OAAO,SAAS,CAAC;IACnB,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACnC,IAAI,UAAU,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,kDAAkD;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,SAAS,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxB,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACrC,OAAO,IAAI,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1F,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,MAAM,EAAE,CAAC;QACxC,OAAO,SAAS,CAAC;IACnB,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,UAAU,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,IAAI,YAAY,KAAK,CAAC,MAAM,IAAI,IAAI,YAAY,KAAK,CAAC,MAAM,EAAE,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,IAAI,YAAY,MAAM,EAAE,CAAC;QAClC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IACvD,CAAC;SAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5E,CAAC;SAAM,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,SAAS,CAAC;IACnB,CAAC;SAAM,IAAI,IAAI,YAAY,iBAAiB,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;SAAM,IAAI,IAAI,YAAY,aAAa,EAAE,CAAC;QACzC,oEAAoE;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;SAAM,IAAI,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACnC,IAAI,MAAM,GAAwB,EAAE,CAAC;QACrC,KAAK,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;SAAM,CAAC;QACN,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,wBAAwB,CAAC;AAEjE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAyB,EACzB,EAAY,EACZ,EAA2B;IAE3B,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;IACtC,IAAI,CAAC;QACH,mEAAmE;QACnE,4EAA4E;QAC5E,+BAA+B;QAC/B,MAAM,EAAE,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,gBAAgB,CAC1D;YACE,GAAG,EAAE,EAAS;SACf,EACD;YACE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE;SACf,EACD;YACE,MAAM,EAAE,IAAI;YACZ,cAAc,EAAE,OAAO;YACvB,OAAO;SACR,CACF,CAAC;QACF,MAAM,IAAI,GAAG,OAAO,CAAC,aAAc,CAAC;QACpC,+DAA+D;QAC/D,OAAO,IAAI,QAAQ,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC;IACtD,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,MAAM,gBAAgB,GAA0B;IAC9C,kBAAkB,EAAE,kBAAkB,CAAC,YAAY;IACnD,yBAAyB,EAAE,kBAAkB,CAAC,YAAY;CAC3D,CAAC"}
1
+ {"version":3,"file":"MongoRelation.js","sourceRoot":"","sources":["../../src/replication/MongoRelation.ts"],"names":[],"mappings":"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"}
@@ -41,12 +41,20 @@ export declare class MongoSnapshotter {
41
41
  private readonly queue;
42
42
  private initialSnapshotDone;
43
43
  private nextItemQueued;
44
- private lastSnapshotOpId;
45
44
  private lastTouchedAt;
45
+ private isDocumentDb;
46
+ private _checkpointImplementation;
46
47
  constructor(options: MongoSnapshotterOptions);
47
48
  private get usePostImages();
48
49
  private get configurePostImages();
49
50
  get supportsConcurrentSnapshots(): boolean;
51
+ /** The active checkpoint strategy. Only valid after ensureDetected(). */
52
+ private get checkpointImplementation();
53
+ /**
54
+ * Detect DocumentDB and select the checkpoint implementation. Idempotent.
55
+ * Also validates server topology (sharded/standalone) for standard MongoDB.
56
+ */
57
+ private ensureDetected;
50
58
  checkSlot(): Promise<InitResult>;
51
59
  setupCheckpointsCollection(): Promise<void>;
52
60
  queueSnapshotTables(snapshotLsn: string | null): Promise<void>;
@@ -5,10 +5,11 @@ import { performance } from 'node:perf_hooks';
5
5
  import { MongoLSN } from '../common/MongoLSN.js';
6
6
  import { PostImagesOption } from '../types/types.js';
7
7
  import { escapeRegExp } from '../utils.js';
8
- import { createCheckpoint, getMongoRelation, STANDALONE_CHECKPOINT_ID } from './MongoRelation.js';
8
+ import { createCheckpointImplementation } from './checkpoints/create-checkpoint-implementation.js';
9
+ import { getMongoRelation } from './MongoRelation.js';
9
10
  import { ChunkedSnapshotQuery } from './MongoSnapshotQuery.js';
10
11
  import { parseChangeDocument, rawChangeStream } from './RawChangeStream.js';
11
- import { CHECKPOINTS_COLLECTION } from './replication-utils.js';
12
+ import { CHECKPOINTS_COLLECTION, detectDocumentDb } from './replication-utils.js';
12
13
  import { DirectSourceRowConverter } from './SourceRowConverter.js';
13
14
  export class MongoSnapshotter {
14
15
  storage;
@@ -30,8 +31,9 @@ export class MongoSnapshotter {
30
31
  queue = new Set();
31
32
  initialSnapshotDone = Promise.withResolvers();
32
33
  nextItemQueued = null;
33
- lastSnapshotOpId = null;
34
34
  lastTouchedAt = performance.now();
35
+ isDocumentDb = false;
36
+ _checkpointImplementation = null;
35
37
  constructor(options) {
36
38
  this.storage = options.storage;
37
39
  this.metrics = options.metrics;
@@ -53,6 +55,8 @@ export class MongoSnapshotter {
53
55
  this.abortSignal.addEventListener('abort', () => {
54
56
  this.nextItemQueued?.resolve();
55
57
  });
58
+ // Errors here should not result in uncaught rejection - calling waitForInitialSnapshot is optional.
59
+ void this.initialSnapshotDone.promise.catch(() => { });
56
60
  }
57
61
  get usePostImages() {
58
62
  return this.connections.options.postImages != PostImagesOption.OFF;
@@ -63,22 +67,58 @@ export class MongoSnapshotter {
63
67
  get supportsConcurrentSnapshots() {
64
68
  return this.storage.storageConfig.softDeleteCurrentData;
65
69
  }
70
+ /** The active checkpoint strategy. Only valid after ensureDetected(). */
71
+ get checkpointImplementation() {
72
+ if (this._checkpointImplementation == null) {
73
+ throw new ServiceError(ErrorCode.PSYNC_S1301, 'Checkpoint implementation not initialized - call ensureDetected() first');
74
+ }
75
+ return this._checkpointImplementation;
76
+ }
77
+ /**
78
+ * Detect DocumentDB and select the checkpoint implementation. Idempotent.
79
+ * Also validates server topology (sharded/standalone) for standard MongoDB.
80
+ */
81
+ async ensureDetected() {
82
+ if (this._checkpointImplementation != null) {
83
+ return;
84
+ }
85
+ this.isDocumentDb = await detectDocumentDb(this.defaultDb);
86
+ if (!this.isDocumentDb) {
87
+ const hello = await this.defaultDb.command({ hello: 1 });
88
+ if (hello.msg == 'isdbgrid') {
89
+ throw new ServiceError(ErrorCode.PSYNC_S1341, 'Sharded MongoDB Clusters are not supported yet (including MongoDB Serverless instances).');
90
+ }
91
+ else if (hello.setName == null) {
92
+ throw new ServiceError(ErrorCode.PSYNC_S1342, 'Standalone MongoDB instances are not supported - use a replicaset.');
93
+ }
94
+ }
95
+ this._checkpointImplementation = createCheckpointImplementation(this.isDocumentDb, {
96
+ client: this.client,
97
+ db: this.defaultDb,
98
+ checkpointStreamId: this.checkpointStreamId,
99
+ logger: this.logger
100
+ });
101
+ }
66
102
  async checkSlot() {
67
103
  const status = await this.storage.getStatus();
68
- if (status.snapshot_done && status.checkpoint_lsn) {
104
+ if (status.snapshotDone) {
69
105
  this.logger.info(`Initial replication already done`);
70
106
  return { needsInitialSync: false, snapshotLsn: null };
71
107
  }
72
- return { needsInitialSync: true, snapshotLsn: status.snapshot_lsn };
108
+ return { needsInitialSync: true, snapshotLsn: status.resumeLsn };
73
109
  }
74
110
  async setupCheckpointsCollection() {
111
+ await this.ensureDetected();
75
112
  const collection = await this.getCollectionInfo(this.defaultDb.databaseName, CHECKPOINTS_COLLECTION);
76
113
  if (collection == null) {
77
114
  await this.defaultDb.createCollection(CHECKPOINTS_COLLECTION, {
78
- changeStreamPreAndPostImages: { enabled: true }
115
+ // DocumentDB does not support changeStreamPreAndPostImages.
116
+ ...(this.isDocumentDb ? {} : { changeStreamPreAndPostImages: { enabled: true } })
79
117
  });
80
118
  }
81
- else if (this.usePostImages && collection.options?.changeStreamPreAndPostImages?.enabled != true) {
119
+ else if (!this.isDocumentDb &&
120
+ this.usePostImages &&
121
+ collection.options?.changeStreamPreAndPostImages?.enabled != true) {
82
122
  // Drop + create requires less permissions than collMod,
83
123
  // and we don't care about the data in this collection.
84
124
  await this.defaultDb.dropCollection(CHECKPOINTS_COLLECTION);
@@ -90,11 +130,21 @@ export class MongoSnapshotter {
90
130
  // Clear the collection on startup, to keep it clean
91
131
  // We never query this collection directly, and don't want to keep the data around.
92
132
  // We only use this to get data into the oplog/changestream.
93
- await this.defaultDb.collection(CHECKPOINTS_COLLECTION).deleteMany({});
133
+ //
134
+ // The implementation supplies the filter: the sentinel implementation must
135
+ // preserve the sentinel checkpoint document, since its counter is the
136
+ // globally-ordered component of every committed DocumentDB LSN.
137
+ await this.defaultDb
138
+ .collection(CHECKPOINTS_COLLECTION)
139
+ .deleteMany(this.checkpointImplementation.checkpointClearFilter);
94
140
  }
95
141
  }
96
142
  async queueSnapshotTables(snapshotLsn) {
97
143
  await this.client.connect();
144
+ // Ensure isDocumentDb is set before any getSourceNamespaceFilters() call below
145
+ // (notably the validateSnapshotLsn resume path, which does not go through
146
+ // getSnapshotLsn). Idempotent.
147
+ await this.ensureDetected();
98
148
  await using writer = await this.storage.createWriter({
99
149
  zeroLSN: MongoLSN.ZERO.comparable,
100
150
  defaultSchema: this.defaultDb.databaseName,
@@ -205,7 +255,7 @@ export class MongoSnapshotter {
205
255
  for (const table of tables) {
206
256
  await this.snapshotTable(batch, table);
207
257
  }
208
- const noCheckpointBefore = await createCheckpoint(this.client, this.defaultDb, STANDALONE_CHECKPOINT_ID);
258
+ const noCheckpointBefore = await this.checkpointImplementation.createStandaloneCheckpoint();
209
259
  await batch.markTableSnapshotDone(tables, noCheckpointBefore);
210
260
  }
211
261
  }
@@ -220,19 +270,15 @@ export class MongoSnapshotter {
220
270
  return;
221
271
  }
222
272
  const status = await this.storage.getStatus();
223
- if (status.snapshot_done) {
273
+ if (status.snapshotDone) {
224
274
  return;
225
275
  }
226
- const lastOp = this.lastSnapshotOpId ?? status.keepalive_op;
227
- if (lastOp != null) {
228
- // Populate the cache _after_ initial replication, but _before_ we switch to this replication stream.
229
- // Keeping snapshot_done false until this completes makes this resumable after interruption.
230
- await this.storage.populatePersistentChecksumCache({
231
- // No checkpoint yet, but we do have the opId.
232
- maxOpId: lastOp,
233
- signal: this.abortSignal
234
- });
235
- }
276
+ // Populate the cache _after_ initial replication, but _before_ we switch to this replication stream.
277
+ // Keeping snapshot_done false until this completes makes this resumable after interruption.
278
+ // No checkpoint exists yet - storage defaults to its highest persisted op id.
279
+ await this.storage.populatePersistentChecksumCache({
280
+ signal: this.abortSignal
281
+ });
236
282
  if (this.queue.size != 0) {
237
283
  return;
238
284
  }
@@ -245,7 +291,7 @@ export class MongoSnapshotter {
245
291
  });
246
292
  // The checkpoint here is a marker - we need to replicate up to at least this
247
293
  // point before the data can be considered consistent.
248
- const checkpoint = await createCheckpoint(this.client, this.defaultDb, STANDALONE_CHECKPOINT_ID);
294
+ const checkpoint = await this.checkpointImplementation.createStandaloneCheckpoint();
249
295
  if (this.queue.size != 0) {
250
296
  return;
251
297
  }
@@ -256,7 +302,7 @@ export class MongoSnapshotter {
256
302
  // KLUDGE: We need to create an extra checkpoint _after_ marking the snapshot done, to fix
257
303
  // issues with order of processing commits(). This is picked up by tests on postgres storage,
258
304
  // the issue may be specific to that storage engine.
259
- await createCheckpoint(this.client, this.defaultDb, STANDALONE_CHECKPOINT_ID);
305
+ await this.checkpointImplementation.createStandaloneCheckpoint();
260
306
  }
261
307
  async replicateTable(tableRequest) {
262
308
  await this.snapshotHooks?.beforeSnapshotStarted?.(tableRequest);
@@ -277,14 +323,11 @@ export class MongoSnapshotter {
277
323
  return;
278
324
  }
279
325
  await this.snapshotTable(writer, table);
280
- const noCheckpointBefore = await createCheckpoint(this.client, this.defaultDb, STANDALONE_CHECKPOINT_ID);
326
+ const noCheckpointBefore = await this.checkpointImplementation.createStandaloneCheckpoint();
281
327
  await writer.markTableSnapshotDone([table], noCheckpointBefore);
282
- // This commit ensures we set keepalive_op.
328
+ // This commit durably records the persisted ops, so a later checkpoint covers them.
283
329
  const resumeLsn = writer.resumeFromLsn ?? MongoLSN.ZERO.comparable;
284
330
  await writer.commit(resumeLsn);
285
- if (writer.last_flushed_op != null) {
286
- this.lastSnapshotOpId = writer.last_flushed_op;
287
- }
288
331
  this.logger.info(`Flushed snapshot at ${writer.last_flushed_op}`);
289
332
  }
290
333
  async resolveQualifiedTableNames(batch, tablePattern) {
@@ -292,6 +335,9 @@ export class MongoSnapshotter {
292
335
  if (tablePattern.connectionTag != this.connections.connectionTag) {
293
336
  return [];
294
337
  }
338
+ if (tablePattern.isSchemaWildcard) {
339
+ throw new ServiceError(ErrorCode.PSYNC_R2201, 'Schema wildcards ("%") in table patterns are not supported for MongoDB connections.');
340
+ }
295
341
  const nameFilter = tablePattern.isWildcard
296
342
  ? new RegExp('^' + escapeRegExp(tablePattern.tablePrefix))
297
343
  : tablePattern.name;
@@ -357,10 +403,7 @@ export class MongoSnapshotter {
357
403
  });
358
404
  }
359
405
  // Important: flush before marking progress
360
- const result = await batch.flush();
361
- if (result?.flushed_op != null) {
362
- this.lastSnapshotOpId = result.flushed_op;
363
- }
406
+ await batch.flush();
364
407
  at += docBatch.length;
365
408
  rowsReplicatedMetric.add(docBatch.length);
366
409
  table = await batch.updateTableProgress(table, {
@@ -384,10 +427,12 @@ export class MongoSnapshotter {
384
427
  // If collectionInfo is null, the collection may have been dropped.
385
428
  // Ignore the postImages check in this case.
386
429
  }
430
+ // Note: resolveTables uses the batch's own parsed sync config set. Passing
431
+ // this.syncRules here would pair sources from one parse with the batch's mapping
432
+ // from another parse.
387
433
  const result = await batch.resolveTables({
388
434
  connection_id: this.connectionId,
389
- source: descriptor,
390
- syncRules: this.syncRules
435
+ source: descriptor
391
436
  });
392
437
  // Drop conflicting collections.
393
438
  // This is generally not expected for MongoDB source dbs, so we log an error.
@@ -421,14 +466,7 @@ export class MongoSnapshotter {
421
466
  }
422
467
  }
423
468
  async getSnapshotLsn() {
424
- const hello = await this.defaultDb.command({ hello: 1 });
425
- // Basic sanity check
426
- if (hello.msg == 'isdbgrid') {
427
- throw new ServiceError(ErrorCode.PSYNC_S1341, 'Sharded MongoDB Clusters are not supported yet (including MongoDB Serverless instances).');
428
- }
429
- else if (hello.setName == null) {
430
- throw new ServiceError(ErrorCode.PSYNC_S1342, 'Standalone MongoDB instances are not supported - use a replicaset.');
431
- }
469
+ await this.ensureDetected();
432
470
  // Open a change stream just to get a resume token for later use.
433
471
  // We could use clusterTime from the hello command, but that won't tell us if the
434
472
  // snapshot isn't valid anymore.
@@ -440,12 +478,17 @@ export class MongoSnapshotter {
440
478
  // Instead, we create a new checkpoint document, and wait until we get that document back in the stream.
441
479
  // To avoid potential race conditions with the checkpoint creation, we create a new checkpoint document
442
480
  // periodically until the timeout is reached.
481
+ //
482
+ // For the sentinel implementation (DocumentDB) there is no
483
+ // startAtOperationTime: the stream opens from "now" (lsn null) and the
484
+ // retry loop below re-creates checkpoints until one is observed.
443
485
  const LSN_TIMEOUT_SECONDS = 60;
444
486
  const LSN_CREATE_INTERVAL_SECONDS = 1;
445
- const firstCheckpointLsn = await createCheckpoint(this.client, this.defaultDb, this.checkpointStreamId);
487
+ this.checkpointImplementation.seedPosition(null);
488
+ const startStreamFromLsn = await this.checkpointImplementation.createFirstBarrier();
446
489
  const filters = this.getSourceNamespaceFilters();
447
490
  const iter = this.rawChangeStreamBatches({
448
- lsn: firstCheckpointLsn,
491
+ lsn: startStreamFromLsn,
449
492
  maxAwaitTimeMS: 0,
450
493
  signal: this.abortSignal,
451
494
  filters
@@ -459,7 +502,7 @@ export class MongoSnapshotter {
459
502
  break;
460
503
  }
461
504
  if (performance.now() - lastCheckpointCreated >= LSN_CREATE_INTERVAL_SECONDS * 1000) {
462
- await createCheckpoint(this.client, this.defaultDb, this.checkpointStreamId);
505
+ await this.checkpointImplementation.createBatchCheckpoint();
463
506
  lastCheckpointCreated = performance.now();
464
507
  }
465
508
  batchesSeen += 1;
@@ -467,14 +510,15 @@ export class MongoSnapshotter {
467
510
  const changeDocument = parseChangeDocument(rawChangeDocument);
468
511
  const ns = 'ns' in changeDocument && 'coll' in changeDocument.ns ? changeDocument.ns : undefined;
469
512
  if (ns?.coll == CHECKPOINTS_COLLECTION && 'documentKey' in changeDocument) {
470
- const checkpointId = changeDocument.documentKey._id;
471
- if (!this.checkpointStreamId.equals(checkpointId)) {
513
+ const kind = this.checkpointImplementation.event.observe(changeDocument);
514
+ if (kind != 'own-barrier') {
515
+ // Standalone events still feed the implementation's coordinate via
516
+ // event.observe above; we only resolve on our own barrier.
472
517
  continue;
473
518
  }
474
- return new MongoLSN({
475
- timestamp: changeDocument.clusterTime,
476
- resume_token: changeDocument._id
477
- }).comparable;
519
+ // Observing our own barrier has set the coordinate (the barrier event
520
+ // carries the sentinel counter directly), so the LSN is ready.
521
+ return this.checkpointImplementation.event.lsn(changeDocument);
478
522
  }
479
523
  eventsSeen += 1;
480
524
  }
@@ -522,7 +566,12 @@ export class MongoSnapshotter {
522
566
  });
523
567
  }
524
568
  }
525
- const nsFilter = multipleDatabases
569
+ // DocumentDB always opens a cluster-level change stream, even in single-database
570
+ // mode, so a coll-only filter would match same-named collections in other
571
+ // databases of the cluster. Filter on the full namespace whenever the stream
572
+ // is cluster-scoped. See ChangeStream.getSourceNamespaceFilters for details.
573
+ const useFullNamespaceFilter = this.isDocumentDb || multipleDatabases;
574
+ const nsFilter = useFullNamespaceFilter
526
575
  ? { ns: { $in: inFilters } }
527
576
  : { 'ns.coll': { $in: inFilters.map((ns) => ns.coll) } };
528
577
  if (regexFilters.length > 0) {
@@ -531,11 +580,15 @@ export class MongoSnapshotter {
531
580
  return { $match: nsFilter, multipleDatabases };
532
581
  }
533
582
  rawChangeStreamBatches(options) {
534
- const lastLsn = options.lsn ? MongoLSN.fromSerialized(options.lsn) : null;
535
- const startAfter = lastLsn?.timestamp;
536
- const resumeAfter = lastLsn?.resumeToken;
583
+ const position = options.lsn ? this.checkpointImplementation.parseResumePosition(options.lsn) : null;
584
+ const startAfter = position?.startAfter ?? undefined;
585
+ const resumeAfter = position?.resumeAfter ?? undefined;
537
586
  let fullDocument;
538
- if (this.usePostImages) {
587
+ if (this.isDocumentDb) {
588
+ // DocumentDB does not support changeStreamPreAndPostImages, so 'required' won't work.
589
+ fullDocument = 'updateLookup';
590
+ }
591
+ else if (this.usePostImages) {
539
592
  // 'read_only' or 'auto_configure'
540
593
  // Configuration happens during snapshot, or when we see new
541
594
  // collections.
@@ -545,26 +598,31 @@ export class MongoSnapshotter {
545
598
  fullDocument = 'updateLookup';
546
599
  }
547
600
  const streamOptions = {
548
- showExpandedEvents: true,
549
601
  fullDocument
550
602
  };
551
- const pipeline = [
552
- { $changeStream: streamOptions },
553
- { $match: options.filters.$match },
554
- { $changeStreamSplitLargeEvent: {} }
555
- ];
603
+ if (!this.isDocumentDb) {
604
+ // DocumentDB does not support showExpandedEvents.
605
+ streamOptions.showExpandedEvents = true;
606
+ }
607
+ const pipeline = [{ $changeStream: streamOptions }, { $match: options.filters.$match }];
608
+ if (!this.isDocumentDb) {
609
+ // DocumentDB does not support $changeStreamSplitLargeEvent.
610
+ pipeline.push({ $changeStreamSplitLargeEvent: {} });
611
+ }
556
612
  // Only one of these options can be supplied at a time.
557
613
  if (resumeAfter) {
558
614
  streamOptions.resumeAfter = resumeAfter;
559
615
  }
560
- else {
616
+ else if (startAfter != null) {
561
617
  // Legacy: We don't persist lsns without resumeTokens anymore, but we do still handle the
562
- // case if we have an old one.
618
+ // case if we have an old one. The sentinel implementation never produces a startAfter,
619
+ // and a fresh DocumentDB stream opens from "now" with neither option set.
563
620
  streamOptions.startAtOperationTime = startAfter;
564
621
  }
565
622
  let watchDb;
566
- if (options.filters.multipleDatabases) {
567
- // Requires readAnyDatabase@admin on Atlas
623
+ if (this.isDocumentDb || options.filters.multipleDatabases) {
624
+ // DocumentDB only supports cluster-level change streams.
625
+ // Requires readAnyDatabase@admin on Atlas.
568
626
  watchDb = this.client.db('admin');
569
627
  streamOptions.allChangesForCluster = true;
570
628
  }
@@ -572,9 +630,13 @@ export class MongoSnapshotter {
572
630
  // Same general result, but requires less permissions than the above
573
631
  watchDb = this.defaultDb;
574
632
  }
633
+ const maxAwaitTimeMS = options.maxAwaitTimeMS ?? this.maxAwaitTimeMS;
575
634
  return rawChangeStream(watchDb, pipeline, {
576
635
  batchSize: options.batchSize ?? this.snapshotChunkLength,
577
- maxAwaitTimeMS: options.maxAwaitTimeMS ?? this.maxAwaitTimeMS,
636
+ maxAwaitTimeMS,
637
+ // maxAwaitTimeMS can be 0 for probe-style streams that do not want an idle wait.
638
+ // In that case there is no client-side wait to emulate for DocumentDB.
639
+ clientSideMaxAwaitTimeMS: this.isDocumentDb && maxAwaitTimeMS > 0,
578
640
  maxTimeMS: this.changeStreamTimeout,
579
641
  signal: options.signal,
580
642
  logger: this.logger,