From ae9ea0d2234c5199895eb4af94a2e4cdc8c13682 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sun, 21 Jan 2018 12:43:15 -0800 Subject: [PATCH 1/8] fix: use parameter decorators for openapi params --- pages/en/lb4/Context.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pages/en/lb4/Context.md b/pages/en/lb4/Context.md index 46618e705..6b5e8c0ae 100644 --- a/pages/en/lb4/Context.md +++ b/pages/en/lb4/Context.md @@ -177,14 +177,17 @@ up. For example, let's take the previous example and make it available on the `GET /greet` route using decorators provided by [`@loopback/rest`](https://github.com/strongloop/loopback-next/blob/master/packages/rest): -```js +```ts class HelloController { - @get('/greet') // tell LoopBack you want this controller method - // to be available at the GET /greet route - @param.query.string('name') // tell LoopBack you want to accept - // the name parameter as a string from - // the query string - greet(name) { + // tell LoopBack you want this controller method + // to be available at the GET /greet route + @get('/greet') + greet( + // tell LoopBack you want to accept + // the name parameter as a string from + // the query string + @param.query.string('name') + name: string) { return `Hello ${name}`; } } From 66945fbee1b4c08f0c5f3443a614482b6f26d821 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sun, 21 Jan 2018 12:43:55 -0800 Subject: [PATCH 2/8] feat: add docs for inject.setter, inject.getter, and inject.tag --- pages/en/lb4/Decorators.md | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pages/en/lb4/Decorators.md b/pages/en/lb4/Decorators.md index 242a8d301..aeff85afd 100644 --- a/pages/en/lb4/Decorators.md +++ b/pages/en/lb4/Decorators.md @@ -237,6 +237,57 @@ class WidgetController { } ``` +A few variants of `@inject` are provided to declare special forms of dependencies: + +- @inject.getter: inject a getter function that returns a promise of the bound value of the key + +```ts +class HelloController { + constructor( + @inject.getter('authentication.currentUser') + private userGetter: Getter) {} + + async greet() { + const user = await this.userGetter(); + return `Hello, ${user.name}`; + } +} +``` + +- @inject.setter: inject a setter function to set bound value of the key + +```ts +class HelloController { + constructor( + @inject.setter('greeting') + private greetingSetter: Setter) {} + + greet() { + greetingSetter('my-greeting-message'); + } +} +``` + +- @inject.tag: inject an array of values by a pattern or regexp to match bindng tags + +```ts + class Store { + constructor(@inject.tag('store:location') public locations: string[]) {} + } + + ctx.bind('store').toClass(Store); + ctx + .bind('store.locations.sf') + .to('San Francisco') + .tag('store:location'); + ctx + .bind('store.locations.sj') + .to('San Jose') + .tag('store:location'); + const store: Store = ctx.getSync('store'); + // `store.locations` is now `['San Francisco', 'San Jose']` +``` + For more information, see the [Dependency Injection](Dependency-Injection.htm) section under [LoopBack Core Concepts](Concepts.htm) ## Authentication Decorator From 2b93dd75eb326265d1534a7fb5d505aac240f7bf Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sun, 21 Jan 2018 12:44:18 -0800 Subject: [PATCH 3/8] feat: add description of method injection --- pages/en/lb4/Dependency-Injection.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pages/en/lb4/Dependency-Injection.md b/pages/en/lb4/Dependency-Injection.md index d308c51e9..c8a852833 100644 --- a/pages/en/lb4/Dependency-Injection.md +++ b/pages/en/lb4/Dependency-Injection.md @@ -37,7 +37,7 @@ Dependency Injection makes the code easier to extend and customize, because the ## Configuring what to inject -Now that we wrote a class that gets the dependencies injected, you are probably wondering where are these values going to be injected from and how to configure what should be injected. This part is typically handled by an IoC Container, where IoC means [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control). +Now that we write a class that gets the dependencies injected, you are probably wondering where are these values going to be injected from and how to configure what should be injected. This part is typically handled by an IoC Container, where IoC means [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control). In LoopBack, we use [Context](Context.html) to keep track of all injectable dependencies. @@ -86,10 +86,11 @@ const authenticate = await app.get('authentication.provider'); You can learn more about Providers in [Creating Components](Creating-components.html). ## Flavors of Dependency Injection -LoopBack supports two kinds of dependency injection: +LoopBack supports three kinds of dependency injection: 1. constructor injection: the dependencies are provided as arguments of the class constructor. 2. property injection: the dependencies are stored in instance properties after the class was constructed. + 3. method injection: the dependencies are provided as arguments of a method invocation. Please note that constructor injection is a special form of method injection to instantiate a class by calling its constructor. ### Constructor injection @@ -126,6 +127,20 @@ class InfoController { } ``` +### Method injection + +Method injection allows injection of dependencies at method invocation level. The parameters are decorated +with `@inject` or other variants to declare dependencies as method arguments. + +```ts +class InfoController { + + greet(@inject('authentication.currentUser') user: UserProfile) { + return `Hello, ${userProfile.name}`; + } +} +``` + ## Additional resources - [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) on Wikipedia From 9e0424f00442e9f05e5ec6344b3c8e96c03f3caf Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sun, 21 Jan 2018 13:00:00 -0800 Subject: [PATCH 4/8] feat: add docs for circular dependency detection --- pages/en/lb4/Dependency-Injection.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pages/en/lb4/Dependency-Injection.md b/pages/en/lb4/Dependency-Injection.md index c8a852833..b078418a2 100644 --- a/pages/en/lb4/Dependency-Injection.md +++ b/pages/en/lb4/Dependency-Injection.md @@ -141,6 +141,30 @@ class InfoController { } ``` +## Circular dependencies + +LoopBack can detect circular dependencies and report the path which leads to the problem. +For example, + +```ts +const context = new Context(); + interface XInterface {} + interface YInterface {} + + class XClass implements XInterface { + @inject('y') public y: YInterface; + } + + class YClass implements YInterface { + @inject('x') public x: XInterface; + } + + context.bind('x').toClass(XClass); + context.bind('y').toClass(YClass); + // An error will be thrown below - Circular dependency detected on path 'x --> y --> x' + const x: XInterface = context.getSync('x'); +``` + ## Additional resources - [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) on Wikipedia From c544fe2f446849b6ef9f03f7b38a9e9c2a4b2030 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sun, 21 Jan 2018 20:29:06 -0800 Subject: [PATCH 5/8] feat: add a guide to create decorators using @loopback/metadata --- _data/sidebars/lb4_sidebar.yml | 4 ++++ pages/en/lb4/Creating-decorators.md | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 pages/en/lb4/Creating-decorators.md diff --git a/_data/sidebars/lb4_sidebar.yml b/_data/sidebars/lb4_sidebar.yml index b739ed4cc..f905a32d9 100644 --- a/_data/sidebars/lb4_sidebar.yml +++ b/_data/sidebars/lb4_sidebar.yml @@ -136,6 +136,10 @@ children: url: Creating-components.html output: 'web, pdf' + - title: 'Creating Decorators' + url: Creating-decorators.html + output: 'web, pdf' + - title: 'Testing your extension' url: Testing-your-extension.html output: 'web, pdf' diff --git a/pages/en/lb4/Creating-decorators.md b/pages/en/lb4/Creating-decorators.md new file mode 100644 index 000000000..043391593 --- /dev/null +++ b/pages/en/lb4/Creating-decorators.md @@ -0,0 +1,13 @@ +--- +lang: en +title: 'Creating decorators' +keywords: LoopBack 4.0, LoopBack 4 +tags: +sidebar: lb4_sidebar +permalink: /doc/en/lb4/Creating-decorators.html +summary: A tutorial to use `@loopback/metadata` module to create new decorators +--- + +[Create decorators](https://github.com/strongloop/loopback-next/blob/master/packages/metadata/README.md) + +--- \ No newline at end of file From 577c7acc395e5dc175484fbf1aa10e1cde8790b2 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 23 Jan 2018 11:16:53 -0800 Subject: [PATCH 6/8] fix: change formatting and add syntaxes for `@inject.*` --- pages/en/lb4/Decorators.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pages/en/lb4/Decorators.md b/pages/en/lb4/Decorators.md index aeff85afd..2a1ce70e7 100644 --- a/pages/en/lb4/Decorators.md +++ b/pages/en/lb4/Decorators.md @@ -239,7 +239,9 @@ class WidgetController { A few variants of `@inject` are provided to declare special forms of dependencies: -- @inject.getter: inject a getter function that returns a promise of the bound value of the key +- `@inject.getter`: inject a getter function that returns a promise of the bound value of the key + +Syntax: `@inject.getter(bindingKey: string)`. ```ts class HelloController { @@ -254,7 +256,9 @@ class HelloController { } ``` -- @inject.setter: inject a setter function to set bound value of the key +- `@inject.setter`: inject a setter function to set bound value of the key + +Syntax: `@inject.setter(bindingKey: string)`. ```ts class HelloController { @@ -268,7 +272,9 @@ class HelloController { } ``` -- @inject.tag: inject an array of values by a pattern or regexp to match bindng tags +- `@inject.tag`: inject an array of values by a pattern or regexp to match bindng tags + +Syntax: `@inject.tag(tag: string | RegExp)`. ```ts class Store { From 7e95ae083ff6799fb2807e5f22a70e5f4f6c70a3 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 23 Jan 2018 11:34:44 -0800 Subject: [PATCH 7/8] feat: add a script to pull in loopback-next readme files --- update-v4-readmes.sh | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100755 update-v4-readmes.sh diff --git a/update-v4-readmes.sh b/update-v4-readmes.sh new file mode 100755 index 000000000..0f1b6c847 --- /dev/null +++ b/update-v4-readmes.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# The following is a 5 column list of org, repo, branch, package, and file. +# - If the branch is NOT specified, then the README for that project +# will be pulled from npmjs.org instead and will reflect the latest +# release. +# - If the branch IS specified, it will be used to fetch the README.md +# from the given github repo. If that branch is NOT master, then the +# branch name will be appended to the local readme file name. +(cat < $DEST + else + # The loopback-example-database repo contains a separate branch for each + # actual example project, so we need to add the branch name to the readme + # name. + if [ "$branch" != "master" ]; then + DEST="pages/en/lb4/readmes/$package-$branch.md" + fi + echo "fetching $org/$repo/$branch/packages/$package from GitHub's raw content domain..." + curl -s $GHURL > $DEST + fi +done From 3e3e51afde00a21fd5a5472a46dfa4abebd6d6fe Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 23 Jan 2018 11:35:18 -0800 Subject: [PATCH 8/8] feat: pull in README from @loopback/metadata --- Jenkinsfile | 2 + pages/en/lb4/Creating-decorators.md | 6 +- pages/en/lb4/readmes/metadata.md | 460 ++++++++++++++++++++++++++++ 3 files changed, 464 insertions(+), 4 deletions(-) create mode 100644 pages/en/lb4/readmes/metadata.md diff --git a/Jenkinsfile b/Jenkinsfile index 57239092e..641ea90c1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,6 +13,7 @@ node('linux && git') { } stage('download updates') { sh './update-readmes.sh' + sh './update-v4-readmes.sh' sh './update-community-readmes.sh' } stage('check updates') { @@ -25,6 +26,7 @@ node('linux && git') { // existing files will have to be done by a human for now. sh 'git add pages/en/lb2/readmes' sh 'git add pages/en/lb3/readmes' + sh 'git add pages/en/lb4/readmes' sh 'git add pages/en/community/readmes' withEnv(gitEnv) { sh 'git commit -m "Update READMEs from other repos"' diff --git a/pages/en/lb4/Creating-decorators.md b/pages/en/lb4/Creating-decorators.md index 043391593..7bf703e14 100644 --- a/pages/en/lb4/Creating-decorators.md +++ b/pages/en/lb4/Creating-decorators.md @@ -2,12 +2,10 @@ lang: en title: 'Creating decorators' keywords: LoopBack 4.0, LoopBack 4 +layout: readme +source: metadata tags: sidebar: lb4_sidebar permalink: /doc/en/lb4/Creating-decorators.html summary: A tutorial to use `@loopback/metadata` module to create new decorators --- - -[Create decorators](https://github.com/strongloop/loopback-next/blob/master/packages/metadata/README.md) - ---- \ No newline at end of file diff --git a/pages/en/lb4/readmes/metadata.md b/pages/en/lb4/readmes/metadata.md new file mode 100644 index 000000000..c711dfea5 --- /dev/null +++ b/pages/en/lb4/readmes/metadata.md @@ -0,0 +1,460 @@ +# @loopback/metadata + +This module contains utilities to help developers implement [TypeScript decorators](https://www.typescriptlang.org/docs/handbook/decorators.html), define/merge +metadata, and inspect metadata. + +* Reflector: Wrapper of + [reflect-metadata](https://github.com/rbuckton/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 + +```ts +import {ClassDecoratorFactory} from '@loopback/metadata'; + +export interface MyClassMetadata { + name: string; + description?: string; +} + +function myClassDecorator(spec: MyClassMetadata): ClassDecorator { + return ClassDecoratorFactory.createDecorator( + 'metadata-key-for-my-class-decorator', + spec, + ); +} +``` + +Alternativley, we can instantiate the factory and create a decorator: + +```ts +function myClassDecorator(spec: MyClassMetadata): ClassDecorator { + const factory = new ClassDecoratorFactory( + 'metadata-key-for-my-class-decorator', + spec, + ); + return factory.create(); +} +``` + +Now we can use `@myClassDecorator` to add metadata to a class as follows: + +```ts +@myClassDecorator({name: 'my-controller'}) +class MyController {} +``` + +## To create a method decorator + +```ts +import {MethodDecoratorFactory} from '@loopback/metadata'; + +export interface MyMethodMetadata { + name: string; + description?: string; +} + +function myMethodDecorator(spec: MyMethodMetadata): MethodDecorator { + return MethodDecoratorFactory.createDecorator( + 'metadata-key-for-my-method-decorator', + spec, + ); +} +``` + +Now we can use `@myMethodDecorator` to add metadata to a method as follows: + +```ts +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 + +```ts +import {PropertyDecoratorFactory} from '@loopback/metadata'; + +export interface MyPropertyMetadata { + name: string; + description?: string; +} + +function myPropertydDecorator(spec: MyPropertyMetadata): PropertyDecorator { + return PropertyDecoratorFactory.createDecorator( + 'metadata-key-for-my-property-decorator', + spec, + ); +} +``` + +Now we can use `@myPropertyDecorator` to add metadata to a property as follows: + +```ts +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 + +```ts +import {ParameterDecoratorFactory} from '@loopback/metadata'; + +export interface MyParameterMetadata { + name: string; + description?: string; +} + +function myParameterdDecorator(spec: MyParameterMetadata): ParameterDecorator { + return ParameterDecoratorFactory.createDecorator( + 'metadata-key-for-my-parameter-decorator', + spec, + ); +} +``` + +Now we can use `@myParameterDecorator` to add metadata to a parameter as follows: + +```ts +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 + +```ts +import {MethodParameterDecoratorFactory} from '@loopback/metadata'; + +export interface MyParameterMetadata { + name: string; + description?: string; +} + +function myMethodParameterDecorator( + spec: MyParameterMetadata, +): MethodDecorator { + return MethodParameterDecoratorFactory.createDecorator( + 'metadata-key-for-my-method-parameter-decorator', + spec, + ); +} +``` + +Now we can use `@myMethodParameterDecorator` to add metadata to a parameter +as follows: + +```ts +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`. + +## 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: + + ```ts + protected inherit(inheritedMetadata: T | undefined | null): T { + // Ignore the inherited metadata + return this.spec; + } + ``` + +2. 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) + + ```ts + { + // Class level metadata + 'my-class-decorator-key': MyClassMetadata, + // Static method (including the construtor) 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 + + ```ts + { + // 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. + + ```ts + protected mergeWithInherited( + inheritedMetadata: M, + target: Object, + member?: string | symbol, + descriptorOrIndex?: TypedPropertyDescriptor | number, + ): M { + // ... + } + + protected mergeWithOwn( + ownMetadata: M, + target: Object, + member?: string | symbol, + descriptorOrIndex?: TypedPropertyDescriptor | number, + ): M { + // ... + } + ``` + +3. 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. + + ```ts + @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 + +```ts +import {MetadataInspector} from '@loopback/metadata'; + +const meta = MetadataInspector.getClassMetadata( + 'my-class-decorator-key', + MyController, +); +``` + +## Inspect own metadata of a class + +```ts +import {MetadataInspector} from '@loopback/metadata'; + +const meta = MetadataInspector.getClassMetadata( + 'my-class-decorator-key', + MyController, + { + ownMetadataOnly: true, + }, +); +``` + +## Inspect metadata of a method + +```ts +import {MetadataInspector} from '@loopback/metadata'; + +const allMethods = MetadataInspector.getAllMethodMetaData( + 'my-method-decorator-key', + MyController.prototype, // Use MyController for static methods +); + +const myMethod = MetadataInspector.getMethodMetaData( + 'my-method-decorator-key', + MyController.prototype, // Use MyController for static methods + 'myMethod', +); +``` + +## Inspect metadata of a property + +```ts +import {MetadataInspector} from '@loopback/metadata'; + +const allProps = MetadataInspector.getAllPropertyMetaData( + 'my-property-decorator-key', + MyController.prototype, // Use MyController for static properties +); + +const myProp = MetadataInspector.getMethodMetaData( + 'my-property-decorator-key', + MyController.prototype, // Use MyController for static properties + 'myProp', +); +``` + +## Inspect metadata of method parameters + +```ts +import {MetadataInspector} from '@loopback/metadata'; + +const allParamsForMyMethod = + MetadataInspector.getAllParameterMetaData( + 'my-parameter-decorator-key', + MyController.prototype, // Use MyController for static methods, + 'myMethod', + ); + +const firstParamForMyMethod = + MetadataInspector.getMyParameterMetaData( + 'my-parameter-decorator-key', + MyController.prototype, // Use MyController for static methods + 'myMethod', + 0, // parameter index + ); + +const allParamsForConstructor = + MetadataInspector.getAllParameterMetaData( + 'my-parameter-decorator-key', + MyController, + '', + ); +``` + +## Inspect design-time metadata of properties/methods + +```ts +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 + +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](https://loopback.io/doc/en/contrib/index.html). + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT