Browse Source

feat(revisions): add MongoDB support (#715)

* feat(revisions): add MongoDB support

* fix: add missing mongodb from revisions

* fix: mongodb bson imports
Karol Sójko 1 year ago
parent
commit
2646b756a9
24 changed files with 472 additions and 185 deletions
  1. 34 118
      .pnp.cjs
  2. BIN
      .yarn/cache/@mongodb-js-saslprep-npm-1.1.0-3906c025b8-2cf6d124d4.zip
  3. BIN
      .yarn/cache/bson-npm-6.0.0-7b3cba060e-7290998ee8.zip
  4. BIN
      .yarn/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip
  5. BIN
      .yarn/cache/mongodb-npm-6.0.0-7c1e74de91-daec6dc9dc.zip
  6. BIN
      .yarn/cache/saslprep-npm-1.0.3-8db649c346-23ebcda091.zip
  7. 8 0
      packages/revisions/.env.sample
  8. 1 0
      packages/revisions/package.json
  9. 53 19
      packages/revisions/src/Bootstrap/Container.ts
  10. 49 8
      packages/revisions/src/Bootstrap/DataSource.ts
  11. 7 2
      packages/revisions/src/Bootstrap/Types.ts
  12. 40 0
      packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevision.ts
  13. 123 0
      packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevisionRepository.ts
  14. 0 0
      packages/revisions/src/Infra/TypeORM/SQLRevision.ts
  15. 1 1
      packages/revisions/src/Infra/TypeORM/SQLRevisionRepository.ts
  16. 1 1
      packages/revisions/src/Mapping/Backup/RevisionItemStringMapper.ts
  17. 1 1
      packages/revisions/src/Mapping/Http/RevisionHttpMapper.ts
  18. 1 1
      packages/revisions/src/Mapping/Http/RevisionMetadataHttpMapper.ts
  19. 41 0
      packages/revisions/src/Mapping/Persistence/MongoDB/MongoDBRevisionMetadataPersistenceMapper.ts
  20. 74 0
      packages/revisions/src/Mapping/Persistence/MongoDB/MongoDBRevisionPersistenceMapper.ts
  21. 3 3
      packages/revisions/src/Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper.ts
  22. 4 3
      packages/revisions/src/Mapping/Persistence/SQL/SQLRevisionPersistenceMapper.ts
  23. 1 1
      packages/syncing-server/package.json
  24. 30 27
      yarn.lock

+ 34 - 118
.pnp.cjs

@@ -3602,6 +3602,16 @@ const RAW_RUNTIME_STATE =
         "linkType": "HARD"\
       }]\
     ]],\
