Get the names of the parameters of the wrapped function - javascript

I want to use the methods of the Minio class without specifying all their parameters, but substituting some of the parameters automatically. How do I do it...
I get all the class methods from the prototype of the Minio class and dynamically create wrappers for them in my class.
For each wrapper method, I get the parameter names from the original method of the Test class.
If there is one in the list of parameters that I want to omit when calling my wrapper method, then I add it to the list of arguments and call originalMethod.apply(this.minioClient, args).
Everything was fine until there were methods that were already wrapped.
I need to get the parameter list of the bucketExists method from outside the Minio class. Any idea how to get parameter names from such a wrapped method?
// minio/dist/main/helpers.js
exports function promisify(fn){
return function(){
const args = [...arguments];
fn.apply(this, args);
}
}
// minio/dist/main/minio.js
class Minio{
bucketExists(bucketName){
return bucketName;
}
methodThatNotWrappedByPromisifyAndWorksFine(bucketName){
return bucketName;
}
}
module.exports = Minio;
Minio.prototype.bucketExists = (0,helpers.promisify)(Minio.prototype.bucketExists)
I want to give an instance of my class with methods wrapped from the original class link the ability to work with only one bucket, that was passed to the my class constructor, without the ability to specify some other one after initialize.
My wrapper
const proxyHandler = () => {
return {
apply: (target, thisArg, argumentsList) => {
const funcParams = getMethodParamNames(target.source ? target.source.functionForWrap : target);
const bucketNameIndex = funcParams.indexOf("bucketName");
const regionNameIndex = funcParams.indexOf("region");
if (bucketNameIndex >= 0) {
argumentsList.splice(bucketNameIndex, 0, this.bucket.name);
}
if (regionNameIndex >= 0) {
argumentsList.splice(regionNameIndex, 0, this.bucket.region);
}
try {
return target.apply(this.minioClient, argumentsList);
} catch (error) {
logger.engine.error(`S3 '${this.bucket.name}' ${target} error: ${error.message}`, error.stack);
}
},
}
}
getMethods(this.minioClient).forEach(func => {
this[func] = new Proxy(this.minioClient[func], proxyHandler());
})
Solved the problem by overriding the method wrapper like this.
const MinioHelpers = require('minio/dist/main/helpers');
const origMinioPromisify = MinioHelpers.promisify;
MinioHelpers.promisify = (functionForWrap) => {
console.log("PATCHED!", functionForWrap);
var fn = origMinioPromisify(functionForWrap);
//from this i'll get all need information about wrapped function
fn.source = {
functionForWrap,
promisify: origMinioPromisify,
}
return fn
}
var Minio = require('minio');

Related

Decorator in Node - is it possible to iterate through class methods inside the constructor in order to override / apply a decorator to them

