@tsrt/typeorm-transactions
Version:
TypeORM convenient transactions management
258 lines (186 loc) • 9.57 kB
Markdown
# TsRT: TypeORM Transactions
[](https://www.npmjs.com/package/@tsrt/typeorm-transactions) [](https://github.com/tsReusableTools/tsrt/blob/master/LICENSE) [](https://www.npmjs.com/package/@tsrt/typeorm-transactions) [](https://www.npmjs.com/package/@tsrt/typeorm-transactions)
Convenient transactions support for great [TypeORM](https://github.com/typeorm/typeorm).
### The most basic example:
[Refer to full example for more info](#full-example)
```ts
import { Transaction } from '/typeorm-transactions';
async function makeStuffInTransaction(): Promise<any> {
const t = new Transaction({ ... });
await t.begin();
try {
// await transaction.manager.createQueryBuilder()...
// Or someRepository.create(...)
await t.commit();
} catch (err) {
await t.rollback(err);
}
}
```
### Preconditions
1. [CLS Namespace should be initialized before transactions usage](#init-cls-namespace).
2. [Everything should be wrapped into created CLS Namespace](#wrap-into-cls-namespace).
3. [Your repositories should (one of)](#repositories):
- extend __BaseRepository__ from `/typeorm-transactions` (annotated w/ TypeORM's `EntityRepository`).
- extend TypeORM's __Repository__ (annotated w/ TypeORM's `EntityRepository`) and before usage `patchTypeOrmRepository` should be called.
- any class w/ __`manager` (TypeORM's `EntityManager`) property__ and before usage `patchTypeOrmRepository` should be called.
- any class w/ TypeORM's `EntityManager` as first argument in constructor and before usage `patchTypeOrmRepository` should be called (or extend __BaseRepository__).
### Init CLS Namespace
```ts
// index.ts
import { createTransactionsNamespace, patchTypeOrmRepository } from '/typeorm-transactions';
createTransactionsNamespace(); // This one should be called before any transactions usage
patchTypeOrmRepository(); // This one is only necessary if repositories extends native TypeORM's Repository.
```
### Wrap into CLS Namespace
```ts
import { bindTransactionsNamespace, execInTransactionsNamespace } from '/typeorm-transactions';
// Wrap only function:
execInTransactionsNamespace(/* Any async code which uses transactions via CLS from this package */)
// Example for Express:
const app = express();
app.use(bindTransactionsNamespace);
// If using only `Transactional` decorator - no need to init namespace first.
class Service {
()
public async doStuff(): Promise<any> {
// do stuff inside transaction
}
}
```
__!!! Note__, that popular [express-session](https://www.npmjs.com/package/express-session) package can lead to [context loss](https://github.com/othiym23/node-continuation-local-storage/issues/29).
To prevent that try (one of):
1. bind namespace to express _AFTER_ calling `express-session` middleware.
2. call `express-session` middleware and bind `next` function to namespace.
Example:
```ts
import express from 'express';
import expressSession from 'express-session';
import { createTransactionsNamespace, bindTransactionsNamespace } from '/typeorm-transactions';;
const ns = createTransactionsNamespace();
const app = express();
// 1. bind namespace to express _AFTER_ calling `express-session` middleware.
app.use(expressSession({ ... }));
app.use(bindTransactionsNamespace);
// 2. call `express-session` middleware and bind `next` function to namespace.
app.use((req, res, next) => expressSession({ ... })(req, res, ns.bind(next)));
```
### Repositories
##### 1. Extending `BaseRepository` from `/typeorm-transactions`:
```ts
// repository.ts
import { BaseRepository } from '/typeorm-transactions';
import { EntityRepository } from 'typeorm';
EntityRepository(SomeEntity)
export class SomeBaseRepository extends BaseRepository { /* ... */ }
// somewhereElse.ts
import { getConnection } from 'typeorm';
import { SomeBaseRepository } from 'path/to/repository.ts';
getConnection(/* ... */).getCustomRepoistory(SomeBaseRepository).create(/* ... */);
```
##### 2. Extending TypeORM's native `Repository` and patching it w/ `patchTypeOrmRepository`:
```ts
// index.ts
import { patchTypeOrmRepository } from '/typeorm-transactions';
patchTypeOrmRepository(); // This on shoud be called somewhere just after `createTransactionsNamespace()`.
// repository.ts
import { Repository, EntityRepository } from 'typeorm';
EntityRepository(SecondEntity)
export class SomeRepository extends Repository { /* ... */ }
// somewhereElse.ts
import { getConnection } from 'typeorm';
import { SomeRepository } from 'path/to/repository.ts';
getConnection(/* ... */).getCustomRepoistory(SomeRepository).create(/* ... */);
```
##### 3. Creating __any__ repository from scratch and call `patchTypeOrmRepository`:
```ts
// repository.ts
import { patchTypeOrmRepository } from '/typeorm-transactions';
import { Repository, EntityManager } from 'typeorm';
export class SomeRepository {
public readonly manager: EntityManager;
/* ... */
}
patchTypeOrmRepository(SomeRepository, { /* ... connection options ... */ });
// somewhereElse.ts
import { SomeRepository } from 'path/to/repository.ts';
new SomeRepository().create(/* ... */);
```
##### 4. Creating __any__ repository w/ `EntityManager` as first constructor argument:
```ts
import { patchTypeOrmRepository } from '/typeorm-transactions';
import { Repository, EntityManager, getManager() } from 'typeorm';
export class SomeRepository {
constructor(public readonly manager: EntityManager;) {}
/* ... */
}
// Then
// 1. patchTypeOrmRepository(SomeRepository, { /* ... connection options ... */ });
// 2. Or extend BaseRepository
// 3. Also could be annotated w/ TypeORM's `EntityRepository`.
// somewhereElse.ts
import { SomeRepository } from 'path/to/repository.ts';
new SomeRepository(getManager()).create(/* ... */);
```
### Propagation
- __REQUIRED__ (default) - supports existing transaction if it is or creates new if there is no.
- __SEPARATE__ - creates new transaction even if there is already an existing transaction.
- __SUPPORT__ - supports only existing transaction and does not create new.
### APIs
1. __Transaction__ - constructor to create new single Transaction.
2. __TransactionManager__ - constructor to create TransactionManager for specific connection w/ default options. Has 3 methods:
- __createTransaction__ - creates new single Transaction w/ TransactionManager default options and connection.
- __transaction__ - creates and starts new single Transaction. Could be provided w/ callback to execute inside transaction which will _rollback automatically_ if no manual commit is called. _If no callback provided, commit and rollback should be done manually._
- __autoTransaction__ - same as __transaction__ method (_with callback case_), but will commit automatically if no error thrown.
3. __Transactional__ - method decorator (more convenient/declarative, but less flexible comparing to __TransactionManager__). Has 2 variants:
- __createTransactional__ - factory function for creating __Transactional__ decorator for specific connection w/ default options. (uses __TransactionManager__ under the hood).
- __Transactional__ - decorator itself w/ ability to provide any connection and other options.
### Full example
```ts
import { PrimaryGeneratedColumn, Column, Entity, Repository, getConnection, EntityRepository } from 'typeorm';
import { createTransactionsNamespace, bindTransactionsNamespace, patchTypeOrmRepository, TransactionManager } from '/typeorm-transactions';
createTransactionsNamespace();
patchTypeOrmRepository();
const tm = new TransactionManager();
const app = express();
app.use(bindTransactionsNamespace);
('Users')
class User {
()
public id: number;
()
public name: string;
}
(User)
class UsersRepository extends Repository {
public async createUser(body: IUserPayload): Promise<User> {
const user = this.manager.create(body);
return this.manager.save(User, user);
}
}
class UsersService {
public async createUsers(users: IUserPayload[]): Promise<User[]> {
return tm.autoTransaction(async () => {
const repository = getConnection().getCustomRepository(UsersRepository);
const promises = users.map((item) => repository.createUser(item));
return Promise.all(promises);
});
}
}
app.use('/test', async (req, res) => {
const users = await new UsersService().createUsers([
{ name: 'First User' },
{ name: 'Second User' },
]);
res.status(200).send(users);
});
app.listen(3333);
```
## Legacy API
Before __0.8.0__ there was ability to use Transactions without [cls-hooked](https://www.npmjs.com/package/cls-hooked)..
Still old API is available for usage under `/typeorm-transactions/dist/legacy`.
Most basic usage is same as in [this example](#the-most-basic-example), w/ only one difference - it is not necessary to initialise and use clsNamespace.
Legacy API instantiates separate repositories for each transaction.
For more docs please refer to [legacy docs](https://www.npmjs.com/package/@tsrt/typeorm-transactions/v/0.7.11#usage).
## License
This project is licensed under the terms of the [MIT license](https://github.com/tsReusableTools/tsrt/blob/master/LICENSE).