Skip to content

Commit

Permalink
fix(inheritance): trigger auto-binding when using parent class as tok…
Browse files Browse the repository at this point in the history
…en (#7)
  • Loading branch information
dirkluijk authored Sep 19, 2024
1 parent f39d6cd commit 0edbec7
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 25 deletions.
30 changes: 10 additions & 20 deletions src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(provider: Provider<T>): this {
const token = isConstructorProvider(provider) ? provider : provider.provide;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -206,24 +206,14 @@ function doConstruct<T>(
}
}

class ProviderMap extends Map<Token<unknown>, Provider<unknown>> {
override get<T>(key: Token<T>): Provider<T> | undefined {
return super.get(key) as Provider<T> | undefined;
}

override set<T>(key: Token<T>, value: Provider<T>): this {
return super.set(key, value);
}
interface ProviderMap extends Map<Token<unknown>, Provider<unknown>> {
get<T>(key: Token<T>): Provider<T> | undefined
set<T>(key: Token<T>, value: Provider<T>): this
}

class SingletonMap extends Map<Token<unknown>, unknown> {
override get<T>(token: Token<T>): T | undefined {
return super.get(token) as T | undefined;
}

override set<T>(token: Token<T>, value: T): this {
return super.set(token, value);
}
interface SingletonMap extends Map<Token<unknown>, unknown> {
get<T>(token: Token<T>): T | undefined
set<T>(token: Token<T>, value: T): this
}

export function bootstrap<T>(token: Token<T>): T {
Expand Down
24 changes: 20 additions & 4 deletions src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,30 @@ type ClassDecorator<C extends Class<unknown>> = (target: C) => C | void;
export const injectableSymbol = Symbol("injectable");

export function injectable<C extends Class<unknown>>(): ClassDecorator<C> {
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<T>(target: Class<T>) {
export function isInjectable<T>(target: Class<T>): target is Class<T> & { [injectableSymbol]: Class<unknown> } {
// eslint-disable-next-line no-prototype-builtins
return target.hasOwnProperty(injectableSymbol);
}

export function getInjectableTarget<T>(target: Class<T>): Class<unknown> {
if (!isInjectable(target)) {
throw Error(`Target ${target} nor any of its subclasses is not annotated with @injectable`)
}

return target[injectableSymbol];
}
1 change: 1 addition & 0 deletions src/providers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface AsyncInjectionTokenOptions<T> {
export class InjectionToken<T> {
constructor(
private description: string | symbol,
public options?: InjectionTokenOptions<NoInfer<T>> | AsyncInjectionTokenOptions<NoInfer<T>>,
public options?: InjectionTokenOptions<T> | AsyncInjectionTokenOptions<T>,
) {}

public toString(): string {
Expand Down

0 comments on commit 0edbec7

Please sign in to comment.