vulcain-corejs
Version:
Vulcain micro-service framework
293 lines (291 loc) • 11.7 kB
JavaScript
"use strict";
require("reflect-metadata");
const preloader_1 = require("../preloader");
const scope_1 = require("./scope");
const resolvers_1 = require("./resolvers");
const resolvers_2 = require("../di/resolvers");
const annotations_1 = require("../di/annotations");
const rabbitAdapter_1 = require("../bus/rabbitAdapter");
const provider_1 = require("../providers/memory/provider");
const provider_2 = require("../providers/mongo/provider");
const providerFactory_1 = require("../providers/providerFactory");
const schema_1 = require("../schemas/schema");
const annotations_2 = require("./annotations");
const files_1 = require("../utils/files");
const conventions_1 = require("../utils/conventions");
const requestContext_1 = require("./../servers/requestContext");
const serviceDescriptions_1 = require("./../pipeline/serviceDescriptions");
const vulcainLogger_1 = require("./../configurations/log/vulcainLogger");
const system_1 = require("./../configurations/globals/system");
const consoleMetrics_1 = require("../metrics/consoleMetrics");
const statsdMetrics_1 = require("../metrics/statsdMetrics");
const defaultAuthorizationPolicy_1 = require("../servers/policy/defaultAuthorizationPolicy");
const defaultTenantPolicy_1 = require("../servers/policy/defaultTenantPolicy");
/**
* Component container for dependency injection
*
* @export
* @class Container
* @implements {IContainer}
*/
class Container {
/**
* Creates an instance of Container.
*
* @param {IContainer} [parent]
* @param {RequestContext} [requestContext]
*
* @memberOf Container
*/
constructor(parent, requestContext) {
this.parent = parent;
this.resolvers = new Map();
if (parent && !requestContext)
throw new Error("RequestContext must not be null.");
this.scope = new scope_1.Scope(parent && parent.scope, requestContext);
this.injectInstance(this, annotations_1.DefaultServiceNames.Container);
this.setRequestContext(requestContext);
if (!parent) {
this.injectInstance(new vulcainLogger_1.VulcainLogger(), annotations_1.DefaultServiceNames.Logger);
this.injectSingleton(serviceDescriptions_1.ServiceDescriptors, annotations_1.DefaultServiceNames.ServiceDescriptors);
this.injectSingleton(providerFactory_1.ProviderFactory, annotations_1.DefaultServiceNames.ProviderFactory);
this.injectSingleton(system_1.System.isDevelopment ? consoleMetrics_1.ConsoleMetrics : statsdMetrics_1.StatsdMetrics, annotations_1.DefaultServiceNames.Metrics);
this.injectSingleton(defaultAuthorizationPolicy_1.DefaultAuthorizationPolicy, annotations_1.DefaultServiceNames.AuthorizationPolicy);
this.injectSingleton(defaultTenantPolicy_1.DefaultTenantPolicy, annotations_1.DefaultServiceNames.TenantPolicy);
}
}
/**
* used by test
*
* @protected
* @param {RequestContext} requestContext
*
* @memberOf Container
*/
setRequestContext(requestContext) {
if (requestContext) {
this.injectInstance(requestContext, annotations_1.DefaultServiceNames.RequestContext);
this.scope.requestContext = requestContext;
}
}
dispose() {
this.scope.dispose();
this.resolvers.clear();
this.parent = null;
}
/**
* Inject all components from files of the specified folder.
* Files are loaded recursively
*
* @param {string} folder path relative to the current directory
* @returns the current container
*/
injectFrom(path) {
files_1.Files.traverse(path);
return this;
}
/**
*
*
* @param {string} address rabbitmq server address (only host name and optional port)
* @param {any} [usage=BusUsage.all]
*/
useRabbitBusAdapter(address, usage = resolvers_2.BusUsage.all) {
let uri = system_1.System.resolveAlias(address || conventions_1.Conventions.instance.defaultRabbitAddress);
let bus = new rabbitAdapter_1.RabbitAdapter("amqp://" + (uri || address || conventions_1.Conventions.instance.defaultRabbitAddress));
if (usage === resolvers_2.BusUsage.all || usage === resolvers_2.BusUsage.eventOnly)
this.injectInstance(bus, annotations_1.DefaultServiceNames.EventBusAdapter);
if (usage === resolvers_2.BusUsage.all || usage === resolvers_2.BusUsage.commandOnly)
this.injectInstance(bus, annotations_1.DefaultServiceNames.ActionBusAdapter);
}
/**
* Use mongo provider
*
* @param {string} mongo server address (only host name or list of host names)
* @param {any} [mongoOptions] Mongodb options
*/
useMongoProvider(address, mongoOptions) {
let uri = system_1.System.resolveAlias(address || conventions_1.Conventions.instance.defaultMongoAddress);
uri = "mongodb://" + (uri || address || conventions_1.Conventions.instance.defaultMongoAddress);
this.injectTransient(provider_2.MongoProvider, annotations_1.DefaultServiceNames.Provider, uri, mongoOptions);
}
/**
* Use a memory provider by default
*
* @param {string} [folder] Data can be persisted on disk (on every change)
*/
useMemoryProvider(folder) {
this.injectTransient(provider_1.MemoryProvider, annotations_1.DefaultServiceNames.Provider, folder);
}
/**
* Register a instance of a component
*
* @param name
* @param fn
* @returns {Container}
*/
injectInstance(fn, name) {
if (!name)
throw new Error("Name is required.");
this.resolvers.set(name, new resolvers_1.InstanceResolver(fn));
if (name !== "Container" && fn.name)
system_1.System.log.info(null, "INFO: Register instance component " + name + " as " + fn.name);
return this;
}
injectSingleton(fn, nameOrArray, ...args) {
let name;
if (Array.isArray(nameOrArray)) {
args = nameOrArray;
name = null;
}
else {
name = nameOrArray;
}
let attr = Reflect.getOwnMetadata(Symbol.for("di:export"), fn);
name = name || attr && attr.name || fn.name;
if (!name)
throw new Error("Can not find a name when injecting component. Use @Export.");
this.resolvers.set(name, new resolvers_1.SingletonResolver(fn, Array.from(args)));
if (fn.name)
system_1.System.log.info(null, "INFO: Register instance component " + name + " as " + fn.name);
return this;
}
injectTransient(fn, nameOrArray, ...args) {
let name;
if (Array.isArray(nameOrArray)) {
args = nameOrArray;
name = null;
}
else {
name = nameOrArray;
}
let attr = Reflect.getOwnMetadata(Symbol.for("di:export"), fn);
name = name || attr && attr.name || fn.name;
if (!name)
return;
this.resolvers.set(name, new resolvers_1.Resolver(fn, annotations_2.LifeTime.Transient, Array.from(args)));
if (fn.name)
system_1.System.log.info(null, "INFO: Register instance component " + name + " as " + fn.name);
return this;
}
injectScoped(fn, nameOrArray, ...args) {
let name;
if (Array.isArray(nameOrArray)) {
args = nameOrArray;
name = null;
}
else {
name = nameOrArray;
}
let attr = Reflect.getOwnMetadata(Symbol.for("di:export"), fn);
name = name || attr && attr.name || fn.name;
if (!name)
throw new Error("Cannot find a name when injecting component. Use @Export.");
this.resolvers.set(name, new resolvers_1.ScopedResolver(fn, Array.from(args)));
if (fn.name)
system_1.System.log.info(null, "INFO: Register instance component " + name + " as " + fn.name);
return this;
}
/**
* Helper for injecting component
*
* @param {string} name Component name
* @param {any} fn Component constructor
* @param {LifeTime} lifeTime Component lifetime
*
* @memberOf Container
*/
inject(name, fn, lifeTime) {
if (lifeTime) {
switch (lifeTime) {
case annotations_2.LifeTime.Singleton:
this.injectSingleton(fn, name);
break;
case annotations_2.LifeTime.Scoped:
this.injectScoped(fn, name);
break;
case annotations_2.LifeTime.Transient:
this.injectTransient(fn, name);
break;
}
}
else
this.injectTransient(fn, name);
}
/**
*
* Instanciate a component and resolve all of its dependencies
* @param fn Component constructor
* @param args List of optional arguments
* @returns {null}
*/
resolve(fn, ...args) {
if (typeof fn !== "function")
throw new Error("fn must be a ctor");
let resolver = new resolvers_1.Resolver(fn, annotations_2.LifeTime.Transient, Array.from(args));
return this._resolve(this, resolver);
}
_resolve(parentContainer, resolver, name, optional) {
let instance = resolver && resolver.resolve(this, name, parentContainer);
if (!instance && !optional)
throw new Error("Unable to resolve component " + name);
return instance;
}
findResolver(name) {
let self = this;
while (self) {
let resolver = self.resolvers.get(name);
if (resolver)
return { resolver, container: self };
self = self.parent;
}
return null;
}
/**
* Get a component by name.
* Throw an exception if the component doesn't exist
* @template T
* @param {string} component name
* @param {boolean} [optional] if true no exception are raised if the component doesn't exist
* @param {LifeTime} [assertLifeTime] If provide check if the registered component has the expected {LifeTime}
* @returns A component
*/
get(name, optional, assertLifeTime) {
let res = this.findResolver(name);
let resolver = res && res.resolver;
if (assertLifeTime && resolver) {
if (!(assertLifeTime & resolver.lifeTime))
throw new Error("Component " + name + " must be declared with a life time = " + assertLifeTime);
}
let component = this._resolve(res && res.container, resolver, name, optional);
return component;
}
}
exports.Container = Container;
/**
* Default container for test
*
* @export
* @class TestContainer
* @extends {Container}
*/
class TestContainer extends Container {
/**
* Creates an instance of TestContainer.
*
* @param {string} domainName Domain name
* @param {(container: IContainer) => void} [addDefaultServices] Additional default services to register
*/
constructor(domainName, addDefaultServices) {
super();
this.domainName = domainName;
this.setRequestContext(requestContext_1.RequestContext.createMock(this));
this.injectTransient(provider_1.MemoryProvider, annotations_1.DefaultServiceNames.Provider);
let domain = new schema_1.Domain(domainName, this);
this.injectInstance(domain, annotations_1.DefaultServiceNames.Domain);
addDefaultServices && addDefaultServices(this);
preloader_1.Preloader.instance.runPreloads(this, domain);
}
}
exports.TestContainer = TestContainer;
//# sourceMappingURL=containers.js.map