diff --git a/CHANGELOG.md b/CHANGELOG.md index afad1d3..1e29b98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ # 0.2.0 - Feature: add support for using `DataSource` and `EntityManager` with transactional [#2](https://github.com/Aliheym/typeorm-transactional/issues/2) + +# 0.3.0 + +- Feature: add maximum hook handlers options [#13](https://github.com/Aliheym/typeorm-transactional/issues/13) +- Fix: improve checks to support newest TypeORM versions [#15](https://github.com/Aliheym/typeorm-transactional/issues/9) diff --git a/README.md b/README.md index 199ea77..1a13d90 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A `Transactional` Method Decorator for [typeorm](http://typeorm.io/) that uses [ See [Changelog](#CHANGELOG.md) - [Typeorm Transactional](#typeorm-transactional) + - [It's a fork of typeorm-transactional-cls-hooked for new versions of TypeORM.](#its-a-fork-of-typeorm-transactional-cls-hooked-for-new-versions-of-typeorm) - [Installation](#installation) - [Initialization](#initialization) - [Usage](#usage) @@ -20,15 +21,15 @@ See [Changelog](#CHANGELOG.md) - [Hooks](#hooks) - [Unit Test Mocking](#unit-test-mocking) - [API](#api) - - [Options](#options) - - [initializeTransactionalContext(): void](#initializetransactionalcontext-void) + - [Library Options](#library-options) + - [Transaction Options](#transaction-options) + - [initializeTransactionalContext(options): void](#initializetransactionalcontext-void) - [addTransactionalDataSource(input): DataSource](#addtransactionaldatasourceinput-datasource) - [runInTransaction(fn: Callback, options?: Options): Promise<...>](#runintransactionfn-callback-options-options-promise) - [wrapInTransaction(fn: Callback, options?: Options): WrappedFunction](#wrapintransactionfn-callback-options-options-wrappedfunction) - [runOnTransactionCommit(cb: Callback): void](#runontransactioncommitcb-callback-void) - [runOnTransactionRollback(cb: Callback): void](#runontransactionrollbackcb-callback-void) - [runOnTransactionComplete(cb: Callback): void](#runontransactioncompletecb-callback-void) - ## Installation ```shell @@ -291,7 +292,17 @@ Repositories, services, etc. can be mocked as usual. ## API -### Options +### Library Options + +```typescript +{ + maxHookHandlers?: number +} +``` + +- `maxHookHandlers` - Controls how many hooks (`commit`, `rollback`, `complete`) can be used simultaneously. If you exceed the number of hooks of same type, you get a warning. This is a useful to find possible memory leaks. You can set this options to `0` or `Infinity` to indicate an unlimited number of listeners. By default, it's `10`. + +### Transaction Options ```typescript { @@ -305,14 +316,16 @@ Repositories, services, etc. can be mocked as usual. - `isolationLevel`- isolation level for transactional context ([isolation levels](#isolation-levels) ) - `propagation`- propagation behaviors for nest transactional contexts ([propagation behaviors](#transaction-propagation)) -### initializeTransactionalContext(): void +### initializeTransactionalContext(options): void Initialize `cls-hooked` namespace. ```typescript -initializeTransactionalContext(); +initializeTransactionalContext(options?: TypeormTransactionalOptions); ``` +Optionally, you can set some [options](#library-options). + ### addTransactionalDataSource(input): DataSource Add TypeORM `DataSource` to transactional context. diff --git a/package.json b/package.json index 42d23bf..becd24a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typeorm-transactional", - "version": "0.2.1", + "version": "0.3.0", "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", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/common/index.ts b/src/common/index.ts index 363ad40..ec0dc34 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -9,9 +9,29 @@ import { } from './constants'; import { EventEmitter } from 'events'; import { TypeOrmUpdatedPatchError } from '../errors/typeorm-updated-patch'; +import { isDataSource } from '../utils'; export type DataSourceName = string | 'default'; +/** + * Options to adjust and manage this library + */ +interface TypeormTransactionalOptions { + /** + * Controls how many hooks (`commit`, `rollback`, `complete`) can be used simultaneously. + * If you exceed the number of hooks of same type, you get a warning. This is a useful to find possible memory leaks. + * You can set this options to `0` or `Infinity` to indicate an unlimited number of listeners. + */ + maxHookHandlers: number; +} + +/** + * Global data and state + */ +interface TypeormTransactionalData { + options: TypeormTransactionalOptions; +} + interface AddTransactionalDataSourceInput { /** * Custom name for data source @@ -34,6 +54,15 @@ interface AddTransactionalDataSourceInput { */ const dataSources = new Map(); +/** + * Default library's state + */ +const data: TypeormTransactionalData = { + options: { + maxHookHandlers: 10, + }, +}; + export const getTransactionalContext = () => getNamespace(NAMESPACE_NAME); export const getEntityManagerByDataSourceName = (context: Namespace, name: DataSourceName) => { @@ -107,7 +136,15 @@ const patchDataSource = (dataSource: DataSource) => { }; }; -export const initializeTransactionalContext = () => { +const setTransactionalOptions = (options?: Partial) => { + data.options = { ...data.options, ...(options || {}) }; +}; + +export const getTransactionalOptions = () => data.options; + +export const initializeTransactionalContext = (options?: Partial) => { + setTransactionalOptions(options); + const patchManager = (repositoryType: unknown) => { Object.defineProperty(repositoryType, 'manager', { get() { @@ -157,7 +194,7 @@ export const initializeTransactionalContext = () => { }; export const addTransactionalDataSource = (input: DataSource | AddTransactionalDataSourceInput) => { - if (input instanceof DataSource) { + if (isDataSource(input)) { input = { name: 'default', dataSource: input, patch: true }; } diff --git a/src/hooks/index.ts b/src/hooks/index.ts index dc4c19d..3f617bd 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,7 +1,12 @@ import { EventEmitter } from 'events'; import { Namespace } from 'cls-hooked'; -import { getHookInContext, getTransactionalContext, setHookInContext } from '../common'; +import { + getHookInContext, + getTransactionalContext, + getTransactionalOptions, + setHookInContext, +} from '../common'; export const getTransactionalContextHook = () => { const context = getTransactionalContext(); @@ -39,8 +44,12 @@ export const runAndTriggerHooks = async (hook: EventEmitter, cb: () => unknown) }; export const createEventEmitterInNewContext = (context: Namespace) => { + const options = getTransactionalOptions(); + return context.runAndReturn(() => { const emitter = new EventEmitter(); + emitter.setMaxListeners(options.maxHookHandlers); + context.bindEmitter(emitter); return emitter; }); diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..b9ff3a0 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,9 @@ +import { DataSource } from 'typeorm'; + +export const isDataSource = (value: unknown): value is DataSource => { + if (!value || typeof value !== 'object') { + return false; + } + + return value.constructor.name === DataSource.name; +}; diff --git a/tests/simple.test.ts b/tests/simple.test.ts index 867b1e8..320df6a 100644 --- a/tests/simple.test.ts +++ b/tests/simple.test.ts @@ -52,7 +52,7 @@ describe('Common tests', () => { return result?.is_transaction || false; } - async function isQueryBuilderWithEntityTransactionActive(queryBuilder: QueryBuilder) { + async function isQueryBuilderWithEntityTransactionActive(queryBuilder: QueryBuilder) { await queryBuilder.delete().from(Post).where('1 = 1').execute(); await queryBuilder.insert().into(Post).values({ message }).execute();