npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

o-method-proxy

v1.0.0

Published

Utility to create configurable Proxies

Downloads

8

Readme

MethodProxy

Helper to create configurable Proxies.

What can I use a Proxy for?

Pretty much for anything you can possibly think of. Just try your best not to.

Here's a short list of topics where MethodProxy can be used:

Take a look at the examples for some interesting uses of the MethodProxy.

On the other hand the use of Proxies may have a cost in performance, it difficults debugging quite a lot and care needs to be taken in aspects like the use of instaceof, typeof, chaining proxies, getOwnProperties , etc.

Installation

npm install o-method-proxy

Usage

Overriding an object method by the method name

Override a single method by its name.

const MethodProxy = require('o-method-proxy')
const util = require('util')

const proxyDefinition = (proxy) => {
  proxy.on({
    method: 'setDescription', // override only the method 'setDescription'
    evaluate: function (proxy, target, methodName, args) {
      console.info(`setDescription was called on the object '${util.inspect(target)}' with arguments '${args}'`) // do something like logging the method call
      const targetMethod = targetObject[methodName]
      return Reflect.apply(targetMethod, proxy, args) // call the underlaying target method
    }
  })
}

// Get or create the target object
const targetObject = new Product()
// Wrap it with the proxy
const product = MethodProxy.on(targetObject, proxyDefinition)

// Then use the proxy as it if was the regular object
product.setDescription('A product')
product.getDescription()

Overriding many methods by name

Override many methods by name.

const MethodProxy = require('o-method-proxy')
const util = require('util')

const proxyDefinition = (proxy) => {
  proxy.on({
    method: 'setDescription',
    evaluate: function (proxy, target, methodName, args) {
      console.info(`setDescription was called on the object '${util.inspect(target)}' with arguments '${args}'`)
      const targetMethod = targetObject[methodName]
      return Reflect.apply(targetMethod, proxy, args)
    }
  })
  proxy.on({
    method: 'setName',
    evaluate: function (proxy, target, methodName, args) {
      console.info(`setName was called on the object '${util.inspect(target)}' with arguments '${args}'`)
      const targetMethod = targetObject[methodName]
      return Reflect.apply(targetMethod, proxy, args)
    }
  })
}

// Get or create the target object
const targetObject = new Product()
const product = MethodProxy.on(targetObject, proxyDefinition)

// Then use the proxy as it if was the regular object
product.setDescription('A product')
product.getDescription()

Overriding many methods by name at once

Override many method by its name at once sharing the same override function.

const MethodProxy = require('o-method-proxy')
const util = require('util')

const proxyDefinition = (proxy) => {
  proxy.on({
    methods: ['setDescription', 'setName']
    evaluate: function (proxy, target, methodName, args) {
      console.info(`${methodName} was called on the object '${util.inspect(target)}' with arguments '${args}'`)
      const targetMethod = targetObject[methodName]
      return Reflect.apply(targetMethod, proxy, args)
    }
  })
}

// Get or create the target object
const targetObject = new Product()
const product = MethodProxy.on(targetObject, proxyDefinition)

// Then use the proxy as it if was the regular object
product.setDescription('A product')
product.getDescription()

Overriding many methods matching a criteria

Override all the methods matching a criteria.

The matching block has the form

function(methodName, proxy, target) {
  return true|false
},

and it's expected to return true if the method should be overriden or false if not.

const MethodProxy = require('o-method-proxy')
const util = require('util')

const proxyDefinition = (proxy) => {
  proxy.on({
    methodMatching: (methodName, proxy, target) => { return methodName.startsWith('set') },
    evaluate: function (proxy, target, methodName, args) {
      console.info(`${methodName} was called on the object '${util.inspect(target)}' with arguments '${args}'`)
      const targetMethod = targetObject[methodName]
      return Reflect.apply(targetMethod, proxy, args)
    }
  })
}

// Get or create the target object
const targetObject = new Product()
const product = MethodProxy.on(targetObject, proxyDefinition)

// Then use the proxy as it if was the regular object
product.setDescription('A product')
product.getDescription()

Overriding absent methods

Override only the methods that are not defined in the target object.

const MethodProxy = require('o-method-proxy')
const util = require('util')

const proxyDefinition = (proxy) => {
  proxy.on({
    absentMethod: function (proxy, target, methodName, args) {
      throw new Error(`Target object '${util.inspect(target)}' does not implement the method ${methodName}`)
    }
  })
}

// Get or create the target object
const targetObject = new Product()
const product = MethodProxy.on(targetObject, proxyDefinition)

// Then use the proxy as it if was the regular object
product.setdescription('A product')
product.getDescription()

Overriding all methods

Override all the messages received by the target object, including both its absent and defined methods.

const MethodProxy = require('o-method-proxy')
const util = require('util')

const proxyDefinition = (proxy) => {
  proxy.on({
    allMethods: function (proxy, target, methodName, args) {
      console.info(`${methodName} was called on the object '${util.inspect(target)}' with arguments '${args}'`)
      const targetMethod = targetObject[methodName]
      return Reflect.apply(targetMethod, proxy, args)
    }
  })
}

// Get or create the target object
const targetObject = new Product()
const product = MethodProxy.on(targetObject, proxyDefinition)

// Then use the proxy as it if was the regular object
product.setdescription('A product')
product.getDescription()

Combining all the previous overrides

All the previous overrides can be combined in the same proxy.

