Why and when should I use TypeScript's "return X as Y"? - node.js

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[]

Related

Using an Abstract class for mutations in NestJS with dynamic input #Args for the update mutation throws type error

Have been working on adding an abstract class to support versioning for Resolvers for a floorPlan feature and was wondering how I might fix this error. At the moment I do not get any Type errors but when the code compiles NestJs throws this error:
Undefined type error. Make sure you are providing an explicit type for the "floorPlanUpdate" (parameter at index [2]) of the "BaseResolverHost" class.
In a normal resolver this is not a problem as you pass in the type e.g.:
#Args('input') input: UpdateFloorPlanV1Input,
However as I am trying to determine the input type based off the version of the Floorplan using this line:
#Args('input') input: UpdateFloorPlanVersion<FloorPlanVersion>,
So it is not possible to explicitly declare it here. Let me know if this is possible or if anyone has any other approaches
This is the abstract Resolver code:
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { UserId } from 'src/users/decorators/user-id.decorator';
import { CreateFloorPlanInput } from './v1/dto/create-floor-plan.input';
import { UpdateFloorPlanV1Input } from './v1/dto/update-floor-plan.input';
import { FloorPlanV1 } from './v1/models/floor-plan.model';
import { UpdateFloorPlanV2Input } from './v2/dto/update-floor-plan.input';
import { FloorPlanV2 } from './v2/models/floor-plan.model';
export type FloorPlanVersion = FloorPlanV1 | FloorPlanV2;
export type UpdateFloorPlanVersion<T extends FloorPlanVersion> =
T extends FloorPlanV1 ? UpdateFloorPlanV1Input : UpdateFloorPlanV2Input;
export interface FloorPlanServiceInterface<FloorPlanVersion> {
create(
userId: string,
createFloorPlanInput: CreateFloorPlanInput,
): Promise<FloorPlanVersion>;
findOne(userId: string, floorPlanId: string): Promise<FloorPlanVersion>;
update(
userId: string,
floorPlanId: string,
input: UpdateFloorPlanV1Input,
): Promise<FloorPlanVersion>;
}
export function BaseResolver<T extends Type<FloorPlanVersion>>(
classRef: T,
version: string,
) {
#Resolver({ isAbstract: true })
abstract class BaseResolverHost {
floorPlanService: FloorPlanServiceInterface<FloorPlanVersion>;
constructor(readonly service: FloorPlanServiceInterface<FloorPlanVersion>) {
this.floorPlanService = service;
}
#Mutation(() => classRef, { name: `floorPlanCreate${version}` })
async floorPlanCreate(
#UserId() userId,
#Args('input')
input: CreateFloorPlanInput,
) {
return this.floorPlanService.create(userId, input);
}
#Query(() => classRef, { name: `floorPlan${version}` })
async floorPlan(#UserId() userId, #Args('id') id: string) {
return this.floorPlanService.findOne(userId, id);
}
#Mutation(() => classRef, { name: `floorPlanUpdate${version}` })
async floorPlanUpdate(
#UserId() userId,
#Args('id') id: string,
#Args('input') input: UpdateFloorPlanVersion<FloorPlanVersion>,
) {
return this.floorPlanService.update(userId, id, input);
}
}
return BaseResolverHost;
}
And then it is called like this:
#Resolver(() => FloorPlanV2)
export class FloorPlanV2Resolver extends BaseResolver(FloorPlanV2, 'V2') {
constructor(
readonly floorPlanService: FloorPlanService,
) {
super(floorPlanService);
}
}
So if the ObjectType was FloorPlanV2 we would expect the UpdateFloorPlanInputType to be UpdateFloorPlanV2Input

Assign fields of class with object keys in typescript/JS

I need to dynamically update the fields of this class dynamically with an object
export default class Foo {
private accessKey: string;
private workspaceId: string;
private api: AxiosInstance;
public bar: string;
public name: string;
...
...
private async fetch() {
try {
// data contains bar and name value
const { data } = await this.api.get("/");
// goal
this = {...this, ...data};
See goal comment, how can I do this dynamically?
Assignments and this
Why assigning to this is disallowed
Disregarding that it's not allowed, you don't want to reassign your this reference.
If it was allowed, we could write this confusing code:
const object = {
reassignSelf() {
this = {};
}
};
const ref = object;
object.reassignSelf();
/* Both ref and object are constant variables,
* so their values (references) should never change.
* But by reassigning `this` in object.reassignSelf,
* what value should this comparison produce?
*/
console.log(ref === object);
Then how to assign to this?
As implied earlier, we don't want to reassign this; we want to reassign its properties:
Static assignment. Example:
this.bar = data.bar;
this.name = data.name;
Dynamic assignment. Example:
Object.assign(this, data);
Use Object.assign
class X {
update(from: Partial<this>) {
Object.assign(this)
}
}

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;

How to set table name in #Entity dynamically?

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.

Externalize strings and use a formatter to substitute variables into string

I'd like to externalize some strings but still use some form of string substitution.
In some of my node based projects I've used:
var format = require('string-format');
format(Constants.COUNTRY_WEATHER_ENDPOINT, {
country: country
})
However In Typescript, I've been trying something like this ..
Error:
Cannot find name 'country'.at line 18 col 66 in repo/src/app/constants.ts
Constants.ts
export class Constants {
public static COUNTRY_WEATHER_ENDPOINT = `/api/country/${country}/weather`;
}
TestService.ts
import { Constants } from './constants';
export class TestService {
constructor(private $http) { }
public getWeather(country) {
let url = Constants.COUNTRY_WEATHER_ENDPOINT;
return this.$http.get(
url,
.then(response => response);
}
}
TestService.$inject = ['$http'];
Use arrow functions:
export const Constants: { readonly [name: string]: (key: string) => string } = {
countryWeatherEndpoint: country => `/api/country/${country}/weather`
}
Then do:
import { Constants } from "./constants";
// ...
const url = Constants.countryWeatherEndpoint(country);
// use your url
String interpolation in Typescript needs your variable country to exist when class is loaded. If you want to format later the constant string, you have to use normal quotes (single or double) and call format in your service.

Resources