<|start_filename|>lib/server/middleware/validate.js<|end_filename|>
import joi from 'joi'
import boom from 'boom'

export default validate => (req, res, next) => {
  let data = {}

  Object.keys(validate).forEach(key => (data[key] = req[key]))

  const { value, error } = joi.validate(data, validate, { stripUnknown: true })

  if (error) {
    if (error.isBoom) {
      return next(error)
    }

    const [{ message, path }] = error.details

    return next(boom.badRequest(message, { source: path.join('.') }))
  }

  Object.keys(value).forEach(key => (req[key] = value[key]))

  next()
}


<|start_filename|>lib/server/middleware/index.js<|end_filename|>
import { json } from 'body-parser'
import query from './query'
import send from './send'

export default router => {
  router.use(send())
  router.use(query())
  router.use(json({ limit: '10mb' }))
}


<|start_filename|>lib/server/middleware/query.js<|end_filename|>
import { parse } from 'querystring'

export default () => (req, res, next) => {
  const { query } = req._parsedUrl
  req.query = query ? parse(query) : {}
  next()
}


<|start_filename|>lib/helpers/map-series.js<|end_filename|>
// Borrowed from https://github.com/toniov/p-iteration/blob/master/lib/static-methods.js
export default async (array, callback) => {
  const result = []

  for (let i = 0; i < array.length; i++) {
    if (i in array) {
      result[i] = await callback.call(this, await array[i], i, array)
    }
  }

  return result
}


<|start_filename|>lib/server/index.js<|end_filename|>
import router from 'router'
import http from 'http'
import mapSeries from '../helpers/map-series'
import asynchronous from './middleware/asynchronous'
import finalhandler from './finalhandler'
import middleware from './middleware'
import route from './route'
import logger from '../helpers/logger'
import Emitter from './emitter'

export default (options = {}) => {
  // Prepare internals
  const internals = {
    // Prepare router
    router: router(),

    // Prepare server
    server: (options.server || http).createServer((req, res) => {
      internals.router(req, res, finalhandler(req, res))
    }),

    // Prepare event emitter
    emitter: new Emitter(),

    // Prepare store
    store: {},

    // Prepare routes array
    routes: []
  }

  // Apply middleware
  middleware(internals.router)

  // Pleasant...
  class Pleasant {
    constructor() {
      this.listener = internals.server
    }

    // Register plugins
    async register(...args) {
      // Get arguments
      const prefix = typeof args[0] === 'string' ? args.shift() : null
      const plugins = !Array.isArray(args[0]) ? [args[0]] : args[0]
      const config = args[1] || {}

      // Save reference to top-level router
      const app = internals.router

      // Prefix enabled?
      if (prefix) {
        // Attach temporary router
        internals.router = router()

        // State that router is prefixed
        internals.router.prefix = `${app.prefix || ''}${prefix}`
      }

      // Register plugins
      await mapSeries(plugins, async plugin => {
        const fn = (plugin && plugin.default) || plugin

        if (typeof fn !== 'function') {
          return
        }

        await fn(this, config).catch(err => {
          logger.error(err)
          process.exit()
        })
      })

      // Prefix enabled?
      if (prefix) {
        // Add temporary router as prefixed middleware to application router
        app.use(prefix, internals.router)

        // Change back to top-level router
        internals.router = app
      }
    }

    // Add event handler
    on(...args) {
      return internals.emitter.on(...args)
    }

    // Remove event handler
    off(...args) {
      return internals.emitter.off(...args)
    }

    // Emit event
    emit(...args) {
      return internals.emitter.emit(...args)
    }

    // Start listeningen
    async listen(...args) {
      await this.emit('ready')
      return internals.server.listen(...args)
    }

    // Close server
    close(...args) {
      return internals.server.close(...args)
    }

    // Add route
    route(config) {
      const { router } = internals
      const { prefix } = router

      const methods = Array.isArray(config.method)
        ? config.method
        : [config.method]

      methods.forEach(method => {
        route(router, {
          ...config,
          method
        })

        let url = prefix ? `${prefix}${config.url}` : config.url

        internals.routes.push({
          ...config,
          method,
          url
        })
      })
    }

    // Return list of routes
    routes() {
      return internals.routes
    }

    // Apply middleware
    use(...args) {
      // Wrap in asynchronous handler
      args = args.map(
        arg => (typeof arg === 'function' ? asynchronous(arg) : arg)
      )

      // Apply middleware
      internals.router.use(...args)

      // Make use statements chainable
      return this
    }

    // Set store value
    set(key, value) {
      internals.store[key] = value
    }

    // Get store value
    get(key) {
      return key ? internals.store[key] : internals.store
    }
  }

  // Return instance
  return new Pleasant()
}


