How to set table name in #Entity dynamically? - nestjs

I read the article https://medium.com/#terence410/working-with-dynamic-table-name-with-typeorm-6a67128b9671
import {Entity, PrimaryColumn, Column} from "typeorm";
export function createEntity(tableName: string) {
#Entity({name: tableName})
class EntityClass {
public static tableName = tableName;
#PrimaryColumn()
public name: string = "";
#Column()
public value: number = 0;
}
return EntityClass;
}
I have error - Return type of exported function has or is using private name 'EntityClass'.
How to set table name dynamic ?

I also followed the post the article https://medium.com/#terence410/working-with-dynamic-table-name-with-typeorm-6a67128b9671. It need to do some change in code to make it work.
Here is the code:
[1] Create the base entity, this entity will contain all properties that you need for your model and we can export this entity for other the class use.
import {Column, PrimaryGeneratedColumn} from 'typeorm'
export class BaseCategory {
#PrimaryGeneratedColumn({unsigned: true})
id: number
#Column({type: 'varchar', length: 255, nullable: true})
name: string
#Column({type: 'bigint', width: 3, nullable: true})
quantity: number
}
[2] Define the CategoryService
#Injectable()
export class CategoryService {
constructor(
#Logger() private readonly logger: LoggerService,
private configService: ConfigService
) {}
public connections: Map<any, Promise<Connection>> = new Map()
/**
* Gets the connection for dynamic entity. By this way, the dynamic entity can register with
* EntityManger and can get the repository for this dynamic entity.
* If don't use this way, the EntityManager don't know the dynamic entity and will throw error
* when try to get the repository for this dynamic entity.
*
* There is the problem with approach: there is too many connections, each merchant will help its own connection.
*
* #param entityType
* #returns
*/
public async getConnection(entityType: object) {
const commissionTableName = (entityType as any).tableName
if (!this.connections.has(commissionTableName)) {
const mysqlConfig = this.configService.get('mysql')
const name = `table:${commissionTableName}`
const newOptions = {...mysqlConfig, name, entities: [entityType] as any}
const connection = createConnection(newOptions)
this.connections.set(commissionTableName, connection)
}
return this.connections.get(commissionTableName) as Promise<Connection>
}
public async getRepository(
dynamicTableName
): Promise<Repository<BaseCategory>> {
#Entity({name: dynamicTableName})
class EntityClass extends BaseCategory {
public static tableName = dynamicTableName
}
const connection = await this.getConnection(EntityClass)
const repository = connection.getRepository(
dynamicTableName
) as Repository<BaseCategory>
return repository
}
public async create() {
// Example we want to create a new entity & table `organ_category`
const repository = await this.getRepository('organ_category')
const entity = new BaseCategory()
await repository.save(entity)
}
}
It worked for me. There are some consider points with this code:
Each category will one connection, it is not good point but we can improve it by using list of dynamic table to init instead of
init one by one. By this way, it can reduce number of connection.

Related

Correctly Saving and Updating Entites in Netsjs+TypeORM

I've got a Question regarding TypeORM-Relations and how to use them 'nest-like'.
Suppose I have two Entities defined ChildEntity and TestEntity, which are related.
TestEntity:
import { ChildEntity } from 'src/modules/child-entity/entities/child-entity.entity';
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class TestEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar')
name: string;
#ManyToOne(() => ChildEntity, (childEntity) => childEntity.testEntities)
childEntity: ChildEntity;
constructor(name: string, childEntity: ChildEntity) {
this.name = name;
this.childEntity = childEntity;
}
}
My first question occurs when I want to create the entity. I have to first translate the passed childEntityId into a ChildEntity, which I can pass to the constructor:
CreateTestEntityDto
import { ApiProperty } from '#nestjs/swagger';
import { IsNotEmpty, IsNumber } from 'class-validator';
export class CreateTestEntityDto {
#ApiProperty()
#IsNotEmpty()
name: string;
#ApiProperty()
#IsNumber()
childEntityId: number;
constructor(name: string, childEntityId: number) {
this.name = name;
this.childEntityId = childEntityId;
}
}
async create(createTestEntityDto: CreateTestEntityDto) {
const { name, childEntityId } = createTestEntityDto;
const childEntity = await this.childEntityService.findOne(childEntityId);
const testEntity = new TestEntity(name, childEntity);
return this.testEntityRepo.save(testEntity);
}
Is there a way to just pass the childEntityId to the save()-Method without explicitly looking for the ChildEntity beforehand?
The Second problem occurs when updating.
UpdateTestEntityDto
import { PartialType } from '#nestjs/swagger';
import { CreateTestEntityDto } from './create-test-entity.dto';
export class UpdateTestEntityDto extends PartialType(CreateTestEntityDto) {}
As updating only a partial Entity is possible I have to check if the Id is even passed along the request and if it is I have to retrieve the correct Entity for the update. Is there a more streamlined way to do this?
async update(id: number, updateTestEntityDto: UpdateTestEntityDto) {
const { name, childEntityId } = updateTestEntityDto;
const props = { name };
if (childEntityId) {
props['childEntity'] = await this.childEntityService.findOne(
childEntityId,
);
}
return this.testEntityRepo.update(id, props);
}
You should add a childEntityId to the test entity:
#Entity()
export class TestEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar')
name: string;
#Column('int')
childEntityId: number;
#ManyToOne(() => ChildEntity, (childEntity) => childEntity.testEntities)
childEntity: ChildEntity;
...
}
and then you can use it to set the id directly. Something like:
async create(dto: Dto) {
const { name, childEntityId } = dto;
const entity = new TestEntity();
entity.name = name;
entity.childEntityId = childEntityId;
return this.testEntityRepo.save(entity);
}
Check this out.
1.) Saving relational entity
There's no need to do all these roundtrips cluttering to save the entity. While, the solution given by #UrosAndelic works but still there's no need to write 3 extra lines of code.
If you hover over a relational param inside the create() method of the repository from an IDE, you'll notice that it accepts two types. First, An Instance of an entity OR Second, a DeepPartial object of an entity.
For instance:
const entity = this.testEntityRepo.create({
name: 'Example 1',
childEntity: {
id: childEntityId // notice: it's a DeepPartial object of ChildEntity
}
})
await this.testEntityRepo.save(entity)
2.) Updating entity
There's no need for child entity's id if you are updating test entity. You can simply update the props of test entity.
const testEntityId = 1;
await this.testEntityRepo.update(testEntityId, {
name: 'Example 2'
})
This will update the name of TestEntity = 1;

