jamcaa-helper (饮茶小助手)
If you are using:
- Nestjs
- TypeORM
- MySQL
Then you can drink a cup of tea instead of spending time on writing CRUD code.
Installation
yarn add @mangroves/jamcaa-helper
Usage
Instantiation
import { JamcaaHelper } from '@mangroves/jamcaa-helper'
import { InjectRepository } from '@nestjs/typeorm'
import { Entity } from './entity.ts'
@Injectable()
export class SomeService {
private readonly crudHelper = new JamcaaHelper(SomeEntity, 'entityUniqueKey' /** , options, connectionName */)
constructor (
// You may need to inject repository if you get a `RepositoryNotFoundError`
@InjectRepository(Entity)
private readonly repository: Repository<Entity>,
) {}
}
Create
Feature
What does this method do?
- Check if record already exists according to unique keys
- Reuse soft deleted record according to options
- Increase data_version
- Add creator and updater to entity
- Add create_time and update_time
- Save to database and return saved entity
Exceptions
- Throw 400 if entity already exists
Example
import { JamcaaHelper } from '@mangroves/jamcaa-helper'
@Injectable()
export class SomeService {
private readonly crudHelper = new JamcaaHelper(SomeEntity, 'entityUniqueKey' /** , options, connectionName */)
async create (dto: DTO, operator: string) {
const partialEntity = getEntityFromDTO(dto)
const savedEntity = await this.crudHelper.createInsertQuery(partialEntity, operator)
return getDTOFromEntity(savedEntity)
}
}
List
Feature
- Create and return a ListQuery instance (an internal class)
- You don't need to care about null/undefined/empty/array filter value in DTO
- Dealing with soft delete according to options
Exceptions
None
Example
import { JamcaaHelper } from '@mangroves/jamcaa-helper'
@Injectable()
export class SomeService {
private readonly crudHelper = new JamcaaHelper(SomeEntity, 'entityUniqueKey' /** , options, connectionName */)
async list (dto: DTO) {
const [entities, totalSize] = await this.crudHelper.createListQuery()
.filter((filterQuery) => {
filterQuery
.equals('column1', dto.column1)
.equals('column2', dto.column2)
})
.showDeletedQuery()
.paginationQuery(dto.page_number, dto.page_size)
.getQueryBuilder()
.getManyAndCount()
return {
entities,
total_size: totalSize
}
}
}
Get
Feature
- Dealing with soft delete according to options
Exceptions
- Throw 404 if entity not found
Example
import { JamcaaHelper } from '@mangroves/jamcaa-helper'
@Injectable()
export class SomeService {
private readonly crudHelper = new JamcaaHelper(SomeEntity, 'entityUniqueKey' /** , options, connectionName */)
async get (id: string) {
const uniqueKeyConditions = { id }
const entity = await this.crudHelper.createGetQuery(uniqueKeyConditions)
return getDTOFromEntity(entity)
}
}
Update
Feature
- Update record using FieldMask
- Update data_version
- Update updater
- Update update_time
Exceptions
- Throw 404 if entity not found
- Throw 400 if update_mask contains disallowed fields
- Throw 400 if nothing updated
Example
Note: If you want to validate data_version, you must transform it in transformToEntity
.
import { JamcaaHelper } from '@mangroves/jamcaa-helper'
@Injectable()
export class SomeService {
private readonly crudHelper = new JamcaaHelper(SomeEntity, 'entityUniqueKey' /** , options, connectionName */)
async update (id: string, dto: DTO, operator: string) {
const uniqueKeyConditions = { id }
const dto = { first_name: 'Charlie', person_info: { age: 12 }, data_version: '1' }
const updateMask = ['first_name', 'person_info.age']
const allowedMask = ['first_name', 'person_info']
const transformFromEntity = (entity) => ({
first_name: entity.firstName,
person_info: entity.personInfo,
})
const transformToEntity = (dto) => ({
firstName: dto.first_name,
personInfo: dto.person_info,
// Transform data_version if you want to validate it.
dataVersion: dto.data_version,
})
const updatedEntity = await this.crudHelper.createUpdateQuery(
uniqueKeyConditions,
dto,
updateMask,
allowedMask,
operator,
transformFromEntity,
transformToEntity,
)
return getDTOFromEntity(updatedEntity)
}
}
Delete
Feature
- Dealing with soft delete according to options
- Add updater if soft delete feature enabled
Exceptions
- Throw 404 if entity not found
Example
import { JamcaaHelper } from '@mangroves/jamcaa-helper'
@Injectable()
export class SomeService {
private readonly crudHelper = new JamcaaHelper(SomeEntity, 'entityUniqueKey' /** , options, connectionName */)
async delete (id: string, operator: string) {
const uniqueKeyConditions = { id }
await this.crudHelper.createDeleteQuery(uniqueKeyConditions, operator)
}
}
Constructor Options
Option | Type | Default | Description |
---|---|---|---|
maxUnspecifiedPageSize | number | 100 | Max page size if it is not specified |
softDelete | boolean | true | Whether soft delete feature should be applied |
softDeleteField | string | 'deleteStatus' | Soft delete column field |
softDeleteEnum | [undeleted: any, deleted: any] | [0, 1] | The values to mark if record is deleted |
reuseSoftDeletedData | boolean | true | Whether to reuse soft deleted data |
dataVersion | boolean | true | Whether to increase data version column on update |
dataVersionField | string | 'dataVersion' | Data version column field |
dataVersionType | 'string' or 'number' | 'string' | Data version type |
validateDataVersion | boolean | true | Whether to validate data_version or not |
hasOperator | boolean | true | Whether there are creator and updater columns |
creatorField | string | 'creator' | Creator column field |
updaterField | string | 'updater' | updater column field |
hasTime | boolean | true | Whether there are create_time and update_time columns |
createTimeField | string | 'createTime' | Create time column field |
updateTimeField | string | 'updateTime' | Update time column field |
timePrecision | 'ms' or 's' | 'ms' | Store time column with the precision to millisecond or second |
onEntityAlreadyExistsError | (entityName: string) => never | Throw 400 exception | To throw an exception when entity already exists |
onEntityNotFoundError | (entityName: string) => never | Throw 404 exception | To throw an exception when entity not found |
onDisallowedUpdateMaskError | (disallowedMask: string[]) => never | Throw 400 exception | To throw an exception when update_mask contains disallowed fields |
onDataVersionError | () => never | Throw 400 exception | To throw an exception when data_version is not equal to the existing entity's |
onNothingUpdatedError | () => never | Throw 400 exception | To throw an exception when nothing updated |
Test
unit
yarn test:unit
e2e
Real MySQL environment required.
We run e2e test with real database running on Docker.
- Install Docker
- Execute the following command to start a MySQL container
yarn db
Then execute
yarn test:e2e
After test run, use the following command to stop and remove MySQL container (Optional)
yarn db:destroy
Caveat
Make sure you are using the same TypeORM library in node_modules, or else JamcaaHelper will not get the correct ORM connection.