diff --git a/packages/transactional-adapters/transactional-adapter-mikro-orm/CHANGES.md b/packages/transactional-adapters/transactional-adapter-mikro-orm/CHANGES.md
new file mode 100644
index 00000000..70825b65
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/CHANGES.md
@@ -0,0 +1,9 @@
+# Changelog
+
+
+
+## [1.0.0](https://github.com/Papooch/nestjs-cls/compare/@nestjs-cls/transactional-adapter-mikro-orm@1.0.0...@nestjs-cls/transactional-adapter-mikro-orm@1.1.0) "@nestjs-cls/transactional-adapter-mikro-orm" (2024-11-15)
+
+### Features
+
+* **transactional-adapter-mikro-orm**: initial version
\ No newline at end of file
diff --git a/packages/transactional-adapters/transactional-adapter-mikro-orm/README.md b/packages/transactional-adapters/transactional-adapter-mikro-orm/README.md
new file mode 100644
index 00000000..c77d2608
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/README.md
@@ -0,0 +1,5 @@
+# @nestjs-cls/transactional-adapter-mikro-orm
+
+`mikro-orm` adapter for the `@nestjs-cls/transactional` plugin.
+
+### ➡️ [Go to the documentation website](https://papooch.github.io/nestjs-cls/plugins/available-plugins/transactional) 📖
diff --git a/packages/transactional-adapters/transactional-adapter-mikro-orm/jest.config.js b/packages/transactional-adapters/transactional-adapter-mikro-orm/jest.config.js
new file mode 100644
index 00000000..14ae88e3
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/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-mikro-orm/package.json b/packages/transactional-adapters/transactional-adapter-mikro-orm/package.json
new file mode 100644
index 00000000..1847ec9c
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/package.json
@@ -0,0 +1,69 @@
+{
+ "name": "@nestjs-cls/transactional-adapter-mikro-orm",
+ "version": "1.0.0",
+ "description": "A mikro-orm adapter for @nestjs-cls/transactional",
+ "author": "whileloop99 ",
+ "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",
+ "mikro-orm"
+ ],
+ "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": {
+ "@mikro-orm/core": "^6.4.0",
+ "@mikro-orm/postgresql": "^6.4.0",
+ "@nestjs-cls/transactional": "workspace:^2.4.2",
+ "nestjs-cls": "workspace:^4.4.1"
+ },
+ "devDependencies": {
+ "@mikro-orm/core": "^6.4.0",
+ "@mikro-orm/postgresql": "^6.4.0",
+ "@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",
+ "reflect-metadata": "^0.1.13",
+ "rimraf": "^3.0.2",
+ "rxjs": "^7.5.5",
+ "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-mikro-orm/src/index.ts b/packages/transactional-adapters/transactional-adapter-mikro-orm/src/index.ts
new file mode 100644
index 00000000..f7a2fc9a
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/src/index.ts
@@ -0,0 +1 @@
+export * from './lib/transactional-adapter-mikro-orm';
diff --git a/packages/transactional-adapters/transactional-adapter-mikro-orm/src/lib/transactional-adapter-mikro-orm.ts b/packages/transactional-adapters/transactional-adapter-mikro-orm/src/lib/transactional-adapter-mikro-orm.ts
new file mode 100644
index 00000000..8696abcd
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/src/lib/transactional-adapter-mikro-orm.ts
@@ -0,0 +1,58 @@
+import { TransactionalAdapter } from '@nestjs-cls/transactional';
+import { MikroORM, EntityManager, IsolationLevel, IDatabaseDriver, Connection } from '@mikro-orm/core';
+
+export interface MikroOrmTransactionalAdapterOptions {
+ /**
+ * The injection token for the MikroORM instance.
+ */
+ dataSourceToken: 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 interface MikroOrmTransactionOptions {
+ isolationLevel?: IsolationLevel;
+}
+
+export class TransactionalAdapterMikroOrm
+ implements
+ TransactionalAdapter<
+ MikroORM>,
+ EntityManager>,
+ MikroOrmTransactionOptions
+ > {
+ connectionToken: any;
+ defaultTxOptions?: Partial;
+
+ constructor(options: MikroOrmTransactionalAdapterOptions) {
+ this.connectionToken = options.dataSourceToken;
+ this.defaultTxOptions = options.defaultTxOptions;
+ }
+
+ optionsFactory = (orm: MikroORM>) => ({
+ wrapWithTransaction: async (
+ options: MikroOrmTransactionOptions,
+ fn: (...args: any[]) => Promise,
+ setClient: (client?: EntityManager>) => void,
+ ) => {
+ const em = orm.em.fork(); // Create a forked EntityManager for the transaction
+ await em.begin({
+ isolationLevel: options?.isolationLevel || this.defaultTxOptions?.isolationLevel,
+ });
+ try {
+ setClient(em);
+ const result = await fn();
+ await em.commit();
+ return result;
+ } catch (error) {
+ await em.rollback();
+ throw error;
+ }
+ },
+ getFallbackInstance: () => orm.em,
+ });
+}
diff --git a/packages/transactional-adapters/transactional-adapter-mikro-orm/test/docker-compose.yml b/packages/transactional-adapters/transactional-adapter-mikro-orm/test/docker-compose.yml
new file mode 100644
index 00000000..85414b22
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/test/docker-compose.yml
@@ -0,0 +1,14 @@
+services:
+ db:
+ image: postgres:15
+ ports:
+ - 5446:5432
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+ POSTGRES_DB: postgres
+ healthcheck:
+ test: ['CMD-SHELL', 'pg_isready -U postgres']
+ interval: 1s
+ timeout: 1s
+ retries: 5
diff --git a/packages/transactional-adapters/transactional-adapter-mikro-orm/test/transactional-adapter-mikro-orm.spec.ts b/packages/transactional-adapters/transactional-adapter-mikro-orm/test/transactional-adapter-mikro-orm.spec.ts
new file mode 100644
index 00000000..16fa07a0
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/test/transactional-adapter-mikro-orm.spec.ts
@@ -0,0 +1,228 @@
+import {
+ ClsPluginTransactional,
+ InjectTransaction,
+ Transaction,
+ Transactional,
+ TransactionHost,
+} from '@nestjs-cls/transactional';
+import { Injectable, Module } from '@nestjs/common';
+import { Test, TestingModule } from '@nestjs/testing';
+import { ClsModule, UseCls } from 'nestjs-cls';
+import { execSync } from 'node:child_process';
+import { MikroORM, Entity, PrimaryKey, Property, IsolationLevel } from '@mikro-orm/core';
+import { TransactionalAdapterMikroOrm } from '../src';
+import { PostgreSqlDriver } from '@mikro-orm/postgresql';
+
+@Entity()
+class User {
+ @PrimaryKey()
+ id!: number;
+
+ @Property()
+ name?: string;
+
+ @Property()
+ email?: string;
+}
+
+const mikroOrm = MikroORM.init({
+ dbName: 'postgres',
+ user: 'postgres',
+ password: 'postgres',
+ host: 'localhost',
+ port: 5446,
+ entities: [User],
+ driver: PostgreSqlDriver,
+ allowGlobalContext: true,
+});
+
+
+@Injectable()
+class UserRepository {
+ constructor(
+ @InjectTransaction()
+ private readonly tx: Transaction,
+ ) { }
+
+ async getUserById(id: number) {
+ // Use the transactional EntityManager directly
+ return await this.tx.findOne(User, { id });
+ }
+
+ async createUser(name: string) {
+ // Use the transactional EntityManager directly
+ const user = this.tx.create(User, {
+ name,
+ email: `${name}@email.com`,
+ });
+ await this.tx.persistAndFlush(user); // Save the user entity
+ return user;
+ }
+}
+
+
+@Injectable()
+class UserService {
+ constructor(
+ private readonly userRepository: UserRepository,
+ private readonly transactionHost: TransactionHost,
+ private readonly orm: MikroORM,
+ ) { }
+
+ @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({
+ isolationLevel: IsolationLevel.SERIALIZABLE,
+ })
+ async transactionWithDecoratorWithOptions() {
+ const r1 = await this.userRepository.createUser('James');
+ const r2 = await this.orm.em.findOne(User, { id: r1.id });
+ const r3 = await this.userRepository.getUserById(r1.id);
+ return { r1, r2, r3 };
+ }
+
+ async transactionWithFunctionWrapper() {
+ return this.transactionHost.withTransaction(
+ { isolationLevel: IsolationLevel.SERIALIZABLE },
+ async () => {
+ const r1 = await this.userRepository.createUser('Joe');
+ const r2 = await this.orm.em.findOne(User, { 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');
+ }
+}
+
+@Module({
+ providers: [
+ {
+ provide: MikroORM,
+ useValue: mikroOrm,
+ },
+ UserRepository,
+ UserService,
+ ],
+ exports: [MikroORM],
+})
+class MikroOrmModule { }
+
+@Module({
+ imports: [
+ MikroOrmModule,
+ ClsModule.forRoot({
+ plugins: [
+ new ClsPluginTransactional({
+ imports: [MikroOrmModule],
+ adapter: new TransactionalAdapterMikroOrm({
+ dataSourceToken: MikroORM,
+ }),
+ enableTransactionProxy: true,
+ }),
+ ],
+ }),
+ ],
+ providers: [UserRepository, UserService],
+})
+class AppModule { }
+
+describe('TransactionalAdapterMikroOrm', () => {
+ let module: TestingModule;
+ let callingService: UserService;
+
+ beforeAll(async () => {
+ execSync(
+ 'docker compose -f test/docker-compose.yml up -d --quiet-pull --wait',
+ {
+ stdio: 'inherit',
+ cwd: process.cwd(),
+ },
+ );
+ const orm = await mikroOrm;
+ await orm.getSchemaGenerator().dropSchema();
+ await orm.getSchemaGenerator().createSchema();
+ }, 60_000);
+
+ beforeEach(async () => {
+ module = await Test.createTestingModule({
+ imports: [AppModule],
+ }).compile();
+ await module.init();
+ callingService = module.get(UserService);
+ });
+
+ afterAll(async () => {
+ const orm = await mikroOrm;
+ await orm.close(true);
+ execSync('docker compose -f test/docker-compose.yml down', {
+ stdio: 'inherit',
+ });
+ }, 60_000);
+
+ describe('Transactions', () => {
+ it('should work without an active transaction', async () => {
+ const { r1, r2 } = await callingService.withoutTransaction();
+ expect(r1).toEqual(r2);
+ const orm = await mikroOrm;
+ const users = await orm.em.find(User, {});
+ 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 orm = await mikroOrm;
+ const users = await orm.em.find(User, {});
+ 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 orm = await mikroOrm;
+ const users = await orm.em.find(User, {});
+ 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 orm = await mikroOrm;
+ const users = await orm.em.find(User, {});
+ expect(users).toEqual(expect.arrayContaining([r1]));
+ });
+
+ it('should rollback a transaction on error', async () => {
+ await expect(
+ callingService.transactionWithDecoratorError(),
+ ).rejects.toThrow(new Error('Rollback'));
+ const orm = await mikroOrm;
+ const users = await orm.em.find(User, {});
+ expect(users).toEqual(
+ expect.not.arrayContaining([{ name: 'Nobody' }]),
+ );
+ });
+ });
+});
diff --git a/packages/transactional-adapters/transactional-adapter-mikro-orm/tsconfig.json b/packages/transactional-adapters/transactional-adapter-mikro-orm/tsconfig.json
new file mode 100644
index 00000000..bbc28fb3
--- /dev/null
+++ b/packages/transactional-adapters/transactional-adapter-mikro-orm/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "."
+ },
+ "include": ["src/**/*.ts", "test/**/*.ts"]
+}