<|start_filename|>lib/server/route.js<|end_filename|>
import http from 'http'
import validate from './middleware/validate'
import asynchronous from './middleware/asynchronous'

const methods = [...http.METHODS, 'ALL']

export default (router, route) => {
  if (!route.method) {
    throw new Error('Route: `method` not provided')
  }

  if (!methods.includes(route.method)) {
    throw new Error('Route: Invalid `method` provided')
  }

  if (!route.url) {
    throw new Error('Route: `url` not provided')
  }

  if (!route.handler) {
    throw new Error('Route: `handler` not provided')
  }

  route.handler = !Array.isArray(route.handler)
    ? [route.handler]
    : route.handler

  if (route.validate) {
    route.handler.splice(-1, 0, validate(route.validate))
  }

  route.handler = route.handler.map(asynchronous)

  router[route.method.toLowerCase()](route.url, ...route.handler)
}


<|start_filename|>lib/helpers/logger.js<|end_filename|>
export default {
  error: err => console.error(err)
}


<|start_filename|>lib/server/middleware/send.js<|end_filename|>
const TYPE = 'Content-Type'
const OSTREAM = 'application/octet-stream'

const sendResponse = (res, next, arg1, arg2) => {
  let headers = {}

  let code = typeof arg1 === 'number' ? arg1 : 200
  let data = (typeof arg1 === 'number' ? arg2 : arg1) || ''
  let type = res.getHeader(TYPE)

  if (!!data && data instanceof Error) {
    return next(data)
  }

  if (!!data && typeof data.pipe === 'function') {
    res.setHeader(TYPE, type || OSTREAM)
    return data.pipe(res)
  }

  if (data instanceof Buffer) {
    type = type || OSTREAM
  } else if (typeof data === 'object') {
    data = JSON.stringify(data)
    type = 'application/json;charset=utf-8'
  }

  headers[TYPE] = type || 'text/plain'
  headers['Content-Length'] = Buffer.byteLength(data)

  res.writeHead(code, headers)
  res.end(data)
}

export default () => (req, res, next) => {
  res.send = setImmediate.bind(null, sendResponse.bind(null, res, next))
  next()
}


<|start_filename|>lib/server/emitter.js<|end_filename|>
export default class Emitter {
  constructor() {
    this.listeners = Object.create(null)
  }

  on(type, handler) {
    if (!this.listeners[type]) {
      this.listeners[type] = []
    }

    this.listeners[type].push(handler)
  }

  off(type, handler) {
    if (this.listeners[type]) {
      let i = this.listeners[type].indexOf(handler)
      if (~i) this.listeners[type].splice(i, 1)
    }
  }

  async emit(type, evt) {
    await Promise.all(
      (this.listeners[type] || []).map(fn => Promise.resolve(fn(evt)))
    )

    await Promise.all(
      (this.listeners['*'] || []).map(fn => Promise.resolve(fn(type, evt)))
    )
  }
}


<|start_filename|>bin/pleasant.js<|end_filename|>
#!/usr/bin/env node
const semver = require('semver')
const colors = require('colors')
const { engines } = require('../package')
const version = engines.node

if (!semver.satisfies(process.version, version)) {
  console.error(
    colors.red(
      `pleasant requires node version ${version}. Not satisfied with current version ${process.version}.\n`
    )
  )
  process.exit()
}

