Skip to content

Instantly share code, notes, and snippets.

@ssube
Created March 24, 2017 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssube/d5d327991dabb8a83dbdfb3c627e1722 to your computer and use it in GitHub Desktop.
Save ssube/d5d327991dabb8a83dbdfb3c627e1722 to your computer and use it in GitHub Desktop.
import {Iterable, List, Map} from 'immutable';
import Config from '~/config/Config';
import {Log} from '~/log/Log';
import {Configurable} from '~/utils/Configurable';
import {Dependency, Descriptor} from './Describe';
import {Inject, injectionSymbol} from './Inject';
import {Module, ProviderType} from './Module';
export interface Constructor<R> {
new(...args: Array<any>): R;
}
// the contract for some type can be identified with...
export type Contract = string | symbol | Function;
/**
* @todo come up with a better name.
*/
export type Fulfillment = Map<Contract, any>;
function contractName(c: Contract): string {
if (typeof c === 'function') {
return c.name;
} else {
return c.toString();
}
}
/**
* This is an exceptionally minimal DI container.
*/
export class Container {
public static from(...modules: Array<Module>) {
return new Container(List(modules));
}
protected _modules: Iterable<number, Module>;
constructor(modules: Iterable<number, Module>) {
this._modules = modules;
}
public configure() {
this._modules.forEach((module) => {
if (module) {
module.configure(this);
}
});
}
public get<R>(contract: Constructor<R> | Contract, config: Config): R;
public get<R>(contract: Constructor<R> | Contract, ...args: Array<any>): R;
public get<R>(contract: Constructor<R> | Contract, args: Config | Array<any>): R {
const module = this.provides(contract);
if (module) {
const type = module.has(contract);
if (type === ProviderType.Constructor) {
return this._construct<R>(module.getConstructor<R>(contract), args);
} else if (type === ProviderType.Factory) {
return this._invoke(module, contract, args);
} else if (type === ProviderType.Instance) {
return this._retrieve(module, contract);
} else {
throw new Error('invalid provider type');
}
} else if (typeof contract === 'function') {
return this._construct(contract as Constructor<R>, args);
} else {
throw new Error(`no provider for contract ${contractName(contract)}`);
}
}
public _construct<R>(ctor: Constructor<R>, args: Config | Array<any>): R {
const deps = this.fulfill(ctor);
if (Array.isArray(args)) {
return Reflect.construct(ctor, [deps, ...args]);
} else {
return Reflect.construct(ctor, [deps.set(Config, args)]);
}
}
public _invoke(module: Module, contract: Contract, args: Config | Array<any>): any {
const factory = module.getFactory(contract);
if (Array.isArray(args)) {
return Reflect.apply(factory, this, args);
} else {
return Reflect.apply(factory, this, [args]);
}
}
public _retrieve(module: Module, contract: Contract): any {
return module.getInstance(contract);
}
/**
* Prepare a map with the dependencies for a descriptor.
*
* This will always inject the container itself to configure children.
*/
public dependencies(descriptor: Descriptor): Fulfillment {
type FulfillmentEntry = [Contract, any];
const values: Array<FulfillmentEntry> = descriptor.dependencies.map((dependency) => {
const {contract, name} = dependency;
const dep = this.get(contract);
const value: FulfillmentEntry = [name, dep];
return value;
});
return Map<Contract, any>(values).set(Container, this);
}
/**
* This is a simple helper for the common `describe` and `dependencies` pair.
*/
public fulfill(implementation: any): Fulfillment {
const descriptor = Reflect.get(implementation, injectionSymbol);
if (descriptor) {
return this.dependencies(descriptor);
} else {
return Map<Contract, any>();
}
}
public provides(contract: Contract): Module | undefined {
return this._modules.find((item) => (!!item && item.has(contract) !== ProviderType.None));
}
public with(...modules: Array<Module>): Container {
const merged = this._modules.concat(modules);
return new Container(merged);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment