Adding Swagger decorators to NestJS module for reuse - node.js

I'd like to create a shared library for reuse in a related Express NestJS app. In order to do so and gain type introspection + examples on nested objects in the input/output, I am struggling to maintain information from the imported package that works just fine.
Here's a sample of my efforts that work locally when I pass in this class directly on the controller. However, when running this from a file that has only this exported, I lose the examples + introspection. I believe there is either an error in my typescript build that is causing metadata/decorators to be cut out, or there is a problem in my nest build in the api file, that causes the types to be lost.
My typed response classes (abbreviated) are here (identical when in the file or imported):
export class ExtendedRelatedSubClass extends RelatedSubClass {
private static exampleSubClass: RelatedSubClass = getRelatedSubClassMock()
#ApiProperty({ example: ExtendedRelatedSubClass.exampleSubClass.randomProp })
public randomProp: string
}
export class ExtendedUserResponse extends UserResponse {
private static exampleUser: UserResponse = getUserMock()
#ApiProperty({ example: ExtendedAccountHolderResponse.exampleAccountHolder.address })
public address: ExtendedAddressResponse = new ExtendedAddressResponse()
#ApiProperty({ example: ExtendedAccountHolderResponse.exampleAccountHolder.legalInformation })
public relatedSubClass: ExtendedRelatedSubClass = new ExtendedRelatedSubClass()
}
The class is then used on a controller like so:
export const userFilteredProps = [
'randomSubClass',
] as const
export class FilteredUserResponse extends PickType(
ExtendedUserResponse,
userFilteredProps
) {}
#Controller('/:market/users')
export class UsersController {
constructor(protected readonly userService: UserService) {}
#Get('/')
#HttpCode(200)
public async getUser(
#Param() params: MarketParam,
): Promise<FilteredUserResponse> {
}
My attempt at creating an external module does not maintain this integrity of the examples/decorators and has this tsconfig (I completely lose typing and examples when it is imported into the old file):
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
My nest-cli file for swagger:
{
"collection": "#nestjs/schematics",
"sourceRoot": "src",
"entryFile": "create-swagger-app",
"compilerOptions": {
"plugins": [{
"name": "#nestjs/swagger",
"options": {
"controllerFileNameSuffix": ".controller.ts",
"classValidatorShim": true,
"dtoFileNameSuffix": [".response.ts", ".request.ts"]
}
}]
}
}
Are there any issues with how the file is being exported from the package? I have tried both nest build and yarn build with no success.
Could there be more parameters in my nest cli swagger build that are needed as well?

Related

How configure a local package for use in a NestJS app?

I cant import classes from a local package, please see summary below.
NestJS app
I created a NestJS app called exp1 with the cli:
nest new exp1
Then, add my local package
yarn add "file:../core"
Next, I want to import classes from the package. In app.controller I put
import { Feature } from '#me/core'
...
console.log(Feature)
So far so good, no warnings. But the console.log() output is undefined.
Package configuration
package.json
{
"name": "#me/core",
"main": "dist/index.js",
"types": "src/index.d.ts",
"files": [
"/dist",
"/src"
],
...
}
tsconfig.json
{
"compilerOptions": {
"module": "ES6",
"esModuleInterop": true,
"moduleResolution": "node",
"declaration": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true,
"lib": ["es2019",],
"target":"ES6"
},
"include": [
"src/**/*"
]
}
index.ts
export { Feature } from './Feature'
Feature.ts
export class Feature {
public type = 'unknown'
}
webpack.mix.js
const mix = require('laravel-mix');
mix.ts('src/index.ts', 'dist')
Running this successfully outputs dist/index.js, src/index.d.ts, src/Feature.d.ts
Clues
The typing seems to work in NestJS app
(new Feature).type // OK
(new Feature).typo // Warning
However when accessing Feature it is always undefined..
Not sure what I am missing. Is it correct to point package.json main attribute to dist/index.js file or should it point to index.ts?

ES2017 NEST JS #IsEmpty Unable to resolve signature of property decorator when called as an expression.This expression is not callable. is not empty

Hi people im newer with Nest JS and i trying to add dto validator but for example when i tried to add isNotEmpty or Max compiler show me this error:
Unable to resolve signature of property decorator when called as an
expression. This expression is not callable.
DTO:
import { Transform, Type } from 'class-transformer';
import { IsInt, isNotEmpty } from 'class-validator';
export class MessagesQueryDTO {
#isNotEmpty()
#IsInt()
#Type(() => Number)
readonly limit: number;
#isNotEmpty()
#Type(() => Number)
#IsInt()
readonly skip: number;
}
My config.json
{
"exclude": ["**/*spec.ts"],
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"esModuleInterop": true,
}
}
The isNotEmpty decorator should be IsNotEmpty with the first letter in uppercase.
To expand on Juan's answer, isNotEmpty is the internally used check from class-valdiator and can be used as a direct method. IsNotEmtpy (notice the capitalization) is the decorator, and should be used with # to denote it as a decorator.

How to use the Lit-Element query decorator?

