Options
All
  • Public
  • Public/Protected
  • All
Menu

patch-method

Build Status npm version Download Total code style: prettier dependencies devDependencies

This package allows you to patch class methods in a fully type-safe way. This is especially useful to create decorators that "mixin" methods.

Usage

patchMethod

Allows you to override any method on a given class. Enforces that the passed methodName actually is a method, and enforces that your hook fn has the same type signature as the original method.

The first argument to fn is a superMethod callback that is bound to the instance of the class. This way you can optionally call the original method implementation and also alter the arguments.

import patchMethod from 'patch-method'

class Foo {
  bar(value: number) {
    console.log(`Received: ${value}`)
    return number
  }
}

patchMethod(Foo, 'bar', function(superMethod, value) {
  console.log(`${this.constructor.name}#bar was called with ${value}.`)
  return superMethod(value + 1)
})

const foo = new Foo()
foo.bar(10)
// => 'Foo#bar was called with 10.'
// => 'Received: 11'
// => 11

beforeMethod

Register a hook to be executed before the original method is run. Gets passed the original arguments the method was called with. The return value of the hook is ignored.

import { beforeMethod } from 'patch-method'

class Foo {
  bar(value: number) {
    return number
  }
}

beforeMethod(Foo, 'bar', function(value) {
  console.log(`${this.constructor.name}#bar was called with ${value}.`)
})

const foo = new Foo()
foo.bar(10)
// => 'Foo#bar was called with 10.'
// => 'Received: 10'
// => 10

afterMethod

Register a hook to be executed right after the original method is run. Gets passed the original arguments the method was called with. Also gets passed the return value of the method as the first parameter. The return value of the hook is ignored.

import { afterMethod } from 'patch-method'

class Foo {
  bar(value: number) {
    return number
  }
}

afterMethod(Foo, 'bar', function(returnValue, value) {
  console.log(`${this.constructor.name}#bar was called with ${value}.`)
})

const foo = new Foo()
foo.bar(10)
// => 'Received: 10'
// => 'Foo#bar was called with 10.'
// => 10

Fallback function

Even though this library guarantees that you can only patch methods that the TypeScript compiler knows about, it can't actually guarantee that the method will exist at run time. For instance, you could be patching an incorrectly typed class, or something might have nuked the method at run time.

If you encounter this problem, you can pass a fourth parameter to the utility functions offered by this libarary. This optional fallback parameter accepts a function that has the same signature as the original super method it would substitute. If the original super method is missing at run time, the fallback function will be called in its place with this bound to the class instance.

class Foo {
  bar!: (value: string) => string
}

patchMethod(
  Foo,
  'bar',
  function(superMethod, value) {
    expect(this).toBeInstanceOf(Foo)
    expect(value).toEqual('test')
    return superMethod(value) + 'called'
  },
  function(value) {
    expect(this).toBeInstanceOf(Foo)
    expect(value).toEqual('test')
    return 'fallback' + value
  }
)

const foo = new Foo()

expect(foo.bar('test')).toEqual('fallbacktestcalled')

Acknowledgements

Many thanks to @dfreeman for his support making this util 100 % type-safe, and to the #e-typescript channel on the Ember Community Discord Discord

Index

Type aliases

Constructor

Constructor<T>: {}

A constructor function / class basically.

Type parameters

  • T

Type declaration

PropertiesOfType

PropertiesOfType<Obj, T>: Values<{}>

All keys of Obj that have a value of type T.

Type parameters

  • Obj

  • T

Values

Values<Obj>: Obj[keyof Obj]

All values on the object Obj.

Type parameters

  • Obj

Functions

