export type Newable = { new(...args: any): T; } export type Factory = Newable; export type ServiceID = string | symbol | Factory; function isFactory(value: any): boolean { return typeof value === 'object' && value !== null && value instanceof Function } export function inject(target: any, key: PropertyKey, index: number) { //return function(target: any) { // This is a no-op because adding the decorator // is enough for TypeScript to emit metadata //} } function describeFactory(factory: Factory): string { return factory.name; } export class Container { private factories: Factory[] = []; private singletons = new Map(); public bindSelf(value: Factory | T): void { if (isFactory(value)) { this.factories.push(value as Factory); } else { this.singletons.set((value as T).constructor as ServiceID, value) } } private canResolve(serviceId: ServiceID): boolean { return this.singletons.has(serviceId); } private resolve(factory: Factory): T; private resolve(serviceId: ServiceID): any { return this.singletons.get(serviceId); } public createInstance(factory: Factory, ...args: any[]): T { const newArgs: any[] = []; const paramTypes = Reflect.getMetadata('design:paramtypes', factory); if (paramTypes === undefined) { return new factory(...args); } let i = 0; for (const paramType of paramTypes) { if (this.canResolve(paramType)) { newArgs.push(this.resolve(paramType)); } else { if (i >= args.length) { throw new Error(`Too few arguments provided to constructor of '${describeFactory(factory)}'.`); } newArgs.push(args[i++]); } } if (i < args.length) { throw new Error(`Too many arguments provided to factory function '${describeFactory(factory)}'.`); } return new factory(...newArgs); } }