diff --git a/src/di.ts b/src/di.ts new file mode 100644 index 000000000..42827b0d7 --- /dev/null +++ b/src/di.ts @@ -0,0 +1,74 @@ + +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); + } + +} + +