Bindings

Don't call it monkey patching 🙈

Bindings attach listener function calls to each other. Append or prepend a listener function to any invocation of another listener function.

Bindings automatically handle asynchronous functions. Functions execute in the order you specify, regardless of synchronicity.

Identifiers define bindings

The listener.bind function receives a bind to function identifier, followed by one or more bind from function identifiers.

For example, here we bind to myLib.a when it is called with an empty listener id:

import { listener } from "@listener-js/listener"

listener.bind([], "myLib.a", "myLib.b")

The bind to argument (myLib.a) must match exactly, which is why the binding only executes when myLib.a is called with an empty listener id.

The first argument to listener.bind is an identifier and does not affect the binding.

Bind to call with custom listener id

Here we bind to myLib.a when it is called with a custom listener id:

listener.bind([],
  ["myLib.a", "customId"],
  "myLib.b"
)

Bind to call with any listener id

Use asterisks to wildcard match on bind to identifiers.

Here we bind to myLib.a when it is called with any listener id:

listener.bind([],
  ["myLib.a", "**"],
  "myLib.b",
  "myLib.c"
)

Double asterisks (**) does a greedy match of any id and a single asterisk (*) matches any single id.

Asterisks may appear at either the beginning or end of the identifier, but not both ends or between named ids.

Binding from a library

Listener libraries use the listenerLoaded callback function to call listener.bind.

Call bind from a listener loaded callback and save it as bindExample.ts:

bindExample.ts
import { ListenerEvent } from "@listener-js/listener"

export class BindExample {

  public a(lid: string[]) {
    console.log(lid)
  }
  
  public b(lid: string[]) {
    console.log(lid)
  }
  
  public listenerLoaded(
    lid: string[],
    { instance, listener }: ListenerEvent
  ) {
    listener.bind(lid,
      [`${instance.id}.a`, "**"],
      `${instance.id}.b`
    )
  }
}

export default new BindExample()

In plain english "call bindExample.b after calling bindExample.a with any identifier."

Load the library, call the listener function, and save it as main.ts:

main.ts
import listener from "@listener-js/listener"
import bindExample from "./bindExample"

listener.load([], { bindExample })
bindExample.a([])

Execute main.ts and view the output:

$ ts-node main.ts
["bindExample.a"]
["bindExample.b", "bindExample.a"]

Bind options

Use options to change the execution of the bind from function in the following ways:

  • Execution order

  • Peek at original return value

  • Overwrite original return value

How to use bind options

Add options to an individual bind from function:

import listener from "@listener-js/listener"

listener.bind(lid,
  ["myLib.a"],
  ["myLib.b", { prepend: true }]
)

Add options to multiple bind from functions:

listener.bind(lid,
  ["myLib.a"],
  ["myLib.b", { prepend: true }],
  ["myLib.c", { peek: true }]
)

Execution order

Use the prepend option to bind myLib.b before myLib.a:

listener.bind(lid,
  ["myLib.a"],
  ["myLib.b", { prepend: true }]
)

The prepend / append options also may receive an index:

listener.bind(lid,
  ["myLib.a"],
  ["myLib.b", { append: 2 }]
)

Indices with the same value execute concurrently if the bind from functions have asynchronous return values.

Trueprepend/append options default to an index of 1.

A higher index means an earlier execution order for prepend, and later execution order for append.

Peek at original return value

The peek option passes the return value of the original bind to function as the first argument to the bind from function:

import { ListenerEvent } from "@listener-js/listener"

class MyLib {
  a(lid: string[]): boolean {
    return true
  }
  
  b(lid: string[], returnValue: boolean) {
    console.log(returnValue) // true
  }
  
  public listenerLoaded(
    lid: string[]
    { listener }: ListenerEvent
  ) {
    listener.bind(lid,
      ["myLib.a"],
      ["myLib.b", { peek: true }]
    )
  }
}

export default new MyLib()

Overwrite original return value

The return option allows a bind from function to overwrite the output of the bind to function:

class MyLib {
  a(lid: string[]): boolean {
    return true
  }
  
  b(lid: string[]) {
    return false
  }
  
  public listenerLoaded(
    lid: string[]
    { listener }: ListenerEvent
  ) {
    listener.bind(lid,
      ["myLib.a"],
      ["myLib.b", { return: true }]
    )
  }
}

export const myLib = new MyLib()

Now calls to myLib.a([]) will return false after myLib is processed by listener.load.

Intercept output

The intercept option enables both peek and return options.

Last updated