فهرست منبع

feat: add mongodb initial support (#696)

* feat: add mongodb initial support

* fix: typeorm annotations for mongodb entity

* wip mongo repo

* feat: add mongodb queries

* fix(syncing-server): env sample

* fix(syncing-server): Mongo connection auth source

* fix(syncing-server): db switch env var name

* fix(syncing-server): persisting and querying by _id as UUID in MongoDB

* fix(syncing-server): items upserts on MongoDB

* fix: remove foreign key migration
Karol Sójko 1 سال پیش
والد
کامیت
b24b576209
25فایلهای تغییر یافته به همراه824 افزوده شده و 28 حذف شده
  1. 229 1
      .pnp.cjs
  2. BIN
      .yarn/cache/@types-webidl-conversions-npm-7.0.0-0903313151-86c337dc1e.zip
  3. BIN
      .yarn/cache/@types-whatwg-url-npm-8.2.2-54c5c24e6c-25f20f5649.zip
  4. BIN
      .yarn/cache/bson-npm-5.4.0-2f854c8216-2c913a45c0.zip
  5. BIN
      .yarn/cache/memory-pager-npm-1.5.0-46e20e6c81-6b00ff499b.zip
  6. BIN
      .yarn/cache/mongodb-connection-string-url-npm-2.6.0-af011ba17f-8a9186dd1b.zip
  7. BIN
      .yarn/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip
  8. BIN
      .yarn/cache/saslprep-npm-1.0.3-8db649c346-23ebcda091.zip
  9. BIN
      .yarn/cache/sparse-bitfield-npm-3.0.3-cb80d0c89f-625ecdf6f4.zip
  10. BIN
      .yarn/cache/tr46-npm-3.0.0-e1ae1ea7c9-3a481676bf.zip
  11. BIN
      .yarn/cache/webidl-conversions-npm-7.0.0-e8c8e30c68-bdbe11c68c.zip
  12. BIN
      .yarn/cache/whatwg-url-npm-11.0.0-073529d93a-ee3a532bfb.zip
  13. 8 0
      packages/syncing-server/.env.sample
  14. 22 0
      packages/syncing-server/migrations/mysql/1692176803410-remove_revisions_foreign_key.ts
  15. 1 0
      packages/syncing-server/package.json
  16. 29 8
      packages/syncing-server/src/Bootstrap/Container.ts
  17. 37 1
      packages/syncing-server/src/Bootstrap/DataSource.ts
  18. 3 0
      packages/syncing-server/src/Bootstrap/Types.ts
  19. 0 1
      packages/syncing-server/src/Domain/Item/ItemQuery.ts
  20. 0 3
      packages/syncing-server/src/Domain/Item/ItemRepositoryInterface.ts
  21. 56 0
      packages/syncing-server/src/Infra/TypeORM/MongoDBItem.ts
  22. 217 0
      packages/syncing-server/src/Infra/TypeORM/MongoDBItemRepository.ts
  23. 0 12
      packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts
  24. 102 0
      packages/syncing-server/src/Mapping/Persistence/MongoDB/MongoDBItemPersistenceMapper.ts
  25. 120 2
      yarn.lock

+ 229 - 1
.pnp.cjs

@@ -5191,6 +5191,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"],\
           ["mysql2", "npm:3.3.3"],\
           ["newrelic", "npm:10.1.2"],\
           ["nodemon", "npm:2.0.22"],\
@@ -5201,7 +5202,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:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
+          ["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#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"],\
@@ -5869,6 +5870,26 @@ const RAW_RUNTIME_STATE =
         "linkType": "HARD"\
       }]\
     ]],\
+    ["@types/webidl-conversions", [\
+      ["npm:7.0.0", {\
+        "packageLocation": "./.yarn/cache/@types-webidl-conversions-npm-7.0.0-0903313151-86c337dc1e.zip/node_modules/@types/webidl-conversions/",\
+        "packageDependencies": [\
+          ["@types/webidl-conversions", "npm:7.0.0"]\
+        ],\
+        "linkType": "HARD"\
+      }]\
+    ]],\
+    ["@types/whatwg-url", [\
+      ["npm:8.2.2", {\
+        "packageLocation": "./.yarn/cache/@types-whatwg-url-npm-8.2.2-54c5c24e6c-25f20f5649.zip/node_modules/@types/whatwg-url/",\
+        "packageDependencies": [\
+          ["@types/whatwg-url", "npm:8.2.2"],\
+          ["@types/node", "npm:20.2.5"],\
+          ["@types/webidl-conversions", "npm:7.0.0"]\
+        ],\
+        "linkType": "HARD"\
+      }]\
+    ]],\
     ["@types/yargs", [\
       ["npm:17.0.24", {\
         "packageLocation": "./.yarn/cache/@types-yargs-npm-17.0.24-b034cf1d8b-f7811cc0b9.zip/node_modules/@types/yargs/",\
@@ -7074,6 +7095,15 @@ const RAW_RUNTIME_STATE =
         "linkType": "HARD"\
       }]\
     ]],\
+    ["bson", [\
+      ["npm:5.4.0", {\
+        "packageLocation": "./.yarn/cache/bson-npm-5.4.0-2f854c8216-2c913a45c0.zip/node_modules/bson/",\
+        "packageDependencies": [\
+          ["bson", "npm:5.4.0"]\
+        ],\
+        "linkType": "HARD"\
+      }]\
+    ]],\
     ["buffer", [\
       ["npm:5.7.1", {\
         "packageLocation": "./.yarn/cache/buffer-npm-5.7.1-513ef8259e-8e611bed4d.zip/node_modules/buffer/",\
@@ -11932,6 +11962,15 @@ const RAW_RUNTIME_STATE =
         "linkType": "HARD"\
       }]\
     ]],\
