{"id":13990825,"url":"https://github.com/odavid/typeorm-transactional-cls-hooked","last_synced_at":"2025-05-15T20:03:46.953Z","repository":{"id":34279927,"uuid":"149818472","full_name":"odavid/typeorm-transactional-cls-hooked","owner":"odavid","description":"A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation and Sequelize CLS","archived":false,"fork":false,"pushed_at":"2023-01-06T16:41:55.000Z","size":376,"stargazers_count":523,"open_issues_count":32,"forks_count":93,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-05-15T10:09:55.525Z","etag":null,"topics":["decorator","nestjs","transaction","typeorm","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/odavid.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-21T21:12:29.000Z","updated_at":"2025-05-12T18:40:42.000Z","dependencies_parsed_at":"2023-01-15T05:51:23.809Z","dependency_job_id":null,"html_url":"https://github.com/odavid/typeorm-transactional-cls-hooked","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/odavid%2Ftypeorm-transactional-cls-hooked","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/odavid%2Ftypeorm-transactional-cls-hooked/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/odavid%2Ftypeorm-transactional-cls-hooked/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/odavid%2Ftypeorm-transactional-cls-hooked/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/odavid","download_url":"https://codeload.github.com/odavid/typeorm-transactional-cls-hooked/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254414493,"owners_count":22067271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decorator","nestjs","transaction","typeorm","typescript"],"created_at":"2024-08-09T13:03:20.248Z","updated_at":"2025-05-15T20:03:45.332Z","avatar_url":"https://github.com/odavid.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# typeorm-transactional-cls-hooked\n[![npm version](http://img.shields.io/npm/v/typeorm-transactional-cls-hooked.svg?style=flat)](https://npmjs.org/package/typeorm-transactional-cls-hooked \"View this project on npm\")\n\n\nA `Transactional` Method Decorator for [typeorm](http://typeorm.io/) that uses [cls-hooked](https://www.npmjs.com/package/cls-hooked) to handle and propagate transactions between different repositories and service methods.\n\nInspired by [Spring Transactional](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html) Annotation and [Sequelize CLS](http://docs.sequelizejs.com/manual/tutorial/transactions.html)\n\nSee [Changelog](CHANGELOG.md)\n\n## Installation\n\n```shell\nnpm install --save typeorm-transactional-cls-hooked\n## Needed dependencies\nnpm install --save typeorm reflect-metadata\n```\n\nOr\n\n```shell\nyarn add typeorm-transactional-cls-hooked\n## Needed dependencies\nyarn add typeorm reflect-metadata\n```\n\n\u003e **Note**: You will need to import `reflect-metadata` somewhere in the global place of your app - https://github.com/typeorm/typeorm#installation\n\n## Initialization\n\nIn order to use it, you will first need to initialize the cls-hooked namespace before your application is started\n\n```typescript\nimport { initializeTransactionalContext } from 'typeorm-transactional-cls-hooked';\n\ninitializeTransactionalContext() // Initialize cls-hooked\n...\napp = express()\n...\n```\n\n## BaseRepository\n\nSince this is an external library, all your typeorm repositories will need to be a [custom repository](https://github.com/typeorm/typeorm/blob/master/docs/custom-repository.md) extending either the `BaseRepository` (when using TypeORM's [`Entity`](https://github.com/typeorm/typeorm/blob/master/docs/entities.md)) or the `BaseTreeRepository` class (when using TypeORM's [`TreeEntity`](https://github.com/typeorm/typeorm/blob/master/docs/tree-entities.md)).\n\n```typescript\n// Post.entity.ts\n@Entity()\nexport class Post{\n  @PrimaryGeneratedColumn()\n  id: number\n\n  @Column\n  message: string\n  ...\n}\n\n// Post.repository.ts\nimport { EntityRepository } from 'typeorm';\nimport { BaseRepository } from 'typeorm-transactional-cls-hooked';\n\n@EntityRepository(Post)\nexport class PostRepository extends BaseRepository\u003cPost\u003e {}\n```\n\nThe only purpose of the `BaseRepository` class is to make sure the `manager` property of the repository will always be the right one. In cases where inheritance is not possible, you can always [Patch the Repository/TreeRepository](#patching-typeorm-repository) to enable the same functionality as the `BaseRepository`\n\n\n### Patching TypeORM Repository\nSometimes there is a need to keep using the [TypeORM Repository](https://github.com/typeorm/typeorm/blob/master/src/repository/Repository.ts) instead of using the `BaseRepository`.\nFor this cases, you will need to *\"mixin/patch\"* the original `Repository` with the `BaseRepository`.\nBy doing so, you will be able to use the original `Repository` and not change the code or use `BaseRepository`.\n\u003e This method was taken from https://gist.github.com/Diluka/87efbd9169cae96a012a43d1e5695667 (Thanks @Diluka)\n\nIn order to do that, the following should be done during initialization:\n\n```typescript\nimport { initializeTransactionalContext, patchTypeORMRepositoryWithBaseRepository } from 'typeorm-transactional-cls-hooked';\n\ninitializeTransactionalContext() // Initialize cls-hooked\npatchTypeORMRepositoryWithBaseRepository() // patch Repository with BaseRepository.\n```\n\nIf there is a need to keep using the TypeORM [`TreeRepository`](https://github.com/typeorm/typeorm/blob/master/docs/tree-entities.md#working-with-tree-entities) instead of using `BaseTreeRepository`, use `patchTypeORMTreeRepositoryWithBaseTreeRepository`.\n\n\n---\n**IMPORTANT NOTE**\n\nCalling [initializeTransactionalContext](#initialization) and [patchTypeORMRepositoryWithBaseRepository](#patching-typeorm-repository) must happen BEFORE any application context is initialized!\n\n---\n\n\n\n## Using Transactional Decorator\n\n- Every service method that needs to be transactional, need to use the `@Transactional()` decorator\n- The decorator can take a `connectionName` as argument (by default it is `default`)\n  - In some cases, where the connectionName should be dynamically evaluated, the value of connectionName can be a function that returns a string.\n- The decorator can take an optional `propagation` as argument to define the [propagation behaviour](#transaction-propagation)\n- The decorator can take an optional `isolationLevel` as argument to define the [isolation level](#isolation-levels) (by default it will use your database driver's default isolation level.)\n\n```typescript\nexport class PostService {\n  constructor(readonly repository: PostRepository)\n\n  @Transactional() // Will open a transaction if one doesn't already exist\n  async createPost(id, message): Promise\u003cPost\u003e {\n    const post = this.repository.create({ id, message })\n    return this.repository.save(post)\n  }\n}\n```\n\n## Transaction Propagation\n\nThe following propagation options can be specified:\n\n- `MANDATORY` - Support a current transaction, throw an exception if none exists.\n- `NESTED` - Execute within a nested transaction if a current transaction exists, behave like `REQUIRED` else.\n- `NEVER` - Execute non-transactionally, throw an exception if a transaction exists.\n- `NOT_SUPPORTED` - Execute non-transactionally, suspend the current transaction if one exists.\n- `REQUIRED` (default behaviour) - Support a current transaction, create a new one if none exists.\n- `REQUIRES_NEW` - Create a new transaction, and suspend the current transaction if one exists.\n- `SUPPORTS` - Support a current transaction, execute non-transactionally if none exists.\n\n## Isolation Levels\n\nThe following isolation level options can be specified:\n\n- `READ_UNCOMMITTED` - A constant indicating that dirty reads, non-repeatable reads and phantom reads can occur.\n- `READ_COMMITTED` - A constant indicating that dirty reads are prevented; non-repeatable reads and phantom reads can occur.\n- `REPEATABLE_READ` - A constant indicating that dirty reads and non-repeatable reads are prevented; phantom reads can occur.\n- `SERIALIZABLE` = A constant indicating that dirty reads, non-repeatable reads and phantom reads are prevented.\n\n**NOTE**: If a transaction already exist and a method is decorated with `@Transactional` and `propagation` *does not equal* to `REQUIRES_NEW`, then the declared `isolationLevel` value will *not* be taken into account.\n\n## Hooks\n\nBecause you hand over control of the transaction creation to this library, there is no way for you to know whether or not the current transaction was sucessfully persisted to the database.\n\nTo circumvent that, we expose three helper methods that allow you to hook into the transaction lifecycle and take appropriate action after a commit/rollback.\n\n- `runOnTransactionCommit(cb)` takes a callback to be executed after the current transaction was sucessfully committed\n- `runOnTransactionRollback(cb)` takes a callback to be executed after the current transaction rolls back. The callback gets the error that initiated the roolback as a parameter.\n- `runOnTransactionComplete(cb)` takes a callback to be executed at the completion of the current transactional context. If there was an error, it gets passed as an argument.\n\n\n\n```typescript\nexport class PostService {\n    constructor(readonly repository: PostRepository, readonly events: EventService) {}\n\n    @Transactional()\n    async createPost(id, message): Promise\u003cPost\u003e {\n        const post = this.repository.create({ id, message })\n        const result = await this.repository.save(post)\n        runOnTransactionCommit(() =\u003e this.events.emit('post created'))\n        return result\n    }\n}\n```\n\n## Unit Test Mocking\n`@Transactional` and `BaseRepository` can be mocked to prevent running any of the transactional code in unit tests.\n\nThis can be accomplished in Jest with:\n\n```typescript\njest.mock('typeorm-transactional-cls-hooked', () =\u003e ({\n  Transactional: () =\u003e () =\u003e ({}),\n  BaseRepository: class {},\n}));\n```\n\nRepositories, services, etc. can be mocked as usual.\n\n## Logging / Debug\nThe `Transactional` uses the [Typeorm Connection logger](https://github.com/typeorm/typeorm/blob/master/docs/logging.md) to emit [`log` messages](https://github.com/typeorm/typeorm/blob/master/docs/logging.md#logging-options).\n\nIn order to enable logs, you should set `logging: [\"log\"]` or `logging: [\"all\"]` to your typeorm logging configuration.\n\nThe Transactional log message structure looks as follows:\n\n```\nTransactional@UNIQ_ID|CONNECTION_NAME|METHOD_NAME|ISOLATION|PROPAGATION - MESSAGE\n```\n* UNIQ_ID - a timestamp taken at the begining of the Transactional call\n* CONNECTION_NAME - The typeorm connection name passed to the Transactional decorator\n* METHOD_NAME - The decorated method in action\n* ISOLATION - The [Isolation Level](#isolation-levels) passed to the Transactional decorator\n* PROPAGATION - The [Propagation](#transaction-propagation) value passed to the Transactional decorator\n\nDuring [initialization](#initialization) and [patching repositories](#patching-typeorm-repository), the [Typeorm Connection logger](https://github.com/typeorm/typeorm/blob/master/docs/logging.md) is not available yet.\nFor this reason, the `console.log()` is being used, but only if `TRANSACTIONAL_CONSOLE_DEBUG` environment variable is defined.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fodavid%2Ftypeorm-transactional-cls-hooked","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fodavid%2Ftypeorm-transactional-cls-hooked","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fodavid%2Ftypeorm-transactional-cls-hooked/lists"}