UNPKG

3.45 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2// Node module: @loopback/context
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6import {ClassDecoratorFactory} from '@loopback/metadata';
7import {
8 asBindingTemplate,
9 asClassOrProvider,
10 asProvider,
11 BindingMetadata,
12 BindingSpec,
13 BINDING_METADATA_KEY,
14 isProviderClass,
15 removeNameAndKeyTags,
16} from './binding-inspector';
17import {Constructor} from './value-promise';
18
19/**
20 * Decorator factory for `@injectable`
21 */
22class InjectableDecoratorFactory extends ClassDecoratorFactory<BindingMetadata> {
23 mergeWithInherited(inherited: BindingMetadata, target: Function) {
24 if (inherited) {
25 return {
26 templates: [
27 ...inherited.templates,
28 removeNameAndKeyTags,
29 ...this.spec.templates,
30 ],
31 target: this.spec.target,
32 };
33 } else {
34 this.withTarget(this.spec, target);
35 return this.spec;
36 }
37 }
38
39 mergeWithOwn(ownMetadata: BindingMetadata) {
40 return {
41 templates: [...ownMetadata.templates, ...this.spec.templates],
42 target: this.spec.target,
43 };
44 }
45
46 withTarget(spec: BindingMetadata, target: Function) {
47 spec.target = target as Constructor<unknown>;
48 return spec;
49 }
50}
51
52/**
53 * Decorate a class with binding configuration
54 *
55 * @example
56 * ```ts
57 * @injectable((binding) => {binding.inScope(BindingScope.SINGLETON).tag('controller')}
58 * )
59 * @injectable({scope: BindingScope.SINGLETON})
60 * export class MyController {
61 * }
62 * ```
63 *
64 * @param specs - A list of binding scope/tags or template functions to
65 * configure the binding
66 */
67export function injectable(...specs: BindingSpec[]): ClassDecorator {
68 const templateFunctions = specs.map(t => {
69 if (typeof t === 'function') {
70 return t;
71 } else {
72 return asBindingTemplate(t);
73 }
74 });
75
76 return (target: Function) => {
77 const cls = target as Constructor<unknown>;
78 const spec: BindingMetadata = {
79 templates: [asClassOrProvider(cls), ...templateFunctions],
80 target: cls,
81 };
82
83 const decorator = InjectableDecoratorFactory.createDecorator(
84 BINDING_METADATA_KEY,
85 spec,
86 {decoratorName: '@injectable'},
87 );
88 decorator(target);
89 };
90}
91
92/**
93 * A namespace to host shortcuts for `@injectable`
94 */
95export namespace injectable {
96 /**
97 * `@injectable.provider` to denote a provider class
98 *
99 * A list of binding scope/tags or template functions to configure the binding
100 */
101 export function provider(
102 ...specs: BindingSpec[]
103 ): (target: Constructor<unknown>) => void {
104 return (target: Constructor<unknown>) => {
105 if (!isProviderClass(target)) {
106 throw new Error(`Target ${target} is not a Provider`);
107 }
108 injectable(
109 // Set up the default for providers
110 asProvider(target),
111 // Call other template functions
112 ...specs,
113 )(target);
114 };
115 }
116}
117
118/**
119 * `@bind` is now an alias to {@link injectable} for backward compatibility
120 * {@inheritDoc injectable}
121 */
122export function bind(...specs: BindingSpec[]): ClassDecorator {
123 return injectable(...specs);
124}
125
126/**
127 * Alias namespace `bind` to `injectable` for backward compatibility
128 *
129 * It should have the same members as `bind`.
130 */
131export namespace bind {
132 /**
133 * {@inheritDoc injectable.provider}
134 */
135 export const provider = injectable.provider;
136}