tsoa #Route decorator with variable dosen't works in express app - node.js

I can't find any information wh #Route decorator doesn't work with a variable instead of "string".
A swagger definition generated with code below is wrong.
export const PATH_GROUP = "/my-path";
export enum PATHS {
GetButtons = "/",
}
#Route(PATH_GROUP)
export class ButtonsController {
#Get(PATH_GROUP.GetButtons)
#Tags("Buttons")
public static async getButtons(): Promise<ButtonViewModel[]> {
const buttons = await buttonsService.getAllButtons();
return buttons.map(button => button.toAddToViewModel());
}
}
When I generate a new swagger definition it looks like:
If I use strings instead of variable/enum everything works correctly.
#Route("/my-path")
export class ButtonsController {
#Get("/")
#Tags("Buttons")
public static async getButtons(): Promise<ButtonViewModel[]> {
const buttons = await buttonsService.getAllButtons();
return buttons.map(button => button.toAddToViewModel());
}
}
Is there any information about not using variables in tsoa decorators?

Related

TypeError: class is not a constructor (TypeScript)

I'm creating an Node.JS API, using Typescript v4.9.4, and Module Alias v2.2.2
There is a factory that creates the controller SignUp like this:
import { SignUpController } from '#/presentation/controllers'
import { type Controller } from '#/presentation/protocols'
import { makeDbAuthentication, makeDbAddUser } from '#/main/factories/usecases'
import { makeSignUpValidator } from './make-sign-up-validator-factory'
export const makeSignUpController = (): Controller => {
const controller = new SignUpController(makeDbAddUser(), makeSignUpValidator(), makeDbAuthentication())
return controller
}
I have a problem on the makeDbAddUser() that has this code:
import { DbAddUser } from '#/data/usecases'
import { type AddUser } from '#/domain/usecases'
import { UserMongoRepository } from '#/infra/db/mongodb/user-mongo-repository'
import { BcryptAdapter } from '#/infra/cryptography'
export const makeDbAddUser = (): AddUser => {
const salt = 12
const bcryptAdapter = new BcryptAdapter(salt)
const userMongoRepository = new UserMongoRepository()
return new DbAddUser(bcryptAdapter, userMongoRepository, userMongoRepository)
}
The error occurs on the line where new UserMongoRepository() is created.
const userMongoRepository = new db_1.UserMongoRepository();
^
TypeError: db_1.UserMongoRepository is not a constructor
And here is the UserMongoRepository class:
export class UserMongoRepository implements AddUserRepository, LoadUserByEmailRepository, CheckUserByEmailRepository, UpdateAccessTokenRepository {
// eslint-disable-next-line #typescript-eslint/no-useless-constructor
constructor () {}
async add (data: AddUserRepository.Params): Promise<AddUserRepository.Result> {
//code...
}
// other methods
}
To me everything seems fine, I have other classes and factories that I use in the same way. I'm probably missing something on the impor/export maybe? But I dont really know where to start looking anymore.
I already tried adding a constructor, even empty on my Class, but the error persists.
Also, tried the solutions on this thread, about a similar problem. Putting the export { UserMongoRepository } in the end of the file.
As I'm using ModuleAlias to have better import names, I tried without # like so:
import { UserMongoRepository } from '../../../infra/db/mongodb/user-mongo-repository'
But the problem persists.

Node.js - Is it a good practice to export all files in index.js for each directory?

// configs/index.js
module.exports = {
AppConfig: require('./app-config'),
AuthConfig: require('./auth-config'),
DbConfig: require('./database-config'),
};
// controllers/some-controller.js
const { AppConfig } = require('../configs');
// tests/some-test.spec.js
// it fails because the controller require() the root index.js and it runs all exported files implicitly.
const SomeController = require('../controllers/some-controller');
The above example works well in development, but it fails in unit testing using mocha because it runs all exported files implicitly. My workaround is to import the file directly like this require('../configs/app-config'). What is your preferred solution? Should I export all files at all? Is it a good practice?
Instead of exporting/importing each of the "sub-configs", you could create a ConfigService-class that encapsulates access to the config files. Something like:
const lodash = require('lodash');
export class ConfigService {
static configuration = ConfigService.initConfig();
static get(key) {
return _.get(ConfigService.configuration, key);
}
// you can remove this if you don't want to support adding/changing configs at runtime
static add(key, val) {
if (ConfigService.configuration[key]) {
throw new Error(`Could not add a new configuration key <${key}>. This key exists already!`);
}
_.set(ConfigService.configuration, key, val);
}
static initConfig() {
ConfigService.configuration = {
AppConfig: require('./app-config'),
AuthConfig: require('./auth-config'),
DbConfig: require('./database-config'),
}
}
}
Usage is rather simply and it even allows you to mock config values (by stubbing the get-method) in your unit-tests:
class DbController {
constructor() {
this.dbHost = ConfigService.get('DbConfig').dbHost;
// ...
}
}

How to create a NestJs Pipe with a config object and dependency?

