I have a JavaScript function in Node module like this (simplified):
index.js:
var BadRequestError = require('./error')
Guard.prototype = {
/**
*
* #param {String} property
* #return {_middleware}
*/
check: function (property) {
const _middleware = function _middleware (req, res, next) {
const ok = property === 'ping'
next(!ok ? new BadRequestError('doing_it_wrong', { message: `you're doing it wrong`}) : null)
}
return _middleware
}
}
module.exports = function (options) {
return new Guard(options)
}
error.js:
module.exports = function BadRequestError (code, error) {
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = error.message
this.code = code
this.status = 400
this.inner = error
}
util.inherits(module.exports, Error)
The usage is as follows:
test.js:
var guard = require('../index')({
someProperty: 'test',
})
var req = {}
guard.check('ping')(req, res, function (err) {
assert.deepStrictEqual(null, err)
})
I tried to create TypeScript typings for it using several approaches but none seems to work:
index.d.ts:
export declare interface IGuardOptions {
property: string
}
exporting via class declaration:
export declare class Guard {
constructor(options: IGuardOptions)
check(required: string | Array<string>): any
}
exporting via interface declaration:
export declare interface Guard {
check(property: string): any
}
export declare type GuardConstructor = new(options: IGuardOptions) => Guard
It's not clear what you mean by "not working" since you are not writing any Typescript code, just declarations, so I will assume that test.js should be test.ts to answer your question.
You'll want to create a type declaration along side the original Javascript source (like you have already done), but you need to make sure that the structure is equivalent to the original module. Your Typescript declarations indicate that there are multiple exports while the Javascript implementations only have a single export.
error.d.ts
declare class BadRequestError extends Error {
code: string;
status: number;
inner: Error;
constructor(code: string, error: Error);
}
export = BadRequestError;
index.d.ts
import {Handler} from 'express';
declare interface GuardOptions {
someProperty: string;
}
declare class Guard {
constructor(options: GuardOptions);
check(property: string): Handler;
}
declare function guardFactory(options: GuardOptions): Guard;
export = guardFactory;
test.ts
import {Request, Response} from 'express';
import guardFactory = require('./index');
const guard = guardFactory({
someProperty: 'test',
});
const middleware = guard.check('ping');
const req = {} as Request;
const res = {} as Response;
const next = function (err) {
assert.deepStrictEqual(null, err);
};
middleware(req, res, next);
Related
I'm using Express into a TypeScript project and I have the following situation
This is my route file
...
import findAllUsersFactory from "src/factory/FindAllUsers";
routes.get("/users", findAllUsersFactory().handle);
...
This is the factory where I do a sequence of injections
const findAllUsersFactory = () => {
const findAllUserRepository = new PrismaUsersRepository();
const findAllUsersBusiness = new FindAllUsersBusiness(findAllUserRepository);
const findAllUsersController = new FindAllUsersController(findAllUsersBusiness);
return findAllUsersController;
};
This is my Controller
class FindAllUsersController {
constructor(private findUserBusiness: FindAllUsersBusiness) { }
async handle(request: Request, response: Response) {
const allUsers = await this.findUserBusiness.execute();
return response.status(200).send({ allUsers });
}
}
And finally my Business
class FindAllUsersBusiness {
constructor(private usersRepository: IUsersRepository) {}
async execute() {
return this.usersRepository.findAll();
}
}
The problem is that I'm getting an error "Cannot read property 'execute' of undefined" because the findUserBusiness into handle function is undefined. And what I can't understand is that if I change my route to
routes.get("/users", (request, response) => {
findAllUsersFactory().handle(request, response);
});
it works
I've tried to log the functions, but I can say why findUserBusiness is undefined since it came from the constructor, and since the handle functions came from an instance of FindAllUsersController it should have it "defined"
You need to make some adjustments in order to adapt your factory to the way router.get expects its parameters.
const findAllUsersFactory = (req, res) => {
const findAllUserRepository = new PrismaUsersRepository();
const findAllUsersBusiness = new FindAllUsersBusiness(findAllUserRepository);
const findAllUsersController = new FindAllUsersController(findAllUsersBusiness);
return findAllUsersController.handle(req, res)
};
Then in your router you need to do the following:
routes.get("/users", findAllUsersFactory);
I have this:
LocationController.ts
import {GenericController} from './_genericController';
interface Response {
id : number,
code: string,
name: string,
type: string,
long: number,
lat: number
}
const fields = ['code','name','type','long','lat'];
class LocationController extends GenericController{
tableName:string = 'location';
fields:Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
}
export {get};
GenericController.ts
interface Response {
id : number
}
export class GenericController{
tableName:string = '';
fields:Array<any> = [];
_get = async (req, res) => {
try{
const id = req.body['id'];
const send = async () => {
const resp : Array<Response> = await db(this.tableName).select(this.fields).where('id', id)
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
}
await send();
}catch (error){
// some error handling
}
}
}
What I want to do is to pass the Response interface from LocationController to the GenericController parent, so that the response is typed accurately depending on how the child class has defined it. Clearly it doesn't work like this since the interface is defined outside of the class so the parent has no idea about the Response interface in the LocationController.ts file.
I've tried passing interface as an argument in the constructor, that doesn't work. So is there a way I can make this happen? I feel like I'm missing something really simple.
Typically, generics are used in a situation like this. Here's how I'd do it:
interface Response {
id: number;
}
// Note the generic parameter <R extends Response>
export class GenericController<R extends Response> {
tableName: string = "";
fields: Array<any> = [];
_get = async (req, res) => {
try {
const id = req.body["id"];
const send = async () => {
// The array is now properly typed. You don't know the exact type,
// but you do know the constraint - R is some type of `Response`
let resp: Array<R> = await db(this.tableName).select(this.fields).where("id", id);
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
};
await send();
} catch (error) {
// some error handling
}
};
}
import { GenericController } from "./_genericController";
interface Response {
id: number;
code: string;
name: string;
type: string;
long: number;
lat: number;
}
const fields = ["code", "name", "type", "long", "lat"];
// Here we tell the GenericController exactly what type of Response it's going to get
class LocationController extends GenericController<Response> {
tableName: string = "location";
fields: Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
};
export { get };
If this is not enough and you wish to somehow know the exact response type you're going to get, I believe the only way is a manual check. For example:
import { LocationResponse } from './locationController';
// ... stuff
// Manual runtime type check
if (this.tableName === 'location') {
// Manual cast
resp = resp as Array<LocationResponse>
}
// ...
You could also check the form of resp[0] (if (resp[0].hasOwnProperty('code')) { ... }) and cast accordingly. There are also nicer ways to write this, but the basic idea remains the same.
Generally, a properly written class should be unaware of any classes that inherit from it. Putting child-class-specific logic into your generic controller is a code smell. Though as always, it all depends on a particular situation.
I am trying to build a simple http app using node-express.
Issue when setting up routes, the constructor of the MyRouter class has this but it's lost in the getRoutes() function.
class Main {
public async run(): Promise<void> {
const myRouter = new MyRouter(this.config);
// this.server is express() during construct
this.server.use("/v1", myRouter.getRoutes);
await this.server.listen(this.config.rest.port);
}
}
class MyRouter {
private router: express.Router;
constructor(private config: any) {
this.router = express.Router();
console.log(this); // Prints the whole Router object!
}
public getRoutes(): express.Router {
console.log(this); // = "undefined" !
this.router.use("/test", otherClass.getRoutes);
return this.router;
}
}
Why is this?
The value of this depends not on where it is defined but by how a function is called. You did this:
this.server.use("/v1", myRouter.getRoutes);
This is equivalent to:
var tmp = myRouter.getRoutes;
this.server.use("/v1", tmp); // `this` will refer to the global object
There are two solutions. Either wrap it in an anonymous function to retain the object that calls the function:
this.server.use("/v1", function(){return myRouter.getRoutes()});
Or use .bind()
this.server.use("/v1", myRouter.getRoutes.bind(myRouter));
After server rebuild, compiller creates instanse in included api controller here:
NewController.ts
import express = require("express");
import INew = require("../interface/INew");
import NewRepository = require("../repositories/NewRepository");
class NewController {
private _newRepository: INew;
constructor() {
this._newRepository = new NewRepository();
this._newRepository.findById(5);
}
retrieve(req: express.Request, res: express.Response): void {
try {
console.log('-----------retrieve--------------------');
this._newRepository.findById(2);
}
catch (e) {
console.log(e);
}
}
}
Object.seal(NewController);
export = NewController;
constructor works: i see console message:
-------------NewRepository------------------
5 'RESULT'
NewRepository.ts:
import INew = require("../interface/INew");
import bluebird = require("bluebird");
class NewRepository implements INew {
sd: string;
constructor() {
console.log('-------------NewRepository------------------');
}
findById(id: number): void {
setTimeout(function () {
console.log(id, 'RESULT');
}, 3000);
}
}
export = NewRepository;
INew.ts
interface INew {
findById: (id: number) => void;
sd: string;
}
export = INew;
Buut when i use controller's method 'retrieve', visit rout '/new' then i get error [TypeError: Cannot read property '_newRepository' of undefined] instead : 2 'RESULT'
Angular 2 helps me with routing:
.............
getCarsRestful(): Promise<New[]> {
console.log('-------------------------------');
return this.http.get('api/new')
.toPromise()
.then(response => response.json())
.catch(this.handleError);
}
...................
and execute backend:
NewRoutes.ts
import express = require("express");
import NewController = require('../controllers/NewController');
var router = express.Router();
class NewRoutes {
private _newController: NewController;
constructor() {
this._newController = new NewController()
}
get routes() {
var controller = this._newController;
router.get("/new", controller.retrieve);
return router;
}
}
Object.seal(NewRoutes);
export = NewRoutes;
my created instanse '_newRepository' doesn't exist already, why? i get console log:
-----------retrieve--------------------
[TypeError: Cannot read property '_newRepository' of undefined]
Help please, how to make 'singltone' in ts
i don't want to create it in every controller's method, though, that works:
.................
retrieve(req: express.Request, res: express.Response): void {
try {
var _newRepository: INew;
_newRepository = new NewRepository();
_newRepository.findById(2);
.............
Try explicitly set this in router config:
router.get("/new", controller.retrieve.bind(controller));
I am stuck at some point working with angular 2 with node js. below is my code. It couldn't set rows(variable) to this.rowsdata (variable).
I think the reason behind is that node Js uses asynchronous call. May be that's why this.rows data get undefined for rows.
/// <reference path="../typings/node/node.d.ts" />
import { Component } from 'angular2/core';
import { OnInit } from 'angular2/core';
declare var module : any;
declare var jQuery : any;
var rowsdata : any[] = [];
// self = this;
interface ROWS {
name : string,
current_balance : number
}
#Component({
selector : 'portfolioList',
templateUrl : '../app/view/portfoliolist.html',
moduleId : module.id
})
export class PortfolioList implements OnInit {
// self : any = this;
constructor() {
// var self : this;
}
ngOnInit(): any {
this.showdata();
// console.log(this.rowsdata);
}
showdata() {
console.log("called");
var portfolioList = require('../app/api/showPortfolio.js');
portfolioList.showPortfolio(function(err:any, rows:any) {
console.log(rows);
// self.rowsdata = rows;
this.rowsdata = rows;
});
// console.log(self.rowsdata);
console.log(rowsdata);
}
}
here is the showPortfolio function
exports.showPortfolio = function(callback) {
db.all(squel .select()
.from("portfolios")
.field("name")
.field("current_balance") .toString() , function(err, rows) { callback(null, rows) });
}
In your code :
portfolioList.showPortfolio(function(err:any, rows:any) {
Change this to a fat arrow:
portfolioList.showPortfolio((err:any, rows:any) => {
More
https://basarat.gitbooks.io/typescript/content/docs/arrow-functions.html
i cant post a comment with all this code, so i post an answer.
it should work like this. i've put some extra logs.
showdata() {
console.log("called");
var _this = this;
console.log(_this);
var portfolioList = require('../app/api/showPortfolio.js');
portfolioList.showPortfolio(function(err:any, rows:any) {
console.log(rows);
console.log(_this);
_this.rowsdata = rows;
});
// console.log(self.rowsdata);
console.log(rowsdata);
}
}