Skip to content

Middleware ​

Middleware wraps downstream execution and can add typed context to the command handler chain. Use derive() when you need typed resolved flags or args before the action. Use middleware when you need next() around the rest of the pipeline.

Defining Middleware ​

ts
import { middleware } from '@kjanat/dreamcli';

const timing = middleware<{ startTime: number }>(
  async ({ next }) => {
    const startTime = Date.now();
    await next({ startTime });
  },
);

const trace = middleware<{ traceId: string }>(
  async ({ next }) =>
    next({ traceId: crypto.randomUUID() }),
);

The generic parameter declares the context shape this middleware provides. The next() call passes context downstream and continues the chain.

Stacking Middleware ​

ts
import { command, middleware } from '@kjanat/dreamcli';

const timing = middleware<{ startTime: number }>(
  async ({ next }) => next({ startTime: Date.now() }),
);
const trace = middleware<{ traceId: string }>(
  async ({ next }) =>
    next({ traceId: crypto.randomUUID() }),
);

command('deploy')
  .middleware(timing)
  .middleware(trace)
  .action(({ ctx }) => {
    const { traceId, startTime } = ctx;
    console.log(
      `[${traceId}] deployed in ${Date.now() - startTime}ms`,
    );
  });

Context types intersect: { startTime: number } & { traceId: string }. Each middleware only needs to know about its own context shape.

Middleware Parameters ​

The middleware handler receives:

ts
import { middleware } from '@kjanat/dreamcli';

middleware(
  async ({ flags, args, ctx, out, meta, next }) => {
    // flags — resolved flag values (type-erased)
    // args  — resolved argument values (type-erased)
    // ctx   — accumulated middleware context (type-erased)
    // out   — output channel
    // meta  — CLI metadata { name, bin, version, command }
    // next  — continue chain, passing context
    return next({});
  },
);

If you need typed command-scoped access to resolved inputs, prefer command(...).derive(...).

Error Handling ​

Middleware can catch and transform errors:

ts
import { CLIError, middleware } from '@kjanat/dreamcli';

const errorBoundary = middleware(async ({ next, out }) => {
  try {
    return await next({});
  } catch (err) {
    if (err instanceof CLIError) {
      out.error(err.message);
    }
    throw err;
  }
});

What's Next? ​

Released under the MIT License.