@nwire/hooks — alone
The universal dispatch primitive. Two compositions — listeners (parallel, fire-and-forget) and chains (series, can short-circuit) — behind one tense-driven API.
What it does
bus.on("EventName", handler)registers a listener.bus.dispatch("EventName", payload, { mode })invokes them.- Four
modechoices map to the four common dispatch shapes:
mode | Semantics | Common use |
|---|---|---|
"parallel" | Run all listeners concurrently; ignore return values. | "X has happened" — past-tense events. |
"series" | Run listeners in order; ignore return values. | Ordered side effects (audit log → metric → notify). |
"series-bail" | Run in order; first thrown error stops the chain. | "Before X" — guard-style validations. |
"chain" | Pass the payload through each listener; return the final value. | Middleware-style transforms. |
Install
bash
pnpm add @nwire/hooksUse it for your own events
ts
import { createBus } from "@nwire/hooks"
const bus = createBus<{
"UserSignedUp": { id: string; email: string }
"UserAboutToDelete": { id: string }
}>()
// Parallel side effects
bus.on("UserSignedUp", async (u) => sendWelcomeEmail(u))
bus.on("UserSignedUp", async (u) => track("signup", { id: u.id }))
await bus.dispatch("UserSignedUp", { id: "u1", email: "a@b.c" }, { mode: "parallel" })
// Series-bail guard
bus.on("UserAboutToDelete", async (u) => {
if (await hasActiveSubscription(u.id)) throw new Error("Has active subscription")
})
await bus.dispatch("UserAboutToDelete", { id: "u1" }, { mode: "series-bail" })
// throws if the guard failsUse it for middleware chains
ts
const pipeline = createBus<{ "Request": { url: string; headers: Record<string, string> } }>()
pipeline.on("Request", (req) => ({ ...req, headers: { ...req.headers, "x-trace": uuid() } }))
pipeline.on("Request", (req) => ({ ...req, url: req.url.toLowerCase() }))
const final = await pipeline.dispatch("Request", { url: "/HELLO", headers: {} }, { mode: "chain" })
// → { url: "/hello", headers: { "x-trace": "..." } }Why this exists
Every framework reinvents the listener half (emittery, EventEmitter, mitt) and the chain half (koa-compose, express middleware, hapi extensions) separately. @nwire/hooks is the smallest surface that covers both, with typed payloads, async by default, and tense-driven dispatch modes that match the way you'd describe the event in English.
What's underneath
- The listener half wraps emittery.
- The chain half is ~30 LOC of
koa-compose-shaped composition.
No reflect-metadata, no decorators, no global state.