+    ["memory-pager", [\
+      ["npm:1.5.0", {\
+        "packageLocation": "./.yarn/cache/memory-pager-npm-1.5.0-46e20e6c81-6b00ff499b.zip/node_modules/memory-pager/",\
+        "packageDependencies": [\
+          ["memory-pager", "npm:1.5.0"]\
+        ],\
+        "linkType": "HARD"\
+      }]\
+    ]],\
     ["meow", [\
       ["npm:8.1.2", {\
         "packageLocation": "./.yarn/cache/meow-npm-8.1.2-bcfe48d4f3-e36c879078.zip/node_modules/meow/",\
@@ -12290,6 +12329,59 @@ const RAW_RUNTIME_STATE =
         "linkType": "HARD"\
       }]\
     ]],\
+    ["mongodb", [\
+      ["npm:5.7.0", {\
+        "packageLocation": "./.yarn/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip/node_modules/mongodb/",\
+        "packageDependencies": [\
+          ["mongodb", "npm:5.7.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/",\
+        "packageDependencies": [\
+          ["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
+          ["@aws-sdk/credential-providers", null],\
+          ["@mongodb-js/zstd", null],\
+          ["@types/aws-sdk__credential-providers", null],\
+          ["@types/kerberos", null],\
+          ["@types/mongodb-client-encryption", null],\
+          ["@types/mongodb-js__zstd", null],\
+          ["@types/snappy", null],\
+          ["bson", "npm:5.4.0"],\
+          ["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"]\
+        ],\
+        "packagePeers": [\
+          "@aws-sdk/credential-providers",\
+          "@mongodb-js/zstd",\
+          "@types/aws-sdk__credential-providers",\
+          "@types/kerberos",\
+          "@types/mongodb-client-encryption",\
+          "@types/mongodb-js__zstd",\
+          "@types/snappy",\
+          "kerberos",\
+          "mongodb-client-encryption",\
+          "snappy"\
+        ],\
+        "linkType": "HARD"\
+      }]\
+    ]],\
+    ["mongodb-connection-string-url", [\
+      ["npm:2.6.0", {\
+        "packageLocation": "./.yarn/cache/mongodb-connection-string-url-npm-2.6.0-af011ba17f-8a9186dd1b.zip/node_modules/mongodb-connection-string-url/",\
+        "packageDependencies": [\
+          ["mongodb-connection-string-url", "npm:2.6.0"],\
+          ["@types/whatwg-url", "npm:8.2.2"],\
+          ["whatwg-url", "npm:11.0.0"]\
+        ],\
+        "linkType": "HARD"\
+      }]\
+    ]],\
     ["ms", [\
       ["npm:2.0.0", {\
         "packageLocation": "./.yarn/cache/ms-npm-2.0.0-9e1101a471-de027828fc.zip/node_modules/ms/",\
@@ -14249,6 +14341,16 @@ 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/",\
@@ -14604,6 +14706,16 @@ const RAW_RUNTIME_STATE =
         "linkType": "HARD"\
       }]\
     ]],\
+    ["sparse-bitfield", [\
+      ["npm:3.0.3", {\
+        "packageLocation": "./.yarn/cache/sparse-bitfield-npm-3.0.3-cb80d0c89f-625ecdf6f4.zip/node_modules/sparse-bitfield/",\
+        "packageDependencies": [\
+          ["sparse-bitfield", "npm:3.0.3"],\
+          ["memory-pager", "npm:1.5.0"]\
+        ],\
+        "linkType": "HARD"\
+      }]\
+    ]],\
     ["spawn-please", [\
       ["npm:2.0.1", {\
         "packageLocation": "./.yarn/cache/spawn-please-npm-2.0.1-265b6b5432-fe19a7ceb5.zip/node_modules/spawn-please/",\
@@ -15246,6 +15358,14 @@ const RAW_RUNTIME_STATE =
           ["tr46", "npm:0.0.3"]\
         ],\
         "linkType": "HARD"\
+      }],\
+      ["npm:3.0.0", {\
+        "packageLocation": "./.yarn/cache/tr46-npm-3.0.0-e1ae1ea7c9-3a481676bf.zip/node_modules/tr46/",\
+        "packageDependencies": [\
+          ["tr46", "npm:3.0.0"],\
+          ["punycode", "npm:2.3.0"]\
+        ],\
+        "linkType": "HARD"\
       }]\
     ]],\
     ["treeverse", [\
@@ -15757,6 +15877,98 @@ const RAW_RUNTIME_STATE =
         ],\
         "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"],\
+          ["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:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.16", {\
         "packageLocation": "./.yarn/__virtual__/typeorm-virtual-fc9b7b780b/0/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
         "packageDependencies": [\
@@ -16191,6 +16403,13 @@ const RAW_RUNTIME_STATE =
           ["webidl-conversions", "npm:3.0.1"]\
         ],\
         "linkType": "HARD"\
+      }],\
+      ["npm:7.0.0", {\
+        "packageLocation": "./.yarn/cache/webidl-conversions-npm-7.0.0-e8c8e30c68-bdbe11c68c.zip/node_modules/webidl-conversions/",\
+        "packageDependencies": [\
+          ["webidl-conversions", "npm:7.0.0"]\
+        ],\
+        "linkType": "HARD"\
       }]\
     ]],\
     ["webpack", [\
@@ -16249,6 +16468,15 @@ const RAW_RUNTIME_STATE =
       }]\
     ]],\
     ["whatwg-url", [\
+      ["npm:11.0.0", {\
+        "packageLocation": "./.yarn/cache/whatwg-url-npm-11.0.0-073529d93a-ee3a532bfb.zip/node_modules/whatwg-url/",\
+        "packageDependencies": [\
+          ["whatwg-url", "npm:11.0.0"],\
+          ["tr46", "npm:3.0.0"],\
+          ["webidl-conversions", "npm:7.0.0"]\
+        ],\
+        "linkType": "HARD"\
+      }],\
       ["npm:5.0.0", {\
         "packageLocation": "./.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-bd0cc6b75b.zip/node_modules/whatwg-url/",\
         "packageDependencies": [\

BIN
.yarn/cache/@types-webidl-conversions-npm-7.0.0-0903313151-86c337dc1e.zip


BIN
.yarn/cache/@types-whatwg-url-npm-8.2.2-54c5c24e6c-25f20f5649.zip


BIN
.yarn/cache/bson-npm-5.4.0-2f854c8216-2c913a45c0.zip


BIN
.yarn/cache/memory-pager-npm-1.5.0-46e20e6c81-6b00ff499b.zip


BIN
.yarn/cache/mongodb-connection-string-url-npm-2.6.0-af011ba17f-8a9186dd1b.zip


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


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


BIN
.yarn/cache/sparse-bitfield-npm-3.0.3-cb80d0c89f-625ecdf6f4.zip


BIN
.yarn/cache/tr46-npm-3.0.0-e1ae1ea7c9-3a481676bf.zip


BIN
.yarn/cache/webidl-conversions-npm-7.0.0-e8c8e30c68-bdbe11c68c.zip


BIN
.yarn/cache/whatwg-url-npm-11.0.0-073529d93a-ee3a532bfb.zip


+ 8 - 0
packages/syncing-server/.env.sample

@@ -52,3 +52,11 @@ FILE_UPLOAD_PATH=
 
 VALET_TOKEN_SECRET=change-me-!
 VALET_TOKEN_TTL=7200
+
+# (Optional) Mongo Setup
+SECONDARY_DB_ENABLED=false
+MONGO_HOST=
+MONGO_PORT=
+MONGO_USERNAME=
+MONGO_PASSWORD=
+MONGO_DATABASE=

+ 22 - 0
packages/syncing-server/migrations/mysql/1692176803410-remove_revisions_foreign_key.ts

@@ -0,0 +1,22 @@
+import { MigrationInterface, QueryRunner } from 'typeorm'
+
+export class RemoveRevisionsForeignKey1692176803410 implements MigrationInterface {
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    const revisionsTableExistsQueryResult = await queryRunner.manager.query(
+      'SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = "revisions"',
+    )
+    const revisionsTableExists = revisionsTableExistsQueryResult[0].count === 1
+    if (revisionsTableExists) {
+      try {
+        await queryRunner.query('ALTER TABLE `revisions` DROP FOREIGN KEY `FK_ab3b92e54701fe3010022a31d90`')
+      } catch (error) {
+        // eslint-disable-next-line no-console
+        console.log('Error dropping foreign key: ', (error as Error).message)
+      }
+    }
+  }
+
+  public async down(): Promise<void> {
+    return
+  }
+}

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

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

+ 29 - 8
packages/syncing-server/src/Bootstrap/Container.ts

@@ -7,7 +7,7 @@ import { AppDataSource } from './DataSource'
 import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
 import { ItemRepositoryInterface } from '../Domain/Item/ItemRepositoryInterface'
 import { TypeORMItemRepository } from '../Infra/TypeORM/TypeORMItemRepository'
-import { Repository } from 'typeorm'
+import { MongoRepository, Repository } from 'typeorm'
 import { Item } from '../Domain/Item/Item'
 import {
   DirectCallDomainEventPublisher,
@@ -158,6 +158,9 @@ import { UpdateStorageQuotaUsedInSharedVault } from '../Domain/UseCase/SharedVau
 import { SharedVaultFileUploadedEventHandler } from '../Domain/Handler/SharedVaultFileUploadedEventHandler'
 import { SharedVaultFileRemovedEventHandler } from '../Domain/Handler/SharedVaultFileRemovedEventHandler'
 import { AddNotificationsForUsers } from '../Domain/UseCase/Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
+import { MongoDBItem } from '../Infra/TypeORM/MongoDBItem'
+import { MongoDBItemRepository } from '../Infra/TypeORM/MongoDBItemRepository'
+import { MongoDBItemPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBItemPersistenceMapper'
 
 export class ContainerConfigLoader {
   private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -210,6 +213,7 @@ export class ContainerConfigLoader {
     container.bind<TimerInterface>(TYPES.Sync_Timer).toConstantValue(new Timer())
 
     const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
+    const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
 
     container.bind<Env>(TYPES.Sync_Env).toConstantValue(env)
 
@@ -381,6 +385,17 @@ export class ContainerConfigLoader {
       .bind<Repository<TypeORMMessage>>(TYPES.Sync_ORMMessageRepository)
       .toConstantValue(appDataSource.getRepository(TypeORMMessage))
 
+    // Mongo
+    if (isSecondaryDatabaseEnabled) {
+      container
+        .bind<MapperInterface<Item, MongoDBItem>>(TYPES.Sync_MongoDBItemPersistenceMapper)
+        .toConstantValue(new MongoDBItemPersistenceMapper())
+
+      container
+        .bind<MongoRepository<MongoDBItem>>(TYPES.Sync_MongoItemRepository)
+        .toConstantValue(appDataSource.getMongoRepository(MongoDBItem))
+    }
+
     // Repositories
     container
       .bind<KeySystemAssociationRepositoryInterface>(TYPES.Sync_KeySystemAssociationRepository)
@@ -401,13 +416,19 @@ export class ContainerConfigLoader {
     container
       .bind<ItemRepositoryInterface>(TYPES.Sync_ItemRepository)
       .toConstantValue(
-        new TypeORMItemRepository(
-          container.get(TYPES.Sync_ORMItemRepository),
-          container.get(TYPES.Sync_ItemPersistenceMapper),
-          container.get(TYPES.Sync_KeySystemAssociationRepository),
-          container.get(TYPES.Sync_SharedVaultAssociationRepository),
-          container.get(TYPES.Sync_Logger),
-        ),
+        isSecondaryDatabaseEnabled
+          ? new MongoDBItemRepository(
+              container.get(TYPES.Sync_MongoItemRepository),
+              container.get(TYPES.Sync_MongoDBItemPersistenceMapper),
+              container.get(TYPES.Sync_Logger),
+            )
+          : new TypeORMItemRepository(
+              container.get(TYPES.Sync_ORMItemRepository),
+              container.get(TYPES.Sync_ItemPersistenceMapper),
+              container.get(TYPES.Sync_KeySystemAssociationRepository),
+              container.get(TYPES.Sync_SharedVaultAssociationRepository),
+              container.get(TYPES.Sync_Logger),
+            ),
       )
     container
       .bind<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository)

+ 37 - 1
packages/syncing-server/src/Bootstrap/DataSource.ts

@@ -1,4 +1,4 @@
-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 { Env } from './Env'
 import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
@@ -10,9 +10,11 @@ import { TypeORMSharedVault } from '../Infra/TypeORM/TypeORMSharedVault'
 import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
 import { TypeORMSharedVaultInvite } from '../Infra/TypeORM/TypeORMSharedVaultInvite'
 import { TypeORMMessage } from '../Infra/TypeORM/TypeORMMessage'
+import { MongoDBItem } from '../Infra/TypeORM/MongoDBItem'
 
 export class AppDataSource {
   private _dataSource: DataSource | undefined
+  private _secondaryDataSource: DataSource | undefined
 
   constructor(private env: Env) {}
 
@@ -24,8 +26,42 @@ export class AppDataSource {
     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: [MongoDBItem],
+      synchronize: true,
+    })
+
+    return this._secondaryDataSource
   }
 
   get dataSource(): DataSource {

+ 3 - 0
packages/syncing-server/src/Bootstrap/Types.ts

@@ -24,6 +24,8 @@ const TYPES = {
   Sync_ORMSharedVaultUserRepository: Symbol.for('Sync_ORMSharedVaultUserRepository'),
   Sync_ORMNotificationRepository: Symbol.for('Sync_ORMNotificationRepository'),
   Sync_ORMMessageRepository: Symbol.for('Sync_ORMMessageRepository'),
+  // Mongo
+  Sync_MongoItemRepository: Symbol.for('Sync_MongoItemRepository'),
   // Middleware
   Sync_AuthMiddleware: Symbol.for('Sync_AuthMiddleware'),
   // env vars
@@ -124,6 +126,7 @@ const TYPES = {
   Sync_MessageHttpMapper: Symbol.for('Sync_MessageHttpMapper'),
   Sync_NotificationHttpMapper: Symbol.for('Sync_NotificationHttpMapper'),
   Sync_ItemPersistenceMapper: Symbol.for('Sync_ItemPersistenceMapper'),
+  Sync_MongoDBItemPersistenceMapper: Symbol.for('Sync_MongoDBItemPersistenceMapper'),
   Sync_ItemHttpMapper: Symbol.for('Sync_ItemHttpMapper'),
   Sync_ItemHashHttpMapper: Symbol.for('Sync_ItemHashHttpMapper'),
   Sync_SavedItemHttpMapper: Symbol.for('Sync_SavedItemHttpMapper'),

+ 0 - 1
packages/syncing-server/src/Domain/Item/ItemQuery.ts

@@ -10,7 +10,6 @@ export type ItemQuery = {
   offset?: number
   limit?: number
   createdBetween?: Date[]
-  selectString?: string
   includeSharedVaultUuids?: string[]
   exclusiveSharedVaultUuids?: string[]
 }

+ 0 - 3
packages/syncing-server/src/Domain/Item/ItemRepositoryInterface.ts

@@ -1,5 +1,4 @@
 import { Uuid } from '@standardnotes/domain-core'
-import { ReadStream } from 'fs'
 
 import { Item } from './Item'
 import { ItemQuery } from './ItemQuery'
@@ -8,8 +7,6 @@ import { ExtendedIntegrityPayload } from './ExtendedIntegrityPayload'
 export interface ItemRepositoryInterface {
   deleteByUserUuid(userUuid: string): Promise<void>
   findAll(query: ItemQuery): Promise<Item[]>
-  findAllRaw<T>(query: ItemQuery): Promise<T[]>
-  streamAll(query: ItemQuery): Promise<ReadStream>
   countAll(query: ItemQuery): Promise<number>
   findContentSizeForComputingTransferLimit(
     query: ItemQuery,

+ 56 - 0
packages/syncing-server/src/Infra/TypeORM/MongoDBItem.ts

@@ -0,0 +1,56 @@
+import { BSON } from 'mongodb'
+import { Column, Entity, Index, ObjectIdColumn } from 'typeorm'
+
+@Entity({ name: 'items' })
+@Index('index_items_on_user_uuid_and_content_type', ['userUuid', 'contentType'])
+@Index('user_uuid_and_deleted', ['userUuid', 'deleted'])
+export class MongoDBItem {
+  @ObjectIdColumn()
+  declare _id: BSON.UUID
+
+  @Column()
+  declare duplicateOf: string | null
+
+  @Column()
+  declare itemsKeyId: string | null
+
+  @Column()
+  declare content: string | null
+
+  @Column()
+  @Index('index_items_on_content_type')
+  declare contentType: string | null
+
+  @Column()
+  declare contentSize: number | null
+
+  @Column()
+  declare encItemKey: string | null
+
+  @Column()
+  declare authHash: string | null
+
+  @Column()
+  @Index('index_items_on_user_uuid')
+  declare userUuid: string
+
+  @Column()
+  @Index('index_items_on_deleted')
+  declare deleted: boolean
+
+  @Column()
+  declare createdAt: Date
+
+  @Column()
+  declare updatedAt: Date
+
+  @Column()
+  declare createdAtTimestamp: number
+
+  @Column()
+  @Index('updated_at_timestamp')
+  declare updatedAtTimestamp: number
+
+  @Column()
+  declare updatedWithSession: string | null
+}

+ 217 - 0
packages/syncing-server/src/Infra/TypeORM/MongoDBItemRepository.ts

@@ -0,0 +1,217 @@
+import { MapperInterface, Uuid } from '@standardnotes/domain-core'
+import { FilterOperators, FindManyOptions, MongoRepository } from 'typeorm'
+import { Logger } from 'winston'
+import { BSON } from 'mongodb'
+
+import { ExtendedIntegrityPayload } from '../../Domain/Item/ExtendedIntegrityPayload'
+import { Item } from '../../Domain/Item/Item'
+import { ItemQuery } from '../../Domain/Item/ItemQuery'
+import { ItemRepositoryInterface } from '../../Domain/Item/ItemRepositoryInterface'
+import { MongoDBItem } from './MongoDBItem'
+
+export class MongoDBItemRepository implements ItemRepositoryInterface {
+  constructor(
+    private mongoRepository: MongoRepository<MongoDBItem>,
+    private mapper: MapperInterface<Item, MongoDBItem>,
+    private logger: Logger,
+  ) {}
+
+  async deleteByUserUuid(userUuid: string): Promise<void> {
+    await this.mongoRepository.deleteMany({ where: { userUuid } })
+  }
+
+  async findAll(query: ItemQuery): Promise<Item[]> {
+    const options = this.createFindOptions(query)
+    const persistence = await this.mongoRepository.find(options)
+
+    const domainItems: Item[] = []
+    for (const persistencItem of persistence) {
+      try {
+        domainItems.push(this.mapper.toDomain(persistencItem))
+      } catch (error) {
+        this.logger.error(
+          `Failed to map item ${persistencItem._id.toHexString()} to domain: ${(error as Error).message}`,
+        )
+      }
+    }
+
+    return domainItems
+  }
+
+  async countAll(query: ItemQuery): Promise<number> {
+    return this.mongoRepository.count(this.createFindOptions(query))
+  }
+
+  async findContentSizeForComputingTransferLimit(
+    query: ItemQuery,
+  ): Promise<{ uuid: string; contentSize: number | null }[]> {
+    const options = this.createFindOptions(query)
+    const rawItems = await this.mongoRepository.find({
+      select: ['uuid', 'contentSize'],
+      ...options,
+    })
+
+    const items = rawItems.map((item) => {
+      return {
+        uuid: item._id.toHexString(),
+        contentSize: item.contentSize,
+      }
+    })
+
+    return items
+  }
+
+  async findDatesForComputingIntegrityHash(userUuid: string): Promise<{ updated_at_timestamp: number }[]> {
+    const rawItems = await this.mongoRepository.find({
+      select: ['updatedAtTimestamp'],
+      where: {
+        $and: [{ userUuid: { $eq: userUuid } }, { deleted: { $eq: false } }],
+      },
+    })
+
+    const items = rawItems.map((item) => {
+      return {
+        updated_at_timestamp: item.updatedAtTimestamp,
+      }
+    })
+
+    return items.sort((itemA, itemB) => itemB.updated_at_timestamp - itemA.updated_at_timestamp)
+  }
+
+  async findItemsForComputingIntegrityPayloads(userUuid: string): Promise<ExtendedIntegrityPayload[]> {
+    const items = await this.mongoRepository.find({
+      select: ['uuid', 'updatedAtTimestamp', 'contentType', 'userUuid', 'deleted'],
+      where: {
+        $and: [{ userUuid: { $eq: userUuid } }, { deleted: { $eq: false } }],
+      },
+    })
+
+    const integrityPayloads = items.map((item) => {
+      return {
+        uuid: item._id.toHexString(),
+        updated_at_timestamp: item.updatedAtTimestamp,
+        content_type: item.contentType,
+        user_uuid: item.userUuid,
+        deleted: item.deleted,
+      }
+    })
+
+    return integrityPayloads.sort((itemA, itemB) => itemB.updated_at_timestamp - itemA.updated_at_timestamp)
+  }
+
+  async findByUuidAndUserUuid(uuid: string, userUuid: string): Promise<Item | null> {
+    const persistence = await this.mongoRepository.findOne({
+      where: {
+        $and: [{ _id: { $eq: BSON.UUID.createFromHexString(uuid) } }, { userUuid: { $eq: userUuid } }],
+      },
+    })
+
+    if (persistence === null) {
+      return null
+    }
+
+    return this.mapper.toDomain(persistence)
+  }
+
+  async findByUuid(uuid: Uuid): Promise<Item | null> {
+    const persistence = await this.mongoRepository.findOne({
+      where: { _id: { $eq: BSON.UUID.createFromHexString(uuid.value) } },
+    })
+
+    if (persistence === null) {
+      return null
+    }
+
+    return this.mapper.toDomain(persistence)
+  }
+
+  async remove(item: Item): Promise<void> {
+    await this.mongoRepository.deleteOne({ where: { _id: { $eq: BSON.UUID.createFromHexString(item.uuid.value) } } })
+  }
+
+  async save(item: Item): Promise<void> {
+    const persistence = this.mapper.toProjection(item)
+
+    const { _id, ...rest } = persistence
+
+    await this.mongoRepository.updateOne(
+      { _id: { $eq: _id } },
+      {
+        $set: rest,
+      },
+      { upsert: true },
+    )
+  }
+
+  async markItemsAsDeleted(itemUuids: string[], updatedAtTimestamp: number): Promise<void> {
+    await this.mongoRepository.updateMany(
+      { where: { _id: { $in: itemUuids.map((uuid) => BSON.UUID.createFromHexString(uuid)) } } },
+      { deleted: true, content: null, encItemKey: null, authHash: null, updatedAtTimestamp },
+    )
+  }
+
+  async updateContentSize(itemUuid: string, contentSize: number): Promise<void> {
+    await this.mongoRepository.updateOne(
+      { where: { _id: { $eq: BSON.UUID.createFromHexString(itemUuid) } } },
+      { contentSize },
+    )
+  }
+
+  private createFindOptions(
+    query: ItemQuery,
+  ): FindManyOptions<MongoDBItem> | Partial<MongoDBItem> | FilterOperators<MongoDBItem> {
+    const options: FindManyOptions<MongoDBItem> | Partial<MongoDBItem> | FilterOperators<MongoDBItem> = {
+      order: undefined,
+      where: undefined,
+    }
+    if (query.sortBy !== undefined && query.sortOrder !== undefined) {
+      options.order = { [query.sortBy]: query.sortOrder }
+    }
+
+    if (query.userUuid !== undefined) {
+      options.where = { ...options.where, userUuid: { $eq: query.userUuid } }
+    }
+
+    if (query.uuids && query.uuids.length > 0) {
+      options.where = {
+        ...options.where,
+        _id: { $in: query.uuids.map((uuid) => BSON.UUID.createFromHexString(uuid)) },
+      }
+    }
+    if (query.deleted !== undefined) {
+      options.where = { ...options.where, deleted: { $eq: query.deleted } }
+    }
+    if (query.contentType) {
+      if (Array.isArray(query.contentType)) {
+        options.where = { ...options.where, contentType: { $in: query.contentType } }
+      } else {
+        options.where = { ...options.where, contentType: { $eq: query.contentType } }
+      }
+    }
+    if (query.lastSyncTime && query.syncTimeComparison) {
+      const mongoComparisonOperator = query.syncTimeComparison === '>' ? '$gt' : '$gte'
+      options.where = {
+        ...options.where,
+        updatedAtTimestamp: { [mongoComparisonOperator]: query.lastSyncTime },
+      }
+    }
+    if (query.createdBetween !== undefined) {
+      options.where = {
+        ...options.where,
+        createdAt: {
+          $gte: query.createdBetween[0].toISOString(),
+          $lte: query.createdBetween[1].toISOString(),
+        },
+      }
+    }
+
+    if (query.offset !== undefined) {
+      options.skip = query.offset
+    }
+    if (query.limit !== undefined) {
+      options.take = query.limit
+    }
+
+    return options
+  }
+}

+ 0 - 12
packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts

@@ -1,4 +1,3 @@
-import { ReadStream } from 'fs'
 import { Repository, SelectQueryBuilder, Brackets } from 'typeorm'
 import { Change, MapperInterface, Uuid } from '@standardnotes/domain-core'
 import { Logger } from 'winston'
@@ -169,14 +168,6 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
     return domainItems
   }
 
-  async findAllRaw<T>(query: ItemQuery): Promise<T[]> {
-    return this.createFindAllQueryBuilder(query).getRawMany<T>()
-  }
-
-  async streamAll(query: ItemQuery): Promise<ReadStream> {
-    return this.createFindAllQueryBuilder(query).stream()
-  }
-
   async countAll(query: ItemQuery): Promise<number> {
     return this.createFindAllQueryBuilder(query).getCount()
   }
@@ -237,9 +228,6 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
       queryBuilder.where('item.user_uuid = :userUuid', { userUuid: query.userUuid })
     }
 
-    if (query.selectString !== undefined) {
-      queryBuilder.select(query.selectString)
-    }
     if (query.uuids && query.uuids.length > 0) {
       queryBuilder.andWhere('item.uuid IN (:...uuids)', { uuids: query.uuids })
     }

+ 102 - 0
packages/syncing-server/src/Mapping/Persistence/MongoDB/MongoDBItemPersistenceMapper.ts

@@ -0,0 +1,102 @@
+import { ContentType, Dates, MapperInterface, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
+
+import { MongoDBItem } from '../../../Infra/TypeORM/MongoDBItem'
+import { Item } from '../../../Domain/Item/Item'
+import { BSON } from 'mongodb'
+
+export class MongoDBItemPersistenceMapper implements MapperInterface<Item, MongoDBItem> {
+  toDomain(projection: MongoDBItem): Item {
+    const uuidOrError = Uuid.create(projection._id.toHexString())
+    if (uuidOrError.isFailed()) {
+      throw new Error(`Failed to create item from projection: ${uuidOrError.getError()}`)
+    }
+    const uuid = uuidOrError.getValue()
+
+    let duplicateOf = null
+    if (projection.duplicateOf) {
+      const duplicateOfOrError = Uuid.create(projection.duplicateOf)
+      if (duplicateOfOrError.isFailed()) {
+        throw new Error(`Failed to create item from projection: ${duplicateOfOrError.getError()}`)
+      }
+      duplicateOf = duplicateOfOrError.getValue()
+    }
+
+    const contentTypeOrError = ContentType.create(projection.contentType)
+    if (contentTypeOrError.isFailed()) {
+      throw new Error(`Failed to create item from projection: ${contentTypeOrError.getError()}`)
+    }
+    const contentType = contentTypeOrError.getValue()
+
+    const userUuidOrError = Uuid.create(projection.userUuid)
+    if (userUuidOrError.isFailed()) {
+      throw new Error(`Failed to create item from projection: ${userUuidOrError.getError()}`)
+    }
+    const userUuid = userUuidOrError.getValue()
+
+    const datesOrError = Dates.create(projection.createdAt, projection.updatedAt)
+    if (datesOrError.isFailed()) {
+      throw new Error(`Failed to create item from projection: ${datesOrError.getError()}`)
+    }
+    const dates = datesOrError.getValue()
+
+    const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
+    if (timestampsOrError.isFailed()) {
+      throw new Error(`Failed to create item from projection: ${timestampsOrError.getError()}`)
+    }
+    const timestamps = timestampsOrError.getValue()
+
+    let updatedWithSession = null
+    if (projection.updatedWithSession) {
+      const updatedWithSessionOrError = Uuid.create(projection.updatedWithSession)
+      if (updatedWithSessionOrError.isFailed()) {
+        throw new Error(`Failed to create item from projection: ${updatedWithSessionOrError.getError()}`)
+      }
+      updatedWithSession = updatedWithSessionOrError.getValue()
+    }
+
+    const itemOrError = Item.create(
+      {
+        duplicateOf,
+        itemsKeyId: projection.itemsKeyId,
+        content: projection.content,
+        contentType,
+        contentSize: projection.contentSize ?? undefined,
+        encItemKey: projection.encItemKey,
+        authHash: projection.authHash,
+        userUuid,
+        deleted: projection.deleted,
+        dates,
+        timestamps,
+        updatedWithSession,
+      },
+      new UniqueEntityId(uuid.value),
+    )
+    if (itemOrError.isFailed()) {
+      throw new Error(`Failed to create item from projection: ${itemOrError.getError()}`)
+    }
+
+    return itemOrError.getValue()
+  }
+
+  toProjection(domain: Item): MongoDBItem {
+    const mongoDbItem = new MongoDBItem()
+
+    mongoDbItem._id = BSON.UUID.createFromHexString(domain.uuid.value)
+    mongoDbItem.duplicateOf = domain.props.duplicateOf ? domain.props.duplicateOf.value : null
+    mongoDbItem.itemsKeyId = domain.props.itemsKeyId
+    mongoDbItem.content = domain.props.content
+    mongoDbItem.contentType = domain.props.contentType.value
+    mongoDbItem.contentSize = domain.props.contentSize ?? null
+    mongoDbItem.encItemKey = domain.props.encItemKey
+    mongoDbItem.authHash = domain.props.authHash
+    mongoDbItem.userUuid = domain.props.userUuid.value
+    mongoDbItem.deleted = domain.props.deleted
+    mongoDbItem.createdAt = domain.props.dates.createdAt
+    mongoDbItem.updatedAt = domain.props.dates.updatedAt
+    mongoDbItem.createdAtTimestamp = domain.props.timestamps.createdAt
+    mongoDbItem.updatedAtTimestamp = domain.props.timestamps.updatedAt
+    mongoDbItem.updatedWithSession = domain.props.updatedWithSession ? domain.props.updatedWithSession.value : null
+
+    return mongoDbItem
+  }
+}

+ 120 - 2
yarn.lock

@@ -4101,6 +4101,7 @@ __metadata:
     inversify-express-utils: "npm:^6.4.3"
     jest: "npm:^29.5.0"
     jsonwebtoken: "npm:^9.0.0"
+    mongodb: "npm:^5.7.0"
     mysql2: "npm:^3.0.1"
     newrelic: "npm:^10.1.2"
     nodemon: "npm:^2.0.19"
@@ -4700,6 +4701,23 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/webidl-conversions@npm:*":
+  version: 7.0.0
+  resolution: "@types/webidl-conversions@npm:7.0.0"
+  checksum: 86c337dc1edd0db2a9e278cb2ddb3b577559c8a282348bedf8505be0435be86354bb83fe858e959e2ce12ab2aa02eb5698d5e1a35454182637e776982013a5d1
+  languageName: node
+  linkType: hard
+
+"@types/whatwg-url@npm:^8.2.1":
+  version: 8.2.2
+  resolution: "@types/whatwg-url@npm:8.2.2"
+  dependencies:
+    "@types/node": "npm:*"
+    "@types/webidl-conversions": "npm:*"
+  checksum: 25f20f5649f0e4a3242bf8f59c8e1b3d057f93ac1039e3aeea49cd6e4eed33517f228b412bfb048670421c11d2198e45cd9e09fe7921a263b6c8a9eb4b833ad1
+  languageName: node
+  linkType: hard
+
 "@types/yargs-parser@npm:*":
   version: 21.0.0
   resolution: "@types/yargs-parser@npm:21.0.0"
@@ -5628,6 +5646,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"bson@npm:^5.4.0":
+  version: 5.4.0
+  resolution: "bson@npm:5.4.0"
+  checksum: 2c913a45c05bf8f1f8120c05e0e4ac9a864928853193c4794634b0c941a7d64397b9cbfe9fa9aba7249eb89d075911c5953efbb1be6b4e0848a0760660dca628
+  languageName: node
+  linkType: hard
+
 "buffer-equal-constant-time@npm:1.0.1":
   version: 1.0.1
   resolution: "buffer-equal-constant-time@npm:1.0.1"
@@ -9863,6 +9888,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"memory-pager@npm:^1.0.2":
+  version: 1.5.0
+  resolution: "memory-pager@npm:1.5.0"
+  checksum: 6b00ff499b3b6a168d8b713d5c33f3ea08fd24c19a8b42adc64847cfa62acdf7a3cfd81f02d6eab51773b6e118c628ba6694ecb55647d4c1efe7b11e67017e35
+  languageName: node
+  linkType: hard
+
 "meow@npm:^8.0.0":
   version: 8.1.2
   resolution: "meow@npm:8.1.2"
@@ -10201,6 +10233,48 @@ __metadata:
   languageName: node
   linkType: hard
 
+"mongodb-connection-string-url@npm:^2.6.0":
+  version: 2.6.0
+  resolution: "mongodb-connection-string-url@npm:2.6.0"
+  dependencies:
+    "@types/whatwg-url": "npm:^8.2.1"
+    whatwg-url: "npm:^11.0.0"
+  checksum: 8a9186dd1b72dfa1ca8e2e7deeec2e412b3682c923d9f887e07a19b2366174e50c1c9f3657353eef62e7acce26f7e6ec16c3cc320fc1c12aab5d4890fa368ce3
+  languageName: node
+  linkType: hard
+
+"mongodb@npm:^5.7.0":
+  version: 5.7.0
+  resolution: "mongodb@npm:5.7.0"
+  dependencies:
+    bson: "npm:^5.4.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
+    "@mongodb-js/zstd": ^1.1.0
+    kerberos: ^2.0.1
+    mongodb-client-encryption: ">=2.3.0 <3"
+    snappy: ^7.2.2
+  dependenciesMeta:
+    saslprep:
+      optional: true
+  peerDependenciesMeta:
+    "@aws-sdk/credential-providers":
+      optional: true
+    "@mongodb-js/zstd":
+      optional: true
+    kerberos:
+      optional: true
+    mongodb-client-encryption:
+      optional: true
+    snappy:
+      optional: true
+  checksum: 23a291ffe7e990f25b527f2d4bd1a848b866211596cc30a36cbe86d773f3bcd74d688aa0a7158b35e24271264d15c35832fcced639b81df4cab7303cdd8442c0
+  languageName: node
+  linkType: hard
+
 "ms@npm:2.0.0":
   version: 2.0.0
   resolution: "ms@npm:2.0.0"
@@ -11480,7 +11554,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"punycode@npm:^2.1.0":
+"punycode@npm:^2.1.0, punycode@npm:^2.1.1":
   version: 2.3.0
   resolution: "punycode@npm:2.3.0"
   checksum: c2b408c805927a6614ef581bd3d00deca1fef9f2da0ec95cecaedf6a985d8596a29e931e31f80f7313f94257895f9ac6cf4c2ae81cdca04964daf9c3c3d221c1
@@ -12003,6 +12077,15 @@ __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"
@@ -12292,7 +12375,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"socks@npm:^2.6.2":
+"socks@npm:^2.6.2, socks@npm:^2.7.1":
   version: 2.7.1
   resolution: "socks@npm:2.7.1"
   dependencies:
@@ -12338,6 +12421,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"sparse-bitfield@npm:^3.0.3":
+  version: 3.0.3
+  resolution: "sparse-bitfield@npm:3.0.3"
+  dependencies:
+    memory-pager: "npm:^1.0.2"
+  checksum: 625ecdf6f4b2652afac82dec575d575cafe492aa06a3010c12cb1f312fb78e62a916df933885a2a4151f1347646d490c87cf3404ce3afc7a3031bd6b622225fc
+  languageName: node
+  linkType: hard
+
 "spawn-please@npm:^2.0.1":
   version: 2.0.1
   resolution: "spawn-please@npm:2.0.1"
@@ -12886,6 +12978,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"tr46@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "tr46@npm:3.0.0"
+  dependencies:
+    punycode: "npm:^2.1.1"
+  checksum: 3a481676bf6956ca7ffd4b21c5826f61d7dd57dcad56ee202a5d9d5a34f5ddd1a98ee938366f7964e8dfabc640377d53725164724da49a7a2331694270a1b7d8
+  languageName: node
+  linkType: hard
+
 "tr46@npm:~0.0.3":
   version: 0.0.3
   resolution: "tr46@npm:0.0.3"
@@ -13518,6 +13619,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"webidl-conversions@npm:^7.0.0":
+  version: 7.0.0
+  resolution: "webidl-conversions@npm:7.0.0"
+  checksum: bdbe11c68c3136ce4e720182d2434215cff65d619de7e7ddcbdc17c7d62aaaf0e16c3a84b2c6e55ffe347e77dea2d55299c7e3690fb07148a8fbe46ead27c55f
+  languageName: node
+  linkType: hard
+
 "webpack-sources@npm:^3.2.3":
   version: 3.2.3
   resolution: "webpack-sources@npm:3.2.3"
@@ -13562,6 +13670,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"whatwg-url@npm:^11.0.0":
+  version: 11.0.0
+  resolution: "whatwg-url@npm:11.0.0"
+  dependencies:
+    tr46: "npm:^3.0.0"
+    webidl-conversions: "npm:^7.0.0"
+  checksum: ee3a532bfb026d307b1c7f75413a45d19292e4eff4f9db62e020ac67d00f6ac81032011604832e3b1e65665c603e6024148570dbe883a71ba93ea4838beeb162
+  languageName: node
+  linkType: hard
+
 "whatwg-url@npm:^5.0.0":
   version: 5.0.0
   resolution: "whatwg-url@npm:5.0.0"