Code fails Typescript compilation when attempting to use the Lit-Element query decorator.
With tsconfig.json
{
"compilerOptions": {
"target": "es2017",
"module": "es2015",
"moduleResolution": "node",
"lib": ["es2017", "dom"],
"outDir": "../../out/client",
"allowJs": true,
"experimentalDecorators": true
}
}
and package.json
"dependencies": {
"lit-element": "^2.2.1"
},
tsc command yields the following error:
Unable to resolve signature of property decorator when called as an expression.
Type '{ kind: string; placement: string; key: any; descriptor: any; }' is not assignable to type 'void'.
However, if I comment out the two lines of the decorator, and un-comment the equivalent get function, no errors.
#customElement("my-app")
export default class extends LitElement {
#query("p")
p: any;
// get p() {
// return this.shadowRoot.querySelector("p");
// }
render() {
return html`
Hello World!
<p>A paragraph</p>
`;
}
I expected identical behavior using either the query decorated property or the property getter.
Do you correctly import the query annotation?
The following element works for me:
/**
* Import LitElement base class, html helper function,
* and TypeScript decorators
**/
import {
LitElement, html, customElement, query
} from 'lit-element';
#customElement("my-app")
export default class extends LitElement {
#query("p")
p: any;
// get p() {
// return this.shadowRoot.querySelector("p");
// }
render() {
return html`
Hello World!
<p>A paragraph</p>
`;
}
}
With a tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es6",
"target": "es6",
"experimentalDecorators": true
}
}

TypeDI #Inject() doesn't work, but Container.get() does

I'm facing weird issue,
Container.get(MyServiceName); returns demanded service, yet class property decorated with #Inject() is undefined.
I've tried decorate class with #Service()
I do import reflect-metada at entry point of my application
import 'reflect-metadata';
I'm wondering if it may be related to fact, that that I'm using typedi not directly from my node_modules but from my dependency?
Application doesn't use Express framework neither routing controllers
My tsconfig.json:
"compilerOptions": {
"declaration": true,
"pretty": true,
"esModuleInterop": true,
"target": "esnext",
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": false,
"moduleResolution": "node",
"noUnusedParameters": true,
"strictPropertyInitialization": false,
"module": "commonjs",
"lib": ["dom", "es2018"],
"importHelpers": true,
"outDir": "./dist",
"strict": true,
"typeRoots": ["node_modules/#types"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true
},
"include": ["./src/**/*"],
"exclude": ["node_modules", "./dist/**/*"]
}
Service that I want to be injected
import MyServiceName from '../services/MyServiceName';
import { Container, Inject, Service } from 'my_dependency/lib';
export default class SomeClass{
#Inject()
private readonly someService: MyServiceName;
public async someMethod(): SomeResponseType {
const thatWorks = Container.get<MyServiceName>(MyServiceName);
console.log(this.someService); // undefined
console.log(thatWorks); // proper class
}
}
Service I want to inject
#Service()
export default class MyServiceName {
public async someMethod(): SomeReturnType {
return someReturnedData;
}
}
I would like to inject my dependencies though #Inject() decorator
You need to create your instance of SomeClass using Container#get, or the container won't be able to inject the property.
This worked for me:
// a.ts
import { Service } from "typedi";
#Service()
export class Foo {
foo() {
return 10;
}
}
// b.ts
import { Foo } from "./a";
import { Inject } from "typedi";
export class Bar {
#Inject() private readonly service: Foo
foo() {
console.log(this.service);
}
}
// test.ts
import "reflect-metadata";
import { Bar } from "./b";
import { Container } from "typedi";
const noFoo = new Bar();
noFoo.foo(); // yields undefined
const withFoo = Container.get<Bar>(Bar);
withFoo.foo(); // yields Foo

How to use LogEntries on Angular 2 applications (TypeScript)

I have this tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"outDir": "dist"
},
"exclude": [
"node_modules"
]
}
With dashboard.component.ts:
import { Component, OnInit } from '#angular/core';
import Logger = require('le_node');
var log = new Logger({
token:'abcdef-1234-ghijklm-5678-nopqrstuvwxyz'
});
#Component({
selector: 'dashboard',
templateUrl: 'app/dashboard.component.html'
})
export class DashboardComponent implements OnInit {
constructor() { }
ngOnInit(): void {
log.log('Test LogEntries...');
}
}
And run npm install le_node --save to install LogEntries. When tried to run the application with npm start, I am getting this error:
> sample-app#2.0.0 start /Users/user/Workspace/sample-app
> tsc && concurrently "tsc -w" "lite-server"
app/dashboard.component.ts(10,25): error TS2307: Cannot find module 'le_node'.
error TS2307: Cannot find module 'le_node'.
you only installed the module not its type definitions. If the module is not written in TypeScript (e.g. like angular) you need to get its types externally. That is what .d.ts files are for.
Fix
Quickfix in a file globals.d.ts:
declare module "le_node";
More
Covered here : https://basarat.gitbooks.io/typescript/content/docs/types/migrating.html
A solution that worked for me is just to not use the le_node module which is not browser friendly.
And use the logentries REST API
#Injectable()
export class LoggerService {
constructor(private http: Http) {
}
public log(level: string, name: string) {
// https://docs.logentries.com/docs/http-post
const LOGENTRIES_URL = "https://webhook.logentries.com/noformat/logs/";
let headers = new Headers();
headers.append("Content-Type", 'application/json');
let requestoptions = new RequestOptions({
method: RequestMethod.Post,
headers: headers,
body: JSON.stringify({ "level": level, "message": message })
});
this.http.request(LOGENTRIES_URL + YOUR_LE_TOKEN, requestoptions).toPromise().catch((error) => {
console.log("faield to send log to the logentries server");
});
}
}

Resources