typeorm-aop-transaction
v1.6.0
Published
Modules that allow transactions of TypeORM 0.3 version to be applied from an AOP perspective
Downloads
43
Maintainers
Readme
- Outline
- Initialization
- Propagations
- Logging
- Message Queue
- Testing
- Future Support Plan
- Test Coverage
- Referenced Libraries
Outline
In Nest.js, the library allows for the establishment of TypeORM Transaction boundaries via AOP. This approach to transactional business logic takes inspiration from the Spring Framework's non-invasive methodology. There is a good library called typeorm-transactional-cls-hooked but it is not compatible with typeorm 0.3^
or higher.
We used @toss/aop for AOP implementation and it is compatible with custom AOP decoder implemented using that library. In addition, much of the code in the typeorm-transactional-cls-hooked library was referenced to implement the Spring Transaction Synchronization Pool. Internally, use Nest.js's AsyncStorage to manage resources in TypeORM during the request lifecycle.
Initialization
step 1
To use this library, you must register the Transaction Module with the App Module.
import { MiddlewareConsumer, Module } from '@nestjs/common';
import {
TransactionMiddleware,
TransactionModule,
} from 'typeorm-aop-transaction';
@Module({
imports: [TransactionModule.regist()],
controllers: [],
providers: [],
})
export class AppModule {}
import { MiddlewareConsumer, Module } from '@nestjs/common';
import {
TransactionMiddleware,
TransactionModule,
} from 'typeorm-aop-transaction';
@Module({
imports: [TransactionModule.regist()],
controllers: [],
providers: [],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(TransactionMiddleware).forRoutes('*'); // <-- add this line
}
}
If you used the connection name when registering a TypeORM Module, you must enter it in the defaultConnectionName
property. The defaultConnectionName
is the name
that you defined when you initialized the TypeORM module, DataSource.
// app.module.ts
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { TransactionModule } from 'typeorm-aop-transaction';
import { TransactionMiddleware } from 'typeorm-aop-transaction';
@Module({
imports: [
TransactionModule.regist({
defaultConnectionName: 'POSTGRES_CONNECTION', // <-- set specific typeorm connection name
}),
],
controllers: [],
providers: [],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(TransactionMiddleware).forRoutes('*');
}
}
...
// (example) database.module.ts
@Module({
imports: [
// Postgres Database
TypeOrmModule.forRootAsync({
name: 'POSTGRES_CONNECTION', // <-- using this connection name
inject: [ConfigService<IsPostgresDatabaseConfig>],
useFactory: (
configService: ConfigService<IsPostgresDatabaseConfig, true>,
) => ({
...
}),
}),
],
providers: [],
})
export class DatabaseModule {}
step 2-1 (with Custom repository)
Add the @CustomTransactionRepository
decorator to dynamically register the Custom Repository using the Transaction Module. (be changed v.1.3.0^)
import { CustomTransactionRepository } from 'typeorm-aop-transaction';
import { BaseRepository } from 'typeorm-aop-transaction';
@CustomTransactionRepository(User) // <-- add this Decorator
@Injectable()
export class UserRepository extends BaseRepository<User> {}
If you want to specify a Repository Token explicitly, pass it to the second parameter.
@CustomTransactionRepository(User, USER_REPOSITORY_TOKEN) // <-- add this Decorator
@Injectable()
export class UserRepository extends BaseRepository<User> {}
You can use the setRepository static method to register a CustomRepository as a provider.
@Module({
imports: [TransactionModule.setRepository([UserRepository])], // <-- register a CustomRepository
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
step 2-2 (without Custom repository)
If you are not using Custom Repository, you can register and use the Entity class.
@Module({
imports: [TransactionModule.setRepository([User])], // <-- regist a Entity Class
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
In this case, it must be injected from the service class using the supplied InjectTransactionRepository decorator.
@Injectable()
export class UserService {
constructor(
@InjectTransactionRepository(User) // <-- add this decorator
private readonly userRepository: UserRepository,
) {}
...
step 3
Use @Transactionl
to apply AOP at the method level of the Service Class.
@Injectable()
export class UserService {
constructor(
@InjectTransactionRepository(User)
private readonly userRepository: Repository<User>,
) {}
@Transactional()
async create(dto: CreateUserDto): Promise<void> {
const user = this.userMapper.to(User, dto);
await this.userRepository.insert(user);
}
@Transactional({
propagation: PROPAGATION.SUPPORTS
})
async findAll(): Promise<User[]> {
const user = await this.userRepository.find({
order: {
created_at: 'DESC',
},
});
return user;
}
The currently supported proposals are "REQUIRES_NEW", "REQUIRED", “NESETED”, “NEVER” and isolationLevel supports all isolation levels provided at the TypeORM level.
Propagations
Currently supported transaction propagation levels are REQUIRES_NEW, REQUIRED, NESTED, NEVER and SUPPORTS
@Transactional
default propagation option is REQUIRED.
REQUIRES_NEW
REQUIRES_NEW
propagation level starts a new transaction regardless of the existence of a parent transaction. Moreover, this newly started transaction is committed or rolled back independently of the nested or parent transaction. Here is an example for better understanding.
In the create
method that has the REQUIRES_NEW
propagation level, the create2
method with the REQUIRED
propagation level is being executed and an error occurs during the execution of the create2
method. create2
is rolled back and create
is committed, and as a result, the Error is thrown out of the Service Class.
[Nest] 23598 - 2023. 06. 18. 오후 5:56:06 DEBUG [Transactional] 1687078566046|POSTGRES_CONNECTION|create|READ COMMITTED|REQUIRES_NEW - New Transaction
query: START TRANSACTION
query: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["2ce-26531b27f5e8","wjdrms15!","[email protected]","+82-10-3252-2568"]
[Nest] 23598 - 2023. 06. 18. 오후 5:56:06 DEBUG [Transactional] 1687078566046|POSTGRES_CONNECTION|create2|READ COMMITTED|REQUIRED - New Transaction
query: START TRANSACTION
query: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["f4b-55aadba0508b","wjdrms15!","2222 [email protected]","+82-10-3252-2568"]
query: ROLLBACK
query: COMMIT
[Nest] 23598 - 2023. 06. 18. 오후 5:56:06 ERROR [ExceptionsHandler] test
In this case, the method create2
with the REQUIRES_NEW
propagation level is being executed within the create
method with the REQUIRED
propagation level, and an error occurs during the execution of the create2
method. In this case, the result of the create2
method with the REQUIRES_NEW
propagation attribute is committed instead of being rolled back.
[Nest] 24146 - 2023. 06. 18. 오후 6:06:06 DEBUG [Transactional] 1687079166691|POSTGRES_CONNECTION|create|READ COMMITTED|REQUIRED - New Transaction
query: START TRANSACTION
query: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["89f-ff92d6554359","wjdrms15!","[email protected]","+82-10-3252-2568"]
[Nest] 24146 - 2023. 06. 18. 오후 6:06:06 DEBUG [Transactional] 1687079166691|POSTGRES_CONNECTION|create2|READ COMMITTED|REQUIRES_NEW - New Transaction
query: START TRANSACTION
query: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["7a3-cce699b7f065","wjdrms15!","2222 [email protected]","+82-10-3252-2568"]
query: COMMIT
query: ROLLBACK
[Nest] 24146 - 2023. 06. 18. 오후 6:06:06 ERROR [ExceptionsHandler] test
REQUIRED
The default propagation level is REQUIRED
. If set to REQUIRED
, transaction boundary settings depend heavily on the existence of a parent transaction. If there is already an ongoing transaction, it participates in the transaction without starting a new one. If there is no ongoing transaction, a new transaction is started.
In the create
method, which has been set to a REQUIRED
propagation level, an error occurs while the create2
method, which has been set to a REQUIRED
propagation level, is executed. Since they behave like a single transaction, both are rolled back.
[Nest] 24304 - 2023. 06. 18. 오후 6:10:53 DEBUG [Transactional] 1687079453250|POSTGRES_CONNECTION|create|READ COMMITTED|REQUIRED - New Transaction
query: START TRANSACTION
query: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["4ed-be402112bcde","wjdrms15!","[email protected]","+82-10-3252-2568"]
[Nest] 24304 - 2023. 06. 18. 오후 6:10:53 DEBUG [Transactional] 1687079453250|POSTGRES_CONNECTION|create2|READ COMMITTED|REQUIRED - Join Transaction
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["2cd-d3159145e24a","wjdrms15!","2222 [email protected]","+82-10-3252-2568"]
query: ROLLBACK
[Nest] 24304 - 2023. 06. 18. 오후 6:10:53 ERROR [ExceptionsHandler] test
NESTED
This propagation option is very similar to REQUIRED
, but there is a difference when the parent transaction exists. In this case, it does not simply participate in the transaction, but sets a savepoint before executing its query. If an error occurs, it rolls back only up to the savepoint it set, so the parent transaction is committed normally.
If there is a NESTED propagation method create2
inside the parent method create
with the REQUIRED
propagation level, and an error occurs during the execution of create2
, create2
saves a savepoint before executing its query. If an error occurs, it rolls back only up to the savepoint it set, so the insert by the parent method is normally committed.
[Nest] 24502 - 2023. 06. 18. 오후 6:15:43 DEBUG [Transactional] 1687079743116|POSTGRES_CONNECTION|create|READ COMMITTED|REQUIRED - New Transaction
query: START TRANSACTION
query: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["615-1bbae146a294","wjdrms15!","[email protected]","+82-10-3252-2568"]
[Nest] 24502 - 2023. 06. 18. 오후 6:15:43 DEBUG [Transactional] 1687079743116|POSTGRES_CONNECTION|create2|READ COMMITTED|NESTED - Make savepiont, Wrap Transaction
query: SAVEPOINT typeorm_1
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["1b9-d065db5d0bc4","wjdrms15!","2222 [email protected]","+82-10-3252-2568"]
query: ROLLBACK TO SAVEPOINT typeorm_1
query: COMMIT
[Nest] 24502 - 2023. 06. 18. 오후 6:15:43 ERROR [ExceptionsHandler] test
NEVER
If the NEVER
propagation option is set, it defaults to returning an error if a parent transaction exists.
[Nest] 15178 - 2023. 07. 02. 오후 5:35:17 DEBUG [Transactional] 1688286917592|POSTGRES_CONNECTION|create|READ COMMITTED|REQUIRED - New Transaction
query: START TRANSACTION
query: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["c2d-5b8df90d6607","wjdrms15!","[email protected]","+82-10-3252-2568"]
query: ROLLBACK
**[Nest] 15178 - 2023. 07. 02. 오후 5:35:17 ERROR [ExceptionsHandler] Attempting to join a transaction in progress. Methods with NEVER properties cannot run within a transaction boundary**
Error: Attempting to join a transaction in progress. Methods with NEVER properties cannot run within a transaction boundary
at AlsTransactionDecorator.<anonymous> (/Users/beobwoo/dev/beebee/server/node_modules/typeorm-aop-transaction/src/providers/als-transaction.decorator.ts:305:15)
at Generator.throw (<anonymous>)
at rejected (/Users/beobwoo/dev/beebee/server/node_modules/typeorm-aop-transaction/dist/providers/als-transaction.decorator.js:18:65)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
If the NEVER
propagation option is normally processable, it does not create a transaction it only executes SQL queries.
[Nest] 15328 - 2023. 07. 02. 오후 5:36:42 DEBUG [Transactional] 1688287002875|POSTGRES_CONNECTION|findAll|READ COMMITTED|NEVER - No Transaction
query: SELECT "*" FROM "user" "User" WHERE "User"."deleted_at" IS NULL ORDER BY "User"."created_at" DESC
SUPPORTS
If the SUPPORTS
transaction option is set and the parent transaction does not exist, it behaves the same as the NEVER
propagation option. Therefore, it only runs SQL Query without any transactions.
[Nest] 15328 - 2023. 07. 02. 오후 5:36:42 DEBUG [Transactional] 1688287002875|POSTGRES_CONNECTION|findAll|READ COMMITTED|NEVER - No Transaction
query: SELECT "*" FROM "user" "User" WHERE "User"."deleted_at" IS NULL ORDER BY "User"."created_at" DESC
If the parent transaction is in progress and is available to participate, it will behave the same way as the REQUIRED
propagation option. Therefore, participate in transactions in progress.
[Nest] 15831 - 2023. 07. 02. 오후 5:41:09 DEBUG [Transactional] 1688287269077|POSTGRES_CONNECTION|create|READ COMMITTED|NESTED - New Transaction
query: START TRANSACTION
query: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["b0f-a42a40a6ba7f","wjdrms15!","[email protected]","+82-10-3252-2568"]
[Nest] 15831 - 2023. 07. 02. 오후 5:41:09 DEBUG [Transactional] 1688287269077|POSTGRES_CONNECTION|create2|READ COMMITTED|**SUPPORTS - Join Transaction // <-- join**
query: INSERT INTO "user"("created_at", "updated_at", "deleted_at", "id", "user_id", "password", "email", "phone_number") VALUES (DEFAULT, DEFAULT, DEFAULT, DEFAULT, $1, $2, $3, $4) RETURNING "created_at", "updated_at", "deleted_at", "id" -- PARAMETERS: ["42b-d4bbdccc8c9a","wjdrms15!","2222 [email protected]","+82-10-3252-2568"]
query: COMMIT
Logging
If you want to log the generation and participation of transactions according to the propagation option, pass the logging
property with the TransactionModule.regist call factor. The default log level is the log
.
@Module({
imports: [
TransactionModule.regist({
logging: 'debug', // logging level
}),
],
controllers: [],
providers: [],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(TransactionMiddleware).forRoutes('*');
}
}
// (example) console logging
[Nest] 20212 - 2023. 07. 24. 오후 11:29:57 DEBUG [Transactional] 1690208997228|POSTGRES_CONNECTION|findAll|READ COMMITTED|REQUIRED - New Transaction
[Nest] 20212 - 2023. 07. 24. 오후 11:46:05 DEBUG [Transactional] 1690209965305|POSTGRES_CONNECTION|create|READ COMMITTED|REQUIRED - New Transaction
Message Queue
Bull
This library supports integration with the @nestjs/bull library. However, the following are the precautions.
If a consumer of a job registered in bull queue calls the @Transactional
service methods, the asynchronously called method has no parent transaction, so it behaves the same as the top-level transaction.
example
// basic-queue.processor.ts
@Processor(QueueName.BASIC_QUEUE)
export class BasicQueueProcessor {
constructor(
@Inject(EmploymentOpportunityInjector.EMPLOYMENT_OPPORTUNITY_SERVICE)
private readonly eopService: EmploymentOpportunityService,
) {}
@Process(AutoDeleteEmploymentOpportunityJobName)
async deleteEmploymentOpportunity(
job: Job<AutoDeleteEmploymentOpportunityJob>,
) {
// call REQUIRED delete method
// has no parent transaction, so start new transaction
await this.eopService.delete(job.data.eopId);
}
}
// eop.service.ts
@Transactional()
delete(eopId: string) {
return this.eopRepository.delete(eopId);
}
Suppose the delete method of the EopService
is called during operation processing by the BasicQueueProcessor
.
From the perspective of a transaction enclosing the delete method, there are no parent transactions, so create a new transaction according to the rules of the REQUIRED
propagation option.
Testing
Unit Test
// user.service.ts
@Injectable()
export class UserService {
constructor(
@InjectTransactionRepository(User)
private readonly userRepository: Repository<User>,
@InjectTransactionRepository(SocialAccount)
private readonly socialAccountRepository: Repository<SocialAccount>,
) {}
...
The type of Transactional Repository injected through TransactionModule.setRepository
is the same as the Repository<EntityType>
provided by TypeORM and uses a token-based provider provided by TypeORM, so you can inject the MockRepository
test module by getRepositoryToken
method.
// user.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from '../dtos/create-user.dto';
import { UserService } from '../service/user.service';
describe('UserService', () => {
let service: UserService;
let userRepository: Repository<User>;
let socialAccountRepository: Repository<SocialAccount>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useValue: {
create: jest.fn(),
save: jest.fn(),
...
},
},
{
provide: getRepositoryToken(SocialAccount),
useValue: {
create: jest.fn(),
save: jest.fn(),
...
},
},
],
}).compile();
service = module.get<UserService>(UserService);
userRepository = module.get<Repository<User>>(getRepositoryToken(User));
socialAccountRepository = module.get<Repository<SocialAccount>>(
getRepositoryToken(SocialAccount),
);
});
...
After that, you can create a typical unit test code through the jest.Spy
object.
it('should save User Entity and related Entities', async () => {
const create = jest.spyOn(userRepository, 'create').mockReturnValue(user);
const save = jest.spyOn(userRepository, 'save').mockResolvedValue({
...user,
_id: 'test uuid',
});
await service.create(createUserDto);
expect(create).toBeCalledTimes(1);
expect(save).toBeCalledTimes(1);
...
});
Integration Test
If you have registered the TransactionModule
through TransactionModule.forRoot
in AppModule
, you can perform the e2e test without any additional procedures.
The @TransactionalDecorator
uses @toss/aop to provide transaction boundaries to methods in the Service class, so you can use a variety of integrated test methods in addition to the examples below for integrated testing with the Controller.
@Module({
imports: [
AopModule,
TransactionModule.regist({
defaultConnectionName: <<Optional>>,
}),
DatabaseModule,
...
],
controllers: [AppController],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(TransactionMiddleware).forRoutes('*');
}
}
This example uses supertest and jest together for integration testing.
Configure the Test Module through AppModule
for integrated testing and organize the data in the test database.
describe('UserController (e2e)', () => {
let app: INestApplication;
let dataSource: DataSource;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
providers: [],
}).compile();
app = module.createNestApplication();
await app.init();
});
beforeEach(async () => {
jest.restoreAllMocks();
dataSource = app.get<DataSource>(getDataSourceToken());
const queryRunner = dataSource.createQueryRunner();
try {
await queryRunner.query('TRUNCATE "user" CASCADE');
} finally {
await queryRunner.release();
}
});
afterAll(async () => {
await app.close();
});
it('GET /user/list', () => {
return request(app.getHttpServer()).get('/user/list').expect(<<expedted_value>>);
});
...
});
Future Support Plan
- add propagation option : ~~NESTED~~, NOT_SUPPORTED, ~~SUPPORTS~~, ~~NEVER~~, MANDATORY
- ~~add Unit Test~~
- ~~add integration test~~
- add Rollback, Commit Callback Hooks
- ~~remove Loggers~~
Test Coverage
| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | | ------------------------------------------ | ------- | -------- | ------- | ------- | ----------------- | | All files | 100 | 100 | 100 | 100 | | src | 100 | 100 | 100 | 100 | | base.repository.ts | 100 | 100 | 100 | 100 | | src/const | 100 | 100 | 100 | 100 | | custom-repository-metadata.ts | 100 | 100 | 100 | 100 | | propagation.ts | 100 | 100 | 100 | 100 | | src/decorators | 100 | 100 | 100 | 100 | | custom-transaction-repository.decorator.ts | 100 | 100 | 100 | 100 | | inject-transaction-repository.decorator.ts | 100 | 100 | 100 | 100 | | transactional.decorator.ts | 100 | 100 | 100 | 100 | | src/exceptions | 100 | 100 | 100 | 100 | | not-rollback.error.ts | 100 | 100 | 100 | 100 | | src/modules | 100 | 100 | 100 | 100 | | transaciton.module.ts | 100 | 100 | 100 | 100 | | src/providers | 100 | 100 | 100 | 100 | | als-transaction.decorator.ts | 100 | 100 | 100 | 100 | | data-source-map.service.ts | 100 | 100 | 100 | 100 | | transaction.logger.ts | 100 | 100 | 100 | 100 | | transaction.middleware.ts | 100 | 100 | 100 | 100 | | transaction.service.ts | 100 | 100 | 100 | 100 | | src/symbols | 100 | 100 | 100 | 100 | | als-service.symbol.ts | 100 | 100 | 100 | 100 | | data-source-map.service.symbol.ts | 100 | 100 | 100 | 100 | | transaciton-module-option.symbol.ts | 100 | 100 | 100 | 100 | | transaction-decorator.symbol.ts | 100 | 100 | 100 | 100 | | src/test/mocks | 100 | 100 | 100 | 100 | | als.service.mock.ts | 100 | 100 | 100 | 100 | | discovery.service.mock.ts | 100 | 100 | 100 | 100 | | transaction-module-option.mock.ts | 100 | 100 | 100 | 100 | | src/utils | 100 | 100 | 100 | 100 | | is-base-repository-prototype.ts | 100 | 100 | 100 | 100 | | is-typeorm-entity.ts | 100 | 100 | 100 | 100 |