# o-method-proxy

> Utility to create configurable Proxies

Latest version **1.0.0** (published 2020-05-30) · ISC license · 0 weekly downloads

## Install

```sh
npm install o-method-proxy
pnpm add o-method-proxy
yarn add o-method-proxy
bun add o-method-proxy
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.0.0 |
| Published | 2020-05-30 |
| First published | 2020-05-30 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 0 |
| Unpacked size | 30.4 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Martin Rubi |
| Maintainers | haijindev |
| Keywords | utilities, helpers, proxy, metaprogramming, introspection, reflection |

## Links

- npm: https://www.npmjs.com/package/o-method-proxy
- Homepage: https://haijin-development@bitbucket.org/haijin-development/method-proxy.git
- npm.io page: https://npm.io/package/o-method-proxy

## Alternatives

- [lodash.assign](https://npm.io/package/lodash.assign.md) — 2.3M weekly downloads
- [lodash.chunk](https://npm.io/package/lodash.chunk.md) — 1.8M weekly downloads
- [react-native-ios-utilities](https://npm.io/package/react-native-ios-utilities.md) — 138.5K weekly downloads
- [@technically/lodash](https://npm.io/package/@technically/lodash.md) — 50.9K weekly downloads
- [@fluid-topics/ft-icon](https://npm.io/package/@fluid-topics/ft-icon.md) — 20.6K weekly downloads

## Recent versions

- 1.0.0 (latest) — 2020-05-30

## 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:

- [Transparent remote procedure calls](examples/remoteProcedureCall.js)
- Transparent lazy loading from a database, file or remote server
- Revertible object freezing
- Transparent persistency of objects to a database
- [Transparent logging of an object method call](examples/logging.js)
- [Mock and stub objects](examples/stubDouble.js)
- [Emulate traits and dynamically add behaviour to existing objects](examples/encryptingInMemory.js)
- [Implement dynamic public/private access to methods and properties](examples/privateMethods.js)
- Benchmark and trace method calls
- Transparent dynamic compilation or optimization of a method on its first call
- Implement syncronous Promises and Futures
- Transparent validation of methods parameters and object invariants

Take a look at the [examples](/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.

```javascript
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.

```javascript
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.

```javascript
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

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

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

```javascript
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.

```javascript
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.

```javascript
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](/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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler)

The complete protocol of ProxyMethod is as follows


```javascript
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

```javascript
// 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:

```javascript
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](examples/wrappingItUp.js) as a guide.

---
_Source: https://npm.io/package/o-method-proxy · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
