@powersync/service-module-mongodb 0.0.0-dev-20250116115804 → 0.0.0-dev-20250122110924

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.
@@ -4,6 +4,7 @@ import { JSONBig, JsonContainer } from '@powersync/service-jsonbig';
4
4
  import { SqliteRow, SqliteValue } from '@powersync/service-sync-rules';
5
5
 
6
6
  import { CHECKPOINTS_COLLECTION } from './replication-utils.js';
7
+ import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
7
8
 
8
9
  export function getMongoRelation(source: mongo.ChangeStreamNameSpace): storage.SourceEntityDescriptor {
9
10
  return {
@@ -97,7 +98,7 @@ function filterJsonData(data: any, depth = 0): any {
97
98
  const autoBigNum = true;
98
99
  if (depth > DEPTH_LIMIT) {
99
100
  // This is primarily to prevent infinite recursion
100
- throw new Error(`json nested object depth exceeds the limit of ${DEPTH_LIMIT}`);
101
+ throw new ServiceError(ErrorCode.PSYNC_S1004, `json nested object depth exceeds the limit of ${DEPTH_LIMIT}`);
101
102
  }
102
103
  if (data === null) {
103
104
  return data;
@@ -1,13 +1,88 @@
1
+ import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
1
2
  import { MongoManager } from './MongoManager.js';
3
+ import { PostImagesOption } from '../types/types.js';
2
4
 
3
5
  export const CHECKPOINTS_COLLECTION = '_powersync_checkpoints';
4
6
 
7
+ const REQUIRED_CHECKPOINT_PERMISSIONS = ['find', 'insert', 'update', 'remove', 'changeStream', 'createCollection'];
8
+
5
9
  export async function checkSourceConfiguration(connectionManager: MongoManager): Promise<void> {
6
10
  const db = connectionManager.db;
11
+
7
12
  const hello = await db.command({ hello: 1 });
8
13
  if (hello.msg == 'isdbgrid') {
9
- throw new Error('Sharded MongoDB Clusters are not supported yet (including MongoDB Serverless instances).');
14
+ throw new ServiceError(
15
+ ErrorCode.PSYNC_S1341,
16
+ 'Sharded MongoDB Clusters are not supported yet (including MongoDB Serverless instances).'
17
+ );
10
18
  } else if (hello.setName == null) {
11
- throw new Error('Standalone MongoDB instances are not supported - use a replicaset.');
19
+ throw new ServiceError(ErrorCode.PSYNC_S1342, 'Standalone MongoDB instances are not supported - use a replicaset.');
20
+ }
21
+
22
+ // https://www.mongodb.com/docs/manual/reference/command/connectionStatus/
23
+ const connectionStatus = await db.command({ connectionStatus: 1, showPrivileges: true });
24
+ const priviledges = connectionStatus.authInfo?.authenticatedUserPrivileges as {
25
+ resource: { db: string; collection: string };
26
+ actions: string[];
27
+ }[];
28
+ let checkpointsActions = new Set<string>();
29
+ let anyCollectionActions = new Set<string>();
30
+ if (priviledges?.length > 0) {
31
+ const onDefaultDb = priviledges.filter((p) => p.resource.db == db.databaseName || p.resource.db == '');
32
+ const onCheckpoints = onDefaultDb.filter(
33
+ (p) => p.resource.collection == CHECKPOINTS_COLLECTION || p.resource?.collection == ''
34
+ );
35
+
36
+ for (let p of onCheckpoints) {
37
+ for (let a of p.actions) {
38
+ checkpointsActions.add(a);
39
+ }
40
+ }
41
+ for (let p of onDefaultDb) {
42
+ for (let a of p.actions) {
43
+ anyCollectionActions.add(a);
44
+ }
45
+ }
46
+
47
+ const missingCheckpointActions = REQUIRED_CHECKPOINT_PERMISSIONS.filter(
48
+ (action) => !checkpointsActions.has(action)
49
+ );
50
+ if (missingCheckpointActions.length > 0) {
51
+ const fullName = `${db.databaseName}.${CHECKPOINTS_COLLECTION}`;
52
+ throw new ServiceError(
53
+ ErrorCode.PSYNC_S1307,
54
+ `MongoDB user does not have the required ${missingCheckpointActions.map((a) => `"${a}"`).join(', ')} priviledge(s) on "${fullName}".`
55
+ );
56
+ }
57
+
58
+ if (connectionManager.options.postImages == PostImagesOption.AUTO_CONFIGURE) {
59
+ // This checks that we have collMod on _any_ collection in the db.
60
+ // This is not a complete check, but does give a basic sanity-check for testing the connection.
61
+ if (!anyCollectionActions.has('collMod')) {
62
+ throw new ServiceError(
63
+ ErrorCode.PSYNC_S1307,
64
+ `MongoDB user does not have the required "collMod" priviledge on "${db.databaseName}", required for "post_images: auto_configure".`
65
+ );
66
+ }
67
+ }
68
+ if (!anyCollectionActions.has('listCollections')) {
69
+ throw new ServiceError(
70
+ ErrorCode.PSYNC_S1307,
71
+ `MongoDB user does not have the required "listCollections" priviledge on "${db.databaseName}".`
72
+ );
73
+ }
74
+ } else {
75
+ // Assume auth is disabled.
76
+ // On Atlas, at least one role/priviledge is required for each user, which will trigger the above.
77
+
78
+ // We do still do a basic check that we can list the collection (it may not actually exist yet).
79
+ await db
80
+ .listCollections(
81
+ {
82
+ name: CHECKPOINTS_COLLECTION
83
+ },
84
+ { nameOnly: false }
85
+ )
86
+ .toArray();
12
87
  }
13
88
  }