2

I have a class MongoQueryResolver which contains a dictionary:

private queries: {[key: string]: (params) => any} = {};

This dictionary holds functions by a key, where each function is a query function to MongoDB.

I have created a decorator MongoQuery which represents a mongo query function:

export function MongoQuery(queryName: string) {
    return function decorator(target, key, func) {
        target.register(queryName, func);
    }
}

This decorator calls MongoQueryResolver#register in order to register the query in the dictionary so I can use it by the query name.

Example of a MongoQuery function I created:

@MongoQuery(QueryType.GET_ALL_ENABLED)
public async getAllEnabled(params) {
    const workersDb = MongoService.getBranch(Branches.WORKERS).db("workers");
    const configCollection = await workersDb.collection('config');

    const criteria: any = {isEnabled: true};

    if (params.proxy) {
        criteria.proxy = params.proxy;
    }

    return await configCollection.find(criteria).toArray();
}

and how I use it outside:

MyRoutes.get('/get-enabled', async (req, res) => {
    const data = await MongoQueryResolver.resolve(QueryType.GET_ALL_ENABLED, {proxy: req.query.proxy});
    res.json(data);
});

The issue

When my application inits, I print the dictionary on set:

set GET_ALL_ENABLED {
  value: [Function: getAllEnabled],
  writable: true,
  enumerable: false,
  configurable: true
}
{
  GET_ALL_ENABLED: {
    value: [Function: getAllEnabled],
    writable: true,
    enumerable: false,
    configurable: true
  }
}

But when I get to use the dictionary, its empty ..

I mark the class as a singleton by export default new MongoQueryResolver()

Why does it happen? seems like it's a new instance somehow?

import MongoQueryResolver from '../../services/mongo/mongo-query-resolver'
Ben Beri
  • 1,101
  • 4
  • 23
  • 64
  • Can you show me how you import the `MongoQueryResolver` in the routes file? Also, it is generally not a good idea to use these `export default new class` as this is considered an antipattern. [read more here](https://stackoverflow.com/questions/39076190/differences-between-creating-a-new-class-to-using-export-const) and [here](https://stackoverflow.com/questions/38739499/anonymous-class-instance-is-it-a-bad-idea) – Tikkes Aug 17 '20 at 12:38
  • Edited from my memory, don't got the source on this pc right now but that's the main idea, I just want to understand why it doesn't work this way – Ben Beri Aug 17 '20 at 13:08
  • Can you show how `.register()` and `.resolve()` are implemented? – eol Aug 22 '20 at 08:12

1 Answers1

0

After debugging for some time, I figured out the issue.

There is one reason that this happens, I hope that I am right about this.

Before I start, let me show a small simplified example:

The decorator

export function MongoQuery(queryName: string) {
    return function decorator(target: any, key: any, func: any) {
        target.register(queryName, func);
    }
}

The Resolver Singleton

class Resolver {
     map: any;

    constructor() {
        this.map = {};
    } 
    
    register(queryName: any, func: any) {
        this.map[queryName] = func;
    }

    resolve(queryName: string) {
       this.map[queryName]();
    }

    @MongoQuery('myQuery')
    public testQuery() {
        console.log("test!");
    }
}
export default new Resolver();

index.ts

Resolver.resolve("myQuery");

The issue with this example which is very related to my original example, is that when the decorator runs on the class creation, it does not run the constructor, nor initializes class variables (if you do map = {} at the top)

Which means that map will only be set after the decorator runs.

To solve this, I will have to check in the register function if this.map exists, and if not, just set it.

register(queryName: any, func: any) {
    this.map = this.map || {};
    this.map[queryName] = func;
}

Now I will always have the map property set.

Now the only issue remaining is the resolve function, when you save the function in the dictionary, it looks like this:

 {
  value: [Function: testQuery],
  writable: true,
  enumerable: false,
  configurable: true
}

Which is why I get is not a function, so instead of this.map[queryName]() I have to run this.map[queryName].value() to run the function.

Hope this helps anyone !

Ben Beri
  • 1,101
  • 4
  • 23
  • 64