callable

A callable object is a data structure that behaves as both an object and a function.

Download as .zip Download as .tar.gz View on GitHub

@lopatnov/callable

A TypeScript abstract base class that lets you create class instances that behave as callable functions. Extend Callable<TResult>, implement _call, and every new instance becomes directly invokable — with full prototype chain, type safety, and IDE completion preserved.

npm downloads npm version License GitHub issues GitHub stars


Table of Contents


Installation

npm install @lopatnov/callable

Browser (CDN via jsDelivr):

<script src="https://cdn.jsdelivr.net/npm/@lopatnov/callable/dist/byBind.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lopatnov/callable/dist/byCallee.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lopatnov/callable/dist/byClosure.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lopatnov/callable/dist/byProxy.umd.min.js"></script>

Usage

TypeScript / ES Modules

import CallableByBind from "@lopatnov/callable/byBind";
import CallableByCallee from "@lopatnov/callable/byCallee";
import CallableByClosure from "@lopatnov/callable/byClosure";
import CallableByProxy from "@lopatnov/callable/byProxy";

class Greeter extends CallableByBind<string> {
  _call(...args: any[]): string {
    return `Hello, ${args[0]}!`;
  }
}

const greet = new Greeter(); // instance is a callable function
console.log(greet("World")); // "Hello, World!"

CommonJS

const CallableByBind = require("@lopatnov/callable/byBind");

class Greeter extends CallableByBind {
  _call(...args) {
    return `Hello, ${args[0]}!`;
  }
}

const greet = new Greeter();
console.log(greet("World")); // "Hello, World!"

Browser UMD

After loading the script tag, the class is available as the global callable:

<script src="https://cdn.jsdelivr.net/npm/@lopatnov/callable/dist/byBind.umd.min.js"></script>
<script>
  class Greeter extends callable {
    _call(...args) {
      return `Hello, ${args[0]}!`;
    }
  }

  const greet = new Greeter();
  console.log(greet("World")); // "Hello, World!"
</script>

API

Abstract method

Every subclass must implement:

abstract _call(...args: any[]): TResult
Parameter Type Description
...args any[] Arguments passed when the instance is called as a function

Returns: TResult — the value returned to the caller.

Implementations comparison

Class Import path Mechanism Strict mode Modifies prototype
CallableByBind @lopatnov/callable/byBind Function.prototype.bind ✅ Yes ❌ No
CallableByCallee @lopatnov/callable/byCallee arguments.callee ❌ No (sloppy mode only) ❌ No
CallableByClosure @lopatnov/callable/byClosure Closure + Object.setPrototypeOf ✅ Yes ✅ Yes
CallableByProxy @lopatnov/callable/byProxy Proxy apply trap ✅ Yes ❌ No

CallableByBind<TResult>

Uses Function.prototype.bind to return a bound function from the constructor. The bound function delegates to _call on the original instance.

Pros: no deprecated APIs, no prototype modification, works in strict mode.

Cons: constructor returns a bound wrapper, not the raw instance.

import CallableByBind from "@lopatnov/callable/byBind";

class Multiplier extends CallableByBind<number> {
  constructor(private factor: number) {
    super();
  }
  _call(value: number): number {
    return value * this.factor;
  }
}

const triple = new Multiplier(3);
console.log(triple(7)); // 21

CallableByCallee<TResult>

Uses arguments.callee inside the dynamically constructed function body.

Pros: minimal implementation.

Cons: arguments.callee is forbidden in strict mode — not suitable for modern bundlers.

const CallableByCallee = require("@lopatnov/callable/byCallee");

class Adder extends CallableByCallee {
  _call(a, b) {
    return a + b;
  }
}

const add = new Adder();
console.log(add(2, 3)); // 5

CallableByClosure<TResult>

Creates an anonymous function in the constructor that closes over itself, then rewires the prototype chain with Object.setPrototypeOf.

Pros: works in strict mode, no bound wrapper.

Cons: calls Object.setPrototypeOf, which may affect JIT optimization.

import CallableByClosure from "@lopatnov/callable/byClosure";

class Counter extends CallableByClosure<void> {
  private count = 0;
  _call(): void {
    console.log(++this.count);
  }
}

const counter = new Counter();
counter(); // 1
counter(); // 2

CallableByProxy<TResult>

Wraps the constructed instance in an ES6 Proxy with an apply trap that delegates to _call.

Pros: clean ES2015+ approach, works in strict mode, no prototype modification.

Cons: adds a Proxy wrapper with a minor per-call overhead.

import CallableByProxy from "@lopatnov/callable/byProxy";

class Logger extends CallableByProxy<void> {
  _call(message: string): void {
    console.log(`[LOG] ${message}`);
  }
}

const log = new Logger();
log("Server started"); // [LOG] Server started

Distribution Formats

Each implementation is published in four formats:

Format File Use case
CommonJS dist/{name}.cjs Node.js require()
ES Module dist/{name}.esm.mjs Bundlers, native ESM import
UMD dist/{name}.umd.js Browser globals (with sourcemap)
UMD (minified) dist/{name}.umd.min.js Production browser

TypeScript declaration files (*.d.ts) are included for all four implementations.


Demo

Try it live:


Contributing

Contributions are welcome! Please read CONTRIBUTING.md before opening a pull request.


Built With


License

Apache-2.0 © 2019–2026 Oleksandr Lopatnov · LinkedIn