@loopback/context
Version:
Facilities to manage artifacts and their dependencies in your Node.js applications. The module exposes TypeScript/JavaScript APIs and decorators to register artifacts, declare dependencies, and resolve artifacts by keys. It also serves as an IoC container
137 lines (126 loc) • 3.45 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
// Node module: @loopback/context
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {ClassDecoratorFactory} from '@loopback/metadata';
import {
asBindingTemplate,
asClassOrProvider,
asProvider,
BindingMetadata,
BindingSpec,
BINDING_METADATA_KEY,
isProviderClass,
removeNameAndKeyTags,
} from './binding-inspector';
import {Constructor} from './value-promise';
/**
* Decorator factory for `@injectable`
*/
class InjectableDecoratorFactory extends ClassDecoratorFactory<BindingMetadata> {
mergeWithInherited(inherited: BindingMetadata, target: Function) {
if (inherited) {
return {
templates: [
...inherited.templates,
removeNameAndKeyTags,
...this.spec.templates,
],
target: this.spec.target,
};
} else {
this.withTarget(this.spec, target);
return this.spec;
}
}
mergeWithOwn(ownMetadata: BindingMetadata) {
return {
templates: [...ownMetadata.templates, ...this.spec.templates],
target: this.spec.target,
};
}
withTarget(spec: BindingMetadata, target: Function) {
spec.target = target as Constructor<unknown>;
return spec;
}
}
/**
* Decorate a class with binding configuration
*
* @example
* ```ts
* @injectable((binding) => {binding.inScope(BindingScope.SINGLETON).tag('controller')}
* )
* @injectable({scope: BindingScope.SINGLETON})
* export class MyController {
* }
* ```
*
* @param specs - A list of binding scope/tags or template functions to
* configure the binding
*/
export function injectable(...specs: BindingSpec[]): ClassDecorator {
const templateFunctions = specs.map(t => {
if (typeof t === 'function') {
return t;
} else {
return asBindingTemplate(t);
}
});
return (target: Function) => {
const cls = target as Constructor<unknown>;
const spec: BindingMetadata = {
templates: [asClassOrProvider(cls), ...templateFunctions],
target: cls,
};
const decorator = InjectableDecoratorFactory.createDecorator(
BINDING_METADATA_KEY,
spec,
{decoratorName: '@injectable'},
);
decorator(target);
};
}
/**
* A namespace to host shortcuts for `@injectable`
*/
export namespace injectable {
/**
* `@injectable.provider` to denote a provider class
*
* A list of binding scope/tags or template functions to configure the binding
*/
export function provider(
...specs: BindingSpec[]
): (target: Constructor<unknown>) => void {
return (target: Constructor<unknown>) => {
if (!isProviderClass(target)) {
throw new Error(`Target ${target} is not a Provider`);
}
injectable(
// Set up the default for providers
asProvider(target),
// Call other template functions
...specs,
)(target);
};
}
}
/**
* `@bind` is now an alias to {@link injectable} for backward compatibility
* {@inheritDoc injectable}
*/
export function bind(...specs: BindingSpec[]): ClassDecorator {
return injectable(...specs);
}
/**
* Alias namespace `bind` to `injectable` for backward compatibility
*
* It should have the same members as `bind`.
*/
export namespace bind {
/**
* {@inheritDoc injectable.provider}
*/
export const provider = injectable.provider;
}