npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@loopback/metadata

v7.0.1

Published

Utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata

Downloads

238,352

Readme

@loopback/metadata

This module contains utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata.

  • Reflector: Wrapper of reflect-metadata
  • Decorator factories: A set of factories for class/method/property/parameter decorators to apply metadata to a given class and its static or instance members.
  • MetadataInspector: High level APIs to inspect a class and/or its members to get metadata applied by decorators.

Basic Use

To create a class decorator

import {ClassDecoratorFactory} from '@loopback/metadata';

export interface MyClassMetadata {
  name: string;
  description?: string;
}

function myClassDecorator(spec: MyClassMetadata): ClassDecorator {
  return ClassDecoratorFactory.createDecorator<MyClassMetadata>(
    'metadata-key-for-my-class-decorator',
    spec,
    {decoratorName: '@myClassDecorator'},
  );
}

Alternatively, we can instantiate the factory and create a decorator:

function myClassDecorator(spec: MyClassMetadata): ClassDecorator {
  const factory = new ClassDecoratorFactory<MyClassMetadata>(
    'metadata-key-for-my-class-decorator',
    spec,
  );
  return factory.create();
}

Now we can use @myClassDecorator to add metadata to a class as follows:

@myClassDecorator({name: 'my-controller'})
class MyController {}

To create a method decorator

import {MethodDecoratorFactory} from '@loopback/metadata';

export interface MyMethodMetadata {
  name: string;
  description?: string;
}

function myMethodDecorator(spec: MyMethodMetadata): MethodDecorator {
  return MethodDecoratorFactory.createDecorator<MyMethodMetadata>(
    'metadata-key-for-my-method-decorator',
    spec,
  );
}

Now we can use @myMethodDecorator to add metadata to a method as follows:

class MyController {
  @myMethodDecorator({name: 'my-method'})
  myMethod(x: string): string {
    return 'Hello, ' + x;
  }

  @myMethodDecorator({name: 'another-method'})
  anotherMethod() {}

  @myMethodDecorator({name: 'my-static-method'})
  static myStaticMethod() {}
}

To create a decorator that can be used multiple times on a single method

Instead of a single immutable object to be merged, the MethodMultiDecoratorFactory reduced parameters into a flat array of items. When fetching the metadata later, you will receive it as an array.

import {MethodMultiDecoratorFactory} from '@loopback/metadata';

function myMultiMethodDecorator(spec: object): MethodDecorator {
  return MethodMultiDecoratorFactory.createDecorator<object>(
    'metadata-key-for-my-method-multi-decorator',
    spec,
  );
}

Now, you can use it multiple times on a method:

class MyController {
  @myMultiMethodDecorator({x: 1})
  @myMultiMethodDecorator({y: 2})
  @myMultiMethodDecorator({z: 3})
  public point() {}
}

class MyOtherController {
  @myMultiMethodDecorator([{x: 1}, {y: 2}, {z: 3}])
  public point() {}
}

And when you access this data:

const arrayOfSpecs = MetadataInspector.getMethodMetadata<object>(
  'metadata-key-for-my-method-multi-decorator',
  constructor.prototype,
  op,
);

// [{z: 3}, {y: 2}, {x: 1}]

Typescript applies decorators in reverse order per class, from the parent down. The metadata array resurned by getOwnMetadata will be in this order:

class Parent {
  @myMultiMethodDecorator('A') // second
  @myMultiMethodDecorator('B') // first
  public greet() {}
}

class Child extends Parent {
  @myMultiMethodDecorator(['C', 'D']) // [third, fourth]
  public greet() {}
}

class Grandchild extends Child {
  @myMultiMethodDecorator('E') // sixth
  @myMultiMethodDecorator('F') // fifth
  public greet() {}
}
// getMethodMetadata = ['B', 'A', 'C', 'D', 'F', 'E']

You can also create a decorator that takes an object that can contain an array:

interface Point {
  x?: number;
  y?: number;
  z?: number;
}
interface GeometryMetadata {
  points: Point[];
}
function geometry(...points: Point[]): MethodDecorator {
  return MethodMultiDecoratorFactory.createDecorator<GeometryMetadata>(
    'metadata-key-for-my-method-multi-decorator',
    points,
  );
}

class MyGeoController {
  @geometry({x: 1})
  @geometry({x: 2}, {y: 3})
  @geometry({z: 5})
  public abstract() {}
}

const arrayOfSpecs = MetadataInspector.getMethodMetadata<GeometryMetadata>(
  'metadata-key-for-my-method-multi-decorator',
  constructor.prototype,
  op,
);

// [
//    { points: [{x: 1}]},
//    { points: [{x:2}, {y:3}]},
//    { points: [{z: 5}]},
// ]

To create a property decorator

