contexify
Version:
A TypeScript library providing a powerful dependency injection container with context-based IoC capabilities, inspired by LoopBack's Context system.
133 lines (121 loc) • 3.35 kB
text/typescript
import { ClassDecoratorFactory } from 'metarize';
import type { Constructor } from '../utils/value-promise.js';
import {
asBindingTemplate,
asClassOrProvider,
asProvider,
BINDING_METADATA_KEY,
type BindingMetadata,
type BindingSpec,
isProviderClass,
removeNameAndKeyTags,
} from './binding-inspector.js';
/**
* Decorator factory for `@injectable`
*/
class InjectableDecoratorFactory extends ClassDecoratorFactory<BindingMetadata> {
mergeWithInherited(inherited: BindingMetadata, target: Constructor<unknown>) {
if (inherited) {
return {
templates: [
...inherited.templates,
removeNameAndKeyTags,
...this.spec.templates,
],
target: this.spec.target,
};
}
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: Constructor<unknown>) {
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;
}
return asBindingTemplate(t);
});
return (target: any) => {
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`
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
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: any) => void {
return (target: any) => {
const cls = target as Constructor<unknown>;
if (!isProviderClass(cls)) {
throw new Error(`Target ${cls} is not a Provider`);
}
injectable(
// Set up the default for providers
asProvider(cls),
// 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`.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace bind {
/**
* {@inheritDoc injectable.provider}
*/
export const provider = injectable.provider;
}