Skip to content

@nwire/handler — alone

The operation primitive. One typed function declaration that runs on HTTP, on a queue worker, or as an MCP tool — the adopter decides where.

What it does

  • defineHandler(name, { input, handler }) produces a typed operation.
  • The same definition can be wired via app.wire(httpBinding, handler), app.wire(queueBinding, handler), or app.wire(toolBinding, handler).
  • No transport coupling in your handler body — ctx is the same shape across all of them.

Install

bash
pnpm add @nwire/handler zod

Declare once

ts
import { defineHandler } from "@nwire/handler"
import { z } from "zod"

export const summarizeText = defineHandler("ai.summarize-text", {
  input: z.object({ text: z.string().max(50_000) }),
  handler: async (ctx) => ({
    summary: await callLLM(ctx.input),
  }),
})

Wire it on multiple transports

ts
import { createApp } from "@nwire/app"
import { endpoint } from "@nwire/endpoint"
import { post } from "@nwire/wires/http"
import { queue } from "@nwire/wires/queue"
import { tool } from "@nwire/wires/mcp"
import { httpKoa } from "@nwire/koa"
import { queueInMemory } from "@nwire/queue"
import { mcpAdapter } from "@nwire/mcp"
import { z } from "zod"

const app = createApp({ appName: "ai-tools" })

// One handler, three transports — no per-transport code change.
app.wire(post("/summarize", { body: z.object({ text: z.string() }) }), summarizeText)
app.wire(queue("ai.summarize"), summarizeText)
app.wire(tool("summarize-text", { input: z.object({ text: z.string() }) }), summarizeText)

await app.start()

await endpoint("ai-tools", { port: 3000 })
  .use(httpKoa({ prefix: "/api" }))
  .use(queueInMemory())
  .use(mcpAdapter())
  .mount(app)
  .run()

When to use this and not defineAction

defineAction (from @nwire/forge) carries domain metadata (persona, journeyStep, emits) and binds the handler into the forge dispatch pipeline alongside actors, events, projections, and workflows.

defineHandler is the plain operation primitive without forge's domain machinery. Reach for it when:

  • You want one operation surface across HTTP + queue + MCP and your code is procedural (no actor, no event emission).
  • You're building an LLM tool, an internal admin endpoint, or a wrapper around a third-party API.

If you find yourself wanting events, actors, or projections, climb to defineAction in @nwire/forge.

MIT licensed.