Skip to content

Hook Composition & Middleware Chain #10

Description

@ashigirl96

Summary

Enable Express/Koa-style middleware composition so hooks can be built from small, reusable units.

Motivation

Complex hooks tend to grow into monolithic handlers that are hard to test and reuse. A composition model lets users combine small, focused functions into pipelines.

Proposed API

import { compose, deny } from "@dawkinsuke/hooks"

cc.on("PreToolUse", "Bash", compose(
  logExecution,           // log the invocation
  rateLimiter(10),        // 10 calls/min limit
  guardDangerousCommands, // block dangerous commands
))

Design

Each middleware receives the input and a next function. Return HookOutput to short-circuit, or call next() to continue the chain:

import { HookOutput, deny } from "@dawkinsuke/hooks"

type Middleware<E extends HookEvent> = (
  input: HookEventMap[E],
  next: () => Promise<HookOutput | void>,
) => Promise<HookOutput | void>

function rateLimiter(max: number): Middleware<"PreToolUse"> {
  return async (input, next) => {
    const count = await store.get<number>("bash_count", 0)
    if (count >= max) return deny("Rate limit exceeded")
    await store.set("bash_count", count + 1)
    return next()
  }
}

function guardDangerousCommands(): Middleware<"PreToolUse"> {
  return async (input, next) => {
    if (input.tool_input.command?.match(/rm\s+-rf\s+\//)) {
      return deny("Destructive command blocked")
    }
    return next()
  }
}

Benefits

  • Each middleware is independently testable
  • Presets from the Built-in Hook Library are naturally middleware-shaped
  • HookOutput chaining works within middleware (deny("x").context("y"))
  • Order is explicit and readable
  • Early termination via HookOutput return short-circuits the chain
  • tool_input is Record<string, any> — no casts needed in middleware

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions