Mongoose adapter
Installation
- npm
- yarn
- pnpm
npm install @nestjs-cls/transactional-adapter-mongoose
yarn add @nestjs-cls/transactional-adapter-mongoose
pnpm add @nestjs-cls/transactional-adapter-mongoose
Registration
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
.
Due to how transactions work in MongoDB, and in turn in Mongoose, 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
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.
The TransactionalAdapterMongoose
does not support the "Transaction Proxy" feature, because proxying a null
value is not supported by the JavaScript Proxy.
Example
const userSchema = new Schema({
name: String,
email: String,
});
const User = mongoose.model('user', userSchema);
@Injectable()
class UserService {
constructor(private readonly userRepository: UserRepository) {}
@Transactional()
async runTransaction() {
// both methods are executed in the same transaction
const user = await this.userRepository.createUser('John');
const foundUser = await this.userRepository.getUserById(user._id);
assert(foundUser._id === user._id);
}
}
@Injectable()
class UserRepository {
constructor(
private readonly txHost: TransactionHost<TransactionalAdapterMongoose>,
) {}
async getUserById(id: ObjectId) {
// txHost.tx is typed as ClientSession
return await User.findById(id)
.session(this.txHost.tx);
}
async createUser(name: string) {
const user = new User({ name: name, email: `${name}@email.com` });
await user
.save({ session: this.txHost.tx });
return user;
}
}
Considerations
Using with built-in Mongoose AsyncLocalStorage support
Mongoose > 8.4 has a built-in support for propagating the session
via 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
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
.