-
Notifications
You must be signed in to change notification settings - Fork 28
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-mongoose): add
mongoose
adapter (#159)
* feat(transactional-adapter-mongoose): add `mongoose` adapter * docs(transactional-adapter-mongoose): add docs
- Loading branch information
Showing
10 changed files
with
616 additions
and
1 deletion.
There are no files selected for viewing
132 changes: 132 additions & 0 deletions
132
docs/docs/06_plugins/01_available-plugins/01-transactional/07-mongoose-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,132 @@ | ||
import Tabs from '@theme/Tabs'; | ||
import TabItem from '@theme/TabItem'; | ||
|
||
# Mongoose adapter | ||
|
||
## Installation | ||
|
||
<Tabs> | ||
<TabItem value="npm" label="npm" default> | ||
|
||
```bash | ||
npm install @nestjs-cls/transactional-adapter-mongoose | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="yarn" label="yarn"> | ||
|
||
```bash | ||
yarn add @nestjs-cls/transactional-adapter-mongoose | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="pnpm" label="pnpm"> | ||
|
||
```bash | ||
pnpm add @nestjs-cls/transactional-adapter-mongoose | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
## Registration | ||
|
||
```ts | ||
ClsModule.forRoot({ | ||
plugins: [ | ||
new ClsPluginTransactional({ | ||
imports: [ | ||
// module in which the Connection instance is provided | ||
MongooseModule, | ||
], | ||
adapter: new TransactionalAdapterMongoose({ | ||
// the injection token of the mongoose Connection | ||
mongooseConnectionToken: Connection, | ||
}), | ||
}), | ||
], | ||
}); | ||
``` | ||
|
||
## Typing & usage | ||
|
||
To work correctly, the adapter needs to inject an instance of mongoose [`Connection`](<https://mongoosejs.com/docs/api/connection.html#Connection()>). | ||
|
||
Due to how transactions work in MongoDB, [and in turn in Mongoose](https://mongoosejs.com/docs/transactions.html), the usage of the Mongoose adapter is a bit different from the others. | ||
|
||
The `tx` property on the `TransactionHost<TransactionalAdapterMongoose>` 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 of `mongodb`, with an active transaction, or `null` when no transaction is active. | ||
|
||
Queries are not executed using the `ClientSession` instance, but instead the `ClientSession` instance or `null` is passed to the query as the `session` option. | ||
|
||
:::important | ||
|
||
The `TransactionalAdapterMongoose` _does not support_ the ["Transaction Proxy"](./index.md#using-the-injecttransaction-decorator) feature, because proxying a `null` value is not supported by the JavaScript Proxy. | ||
|
||
:::: | ||
|
||
## Example | ||
|
||
```ts title="database.schemas.ts" | ||
const userSchema = new Schema({ | ||
name: String, | ||
email: String, | ||
}); | ||
|
||
const User = mongoose.model('user', userSchema); | ||
``` | ||
|
||
```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(user._id); | ||
// highlight-end | ||
assert(foundUser._id === user._id); | ||
} | ||
} | ||
``` | ||
|
||
```ts title="user.repository.ts" | ||
@Injectable() | ||
class UserRepository { | ||
constructor( | ||
private readonly txHost: TransactionHost<TransactionalAdapterMongoose>, | ||
) {} | ||
|
||
async getUserById(id: ObjectId) { | ||
// txHost.tx is typed as ClientSession | ||
return await User.findById(id) | ||
// highlight-start | ||
.session(this.txHost.tx); | ||
// highlight-end | ||
} | ||
|
||
async createUser(name: string) { | ||
const user = new User({ name: name, email: `${name}@email.com` }); | ||
await user | ||
// highlight-start | ||
.save({ session: this.txHost.tx }); | ||
// highlight-end | ||
return user; | ||
} | ||
} | ||
``` | ||
|
||
## Considerations | ||
|
||
### Using with built-in Mongoose AsyncLocalStorage support | ||
|
||
Mongoose > 8.4 has a built-in support for [propagating the `session` via `AsyncLocalStorage`](https://mongoosejs.com/docs/transactions.html#asynclocalstorage). | ||
|
||
The feature is compatible with `@nestjs-cls/transactional` and when enabled, one _does not_ have to pass `TransactionHost#tx` to queries and still enjoy the simplicity of the `@Transactional` decorator, which starts and ends the underlying transaction automatically. | ||
|
||
**However**, because `@nestjs-cls/transactional` has no control over the propagation of the `session` instance via Mongoose's `AsyncLocalStorage`, | ||
there is **no implicit support for opting out of an ongoing transaction** via `TransactionHost#withoutTransaction` (or analogously the [`Propagation.NotSupported`](./index.md#transaction-propagation) mode). | ||
|
||
To opt out of an ongoing transaction, you have to explicitly pass `null` to the `session` option when calling the query. Alternatively, you can explicitly pass in the value of `TransactionHost#tx` if the query should support both transactional and non-transactional mode and you want to control it using `Propagation`. |
5 changes: 5 additions & 0 deletions
5
packages/transactional-adapters/transactional-adapter-mongoose/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 | ||
|
||
Mongoose adapter for the `@nestjs-cls/transactional` plugin. | ||
|
||
### ➡️ [Go to the documentation website](https://papooch.github.io/nestjs-cls/plugins/available-plugins/transactional/mongoose-adapter) 📖 |
17 changes: 17 additions & 0 deletions
17
packages/transactional-adapters/transactional-adapter-mongoose/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-mongoose/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-mongoose", | ||
"version": "1.0.0", | ||
"description": "A mongoose 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", | ||
"mongoose" | ||
], | ||
"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", | ||
"mongoose": "> 8", | ||
"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-memory-server": "^9.4.0", | ||
"mongoose": "^8.4.4", | ||
"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-mongoose/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-mongoose'; |
53 changes: 53 additions & 0 deletions
53
...ctional-adapters/transactional-adapter-mongoose/src/lib/transactional-adapter-mongoose.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,53 @@ | ||
import { TransactionalAdapter } from '@nestjs-cls/transactional'; | ||
import { ClientSession, Connection } from 'mongoose'; | ||
|
||
type MongooseTransactionOptions = Parameters<Connection['transaction']>[1]; | ||
|
||
export interface MongoDBTransactionalAdapterOptions { | ||
/** | ||
* The injection token for the mongoose Connection instance. | ||
*/ | ||
mongooseConnectionToken: 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<MongooseTransactionOptions>; | ||
} | ||
|
||
export class TransactionalAdapterMongoose | ||
implements | ||
TransactionalAdapter< | ||
Connection, | ||
ClientSession | null, | ||
MongooseTransactionOptions | ||
> | ||
{ | ||
connectionToken: any; | ||
|
||
defaultTxOptions?: Partial<MongooseTransactionOptions>; | ||
|
||
constructor(options: MongoDBTransactionalAdapterOptions) { | ||
this.connectionToken = options.mongooseConnectionToken; | ||
this.defaultTxOptions = options.defaultTxOptions; | ||
} | ||
|
||
supportsTransactionProxy = false; | ||
|
||
optionsFactory(connection: Connection) { | ||
return { | ||
wrapWithTransaction: async ( | ||
options: MongooseTransactionOptions, | ||
fn: (...args: any[]) => Promise<any>, | ||
setTx: (tx?: ClientSession) => void, | ||
) => { | ||
return connection.transaction((session) => { | ||
setTx(session); | ||
return fn(); | ||
}, options); | ||
}, | ||
getFallbackInstance: () => null, | ||
}; | ||
} | ||
} |
Oops, something went wrong.