+    ["@mongodb-js/saslprep", [\
+      ["npm:1.1.0", {\
+        "packageLocation": "./.yarn/cache/@mongodb-js-saslprep-npm-1.1.0-3906c025b8-2cf6d124d4.zip/node_modules/@mongodb-js/saslprep/",\
+        "packageDependencies": [\
+          ["@mongodb-js/saslprep", "npm:1.1.0"],\
+          ["sparse-bitfield", "npm:3.0.3"]\
+        ],\
+        "linkType": "HARD"\
+      }]\
+    ]],\
     ["@mrleebo/prisma-ast", [\
       ["npm:0.5.2", {\
         "packageLocation": "./.yarn/cache/@mrleebo-prisma-ast-npm-0.5.2-538c9d793e-69a7f3c188.zip/node_modules/@mrleebo/prisma-ast/",\
@@ -4997,6 +5007,7 @@ const RAW_RUNTIME_STATE =
           ["inversify", "npm:6.0.1"],\
           ["inversify-express-utils", "npm:6.4.3"],\
           ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
+          ["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
           ["mysql2", "npm:3.3.3"],\
           ["newrelic", "npm:10.1.2"],\
           ["npm-check-updates", "npm:16.10.12"],\
@@ -5191,7 +5202,7 @@ const RAW_RUNTIME_STATE =
           ["inversify-express-utils", "npm:6.4.3"],\
           ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
           ["jsonwebtoken", "npm:9.0.0"],\
-          ["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
+          ["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
           ["mysql2", "npm:3.3.3"],\
           ["newrelic", "npm:10.1.2"],\
           ["nodemon", "npm:2.0.22"],\
@@ -5202,7 +5213,7 @@ const RAW_RUNTIME_STATE =
           ["semver", "npm:7.5.1"],\
           ["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
           ["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
-          ["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16"],\
+          ["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
           ["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
           ["ua-parser-js", "npm:1.0.35"],\
           ["uuid", "npm:9.0.0"],\
@@ -7096,10 +7107,10 @@ const RAW_RUNTIME_STATE =
       }]\
     ]],\
     ["bson", [\
-      ["npm:5.4.0", {\
-        "packageLocation": "./.yarn/cache/bson-npm-5.4.0-2f854c8216-2c913a45c0.zip/node_modules/bson/",\
+      ["npm:6.0.0", {\
+        "packageLocation": "./.yarn/cache/bson-npm-6.0.0-7b3cba060e-7290998ee8.zip/node_modules/bson/",\
         "packageDependencies": [\
-          ["bson", "npm:5.4.0"]\
+          ["bson", "npm:6.0.0"]\
         ],\
         "linkType": "HARD"\
       }]\
@@ -12330,43 +12341,50 @@ const RAW_RUNTIME_STATE =
       }]\
     ]],\
     ["mongodb", [\
-      ["npm:5.7.0", {\
-        "packageLocation": "./.yarn/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip/node_modules/mongodb/",\
+      ["npm:6.0.0", {\
+        "packageLocation": "./.yarn/cache/mongodb-npm-6.0.0-7c1e74de91-daec6dc9dc.zip/node_modules/mongodb/",\
         "packageDependencies": [\
-          ["mongodb", "npm:5.7.0"]\
+          ["mongodb", "npm:6.0.0"]\
         ],\
         "linkType": "SOFT"\
       }],\
-      ["virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0", {\
-        "packageLocation": "./.yarn/__virtual__/mongodb-virtual-eb0cd47e23/0/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip/node_modules/mongodb/",\
+      ["virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0", {\
+        "packageLocation": "./.yarn/__virtual__/mongodb-virtual-789f2eaaac/0/cache/mongodb-npm-6.0.0-7c1e74de91-daec6dc9dc.zip/node_modules/mongodb/",\
         "packageDependencies": [\
-          ["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
+          ["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
           ["@aws-sdk/credential-providers", null],\
+          ["@mongodb-js/saslprep", "npm:1.1.0"],\
           ["@mongodb-js/zstd", null],\
           ["@types/aws-sdk__credential-providers", null],\
+          ["@types/gcp-metadata", null],\
           ["@types/kerberos", null],\
           ["@types/mongodb-client-encryption", null],\
           ["@types/mongodb-js__zstd", null],\
           ["@types/snappy", null],\
-          ["bson", "npm:5.4.0"],\
+          ["@types/socks", null],\
+          ["bson", "npm:6.0.0"],\
+          ["gcp-metadata", null],\
           ["kerberos", null],\
           ["mongodb-client-encryption", null],\
           ["mongodb-connection-string-url", "npm:2.6.0"],\
-          ["saslprep", "npm:1.0.3"],\
           ["snappy", null],\
-          ["socks", "npm:2.7.1"]\
+          ["socks", null]\
         ],\
         "packagePeers": [\
           "@aws-sdk/credential-providers",\
           "@mongodb-js/zstd",\
           "@types/aws-sdk__credential-providers",\
+          "@types/gcp-metadata",\
           "@types/kerberos",\
           "@types/mongodb-client-encryption",\
           "@types/mongodb-js__zstd",\
           "@types/snappy",\
+          "@types/socks",\
+          "gcp-metadata",\
           "kerberos",\
           "mongodb-client-encryption",\
-          "snappy"\
+          "snappy",\
+          "socks"\
         ],\
         "linkType": "HARD"\
       }]\
@@ -14341,16 +14359,6 @@ const RAW_RUNTIME_STATE =
         "linkType": "HARD"\
       }]\
     ]],\
-    ["saslprep", [\
-      ["npm:1.0.3", {\
-        "packageLocation": "./.yarn/cache/saslprep-npm-1.0.3-8db649c346-23ebcda091.zip/node_modules/saslprep/",\
-        "packageDependencies": [\
-          ["saslprep", "npm:1.0.3"],\
-          ["sparse-bitfield", "npm:3.0.3"]\
-        ],\
-        "linkType": "HARD"\
-      }]\
-    ]],\
     ["schema-utils", [\
       ["npm:3.1.2", {\
         "packageLocation": "./.yarn/cache/schema-utils-npm-3.1.2-d97c6dc247-11d35f997e.zip/node_modules/schema-utils/",\
@@ -15821,99 +15829,7 @@ const RAW_RUNTIME_STATE =
           ["hdb-pool", null],\
           ["ioredis", null],\
           ["mkdirp", "npm:2.1.6"],\
-          ["mongodb", null],\
-          ["mssql", null],\
-          ["mysql2", "npm:3.3.3"],\
-          ["oracledb", null],\
-          ["pg", null],\
-          ["pg-native", null],\
-          ["pg-query-stream", null],\
-          ["redis", null],\
-          ["reflect-metadata", "npm:0.1.13"],\
-          ["sha.js", "npm:2.4.11"],\
-          ["sql.js", null],\
-          ["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
-          ["ts-node", null],\
-          ["tslib", "npm:2.5.2"],\
-          ["typeorm-aurora-data-api-driver", null],\
-          ["uuid", "npm:9.0.0"],\
-          ["yargs", "npm:17.7.2"]\
-        ],\
-        "packagePeers": [\
-          "@google-cloud/spanner",\
-          "@sap/hana-client",\
-          "@types/better-sqlite3",\
-          "@types/google-cloud__spanner",\
-          "@types/hdb-pool",\
-          "@types/ioredis",\
-          "@types/mongodb",\
-          "@types/mssql",\
-          "@types/mysql2",\
-          "@types/oracledb",\
-          "@types/pg-native",\
-          "@types/pg-query-stream",\
-          "@types/pg",\
-          "@types/redis",\
-          "@types/sap__hana-client",\
-          "@types/sql.js",\
-          "@types/sqlite3",\
-          "@types/ts-node",\
-          "@types/typeorm-aurora-data-api-driver",\
-          "better-sqlite3",\
-          "hdb-pool",\
-          "ioredis",\
-          "mongodb",\
-          "mssql",\
-          "mysql2",\
-          "oracledb",\
-          "pg-native",\
-          "pg-query-stream",\
-          "pg",\
-          "redis",\
-          "sql.js",\
-          "sqlite3",\
-          "ts-node",\
-          "typeorm-aurora-data-api-driver"\
-        ],\
-        "linkType": "HARD"\
-      }],\
-      ["virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16", {\
-        "packageLocation": "./.yarn/__virtual__/typeorm-virtual-13b6364fde/0/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
-        "packageDependencies": [\
-          ["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16"],\
-          ["@google-cloud/spanner", null],\
-          ["@sap/hana-client", null],\
-          ["@sqltools/formatter", "npm:1.2.5"],\
-          ["@types/better-sqlite3", null],\
-          ["@types/google-cloud__spanner", null],\
-          ["@types/hdb-pool", null],\
-          ["@types/ioredis", null],\
-          ["@types/mongodb", null],\
-          ["@types/mssql", null],\
-          ["@types/mysql2", null],\
-          ["@types/oracledb", null],\
-          ["@types/pg", null],\
-          ["@types/pg-native", null],\
-          ["@types/pg-query-stream", null],\
-          ["@types/redis", null],\
-          ["@types/sap__hana-client", null],\
-          ["@types/sql.js", null],\
-          ["@types/sqlite3", null],\
-          ["@types/ts-node", null],\
-          ["@types/typeorm-aurora-data-api-driver", null],\
-          ["app-root-path", "npm:3.1.0"],\
-          ["better-sqlite3", null],\
-          ["buffer", "npm:6.0.3"],\
-          ["chalk", "npm:4.1.2"],\
-          ["cli-highlight", "npm:2.1.11"],\
-          ["date-fns", "npm:2.30.0"],\
-          ["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"],\
-          ["dotenv", "npm:16.1.3"],\
-          ["glob", "npm:8.1.0"],\
-          ["hdb-pool", null],\
-          ["ioredis", null],\
-          ["mkdirp", "npm:2.1.6"],\
-          ["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
+          ["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
           ["mssql", null],\
           ["mysql2", "npm:3.3.3"],\
           ["oracledb", null],\

BIN
.yarn/cache/@mongodb-js-saslprep-npm-1.1.0-3906c025b8-2cf6d124d4.zip


BIN
.yarn/cache/bson-npm-5.4.0-2f854c8216-2c913a45c0.zip → .yarn/cache/bson-npm-6.0.0-7b3cba060e-7290998ee8.zip


BIN
.yarn/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip


BIN
.yarn/cache/mongodb-npm-6.0.0-7c1e74de91-daec6dc9dc.zip


BIN
.yarn/cache/saslprep-npm-1.0.3-8db649c346-23ebcda091.zip


+ 8 - 0
packages/revisions/.env.sample

@@ -33,3 +33,11 @@ NEW_RELIC_NO_CONFIG_FILE=true
 NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=false
 NEW_RELIC_LOG_ENABLED=false
 NEW_RELIC_LOG_LEVEL=info
+
+# (Optional) Mongo Setup
+SECONDARY_DB_ENABLED=false
+MONGO_HOST=
+MONGO_PORT=
+MONGO_USERNAME=
+MONGO_PASSWORD=
+MONGO_DATABASE=

+ 1 - 0
packages/revisions/package.json

@@ -40,6 +40,7 @@
     "express": "^4.18.2",
     "inversify": "^6.0.1",
     "inversify-express-utils": "^6.4.3",
+    "mongodb": "^6.0.0",
     "mysql2": "^3.0.1",
     "reflect-metadata": "0.1.13",
     "sqlite3": "^5.1.6",

+ 53 - 19
packages/revisions/src/Bootstrap/Container.ts

@@ -1,15 +1,13 @@
 import { ControllerContainer, ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
 import { Container, interfaces } from 'inversify'
-import { Repository } from 'typeorm'
+import { MongoRepository, Repository } from 'typeorm'
 import * as winston from 'winston'
 
 import { Revision } from '../Domain/Revision/Revision'
 import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
 import { RevisionRepositoryInterface } from '../Domain/Revision/RevisionRepositoryInterface'
-import { TypeORMRevisionRepository } from '../Infra/TypeORM/TypeORMRevisionRepository'
-import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
-import { RevisionMetadataPersistenceMapper } from '../Mapping/RevisionMetadataPersistenceMapper'
-import { RevisionPersistenceMapper } from '../Mapping/RevisionPersistenceMapper'
+import { TypeORMRevisionRepository } from '../Infra/TypeORM/SQLRevisionRepository'
+import { TypeORMRevision } from '../Infra/TypeORM/SQLRevision'
 import { AppDataSource } from './DataSource'
 import { Env } from './Env'
 import TYPES from './Types'
@@ -21,8 +19,7 @@ import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
 import { GetRequiredRoleToViewRevision } from '../Domain/UseCase/GetRequiredRoleToViewRevision/GetRequiredRoleToViewRevision'
 import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
 import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada'
-import { RevisionHttpMapper } from '../Mapping/RevisionHttpMapper'
-import { RevisionMetadataHttpMapper } from '../Mapping/RevisionMetadataHttpMapper'
+import { RevisionMetadataHttpMapper } from '../Mapping/Http/RevisionMetadataHttpMapper'
 import { S3Client } from '@aws-sdk/client-s3'
 import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
 import {
@@ -44,9 +41,16 @@ import { RevisionsCopyRequestedEventHandler } from '../Domain/Handler/RevisionsC
 import { CopyRevisions } from '../Domain/UseCase/CopyRevisions/CopyRevisions'
 import { FSDumpRepository } from '../Infra/FS/FSDumpRepository'
 import { S3DumpRepository } from '../Infra/S3/S3ItemDumpRepository'
-import { RevisionItemStringMapper } from '../Mapping/RevisionItemStringMapper'
+import { RevisionItemStringMapper } from '../Mapping/Backup/RevisionItemStringMapper'
 import { BaseRevisionsController } from '../Infra/InversifyExpress/Base/BaseRevisionsController'
 import { Transform } from 'stream'
+import { MongoDBRevision } from '../Infra/TypeORM/MongoDB/MongoDBRevision'
+import { MongoDBRevisionRepository } from '../Infra/TypeORM/MongoDB/MongoDBRevisionRepository'
+import { SQLRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper'
+import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionPersistenceMapper'
+import { MongoDBRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBRevisionMetadataPersistenceMapper'
+import { MongoDBRevisionPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBRevisionPersistenceMapper'
+import { RevisionHttpMapper } from '../Mapping/Http/RevisionHttpMapper'
 
 export class ContainerConfigLoader {
   async load(configuration?: {
@@ -62,6 +66,7 @@ export class ContainerConfigLoader {
     env.load()
 
     const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
+    const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
 
     const container = new Container({
       defaultScope: 'Singleton',
@@ -101,11 +106,19 @@ export class ContainerConfigLoader {
 
     // Map
     container
-      .bind<MapperInterface<RevisionMetadata, TypeORMRevision>>(TYPES.Revisions_RevisionMetadataPersistenceMapper)
-      .toDynamicValue(() => new RevisionMetadataPersistenceMapper())
+      .bind<MapperInterface<RevisionMetadata, TypeORMRevision>>(TYPES.Revisions_SQLRevisionMetadataPersistenceMapper)
+      .toConstantValue(new SQLRevisionMetadataPersistenceMapper())
     container
-      .bind<MapperInterface<Revision, TypeORMRevision>>(TYPES.Revisions_RevisionPersistenceMapper)
-      .toDynamicValue(() => new RevisionPersistenceMapper())
+      .bind<MapperInterface<Revision, TypeORMRevision>>(TYPES.Revisions_SQLRevisionPersistenceMapper)
+      .toConstantValue(new SQLRevisionPersistenceMapper())
+    container
+      .bind<MapperInterface<RevisionMetadata, MongoDBRevision>>(
+        TYPES.Revisions_MongoDBRevisionMetadataPersistenceMapper,
+      )
+      .toConstantValue(new MongoDBRevisionMetadataPersistenceMapper())
+    container
+      .bind<MapperInterface<Revision, MongoDBRevision>>(TYPES.Revisions_MongoDBRevisionPersistenceMapper)
+      .toConstantValue(new MongoDBRevisionPersistenceMapper())
 
     // ORM
     container
@@ -115,14 +128,35 @@ export class ContainerConfigLoader {
     // Repositories
     container
       .bind<RevisionRepositoryInterface>(TYPES.Revisions_RevisionRepository)
-      .toDynamicValue((context: interfaces.Context) => {
-        return new TypeORMRevisionRepository(
-          context.container.get(TYPES.Revisions_ORMRevisionRepository),
-          context.container.get(TYPES.Revisions_RevisionMetadataPersistenceMapper),
-          context.container.get(TYPES.Revisions_RevisionPersistenceMapper),
-          context.container.get(TYPES.Revisions_Logger),
+      .toConstantValue(
+        new TypeORMRevisionRepository(
+          container.get<Repository<TypeORMRevision>>(TYPES.Revisions_ORMRevisionRepository),
+          container.get<MapperInterface<RevisionMetadata, TypeORMRevision>>(
+            TYPES.Revisions_SQLRevisionMetadataPersistenceMapper,
+          ),
+          container.get<MapperInterface<Revision, TypeORMRevision>>(TYPES.Revisions_SQLRevisionPersistenceMapper),
+          container.get<winston.Logger>(TYPES.Revisions_Logger),
+        ),
+      )
+
+    if (isSecondaryDatabaseEnabled) {
+      container
+        .bind<MongoRepository<MongoDBRevision>>(TYPES.Revisions_ORMMongoRevisionRepository)
+        .toConstantValue(appDataSource.getMongoRepository(MongoDBRevision))
+
+      container
+        .bind<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
+        .toConstantValue(
+          new MongoDBRevisionRepository(
+            container.get<MongoRepository<MongoDBRevision>>(TYPES.Revisions_ORMMongoRevisionRepository),
+            container.get<MapperInterface<RevisionMetadata, MongoDBRevision>>(
+              TYPES.Revisions_MongoDBRevisionMetadataPersistenceMapper,
+            ),
+            container.get<MapperInterface<Revision, MongoDBRevision>>(TYPES.Revisions_MongoDBRevisionPersistenceMapper),
+            container.get<winston.Logger>(TYPES.Revisions_Logger),
+          ),
         )
-      })
+    }
 
     container.bind<TimerInterface>(TYPES.Revisions_Timer).toDynamicValue(() => new Timer())
 

+ 49 - 8
packages/revisions/src/Bootstrap/DataSource.ts

@@ -1,25 +1,66 @@
-import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
+import { DataSource, EntityTarget, LoggerOptions, MongoRepository, ObjectLiteral, Repository } from 'typeorm'
 import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
 
-import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
+import { TypeORMRevision } from '../Infra/TypeORM/SQLRevision'
 
 import { Env } from './Env'
 import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
+import { MongoDBRevision } from '../Infra/TypeORM/MongoDB/MongoDBRevision'
 
 export class AppDataSource {
-  private dataSource: DataSource | undefined
+  private _dataSource: DataSource | undefined
+  private _secondaryDataSource: DataSource | undefined
 
   constructor(private env: Env) {}
 
   getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
-    if (!this.dataSource) {
+    if (!this._dataSource) {
       throw new Error('DataSource not initialized')
     }
 
-    return this.dataSource.getRepository(target)
+    return this._dataSource.getRepository(target)
+  }
+
+  getMongoRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): MongoRepository<Entity> {
+    if (!this._secondaryDataSource) {
+      throw new Error('Secondary DataSource not initialized')
+    }
+
+    return this._secondaryDataSource.getMongoRepository(target)
   }
 
   async initialize(): Promise<void> {
+    await this.dataSource.initialize()
+    const secondaryDataSource = this.secondaryDataSource
+    if (secondaryDataSource) {
+      await secondaryDataSource.initialize()
+    }
+  }
+
+  get secondaryDataSource(): DataSource | undefined {
+    this.env.load()
+
+    if (this.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
+      return undefined
+    }
+
+    this._secondaryDataSource = new DataSource({
+      type: 'mongodb',
+      host: this.env.get('MONGO_HOST'),
+      authSource: 'admin',
+      port: parseInt(this.env.get('MONGO_PORT')),
+      username: this.env.get('MONGO_USERNAME'),
+      password: this.env.get('MONGO_PASSWORD', true),
+      database: this.env.get('MONGO_DATABASE'),
+      entities: [MongoDBRevision],
+      retryWrites: false,
+      synchronize: true,
+    })
+
+    return this._secondaryDataSource
+  }
+
+  get dataSource(): DataSource {
     this.env.load()
 
     const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
@@ -74,7 +115,7 @@ export class AppDataSource {
         database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
       }
 
-      this.dataSource = new DataSource(mySQLDataSourceOptions)
+      this._dataSource = new DataSource(mySQLDataSourceOptions)
     } else {
       const sqliteDataSourceOptions: SqliteConnectionOptions = {
         ...commonDataSourceOptions,
@@ -84,9 +125,9 @@ export class AppDataSource {
         busyErrorRetry: 2000,
       }
 
-      this.dataSource = new DataSource(sqliteDataSourceOptions)
+      this._dataSource = new DataSource(sqliteDataSourceOptions)
     }
 
-    await this.dataSource.initialize()
+    return this._dataSource
   }
 }

+ 7 - 2
packages/revisions/src/Bootstrap/Types.ts

@@ -5,15 +5,20 @@ const TYPES = {
   Revisions_S3: Symbol.for('Revisions_S3'),
   Revisions_Env: Symbol.for('Revisions_Env'),
   // Map
-  Revisions_RevisionMetadataPersistenceMapper: Symbol.for('Revisions_RevisionMetadataPersistenceMapper'),
-  Revisions_RevisionPersistenceMapper: Symbol.for('Revisions_RevisionPersistenceMapper'),
+  Revisions_SQLRevisionMetadataPersistenceMapper: Symbol.for('Revisions_SQLRevisionMetadataPersistenceMapper'),
+  Revisions_SQLRevisionPersistenceMapper: Symbol.for('Revisions_SQLRevisionPersistenceMapper'),
+  Revisions_MongoDBRevisionMetadataPersistenceMapper: Symbol.for('Revisions_MongoDBRevisionMetadataPersistenceMapper'),
+  Revisions_MongoDBRevisionPersistenceMapper: Symbol.for('Revisions_MongoDBRevisionPersistenceMapper'),
   Revisions_RevisionItemStringMapper: Symbol.for('Revisions_RevisionItemStringMapper'),
   Revisions_RevisionHttpMapper: Symbol.for('Revisions_RevisionHttpMapper'),
   Revisions_RevisionMetadataHttpMapper: Symbol.for('Revisions_RevisionMetadataHttpMapper'),
   // ORM
   Revisions_ORMRevisionRepository: Symbol.for('Revisions_ORMRevisionRepository'),
+  // Mongo
+  Revisions_ORMMongoRevisionRepository: Symbol.for('Revisions_ORMMongoRevisionRepository'),
   // Repositories
   Revisions_RevisionRepository: Symbol.for('Revisions_RevisionRepository'),
+  Revisions_MongoDBRevisionRepository: Symbol.for('Revisions_MongoDBRevisionRepository'),
   Revisions_DumpRepository: Symbol.for('Revisions_DumpRepository'),
   // env vars
   Revisions_AUTH_JWT_SECRET: Symbol.for('Revisions_AUTH_JWT_SECRET'),

+ 40 - 0
packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevision.ts

@@ -0,0 +1,40 @@
+import { BSON } from 'mongodb'
+import { Column, Entity, Index, ObjectIdColumn } from 'typeorm'
+
+@Entity({ name: 'revisions' })
+export class MongoDBRevision {
+  @ObjectIdColumn()
+  declare _id: BSON.UUID
+
+  @Column()
+  @Index('item_uuid_on_revisions')
+  declare itemUuid: string
+
+  @Column()
+  @Index('user_uuid_on_revisions')
+  declare userUuid: string | null
+
+  @Column()
+  declare content: string | null
+
+  @Column()
+  declare contentType: string | null
+
+  @Column()
+  declare itemsKeyId: string | null
+
+  @Column()
+  declare encItemKey: string | null
+
+  @Column()
+  declare authHash: string | null
+
+  @Column()
+  declare creationDate: Date
+
+  @Column()
+  declare createdAt: Date
+
+  @Column()
+  declare updatedAt: Date
+}

+ 123 - 0
packages/revisions/src/Infra/TypeORM/MongoDB/MongoDBRevisionRepository.ts

@@ -0,0 +1,123 @@
+import { MapperInterface, Uuid } from '@standardnotes/domain-core'
+import { MongoRepository } from 'typeorm'
+import { BSON } from 'mongodb'
+import { Logger } from 'winston'
+
+import { MongoDBRevision } from './MongoDBRevision'
+import { Revision } from '../../../Domain/Revision/Revision'
+import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
+import { RevisionRepositoryInterface } from '../../../Domain/Revision/RevisionRepositoryInterface'
+
+export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
+  constructor(
+    private mongoRepository: MongoRepository<MongoDBRevision>,
+    private revisionMetadataMapper: MapperInterface<RevisionMetadata, MongoDBRevision>,
+    private revisionMapper: MapperInterface<Revision, MongoDBRevision>,
+    private logger: Logger,
+  ) {}
+
+  async removeByUserUuid(userUuid: Uuid): Promise<void> {
+    await this.mongoRepository.deleteMany({ where: { userUuid: { $eq: userUuid.value } } })
+  }
+
+  async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
+    await this.mongoRepository.deleteOne({
+      where: {
+        $and: [
+          { _id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) } },
+          { userUuid: { $eq: userUuid.value } },
+        ],
+      },
+    })
+  }
+
+  async findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null> {
+    const persistence = await this.mongoRepository.findOne({
+      where: {
+        $and: [
+          { _id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) } },
+          { userUuid: { $eq: userUuid.value } },
+        ],
+      },
+    })
+
+    if (persistence === null) {
+      return null
+    }
+
+    return this.revisionMapper.toDomain(persistence)
+  }
+
+  async findByItemUuid(itemUuid: Uuid): Promise<Revision[]> {
+    const persistence = await this.mongoRepository.find({
+      where: {
+        itemUuid: { $eq: itemUuid.value },
+      },
+    })
+
+    const revisions: Revision[] = []
+
+    for (const revision of persistence) {
+      try {
+        revisions.push(this.revisionMapper.toDomain(revision))
+      } catch (error) {
+        this.logger.error(`Failed to map revision ${revision._id.toHexString()} to domain: ${(error as Error).message}`)
+      }
+    }
+
+    return revisions
+  }
+
+  async findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<RevisionMetadata[]> {
+    const persistence = await this.mongoRepository.find({
+      select: ['_id', 'contentType', 'createdAt', 'updatedAt'],
+      where: {
+        $and: [{ itemUuid: { $eq: itemUuid.value } }, { userUuid: { $eq: userUuid.value } }],
+      },
+      order: {
+        createdAt: 'DESC',
+      },
+    })
+
+    const revisions: RevisionMetadata[] = []
+
+    for (const revision of persistence) {
+      try {
+        revisions.push(this.revisionMetadataMapper.toDomain(revision))
+      } catch (error) {
+        this.logger.error(`Failed to map revision ${revision._id.toHexString()} to domain: ${(error as Error).message}`)
+      }
+    }
+
+    return revisions
+  }
+
+  async updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void> {
+    await this.mongoRepository.updateMany(
+      {
+        itemUuid: { $eq: itemUuid.value },
+      },
+      {
+        $set: {
+          userUuid: userUuid.value,
+        },
+      },
+    )
+  }
+
+  async save(revision: Revision): Promise<Revision> {
+    const persistence = this.revisionMapper.toProjection(revision)
+
+    const { _id, ...rest } = persistence
+
+    await this.mongoRepository.updateOne(
+      { _id: { $eq: _id } },
+      {
+        $set: rest,
+      },
+      { upsert: true },
+    )
+
+    return revision
+  }
+}

+ 0 - 0
packages/revisions/src/Infra/TypeORM/TypeORMRevision.ts → packages/revisions/src/Infra/TypeORM/SQLRevision.ts


+ 1 - 1
packages/revisions/src/Infra/TypeORM/TypeORMRevisionRepository.ts → packages/revisions/src/Infra/TypeORM/SQLRevisionRepository.ts

@@ -5,7 +5,7 @@ import { Logger } from 'winston'
 import { Revision } from '../../Domain/Revision/Revision'
 import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
 import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface'
-import { TypeORMRevision } from './TypeORMRevision'
+import { TypeORMRevision } from './SQLRevision'
 
 export class TypeORMRevisionRepository implements RevisionRepositoryInterface {
   constructor(

+ 1 - 1
packages/revisions/src/Mapping/RevisionItemStringMapper.ts → packages/revisions/src/Mapping/Backup/RevisionItemStringMapper.ts

@@ -1,6 +1,6 @@
 import { MapperInterface, Dates, Uuid, ContentType } from '@standardnotes/domain-core'
 
-import { Revision } from '../Domain/Revision/Revision'
+import { Revision } from '../../Domain/Revision/Revision'
 
 export class RevisionItemStringMapper implements MapperInterface<Revision, string> {
   toDomain(projection: string): Revision {

+ 1 - 1
packages/revisions/src/Mapping/RevisionHttpMapper.ts → packages/revisions/src/Mapping/Http/RevisionHttpMapper.ts

@@ -1,6 +1,6 @@
 import { MapperInterface } from '@standardnotes/domain-core'
 
-import { Revision } from '../Domain/Revision/Revision'
+import { Revision } from '../../Domain/Revision/Revision'
 
 export class RevisionHttpMapper
   implements

+ 1 - 1
packages/revisions/src/Mapping/RevisionMetadataHttpMapper.ts → packages/revisions/src/Mapping/Http/RevisionMetadataHttpMapper.ts

@@ -1,6 +1,6 @@
 import { MapperInterface, SyncUseCaseInterface } from '@standardnotes/domain-core'
 
-import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
+import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
 
 export class RevisionMetadataHttpMapper
   implements

+ 41 - 0
packages/revisions/src/Mapping/Persistence/MongoDB/MongoDBRevisionMetadataPersistenceMapper.ts

@@ -0,0 +1,41 @@
+import { MapperInterface, Dates, UniqueEntityId, ContentType } from '@standardnotes/domain-core'
+
+import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
+import { MongoDBRevision } from '../../../Infra/TypeORM/MongoDB/MongoDBRevision'
+
+export class MongoDBRevisionMetadataPersistenceMapper implements MapperInterface<RevisionMetadata, MongoDBRevision> {
+  toDomain(projection: MongoDBRevision): RevisionMetadata {
+    const contentTypeOrError = ContentType.create(projection.contentType)
+    if (contentTypeOrError.isFailed()) {
+      throw new Error(`Could not create content type: ${contentTypeOrError.getError()}`)
+    }
+    const contentType = contentTypeOrError.getValue()
+
+    const createdAt = projection.createdAt instanceof Date ? projection.createdAt : new Date(projection.createdAt)
+    const updatedAt = projection.updatedAt instanceof Date ? projection.updatedAt : new Date(projection.updatedAt)
+
+    const datesOrError = Dates.create(createdAt, updatedAt)
+    if (datesOrError.isFailed()) {
+      throw new Error(`Could not create dates: ${datesOrError.getError()}`)
+    }
+    const dates = datesOrError.getValue()
+
+    const revisionMetadataOrError = RevisionMetadata.create(
+      {
+        contentType,
+        dates,
+      },
+      new UniqueEntityId(projection._id.toHexString()),
+    )
+
+    if (revisionMetadataOrError.isFailed()) {
+      throw new Error(`Could not create revision metdata: ${revisionMetadataOrError.getError()}`)
+    }
+
+    return revisionMetadataOrError.getValue()
+  }
+
+  toProjection(_domain: RevisionMetadata): MongoDBRevision {
+    throw new Error('Method not implemented.')
+  }
+}

+ 74 - 0
packages/revisions/src/Mapping/Persistence/MongoDB/MongoDBRevisionPersistenceMapper.ts

@@ -0,0 +1,74 @@
+import { MapperInterface, Dates, UniqueEntityId, Uuid, ContentType } from '@standardnotes/domain-core'
+
+import { MongoDBRevision } from '../../../Infra/TypeORM/MongoDB/MongoDBRevision'
+import { Revision } from '../../../Domain/Revision/Revision'
+import { BSON } from 'mongodb'
+
+export class MongoDBRevisionPersistenceMapper implements MapperInterface<Revision, MongoDBRevision> {
+  toDomain(projection: MongoDBRevision): Revision {
+    const contentTypeOrError = ContentType.create(projection.contentType)
+    if (contentTypeOrError.isFailed()) {
+      throw new Error(`Could not map typeorm revision to domain revision: ${contentTypeOrError.getError()}`)
+    }
+    const contentType = contentTypeOrError.getValue()
+
+    const datesOrError = Dates.create(projection.createdAt, projection.updatedAt)
+    if (datesOrError.isFailed()) {
+      throw new Error(`Could not map typeorm revision to domain revision: ${datesOrError.getError()}`)
+    }
+    const dates = datesOrError.getValue()
+
+    const itemUuidOrError = Uuid.create(projection.itemUuid)
+    if (itemUuidOrError.isFailed()) {
+      throw new Error(`Could not map typeorm revision to domain revision: ${itemUuidOrError.getError()}`)
+    }
+    const itemUuid = itemUuidOrError.getValue()
+
+    let userUuid = null
+    if (projection.userUuid !== null) {
+      const userUuidOrError = Uuid.create(projection.userUuid)
+      if (userUuidOrError.isFailed()) {
+        throw new Error(`Could not map typeorm revision to domain revision: ${userUuidOrError.getError()}`)
+      }
+      userUuid = userUuidOrError.getValue()
+    }
+
+    const revisionOrError = Revision.create(
+      {
+        authHash: projection.authHash,
+        content: projection.content,
+        contentType,
+        creationDate: projection.creationDate,
+        encItemKey: projection.encItemKey,
+        itemsKeyId: projection.itemsKeyId,
+        itemUuid,
+        userUuid,
+        dates,
+      },
+      new UniqueEntityId(projection._id.toHexString()),
+    )
+    if (revisionOrError.isFailed()) {
+      throw new Error(`Could not map typeorm revision to domain revision: ${revisionOrError.getError()}`)
+    }
+
+    return revisionOrError.getValue()
+  }
+
+  toProjection(domain: Revision): MongoDBRevision {
+    const mongoDBRevision = new MongoDBRevision()
+
+    mongoDBRevision.authHash = domain.props.authHash
+    mongoDBRevision.content = domain.props.content
+    mongoDBRevision.contentType = domain.props.contentType.value
+    mongoDBRevision.createdAt = domain.props.dates.createdAt
+    mongoDBRevision.updatedAt = domain.props.dates.updatedAt
+    mongoDBRevision.creationDate = domain.props.creationDate
+    mongoDBRevision.encItemKey = domain.props.encItemKey
+    mongoDBRevision.itemUuid = domain.props.itemUuid.value
+    mongoDBRevision.itemsKeyId = domain.props.itemsKeyId
+    mongoDBRevision.userUuid = domain.props.userUuid ? domain.props.userUuid.value : null
+    mongoDBRevision._id = BSON.UUID.createFromHexString(domain.id.toString())
+
+    return mongoDBRevision
+  }
+}

+ 3 - 3
packages/revisions/src/Mapping/RevisionMetadataPersistenceMapper.ts → packages/revisions/src/Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper.ts

@@ -1,9 +1,9 @@
 import { MapperInterface, Dates, UniqueEntityId, ContentType } from '@standardnotes/domain-core'
 
-import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
-import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
+import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
+import { TypeORMRevision } from '../../../Infra/TypeORM/SQLRevision'
 
-export class RevisionMetadataPersistenceMapper implements MapperInterface<RevisionMetadata, TypeORMRevision> {
+export class SQLRevisionMetadataPersistenceMapper implements MapperInterface<RevisionMetadata, TypeORMRevision> {
   toDomain(projection: TypeORMRevision): RevisionMetadata {
     const contentTypeOrError = ContentType.create(projection.contentType)
     if (contentTypeOrError.isFailed()) {

+ 4 - 3
packages/revisions/src/Mapping/RevisionPersistenceMapper.ts → packages/revisions/src/Mapping/Persistence/SQL/SQLRevisionPersistenceMapper.ts

@@ -1,8 +1,9 @@
 import { MapperInterface, Dates, UniqueEntityId, Uuid, ContentType } from '@standardnotes/domain-core'
-import { Revision } from '../Domain/Revision/Revision'
-import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
 
-export class RevisionPersistenceMapper implements MapperInterface<Revision, TypeORMRevision> {
+import { Revision } from '../../../Domain/Revision/Revision'
+import { TypeORMRevision } from '../../../Infra/TypeORM/SQLRevision'
+
+export class SQLRevisionPersistenceMapper implements MapperInterface<Revision, TypeORMRevision> {
   toDomain(projection: TypeORMRevision): Revision {
     const contentTypeOrError = ContentType.create(projection.contentType)
     if (contentTypeOrError.isFailed()) {

+ 1 - 1
packages/syncing-server/package.json

@@ -49,7 +49,7 @@
     "inversify": "^6.0.1",
     "inversify-express-utils": "^6.4.3",
     "jsonwebtoken": "^9.0.0",
-    "mongodb": "^5.7.0",
+    "mongodb": "^6.0.0",
     "mysql2": "^3.0.1",
     "nodemon": "^2.0.19",
     "prettyjson": "^1.2.5",

+ 30 - 27
yarn.lock

@@ -2666,6 +2666,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@mongodb-js/saslprep@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "@mongodb-js/saslprep@npm:1.1.0"
+  dependencies:
+    sparse-bitfield: "npm:^3.0.3"
+  checksum: 2cf6d124d48d517716eb3a18a09de27bd9b190863692234494954bc7d80cf69e65f6c3165f7d4bbf399c3e70a7e195ac8fb93fbc720f01250d7d987f681d8708
+  languageName: node
+  linkType: hard
+
 "@mrleebo/prisma-ast@npm:^0.5.2":
   version: 0.5.2
   resolution: "@mrleebo/prisma-ast@npm:0.5.2"
@@ -3901,6 +3910,7 @@ __metadata:
     inversify: "npm:^6.0.1"
     inversify-express-utils: "npm:^6.4.3"
     jest: "npm:^29.5.0"
+    mongodb: "npm:^6.0.0"
     mysql2: "npm:^3.0.1"
     newrelic: "npm:^10.1.2"
     npm-check-updates: "npm:^16.0.1"
@@ -4101,7 +4111,7 @@ __metadata:
     inversify-express-utils: "npm:^6.4.3"
     jest: "npm:^29.5.0"
     jsonwebtoken: "npm:^9.0.0"
-    mongodb: "npm:^5.7.0"
+    mongodb: "npm:^6.0.0"
     mysql2: "npm:^3.0.1"
     newrelic: "npm:^10.1.2"
     nodemon: "npm:^2.0.19"
@@ -5646,10 +5656,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"bson@npm:^5.4.0":
-  version: 5.4.0
-  resolution: "bson@npm:5.4.0"
-  checksum: 2c913a45c05bf8f1f8120c05e0e4ac9a864928853193c4794634b0c941a7d64397b9cbfe9fa9aba7249eb89d075911c5953efbb1be6b4e0848a0760660dca628
+"bson@npm:^6.0.0":
+  version: 6.0.0
+  resolution: "bson@npm:6.0.0"
+  checksum: 7290998ee8eb7d105f9168e5940a6a04743001fe39674d897d802da31c8b326a2934b9e782ba1650906264513fdd27777a802451e69193ba10d6163032214d0a
   languageName: node
   linkType: hard
 
@@ -10243,35 +10253,37 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mongodb@npm:^5.7.0":
-  version: 5.7.0
-  resolution: "mongodb@npm:5.7.0"
+"mongodb@npm:^6.0.0":
+  version: 6.0.0
+  resolution: "mongodb@npm:6.0.0"
   dependencies:
-    bson: "npm:^5.4.0"
+    "@mongodb-js/saslprep": "npm:^1.1.0"
+    bson: "npm:^6.0.0"
     mongodb-connection-string-url: "npm:^2.6.0"
-    saslprep: "npm:^1.0.3"
-    socks: "npm:^2.7.1"
   peerDependencies:
-    "@aws-sdk/credential-providers": ^3.201.0
+    "@aws-sdk/credential-providers": ^3.188.0
     "@mongodb-js/zstd": ^1.1.0
+    gcp-metadata: ^5.2.0
     kerberos: ^2.0.1
-    mongodb-client-encryption: ">=2.3.0 <3"
+    mongodb-client-encryption: ">=6.0.0 <7"
     snappy: ^7.2.2
-  dependenciesMeta:
-    saslprep:
-      optional: true
+    socks: ^2.7.1
   peerDependenciesMeta:
     "@aws-sdk/credential-providers":
       optional: true
     "@mongodb-js/zstd":
       optional: true
+    gcp-metadata:
+      optional: true
     kerberos:
       optional: true
     mongodb-client-encryption:
       optional: true
     snappy:
       optional: true
-  checksum: 23a291ffe7e990f25b527f2d4bd1a848b866211596cc30a36cbe86d773f3bcd74d688aa0a7158b35e24271264d15c35832fcced639b81df4cab7303cdd8442c0
+    socks:
+      optional: true
+  checksum: daec6dc9dc937a9a38c1b1605ef93088928821615499d8f297cf41fcea774682ab1e6a645c43fb34b584b3a5077c74e650d6d9fbf474010a20f5f93279f492d5
   languageName: node
   linkType: hard
 
@@ -12077,15 +12089,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"saslprep@npm:^1.0.3":
-  version: 1.0.3
-  resolution: "saslprep@npm:1.0.3"
-  dependencies:
-    sparse-bitfield: "npm:^3.0.3"
-  checksum: 23ebcda091621541fb9db9635ff36b9be81dc35a79a2adbf2a8309e162bcc9607513488aa3a9da757f11e856592ab8a727ac45c98c6084ff93d627509a882b84
-  languageName: node
-  linkType: hard
-
 "schema-utils@npm:^3.1.1, schema-utils@npm:^3.1.2":
   version: 3.1.2
   resolution: "schema-utils@npm:3.1.2"
@@ -12375,7 +12378,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"socks@npm:^2.6.2, socks@npm:^2.7.1":
+"socks@npm:^2.6.2":
   version: 2.7.1
   resolution: "socks@npm:2.7.1"
   dependencies: