Instantiate object after promisified import - node.js

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();
}));
...
}

Related

What is the difference between a singleton and a module that instantiates an instance?

I want to add a metrics class to my code. What is the difference between
class MyMetrics {
constructor () {
if (!Singleton.instance) {
Singleton.instance = new Metrics()
}
return Singleton.instance
}
}
const metrics = new MyMetrics()
and
export const metrics = new Metrics()
Wouldn't each module that imported metrics be using the same Metrics instance?
Are they functionally the same for my usage? Which would be recommended?
Wouldn't each module that imported metrics be using the same Metrics instance?
Yes, they would.
Are they functionally the same for my usage?
As long as A) you aren't creating other instances within the module and B) you aren't exporting Metrics, yes, almost. But one thing to remember is that any code that has access to your metrics import has access to your Metrics constructor, indirectly via the constructor property metrics inherits from Metrics.prototype:
import { metrics } from "./your-module.js";
const newMetrics = new metrics.constructor();
You might think that with the singleton, you've avoided that, but that's easily defeated as instance is public:
import { metrics } from "./your-module.js";
const Metrics = metrics.constructor;
Metrics.instance = null;
const newMetrics = new Metrics();
So you might want to make it private (either using a static private property, or just using metrics itself to check if you've created it).
Which would be recommended?
That's a matter of opinion. But you might not even need a class at all. You could:
Just make an object directly without a constructor function.
Export functions and close over module-private variables.
For instance, consider this (fairly silly) class:
// stuff.js
class Stuff {
#items = new Map();
constructor() {
// ...pretend there's singleton logic here...
}
put(key, value) {
this.#items.set(key, value);
}
get(key) {
return this.#items.get(key);
}
}
export const stuff = new Stuff();
The way you use that is:
import { stuff } from "./stuff.js";
stuff.put("this", "that");
stuff.get("this"); // "that"
You could just get rid of the class entirely:
// stuff.js
const items = new Map();
export const stuff = {
put(key, value) {
items.set(key, value);
},
get(key) {
return items.get(key);
}
};
Usage would be the same:
import { stuff } from "./stuff.js";
stuff.put("this", "that");
stuff.get("this"); // "that"
Or you could just export put and get:
// stuff.js
const items = new Map();
export const put = (key, value) => {
items.set(key, value);
};
export const get = (key) => items.get(key);
Then usage is:
import { get, put } from "./stuff.js";
put("this", "that");
get("this"); // "that"
Or if those names may conflict with other things:
import { get as getStuff, put as putStuff } from "./stuff.js";
putStuff("this", "that");
getStuff("this"); // "that"
So you have lots of options to choose from. Constructor functions (including ones created with class syntax) are useful if you need to construct multiple objects with shared characteristics, but if you aren't doing that (and you aren't with a singleton), just writing the object directly or (with modules) exporting the things it can do directly may be good options for you.

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

Stackable functions

I am looking for a library agnostic way to "stack" functions. The paradigm's I am used to is "middleware", where something happens within a function errors can be thrown, and a context (or req) global is used to attach new properties or change existing ones. These ideas are found in libraries like express, or type-graphql.
I am looking for some agnostic way to chain middleware, not dependent on these type of libraries.
Here's an example of the kinds of functions I have.
I am struggling with some kind of clean way to author functions. The global approach is not complimentary to proper typing using typescript, and isn't very functional.
Where the more functional approach lacks this kind of "chainablity", where I can simply have an array of functions like below.
// logs the start of middleware
context.utility.log(debug, ids.onLoad),
// fetches user by email submitted
context.potentialUser.fetchByEmail(SignupOnSubmitArgs),
// throws error if the user is found
context.potentialUser.errorContextPropPresent,
// checks if passowrd and reenterPassword match
context.potentialUser.signupPassword(SignupOnSubmitArgs),
// creates the user
context.user.create(SignupOnSubmitArgs, ''),
// thows error if create failed in some way
context.user.errorContextPropAbsent,
// adds user id to session
context.utility.login,
// redirects user to dashboard
context.utility.redirect(Pages2.dashboardManage)
Is there any tools / libraries out there that will allow be to author clear and clean chain-able functions, and glue them together in a stackable way?
Returning this is usually the way for being able to chain methods. I made you an example showing both sync and async functions:
class ChainedOperations {
constructor(private value: number){}
public add(n: number): this {
this.value += n;
return this;
}
public subtract(n: number): this {
this.value -= n;
return this;
}
public async send(): Promise<this> {
console.log(`Sending ${this.value} somewhere`);
return this;
}
}
async function somewhereElse(): Promise<void> {
const firstChain = await new ChainedOperations(1).add(1).subtract(1).send();
await firstChain.add(1).subtract(2).send()
}
somewhereElse().catch(e => { throw new Error(e) });
For better dealing with async functions you can use pipe pattern where you chain but also wait for the final result and pass it to the next guy:
abstract class Pipable {
public pipe(...functions: Function[]) {
return (input: any) => functions.reduce((chain, func: any) => chain.then(func.bind(this)), Promise.resolve(input));
}
}
class AClass extends Pipable {
constructor(private value: number){
super();
}
public add(n: number): number {
this.value += n;
return this.value;
}
public subtract(n: number): number {
this.value -= n;
return this.value;
}
public async send(): Promise<number> {
console.log(`Sending ${this.value} somewhere`);
return this.value;
}
}
async function Something(){
const myClass = new AClass(2);
const composition = await myClass.pipe(myClass.add, myClass.subtract, myClass.send)(2);
}
Something();
Some people don't like to start from beginning but work their way backwards from the last function. If you want that just replace .reduce with .reduceRight. If you like fancy names, starting from last is called Composing as opposed to piping.