Up front I'm new to Node / Javascript an. What I am trying to do is to add a logging to my repository using a decorator function. Therefor I'm trying to iterate though each function from inside the constructor and override it with something like: "
Object.getOwnPropertyNames(Repository.prototype).forEach((func) => this.decorator(func));"
My problem is that "Object.getOwnPropertyNames" only returns the function names instead of the actual function. Is there a way to apply this decorator to each function?
"use strict"
const db = require("./Database/db_operations");
const logger = require("./utils/logger")
const {createTables} = require("./Database/db_operations");
const loggingTypes = require("./utils/logginTypes")
class Repository {
async saveTermin(Termin) {
}
async saveToDo(toDo) {
return await db.saveToDo(toDo);
}
async saveAppointment(Appointment) {
return await db.saveAppointment(Appointment);
}
async updateAppointment(Appointment) {
return await db.updateAppointment(Appointment);
}
async deleteAppointment(uuid) {
return await db.deleteAppointment(uuid);
}
async saveAppointmentParticipants(appointment) {
return await db.saveAppointmentParticipants(appointment);
}
async saveAppointmentFiles(appointment) {
return await db.saveAppointmentFiles(appointment)
}
async getAppointmentFiles(appointment) {
return await db.getAppointmentFiles(appointment)
}
async deleteToDo(todo) {
return await db.deleteToDo(todo)
}
}
// All functions will be mapped to there type to optimize logging. If a function is not mapped to its type,
// it will be automaticly assigned to the "unspecified type". Logging will still work, but depending on what
// arguments are given and what is returned, the output might not perfectly fit
const funcMapping = new Map();
// GET
funcMapping.set(Repository.prototype.getAppointmentFiles, loggingTypes.GET);
funcMapping.set(Repository.prototype.getAllDatas, loggingTypes.GET);
funcMapping.set(Repository.prototype.getAllToDos, loggingTypes.GET);
//SAVE
funcMapping.set(Repository.prototype.saveToDo, loggingTypes.SAVE);
funcMapping.set(Repository.prototype.saveAppointment, loggingTypes.SAVE);
funcMapping.set(Repository.prototype.saveAppointmentParticipants, loggingTypes.SAVE);
//DELETE
funcMapping.set(Repository.prototype.deleteAppointment, loggingTypes.DELETE);
funcMapping.set(Repository.prototype.deleteToDo, loggingTypes.DELETE);
Object.getOwnPropertyNames(Repository.prototype)
.forEach(name => {
const func = Repository.prototype[name];
// checking loggingTypes - if no type is assigned function will be assigned to "UNASSIGNED".
// console.log(funcMapping.has(func) +" "+ func.name)
if (!funcMapping.has(func)) {
funcMapping.set(func, loggingTypes.UNASSIGNED);
}
// function will only be wrapped if logging is enabled.
if (funcMapping.get(func)[1]) {
Repository.prototype[name] = async function (...args) {
// calls the original methode
const returnValue = await func.apply(this, args);
const argumentsInArray = Array.prototype.slice.call(args);
// Put any additional logic here and it will be applied -> magic
// Logging
db.writeLogging(logger(func, returnValue, funcMapping.get(func)[0]), args).then(() => {
console.log(`Function "${name}()" was successfully logged and saved to Database`)
}).catch(e => {
console.log(`Function "${name}()" could not be logged and saved to Database. ${func}`)
console.log(e)
})
return returnValue;
}
}
});
module.exports = new Repository();
const appointment_model = require('../models/Appointment');
const contact_model = require('../models/Contact');
const toDo_model = require('../models/ToDo');
const file_model = require('../models/File');
const loggingTypes = require("./logginTypes")
function log() {
// returns a function that returns an object. When this function is then called the object is returned
return function decorator(funcToLog, returnValue, funcType, ...args) {
// console.log("arguments in logger" + args);
// create prototype for object that later will be passed to database
const descriptor = function (user, change, changedAt) {
this.user = user; // some user id
this.change = change; //
this.changedAt = changedAt; // date when changes occoured
this.appointmentId = getUuid(appointment_model);
this.todoId = getUuid(toDo_model);
this.contactId = getUuid(contact_model);
this.fileId = getUuid(file_model);
};
// contains all logging Data about the function beeing called -> name of function, usedArguments and returnValue
function getChanges(func, funcType, returnValue, args) {
let changes = null;
switch (funcType) {
case loggingTypes.GET[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args},
newData: returnValue
}
break;
case loggingTypes.SAVE[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args}, // ?
newData: returnValue // could call function here
}
break;
case loggingTypes.UPDATE[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args},
newData: returnValue
}
break;
case loggingTypes.DELETE[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args},
newData: returnValue
}
break;
case loggingTypes.UNASSIGNED[0]:
changes = {
funcName: func.name, //
funcType: funcType, //
dataSetToChange: {...args},
newData: returnValue
}
}
return changes;
}
function getUuid(model_type) {
let uuid = null;
console.log(args)
for (let i = 0; i < args.length; i++) {
console.log(args[i])
if (args[i] instanceof model_type) {
uuid = parseInt(args[i].uuid);
}
return uuid;
}
}
return new descriptor("someUserId", JSON.stringify(getChanges(funcToLog, funcType, returnValue, args)), new Date())
}
}
module.exports = log();
You can easily map function names to their values using an intermediate step:
Object.getOwnPropertyNames(Repository.prototype)
.map(name => Repository.prototype[name])
.forEach((func) => this.decorator(func));
Anyway, the constructor is not the best place to do this, because you would end up applying the decorator every time a new instance of the class is created.
I would rather move the whole decorator logic after the class definition, before the assignment to module.exports.
Object.getOwnPropertyNames(Repository.prototype)
.forEach(name => {
const func = Repository.prototype[name];
Repository.prototype[name] = function (...args) {
console.log("Decorator was called");
const returnValue = func.apply(this, args);
// Put additional logging logic here...
return returnValue;
}
});
Update
In response to what noted in the comments, here is a somewhat more robust version of the code above, with additional precautions you may or may not need:
Preserve non-functions
Preserve non-value properties
Preserve the constructor
Preserve non-configurable properties
Include properties with symbol keys
Reflect.ownKeys(Repository.prototype).forEach(key => {
const descriptor = Reflect.getOwnPropertyDescriptor(Repository.prototype, key);
if (!descriptor.configurable) return;
const { value } = descriptor;
if (typeof value !== 'function') return;
if (value === Repository) return;
descriptor.value = function (...args) {
console.log("Decorator was called");
const returnValue = value.apply(this, args);
// Additional logging logic here...
return returnValue;
};
Object.defineProperty(Repository.prototype, key, descriptor);
});
Another thing I left out is additional logic to make sure that the decorated methods have the same length and name properties and the same prototype as the original functions. You may want to adjust even more details as you discover additional requirements while using your code.

