Skip to content

Middleware Reference

Middleware in Klix wraps command execution after parsing and before the handler runs.

For design guidance, see Using Middleware. This page focuses on the exact runtime contract.

Registration

Register middleware with @app.middleware:

python
@app.middleware
async def log(ctx: klix.MiddlewareContext, next: klix.NextFn) -> None:
    ctx.session.ui.print(f"running {ctx.command.name}", color="muted")
    await next(ctx)

MiddlewareContext

Fields:

  • raw_input: the original command text
  • session: the active Session
  • command: the parsed ParsedCommand
  • cancelled: boolean signal to stop execution

command is available before middleware runs. That is an important part of the current architecture.

NextFn

next is the continuation that advances the chain.

If middleware should allow the command to continue, it must call:

python
await next(ctx)

If middleware should stop the command, it can:

  • return early
  • set ctx.cancelled = True
  • optionally print an error or warning message

Execution Order

Middleware runs in registration order.

Given:

python
@app.middleware
async def a(ctx, next): ...


@app.middleware
async def b(ctx, next): ...

The effective nesting is:

text
a(b(handler))

Sync Middleware

The middleware chain expects async callables. In practice, define middleware as async def even if it only does synchronous work.

Typical Patterns

Logging

python
@app.middleware
async def audit(ctx, next):
    ctx.session.ui.print(f"[audit] {ctx.raw_input}", color="muted")
    await next(ctx)

Authorization

python
@app.middleware
async def require_login(ctx, next):
    if ctx.command.name == "/deploy" and not ctx.session.state.logged_in:
        ctx.cancelled = True
        ctx.session.ui.print("Please log in first.", color="error")
        return
    await next(ctx)

Status updates

python
@app.middleware
async def status(ctx, next):
    ctx.session.ui.layout.status.set("busy", ctx.command.name, color="muted")
    ctx.session.ui.layout.redraw_ui()
    try:
        await next(ctx)
    finally:
        ctx.session.ui.layout.status.set("ready", "", color="muted")
        ctx.session.ui.layout.redraw_ui()

Current Scope

Middleware applies to command execution. It is not a general-purpose pipeline for every keystroke or every UI action in the framework.

If you build a custom loop, you can still reuse the middleware chain directly, as the chat example does.

Common Mistakes

  • Forgetting await next(ctx).
  • Assuming middleware sees non-command text in the default app loop.
  • Catching errors and swallowing them without logging or re-raising.

Released under the MIT License.