All files / services ComponentContainer.js

100% Statements 23/23
100% Branches 16/16
100% Functions 11/11
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90                                          16x 1x     15x               6x 2x     3x       5x 5x 1x     4x     3x     1x       5x 4x 1x     4x           4x     6x     7x       8x 4x 1x     3x              
// @flow
 
import CircularDependencyError from '../errors/CircularDependencyError';
import DuplicateError from '../errors/DuplicateError';
import NotFoundError from '../errors/NotFoundError';
 
export type ComponentDefinition = {
  name: string,
  constructor: Function,
  dependencies: string[],
};
 
export type ComponentDefinitionMap = {
  [string]: ComponentDefinition,
};
 
export class ComponentContainer {
  registered: ComponentDefinitionMap = {};
  components = {};
 
  register({ name, constructor, dependencies }: ComponentDefinition): void {
    if (this.registered[name]) {
      throw new DuplicateError(name);
    }
 
    this.registered[name] = {
      name,
      constructor,
      dependencies,
    };
  }
 
  start(): void {
    if (this.areDependenciesCyclic()) {
      throw new CircularDependencyError();
    }
 
    this.createDependencies();
  }
 
  getComponent = (name: string): any => {
    const component = this.components[name];
    if (component === undefined) {
      throw new NotFoundError(name);
    }
 
    return component;
  }
 
  createDependencies = () => Object.keys(this.registered).forEach(this.createDependency);
 
  createNodeDependencies = (key: string) => {
    this.registered[key].dependencies.forEach(this.createDependency);
  }
 
  createDependency = (dep: string) => {
    if (this.components[dep] === undefined) {
      if (this.registered[dep].dependencies.length > 0) {
        this.createNodeDependencies(dep);
      }
 
      this.components[dep] = new this.registered[dep]
        .constructor(...this.getDependenciesFromContainer(this.registered[dep].dependencies));
    }
  }
 
  getDependenciesFromContainer = (dependencies: string[]): any[] =>
    dependencies.map(this.getComponent)
 
  areDependenciesCyclic(): boolean {
    return Object.keys(this.registered).some(this.isComponentCyclic);
  }
 
  isComponentCyclic = (key: string): boolean => this.registered[key].dependencies.includes(key)
      || this.checkComponentDependencies(key, this.registered[key].dependencies)
 
  checkComponentDependencies(key: string, dependencies: string[]): boolean {
    return dependencies.some((dep: string) => {
      if (this.registered[dep] === undefined) {
        throw new NotFoundError(dep);
      }
 
      return dependencies.includes(key)
        || this.checkComponentDependencies(key, this.registered[dep].dependencies);
    });
  }
}
 
export default new ComponentContainer();