I would Like to pass a configuration string to a Pipe but also want to inject a service. The NesJs docs describe how to do both of these independent of each other but not together. Take the following example:
pipe.ts
#Injectable()
export class FileExistsPipe implements PipeTransform {
constructor(private filePath: string, db: DatabaseService) { }
async transform(value: any, metadata: ArgumentMetadata) {
const path = value[this.filePath];
const doesExist = await this.db.file(path).exists()
if(!doesExist) throw new BadRequestException();
return value;
}
}
controller.ts
#Controller('transcode')
export class TranscodeController {
#Post()
async transcode (
#Body( new FileExistsPipe('input')) transcodeRequest: JobRequest) {
return await this.videoProducer.addJob(transcodeRequest);
}
Basically, I want to be able to pass a property name to my pipe (e.g.'input') and then have the pipe look up the value of the property in the request (e.g.const path = value[this.filePath]) and then look to see if the file exists or not in the database. If it doesn't, throw a Bad Request error, otherwise continue.
The issue I am facing is that I need NestJs to inject my DataBaseService. With the current example, It won't and my IDE gives me an error that new FileExistsPipe('input') only has one argument passed but was expecting two (e.g. DatabaseService).
Is there anyway to achieve this?
EDIT: I just checked your repo (sorry for missing it before). Your DatabaseService is undefined in the FIleExistPipe because you use the pipe in AppController. AppController will be resolved before the DatabaseModule gets resolved. You can use forwardRef() to inject the DatabaseService in your pipe if you are going to use the pipe in AppController. The good practice here is to have feature controllers provided in feature modules.
export const FileExistPipe: (filePath: string) => PipeTransform = memoize(
createFileExistPipe
);
function createFileExistPipe(filePath: string): Type<PipeTransform> {
class MixinFileExistPipe implements PipeTransform {
constructor(
// use forwardRef here
#Inject(forwardRef(() => DatabaseService)) private db: DatabaseService
) {
console.log(db);
}
async transform(value: ITranscodeRequest, metadata: ArgumentMetadata) {
console.log(filePath, this.db);
const doesExist = await this.db.checkFileExists(filePath);
if (!doesExist) throw new BadRequestException();
return value;
}
}
return mixin(MixinFileExistPipe);
}
You can achieve this with Mixin. Instead of exporting an injectable class, you'd export a factory function that would return such class.
export const FileExistPipe: (filePath: string) => PipeTransform = memoize(createFileExistPipe);
function createFileExistPipe(filePath: string) {
class MixinFileExistPipe implements PipeTransform {
constructor(private db: DatabaseService) {}
...
}
return mixin(MixinFileExistPipe);
}
memoize is just a simple function to cache the created mixin-pipe with the filePath. So for each filePath, you'd only have a single version of that pipe.
mixin is a helper function imported from nestjs/common which will wrap the MixinFileExistPipe class and make the DI container available (so DatabaseService can be injected).
Usage:
#Controller('transcode')
export class TranscodeController {
#Post()
async transcode (
// notice, there's no "new"
#Body(FileExistsPipe('input')) transcodeRequest: JobRequest) {
return await this.videoProducer.addJob(transcodeRequest);
}
a mixin guard injecting the MongoDB Connection
the console shows the connection being logged

Adonis: ReferenceError view is not defined

I have controller like this
class TicketController {
index(){
return view.render('tickets')
}
}
and create file in resource\view\tickets.edge and my route is
const Route = use('Route')
Route.resource('tickets', 'TicketController');
when I go to http://127.0.0.1:3333/tickets show me this error
ReferenceError
view is not defined
You need to use view object from http context :
index ({ view }) {
return view.render('hello-world')
}
Adonis documentation example
I had forgotten to import view class and fix it by this code:
const view = use('View');
class TicketController {
index(){
return view.render('tickets')
}
}

Instantiate object after promisified import

I am struggling to instantiate object from dynamically imported classes. Basically I have some plugins which kinda look like this:
export interface IPlugin {
compile(logEvent: LogEventInfo): string;
}
export class DatePlugin implements IPlugin {
public compile(logEvent: LogEventInfo): string {
const date: Date = new Date();
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
}
}
In another file I want to dynamically crawl a folder, load all source files and instantiate them. I saw that import(...).then() can return a loaded object however in my case it returns the class and my object creation starts looking very ugly:
public async loadPlugins(): Promise<void> {
// ...
await Promise.all(pluginFiles.map(async (pluginFile: string): Promise<void> => {
const pluginFilePath: string = path.join(pluginsFolder, pluginFile);
import(pluginFilePath).then((plugin: any): void => {
const obj: IPlugin = (new plugin[Object.keys(plugin)[0]]() as IPlugin;
// ...
});
}));
}
Isn't there any better way to instantiate all those classes when loading them?
import() promises aren't chained, this is a mistake similar to this case that may result in problems with error handling and race conditions.
map shares a common potential problem with this case. It's used only to provide promises to wait for them, but not actual values. Since the purpose of async function call is to get class instance, it's reasonable to map pluginFile input to obj output value if it's supposed to be stored then - or compile result if it isn't:
public async loadPlugins(): Promise<...> {
const plugins = await Promise.all(pluginFiles.map(async (pluginFile: string): Promise<IPlugin> => {
const pluginFilePath: string = path.join(pluginsFolder, pluginFile);
const pluginExports = await import(pluginFilePath);
// preferably pluginExports.default export to not rely on keys order
const Plugin: { new(): IPlugin } = Object.values(pluginExports)[0];
return new Plugin();
}));
...
}
The only real benefit that import provides here is that it's future-proof, it can seamlessly be used natively in Node.js with third-party ES modules (.mjs) files. Since TypeScript is used any way and uses require for ES module imports under the hood, it may be reasonable to discard asynchronous routine and use require synchronously instead of import for dynamic imports:
public loadPlugins(): <...> {
const plugins = pluginFiles.map((pluginFile: string): IPlugin => {
const pluginFilePath: string = path.join(pluginsFolder, pluginFile);
const pluginExports = require(pluginFilePath);
// preferably pluginExports.default export to not rely on keys order
const Plugin: { new(): IPlugin } = Object.values(pluginExports)[0];
return new Plugin();
}));
...
}

Resources