@powersync/service-module-mongodb 0.0.0-dev-20261007110800 → 0.0.0-dev-20261015071128
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 +13 -11
- package/benchmarks/env.ts +11 -0
- package/benchmarks/replication_branch_throughput.benchmark.ts +240 -0
- package/dist/api/MongoRouteAPIAdapter.d.ts +5 -0
- package/dist/api/MongoRouteAPIAdapter.js +11 -4
- package/dist/api/MongoRouteAPIAdapter.js.map +1 -1
- package/dist/module/MongoModule.js +1 -0
- package/dist/module/MongoModule.js.map +1 -1
- package/dist/replication/ChangeStream.d.ts +2 -1
- package/dist/replication/ChangeStream.js +164 -150
- package/dist/replication/ChangeStream.js.map +1 -1
- package/dist/replication/ChangeStreamReplicationJob.js +17 -9
- package/dist/replication/ChangeStreamReplicationJob.js.map +1 -1
- package/dist/replication/MongoReplicationQueryProvider.d.ts +9 -11
- package/dist/replication/MongoReplicationQueryProvider.js.map +1 -1
- package/dist/replication/MongoReplicationStream.d.ts +37 -48
- package/dist/replication/MongoReplicationStream.js +157 -30
- package/dist/replication/MongoReplicationStream.js.map +1 -1
- 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/MongoSnapshotter.js +45 -25
- package/dist/replication/MongoSnapshotter.js.map +1 -1
- package/dist/replication/replication-index.d.ts +1 -0
- package/dist/replication/replication-index.js +1 -0
- package/dist/replication/replication-index.js.map +1 -1
- package/package.json +11 -10
- package/src/api/MongoRouteAPIAdapter.ts +12 -5
- package/src/module/MongoModule.ts +1 -0
- package/src/replication/ChangeStream.ts +183 -173
- package/src/replication/ChangeStreamReplicationJob.ts +16 -9
- package/src/replication/MongoReplicationQueryProvider.ts +9 -11
- package/src/replication/MongoReplicationStream.ts +196 -83
- package/src/replication/MongoReplicationStreamTypes.ts +169 -0
- package/src/replication/MongoSnapshotter.ts +49 -28
- package/src/replication/replication-index.ts +1 -0
- package/test/src/change_event_memory.test.ts +33 -0
- package/test/src/change_stream_abort.test.ts +110 -0
- 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 +1 -0
- package/test/src/query_provider_diagnostics.test.ts +55 -0
- package/test/src/replication_stream.test.ts +411 -34
- package/test/src/stream_progress.test.ts +62 -44
- package/test/tsconfig.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.benchmark.config.ts +15 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_MONGO_REPLICATION_QUERY_PROVIDER } from '@module/replication/MongoReplicationQueryProvider.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createMongoReplicationStreamBatch,
|
|
4
|
+
filterMongoReplicationStream
|
|
5
|
+
} from '@module/replication/MongoReplicationStream.js';
|
|
6
|
+
import type { MongoReplicationStreamBatch } from '@module/replication/MongoReplicationStreamTypes.js';
|
|
7
|
+
import { MongoReplicationFilterResult } from '@module/replication/MongoReplicationStreamTypes.js';
|
|
3
8
|
import { describe, expect, test, vi } from 'vitest';
|
|
4
9
|
import { openChangeStreamTestContext } from './change_stream_test_setup.js';
|
|
5
10
|
import { DATABASE_TYPE, DatabaseType } from './DatabaseType.js';
|
|
@@ -41,31 +46,33 @@ describe.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('MongoDB durable adapt
|
|
|
41
46
|
const provider = {
|
|
42
47
|
...DEFAULT_MONGO_REPLICATION_QUERY_PROVIDER,
|
|
43
48
|
openChangeStream: ({ open }: Parameters<typeof DEFAULT_MONGO_REPLICATION_QUERY_PROVIDER.openChangeStream>[0]) =>
|
|
44
|
-
(async function* (): AsyncGenerator<
|
|
49
|
+
(async function* (): AsyncGenerator<MongoReplicationStreamBatch> {
|
|
45
50
|
let retained = false;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const filteredStream = filterMongoReplicationStream({
|
|
52
|
+
batches: open({}),
|
|
53
|
+
filter: (event) => {
|
|
54
|
+
if (event.ns.coll == 'documents') {
|
|
55
|
+
if (String(event.documentKey._id) != '1') {
|
|
56
|
+
return MongoReplicationFilterResult.EXCLUDE;
|
|
57
|
+
}
|
|
58
|
+
retained = true;
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
} else if (item.type == 'progress') {
|
|
55
|
-
// open({}) uses the shared reader, which emits progress with the batch's resume token
|
|
56
|
-
// after consuming a MongoDB batch, provided no split event is incomplete. This is a
|
|
57
|
-
// reader-generated boundary, not a MongoDB change event or a result of filtering.
|
|
58
|
-
// If a boundary arrives after retaining document 1 but before excluding any later
|
|
59
|
-
// documents, suppress it until a boundary also covers excluded changes. Forwarding
|
|
60
|
-
// that boundary triggers the injected flush failure with document 1 still pending,
|
|
61
|
-
// verifying that its write must be durable before the resume token can be saved.
|
|
62
|
-
if (retained && filtered == 0) continue;
|
|
63
|
-
yield { ...item, filteredCount: filtered };
|
|
64
|
-
retained = false;
|
|
65
|
-
filtered = 0;
|
|
66
|
-
continue;
|
|
60
|
+
return MongoReplicationFilterResult.KEEP;
|
|
67
61
|
}
|
|
68
|
-
|
|
62
|
+
});
|
|
63
|
+
for await (const batch of filteredStream) {
|
|
64
|
+
yield createMongoReplicationStreamBatch({
|
|
65
|
+
events: batch.events,
|
|
66
|
+
resolveResumeToken: () => {
|
|
67
|
+
const progress = batch.getResumeToken();
|
|
68
|
+
if (progress == null || (retained && batch.metadata.filteredCount == 0)) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
retained = false;
|
|
72
|
+
return progress;
|
|
73
|
+
},
|
|
74
|
+
resolveMetadata: () => batch.metadata
|
|
75
|
+
});
|
|
69
76
|
}
|
|
70
77
|
})()
|
|
71
78
|
};
|
|
@@ -224,32 +231,43 @@ describe.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('MongoDB durable adapt
|
|
|
224
231
|
createReplicationQueryProvider: () => ({
|
|
225
232
|
...DEFAULT_MONGO_REPLICATION_QUERY_PROVIDER,
|
|
226
233
|
openChangeStream({ open }) {
|
|
227
|
-
return (async function* (): AsyncGenerator<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
234
|
+
return (async function* (): AsyncGenerator<MongoReplicationStreamBatch> {
|
|
235
|
+
for await (const batch of open({})) {
|
|
236
|
+
let count = 0;
|
|
237
|
+
const positionBeforeBatch = (await context.storage!.getStatus()).resumeLsn!;
|
|
238
|
+
const events = (function* () {
|
|
239
|
+
for (const change of batch.events) {
|
|
240
|
+
if ('ns' in change.event && change.event.ns.coll == 'documents') {
|
|
241
|
+
count++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
yield change;
|
|
245
|
+
}
|
|
246
|
+
})();
|
|
247
|
+
let forwardedProgress = false;
|
|
248
|
+
yield createMongoReplicationStreamBatch({
|
|
249
|
+
events: events,
|
|
250
|
+
resolveResumeToken: () => {
|
|
251
|
+
const progress = batch.getResumeToken();
|
|
252
|
+
if (progress == null || count == 0) {
|
|
253
|
+
return progress;
|
|
254
|
+
}
|
|
255
|
+
filtered += count;
|
|
256
|
+
beforeFailure = positionBeforeBatch;
|
|
257
|
+
if (scenario == 'adapter failure') throw new Error('injected adapter failure');
|
|
258
|
+
failFlush = scenario == 'flush failure';
|
|
259
|
+
forwardedProgress = true;
|
|
260
|
+
return progress;
|
|
261
|
+
},
|
|
262
|
+
resolveMetadata: () => ({ filteredCount: count })
|
|
263
|
+
});
|
|
264
|
+
if (forwardedProgress) {
|
|
265
|
+
// next() is requested only after this module has handled the batch, including its resume token. Inspect actual
|
|
246
266
|
// persisted state here, without sleeps or waiting for a later checkpoint event.
|
|
247
267
|
savedPositions.push((await context.storage!.getStatus()).resumeLsn!);
|
|
248
268
|
// End the successful case deliberately while the later checkpoint marker is
|
|
249
269
|
// still pending, so assertions observe progress independently of a commit.
|
|
250
270
|
if (savedPositions.length == 3) throw new Error('stopped after three safe boundaries');
|
|
251
|
-
} else {
|
|
252
|
-
yield item;
|
|
253
271
|
}
|
|
254
272
|
}
|
|
255
273
|
})();
|
package/test/tsconfig.json
CHANGED