keymetrics/pm2-io-apm

View on GitHub
src/featureManager.ts

Summary

Maintainability
A
55 mins
Test Coverage

import { NotifyFeature } from './features/notify'
import { ProfilingFeature } from './features/profiling'
import { EventsFeature } from './features/events'
import { IOConfig } from './pmx'
import { MetricsFeature } from './features/metrics'
import { DependenciesFeature } from './features/dependencies'
import * as Debug from 'debug'

export function getObjectAtPath (context: Object, path: string): any {
  if (path.indexOf('.') === -1 && path.indexOf('[') === -1) {
    return context[path]
  }

  let crumbs = path.split(/\.|\[|\]/g)
  let i = -1
  let len = crumbs.length
  let result

  while (++i < len) {
    if (i === 0) result = context
    if (!crumbs[i]) continue
    if (result === undefined) break
    result = result[crumbs[i]]
  }

  return result
}

class AvailableFeature {
  /**
   * Name of the feature
   */
  name: string
  /**
   * The non-instancied class of the feature, used to init it
   */
  module: { new(): Feature }
  /**
   * Option path is the path of the configuration for this feature
   * Possibles values:
   *  - undefined: the feature doesn't need any configuration
   *  - '.': the feature need the top level configuration
   *  - everything else: the path to the value that contains the config, it can any anything
   */
  optionsPath?: string
  /**
   * Current instance of the feature used
   */
  instance?: Feature
}

const availablesFeatures: AvailableFeature[] = [
  {
    name: 'notify',
    optionsPath: '.',
    module: NotifyFeature
  },
  {
    name: 'profiler',
    optionsPath: 'profiling',
    module: ProfilingFeature
  },
  {
    name: 'events',
    module: EventsFeature
  },
  {
    name: 'metrics',
    optionsPath: 'metrics',
    module: MetricsFeature
  },
  {
    name: 'dependencies',
    module: DependenciesFeature
  }
]

export class FeatureManager {

  private logger: Function = Debug('axm:features')
  /**
   * Construct all the features and init them with their respective configuration
   * It will return a map with each public API method
   */
  init (options: IOConfig): void {
    for (let availableFeature of availablesFeatures) {
      this.logger(`Creating feature ${availableFeature.name}`)
      const feature = new availableFeature.module()
      let config: any = undefined
      if (typeof availableFeature.optionsPath !== 'string') {
        config = {}
      } else if (availableFeature.optionsPath === '.') {
        config = options
      } else {
        config = getObjectAtPath(options, availableFeature.optionsPath)
      }
      this.logger(`Init feature ${availableFeature.name}`)
      // @ts-ignore
      // thanks mr typescript but we don't know the shape that the
      // options will be, so we just ignore the warning there
      feature.init(config)
      availableFeature.instance = feature
    }
  }

  /**
   * Get a internal implementation of a feature method
   * WARNING: should only be used by user facing API
   */
  get (name: string): Feature {
    const feature = availablesFeatures.find(feature => feature.name === name)
    if (feature === undefined || feature.instance === undefined) {
      throw new Error(`Tried to call feature ${name} which doesn't exist or wasn't initiated`)
    }
    return feature.instance
  }

  destroy () {
    for (let availableFeature of availablesFeatures) {
      if (availableFeature.instance === undefined) continue
      this.logger(`Destroy feature ${availableFeature.name}`)
      availableFeature.instance.destroy()
    }
  }
}

// just to be able to cast
export class FeatureConfig { }

export interface Feature {
  init (config?: any): void
  destroy (): void
}