{"size": 3473, "ext": "js", "max_stars_repo_path": "lib/check.js", "max_stars_repo_name": "meltwater/mlabs-health", "max_stars_repo_stars_event_min_datetime": "2019-05-31T01:23:24.000Z", "max_stars_repo_stars_event_max_datetime": "2022-03-06T22:15:50.000Z", "max_issues_repo_path": "lib/check.js", "max_issues_repo_name": "meltwater/mlabs-health", "max_issues_repo_issues_event_min_datetime": "2019-09-07T11:49:47.000Z", "max_issues_repo_issues_event_max_datetime": "2022-02-15T20:13:23.000Z", "max_forks_repo_path": "lib/check.js", "max_forks_repo_name": "meltwater/mlabs-health", "max_forks_repo_forks_event_min_datetime": "2020-11-20T17:23:00.000Z", "max_forks_repo_forks_event_max_datetime": "2020-11-20T17:23:00.000Z"}
{"max_stars_count": 2, "max_issues_count": 11, "max_forks_count": 1, "avg_line_length": 29.4322033898, "max_line_length": 77, "alphanum_fraction": 0.642384106}
import cacheManager from 'cache-manager'
import { has, isNil, isNotNil, sleeP } from '@meltwater/phi'
import { v4 as uuidv4 } from 'uuid'

export const defaultError = 'Health check returned untrue.'

export const wrap = (target) => {
  if (typeof target === 'boolean') return async () => target
  if (typeof target === 'function') return async (...args) => target(...args)

  if (target != null && typeof target === 'object') {
    if (typeof target.health !== 'function') {
      throw new Error(
        'Object missing health method, cannot create health function from',
        target
      )
    }
    return async (...args) => target.health(...args)
  }

  throw new Error('Cannot create health function from', target)
}

const withTimeout = (ms, promise) =>
  new Promise((resolve, reject) => {
    const err = `Health check timed out after ${ms} ms`
    sleeP(ms)
      .then(() => reject(new Error(err)))
      .catch(reject)
    promise.then(resolve).catch(reject)
  })

const formatHealth = (health, cached) => {
  const hasHealthy = typeof health === 'object' && !isNil(health.healthy)

  if (hasHealthy) return { cached, error: null, ...health }

  if (health !== true) {
    const unhealthy = (error) => ({ healthy: false, cached, error })
    if (has('err', health)) return unhealthy(health.err)
    return unhealthy(new Error(defaultError))
  }

  return { healthy: true, cached, error: null }
}

/*
 * Creating an observable stream from an async function containing
 * the await keyword fails when used with concatMap.
 * This internal function is provided for creating observable steams
 * which emit promise wrapped health check objects
 * consistent with the async counterpart.
 * TODO: Replace use of this method when possible.
 * See https://github.com/ReactiveX/rxjs/issues/1497.
 */
export const createSyncCheck = (target) => (...args) => {
  if (typeof target !== 'function') {
    throw new Error('Cannot create sync health function from', target)
  }

  try {
    const check = target
    const health = check(...args)
    if (health !== true) throw new Error(defaultError)
    return Promise.resolve({ healthy: true, cached: false, error: null })
  } catch (error) {
    return Promise.resolve({ healthy: false, cached: false, error })
  }
}

export default (
  target,
  {
    ttl = 60,
    timeout = 60 * 1000,
    cache = cacheManager.caching({ ttl, max: 1 })
  } = {}
) => {
  const id = uuidv4()
  const healthCheck = wrap(target)

  const getHealth = async (...args) => {
    try {
      const health = await healthCheck(...args)
      const hasHealth = typeof health === 'object' && !isNil(health.health)
      if (hasHealth) return await wrap(health)(...args)
      return health
    } catch (err) {
      return { err }
    }
  }

  const getHealthWithTimeout = (...args) =>
    withTimeout(timeout, getHealth(...args))

  const checkHealth = async (...args) => {
    if (cache === null) {
      const health = await getHealthWithTimeout(...args)
      return formatHealth(health, false)
    }

    const healthFromCache = await cache.get(id)
    if (isNotNil(healthFromCache)) return formatHealth(healthFromCache, true)

    const freshHealth = await getHealthWithTimeout(...args)
    await cache.set(id, freshHealth)
    return formatHealth(freshHealth, false)
  }

  return async (...args) => {
    try {
      return await checkHealth(...args)
    } catch (error) {
      return { healthy: false, cached: false, error }
    }
  }
}