NestJS: dynamically call various services for batch processing

I have some Service classes as follows:
//Cat Service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository, getManager } from 'typeorm';
import { CatRepo } from '../tables/catrepo.entity';
import { CatInterface } from './cat.interface';
#Injectable()
export class CatService {
constructor(
#InjectRepository(CatRepo)
private catRepo: Repository<CatRepo>,
) {}
async customFindAll(offset:number, limit: number): Promise<CatRepo[]> {
const entityManager = getManager();
const catRows = await entityManager.query(
`
SELECT * FROM CATREPO
${offset ? ` OFFSET ${offset} ROWS ` : ''}
${limit ? `FETCH NEXT ${limit} ROWS ONLY` : ''}
`,
);
return catRows;
}
formResponse(cats: CatRepo[]): CatInterface[] {
const catsResults: CatInterface[] = [];
.
//form cat response etc.
.
//then return
return catsResults;
}
}
//Pet Service:
import { Injectable } from '#nestjs/common';
import { getManager } from 'typeorm';
import { PetInterface } from './pet.interface';
#Injectable()
export class PetService {
async customFindAll(offset:number, limit: number) {
const entityManager = getManager();
const petRows = await entityManager.query(
`
JOIN ON TABLES......
${offset ? ` OFFSET ${offset} ROWS ` : ''}
${limit ? `FETCH NEXT ${limit} ROWS ONLY` : ''}
`,
);
//returns list of objects
return petRows;
}
formResponse(pets): PetInteface[] {
const petsResults: PetInteface[] = [];
.
. //form pet response etc.
.
//then return
return petsResults;
}
}
I am running a cron BatchService that uses these two services subsequently saving the data into respective batch files.
I'm calling CatService and PetService from the BatchService as follows:
/Start the Batch job for Cats.
if(resource === "Cat") {
//Call Cat Service
result = await this.catService.findAllWithOffest(startFrom, fetchRows);
finalResult = this.catService.formResponse(result);
}
//Start the batch job for Pets.
if(resource === "Pet") {
//Call Pet Service
result = await this.petService.findAllWithOffest(startFrom, fetchRows);
finalResult = this.petService.formResponse(result);
}
However, instead of the above I want to use these Services dynamically.
In order to achieve the CatService and PetService now extends AbstractService...
export abstract class AbstractService {
public batchForResource(startFrom, fetchRows) {}
}
//The new CatService is as follows:
export class CatService extends AbstractService{
constructor(
#InjectRepository(CatRepo)
private catRepo: Repository<CatRepo>,
) {}
.
.
.
}
//the new PetService is:
export class PetService extends AbstractService{
constructor(
) {super()}
.
.
.
}
//the BatchService...
public getService(context: string) : AbstractService {
switch(context) {
case 'Cat': return new CatService();
case 'Pet': return new PetService();
default: throw new Error(`No service found for: "${context}"`);
}
}
However in the CatService I'm getting the a compilation error...(Expected 1 Argument but got 0). What should be the argument passed in the CatService.
Also, the larger question is if this can be achieved by using NestJS useValue/useFactory...If so how to do it?
You can probably use useFactory to dynamically retrieve your dependencies but there are some gotcha's.
You must make the lifecycle of your services transient, since NestJS dependencies are registered as singletons by default. If not, you would get the same first service injected each time, regardless of the context of subsequent calls.
Your context must come from another injected dependency - ExecutionContext, Request or something similarly dynamic, or something you register yourself.
Alternative
As an alternative, you can implement the "servicelocator/factory" pattern. You're already halfway there with your BatchService. Instead of your service creating instances of the CatService and PetService, you have it injected and just return the injected services depending on the context. Like so:
#Injectable()
export class BatchService {
constructor(
private readonly catService: CatService,
private readonly petService: PetService
)
public getService(context: string) : AbstractService {
switch(context) {
case 'Cat': return this.catService;
case 'Pet': return this.petService;
default: throw new Error(`No service found for: "${context}"`);
}
}
}
The alternative is more flexible than using useFactory, since your context is not limited to what is available in the DI container. On the negative side, it does expose some (usually unwanted) infrastructure details to the calling code, but that's the tradeoff you'll have to make.

