I'm writing a class decorator for my controllers. It looks like:
export function Controller<T extends { new(...args: any[]): {} }> (ctor: T) {
return class extends ctor {
public readonly name = name;
}
}
ctor is a constructor of a class decorated with #Controller.
Full path to the controller's file is src/modules/{module}/controllers/{ctrl}Controller.ts. I need to get parts in curly braces and concatenate them into {module}.{ctrl}.
To do so I need a filepath of module from which ctor is imported. How can I obtain it?
There is no way to get file path information from ctor parameter. It's just a function that was defined somewhere.
Basically, module and ctrl preferably have to be provided to controller class on registration, since the path is known at this moment, i.e.:
for (const filename of filenames) {
const Ctrl = require(filename).default;
const [moduleName, ctrlName] = parseCtrlFilename(filename);
Ctrl._module = moduleName;
Ctrl._name = ctrlName;
}
The only and hacky workarount is to get file path of a place where Controller was called. This is achieved by getting stacktrace, e.g:
const caller = require('caller-callsite');
export function Controller<T extends { new(...args: any[]): {} }> (ctor: T) {
const fullPath = caller().getFileName();
...
}
The problem is that it's the path where Controller is called:
.../foo.ts
#Controller
export class Foo {...}
.../bar.ts
import { Foo } from '.../foo.ts';
// fullPath is still .../foo.ts
export class Bar extends Foo {}
A less hacky and more reliable way is to provide file path explicitly from the module where it is available:
#Controller(__filename)
export class Foo {...}
There is import.meta proposal which is supported by TypeScript. It depends on Node project configuration because it works with esnext target:
#Controller(import.meta)
export class Foo {...}
import.meta that was passed to #Controller can be consumed as meta.__dirname.
Related
I'm looking for a way to get the filename of a derived class from a base class in typescript running on node.js. An example of this would be:
Foo.ts
export abstract class Foo {
constructor() { }
name() { return (__filename); }
print() { console.log(this.name()); }
}
Bar.ts
import { Foo } from './Foo';
export class Bar extends Foo {
constructor() { super(); }
}
main.ts
import { Bar } from './Bar';
let bar = new Bar();
bar.print(); // should yield the location of Bar.ts
Due to the number of files involved and just cleanliness I'd like this to be confined to the Foo class rather than having an override of the name() function in each derived class.
I was able to sort-of solve this with the code:
private getDerivedFilePath(): string {
let errorStack: string[] = new Error().stack.split('\n');
let ret: string = __filename;
let baseClass: any = ThreadPoolThreadBase;
for (let i: number = 3; i < errorStack.length; i++) {
let filename: string = errorStack[i].slice(
errorStack[i].lastIndexOf('(') + 1,
Math.max(errorStack[i].lastIndexOf('.js'), errorStack[i].lastIndexOf('.ts')) + 3
);
let other: any = require(filename);
if (other.__proto__ === baseClass) {
ret = filename;
baseClass = other;
} else {
break;
}
}
return (ret || '');
}
Added to Foo, which will work when called from the constructor to set a private _filename property, for inheritance chains beyond the example above so long as the files are structured with a default export of the class being used. There may also be a caveat that if a base class from which a derived object is inheriting directly is initialized as a separate instance within the constructor of any member of the inheritance chain it could get confused and jump to another independent derived class - so it's a bit of a hacky work-around and I'd be interested if someone comes up with something better, but wanted to post this in case someone stumbles across this question and it works for them.
You can use require.cache to get all cached NodeModule objects and filter it to find your module.
https://nodejs.org/api/modules.html#requirecache
class ClassA {
public static getFilePath():string{
const nodeModule = this.getNodeModule();
return (nodeModule) ? nodeModule.filename : "";
}
public static getNodeModule(): NodeModule | undefined{
const nodeModule = Object.values(require.cache)
.filter((chl) => chl?.children.includes(module))
.filter((mn)=> mn?.filename.includes(this.name))
.shift();
return nodeModule;
}
}
class ClassB extends ClassA {
constructor(){}
}
const pathA = ClassA.getFilePath(); //Must return the absolute path of ClassA
const pathB = ClassB.getFilePath(); //Must return the absolute path of ClassB
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
I am trying to use a more object oriented approach with node.js "embedding" functions ( if that is the right word ) so that I can use functions and objects as if they are in the objects context. It might be easier to show in code.
I realise you can assign individual functions in the constructor - and this would work.. but I am not sure how to assign a whole module with functions to all the functions can access values in the objects context.
So , my question is : How can I assign a module to a class so that all the functions within the module can access everything within the objects context.
app.js
const myFunctions = require('./functions');
class myClass{
constructor() {
this.myFunctions = myFunctions ;
}
}
var mc = new myClass();
mc.myObject = { aaa: 'test'}
mc.myFunctions.outputValue(); // << should output the previous value set.
functions.js
function outputValue(){
console.log(this.myObject)
}
module.exports = {
outputValue
}
You could do it in two ways:
1 - Bound your class instance this to each one of the external functions:
class myClass {
constructor() {
this.myFunctions = {
outputValue: myFunctions.outputValue.bind(this),
};
}
}
2 - Define a method in your class to call the external functions, like:
class myClass {
constructor() {
}
callFunction(fnName) {
const fn = myFunctions[fnName];
if (fn) fn.apply(this);
}
}
Said that I will recommend avoiding using classes and this at all (at least it's completely necessary) and instead use pure functions, functions that only receive parameters does some processing and return some value.
The best way to do this which also follows the injection pattern,
const myClass = new myClass(myFunctions);
myClass.outputValue.bind(myClass);
Here it binds and inject all the class objects so it is accessible to other methods in different class .
Note : Look at "bind" usage.
I'm trying to use TypeScript to create a common library for a set of related web sites. I started off code like this:
module Lib {
export module Tools {
export class Opener {
public Path: string;
public static Open(): boolean { /* ... */ }
}
export class Closer { /* ... */ }
}
export module Controls {
export class InfoDisplay { /* ... */ }
export class Logon { /* ... */ }
}
export module Entities {
export class BigThing { /* ... */ }
export class LittleThing { /* ... */ }
}
}
var Initial: boolean = Lib.Tools.Opener.Open();
var CustomOpener: Lib.Tools.Opener = new Lib.Tools.Opener();
This worked quite well and allowed me to use some of TypeScript's nice features, like static methods and namespaced class names. As the project has grown, however, the need to use a module system for dependency resolution has become clear. My problem is that I'm a RequireJS noob so I can't quite figure out how get preserve the desirable features mentioned above to work in my project once RequireJS is in the mix. Here's my best attempt so far (which, to save space, only shows the code trail for Opener):
// ---- Opener.ts --------------------------------------------------------
/// <reference path="../typings/requirejs/require.d.ts"/>
class Opener {
public Path: string;
public static Open(): boolean { /* ... */ }
}
export = Opener;
// ---- Tools.ts --------------------------------------------------------
/// <reference path="../typings/requirejs/require.d.ts"/>
import Opener = require("./Opener");
import Closer = require("./Closer");
class Tools {
public Opener: Opener = new Opener();
public Closer: Closer = new Closer();
}
export = Tools;
// ---- ReqLib.ts --------------------------------------------------------
/// <reference path="../typings/requirejs/require.d.ts"/>
import Tools = require("./Tools");
class ReqLib {
public Tools: Tools = new Tools();
}
export = ReqLib;
// ---- App.ts --------------------------------------------------------
import ReqLib = require("./ReqLib");
var RL: ReqLib = new ReqLib();
var Initial: boolean = RL.Tools.Opener.Open(); // <== red squiggles
var CustomerOpener: ReqLib.Tools.Opener = new ReqLib.Tools.Opener(); // <== red squiggles
Visual Studio doesn't like the last two lines. It can't see the static method in the first line and it just flat out doesn't like the second because it looks like instances are being used as types. It's also the more troubling case because TypeScript kind of needs to have types to work with.
In your code RL.Tools.Opener is resolving to new Opener(); The static public static Open() does not exist on an instance but instead exists on the class Opener, hence the compiler error.
Suggestion : don't make it static. There might be other suggestions but now you know the reason for the error.
UPDATE
for new ReqLib.Tools.Opener(); you need to do new ReqLib().Tools.Opener;
I have a TypeScript d.ts file which I'm referencing from another file, but for some reason the exported class definitions don't seem to be recognised.
foo.d.ts
export declare class MyClass {
constructor();
public MyFunc(id: number): void;
}
bar.ts
/// <reference path="typings/MyClass.d.ts" />
class BarClass {
private something: MyClass;
constructor(thing: MyClass) {
this.something = thing;
}
}
That's about as simple an example I can give, but when doing this I get Could not find symbol 'MyClass'
I'm sure this used to work prior to updating TypeScript to the latest version, but on checking the breaking changes, I can't see anything which would cause the issue.
Does anyone have any ideas here?
Remove the export keyword. i.e
export declare class MyClass {
constructor();
public MyFunc(id: number): void;
}
to
declare class MyClass {
constructor();
public MyFunc(id: number): void;
}
Reason: The export keyword at the root of the file is reserved for external modules. A video on external modules : http://www.youtube.com/watch?v=KDrWLMUY0R0&hd=1