import {PropertyDecoratorFactory} from '@loopback/metadata';

export interface MyPropertyMetadata {
  name: string;
  description?: string;
}

function myPropertyDecorator(spec: MyPropertyMetadata): PropertyDecorator {
  return PropertyDecoratorFactory.createDecorator<MyPropertyMetadata>(
    'metadata-key-for-my-property-decorator',
    spec,
  );
}

Now we can use @myPropertyDecorator to add metadata to a property as follows:

class MyController {
  @myPropertyDecorator({name: 'my-property'})
  myProperty: string;

  @myPropertyDecorator({name: 'another-property'})
  anotherProperty: boolean;

  @myPropertyDecorator({name: 'my-static-property'})
  static myStaticProperty: string;
}

To create a parameter decorator

import {ParameterDecoratorFactory} from '@loopback/metadata';

export interface MyParameterMetadata {
  name: string;
  description?: string;
}

function myParameterDecorator(spec: MyParameterMetadata): ParameterDecorator {
  return ParameterDecoratorFactory.createDecorator<MyParameterMetadata>(
    'metadata-key-for-my-parameter-decorator',
    spec,
  );
}

Now we can use @myParameterDecorator to add metadata to a parameter as follows:

class MyController {
  constructor(
    @myParameterDecorator({name: 'logging-prefix'}) public prefix: string,
    @myParameterDecorator({name: 'logging-level'}) public level: number,
  ) {}

  myMethod(
    @myParameterDecorator({name: 'x'}) x: number,
    @myParameterDecorator({name: 'y'}) y: number,
  ) {}

  static myStaticMethod(
    @myParameterDecorator({name: 'a'}) a: string,
    @myParameterDecorator({name: 'b'}) b: string,
  ) {}
}

To create method decorator for parameters

import {MethodParameterDecoratorFactory} from '@loopback/metadata';

export interface MyParameterMetadata {
  name: string;
  description?: string;
}

function myMethodParameterDecorator(
  spec: MyParameterMetadata,
): MethodDecorator {
  return MethodParameterDecoratorFactory.createDecorator<MyParameterMetadata>(
    'metadata-key-for-my-method-parameter-decorator',
    spec,
  );
}

Now we can use @myMethodParameterDecorator to add metadata to a parameter as follows:

class MyController {
  @myMethodParameterDecorator({name: 'x'})
  @myMethodParameterDecorator({name: 'y'})
  myMethod(x: number, y: number) {}
}

WARNING: Using method decorators to provide metadata for parameters is strongly discouraged for a few reasons:

  1. Method decorators cannot be applied to a constructor
  2. Method decorators depends on the positions to match parameters

We recommend that ParameterDecorator be used instead.

Decorator options

An object of type DecoratorOptions can be passed in to create decorator functions. There are two flags for the options:

  • allowInheritance: Controls if inherited metadata will be honored. Default to true.
  • cloneInputSpec: Controls if the value of spec argument will be cloned. Sometimes we use shared spec for the decoration, but the decorator function might need to mutate the object. Cloning the input spec makes it safe to use the same spec (template) to decorate different members. Default to true.
  • decoratorName: Name for the decorator such as @inject for error and debugging messages.

Customize inheritance of metadata

By default, the decorator factories allow inheritance with the following rules:

  1. If the metadata is an object, we merge the spec argument from the decorator function into the inherited value from base classes. For metadata of array and other primitive types, the spec argument is used if provided.

    • We can override inherit method of the decorator factory to customize how to resolve spec against the inherited metadata. For example:
protected inherit(inheritedMetadata: T | undefined | null): T {
  // Ignore the inherited metadata
  return this.spec;
}
  1. Method/property/parameter level metadata is applied to the class or its prototype as a map keyed method/property names. We think this approach is better than keeping metadata at method/property level as it's not easy to inspect a class to find static/instance methods and properties with decorations. The metadata for a class is illustrated below:

    • MyClass (the constructor function itself)
{
  // Class level metadata
  'my-class-decorator-key': MyClassMetadata,
  // Static method (including the constructor) parameter metadata
  'my-static-parameter-decorator-key': {
    '': [MyConstructorParameterMetadata], // Constructor parameter metadata
    'myStaticMethod1': [MyStaticMethodParameterMetadata],
    'myStaticMethod2': [MyStaticMethodParameterMetadata],
  },
  // Static method metadata
  'my-static-method-decorator-key': {
    'myStaticMethod1': MyStaticMethodMetadata,
    'myStaticMethod2': MyStaticMethodMetadata,
  },
  // Static property metadata
  'my-static-property-decorator-key': {
    'myStaticMethod1': MyStaticPropertyMetadata,
    'myStaticMethod1': MyStaticPropertyMetadata,
  }
}
  • MyClass.prototype
{
  // Instance method parameter metadata
  'my-instance-parameter-decorator-key': {
    'myMethod1': [MyMethodParameterMetadata],
    'myMethod2': [MyMethodParameterMetadata],
  },
  // Instance method metadata
  'my-instance-method-decorator-key': {
    'myMethod1': MyMethodMetadata,
    'myMethod2': MyMethodMetadata,
  },
  // Instance property metadata
  'my-instance-property-decorator-key': {
    'myProperty1': MyPropertyMetadata,
    'myProperty2': MyPropertyMetadata,
  }
}

