Abstract middleware stacks

{EventEmitter} = require 'events'

config = require 'config'
pkgman = require 'pkgman'

Implements a middleware stack. Middleware functions can be added to the stack with use. Calling dispatch invokes the middleware functions serially.

Defining middleware

Each middleware accepts an arbitrary parameters and finally a next function. When a middleware finishes, it must call the next function. If there was an error, it must be thrown or passed as the first argument to next. If no error occurred, next must be invoked without arguments. Error-handling middleware can also be defined. These middleware take an additional parameter at the beginning of the function signature: error. Error-handling middleware are only called if a previous middleware threw or passed an error. Conversely, non-error-handling middleware are skipped if a previous error occurred.

exports.Middleware = class Middleware extends EventEmitter

constructor

Create a middleware stack.

  constructor: -> @_middleware = []

Middlware#use

  • (function) fn - A middleware function.

Add a middleware function to the stack.

  use: (fn) -> @_middleware.push fn

Middleware#dispatch

  • (mixed) ... - Zero or more values to pass to the middleware.

  • (function) fn - A function invoked when the middleware stack has finished. If an error occurred, it will be passed as the first argument.

Invoke the middleware functions serially.

  dispatch: (args..., fn) ->
    self = this

    index = 0

    invoke = (error) ->

      self.emit 'invoked', self._middleware[index - 1] if index > 0

Call fn with any error if we're done.

      if index is self._middleware.length

Ignore any final error, as we don't want it to cascade back up the recursive invocation path.

        try
          return fn error
        catch error
          return

      current = self._middleware[index++]

Error-handling middleware.

      if current.length is args.length + 2

An error occurred previously.

        if error?

Try to invoke the middleware, if it throws, just catch the error and pass it along.

          try
            localArgs = args.concat()
            localArgs.unshift error
            localArgs.push invoke
            self.emit 'invoking', current
            current localArgs...
          catch error
            invoke error

No previous error; skip this middleware.

        else

          invoke error

Non-error-handling middleware.

      else

An error occurred previously, skip this middleware.

        if error?

          invoke error

No previous error.

        else

Try to invoke the middleware, if it throws, just catch the error and pass it along.

          try
            localArgs = args.concat()
            localArgs.push invoke
            self.emit 'invoking', current
            current localArgs...
          catch error
            invoke error

Kick things off.

    invoke()

middleware.fromHook

Create a middleware stack from the results of a hook and path configuration.

exports.fromHook = (hook, paths, args...) ->

  debugSilly = require('debug') 'truss-silly:middleware'

  middleware = new Middleware()

Invoke the hook and use the middleware in the paths configuration order.

  args.unshift hook
  hookResults = pkgman.invoke args...
  for path in paths ? []
    continue unless (spec = hookResults[path])?

    debugSilly "- - #{spec.label}"

    middleware.use fn for fn in spec.middleware ? []

  middleware

middleware.fromConfig

Create a middleware stack from a configuration path.

exports.fromConfig = (path) ->

  exports.fromHook(
    pkgman.PackageManager.normalizePath path
    config.get "packageConfig:#{path}"
  )