first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user