Joanderson
Joanderson

Reputation: 3

Typescript lint doesn't recognize type transform decorator

I'm using nestjs with class validator to validate env variables, configuration.service.ts is getting its values from configService file which get what it needs from .env and put it in a json object.

In the port and timeout properties I expect to receive a string since it came from a .env file, the decorator @Type(() => Number) will try to convert whatever it receives into a Number. As far I'm concerned, the decorator only runs as the function call ends, so the function takes a string and returns a string and after that the string is casted into a Number.

The problem is the properties port and connectTimeoutMS expects a Number, the typescript lint claims it's receiving a string, looks like it's not recognizing the cast decorator. I want to know whether there's a way to get around this or I have to drop the cast decorator.

configuration.service.ts

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Type } from 'class-transformer';
import {
    IsBoolean,
    IsDefined,
    IsInt,
    IsNumber,
    IsString,
} from 'class-validator';
import { ValidatedConfigService } from 'src/config/utils/validate.config';
import { DatabaseType, Logger } from 'typeorm';

@Injectable()
export class DatabaseConfigService extends ValidatedConfigService {
    constructor(private configService: ConfigService) {
        super();
    }

    @IsString()
    @IsDefined()
    get type(): DatabaseType {
        return this.configService.get<DatabaseType>('database.type');
    }

    @IsString()
    @IsDefined()
    get host(): string {
        return this.configService.get<string>('database.host');
    }

    @IsInt()   
    @IsDefined()
    @Type(() => Number)
    get port(): string {
        return this.configService.get<string>('database.port');
    }
    
    @IsNumber()   
    @IsDefined()
    @Type(() => Number)
    get timeOut(): string {
        return (
            this.configService.get<string>('database.connectTimeoutMS') ||
            '2000'
        );
    }

    @IsString()
    @IsDefined()
    get entities(): string {
        return this.configService.get<string>('database.entities');
    }

  
}

database.configuration.ts

import { Injectable } from '@nestjs/common';
import { ConnectionOptions } from 'typeorm';
import { DatabaseConfigService } from './configuration.service';

export class DatabaseConfigController {
    constructor(private readonly configService: DatabaseConfigService) {}

    get settings(): ConnectionOptions {
        return {
            type: this.configService.type,
            host: this.configService.host,
            port: this.configService.port,
            connectTimeoutMS: this.configService.timeOut,
            entities: [this.configService.entities],           
        };
    }
}

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,    
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false
  }
}

[Edit 12/20/21]

I had to give up from using the @Type() decorator, as Micael Levi stated in his answer, this line is explicitly telling to TSC to expect a string get timeOut(): string.

So I have to change it to number, but doing that I also need to change the type I am returning inside the function to either any or casting it to a number.

This way gives an obvious type error.

    @IsInt()
    @IsDefined()
    get port(): number {       
        return this.configService.get<string>('db.port');
    }

Not specifying the type will not trigger the lint but at runtime will throw an error due to the IsInt() decorator because it's getting a string from database.configuration.ts

    @IsInt()
    @IsDefined()
    get port(): number {       
        return this.configService.get('db.port');
    }

The solution is casting it to a number by hand, this casting process also can be done in database.configuration.ts.

    @IsInt()
    @IsDefined()
    get port(): number {       
        return parseInt(this.configService.get<string>('db.port'), 10);
    }

Maybe I was using the @Type() decorator in a way that it is not intended to be used, tbh I never needed to use it, I just wanted to do all validations using this package.

Upvotes: 0

Views: 670

Answers (1)

Micael Levi
Micael Levi

Reputation: 6665

In the following line you're (not TS) telling to TSC that this.configService.timeOut is an string

get timeOut(): string

just change it to get timeOut(): number. Decorators won't type anything for you.

Upvotes: 2

Related Questions