The following methods in DecoratorFactory allow subclasses to customize how to merge the spec with existing metadata for a class, methods, properties, and method parameters. Please note M is a map for methods/properties/parameters.

protected mergeWithInherited(
  inheritedMetadata: M,
  target: Object,
  member?: string,
  descriptorOrIndex?: TypedPropertyDescriptor<any> | number,
): M {
  // ...
}

protected mergeWithOwn(
  ownMetadata: M,
  target: Object,
  member?: string,
  descriptorOrIndex?: TypedPropertyDescriptor<any> | number,
): M {
  // ...
}
  1. The default implementation throws errors if the same decorator function is applied to a given target member (class/method/property/parameter) more than once. For example, the following usage will report an error at runtime.
@myClassDecorator({name: 'my-controller'})
@myClassDecorator({name: 'your-controller'})
class MyController {}

Inspect metadata

MetadataInspector provides API to inspect metadata from a class and its members.

Inspect metadata of a class

import {MetadataInspector} from '@loopback/metadata';

const meta = MetadataInspector.getClassMetadata(
  'my-class-decorator-key',
  MyController,
);

Inspect own metadata of a class

import {MetadataInspector} from '@loopback/metadata';

const meta = MetadataInspector.getClassMetadata<MyClassMetaData>(
  'my-class-decorator-key',
  MyController,
  {
    ownMetadataOnly: true,
  },
);

Inspect metadata of a method

import {MetadataInspector} from '@loopback/metadata';

const allMethods = MetadataInspector.getAllMethodMetaData<MyMethodMetadata>(
  'my-method-decorator-key',
  MyController.prototype, // Use MyController for static methods
);

const myMethod = MetadataInspector.getMethodMetaData<MyMethodMetadata>(
  'my-method-decorator-key',
  MyController.prototype, // Use MyController for static methods
  'myMethod',
);

Inspect metadata of a property

import {MetadataInspector} from '@loopback/metadata';

const allProps = MetadataInspector.getAllPropertyMetaData<MyPropertyMetadata>(
  'my-property-decorator-key',
  MyController.prototype, // Use MyController for static properties
);

const myProp = MetadataInspector.getMethodMetaData<MyMethodMetadata>(
  'my-property-decorator-key',
  MyController.prototype, // Use MyController for static properties
  'myProp',
);

Inspect metadata of method parameters

import {MetadataInspector} from '@loopback/metadata';

const allParamsForMyMethod =
  MetadataInspector.getAllParameterMetaData<MyParameterMetadata>(
    'my-parameter-decorator-key',
    MyController.prototype, // Use MyController for static methods,
    'myMethod',
  );

const firstParamForMyMethod =
  MetadataInspector.getMyParameterMetaData<MyParameterMetadata>(
    'my-parameter-decorator-key',
    MyController.prototype, // Use MyController for static methods
    'myMethod',
    0, // parameter index
  );

const allParamsForConstructor =
  MetadataInspector.getAllParameterMetaData<MyParameterMetadata>(
    'my-parameter-decorator-key',
    MyController,
    '',
  );

Use strong-typed metadata access key

You can use MetadataAccessor to provide type checks for metadata access via keys. For example,

const CLASS_KEY = MetadataAccessor.create<MyClassMetadata, ClassDecorator>(
  'my-class-decorator-key',
);

// Create a class decorator with the key
const myClassDecorator = ClassDecoratorFactory.createDecorator(CLASS_KEY);

// Inspect a class with the key
const myClassMeta = MetadataInspector.getClassMetaData(CLASS_KEY, MyController);

Please note MetadataKey can be an instance of MetadataAccessor or a string.

Inspect design-time metadata of properties/methods

import {MetadataInspector} from '@loopback/metadata';

const myPropType = MetadataInspector.getDesignTypeForProperty(
  MyController.prototype,
  'myProp',
);

const myConstructor = MetadataInspector.getDesignTypeForMethod(
  MyController,
  '',
);

const myMethod = MetadataInspector.getDesignTypeForMethod(
  MyController.prototype, // Use MyController for static methods
  'myMethod',
);

Installation

npm install --save @loopback/metadata

Contributions

Tests

Run npm test from the root folder.

Contributors

See all contributors.

License

MIT