3.6 KiB
request-bus.ts — request-scoped event-bus handle middleware
Purpose
Provides the Hono middleware that installs a ScopedEventBusHandle
on every request via c.set("eventBus", handle). The handle is bound
to the request's tenant id (read from c.get("tenant")?.id) so
handler code can publish/subscribe without threading the tenant id
through every call. Pulling the handle construction into a free
function (bindScopedHandle) lets the HTTP routes reuse the same
binding logic for their own per-request execution.
Features
requestBusMiddleware({ service, tenantIsolation })— Hono middleware factory.bindScopedHandle(service, tenantId)— pure binding helper used by both the middleware AND the HTTP routes.extractTenantId(c)— defensive tenant-id reader that tolerates missing / malformedtenantslots.- Tenant binding is opt-out via
tenantIsolation: false.
Usage: Basic
import { requestBusMiddleware } from "@suaveplan/api-plugin-event-bus/middleware/request-bus";
app.use(
"*",
requestBusMiddleware({ service: eventBusService, tenantIsolation: true })
);
app.get("/me/events", async (c) => {
const bus = c.get("eventBus");
const list = bus.listSubscriptions();
return c.json({ list });
});
Usage: Advanced
import {
bindScopedHandle,
requestBusMiddleware,
} from "@suaveplan/api-plugin-event-bus/middleware/request-bus";
// Inside a sibling plugin's route handler that runs OUTSIDE the
// standard middleware chain (e.g. a SSE handler) — bind directly.
const handle = bindScopedHandle(eventBusService, "acme");
await handle.publish("order.created", { id: "o1" });
// Standard wiring — the middleware installs the handle on every
// request and auto-extracts the tenant from c.get("tenant").
app.use("*", requestBusMiddleware({ service, tenantIsolation: true }));
API Reference
requestBusMiddleware(options): MiddlewareHandler
Returns a Hono middleware that:
- Extracts the tenant id via
extractTenantId(c)(whentenantIsolationistrue). - Builds the scoped handle via
bindScopedHandle. - Calls
c.set("eventBus", handle). - Awaits
next().
bindScopedHandle(service, tenantId): ScopedEventBusHandle
Returns a handle whose publish / subscribe / unsubscribe /
listSubscriptions calls auto-inject the bound tenantId. The
unsubscribe handler swallows EventBusError(TENANT_MISMATCH) and
returns false so handler code does not have to special-case the
cross-tenant detach attempt.
extractTenantId(c): string | undefined
Reads c.get("tenant")?.id defensively. Returns undefined when the
slot is missing, the tenant has no id, or the id is empty.
interface ScopedEventBusHandle
Same surface as EventBusService minus the explicit tenantId
parameter. See services/event-bus-service.md.
Implementation Notes
- The cast in
extractTenantIdis required because the Hono context type widensc.get(name)tounknownwithout an augmentation — thetenantslot itself is owned by@suaveplan/api-plugin-tenantand we deliberately do not depend on that package at runtime. - The
unsubscribewrapper swallowsTENANT_MISMATCHbecause the HTTP layer needs to distinguish "not found" from "owned by another tenant" only at the route handler — the middleware-level handle treats both as "false". The HTTP route surface usesservice.unsubscribe(id, tenantId)directly and translates the thrown error into a 404 response. - When
tenantIsolation: false, every request observestenantId: undefinedon the handle and the service-level scoping becomes a no-op.