@qiwi/decorator-utils

Tiny helper for TS/JS decorator building

Usage no npm install needed!

<script type="module">
  import qiwiDecoratorUtils from 'https://cdn.skypack.dev/@qiwi/decorator-utils';
</script>

README

decorator-utils

Build Status Maintainability Test Coverage CodeStyle

Motivation

  1. — Decorator, what's that?
    — It's just a proposal "aspect" syntax for JS.
  2. — How does it work?
    Addy Osmany's answer
  3. — Is there any ready solution?
    — There're many awesome things around. Look at core-decorators and lodash-decorators.
  4. — Do I need this lib?
    — You should try this one before: decorator-utils by Luke Horvat
  5. — ...
    Just give a chance to Google
  6. — ...
    — How about writing your own? Netanel Basal's practical tips may be very helpful.
  7. — ...
    Yes, go ahead.
NOTE

There's no right way to support both decorator types: with @parentheses() and @plain. Holy War Thread: https://github.com/wycats/javascript-decorators/issues/23

Install

yarn add @qiwi/decorator-utils

Usage

Method

import {constructDecorator} from '@qiwi/decorator-utils'

const decorator = constructDecorator((targetType, target, param) => {
  if (targetType === METHOD) {
    return value => param || 'qux'
  }
})

class Foo {
  @decorator()
  foo () { return 'bar' }
  @decorator('BAZ')
  baz () { return 'baz' }
}

Class

const decorator = constructDecorator((targetType, target) => {
  if (targetType === CLASS) {
    return class Bar extends target {
      constructor (name, age) {
        super(name)
        this.age = age
      }
    }
  }
})

@decorator()
class Foo {
  constructor (name) {
    this.name = name
  }
  foo () { return 'bar' }
}

Field & Param

import {createDecorator, FIELD, PARAM} from '@qiwi/decorator-utils'

const meta: any = {}
const decorator = constructDecorator(({
    propName,
    paramIndex,
    targetType,
    target,
    args: [param]
}: IDecoratorHandlerContext) => {
  if (targetType === PARAM) {
    if (propName && typeof paramIndex === 'number') {
      meta[propName] = meta[propName] || {}
      meta[propName][paramIndex] = target
    }
  }

  if (targetType === FIELD) {
    if (propName) {
      meta[propName] = param
    }
  }
})

class Foo {
  @decorator('arg')
  foo = 'bar'

  bar(one: any, @decorator() two: any) {
    return 'bar'
  }
}

/**
    Now `meta` is smth like:
    {
      foo: 'arg',
      bar: {
        1: Foo.prototype.bar,
      },
    }
*/

You may also apply the decorator to the class, but decorate its methods:

const decorator = constructDecorator((targetType, target) => {
      if (targetType === METHOD) {
        return () => {
          return target().toUpperCase()
        }
      }
    })

    @decorator()
    class Foo {
      foo () { return 'bar' }
      baz () { return 'baz' }
    }

IDecoratorHandlerContext

constructDecorator factory provides the handler access to the decorator context. This data describes the specifics of the decorated target, decorator arguments and so on.

export type IDecoratorHandlerContext = {
  targetType: ITargetType | null
  target: ITarget
  proto: IProto
  ctor: Function
  propName?: IPropName
  paramIndex?: IParamIndex
  descriptor?: IDescriptor
  args: IDecoratorArgs
}