The priority of the overrides is as follows:

  1. method: and methods: methods has the higher priority
  2. methodMatching:
  3. absentMethod:
  4. allMethods:

Take a look at the examples for some interesting uses of the MethodProxy.

Properties overrides

ProxyMethod can also override gets and sets of an object properties and/or the rest of the js Proxy handlers defined in the ECMAScript standard

The complete protocol of ProxyMethod is as follows

const MethodProxy = require('o-method-proxy')
const util = require('util')

const proxyDefinition = (proxy) => {
  /// Methods overrides

  proxy.on({
    method: 'setDescription',
    evaluate: function (proxy, target, methodName, args) {
      // ...
    }
  })

  proxy.on({
    methodMatching: (methodName, proxy, target) => { return methodName.startsWith('set') },
    evaluate: function (proxy, target, methodName, args) {
      // ...
    }
  })

  proxy.on({
    methodMatching: (methodName, proxy, target) => { return methodName.startsWith('set') },
    evaluate: function (proxy, target, methodName, args) {
      // ...
    }
  })

  proxy.on({
    absentMethod: function (proxy, target, methodName, args) {
      // ...
    }
  })

  proxy.on({
    allMethods: function (proxy, target, methodName, args) {
      // ...
    }
  })

  /// Properties overrides

  proxy.on({
    propertyGet: 'n',
    evaluate: function (proxy, target, propertyName) {
      // ...
    }
  })

  proxy.on({
    propertiesGet: ['n', 'm'],
    evaluate: function (proxy, target, propertyName) {
      // ...
    }
  })

  proxy.on({
    propertyGetMatching: function (propertyName) { return propertyName.startsWith('___') },
    evaluate: function (proxy, target, propertyName) {
      // ...
    }
  })

  proxy.on({
    absentPropertyGet: function (proxy, target, propertyName) {
      // ...
    }
  })

  proxy.on({
    allPropertiesGet: function (proxy, target, propertyName) {
      // ...
    }
  })

  proxy.on({
    propertySet: 'n',
    evaluate: function (proxy, target, propertyName) {
      // ...
    }
  })

  proxy.on({
    allPropertiesSet: function (proxy, target, propertyName, value) {
      // ...
    }
  })

  proxy.on({
    propertySet: 'n',
    evaluate: function (proxy, target, propertyName) {
      // ...
    }
  })

  proxy.on({
    absentPropertySet: function (proxy, target, propertyName, value) {
      // ...
    }
  })

  proxy.on({
    allPropertiesSet: function (proxy, target, propertyName, value) {
      // ...
    }
  })

  /// Handlers overrides

  proxy.on({
    getPrototypeOf: function (target) {
      // ...
    }
  })

  proxy.on({
    setPrototypeOf: function (target, objectPrototype) {
      // ...
    }
  })

  proxy.on({
    isExtensible: function (target) {
      // ...
    }
  })

  proxy.on({
    preventExtensions: function (target) {
      // ...
    }
  })

  proxy.on({
    getOwnPropertyDescriptor: function (target, property) {
      // ...
    }
  })

  proxy.on({
    defineProperty: function (target, property, descriptor) {
      // ...
    }
  })

  proxy.on({
    has: function (target, property) {
      // ...
    }
  })

  proxy.on({
    get: function (target, property, receiver) {
      // ...
    }
  })

  proxy.on({
    set: function (target, property, value) {
      // ...
    }
  })

  proxy.on({
    deleteProperty: function (target, property) {
      // ...
    }
  })

  proxy.on({
    ownKeys: function (target) {
      // ...
    }
  })

  proxy.on({
    apply: function (target, proxy, argumentsList) {
      // ...
    }
  })

  proxy.on({
    construct: function (target, argumentsList) {
      // ...
    }
  })
}

It is not necessary (or even advisable) to define each override. Define just the ones you need.

Proxy handler custom configuration

If for some reason you need the Proxy handler as its given to the Proxy constructor instead of calling MethodProxy.on() create the handler with MethodProxy.handler() and customize it or pass it along in your program

// Instead of creating the proxy object at once create the Proxy handler ...
const proxyHandler = MethodProxy.handler( (config) => {
  config.on({
    allMethods: function(proxy, target, methodName, args) {
      /// ...
    }
  })
})

// customize the handler to fit your needs ...
handler.set = function(...) { .... }
handler.getPrototypeOf = function(...) { .... }

// and create the proxy object with the customized handler
const proxy = new Proxy(target, handler)

Overriding apply

apply override only works on functions therefore the target object of the MethodProxy must be a function:

const proxyDefinition = (proxy) => {
  proxy.on({
    apply: function (target, proxy, argumentsList) {
      // ...
    }
  })
}
const targetObject = function () {} // <-- Function
const object = MethodProxy.on(targetObject, proxyDefinition)

Overriding both properties and methods

Overriding both properties and methods with a combination of allMethods|absentMethod and allPropertiesGet|allPropertiesSet|absentPropertyGet|absentPropertySet can be tricky because in js a method is a property of type function.

For example if you override both allMethods and allPropertiesGet it will only hook the allPropertiesGet because to call the method js first gets the object property and then it calls apply on it.

To make it work make sure that allPropertiesGet does not handle the methods you want to hook with allMethods, for example filtering the property by its name.

Recomendation

In this documentation and examples the behaviour of each proxy is inlined in the proxy definition.

That is to make the example more simple and concise but it's not a good practice.

It would be better to extract each MethodProxy definition to a class and I encourage you to do so.

You can use this example as a guide.