Wires
A wire binds one handler to one way of being called. wire(binding, handler) is the join: the binding says how the logic is reached (an HTTP route, a queue topic, a cron schedule, an MCP tool); the handler is the logic itself. The two stay separate, and the wire is the single line that connects them.
import { createApp } from "@nwire/app";
import { post } from "@nwire/wires/http";
import { z } from "zod";
const app = createApp({ appName: "orders" });
app.wire(
post("/orders", { body: z.object({ sku: z.string() }) }),
async (input) => ({ ordered: input.sku }),
);Bindings come from @nwire/wires
A binding is a small typed descriptor. Each transport contributes its own factory, imported from a subpath so you only pull in what you use:
| Import | Factories | Binds to |
|---|---|---|
@nwire/wires/http | get post put patch del | an HTTP route |
@nwire/wires/queue | queue | a queue topic / worker |
@nwire/wires/cron | cron | a schedule |
@nwire/wires/mcp | tool | an MCP tool |
The HTTP factories take a path and optional Zod schemas for params, query, and body; the schema is what validates input before the handler runs and what feeds the generated OpenAPI spec. A binding carries no logic — it's just the address.
The same handler, more than one wire
Because a binding is separate from the handler, wiring the same operation to a second transport is another line, never a rewrite:
app.wire(post("/orders/:id/charge", { params: z.object({ id: z.string() }) }), charge);
app.wire(queue("orders.charge", { input: z.object({ id: z.string() }) }), charge);POST /orders/o-1/charge and an enqueue to orders.charge now run the identical charge handler. This is the property nothing else in the stack gives you for free — see the handler for why it holds.
The verbs that wire
In flux. These verbs are split across two surfaces — the app's wire collection and the transport interface builder — and which verb lives where is being settled alongside the discovery work. The verbs below are real; the surface they attach to may shift. Revisit once discovery lands.
Wiring lives on the app and on the endpoint's interface. The set is small and fixed — there's no transport-specific vocabulary to learn:
wire(binding, handler)— bind a handler to a transport binding.from(source)— tag a wire's source label, so routes can be grouped by the feature they belong to (the trace viewer uses this).provide(builder)— accumulate per-request context available to the handler'sctx.merge(other)— append another interface's wires (how composed apps pool their routes).
Reacting to events is deliberately not in this list — that's when in a listener file, not a wire. A wire is a way to be called; a listener is a way to react. Keeping them distinct is what keeps the surface honest.
See also
- The handler — what sits on the other end of a wire.
- The endpoint — which transports are mounted to serve these wires.
- Route bindings reference — every HTTP factory and its schema options.