Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@darkobits/class-decorator

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@darkobits/class-decorator

Decorator factories for classes and class methods.

  • 4.0.0
  • latest
  • npm
  • Socket score

Version published
Weekly downloads
3
increased by50%
Maintainers
1
Weekly downloads
 
Created
Source

class-decorator

This package attempts to improve the way classes are decorated (see: decorator proposal) by not polluting the prototype chain, encouraging composition over inheritance.

Install

This package requires @babel/plugin-proposal-decorators.

$ npm i -D @babel/plugin-proposal-decorators
$ npm i @darkobits/class-decorator

Then, update your .babelrc file:

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", {
      "legacy": true
    }]
  ]
}

Use

ClassDecorator

This package's default export is a function that accepts a decorator implementation function and returns a decorator that may be applied to a class. The decorator implementation function is invoked with the target class. If the decorator implementation returns a function, that function will be used as a proxy constructor for the decorated class. The proxy constructor will be passed an object with the following shape:

{
  // Invoke this function to call the decorated class' original constructor.
  constructor: Function;
  // Array of any arguments passed to the constructor.
  args: Array<any>;
}

Example:

In this example, we will create a higher-order decorator that accepts a list of superpowers to apply to class instances.

First, we will look at how this is done using the typical approach, then how to accomplish it using this package.

function AddSuperpowers (...powers) {
  return function (Ctor: Function): Function {
    return class AddSuperPowers extends Ctor {
      constructor(...args) {
        super(...args);

        powers.forEach(power => {
          this[power] = true;
        });
      }

      hasSuperpower(power: string): boolean {
        return this[power];
      }
    }
  };
}

@AddSuperpowers('strength', 'speed', 'flight')
class Person {
  constructor(name) {
    this.name = name;
  }

  getName(): string {
    return this.name;
  }
}

const bob = new Person('Bob');

bob.strength; //=> true
bob.speed; //=> true
bob.flight; //=> true

This is great, but if we examine the prototype chain of the bob instance, it will look something like this:

bob: {
  name: 'Bob'
  strength: true
  speed: true
  flight: true
  [[Prototype]] => AddSuperpowers: {
}                    hasSuperpower()
                     [[Prototype]] => Person: {
                   }                    getName()
                                        [[Prototype]] => Object
                                      }

If we used 5 decorators on the Person class, we would find 5 degrees of inheritance added to each instance of Person. Decorators should faciliate composition, not exacerbate existing issues with inheritance. You might also notice that our decorator's prototype inherits from our original class, meaning that consumers of our decorator will not be able to shadow properties or methods applied by the decorator. This is bad behavior; the decorated class should always retain the ability to shadow properties set by ancestors and decorators.

Let's see how with a few modifications we can improve this situation:

import ClassDecorator from '@darkobits/class-decorator';

const AddSuperpowers = (...powers: Array<any>): Function => ClassDecorator(Ctor => {
  // Add hasSuperpower to the decorated class.
  Ctor.prototype.hasSuperpower = function (power: string): boolean {
    return this[power];
  };

  // Return a proxy constructor. The proxy constructor will be invoked with an
  // object with the following shape:
  return function ({constructor, args:}: {constructor: Function; args: Array<any>}): void {
    powers.forEach(power => {
      this[power] = true;
    });

    // Call the original constructor, forwarding any arguments provided to the
    // proxy constructor.
    constructor(...args);
  }
});


@AddSuperpowers('strength', 'speed', 'flight')
class Person {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  getName(): string {
    return this.name;
  }
}

const bob = new Person('Bob');

If we looked at the protoype chain for this instance of bob, we would see:

bob: {
  name: 'Bob'
  strength: true
  speed: true
  flight: true
  [[Prototype]] => Person: {
}                    getName()
                     hasSuperpower()
                     [[Prototype]] => Object
                   }

MethodDecorator

Accepts a decorator implementation function and returns a decorator that may be applied to class methods. The decorator implementation function is invoked each time the decorated method is invoked, and is provided an object with the following shape:

{
  // Original, pre-bound method.
  method: Function;
  // Name of the decorated method.
  methodName: string;
  // Any arguments passed to the method.
  args: Array<any>;
}

Example:

import {MethodDecorator} from '@darkobits/class-decorator';

const AddSalutation = MethodDecorator(({method}) => {
  return `Hello, my name is ${method()}.`;
});

class Person {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  @AddSalutation
  getName(): string {
    return this.name;
  }
}

const bob = new Person('Bob');
bob.getName() //=> 'Hello, my name is Bob.'

 


FAQs

Package last updated on 05 Jun 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc