first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
# events-routes
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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`.
- `maxInvitesPerRequest` enforcement inside the `/invite` handler.
- OTel counter emission on every successful mutation.
- Typed `emitEvent` callback for cross-plugin fan-out.
## Usage: Basic Example
```ts
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
```ts
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: IEventRepository`
- `rsvpRepository: IRsvpRepository`
- `maxInvitesPerRequest: number`
- `emitEvent?: (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.