Why and when should I use TypeScript's "return X as Y"?

Reading a code sample that contains the code below.
import { Group } from '../models/Group'
export class GroupAccess {
constructor(
private readonly docClient: DocumentClient = createDynamoDBClient(),
private readonly groupsTable = process.env.GROUPS_TABLE) {
}
async getAllGroups(): Promise<Group[]> {
console.log('Getting all groups')
const result = await this.docClient.scan({
TableName: this.groupsTable
}).promise()
const items = result.Items
return items as Group[]
}
...
content in the ../models/Group
export interface Group {
id: string
name: string
description: string
userId: string
timestamp: string
}
Q:
items is an AWS.DynamoDB.DocumentList.ItemList. Is the code below trying to typecast to Group[]? The syntax looks different from the regular type conversion https://www.w3schools.com/js/js_type_conversion.asp
return items as Group[]

extending a service to create a single definition of a database throughout application

I've created a nodejs service to create/interact with databases.
class CRUDService {
private section: string;
private db: any;
constructor(section: string) {
this.section = section;
this.db = new DB('.' + section);
}
public create(data: any): Promise<string> {
...
}
public retrieve(id: string): Promise<string> {
...
}
public update(id: any, data: any): Promise<string> {
...
}
public delete(item: any): Promise<string> {
...
}
public all(): Promise<string> {
...
}
}
export {CRUDService};
And then I was using it by passing a database name to it:
this.db1 = new DatabaseService('database-one');
this.db2 = new DatabaseService('database-two');
This has been working well for me but recently I noticed that I am defining the same databases over and over again (in different modules) and figured there has got to be a better way to doing it so that the database can be defined once and shared across everything.
Is there a way I can extend my current service to define/initialize the database once and reference it?
export class DbOneService extends CRUDservice {
protected section = 'database-one';
}
And then use it like:
this.db1 = DbOneService;
I'm just a bit lost and the above doesn't seem to work.
UPDATE
By leaving the CRUDService as-is I was able to achieve what I by
import {CRUDService} from '../crud.service';
function DbOneService() {
return new CRUDService('database-one');
}
export {DbOneService};
Then I use it as follows:
import {DbOneService} from 'db/database-one';
const DbOneServiceDB = DbOneService();
DbOneServiceDB.all(){...}
Is there anything wrong with this approach?

Node.js and sequelize-typescript - data access objects and business objects

I am using the sequelize-typescript in my Node.js service
I have the Category class which maps to category table
import { Model, Table, Column } from "sequelize-typescript";
#Table
export class Category extends Model<Category>{
#Column
name: string
}
I also have CategoryController and CategoryService
export class CategoryController {
...
async getAll(request: Request, response: Response) {
let categories = await this.categoryService.getCatergories();
response.json(categories)
}
}
export class CategoryService {
async getCatergories(): Promise<Category[]> {
let categories = await Category.findAll<Category>()
return categories
}
}
And everything is as it should be.
But returning a Category to the controller allows it to use the use the inherited methods from the model class like:
export class CategoryController {
...
async getAll(request: Request, response: Response) {
let categories = await this.categoryService.getCatergories();
// Remove associated row in the database
categories[0].destroy()
response.json(categories)
}
}
I was thinking to create a CategoryModel class like this:
export class CategoryModel {
id : number
name : string
}
And modify all methods in CategoryService to return CategoryModel instances instead of Category and rename Category to CategoryEntity
What is the best way to deal with such a problem?
Use toJSON() of Category instance to get "a JSON representation" of the instance.
See sequelize docs for more information: http://docs.sequelizejs.com/class/lib/model.js~Model.html#instance-method-toJSON
Additionally you could add an interface to achieve type safety for the return value of toJSON() instead of defining another class:
interface ICategory {
id: number;
name: string;
}
#Table
export class Category extends Model<Category> implements ICategory{
#Column
name: string
}
Using toJSON():
Category.findOne(result => {
const category: ICategory = result.toJSON();
});

Resources