@powersync/service-module-mongodb 0.16.0 → 0.17.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 +33 -0
- package/dist/api/MongoRouteAPIAdapter.js +12 -21
- package/dist/api/MongoRouteAPIAdapter.js.map +1 -1
- package/dist/replication/ChangeStream.d.ts +18 -37
- package/dist/replication/ChangeStream.js +136 -351
- package/dist/replication/ChangeStream.js.map +1 -1
- package/dist/replication/MongoRelation.d.ts +1 -1
- package/dist/replication/MongoRelation.js +41 -21
- package/dist/replication/MongoRelation.js.map +1 -1
- package/dist/replication/MongoSnapshotter.d.ts +81 -0
- package/dist/replication/MongoSnapshotter.js +594 -0
- package/dist/replication/MongoSnapshotter.js.map +1 -0
- package/package.json +8 -8
- package/src/api/MongoRouteAPIAdapter.ts +13 -21
- package/src/replication/ChangeStream.ts +150 -426
- package/src/replication/MongoRelation.ts +51 -25
- package/src/replication/MongoSnapshotter.ts +729 -0
- package/test/src/change_stream.test.ts +210 -17
- package/test/src/change_stream_utils.ts +24 -17
- package/test/src/checkpoint_retry.test.ts +131 -0
- package/test/src/resuming_snapshots.test.ts +10 -6
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -13,12 +13,15 @@ import {
|
|
|
13
13
|
TimeValuePrecision
|
|
14
14
|
} from '@powersync/service-sync-rules';
|
|
15
15
|
|
|
16
|
-
import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
16
|
+
import { ErrorCode, logger, ServiceAssertionError, ServiceError } from '@powersync/lib-services-framework';
|
|
17
17
|
import { MongoLSN } from '../common/MongoLSN.js';
|
|
18
|
-
import { CHECKPOINTS_COLLECTION } from './replication-utils.js';
|
|
19
18
|
|
|
20
|
-
export function getMongoRelation(
|
|
19
|
+
export function getMongoRelation(
|
|
20
|
+
source: mongo.ChangeStreamNameSpace,
|
|
21
|
+
connectionTag: string
|
|
22
|
+
): storage.SourceEntityDescriptor {
|
|
21
23
|
return {
|
|
24
|
+
connectionTag,
|
|
22
25
|
name: source.coll,
|
|
23
26
|
schema: source.db,
|
|
24
27
|
// Not relevant for MongoDB - we use db + coll name as the identifier
|
|
@@ -175,30 +178,53 @@ export async function createCheckpoint(
|
|
|
175
178
|
db: mongo.Db,
|
|
176
179
|
id: mongo.ObjectId | string
|
|
177
180
|
): Promise<string> {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
{
|
|
188
|
-
$inc: { i: 1 }
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
upsert: true,
|
|
192
|
-
returnDocument: 'after',
|
|
193
|
-
session
|
|
181
|
+
const TRIES = 2;
|
|
182
|
+
for (let i = 0; i < TRIES; i++) {
|
|
183
|
+
try {
|
|
184
|
+
return await createCheckpointInner(client, db, id);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
if (i < TRIES - 1) {
|
|
187
|
+
logger.warn(`Failed to create checkpoint on attempt ${i + 1}`, e);
|
|
188
|
+
} else {
|
|
189
|
+
throw e;
|
|
194
190
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
throw new ServiceAssertionError(`Unreachable code`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function createCheckpointInner(
|
|
197
|
+
client: mongo.MongoClient,
|
|
198
|
+
db: mongo.Db,
|
|
199
|
+
id: mongo.ObjectId | string
|
|
200
|
+
): Promise<string> {
|
|
201
|
+
// We use an unique id per process, and clear documents on startup.
|
|
202
|
+
// This is so that we can filter events for our own process only, and ignore
|
|
203
|
+
// events from other processes.
|
|
204
|
+
|
|
205
|
+
// We use a command instead of a regular update to avoid auto retries on writes.
|
|
206
|
+
// An auto retry on the write can trigger a weird edge case where the change stream event
|
|
207
|
+
// has the clusterTime of the first write, while the returned operation time is for the second no-op write.
|
|
208
|
+
// Instead, we do manual retries, which does not have the same write de-duplication logic.
|
|
209
|
+
// A sentinal-based approach would be better here, but that is a much bigger change.
|
|
210
|
+
|
|
211
|
+
const response = await db.command({
|
|
212
|
+
findAndModify: '_powersync_checkpoints',
|
|
213
|
+
query: {
|
|
214
|
+
_id: id as any
|
|
215
|
+
},
|
|
216
|
+
new: true,
|
|
217
|
+
upsert: true,
|
|
218
|
+
update: {
|
|
219
|
+
$inc: { i: 1 }
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const time = response.operationTime as mongo.Timestamp | undefined;
|
|
224
|
+
if (time == null) {
|
|
225
|
+
throw new ServiceError(ErrorCode.PSYNC_S1004, `clusterTime not available for checkpoint`);
|
|
201
226
|
}
|
|
227
|
+
return new MongoLSN({ timestamp: time }).comparable;
|
|
202
228
|
}
|
|
203
229
|
|
|
204
230
|
const mongoTimeOptions: DateTimeSourceOptions = {
|