middleware()

Define middleware that wraps command execution with pre/post logic.

Import from maltty.

import { middleware } from 'maltty'

const timing = middleware(async (ctx, next) => {
  const start = Date.now()
  await next()
  ctx.log.info(`Completed in ${Date.now() - start}ms`)
})

Signature

function middleware<TEnv>(fn: MiddlewareFn): Middleware<TEnv>
ParameterTypeDescription
fnMiddlewareFnFunction receiving (ctx, next)

The TEnv type parameter declares environment requirements (e.g., which properties middleware adds to ctx).

Root vs command middleware

Root middleware runs for every command. Register on cli():

cli({
  name: 'my-app',
  version: '1.0.0',
  middleware: [timing, logging],
  commands: { deploy },
})

Command middleware runs only for a specific command:

const deploy = command({
  description: 'Deploy the application',
  middleware: [requireAuth],
  async handler(ctx) {
    ctx.log.raw('Deploying')
  },
})

Root middleware wraps command middleware, which wraps the handler (onion model).

decorateContext()

Add a typed, immutable property to a context instance at runtime. Used by middleware authors to extend ctx with custom properties.

import { decorateContext, middleware } from 'maltty'

import type { HttpClient } from 'maltty/http'

declare module 'maltty' {
  interface Context {
    readonly github: HttpClient
  }
}

const github = middleware(async (ctx, next) => {
  decorateContext(ctx, 'github', createHttpClient({ baseUrl: 'https://api.github.com' }))
  await next()
})
ParameterTypeDescription
ctxContextThe context instance (mutated in place)
keystringThe property name
valueunknownThe property value

Returns the same ctx reference. The property is non-writable and non-configurable after assignment. Pair with module augmentation on the Context interface for compile-time visibility.

References