afterMethod

  • afterMethod<Class, Instance, K, SuperMethod>(klass: Class, methodName: K, fn: (this: Instance, returnValue: ReturnType<SuperMethod>, ...args: Parameters<SuperMethod>) => any, fallback?: undefined | ((this: Instance, ...args: Parameters<SuperMethod>) => ReturnType<SuperMethod>)): Class
  • Executes the hook after the super method has been executed and passes the return value to the hook.

    class Foo {
      bar(hello: string): boolean {
        return true;
      }
    }
    
    afterMethod(Foo, 'bar', function(returnValue, hello) {
      console.log('Do something here.');
    });

    Type parameters

    Parameters

    • klass: Class

      The class to hook into.

    • methodName: K

      The name of the method on the class to hook into.

    • fn: (this: Instance, returnValue: ReturnType<SuperMethod>, ...args: Parameters<SuperMethod>) => any

      The hook to execute.

        • (this: Instance, returnValue: ReturnType<SuperMethod>, ...args: Parameters<SuperMethod>): any
        • Parameters

          • this: Instance
          • returnValue: ReturnType<SuperMethod>
          • Rest ...args: Parameters<SuperMethod>

          Returns any

    • Optional fallback: undefined | ((this: Instance, ...args: Parameters<SuperMethod>) => ReturnType<SuperMethod>)

      A fallback function to be called in case the original super method does not exist.

    Returns Class

beforeMethod

  • beforeMethod<Class, Instance, K, SuperMethod>(klass: Class, methodName: K, fn: (this: Instance, ...args: Parameters<SuperMethod>) => any, fallback?: undefined | ((this: Instance, ...args: Parameters<SuperMethod>) => ReturnType<SuperMethod>)): Class
  • Executes the hook before the super method is executed.

    class Foo {
      bar(hello: string): boolean {
        return true;
      }
    }
    
    beforeMethod(Foo, 'bar', function(hello) {
      console.log('Do something here.');
    });

    Type parameters

    Parameters

    • klass: Class

      The class to hook into.

    • methodName: K

      The name of the method on the class to hook into.

    • fn: (this: Instance, ...args: Parameters<SuperMethod>) => any

      The hook to execute.

        • (this: Instance, ...args: Parameters<SuperMethod>): any
        • Parameters

          • this: Instance
          • Rest ...args: Parameters<SuperMethod>

          Returns any

    • Optional fallback: undefined | ((this: Instance, ...args: Parameters<SuperMethod>) => ReturnType<SuperMethod>)

      A fallback function to be called in case the original super method does not exist.

    Returns Class

patchMethod

  • patchMethod<Class, Instance, K, SuperMethod>(klass: Class, methodName: K, fn: (this: Instance, superMethod: SuperMethod, ...args: Parameters<SuperMethod>) => ReturnType<SuperMethod>, fallback?: undefined | ((this: Instance, ...args: Parameters<SuperMethod>) => ReturnType<SuperMethod>)): Class
  • Allows you to easily hook into / extend a method on a class.

    class Foo {
      bar(hello: string): boolean {
        return true;
      }
    }
    
    patchMethod(Foo, 'bar', function(superMethod, hello) {
      console.log('Do something here.');
      return superMethod(hello);
    });

    Type parameters

    • Class: Constructor<any>

    • Instance: InstanceType<Class>

    • K: PropertiesOfType<Instance, Function>

    • SuperMethod: Extract<Instance[K], Function>

    Parameters

    • klass: Class

      The class to hook into.

    • methodName: K

      The name of the method on the class to hook into.

    • fn: (this: Instance, superMethod: SuperMethod, ...args: Parameters<SuperMethod>) => ReturnType<SuperMethod>

      The hook to execute.

        • (this: Instance, superMethod: SuperMethod, ...args: Parameters<SuperMethod>): ReturnType<SuperMethod>
        • Parameters

          • this: Instance
          • superMethod: SuperMethod
          • Rest ...args: Parameters<SuperMethod>

          Returns ReturnType<SuperMethod>

    • Optional fallback: undefined | ((this: Instance, ...args: Parameters<SuperMethod>) => ReturnType<SuperMethod>)

      A fallback function to be called in case the original super method does not exist.

    Returns Class

Legend

Generated using TypeDoc