3.9 KiB
events-routes
(no submodules)
Purpose
Builds the nine-route OpenAPI Hono module mounted by
createEventsPlugin. Each route is wrapped by @hono/zod-openapi's
createRoute so the package's local Zod schemas drive both
input-validation and generated OpenAPI doc shape. The route layer is
the single point at which auth (c.get("user")), authorisation
(eventRepository.isCreator), maxInvitesPerRequest enforcement,
OTel emission, and EventBus fan-out converge.
The handlers themselves are deliberately thin: every meaningful
side-effect (CREATE row, UPDATE counter, DELETE row, EMIT event)
funnels through the two repositories and the optional emitEvent
callback. The callback is the integration point the plugin's
onReady uses to forward route events onto the shared
ctx.eventBus — that indirection is what fixes the upstream
"local emitter" smell flagged in enterprise/api-plugin-events.md.
Features
- 9 routes: list / get / create / update / delete / rsvp / cancel-rsvp / my-rsvp / attendees / invite.
- Zod validation on every body / param / query.
- Auth guard on every route via
c.get("user"). - Authorisation guard on every mutating route via
eventRepository.isCreator. maxInvitesPerRequestenforcement inside the/invitehandler.- OTel counter emission on every successful mutation.
- Typed
emitEventcallback for cross-plugin fan-out.
Usage: Basic Example
import { createEventRoutes } from "@suaveplan/api-plugin-events";
import { OpenAPIHono } from "@hono/zod-openapi";
const routes = createEventRoutes({
eventRepository,
rsvpRepository,
maxInvitesPerRequest: 50,
});
const parent = new OpenAPIHono();
parent.use(async (c, next) => {
c.set("user", await resolveUser(c));
await next();
});
parent.route("/events", routes);
Usage: Advanced Example
import { createEventRoutes } from "@suaveplan/api-plugin-events";
const routes = createEventRoutes({
eventRepository,
rsvpRepository,
maxInvitesPerRequest: 25,
emitEvent: (event) => {
// Forward onto the host's typed bus
host.eventBus.emit(event.type, event.payload);
// Side-channel: persistent audit log
auditLog.write({
kind: event.type,
payload: event.payload,
at: new Date(),
});
},
});
// `routes` is an OpenAPIHono instance — feed it into the OpenAPI
// document generator:
host.openAPIRegistry.attach(routes);
API Reference
createEventRoutes(config: EventRoutesConfig): OpenAPIHono
Returns an OpenAPIHono<{ Variables: EventsRoutesVariables }> with
the nine routes registered.
interface EventRoutesConfig
eventRepository: IEventRepositoryrsvpRepository: IRsvpRepositorymaxInvitesPerRequest: numberemitEvent?: (event: EventsRouteEvent) => void
interface EventsRoutesVariables
{ user: { id: string } & Record<string, unknown> | null | undefined }
— the route handler reads c.get("user") from this slot; the host
supplies the auth middleware that populates it.
Implementation Notes
The handlers manually try { getUser(c) } catch { return c.json(...) }
rather than relying on Hono's onError because the OpenAPI route
spec requires every documented response status to be reachable from
the handler body. Catching EventsPluginError(UNAUTHENTICATED) and
returning a 401 inline keeps the spec honest. The pattern repeats
on every route — verbose, but each block is a single
3-line try/catch/json triple.
The app.route("/", routes) line in test fixtures and in the plugin
mount mirrors the parent/child Hono pattern: the parent app owns the
auth middleware, the child app owns the routes. Hono middleware
registered on the parent BEFORE the .route() call fires before the
child's handlers, so c.get("user") resolves correctly during the
child's c.req.valid(...) parsing phase.