-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(transactional-adapter-mongodb): add
mongodb
adapter (#158)
- Loading branch information
Showing
11 changed files
with
1,037 additions
and
5 deletions.
There are no files selected for viewing
112 changes: 112 additions & 0 deletions
112
docs/docs/06_plugins/01_available-plugins/01-transactional/06-mongodb-adapter.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import Tabs from '@theme/Tabs'; | ||
import TabItem from '@theme/TabItem'; | ||
|
||
# MongoDB adapter | ||
|
||
## Installation | ||
|
||
<Tabs> | ||
<TabItem value="npm" label="npm" default> | ||
|
||
```bash | ||
npm install @nestjs-cls/transactional-adapter-mongodb | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="yarn" label="yarn"> | ||
|
||
```bash | ||
yarn add @nestjs-cls/transactional-adapter-mongodb | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="pnpm" label="pnpm"> | ||
|
||
```bash | ||
pnpm add @nestjs-cls/transactional-adapter-mongodb | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
## 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<TransactionalAdapterMongoDB>` 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<TransactionalAdapterMongoDB>, | ||
) {} | ||
|
||
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; | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
packages/transactional-adapters/transactional-adapter-mongodb/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) 📖 |
17 changes: 17 additions & 0 deletions
17
packages/transactional-adapters/transactional-adapter-mongodb/jest.config.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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', | ||
}; |
73 changes: 73 additions & 0 deletions
73
packages/transactional-adapters/transactional-adapter-mongodb/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
packages/transactional-adapters/transactional-adapter-mongodb/src/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './lib/transactional-adapter-mongodb'; |
77 changes: 77 additions & 0 deletions
77
...sactional-adapters/transactional-adapter-mongodb/src/lib/transactional-adapter-mongodb.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<MongoDBTransactionOptions>; | ||
} | ||
|
||
export class TransactionalAdapterMongoDB | ||
implements | ||
TransactionalAdapter< | ||
MongoClient, | ||
ClientSession, | ||
MongoDBTransactionOptions | ||
> | ||
{ | ||
connectionToken: any; | ||
|
||
defaultTxOptions?: Partial<TransactionOptions>; | ||
|
||
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<any>, | ||
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; | ||
}, | ||
}; | ||
} | ||
} |
Oops, something went wrong.