require = require('esm')(module, { mode: 'auto', cjs: true })
require('../lib/cli/index.js').default()


<|start_filename|>lib/cli/help.js<|end_filename|>
import colors from 'colors/safe'

const { green } = colors

export default unknown => {
  const usage = `\n  Usage: ${green('pleasant')} [options] entry
  ${unknown
    ? `\n  The option "${unknown}" is unknown. Use one of these:\n`
    : ''}
  Options:
    ${green('-p, --port <n>')}  Port to listen on (defaults to 3000)
    ${green('-H, --host')}      The host on which pleasant will run
    ${green('-v, --version')}   Output the version number
    ${green('-h, --help')}      Show this usage information
  `

  return usage
}


<|start_filename|>lib/server/finalhandler.js<|end_filename|>
import boom from 'boom'
import logger from '../helpers/logger'

export default (req, res) => err => {
  if (err) {
    const boomed = err.isBoom
      ? err
      : boom.boomify(err, {
          statusCode: err.statusCode || err.status || 500
        })

    if (boomed.isServer) {
      logger.error(boomed)
    }

    res.statusCode = boomed.output.statusCode

    return res.end(
      JSON.stringify({
        ...boomed.output.payload,
        ...boomed.data
      })
    )
  }

  res.statusCode = 404

  return res.end(
    JSON.stringify({
      statusCode: 404,
      error: 'Not Found',
      message: 'Not Found'
    })
  )
}


<|start_filename|>lib/cli/setup.js<|end_filename|>
import path from 'path'
import colors from 'colors/safe'
import server from '../server'
import logger from '../helpers/logger'

const { red, green, cyan, bold } = colors

export default async ({ entry, port, host, manual }) => {
  const app = server()

  if (entry) {
    await app.register([import(path.resolve(process.cwd(), entry))])
  } else {
    console.log(red('No entry file specified\n'))
    process.exit()
  }

  app.listen(port, host, err => {
    if (err) {
      logger.error(err)
      process.exit()
    }

    const routes = app.routes()

    let status = bold(
      `Accepting connections on ${cyan.underline(`${host}:${port}`)}\n`
    )

    status += '\n'

    if (routes.length) {
      status += bold('Routes:\n')
      routes.forEach(route => {
        status += `- ${green(route.method.padEnd(8))} ${route.url}\n`
      })
    } else {
      status += red('No routes\n')
    }

    console.log(status)
  })
}


<|start_filename|>index.js<|end_filename|>
require = require('esm')(module, { mode: 'auto', cjs: true })
module.exports = require('./lib/server/index.js').default


<|start_filename|>lib/cli/index.js<|end_filename|>
import mri from 'mri'
import path from 'path'
import fs from 'fs'
import help from './help'
import setup from './setup'
import logger from '../helpers/logger'

export default async () => {
  const flags = mri(process.argv.slice(2), {
    default: {
      port: process.env.PORT || 3000,
      host: process.env.HOST || '0.0.0.0'
    },
    alias: {
      p: 'port',
      H: 'host',
      v: 'version',
      h: 'help'
    },
    unknown(uknown) {
      console.log(help(uknown))
      process.exit(1)
    }
  })

  if (flags.help) {
    console.log(help())
    process.exit()
  }

  if (flags.version) {
    const packageJson = fs.readFileSync(
      path.resolve(__dirname, '../../package.json'),
      'utf-8'
    )
    const { version } = JSON.parse(packageJson)

    console.log(version)
    process.exit()
  }

  const entry = flags._[0] || null
  const { port, host } = flags

  setup({ entry, port, host }).catch(err => {
    logger.error(err)
    process.exit()
  })

  process.on('uncaughtException', err => {
    logger.error(err)
    process.exit()
  })
}


<|start_filename|>lib/server/middleware/asynchronous.js<|end_filename|>
export default middleware => {
  if (middleware.length === 4) {
    return (err, req, res, next) => {
      Promise.resolve(middleware(err, req, res, next)).catch(next)
    }
  }
  return (req, res, next) => {
    Promise.resolve(middleware(req, res, next)).catch(next)
  }
}