odataclient.query is not a function in nodejs

I have function in my application which returns the student details by making an ODATA call.
However the below code returns "this.edmOdataClient.query is not a function" error.
value of Id that is passed to the function is
4B199,9h7dH,ATC3S,fDB5Y,h33Ny,kousB,lTibg,nuGM
Below is my code snippet
async getStudent(Id) {
try {
if (Id != undefined) {
let index: number;
for (index = 0; index < Id.length; index++) {
const element = Id[index];
console.log("ELEMENT" +element);
this.student = await this.OdataClient.get<any>
(
this.edmOdataClient
.query(`CD_STUDENT`)
.filter(new FilterClause("SECTION").eq("A"))
.andFilter(new FilterClause("ID").eq(element))
.select(["NAME", "GRADE"])
.orderBy("ID")
).then(result => result.value[0])
}
}
return this.student;
}
catch (error) {
logger.info(error.message)
return error;
}
Also is there a way to check result.value.length?
When am trying to do so am getting error that result is undefined
this is only something you can call if your function belongs to a class (also note that arrow functions dont respond to this unless you bind the function)
const externalFunc = () => {
console.log('external func')
}
class MyClass {
constructor () {
this.externalFunc = externalFunc.bind(this)
}
myFuncOne () {
console.log('func one')
}
myFuncTwo () {
console.log('func two')
this.myFuncOne()
}
}
const klass = new MyClass()
klass.myFuncTwo()
// => func two
// => func one
klass.externalFunc()
// => external func
in your code, essentially the error is saying that getStudent is unable to reach edmOdataClient because its not bound to this
whatever class your code is running within doesnt have access to edmOdataClient

How to create a method for a function?

I wish I would create something that will help me to get the last item from an array like this
$$("div", list).getLastItem()
my function is:
const $$ = (selector, parent = document) => {
x = parent.querySelectorAll(selector);
x = Array.prototype.slice.call(x);
return x;
}
I was thinking of how to do it, but I'm unable to figure it out, I've tried to do it like this:
$$.prototype.getLastItem = ()=>{
return this.slice(-1)[0];
}
but it's giving an error:
Uncaught TypeError: Cannot set property 'getLastItem' of undefined
1-How to implement this?
2-Why my code is not working?
Return an instance of a class which has a getLastItem method:
class Dollar {
constructor(selector, parent = document) {
this.elements = [...parent.querySelectorAll(selector)];
}
getLastItem() {
return this.elements.slice(-1)[0];
}
}
const $$ = (...args) => new Dollar(...args);
console.log($$('.something').getLastItem());
<div class='something'>something</div>
<div class='something'>something else</div>
To access the nth element, reference the instance's .elements[n] property.
If you also want to be able to access the underlying elements without going through .elements, assign to numeric properties in the constructor:
class Dollar {
constructor(selector, parent = document) {
this.elements = [...parent.querySelectorAll(selector)];
Object.assign(this, this.elements);
}
getLastItem() {
return this.elements.slice(-1)[0];
}
}
const $$ = (...args) => new Dollar(...args);
const somethings = $$('.something');
console.log(somethings[0]);
console.log(somethings.getLastItem());
<div class='something'>something</div>
<div class='something'>something else</div>
getLastItem should be bound to the prototype of Array:
Array.prototype.getLastItem = function () { ... }
I'm not sure if you just want to get the last item or require creating a method for a function.
If you just want to get the last item, can't you just do:
const lastItem = $$('div').slice(-1)[0];
That seems to get the last div just fine...
You could achieve what you expected right from your current solution with a some changes
use function to access the this context
use New-Agnostic Constructor Pattern (as described in David Herman’s Effective JavaScript) to construct object without the need of using new
return the instance itself (return this) to use further defined method
function $$(selector, parent = document) {
if (!(this instanceof $$)) {
return new $$(selector, parent);
}
const x = parent.querySelectorAll(selector);
this.selected = Array.prototype.slice.call(x);
return this;
}
$$.prototype.getLastItem = function() {
return this.selected.slice(-1)[0];
}
console.log($$("div").getLastItem())
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
You can simply move function getLastItem inside $$ and assign it to x as x.getLastItem, use return x.slice(-1)[0] and return x. It will work.
Try it below.
const $$ = (selector, parent = document) => {
let x = parent.querySelectorAll(selector);
x = Array.prototype.slice.call(x);
x.getLastItem = () => {
return x.slice(-1)[0];
}
return x;
}
console.log($$("div").getLastItem());
console.log($$("div")[0]);
<div id='test'></div>

Rx.Observable.bindCallback with scope in rxjs

It seems in rxjs 4.x, Rx.Observable.fromCallback accept scope as the second parameter, but in 5.0, this method is changed to Rx.Observable.bindCallback and doesn't accept scope parameter. How to add scope parameter in bindCallback. For example in ES6.
class Test {
constructor(input) {
this.input = input;
}
callback(cb) {
return cb(this.input);
}
rx() {
// this works on rx 4.x
// var observable = Rx.Observable.fromCallback(this.callback, this)();
// this doesn't work, because this.callback function doesn't use original this, so cannot get this.input
var observable = Rx.Observable.bindCallback(this.callback)();
// Work around: Rx.Observable.bindCallback(this.callback)();
// var me = this;
// var observable = Rx.Observable.bindCallback((cb) => {me.callback(cb);})();
observable.subscribe(
input => console.log('get data => ' + input),
err => console.log('get error =>' + err),
() => console.log('complete')
);
}
}
new Test(100).rx();
There is an example at http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#static-method-bindCallback which shows how to do this.
Use bindCallback on object method
const boundMethod = Rx.Observable.bindCallback(someObject.methodWithCallback);
boundMethod.call(someObject) // make sure methodWithCallback has access to someObject
.subscribe(subscriber);
You can call it immediately without declaring a variable, and also pass args like this:
Rx.Observable.bindCallback(someObject.callback).call(someObject,<args>)
So to bind to this you can simply call
Rx.Observable.bindCallback(this.callback).call(this,<args>)
It works for me, when I add this to the constructor
constructor(input) {
this.input = input;
this.callback = this.callback.bind(this)
}

Can I have multiple instances of a RequireJS Module?

I am obviously missing some concept/understanding and most definitely javascript OO basics!
I am loving using RequireJS, and my web app now looks more like a structured app now rather than a whole heap of crazy code.
I am just struggling to understand how/if the following is possible.
I have a module which acts as a base dataservice module called dataservice_base as follows:
define(['dataservices/dataservice'], function (dataservice) {
// Private: Route URL
this.route = '/api/route-not-set/';
var setRoute = function (setRoute) {
this.route = setRoute;
return;
}
// Private: Return route with/without id
var routeUrl = function (route, id) {
console.log('** Setting route to: ' + route);
return route + (id || "")
}
// Private: Returns all entities for given route
getAllEntities = function (callbacks) {
return dataservice.ajaxRequest('get', routeUrl())
.done(callbacks.success)
.fail(callbacks.error)
};
getEntitiesById = function (id, callbacks) {
return dataservice.ajaxRequest('get', routeUrl(this.route, id))
.done(callbacks.success)
.fail(callbacks.error)
};
putEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('put', routeUrl(this.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
};
postEntity = function (data, callbacks) {
return dataservice.ajaxRequest('post', routeUrl(this.route), data)
.done(callbacks.success)
.fail(callbacks.error)
};
deleteEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('delete', routeUrl(this.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
};
// Public: Return public interface
return {
setRoute: setRoute,
getAllEntities: getAllEntities,
getEntitiesById: getEntitiesById,
putEntity: putEntity,
postEntity: postEntity,
deleteEntity: deleteEntity
};
});
As you can see, I am referencing dataservices/dataservice, which is actually the core AJAX call mechanism (not shown, but really just basic jQuery ajax call in a wrapper).
What I am trying to do is allow this base dataservice module to be "instanced" as follows (within another module - snippet code only):
define(['dataservices/dataservice_base', 'dataservices/dataservice_base', 'dataservices/dataservice_base'], function (dataservice_profile, dataservice_qualifications, dataservice_subjects) {
// Set the service route(s)
dataservice_profile.setRoute('/api/profile/');
dataservice_qualifications.setRoute('/api/qualification/');
dataservice_subjects.setRoute('/api/subject/');
As you can see, I am trying to include the same dataservice_base(defined above) 3 times, but in the function references, I am trying to refer to each instance by named vars i.e:
dataservice_profile, dataservice_qualifications, dataservice_subjects
.. and of course I am trying be able to set a unique setRoute value for each of those instances to use further on in the module.. whilst leveraging the common calls (get,puts,posts etc).
Obviously I am missing a few things here.. but any help to point me back on the road would be very gratefully received!!
Kind Regards,
David.
I think you need to include your dependency only once and use the new keyword. Possibly you will need to refactor so that the common functions are in a depending module:
define(['dataservices/dataservice'], function (dataservice) {
var dataservice_profile = new dataservice();
var dataservice_qualifications = new dataservice();
var dataservice_subjects = new dataservice();
// Set the service route(s)
dataservice_profile.setRoute('/api/profile/');
dataservice_qualifications.setRoute('/api/qualification/');
dataservice_subjects.setRoute('/api/subject/');
// define needs to return something
return {
profile: dataservice_profile,
qualifications: dataservice_qualifications,
subjects: dataservice_subjects
};
});
Yes, brain-freeze or whatever.. problems of working alone sometimes!
So, as #asgoth mentioned, quite rightly had to clear my mind and think things through a bit!
I ended up with a re-factored dataservice_base module as follows:
define(['dataservices/dataservice'], function (dataservice) {
// Set any class/static vars
// Set the instance function
function dataservice_base(setRoute) {
var self = this;
self.route = setRoute;
console.log('setting route: ' + self.route);
function routeUrl(route, id) {
console.log('** Setting route to: ' + route);
return route + (id || "")
}
self.getAllEntities = function (callbacks) {
return dataservice.ajaxRequest('get', routeUrl())
.done(callbacks.success)
.fail(callbacks.error)
}
self.getEntitiesById = function (id, callbacks) {
return dataservice.ajaxRequest('get', routeUrl(self.route, id))
.done(callbacks.success)
.fail(callbacks.error)
}
self.putEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('put', routeUrl(self.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
}
self.postEntity = function (data, callbacks) {
return dataservice.ajaxRequest('post', routeUrl(self.route), data)
.done(callbacks.success)
.fail(callbacks.error)
}
self.deleteEntity = function (id, data, callbacks) {
return dataservice.ajaxRequest('delete', routeUrl(self.route, id), data)
.done(callbacks.success)
.fail(callbacks.error)
}
} // eof instance
return dataservice_base;
}
and of course again as #asgoth mentioned, I only need to of course include one reference to the dataservice_base module, and instance it for my needs as follows:
define(['dataservices/dataservice_base','viewmodels/viewmodel_profile', 'viewmodels/viewmodel_qualifications', 'viewmodels/viewmodel_subjects', 'app/common'], function (dataservice_base, viewmodel_profile, viewmodel_qualifications, viewmodel_subjects, common) {
var dataservice_profile = new dataservice_base('/api/profile/');
var dataservice_qualifications = new dataservice_base('/api/qualification/');
var dataservice_subjects = new dataservice_base('/api/subject/');
// do whatever now with those instance objects...
}
SO.. now all working!
I guess the only other thing I need to do is looking up about cleaning up process to ensure these objects are released.. however there will only ever be a few.. but still..
thanks again #asgoth
Just return a function instead of a object like this
return function(){
return {
// your public interface goes here
};
}
Now you can create new instances of your plugin with new componentName().

Categories