diff --git a/src/container.ts b/src/container.ts index 9cb7d64..1c40614 100644 --- a/src/container.ts +++ b/src/container.ts @@ -12,11 +12,11 @@ import { type Provider, isAsyncFactoryProvider, } from "./providers.js"; -import { isInjectable } from "./decorators.js"; +import { getInjectableTarget, isInjectable } from './decorators.js'; export class Container { - private providers = new ProviderMap(); - private singletons = new SingletonMap(); + private providers: ProviderMap = new Map(); + private singletons: SingletonMap = new Map(); bind(provider: Provider): this { const token = isConstructorProvider(provider) ? provider : provider.provide; @@ -96,7 +96,7 @@ export class Container { if (isClassToken(token) && isInjectable(token)) { this.bind({ provide: token, - useClass: token, + useClass: getInjectableTarget(token), }); // inheritance support: also bind for its super classes @@ -206,24 +206,14 @@ function doConstruct( } } -class ProviderMap extends Map, Provider> { - override get(key: Token): Provider | undefined { - return super.get(key) as Provider | undefined; - } - - override set(key: Token, value: Provider): this { - return super.set(key, value); - } +interface ProviderMap extends Map, Provider> { + get(key: Token): Provider | undefined + set(key: Token, value: Provider): this } -class SingletonMap extends Map, unknown> { - override get(token: Token): T | undefined { - return super.get(token) as T | undefined; - } - - override set(token: Token, value: T): this { - return super.set(token, value); - } +interface SingletonMap extends Map, unknown> { + get(token: Token): T | undefined + set(token: Token, value: T): this } export function bootstrap(token: Token): T { diff --git a/src/decorators.ts b/src/decorators.ts index 2c00a37..dca7fbd 100644 --- a/src/decorators.ts +++ b/src/decorators.ts @@ -6,14 +6,30 @@ type ClassDecorator> = (target: C) => C | void; export const injectableSymbol = Symbol("injectable"); export function injectable>(): ClassDecorator { - return (target) => + return (target) => { + let superClass = Object.getPrototypeOf(target); + while (superClass.name) { + Object.defineProperty(superClass, injectableSymbol, { + get: () => target, + }); + superClass = Object.getPrototypeOf(superClass); + } + Object.defineProperty(target, injectableSymbol, { - value: true, - writable: false, + get: () => target, }); + }; } -export function isInjectable(target: Class) { +export function isInjectable(target: Class): target is Class & { [injectableSymbol]: Class } { // eslint-disable-next-line no-prototype-builtins return target.hasOwnProperty(injectableSymbol); } + +export function getInjectableTarget(target: Class): Class { + if (!isInjectable(target)) { + throw Error(`Target ${target} nor any of its subclasses is not annotated with @injectable`) + } + + return target[injectableSymbol]; +} diff --git a/src/providers.test.ts b/src/providers.test.ts index e93224d..197bbf5 100644 --- a/src/providers.test.ts +++ b/src/providers.test.ts @@ -194,6 +194,7 @@ describe("Providers", () => { const container = new Container(); + expect(container.get(AbstractService)).toBeInstanceOf(FooService); expect(container.get(FooService)).toBeInstanceOf(FooService); expect(container.get(FooService)).toBeInstanceOf(AbstractService); diff --git a/src/tokens.ts b/src/tokens.ts index 94180f2..1e81527 100644 --- a/src/tokens.ts +++ b/src/tokens.ts @@ -20,7 +20,7 @@ interface AsyncInjectionTokenOptions { export class InjectionToken { constructor( private description: string | symbol, - public options?: InjectionTokenOptions> | AsyncInjectionTokenOptions>, + public options?: InjectionTokenOptions | AsyncInjectionTokenOptions, ) {} public toString(): string {