@powersync/service-module-mongodb 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/benchmarks/env.ts +11 -0
- package/benchmarks/replication_branch_throughput.benchmark.ts +240 -0
- package/ci/test-connection.yaml +12 -0
- package/dist/api/MongoRouteAPIAdapter.d.ts +6 -0
- package/dist/api/MongoRouteAPIAdapter.js +33 -4
- package/dist/api/MongoRouteAPIAdapter.js.map +1 -1
- package/dist/api/infer-collection-schema.d.ts +5 -0
- package/dist/api/infer-collection-schema.js +24 -0
- package/dist/api/infer-collection-schema.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/module/MongoModule.d.ts +1 -2
- package/dist/module/MongoModule.js +1 -1
- package/dist/module/MongoModule.js.map +1 -1
- package/dist/replication/ChangeStream.d.ts +13 -3
- package/dist/replication/ChangeStream.js +130 -196
- package/dist/replication/ChangeStream.js.map +1 -1
- package/dist/replication/ChangeStreamReplicationJob.d.ts +4 -1
- package/dist/replication/ChangeStreamReplicationJob.js +19 -8
- package/dist/replication/ChangeStreamReplicationJob.js.map +1 -1
- package/dist/replication/ChangeStreamReplicator.d.ts +4 -0
- package/dist/replication/ChangeStreamReplicator.js +6 -0
- package/dist/replication/ChangeStreamReplicator.js.map +1 -1
- package/dist/replication/MongoRelation.js +5 -2
- package/dist/replication/MongoRelation.js.map +1 -1
- package/dist/replication/MongoReplicationQueryProvider.d.ts +85 -0
- package/dist/replication/MongoReplicationQueryProvider.js +23 -0
- package/dist/replication/MongoReplicationQueryProvider.js.map +1 -0
- package/dist/replication/MongoReplicationStream.d.ts +74 -0
- package/dist/replication/MongoReplicationStream.js +228 -0
- package/dist/replication/MongoReplicationStream.js.map +1 -0
- package/dist/replication/MongoReplicationStreamTypes.d.ts +157 -0
- package/dist/replication/MongoReplicationStreamTypes.js +19 -0
- package/dist/replication/MongoReplicationStreamTypes.js.map +1 -0
- package/dist/replication/MongoSnapshotQuery.d.ts +2 -0
- package/dist/replication/MongoSnapshotQuery.js +12 -2
- package/dist/replication/MongoSnapshotQuery.js.map +1 -1
- package/dist/replication/MongoSnapshotter.d.ts +7 -2
- package/dist/replication/MongoSnapshotter.js +90 -88
- package/dist/replication/MongoSnapshotter.js.map +1 -1
- package/dist/replication/RawChangeStream.d.ts +1 -1
- package/dist/replication/RawChangeStream.js +1 -1
- package/dist/replication/RawChangeStream.js.map +1 -1
- package/dist/replication/SourceRowConverter.d.ts +1 -0
- package/dist/replication/SourceRowConverter.js +4 -2
- package/dist/replication/SourceRowConverter.js.map +1 -1
- package/dist/replication/bufferToSqlite.d.ts +3 -1
- package/dist/replication/bufferToSqlite.js +35 -23
- package/dist/replication/bufferToSqlite.js.map +1 -1
- package/dist/replication/estimateFilteredSnapshotCount.d.ts +12 -0
- package/dist/replication/estimateFilteredSnapshotCount.js +79 -0
- package/dist/replication/estimateFilteredSnapshotCount.js.map +1 -0
- package/dist/replication/replication-index.d.ts +7 -0
- package/dist/replication/replication-index.js +7 -0
- package/dist/replication/replication-index.js.map +1 -1
- package/dist/test-utils/ChangeStreamTestContext.d.ts +131 -0
- package/dist/test-utils/ChangeStreamTestContext.js +390 -0
- package/dist/test-utils/ChangeStreamTestContext.js.map +1 -0
- package/dist/test-utils/test-utils-index.d.ts +1 -0
- package/dist/test-utils/test-utils-index.js +2 -0
- package/dist/test-utils/test-utils-index.js.map +1 -0
- package/package.json +11 -10
- package/src/api/MongoRouteAPIAdapter.ts +38 -4
- package/src/api/infer-collection-schema.ts +26 -0
- package/src/index.ts +1 -0
- package/src/module/MongoModule.ts +2 -3
- package/src/replication/ChangeStream.ts +143 -226
- package/src/replication/ChangeStreamReplicationJob.ts +21 -9
- package/src/replication/ChangeStreamReplicator.ts +9 -0
- package/src/replication/MongoRelation.ts +8 -1
- package/src/replication/MongoReplicationQueryProvider.ts +106 -0
- package/src/replication/MongoReplicationStream.ts +314 -0
- package/src/replication/MongoReplicationStreamTypes.ts +169 -0
- package/src/replication/MongoSnapshotQuery.ts +19 -3
- package/src/replication/MongoSnapshotter.ts +101 -92
- package/src/replication/RawChangeStream.ts +2 -2
- package/src/replication/SourceRowConverter.ts +4 -2
- package/src/replication/bufferToSqlite.ts +43 -23
- package/src/replication/estimateFilteredSnapshotCount.ts +93 -0
- package/src/replication/replication-index.ts +7 -0
- package/src/test-utils/ChangeStreamTestContext.ts +520 -0
- package/src/test-utils/test-utils-index.ts +1 -0
- package/test/src/buffer_to_sqlite.test.ts +9 -0
- package/test/src/change_event_memory.test.ts +33 -0
- package/test/src/change_stream.test.ts +3 -3
- package/test/src/change_stream_abort.test.ts +110 -0
- package/test/src/change_stream_test_setup.ts +38 -0
- package/test/src/chunked_snapshot.test.ts +3 -3
- package/test/src/documentdb_mode.test.ts +3 -3
- package/test/src/filtering_example.test.ts +65 -0
- package/test/src/filtering_lifecycle.test.ts +101 -0
- package/test/src/query_provider_availability.test.ts +77 -0
- package/test/src/query_provider_diagnostics.test.ts +55 -0
- package/test/src/replication_stream.test.ts +688 -0
- package/test/src/resume.test.ts +10 -4
- package/test/src/resuming_snapshots.test.ts +3 -3
- package/test/src/schema.test.ts +62 -19
- package/test/src/slow_tests.test.ts +3 -3
- package/test/src/snapshot_count.test.ts +146 -0
- package/test/src/snapshot_count_integration.test.ts +70 -0
- package/test/src/snapshot_query_provider.test.ts +122 -0
- package/test/src/stream_progress.test.ts +334 -0
- package/test/tsconfig.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.benchmark.config.ts +15 -0
- package/test/src/change_stream_utils.ts +0 -375
|
@@ -80,12 +80,16 @@ const SHARED_WRITER = new JsonBufferWriter();
|
|
|
80
80
|
*
|
|
81
81
|
* @param bytes the source BSON bytes
|
|
82
82
|
* @param dateRenderMode derive using getDateRenderMode(compatibilityContext)
|
|
83
|
+
* @param fixedBooleansInJson whether to serialize booleans in nested JSON as booleans, derived from compatibility
|
|
84
|
+
* context.
|
|
83
85
|
*
|
|
84
86
|
* @returns a SqliteRow
|
|
85
87
|
*/
|
|
86
|
-
export function bufferToSqlite(bytes: Buffer, dateRenderMode: DateRenderMode): SqliteRow {
|
|
88
|
+
export function bufferToSqlite(bytes: Buffer, dateRenderMode: DateRenderMode, fixedBooleansInJson: boolean): SqliteRow {
|
|
87
89
|
const row: SqliteRow = {};
|
|
88
90
|
const jsonWriter = SHARED_WRITER;
|
|
91
|
+
const options: SerializeToJsonOptions = { dateRenderMode, fixedBooleans: fixedBooleansInJson };
|
|
92
|
+
|
|
89
93
|
// BSON documents are length-prefixed and null-terminated. We parse directly
|
|
90
94
|
// from raw bytes, so structural validation happens here rather than in the
|
|
91
95
|
// upstream BSON decoder.
|
|
@@ -112,14 +116,14 @@ export function bufferToSqlite(bytes: Buffer, dateRenderMode: DateRenderMode): S
|
|
|
112
116
|
}
|
|
113
117
|
case BSON_TYPE_ARRAY: {
|
|
114
118
|
jsonWriter.reset();
|
|
115
|
-
const result = serializeNestedArrayToJson(bytes, offset, 0, jsonWriter,
|
|
119
|
+
const result = serializeNestedArrayToJson(bytes, offset, 0, jsonWriter, options);
|
|
116
120
|
row[key] = jsonWriter.toString();
|
|
117
121
|
offset = result.nextOffset;
|
|
118
122
|
break;
|
|
119
123
|
}
|
|
120
124
|
case BSON_TYPE_DOCUMENT: {
|
|
121
125
|
jsonWriter.reset();
|
|
122
|
-
const result = serializeNestedObjectToJson(bytes, offset, 0, jsonWriter,
|
|
126
|
+
const result = serializeNestedObjectToJson(bytes, offset, 0, jsonWriter, options);
|
|
123
127
|
row[key] = jsonWriter.toString();
|
|
124
128
|
offset = result.nextOffset;
|
|
125
129
|
break;
|
|
@@ -195,7 +199,7 @@ export function bufferToSqlite(bytes: Buffer, dateRenderMode: DateRenderMode): S
|
|
|
195
199
|
}
|
|
196
200
|
case BSON_TYPE_CODE_WITH_SCOPE: {
|
|
197
201
|
jsonWriter.reset();
|
|
198
|
-
const nextOffset = writeCodeWithScopeJson(bytes, offset, 0, jsonWriter,
|
|
202
|
+
const nextOffset = writeCodeWithScopeJson(bytes, offset, 0, jsonWriter, options);
|
|
199
203
|
row[key] = jsonWriter.toString();
|
|
200
204
|
offset = nextOffset;
|
|
201
205
|
break;
|
|
@@ -437,12 +441,17 @@ function skipBsonValue(bytes: Buffer, offset: number, type: number) {
|
|
|
437
441
|
}
|
|
438
442
|
}
|
|
439
443
|
|
|
444
|
+
interface SerializeToJsonOptions {
|
|
445
|
+
dateRenderMode: DateRenderMode;
|
|
446
|
+
fixedBooleans: boolean;
|
|
447
|
+
}
|
|
448
|
+
|
|
440
449
|
function serializeNestedObjectToJson(
|
|
441
450
|
bytes: Buffer,
|
|
442
451
|
offset: number,
|
|
443
452
|
depth: number,
|
|
444
453
|
writer: JsonBufferWriter,
|
|
445
|
-
|
|
454
|
+
options: SerializeToJsonOptions
|
|
446
455
|
): { nextOffset: number } {
|
|
447
456
|
if (depth > NESTED_DEPTH_LIMIT) {
|
|
448
457
|
throw new Error(`json nested object depth exceeds the limit of ${NESTED_DEPTH_LIMIT}`);
|
|
@@ -475,7 +484,7 @@ function serializeNestedObjectToJson(
|
|
|
475
484
|
type,
|
|
476
485
|
depth,
|
|
477
486
|
writer,
|
|
478
|
-
|
|
487
|
+
options
|
|
479
488
|
);
|
|
480
489
|
cursor = afterValue;
|
|
481
490
|
// Malformed BSON must fail fast instead of getting the parser stuck on the
|
|
@@ -499,7 +508,7 @@ function serializeNestedArrayToJson(
|
|
|
499
508
|
offset: number,
|
|
500
509
|
depth: number,
|
|
501
510
|
writer: JsonBufferWriter,
|
|
502
|
-
|
|
511
|
+
options: SerializeToJsonOptions
|
|
503
512
|
): { nextOffset: number } {
|
|
504
513
|
if (depth > NESTED_DEPTH_LIMIT) {
|
|
505
514
|
throw new Error(`json nested object depth exceeds the limit of ${NESTED_DEPTH_LIMIT}`);
|
|
@@ -527,7 +536,7 @@ function serializeNestedArrayToJson(
|
|
|
527
536
|
type,
|
|
528
537
|
depth,
|
|
529
538
|
writer,
|
|
530
|
-
|
|
539
|
+
options
|
|
531
540
|
);
|
|
532
541
|
cursor = afterValue;
|
|
533
542
|
assertAdvanced(previousCursor, cursor);
|
|
@@ -547,7 +556,7 @@ function serializeNestedElementValue(
|
|
|
547
556
|
type: number,
|
|
548
557
|
depth: number,
|
|
549
558
|
writer: JsonBufferWriter,
|
|
550
|
-
|
|
559
|
+
options: SerializeToJsonOptions
|
|
551
560
|
): { nextOffset: number; defined: boolean } {
|
|
552
561
|
switch (type) {
|
|
553
562
|
case BSON_TYPE_DOUBLE: // Double
|
|
@@ -555,9 +564,9 @@ function serializeNestedElementValue(
|
|
|
555
564
|
case BSON_TYPE_STRING: // String
|
|
556
565
|
return serializeNestedStringElement(bytes, offset, writer);
|
|
557
566
|
case BSON_TYPE_DOCUMENT: // Embedded document
|
|
558
|
-
return serializeNestedObjectElement(bytes, offset, depth, writer,
|
|
567
|
+
return serializeNestedObjectElement(bytes, offset, depth, writer, options);
|
|
559
568
|
case BSON_TYPE_ARRAY: // Array
|
|
560
|
-
return serializeNestedArrayElement(bytes, offset, depth, writer,
|
|
569
|
+
return serializeNestedArrayElement(bytes, offset, depth, writer, options);
|
|
561
570
|
case BSON_TYPE_BINARY: // Binary
|
|
562
571
|
return serializeNestedBinaryElement(bytes, offset, writer);
|
|
563
572
|
case BSON_TYPE_UNDEFINED: // Undefined
|
|
@@ -567,11 +576,22 @@ function serializeNestedElementValue(
|
|
|
567
576
|
writer.writeQuotedHexLower(bytes, offset, 12);
|
|
568
577
|
return { nextOffset: offset + 12, defined: true };
|
|
569
578
|
}
|
|
570
|
-
case BSON_TYPE_BOOLEAN:
|
|
571
|
-
|
|
579
|
+
case BSON_TYPE_BOOLEAN: {
|
|
580
|
+
// Boolean
|
|
581
|
+
const value = !!bytes[offset];
|
|
582
|
+
|
|
583
|
+
if (options.fixedBooleans) {
|
|
584
|
+
const str = value ? 'true' : 'false';
|
|
585
|
+
|
|
586
|
+
writer.writeAscii(str);
|
|
587
|
+
} else {
|
|
588
|
+
writer.writeByte(value ? BYTE_ONE : BYTE_ZERO);
|
|
589
|
+
}
|
|
590
|
+
|
|
572
591
|
return { nextOffset: offset + 1, defined: true };
|
|
592
|
+
}
|
|
573
593
|
case BSON_TYPE_UTC_DATETIME: // UTC datetime
|
|
574
|
-
return serializeNestedDateTimeElement(bytes, offset, writer, dateRenderMode);
|
|
594
|
+
return serializeNestedDateTimeElement(bytes, offset, writer, options.dateRenderMode);
|
|
575
595
|
case BSON_TYPE_NULL: // Null
|
|
576
596
|
case BSON_TYPE_MIN_KEY: // MinKey
|
|
577
597
|
case BSON_TYPE_MAX_KEY: // MaxKey
|
|
@@ -586,7 +606,7 @@ function serializeNestedElementValue(
|
|
|
586
606
|
case BSON_TYPE_SYMBOL: // Symbol
|
|
587
607
|
return serializeNestedSymbolElement(bytes, offset, writer);
|
|
588
608
|
case BSON_TYPE_CODE_WITH_SCOPE: // JavaScript code with scope
|
|
589
|
-
return serializeNestedCodeWithScopeElement(bytes, offset, depth, writer,
|
|
609
|
+
return serializeNestedCodeWithScopeElement(bytes, offset, depth, writer, options);
|
|
590
610
|
case BSON_TYPE_INT32: {
|
|
591
611
|
// Int32
|
|
592
612
|
writer.writeAscii(String(readInt32LE(bytes, offset)));
|
|
@@ -641,9 +661,9 @@ function serializeNestedObjectElement(
|
|
|
641
661
|
offset: number,
|
|
642
662
|
depth: number,
|
|
643
663
|
writer: JsonBufferWriter,
|
|
644
|
-
|
|
664
|
+
options: SerializeToJsonOptions
|
|
645
665
|
): { nextOffset: number; defined: boolean } {
|
|
646
|
-
const result = serializeNestedObjectToJson(bytes, offset, depth + 1, writer,
|
|
666
|
+
const result = serializeNestedObjectToJson(bytes, offset, depth + 1, writer, options);
|
|
647
667
|
return { nextOffset: result.nextOffset, defined: true };
|
|
648
668
|
}
|
|
649
669
|
|
|
@@ -652,9 +672,9 @@ function serializeNestedArrayElement(
|
|
|
652
672
|
offset: number,
|
|
653
673
|
depth: number,
|
|
654
674
|
writer: JsonBufferWriter,
|
|
655
|
-
|
|
675
|
+
options: SerializeToJsonOptions
|
|
656
676
|
): { nextOffset: number; defined: boolean } {
|
|
657
|
-
const result = serializeNestedArrayToJson(bytes, offset, depth + 1, writer,
|
|
677
|
+
const result = serializeNestedArrayToJson(bytes, offset, depth + 1, writer, options);
|
|
658
678
|
return { nextOffset: result.nextOffset, defined: true };
|
|
659
679
|
}
|
|
660
680
|
|
|
@@ -791,14 +811,14 @@ function writeCodeWithScopeJson(
|
|
|
791
811
|
offset: number,
|
|
792
812
|
depth: number,
|
|
793
813
|
writer: JsonBufferWriter,
|
|
794
|
-
|
|
814
|
+
options: SerializeToJsonOptions
|
|
795
815
|
) {
|
|
796
816
|
const totalLength = readInt32LE(bytes, offset);
|
|
797
817
|
const { value: code, nextOffset: afterCode } = readBsonString(bytes, offset + 4);
|
|
798
818
|
writer.writeAscii('{"code":');
|
|
799
819
|
writer.writeQuotedJsonString(code);
|
|
800
820
|
writer.writeAscii(',"scope":');
|
|
801
|
-
serializeNestedObjectToJson(bytes, afterCode, depth + 1, writer,
|
|
821
|
+
serializeNestedObjectToJson(bytes, afterCode, depth + 1, writer, options);
|
|
802
822
|
writer.writeByte(BYTE_RBRACE);
|
|
803
823
|
// code_w_scope carries its own total byte length, so we trust that wrapper
|
|
804
824
|
// rather than reconstructing the end position from the nested scope.
|
|
@@ -885,10 +905,10 @@ function serializeNestedCodeWithScopeElement(
|
|
|
885
905
|
offset: number,
|
|
886
906
|
depth: number,
|
|
887
907
|
writer: JsonBufferWriter,
|
|
888
|
-
|
|
908
|
+
options: SerializeToJsonOptions
|
|
889
909
|
): { nextOffset: number; defined: boolean } {
|
|
890
910
|
return {
|
|
891
|
-
nextOffset: writeCodeWithScopeJson(bytes, offset, depth, writer,
|
|
911
|
+
nextOffset: writeCodeWithScopeJson(bytes, offset, depth, writer, options),
|
|
892
912
|
defined: true
|
|
893
913
|
};
|
|
894
914
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import type { Logger } from '@powersync/lib-services-framework';
|
|
3
|
+
import { performance } from 'node:perf_hooks';
|
|
4
|
+
|
|
5
|
+
const COUNT_TIMEOUT_MS = 10_000;
|
|
6
|
+
/**
|
|
7
|
+
* Keep below 5% for MongoDB's random-cursor optimization on large collections.
|
|
8
|
+
* https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/
|
|
9
|
+
*/
|
|
10
|
+
const SAMPLE_FRACTION = 0.01;
|
|
11
|
+
const MAX_SAMPLE_SIZE = 10_000;
|
|
12
|
+
|
|
13
|
+
interface SampleCounts {
|
|
14
|
+
sampled: { count: number }[];
|
|
15
|
+
matches: { count: number }[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Try an exact filtered count, then estimate selectivity from a bounded sample.
|
|
20
|
+
* Each attempt has a separate 10-second budget. Failures leave progress unknown (-1).
|
|
21
|
+
*/
|
|
22
|
+
export async function estimateFilteredSnapshotCount(options: {
|
|
23
|
+
collection: mongo.Collection;
|
|
24
|
+
filter: mongo.Filter<mongo.Document>;
|
|
25
|
+
signal: AbortSignal;
|
|
26
|
+
logger: Logger;
|
|
27
|
+
}): Promise<number> {
|
|
28
|
+
const { collection, filter, signal, logger } = options;
|
|
29
|
+
signal.throwIfAborted();
|
|
30
|
+
try {
|
|
31
|
+
return await collection.countDocuments(filter, {
|
|
32
|
+
collation: { locale: 'simple' },
|
|
33
|
+
readConcern: 'majority',
|
|
34
|
+
timeoutMS: COUNT_TIMEOUT_MS,
|
|
35
|
+
signal
|
|
36
|
+
});
|
|
37
|
+
} catch (error) {
|
|
38
|
+
signal.throwIfAborted();
|
|
39
|
+
logger.debug(`Filtered snapshot count unavailable for ${collection.namespace}; trying a sample strategy`, error);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sampleStartedAt = performance.now();
|
|
43
|
+
try {
|
|
44
|
+
const metadataOptions = { timeoutMS: COUNT_TIMEOUT_MS, signal };
|
|
45
|
+
const totalCount = await collection.estimatedDocumentCount(metadataOptions);
|
|
46
|
+
signal.throwIfAborted();
|
|
47
|
+
if (totalCount <= 0) {
|
|
48
|
+
// Metadata alone cannot establish that a filtered result is empty after the count failed.
|
|
49
|
+
return -1;
|
|
50
|
+
}
|
|
51
|
+
const remainingTimeMS = Math.floor(COUNT_TIMEOUT_MS - (performance.now() - sampleStartedAt));
|
|
52
|
+
if (remainingTimeMS <= 0) {
|
|
53
|
+
return -1;
|
|
54
|
+
}
|
|
55
|
+
// Keep $sample first and below 5% for MongoDB's random-cursor optimization on large collections.
|
|
56
|
+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/
|
|
57
|
+
const sampleSize = Math.min(MAX_SAMPLE_SIZE, Math.max(1, Math.floor(totalCount * SAMPLE_FRACTION)));
|
|
58
|
+
const cursor = collection.aggregate<SampleCounts>(
|
|
59
|
+
[
|
|
60
|
+
{ $sample: { size: sampleSize } },
|
|
61
|
+
{
|
|
62
|
+
$facet: {
|
|
63
|
+
sampled: [{ $count: 'count' }],
|
|
64
|
+
matches: [{ $match: filter }, { $count: 'count' }]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
{
|
|
69
|
+
collation: { locale: 'simple' },
|
|
70
|
+
readConcern: 'majority',
|
|
71
|
+
timeoutMS: remainingTimeMS,
|
|
72
|
+
signal,
|
|
73
|
+
allowDiskUse: false
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
try {
|
|
77
|
+
const sample = await cursor.next();
|
|
78
|
+
const sampledCount = sample?.sampled[0]?.count ?? 0;
|
|
79
|
+
const matchedCount = sample?.matches[0]?.count ?? 0;
|
|
80
|
+
if (sampledCount == 0 || matchedCount == 0) {
|
|
81
|
+
// A rare predicate can miss the whole sample; that does not establish an empty result.
|
|
82
|
+
return -1;
|
|
83
|
+
}
|
|
84
|
+
return Math.round((matchedCount / sampledCount) * totalCount);
|
|
85
|
+
} finally {
|
|
86
|
+
await cursor.close();
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
signal.throwIfAborted();
|
|
90
|
+
logger.debug(`Filtered snapshot sample unavailable for ${collection.namespace}; total remains unknown`, error);
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export * from './ChangeStream.js';
|
|
2
2
|
export * from './ChangeStreamReplicationJob.js';
|
|
3
3
|
export * from './ChangeStreamReplicator.js';
|
|
4
|
+
export * from './ConnectionManagerFactory.js';
|
|
4
5
|
export * from './internal-mongodb-utils.js';
|
|
6
|
+
export * from './MongoErrorRateLimiter.js';
|
|
5
7
|
export * from './MongoRelation.js';
|
|
8
|
+
export * from './MongoReplicationQueryProvider.js';
|
|
9
|
+
export * from './MongoReplicationStream.js';
|
|
10
|
+
export * from './MongoReplicationStreamTypes.js';
|
|
11
|
+
export * from './MongoSnapshotter.js';
|
|
6
12
|
export * from './RawChangeStream.js';
|
|
13
|
+
export { CHECKPOINTS_COLLECTION } from './replication-utils.js';
|
|
7
14
|
export * from './SourceRowConverter.js';
|