diff --git a/docs/docs/06_plugins/01_available-plugins/01-transactional/05-typeorm-adapter.md b/docs/docs/06_plugins/01_available-plugins/01-transactional/05-typeorm-adapter.md index 9c633f9..eac49e9 100644 --- a/docs/docs/06_plugins/01_available-plugins/01-transactional/05-typeorm-adapter.md +++ b/docs/docs/06_plugins/01_available-plugins/01-transactional/05-typeorm-adapter.md @@ -1,7 +1,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# TypeOrm adapter +# TypeORM adapter ## Installation @@ -29,6 +29,14 @@ pnpm add @nestjs-cls/transactional-adapter-typeorm +:::important + +Please note that due to the deliberate choice to _not_ monkey-patch any underlying library, the only way to propagate the transaction using this adapter is by using the `EntityManager`. There is no transactional support for working directly with repositories without getting them through the (transactional) `EntityManager` + +For a more fully-featured solution for TypeORM, see the [`typeorm-transactional`](https://github.com/Aliheym/typeorm-transactional) community package. + +::: + ## Registration ```ts @@ -48,3 +56,61 @@ ClsModule.forRoot({ ], }), ``` + +:::note + +When using with `@nestjs/typeorm`, the data source token needs to be retrieved with the `getDataSourceToken` function, that can be optionally provided with a custom connection name. + +```ts +import { getDataSourceToken } from '@nestjs/typeorm'; +// ... +dataSourceToken: getDataSourceToken(), +``` + +::: + +## Typing & usage + +The `tx` property on the `TransactionHost` is typed as [`EntityManager`](https://typeorm.io/working-with-entity-manager). + +## 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( + private readonly txHost: TransactionHost, + ) {} + + async getUserById(id: number) { + // highlight-start + // txHost.tx is typed as EntityManager + return await this.txHost.tx.getRepository(User).findOneBy({ id }); + // highlight-end + } + + async createUser(name: string, email: string) { + return await this.txHost.tx.getRepository(User).save({ + name, + email: `${name}@email.com`, + }); + } +} +``` diff --git a/packages/transactional-adapters/transactional-adapter-knex/test/docker-compose.yml b/packages/transactional-adapters/transactional-adapter-knex/test/docker-compose.yml deleted file mode 100644 index d28f3da..0000000 --- a/packages/transactional-adapters/transactional-adapter-knex/test/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - db: - image: postgres:15 - ports: - - 5432:5432 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - \ No newline at end of file diff --git a/packages/transactional-adapters/transactional-adapter-typeorm/CHANGES.md b/packages/transactional-adapters/transactional-adapter-typeorm/CHANGES.md deleted file mode 100644 index 825c32f..0000000 --- a/packages/transactional-adapters/transactional-adapter-typeorm/CHANGES.md +++ /dev/null @@ -1 +0,0 @@ -# Changelog diff --git a/packages/transactional-adapters/transactional-adapter-typeorm/package.json b/packages/transactional-adapters/transactional-adapter-typeorm/package.json index e74a5d0..7025628 100644 --- a/packages/transactional-adapters/transactional-adapter-typeorm/package.json +++ b/packages/transactional-adapters/transactional-adapter-typeorm/package.json @@ -1,6 +1,6 @@ { "name": "@nestjs-cls/transactional-adapter-typeorm", - "version": "1.2.0", + "version": "1.0.0", "description": "A typeorm adapter for @nestjs-cls/transactional", "author": "Giosuè Delgado S.Z. - Relybytes Srl ", "license": "MIT", @@ -64,4 +64,4 @@ "typeorm": "0.3.20", "typescript": "5.0" } -} \ No newline at end of file +} diff --git a/packages/transactional-adapters/transactional-adapter-typeorm/src/lib/transactional-adapter-typeorm.ts b/packages/transactional-adapters/transactional-adapter-typeorm/src/lib/transactional-adapter-typeorm.ts index 01a4901..abcf3ac 100644 --- a/packages/transactional-adapters/transactional-adapter-typeorm/src/lib/transactional-adapter-typeorm.ts +++ b/packages/transactional-adapters/transactional-adapter-typeorm/src/lib/transactional-adapter-typeorm.ts @@ -1,9 +1,17 @@ -import { DataSource, EntityManager } from 'typeorm'; -import { - TypeOrmTransactionOptions, - TypeOrmTransactionalAdapterOptions, -} from './transactional-options'; import { TransactionalAdapter } from '@nestjs-cls/transactional'; +import { DataSource, EntityManager } from 'typeorm'; +import type { IsolationLevel } from 'typeorm/driver/types/IsolationLevel'; + +export interface TypeOrmTransactionalAdapterOptions { + /** + * The injection token for the TypeORM DataSource instance. + */ + dataSourceToken: any; +} + +export interface TypeOrmTransactionOptions { + isolationLevel: IsolationLevel; +} export class TransactionalAdapterTypeOrm implements diff --git a/packages/transactional-adapters/transactional-adapter-typeorm/src/lib/transactional-options.ts b/packages/transactional-adapters/transactional-adapter-typeorm/src/lib/transactional-options.ts deleted file mode 100644 index db62b87..0000000 --- a/packages/transactional-adapters/transactional-adapter-typeorm/src/lib/transactional-options.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IsolationLevel } from 'typeorm/driver/types/IsolationLevel'; - -export interface TypeOrmTransactionalAdapterOptions { - /** - * The injection token for the typeOrm instance. - */ - dataSourceToken: any; -} - -export interface TypeOrmTransactionOptions { - isolationLevel: IsolationLevel; -} diff --git a/packages/transactional-adapters/transactional-adapter-typeorm/test/docker-compose.yml b/packages/transactional-adapters/transactional-adapter-typeorm/test/docker-compose.yml index c4cacdd..85414b2 100644 --- a/packages/transactional-adapters/transactional-adapter-typeorm/test/docker-compose.yml +++ b/packages/transactional-adapters/transactional-adapter-typeorm/test/docker-compose.yml @@ -2,7 +2,7 @@ services: db: image: postgres:15 ports: - - 5444:5432 + - 5446:5432 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres diff --git a/packages/transactional-adapters/transactional-adapter-typeorm/test/transactional-adapter-typeorm.spec.ts b/packages/transactional-adapters/transactional-adapter-typeorm/test/transactional-adapter-typeorm.spec.ts index 653b195..e84d924 100644 --- a/packages/transactional-adapters/transactional-adapter-typeorm/test/transactional-adapter-typeorm.spec.ts +++ b/packages/transactional-adapters/transactional-adapter-typeorm/test/transactional-adapter-typeorm.spec.ts @@ -1,5 +1,7 @@ import { ClsPluginTransactional, + InjectTransaction, + Transaction, Transactional, TransactionHost, } from '@nestjs-cls/transactional'; @@ -25,7 +27,7 @@ class User { const dataSource = new DataSource({ type: 'postgres', host: 'localhost', - port: 5444, + port: 5446, username: 'postgres', password: 'postgres', database: 'postgres', @@ -36,17 +38,16 @@ const dataSource = new DataSource({ @Injectable() class UserRepository { constructor( - private readonly transactionHost: TransactionHost, + @InjectTransaction() + private readonly tx: Transaction, ) {} async getUserById(id: number) { - return await this.transactionHost.tx - .getRepository(User) - .findOneBy({ id }); + return await this.tx.getRepository(User).findOneBy({ id }); } async createUser(name: string) { - return await this.transactionHost.tx.getRepository(User).save({ + return await this.tx.getRepository(User).save({ name, email: `${name}@email.com`, }); @@ -58,8 +59,7 @@ class UserService { constructor( private readonly userRepository: UserRepository, private readonly transactionHost: TransactionHost, - - private datasource: DataSource, + private readonly dataSource: DataSource, ) {} @Transactional() @@ -74,7 +74,7 @@ class UserService { }) async transactionWithDecoratorWithOptions() { const r1 = await this.userRepository.createUser('James'); - const r2 = await this.datasource + const r2 = await this.dataSource .getRepository(User) .createQueryBuilder('user') .where('user.id = :id', { id: r1.id }) @@ -88,7 +88,7 @@ class UserService { { isolationLevel: 'SERIALIZABLE' }, async () => { const r1 = await this.userRepository.createUser('Joe'); - const r2 = await this.datasource + const r2 = await this.dataSource .getRepository(User) .createQueryBuilder('user') .where('user.id = :id', { id: r1.id }) @@ -129,6 +129,7 @@ class TypeOrmModule {} adapter: new TransactionalAdapterTypeOrm({ dataSourceToken: DataSource, }), + enableTransactionProxy: true, }), ], }),