@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.
Usage
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,
);
}
Alternativley, 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 property decorator
import {PropertyDecoratorFactory} from '@loopback/metadata';
export interface MyPropertyMetadata {
name: string;
description?: string;
}
function myPropertydDecorator(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 myParameterdDecorator(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:
- Method decorators cannot be applied to a constructor
- 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
.
Customize inheritance of metadata
By default, the decorator factories allow inheritance with the following rules:
-
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 {
return this.spec;
}
-
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)
{
'my-class-decorator-key': MyClassMetadata,
'my-static-parameter-decorator-key': {
'': [MyConstructorParameterMetadata],
'myStaticMethod1': [MyStaticMethodParameterMetadata],
'myStaticMethod2': [MyStaticMethodParameterMetadata],
},
'my-static-method-decorator-key': {
'myStaticMethod1': MyStaticMethodMetadata,
'myStaticMethod2': MyStaticMethodMetadata,
},
'my-static-property-decorator-key': {
'myStaticMethod1': MyStaticPropertyMetadata,
'myStaticMethod1': MyStaticPropertyMetadata,
}
}
{
'my-instance-parameter-decorator-key': {
'myMethod1': [MyMethodParameterMetadata],
'myMethod2': [MyMethodParameterMetadata],
},
'my-instance-method-decorator-key': {
'myMethod1': MyMethodMetadata,
'myMethod2': MyMethodMetadata,
},
'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 | symbol,
descriptorOrIndex?: TypedPropertyDescriptor<any> | number,
): M {
}
protected mergeWithOwn(
ownMetadata: M,
target: Object,
member?: string | symbol,
descriptorOrIndex?: TypedPropertyDescriptor<any> | number,
): M {
}
-
The default implemention 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,
);
const myMethod = MetadataInspector.getMethodMetaData<MyMethodMetadata>(
'my-method-decorator-key',
MyController.prototype,
'myMethod',
);
Inspect metadata of a property
import {MetadataInspector} from '@loopback/metadata';
const allProps = MetadataInspector.getAllPropertyMetaData<MyPropertyMetadata>(
'my-property-decorator-key',
MyController.prototype,
);
const myProp = MetadataInspector.getMethodMetaData<MyMethodMetadata>(
'my-property-decorator-key',
MyController.prototype,
'myProp',
);
Inspect metadata of method parameters
import {MetadataInspector} from '@loopback/metadata';
const allParamsForMyMethod =
MetadataInspector.getAllParameterMetaData<MyParameterMetadata>(
'my-parameter-decorator-key',
MyController.prototype,
'myMethod',
);
const firstParamForMyMethod =
MetadataInspector.getMyParameterMetaData<MyParameterMetadata>(
'my-parameter-decorator-key',
MyController.prototype,
'myMethod',
0,
);
const allParamsForConstructor =
MetadataInspector.getAllParameterMetaData<MyParameterMetadata>(
'my-parameter-decorator-key',
MyController,
'',
);
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,
'myMethod',
);
Installation
$ npm install --save @loopback/metadata
Contributions
IBM/StrongLoop is an active supporter of open source and welcomes contributions
to our projects as well as those of the Node.js community in general. For more
information on how to contribute please refer to the
Contribution Guide.
Tests
Run npm test
from the root folder.
Contributors
See
all contributors.
License
MIT