Files
skills/docs/api-stack/api/api-plugin-event-bus/src/middleware/request-bus.md
2026-05-29 08:20:56 +01:00

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 / malformed tenant slots.
  • 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:

  1. Extracts the tenant id via extractTenantId(c) (when tenantIsolation is true).
  2. Builds the scoped handle via bindScopedHandle.
  3. Calls c.set("eventBus", handle).
  4. 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 extractTenantId is required because the Hono context type widens c.get(name) to unknown without an augmentation — the tenant slot itself is owned by @suaveplan/api-plugin-tenant and we deliberately do not depend on that package at runtime.
  • The unsubscribe wrapper swallows TENANT_MISMATCH because 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 uses service.unsubscribe(id, tenantId) directly and translates the thrown error into a 404 response.
  • When tenantIsolation: false, every request observes tenantId: undefined on the handle and the service-level scoping becomes a no-op.