What is a real-world way of allowing a mocha test to stub/mock fs so I can test a function without it accessing the disk?

So my code looks like this:
import FsAsyncFactory from '../fs/FsAsyncFactory'
import ObjectHelper from '../utils/ObjectHelper'
import Config from './Config'
export class ConfigHelper {
// this is the function under test
public static async primeCacheObj(): Promise<void> {
const configFileObj: any = await ConfigHelper.getConfigFileObj()
}
private static configCacheObj: Config = null
private static async getConfigFileObj(): Promise<any> {
const fsAsync: any = FsAsyncFactory.getFsAsync()
const fileContents: string = await fsAsync.readFileAsync('./config/hubnodeConfig.json')
return JSON.parse(fileContents)
}
}
export default ConfigHelper
What's the best way to allow my unit testing code to stop it from actually hitting the disk?
Should I be using something like InversifyJS to allow dependency injection? I'd have an init script that would setup the "defaults" for when the app is running normally then "override" those defaults in the tests?
In JavaScript, traditionally people have been using monkey patching to write unit tests.
I personally think that monkey patching is a bad practice and leads to unmaintainable code. I prefer dependency injection and that is why I created InversifyJS.
You can use InversifyJS to override bindings on unit tests.
This means that ConfigHelper will get an instance of FsAsync via dependency injection.
#injectable()
export class ConfigHelper {
#inject("FsAsync") private readonly _fsAsync: FsAsync;
// this is the function under test
public static async primeCacheObj(): Promise<void> {
const configFileObj: any = await ConfigHelper.getConfigFileObj()
}
private static configCacheObj: Config = null
private static async getConfigFileObj(): Promise<any> {
const json = await this_fsAsync.readFileAsync('./xxx.json')
return JSON.parse(json)
}
}
During the unit test you can replace the FsAsync type binding:
container.bind<FsAsync>("FsAsync")
.toDynamicValue(() => FsAsyncFactory.getFsAsync());
To inject a mock:
container.rebind<FsAsync>("FsAsync")
.toDynamicValue(() => ({
readFileAsync: (path) => {
return Promise.resolve("{ hardCodedJson: "happy days!" }");
}
}));

Node.js performance

Imagine, we got 2 simple classes (omitted export):
class SomeService {
async func () {
// some async function logic
return value;
}
}
class SomeController {
constructor (service) {
this.service = service;
}
async func () {
const value = await this.service.func();
// some controller-specific logic here
return value;
}
}
Also, we got simple function, that use this two classes in two ways.
Case one:
// require clasess
const somefunc = async () => {
const controller = new SomeController(new SomeService());
const value = await controller.func();
return value;
}
module.exports = somefunc;
Case two:
// require clasess
const controller = new SomeController(new SomeService());
const somefunc = async () => {
const value = await controller.func();
return value;
}
module.exports = somefunc;
As far as i understand how node's require works in the first case controller will be created each time when the somefunc is called. In the second case controller is created only one time, when the file will be evaluated. What case is better and more importantly what should i read or lookup to understand why?
I imagine the answer depends on your use-case. You have to ask yourself if you need to recreate SomeController every time that the function is called or not. Another thing to also consider is whether SomeController relies on something async when being initialized. For instance, if the controller needs access to a database connection in its constructor, then you'd have to wait for the database connection to be established. In situations like that, you may have no choice but to initialize SomeController inside the function. As I said, there's no objective better in cases like this. It's all dependent on your use-case.

Resources