From bec83579eab9050ab2464de51146decf31c47709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0vanda?= <46406259+Papooch@users.noreply.github.com> Date: Thu, 27 Jun 2024 23:59:39 +0200 Subject: [PATCH 1/2] feat(transactional-adapter-mongodb): add `mongodb` adapter (#158) --- .../01-transactional/06-mongodb-adapter.md | 112 ++++ .../01-transactional/index.md | 1 + .../transactional-adapter-mongodb/README.md | 5 + .../jest.config.js | 17 + .../package.json | 73 +++ .../src/index.ts | 1 + .../src/lib/transactional-adapter-mongodb.ts | 77 +++ .../transactional-adapter-mongodb.spec.ts | 253 +++++++++ .../tsconfig.json | 8 + tsconfig.json | 13 +- yarn.lock | 482 +++++++++++++++++- 11 files changed, 1037 insertions(+), 5 deletions(-) create mode 100644 docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md create mode 100644 packages/transactional-adapters/transactional-adapter-mongodb/README.md create mode 100644 packages/transactional-adapters/transactional-adapter-mongodb/jest.config.js create mode 100644 packages/transactional-adapters/transactional-adapter-mongodb/package.json create mode 100644 packages/transactional-adapters/transactional-adapter-mongodb/src/index.ts create mode 100644 packages/transactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts create mode 100644 packages/transactional-adapters/transactional-adapter-mongodb/test/transactional-adapter-mongodb.spec.ts create mode 100644 packages/transactional-adapters/transactional-adapter-mongodb/tsconfig.json diff --git a/docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md b/docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md new file mode 100644 index 00000000..e7b23c57 --- /dev/null +++ b/docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md @@ -0,0 +1,112 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# MongoDB adapter + +## Installation + + + + +```bash +npm install @nestjs-cls/transactional-adapter-mongodb +``` + + + + +```bash +yarn add @nestjs-cls/transactional-adapter-mongodb +``` + + + + +```bash +pnpm add @nestjs-cls/transactional-adapter-mongodb +``` + + + + +## Registration + +```ts +ClsModule.forRoot({ + plugins: [ + new ClsPluginTransactional({ + imports: [ + // module in which the MongoClient client is provided + MongoDBModule + ], + adapter: new TransactionalAdapterMongoDB({ + // the injection token of the MongoClient + mongoClientToken: MONGO_CLIENT, + }), + }), + ], +}), +``` + +## Typing & usage + +Due to how [transactions work in MongoDB](https://www.mongodb.com/docs/drivers/node/current/fundamentals/transactions), the usage of the `MongoDBAdapter` adapter is a bit different from the others. + +The `tx` property on the `TransactionHost` does _not_ refer to any _transactional_ instance, but rather to a `ClientSession` instance. + +Queries are not executed using the `ClientSession` instance, but instead the `ClientSession` instance must be passed to the query. The `TransactionalAdapterMongoDB` ensures, that the `ClientSession` provided under the `tx` property refers to a session in which a transaction was started. Outside of a transaction a fallback `ClientSession` _without_ a started transaction is used. + +## Example + +```ts title="user.service.ts" +@Injectable() +class UserService { + constructor(private readonly userRepository: UserRepository) {} + + @Transactional() + async runTransaction() { + // highlight-start + // both methods are executed in the same transaction + const user = await this.userRepository.createUser('John'); + const foundUser = await this.userRepository.getUserById(r1.id); + // highlight-end + assert(foundUser.id === user.id); + } +} +``` + +```ts title="user.repository.ts" +@Injectable() +class UserRepository { + constructor( + @Inject(MONGO_CLIENT) + private readonly mongoClient: MongoClient, // we are using a regular mongoClient here + private readonly txHost: TransactionHost, + ) {} + + async getUserById(id: ObjectId) { + // txHost.tx is typed as Knex + return this.mongoClient.db('default').collection('user').findOne( + { _id: id }, + // highlight-start + { session: this.txHost.tx }, // here, the `tx` is passed as the `session` + // highlight-end + ); + } + + async createUser(name: string) { + const created = await this.mongo + .db('default') + .collection('user') + .insertOne( + { name: name, email: `${name}@email.com` }, + // highlight-start + { session: this.txHost.tx }, // here, the `tx` is passed as the `session` + // highlight-end + ); + const createdId = created.insertedId; + const createdUser = await this.getUserById(createdId); + return createdUser; + } +} +``` diff --git a/docs/docs/06_plugins/01_available-plugins/01-transactional/index.md b/docs/docs/06_plugins/01_available-plugins/01-transactional/index.md index 410356fa..da8c4639 100644 --- a/docs/docs/06_plugins/01_available-plugins/01-transactional/index.md +++ b/docs/docs/06_plugins/01_available-plugins/01-transactional/index.md @@ -45,6 +45,7 @@ Adapters for the following libraries are available: - Kysely (see [@nestjs-cls/transactional-adapter-knex](./03-kysely-adapter.md)) - Pg-promise (see [@nestjs-cls/transactional-adapter-pg-promise](./04-pg-promise-adapter.md)) - TypeORM (see [@nestjs-cls/transactional-adapter-typeorm](./05-typeorm-adapter.md)) +- MongoDB (see [@nestjs-cls/transactional-adapter-mongodb](./06-mongodb-adapter.md)) Adapters _will not_ be implemented for the following libraries (unless there is a serious demand): diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/README.md b/packages/transactional-adapters/transactional-adapter-mongodb/README.md new file mode 100644 index 00000000..add9540d --- /dev/null +++ b/packages/transactional-adapters/transactional-adapter-mongodb/README.md @@ -0,0 +1,5 @@ +# @nestjs-cls/transactional-adapter-knex + +Mongodb adapter for the `@nestjs-cls/transactional` plugin. + +### ➡️ [Go to the documentation website](https://papooch.github.io/nestjs-cls/plugins/available-plugins/transactional/mongodb-adapter) 📖 diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/jest.config.js b/packages/transactional-adapters/transactional-adapter-mongodb/jest.config.js new file mode 100644 index 00000000..14ae88e3 --- /dev/null +++ b/packages/transactional-adapters/transactional-adapter-mongodb/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + moduleFileExtensions: ['js', 'json', 'ts'], + rootDir: '.', + testRegex: '.*\\.spec\\.ts$', + transform: { + '^.+\\.ts$': [ + 'ts-jest', + { + isolatedModules: true, + maxWorkers: 1, + }, + ], + }, + collectCoverageFrom: ['src/**/*.ts'], + coverageDirectory: '../coverage', + testEnvironment: 'node', +}; diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/package.json b/packages/transactional-adapters/transactional-adapter-mongodb/package.json new file mode 100644 index 00000000..50d14645 --- /dev/null +++ b/packages/transactional-adapters/transactional-adapter-mongodb/package.json @@ -0,0 +1,73 @@ +{ + "name": "@nestjs-cls/transactional-adapter-mongodb", + "version": "1.0.0", + "description": "A mongodb adapter for @nestjs-cls/transactional", + "author": "papooch", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Papooch/nestjs-cls.git" + }, + "homepage": "https://papooch.github.io/nestjs-cls/", + "keywords": [ + "nest", + "nestjs", + "cls", + "continuation-local-storage", + "als", + "AsyncLocalStorage", + "async_hooks", + "request context", + "async context", + "transaction", + "transactional", + "transactional decorator", + "aop", + "mongodb" + ], + "main": "dist/src/index.js", + "types": "dist/src/index.d.ts", + "files": [ + "dist/src/**/!(*.spec).d.ts", + "dist/src/**/!(*.spec).js" + ], + "scripts": { + "prepack": "cp ../../../LICENSE ./LICENSE", + "prebuild": "rimraf dist", + "build": "tsc", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage" + }, + "peerDependencies": { + "@nestjs-cls/transactional": "workspace:^2.2.2", + "mongodb": "> 6", + "nestjs-cls": "workspace:^4.3.0" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.2", + "@nestjs/common": "^10.3.7", + "@nestjs/core": "^10.3.7", + "@nestjs/testing": "^10.3.7", + "@types/jest": "^28.1.2", + "@types/node": "^18.0.0", + "jest": "^29.7.0", + "mongodb": "^6.7.0", + "mongodb-memory-server": "^9.4.0", + "reflect-metadata": "^0.1.13", + "rimraf": "^3.0.2", + "rxjs": "^7.5.5", + "sqlite3": "^5.1.7", + "ts-jest": "^29.1.2", + "ts-loader": "^9.3.0", + "ts-node": "^10.8.1", + "tsconfig-paths": "^4.0.0", + "typescript": "5.0" + } +} diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/src/index.ts b/packages/transactional-adapters/transactional-adapter-mongodb/src/index.ts new file mode 100644 index 00000000..e76b1ddd --- /dev/null +++ b/packages/transactional-adapters/transactional-adapter-mongodb/src/index.ts @@ -0,0 +1 @@ +export * from './lib/transactional-adapter-mongodb'; diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts b/packages/transactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts new file mode 100644 index 00000000..359b0a5b --- /dev/null +++ b/packages/transactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts @@ -0,0 +1,77 @@ +import { TransactionalAdapter } from '@nestjs-cls/transactional'; + +import { + ClientSession, + ClientSessionOptions, + MongoClient, + TransactionOptions, +} from 'mongodb'; + +export type MongoDBTransactionOptions = TransactionOptions & { + /** + * Options for the encompassing `session` that hosts the transaction. + */ + sessionOptions?: ClientSessionOptions; +}; + +export interface MongoDBTransactionalAdapterOptions { + /** + * The injection token for the MongoClient instance. + */ + mongoClientToken: any; + + /** + * Default options for the transaction. These will be merged with any transaction-specific options + * passed to the `@Transactional` decorator or the `TransactionHost#withTransaction` method. + */ + defaultTxOptions?: Partial; +} + +export class TransactionalAdapterMongoDB + implements + TransactionalAdapter< + MongoClient, + ClientSession, + MongoDBTransactionOptions + > +{ + connectionToken: any; + + defaultTxOptions?: Partial; + + private fallbackSession: ClientSession | undefined; + + constructor(options: MongoDBTransactionalAdapterOptions) { + this.connectionToken = options.mongoClientToken; + this.defaultTxOptions = options.defaultTxOptions; + } + + async onModuleDestroy() { + await this.fallbackSession?.endSession({ force: true }); + } + + optionsFactory(mongoClient: MongoClient) { + return { + wrapWithTransaction: async ( + options: MongoDBTransactionOptions, + fn: (...args: any[]) => Promise, + setTx: (tx?: ClientSession) => void, + ) => { + return mongoClient.withSession( + options.sessionOptions ?? {}, + async (session) => + session.withTransaction(() => { + setTx(session); + return fn(); + }, options), + ); + }, + getFallbackInstance: () => { + if (!this.fallbackSession || this.fallbackSession.hasEnded) { + this.fallbackSession = mongoClient.startSession(); + } + return this.fallbackSession; + }, + }; + } +} diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/test/transactional-adapter-mongodb.spec.ts b/packages/transactional-adapters/transactional-adapter-mongodb/test/transactional-adapter-mongodb.spec.ts new file mode 100644 index 00000000..c40c8ec1 --- /dev/null +++ b/packages/transactional-adapters/transactional-adapter-mongodb/test/transactional-adapter-mongodb.spec.ts @@ -0,0 +1,253 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { + ClsPluginTransactional, + InjectTransaction, + Transaction, + Transactional, + TransactionHost, +} from '@nestjs-cls/transactional'; +import { Inject, Injectable, Module } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MongoClient, ObjectId, WriteConcern } from 'mongodb'; +import { MongoMemoryReplSet } from 'mongodb-memory-server'; +import { ClsModule, UseCls } from 'nestjs-cls'; +import { TransactionalAdapterMongoDB } from '../src'; + +const MONGO_CLIENT = 'MONGO_CLIENT'; + +@Injectable() +class UserRepository { + constructor( + @InjectTransaction() + private readonly session: Transaction, + @Inject(MONGO_CLIENT) + private readonly mongo: MongoClient, + ) {} + + async getUserById(id: ObjectId) { + return this.mongo + .db('default') + .collection('user') + .findOne({ _id: id }, { session: this.session }); + } + + async createUser(name: string) { + const created = await this.mongo + .db('default') + .collection('user') + .insertOne( + { name: name, email: `${name}@email.com` }, + { session: this.session }, + ); + const createdId = created.insertedId; + const createdUser = await this.getUserById(createdId); + return createdUser; + } +} + +@Injectable() +class UserService { + constructor( + private readonly userRepository: UserRepository, + private readonly txHost: TransactionHost, + @Inject(MONGO_CLIENT) + private readonly mongo: MongoClient, + ) {} + + @UseCls() + async withoutTransaction() { + const r1 = await this.userRepository.createUser('Jim'); + const r2 = await this.userRepository.getUserById(r1!._id); + return { r1, r2 }; + } + + @Transactional() + async transactionWithDecorator() { + const r1 = await this.userRepository.createUser('John'); + const r2 = await this.userRepository.getUserById(r1!._id); + + return { r1, r2 }; + } + + @Transactional({ + writeConcern: new WriteConcern('majority'), + }) + async transactionWithDecoratorWithOptions() { + const r1 = await this.userRepository.createUser('James'); + const r2 = await this.mongo + .db('default') + .collection('user') + .findOne({ _id: r1!._id }); + const r3 = await this.userRepository.getUserById(r1!._id); + return { r1, r2, r3 }; + } + + async transactionWithFunctionWrapper() { + return this.txHost.withTransaction( + { + writeConcern: new WriteConcern('majority'), + }, + async () => { + const r1 = await this.userRepository.createUser('Joe'); + const r2 = await this.mongo + .db('default') + .collection('user') + .findOne({ _id: r1!._id }); + const r3 = await this.userRepository.getUserById(r1!._id); + return { r1, r2, r3 }; + }, + ); + } + + @Transactional() + async transactionWithDecoratorError() { + await this.userRepository.createUser('Nobody'); + throw new Error('Rollback'); + } +} + +const replSet = new MongoMemoryReplSet({ + replSet: { count: 2, dbName: 'default' }, +}); + +@Module({ + providers: [ + { + provide: MONGO_CLIENT, + useFactory: async () => { + const mongo = new MongoClient(replSet.getUri()); + await mongo.connect(); + return mongo; + }, + }, + ], + exports: [MONGO_CLIENT], +}) +class MongoDBModule {} + +@Module({ + imports: [ + MongoDBModule, + ClsModule.forRoot({ + plugins: [ + new ClsPluginTransactional({ + imports: [MongoDBModule], + adapter: new TransactionalAdapterMongoDB({ + mongoClientToken: MONGO_CLIENT, + }), + enableTransactionProxy: true, + }), + ], + }), + ], + providers: [UserService, UserRepository], +}) +class AppModule {} + +describe('Transactional', () => { + let mongo: MongoClient; + let module: TestingModule; + let callingService: UserService; + + beforeAll(async () => { + await replSet.start(); + }); + + beforeEach(async () => { + module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + await module.init(); + callingService = module.get(UserService); + mongo = module.get(MONGO_CLIENT); + + await mongo.db('default').createCollection('user'); + }); + + afterEach(async () => { + await mongo.db('default').dropCollection('user'); + await mongo?.close(); + }); + + afterAll(async () => { + await replSet.stop({ force: true }); + }); + + describe('TransactionalAdapterKnex', () => { + it('should work without an active transaction', async () => { + const { r1, r2 } = await callingService.withoutTransaction(); + expect(r1).toEqual(r2); + const users = await mongo + .db('default') + .collection('user') + .find() + .toArray(); + expect(users).toEqual(expect.arrayContaining([r1])); + }); + + it('should run a transaction with the default options with a decorator', async () => { + const { r1, r2 } = await callingService.transactionWithDecorator(); + expect(r1).toEqual(r2); + const users = await mongo + .db('default') + .collection('user') + .find() + .toArray(); + expect(users).toEqual(expect.arrayContaining([r1])); + }); + + it('should run a transaction with the specified options with a decorator', async () => { + const { r1, r2, r3 } = + await callingService.transactionWithDecoratorWithOptions(); + expect(r1).toEqual(r3); + expect(r2).toBeNull(); + const users = await mongo + .db('default') + .collection('user') + .find() + .toArray(); + expect(users).toEqual(expect.arrayContaining([r1])); + }); + it('should run a transaction with the specified options with a function wrapper', async () => { + const { r1, r2, r3 } = + await callingService.transactionWithFunctionWrapper(); + expect(r1).toEqual(r3); + expect(r2).toBeNull(); + const users = await mongo + .db('default') + .collection('user') + .find() + .toArray(); + expect(users).toEqual(expect.arrayContaining([r1])); + }); + + it('should rollback a transaction on error', async () => { + await expect( + callingService.transactionWithDecoratorError(), + ).rejects.toThrow(new Error('Rollback')); + const users = await mongo + .db('default') + .collection('user') + .find() + .toArray(); + expect(users).toEqual( + expect.not.arrayContaining([{ name: 'Nobody' }]), + ); + }); + }); +}); + +describe('Default options', () => { + it('Should correctly set default options on the adapter instance', async () => { + const adapter = new TransactionalAdapterMongoDB({ + mongoClientToken: MONGO_CLIENT, + defaultTxOptions: { + readConcern: { level: 'snapshot' }, + }, + }); + + expect(adapter.defaultTxOptions).toEqual({ + readConcern: { level: 'snapshot' }, + }); + }); +}); diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/tsconfig.json b/packages/transactional-adapters/transactional-adapter-mongodb/tsconfig.json new file mode 100644 index 00000000..bbc28fb3 --- /dev/null +++ b/packages/transactional-adapters/transactional-adapter-mongodb/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "." + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index 162e69e7..fddaf0a4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,10 +32,19 @@ "path": "packages/transactional-adapters/transactional-adapter-knex" }, { - "path": "packages/transactional-adapters/transactional-adapter-prisma" + "path": "packages/transactional-adapters/transactional-adapter-kysely" + }, + { + "path": "packages/transactional-adapters/transactional-adapter-mongodb" }, { "path": "packages/transactional-adapters/transactional-adapter-pg-promise" + }, + { + "path": "packages/transactional-adapters/transactional-adapter-prisma" + }, + { + "path": "packages/transactional-adapters/transactional-adapter-typeorm" } ] -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5adc82ee..7c236ffe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4714,6 +4714,15 @@ __metadata: languageName: node linkType: hard +"@mongodb-js/saslprep@npm:^1.1.0, @mongodb-js/saslprep@npm:^1.1.5": + version: 1.1.7 + resolution: "@mongodb-js/saslprep@npm:1.1.7" + dependencies: + sparse-bitfield: "npm:^3.0.3" + checksum: 2c3fa88eaa26be671a3f95866049612bb5a29532e73a685faf2b4a85487a903cecf889e91cc15701ef144483a8ed5b24f54e28155f87fb9d65d0656bb288ad5f + languageName: node + linkType: hard + "@monodeploy/changelog@npm:^5.0.1": version: 5.0.1 resolution: "@monodeploy/changelog@npm:5.0.1" @@ -4935,6 +4944,35 @@ __metadata: languageName: unknown linkType: soft +"@nestjs-cls/transactional-adapter-mongodb@workspace:packages/transactional-adapters/transactional-adapter-mongodb": + version: 0.0.0-use.local + resolution: "@nestjs-cls/transactional-adapter-mongodb@workspace:packages/transactional-adapters/transactional-adapter-mongodb" + dependencies: + "@nestjs/cli": "npm:^10.0.2" + "@nestjs/common": "npm:^10.3.7" + "@nestjs/core": "npm:^10.3.7" + "@nestjs/testing": "npm:^10.3.7" + "@types/jest": "npm:^28.1.2" + "@types/node": "npm:^18.0.0" + jest: "npm:^29.7.0" + mongodb: "npm:^6.7.0" + mongodb-memory-server: "npm:^9.4.0" + reflect-metadata: "npm:^0.1.13" + rimraf: "npm:^3.0.2" + rxjs: "npm:^7.5.5" + sqlite3: "npm:^5.1.7" + ts-jest: "npm:^29.1.2" + ts-loader: "npm:^9.3.0" + ts-node: "npm:^10.8.1" + tsconfig-paths: "npm:^4.0.0" + typescript: "npm:5.0" + peerDependencies: + "@nestjs-cls/transactional": "workspace:^2.2.2" + mongodb: "> 6" + nestjs-cls: "workspace:^4.3.0" + languageName: unknown + linkType: soft + "@nestjs-cls/transactional-adapter-pg-promise@workspace:packages/transactional-adapters/transactional-adapter-pg-promise": version: 0.0.0-use.local resolution: "@nestjs-cls/transactional-adapter-pg-promise@workspace:packages/transactional-adapters/transactional-adapter-pg-promise" @@ -6691,6 +6729,32 @@ __metadata: languageName: node linkType: hard +"@types/webidl-conversions@npm:*": + version: 7.0.3 + resolution: "@types/webidl-conversions@npm:7.0.3" + checksum: ac2ccff93b95ac7c8ca73dc6064403181691bba7ea144296f462dc9108a07be16cbad7b9c704b3df706dcc5a117e1f7bf7fb27aeb75b09c0f3148de8aee11aff + languageName: node + linkType: hard + +"@types/whatwg-url@npm:^11.0.2": + version: 11.0.5 + resolution: "@types/whatwg-url@npm:11.0.5" + dependencies: + "@types/webidl-conversions": "npm:*" + checksum: 7a9b9252dee98df6db1ad62337daca7f59ae50d7a3406d14ac6b57168d406004359994f3371155e24f3cf12002c4cb8bbb0883bd4cefb9d7ee8e2b510bdd7f5e + 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: 7e5b6837daff8c6d189b13d19cc6d69e3bf954751f4f8a92d9a762c7f32ba75464d8e686a69c7d70e5092499c8ac14933c0ed416cf563689b04c4e10bff95e40 + languageName: node + linkType: hard + "@types/ws@npm:^8.5.5": version: 8.5.5 resolution: "@types/ws@npm:8.5.5" @@ -7694,6 +7758,15 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:^7.0.2": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 + languageName: node + linkType: hard + "agentkeepalive@npm:^4.1.3": version: 4.5.0 resolution: "agentkeepalive@npm:4.5.0" @@ -8064,6 +8137,15 @@ __metadata: languageName: node linkType: hard +"async-mutex@npm:^0.4.1": + version: 0.4.1 + resolution: "async-mutex@npm:0.4.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 3c412736c0bc4a9a2cfd948276a8caab8686aa615866a5bd20986e616f8945320acb310058a17afa1b31b8de6f634a78b7ec2217a33d7559b38f68bb85a95854 + languageName: node + linkType: hard + "async-retry@npm:^1.2.1": version: 1.3.3 resolution: "async-retry@npm:1.3.3" @@ -8158,6 +8240,13 @@ __metadata: languageName: node linkType: hard +"b4a@npm:^1.6.4": + version: 1.6.6 + resolution: "b4a@npm:1.6.6" + checksum: 56f30277666cb511a15829e38d369b114df7dc8cec4cedc09cc5d685bc0f27cb63c7bcfb58e09a19a1b3c4f2541069ab078b5328542e85d74a39620327709a38 + languageName: node + linkType: hard + "babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -8356,6 +8445,13 @@ __metadata: languageName: node linkType: hard +"bare-events@npm:^2.2.0": + version: 2.4.2 + resolution: "bare-events@npm:2.4.2" + checksum: 09fa923061f31f815e83504e2ed4a8ba87732a01db40a7fae703dbb7eef7f05d99264b5e186074cbe9698213990d1af564c62cca07a5ff88baea8099ad9a6303 + languageName: node + linkType: hard + "base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -8590,6 +8686,27 @@ __metadata: languageName: node linkType: hard +"bson@npm:^5.5.0": + version: 5.5.1 + resolution: "bson@npm:5.5.1" + checksum: 00fabdafe98d20609bd76f607cada03ec544b90225103f7ae859fba7674bd96cae56432b0516e30291af0c40634d306f8a45b63b706a034e95fe583c749ef5b3 + languageName: node + linkType: hard + +"bson@npm:^6.7.0": + version: 6.7.0 + resolution: "bson@npm:6.7.0" + checksum: 6cc5c66bafaa2b7127409abda094af7e6f76771123ffc9526b167de23860d62560ba5db9b59a1f51bad3e51d320adf372e8ae0b2e0d156b0781683b094f3a325 + languageName: node + linkType: hard + +"buffer-crc32@npm:~0.2.3": + version: 0.2.13 + resolution: "buffer-crc32@npm:0.2.13" + checksum: cb0a8ddf5cf4f766466db63279e47761eb825693eeba6a5a95ee4ec8cb8f81ede70aa7f9d8aeec083e781d47154290eb5d4d26b3f7a465ec57fb9e7d59c47150 + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -8782,7 +8899,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.2.0": +"camelcase@npm:^6.2.0, camelcase@npm:^6.3.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 @@ -9402,6 +9519,13 @@ __metadata: languageName: node linkType: hard +"commondir@npm:^1.0.1": + version: 1.0.1 + resolution: "commondir@npm:1.0.1" + checksum: 33a124960e471c25ee19280c9ce31ccc19574b566dc514fe4f4ca4c34fa8b0b57cf437671f5de380e11353ea9426213fca17687dd2ef03134fea2dbc53809fd6 + languageName: node + linkType: hard + "compare-func@npm:^2.0.0": version: 2.0.0 resolution: "compare-func@npm:2.0.0" @@ -10049,6 +10173,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.3.5": + version: 4.3.5 + resolution: "debug@npm:4.3.5" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 082c375a2bdc4f4469c99f325ff458adad62a3fc2c482d59923c260cb08152f34e2659f72b3767db8bb2f21ca81a60a42d1019605a412132d7b9f59363a005cc + languageName: node + linkType: hard + "decamelize-keys@npm:^1.1.0": version: 1.1.1 resolution: "decamelize-keys@npm:1.1.1" @@ -11282,6 +11418,13 @@ __metadata: languageName: node linkType: hard +"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": + version: 1.3.2 + resolution: "fast-fifo@npm:1.3.2" + checksum: d53f6f786875e8b0529f784b59b4b05d4b5c31c651710496440006a398389a579c8dbcd2081311478b5bf77f4b0b21de69109c5a4eabea9d8e8783d1eb864e4c + languageName: node + linkType: hard + "fast-glob@npm:3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" @@ -11573,6 +11716,17 @@ __metadata: languageName: node linkType: hard +"find-cache-dir@npm:^3.3.2": + version: 3.3.2 + resolution: "find-cache-dir@npm:3.3.2" + dependencies: + commondir: "npm:^1.0.1" + make-dir: "npm:^3.0.2" + pkg-dir: "npm:^4.1.0" + checksum: 92747cda42bff47a0266b06014610981cfbb71f55d60f2c8216bc3108c83d9745507fb0b14ecf6ab71112bed29cd6fb1a137ee7436179ea36e11287e3159e587 + languageName: node + linkType: hard + "find-cache-dir@npm:^4.0.0": version: 4.0.0 resolution: "find-cache-dir@npm:4.0.0" @@ -11669,6 +11823,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.6": + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" + peerDependenciesMeta: + debug: + optional: true + checksum: 9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0": version: 3.1.1 resolution: "foreground-child@npm:3.1.1" @@ -13032,6 +13196,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^7.0.4": + version: 7.0.4 + resolution: "https-proxy-agent@npm:7.0.4" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:4" + checksum: bc4f7c38da32a5fc622450b6cb49a24ff596f9bd48dcedb52d2da3fa1c1a80e100fb506bd59b326c012f21c863c69b275c23de1a01d0b84db396822fdf25e52b + languageName: node + linkType: hard + "human-signals@npm:^1.1.1": version: 1.1.1 resolution: "human-signals@npm:1.1.1" @@ -13349,6 +13523,16 @@ __metadata: languageName: node linkType: hard +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + "ip@npm:^2.0.0": version: 2.0.0 resolution: "ip@npm:2.0.0" @@ -14444,6 +14628,13 @@ __metadata: languageName: node linkType: hard +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + "jsesc@npm:^2.5.1": version: 2.5.2 resolution: "jsesc@npm:2.5.2" @@ -14949,7 +15140,7 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.0.0": +"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -15332,6 +15523,13 @@ __metadata: languageName: node linkType: hard +"memory-pager@npm:^1.0.2": + version: 1.5.0 + resolution: "memory-pager@npm:1.5.0" + checksum: 2596e80c99fee24f05bd8a20cde2ee899012c996f4ec361ac76ed6f009f34149d733ac6f76880106ccd6a66d062ad439357578d383d429df66ba1278f68806e9 + languageName: node + linkType: hard + "meow@npm:^12.0.1": version: 12.1.1 resolution: "meow@npm:12.1.1" @@ -16249,6 +16447,122 @@ __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: 1e26b045063f4b3eb58fe445bfaf4e1ac3b9b9ceebc30c6deef5e769323cadb00e62cbe1d26a15fda457643d40a9ef9a24a94a1e993addb9261d87cad1fbf0ae + languageName: node + linkType: hard + +"mongodb-connection-string-url@npm:^3.0.0": + version: 3.0.1 + resolution: "mongodb-connection-string-url@npm:3.0.1" + dependencies: + "@types/whatwg-url": "npm:^11.0.2" + whatwg-url: "npm:^13.0.0" + checksum: 758f6fddde603cbe584d77f71216fc333bff18b53a8524291bb9924754de39a36af812905f3295b85ae1b20ed0a0155e8176207d34fe229f9d10c672393b7f4f + languageName: node + linkType: hard + +"mongodb-memory-server-core@npm:9.4.0": + version: 9.4.0 + resolution: "mongodb-memory-server-core@npm:9.4.0" + dependencies: + async-mutex: "npm:^0.4.1" + camelcase: "npm:^6.3.0" + debug: "npm:^4.3.5" + find-cache-dir: "npm:^3.3.2" + follow-redirects: "npm:^1.15.6" + https-proxy-agent: "npm:^7.0.4" + mongodb: "npm:^5.9.2" + new-find-package-json: "npm:^2.0.0" + semver: "npm:^7.6.2" + tar-stream: "npm:^3.1.7" + tslib: "npm:^2.6.3" + yauzl: "npm:^3.1.3" + checksum: 42e9d2ab3b459ea78ed210ff1032c2bf3812cc8e01178c1c65d5bfbd780992a5b4ae5d322e60fd05b5316ee174ba0748229023c362348cafd0e2760be5028ea8 + languageName: node + linkType: hard + +"mongodb-memory-server@npm:^9.4.0": + version: 9.4.0 + resolution: "mongodb-memory-server@npm:9.4.0" + dependencies: + mongodb-memory-server-core: "npm:9.4.0" + tslib: "npm:^2.6.3" + checksum: ec7030671be4110f6c9e96f7fc5a24dc73eb393614434b3b29624a5f1569faaa884ba7c2bf14c5d2833a596c79276967aa5ecba162584448054cce9c461a1608 + languageName: node + linkType: hard + +"mongodb@npm:^5.9.2": + version: 5.9.2 + resolution: "mongodb@npm:5.9.2" + dependencies: + "@mongodb-js/saslprep": "npm:^1.1.0" + bson: "npm:^5.5.0" + mongodb-connection-string-url: "npm:^2.6.0" + socks: "npm:^2.7.1" + peerDependencies: + "@aws-sdk/credential-providers": ^3.188.0 + "@mongodb-js/zstd": ^1.0.0 + kerberos: ^1.0.0 || ^2.0.0 + mongodb-client-encryption: ">=2.3.0 <3" + snappy: ^7.2.2 + dependenciesMeta: + "@mongodb-js/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: c1bb3e0b390c74cd53ae2deedf6fb87d15f4bf11677f499b095c7fcd8d4692cd664a63b49b8b525330069faa0e689a76eda4b2a7a97bb7f26645d9310617b07f + languageName: node + linkType: hard + +"mongodb@npm:^6.7.0": + version: 6.7.0 + resolution: "mongodb@npm:6.7.0" + dependencies: + "@mongodb-js/saslprep": "npm:^1.1.5" + bson: "npm:^6.7.0" + mongodb-connection-string-url: "npm:^3.0.0" + peerDependencies: + "@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: ">=6.0.0 <7" + snappy: ^7.2.2 + 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 + socks: + optional: true + checksum: 4a8a3781b6c4851dc211df559ac2b82b62f6c1bcc0dcd14c86264891221c081436d03254cb257d0da696efbba9f426919c0e6a9e14398faac4a93db3c1c8d335 + languageName: node + linkType: hard + "monodeploy@npm:^5.0.1": version: 5.0.1 resolution: "monodeploy@npm:5.0.1" @@ -16479,6 +16793,15 @@ __metadata: languageName: unknown linkType: soft +"new-find-package-json@npm:^2.0.0": + version: 2.0.0 + resolution: "new-find-package-json@npm:2.0.0" + dependencies: + debug: "npm:^4.3.4" + checksum: b5265976629894369726c7bb0705493783071a00e202c5d9da1903458823192c25a954e24d387476eae8dfa6c5c3fff61018fd2bb7ea7b66424a27ff11b49606 + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -17283,6 +17606,13 @@ __metadata: languageName: node linkType: hard +"pend@npm:~1.2.0": + version: 1.2.0 + resolution: "pend@npm:1.2.0" + checksum: 8a87e63f7a4afcfb0f9f77b39bb92374afc723418b9cb716ee4257689224171002e07768eeade4ecd0e86f1fa3d8f022994219fb45634f2dbd78c6803e452458 + languageName: node + linkType: hard + "periscopic@npm:^3.0.0": version: 3.1.0 resolution: "periscopic@npm:3.1.0" @@ -17477,7 +17807,7 @@ __metadata: languageName: node linkType: hard -"pkg-dir@npm:^4.2.0": +"pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" dependencies: @@ -18274,6 +18604,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^2.1.1, punycode@npm:^2.3.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + "pupa@npm:^3.1.0": version: 3.1.0 resolution: "pupa@npm:3.1.0" @@ -18324,6 +18661,13 @@ __metadata: languageName: node linkType: hard +"queue-tick@npm:^1.0.1": + version: 1.0.1 + resolution: "queue-tick@npm:1.0.1" + checksum: 0db998e2c9b15215317dbcf801e9b23e6bcde4044e115155dae34f8e7454b9a783f737c9a725528d677b7a66c775eb7a955cf144fe0b87f62b575ce5bfd515a9 + languageName: node + linkType: hard + "queue@npm:6.0.2": version: 6.0.2 resolution: "queue@npm:6.0.2" @@ -19468,6 +19812,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.2": + version: 7.6.2 + resolution: "semver@npm:7.6.2" + bin: + semver: bin/semver.js + checksum: 97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -19801,6 +20154,16 @@ __metadata: languageName: node linkType: hard +"socks@npm:^2.7.1": + version: 2.8.3 + resolution: "socks@npm:2.8.3" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 + languageName: node + linkType: hard + "sonic-boom@npm:^3.7.0": version: 3.8.0 resolution: "sonic-boom@npm:3.8.0" @@ -19872,6 +20235,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: 248c6ff7b5e354735e1daac4059222a29c9d291dfcf265daf675d13515eeaac454cfcccd687c8d134f86698b39abd7ad4d7434f7272dd6f8e41a00f21aae4194 + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -19956,6 +20328,13 @@ __metadata: languageName: node linkType: hard +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + "sprintf-js@npm:~1.0.2": version: 1.0.3 resolution: "sprintf-js@npm:1.0.3" @@ -20062,6 +20441,21 @@ __metadata: languageName: node linkType: hard +"streamx@npm:^2.15.0": + version: 2.18.0 + resolution: "streamx@npm:2.18.0" + dependencies: + bare-events: "npm:^2.2.0" + fast-fifo: "npm:^1.3.2" + queue-tick: "npm:^1.0.1" + text-decoder: "npm:^1.1.0" + dependenciesMeta: + bare-events: + optional: true + checksum: ef50f419252a73dd35abcde72329eafbf5ad9cd2e27f0cc3abebeff6e0dbea124ac6d3e16acbdf081cce41b4125393ac22f9848fcfa19e640830734883e622ba + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -20386,6 +20780,17 @@ __metadata: languageName: node linkType: hard +"tar-stream@npm:^3.1.7": + version: 3.1.7 + resolution: "tar-stream@npm:3.1.7" + dependencies: + b4a: "npm:^1.6.4" + fast-fifo: "npm:^1.2.0" + streamx: "npm:^2.15.0" + checksum: a09199d21f8714bd729993ac49b6c8efcb808b544b89f23378ad6ffff6d1cb540878614ba9d4cfec11a64ef39e1a6f009a5398371491eb1fda606ffc7f70f718 + languageName: node + linkType: hard + "tar@npm:^6.0.2": version: 6.2.0 resolution: "tar@npm:6.2.0" @@ -20521,6 +20926,15 @@ __metadata: languageName: node linkType: hard +"text-decoder@npm:^1.1.0": + version: 1.1.0 + resolution: "text-decoder@npm:1.1.0" + dependencies: + b4a: "npm:^1.6.4" + checksum: 623a6cfb5ee86c250fea31f369a0d40e4ef5c2c32ce8db43492648b51193858213e61bf47a6078f285053715dcc6342806ce6ea9a49d7847ffca282ca88ad7e8 + languageName: node + linkType: hard + "text-extensions@npm:^1.0.0": version: 1.9.0 resolution: "text-extensions@npm:1.9.0" @@ -20700,6 +21114,24 @@ __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: cdc47cad3a9d0b6cb293e39ccb1066695ae6fdd39b9e4f351b010835a1f8b4f3a6dc3a55e896b421371187f22b48d7dac1b693de4f6551bdef7b6ab6735dfe3b + languageName: node + linkType: hard + +"tr46@npm:^4.1.1": + version: 4.1.1 + resolution: "tr46@npm:4.1.1" + dependencies: + punycode: "npm:^2.3.0" + checksum: 92085dcf186f56a49ba4124a581d9ae6a5d0cd4878107c34e2e670b9ddc49da85e4950084bb3e75015195cc23f37ae1c02d45064e94dd6018f5e789aa51d93a8 + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -20891,6 +21323,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.6.3": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -21739,6 +22178,13 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: 228d8cb6d270c23b0720cb2d95c579202db3aaf8f633b4e9dd94ec2000a04e7e6e43b76a94509cdb30479bd00ae253ab2371a2da9f81446cc313f89a4213a2c4 + languageName: node + linkType: hard + "webpack-bundle-analyzer@npm:^4.9.0": version: 4.10.1 resolution: "webpack-bundle-analyzer@npm:4.10.1" @@ -21962,6 +22408,26 @@ __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: f7ec264976d7c725e0696fcaf9ebe056e14422eacbf92fdbb4462034609cba7d0c85ffa1aab05e9309d42969bcf04632ba5ed3f3882c516d7b093053315bf4c1 + languageName: node + linkType: hard + +"whatwg-url@npm:^13.0.0": + version: 13.0.0 + resolution: "whatwg-url@npm:13.0.0" + dependencies: + tr46: "npm:^4.1.1" + webidl-conversions: "npm:^7.0.0" + checksum: 06b0c1808bb80eaab6582087bd8be594a1947843cbb0ce1a90635841a549a5849dff5be2b28f5b1b596ef70ea4c3df5c599d35b160899d12343334149851e688 + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -22260,6 +22726,16 @@ __metadata: languageName: node linkType: hard +"yauzl@npm:^3.1.3": + version: 3.1.3 + resolution: "yauzl@npm:3.1.3" + dependencies: + buffer-crc32: "npm:~0.2.3" + pend: "npm:~1.2.0" + checksum: e04a2567860e1337798cd2570d776b4040520b20660e7ec5dfcce24b8be2b134d6a5ae835804a0186b1a58cb8b1741b37eaa6a86f7546b6219b62a265dbaf3fc + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" From c6657bc6bc98d4f571c2e70a2e6956c2fa4c55ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0vanda?= <46406259+Papooch@users.noreply.github.com> Date: Mon, 1 Jul 2024 20:10:51 +0200 Subject: [PATCH 2/2] docs(transactional-adapter-mongodb): add docs --- .../01-transactional/06-mongodb-adapter.md | 26 ++++++++++++++----- .../src/lib/transactional-adapter-mongodb.ts | 16 +++--------- .../transactional-adapter-mongodb.spec.ts | 10 +++---- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md b/docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md index e7b23c57..42b86b35 100644 --- a/docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md +++ b/docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md @@ -36,8 +36,8 @@ ClsModule.forRoot({ plugins: [ new ClsPluginTransactional({ imports: [ - // module in which the MongoClient client is provided - MongoDBModule + // module in which the MongoClient instance is provided + MongoDBModule, ], adapter: new TransactionalAdapterMongoDB({ // the injection token of the MongoClient @@ -45,16 +45,24 @@ ClsModule.forRoot({ }), }), ], -}), +}); ``` ## Typing & usage +To work correctly, the adapter needs to inject an instance of [`MongoClient`](https://mongodb.github.io/node-mongodb-native/6.7/classes/MongoClient.html) + Due to how [transactions work in MongoDB](https://www.mongodb.com/docs/drivers/node/current/fundamentals/transactions), the usage of the `MongoDBAdapter` adapter is a bit different from the others. -The `tx` property on the `TransactionHost` does _not_ refer to any _transactional_ instance, but rather to a `ClientSession` instance. +The `tx` property on the `TransactionHost` does _not_ refer to any _transactional_ instance, but rather to a [`ClientSession`](https://mongodb.github.io/node-mongodb-native/6.7/classes/ClientSession.html) instance with an active transaction, or `undefined` when no transaction is active. + +Queries are not executed using the `ClientSession` instance, but instead the `ClientSession` instance or `undefined` is passed to the query as the `session` option. + +:::important + +The `TransactionalAdapterMongoDB` _does not support_ the ["Transaction Proxy"](./index.md#using-the-injecttransaction-decorator) feature, because proxying an `undefined` value is not supported by the JavaScript Proxy. -Queries are not executed using the `ClientSession` instance, but instead the `ClientSession` instance must be passed to the query. The `TransactionalAdapterMongoDB` ensures, that the `ClientSession` provided under the `tx` property refers to a session in which a transaction was started. Outside of a transaction a fallback `ClientSession` _without_ a started transaction is used. +:::: ## Example @@ -80,7 +88,7 @@ class UserService { class UserRepository { constructor( @Inject(MONGO_CLIENT) - private readonly mongoClient: MongoClient, // we are using a regular mongoClient here + private readonly mongoClient: MongoClient, // use a regular mongoClient here private readonly txHost: TransactionHost, ) {} @@ -110,3 +118,9 @@ class UserRepository { } } ``` + +:::note + +Queries don't have to be run using the "raw" `MongoClient`. You can as well use collection aliases and whatnot. What is important, is that they all reference the same underlying `MongoClient` instance, and that you pass the `tx` as the `session` option to the query. + +::: diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts b/packages/transactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts index 359b0a5b..7e3a9bdc 100644 --- a/packages/transactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts +++ b/packages/transactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts @@ -31,7 +31,7 @@ export class TransactionalAdapterMongoDB implements TransactionalAdapter< MongoClient, - ClientSession, + ClientSession | undefined, MongoDBTransactionOptions > { @@ -39,16 +39,13 @@ export class TransactionalAdapterMongoDB defaultTxOptions?: Partial; - private fallbackSession: ClientSession | undefined; - constructor(options: MongoDBTransactionalAdapterOptions) { this.connectionToken = options.mongoClientToken; this.defaultTxOptions = options.defaultTxOptions; } - async onModuleDestroy() { - await this.fallbackSession?.endSession({ force: true }); - } + /** cannot proxy an `undefined` value */ + supportsTransactionProxy = false; optionsFactory(mongoClient: MongoClient) { return { @@ -66,12 +63,7 @@ export class TransactionalAdapterMongoDB }, options), ); }, - getFallbackInstance: () => { - if (!this.fallbackSession || this.fallbackSession.hasEnded) { - this.fallbackSession = mongoClient.startSession(); - } - return this.fallbackSession; - }, + getFallbackInstance: () => undefined, }; } } diff --git a/packages/transactional-adapters/transactional-adapter-mongodb/test/transactional-adapter-mongodb.spec.ts b/packages/transactional-adapters/transactional-adapter-mongodb/test/transactional-adapter-mongodb.spec.ts index c40c8ec1..f3b6adac 100644 --- a/packages/transactional-adapters/transactional-adapter-mongodb/test/transactional-adapter-mongodb.spec.ts +++ b/packages/transactional-adapters/transactional-adapter-mongodb/test/transactional-adapter-mongodb.spec.ts @@ -1,8 +1,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { ClsPluginTransactional, - InjectTransaction, - Transaction, Transactional, TransactionHost, } from '@nestjs-cls/transactional'; @@ -18,8 +16,7 @@ const MONGO_CLIENT = 'MONGO_CLIENT'; @Injectable() class UserRepository { constructor( - @InjectTransaction() - private readonly session: Transaction, + private readonly txHost: TransactionHost, @Inject(MONGO_CLIENT) private readonly mongo: MongoClient, ) {} @@ -28,7 +25,7 @@ class UserRepository { return this.mongo .db('default') .collection('user') - .findOne({ _id: id }, { session: this.session }); + .findOne({ _id: id }, { session: this.txHost.tx }); } async createUser(name: string) { @@ -37,7 +34,7 @@ class UserRepository { .collection('user') .insertOne( { name: name, email: `${name}@email.com` }, - { session: this.session }, + { session: this.txHost.tx }, ); const createdId = created.insertedId; const createdUser = await this.getUserById(createdId); @@ -135,7 +132,6 @@ class MongoDBModule {} adapter: new TransactionalAdapterMongoDB({ mongoClientToken: MONGO_CLIENT, }), - enableTransactionProxy: true, }), ], }),