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

197 lines
11 KiB
Markdown

# @suaveplan/api-plugin-event-bus - Typed event-bus plugin for @suaveplan/api. Wraps @suaveplan/events (pub/sub, async dispatch, interceptors, bounded-queue backpressure, idempotent listener wrapper) as a single definePlugin descriptor. Registers a typed eventBusService in the host ServiceRegistry, installs request middleware that exposes a tenant-scoped publish/subscribe handle on c.get("eventBus"), and (optionally) mounts 3 auth-gated OpenAPI routes for publish/list/delete. Per-tenant topic scoping isolates publishes across tenants by prefixing topics with tenant:<id>:. Server-tier — composes cleanly with @suaveplan/api-plugin-auth + @suaveplan/api-plugin-tenant.
Typed event-bus plugin for `@suaveplan/api`. Wraps `@suaveplan/events`
(pub/sub, async dispatch, interceptors, bounded-queue backpressure,
idempotent listener wrapper) as a single `definePlugin` consumer so
apps composing `@suaveplan/api` get a working, instrumented,
per-tenant scoped event bus by adding one entry to their plugins
array.
Naming note: `enterprise/api-plugin-events.md` documents a *different*
package (social calendar / RSVP / meetup feed) that is deferred to a
later wave. This `event-bus` plugin is the in-process event-bus
integration wrapper.
<!-- begin:auto-docs -->
## Documentation tree
- [error-codes](./src/error-codes/error-codes.md) — Declares the stable error-code registry consumed by every failure path in `@suaveplan/api-plugin-event-bus`. The regist…
- [error-codes](./src/error-codes/error-codes.md) — Declares the stable error-code registry consumed by every failure path in `@suaveplan/api-plugin-event-bus`. The regist…
- [context-augmentation](./src/middleware/context-augmentation.md) — Adds a typed `eventBus` slot to Hono's `ContextVariableMap` so request handlers see `c.get("eventBus")` resolve to `Sco…
- [request-bus](./src/middleware/request-bus.md) — Provides the Hono middleware that installs a `ScopedEventBusHandle` on every request via `c.set("eventBus", handle)`. T…
- [otel](./src/otel/instruments.md) — Exposes the five OpenTelemetry instruments emitted from the plugin's production paths and a lazy singleton (`getEventBu…
- [instruments](./src/otel/instruments.md) — Exposes the five OpenTelemetry instruments emitted from the plugin's production paths and a lazy singleton (`getEventBu…
- [plugin](./src/plugin/plugin.md) — Top-level factory exported as the package's main entry point. Builds a typed `PluginDescriptor<EventBusPluginConfig>` p…
- [plugin](./src/plugin/plugin.md) — Top-level factory exported as the package's main entry point. Builds a typed `PluginDescriptor<EventBusPluginConfig>` p…
- [routes](./src/routes/build-routes.md) — Builds the three `PluginRouteEntry` objects the plugin mounts on the host Hono app + `OpenAPIRegistry` via `definePlugi…
- [build-routes](./src/routes/build-routes.md) — Builds the three `PluginRouteEntry` objects the plugin mounts on the host Hono app + `OpenAPIRegistry` via `definePlugi…
- [options](./src/schemas/options.md) — Boundary Zod schema validated by `createEventBusPlugin(options)` at the factory entry point. Invalid input produces an…
- [routes](./src/schemas/routes.md) — Boundary Zod schemas validated by the three OpenAPI routes the plugin optionally mounts: `POST /event-bus/publish`, `GE…
- [backpressure](./src/services/backpressure.md) — Wraps `@suaveplan/events`' `BoundedQueue` with a small adapter that treats the queue as a pure accounting primitive: a…
- [event-bus-service](./src/services/event-bus-service.md) — Builds the canonical `EventBusService` registered in the host `ServiceRegistry` by `createEventBusPlugin`. The service…
- [helpers](./src/services/helpers.md) — Holds the pure helpers extracted from the main service body so the service itself stays under the 300-line cap and each…
- [idempotency-cache](./src/services/idempotency-cache.md) — Provides the default `IdempotencyCache` implementation used by `eventBusService` to suppress duplicate publishes. The c…
- [registry-augmentation](./src/services/registry-augmentation.md) — Augments `@suaveplan/api-plugin/service-registry`'s `ServiceRegistry` interface to advertise the `eventBusService` slot…
- [topic](./src/topic/topic.md) — Pure functions for validating event-bus topics and projecting them into their tenant-scoped form. Every `publish` and `…
- [topic](./src/topic/topic.md) — Pure functions for validating event-bus topics and projecting them into their tenant-scoped form. Every `publish` and `…
- [types](./src/types/types.md) — Declares the two top-level shapes consumed by external callers of the plugin: `EventBusPluginOptions` (what `createEven…
- [types](./src/types/types.md) — Declares the two top-level shapes consumed by external callers of the plugin: `EventBusPluginOptions` (what `createEven…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/api-plugin-event-bus
```
## Quick start
```ts
import { defineApp } from "@suaveplan/api";
import { createEventBusPlugin } from "@suaveplan/api-plugin-event-bus";
const app = await defineApp()
.name("my-api")
.use(createEventBusPlugin({}))
.start();
```
`createEventBusPlugin` returns a typed `PluginDescriptor`. The host's
`PluginRegistry` calls `onInit(ctx)` which:
1. Resolves `app` and (optionally) `openAPIRegistry` from the host
`ServiceRegistry`.
2. Constructs an `EventBus` from `@suaveplan/events` and applies
configured interceptors, bounded-queue backpressure, and idempotent
listener wiring.
3. Registers `eventBusService` in the typed `ServiceRegistry`.
4. Auto-prefixes topics with `tenant:<id>:` when a tenant slot is
present on the request (provided by `@suaveplan/api-plugin-tenant`).
5. Installs request middleware so every handler sees a
`ScopedEventBusHandle` under `c.get("eventBus")`.
6. Optionally mounts 3 OpenAPI routes (`POST /event-bus/publish`,
`GET /event-bus/subscriptions`, `DELETE /event-bus/subscriptions/:id`),
auth-gated by `requireAuth()` when `requireAuthForRoutes: true`
(the default).
## In-handler usage
```ts
app.post("/checkout", async (c) => {
const bus = c.get("eventBus");
const result = await bus.publish("order.created", {
id: "o1",
total: 9.99,
});
return c.json({ delivered: result.delivered });
});
app.get("/me/subs", (c) => {
const bus = c.get("eventBus");
return c.json({ subscriptions: bus.listSubscriptions() });
});
```
The handle is automatically scoped to the request's tenant — a
publish on tenant A's request never reaches a subscriber registered
through tenant B's handle.
## Per-tenant isolation
When `tenantIsolation: true` (the default) the service prefixes every
publish and subscribe topic with `tenant:<id>:` extracted from
`c.get("tenant")?.id`. The HTTP routes follow the same rule and
unscope topics on the way out so wire responses never leak the
internal prefix. Cross-tenant `unsubscribe(id)` attempts return HTTP
404 to avoid leaking subscription-id ownership.
Set `tenantIsolation: false` for single-tenant deployments — every
publish flows on the bare topic.
## Backpressure
Every publish flows through a `BoundedQueue` from `@suaveplan/events`
configured by `backpressure: { capacity, policy }`. The four
`QueuePolicy` values (`drop-oldest`, `drop-newest`, `block`, `throw`)
are forwarded verbatim. Under `drop-oldest`/`drop-newest`, the
`PublishResult.dropped` field reports overflow events alongside an
`event_bus.dropped_total{policy}` counter increment. Under
`policy: "throw"`, overflow rejects the publish promise with
`EventBusError(EVENT_BUS.BACKPRESSURE_DROPPED)`.
## Idempotency
A subscription registered with `idempotencyKey: (payload) => string`
fires its handler at most once per derived key. The plugin-level
`idempotency.enabled: true` flag activates the same wrapper for every
subscriber that does not supply its own key function — the default
derivation is `JSON.stringify(payload)`. Duplicates increment
`event_bus.duplicates_total`.
## OTel metrics
| Metric | Type | Labels |
|---|---|---|
| `event_bus.published_total` | Counter | `outcome`, `topic_root` |
| `event_bus.dropped_total` | Counter | `policy` |
| `event_bus.duplicates_total` | Counter | — |
| `event_bus.subscriptions_active` | UpDownCounter | — |
| `event_bus.publish_duration_ms` | Histogram | — |
## Error codes
`EVENT_BUS_CODES` exports 8 stable codes: `INVALID_INPUT`,
`INVALID_TOPIC`, `SERVICE_NOT_AVAILABLE`, `ROUTE_PREFIX_COLLISION`,
`SUBSCRIPTION_NOT_FOUND`, `BACKPRESSURE_DROPPED`, `TENANT_MISMATCH`,
`PUBLISH_FAILED`. See `src/error-codes/error-codes.md` for the full
table and `context.statusCode` mapping.
## Modules
- [`src/error-codes/error-codes.md`](./src/error-codes/error-codes.md)
— `EVENT_BUS_CODES`, `EventBusError`, `EventBusErrorCode`.
- [`src/middleware/context-augmentation.md`](./src/middleware/context-augmentation.md)
— Hono `ContextVariableMap.eventBus` declaration.
- [`src/middleware/request-bus.md`](./src/middleware/request-bus.md)
— `requestBusMiddleware`, `bindScopedHandle`, `extractTenantId`.
- [`src/otel/instruments.md`](./src/otel/instruments.md) — 5-metric
bundle + lazy singleton + reset helper.
- [`src/plugin/plugin.md`](./src/plugin/plugin.md) — `createEventBusPlugin`
factory + 6-step `onInit` lifecycle.
- [`src/routes/build-routes.md`](./src/routes/build-routes.md) — 3
OpenAPI route entries (publish / list / delete).
- [`src/schemas/options.md`](./src/schemas/options.md) — boundary
Zod schema for `EventBusPluginOptions`.
- [`src/schemas/routes.md`](./src/schemas/routes.md) — Zod schemas
for the HTTP route surface.
- [`src/services/backpressure.md`](./src/services/backpressure.md) —
bounded-queue accounting primitive.
- [`src/services/event-bus-service.md`](./src/services/event-bus-service.md)
— `EventBusService` factory + per-tenant scoping + idempotency.
- [`src/services/helpers.md`](./src/services/helpers.md) — id
generation, error projection, OTel emission helpers.
- [`src/services/idempotency-cache.md`](./src/services/idempotency-cache.md)
— bounded set-based `IdempotencyCache` implementation.
- [`src/services/registry-augmentation.md`](./src/services/registry-augmentation.md)
— `ServiceRegistry` slot declaration for `eventBusService`.
- [`src/topic/topic.md`](./src/topic/topic.md) — `EVENT_TOPIC_PATTERN`
- `scopeTopic` / `unscopeTopic` / `assertValidTopic`.
- [`src/types/types.md`](./src/types/types.md) — public option /
config interfaces.
## Subpath exports
`.`, `./plugin/plugin`, `./middleware/request-bus`,
`./middleware/context-augmentation`, `./services/event-bus-service`,
`./services/registry-augmentation`, `./schemas/options`,
`./schemas/routes`, `./routes/build-routes`, `./topic/topic`,
`./error-codes/error-codes`, `./otel/instruments`, `./types/types`.
## Peer deps
- `hono`, `@hono/zod-openapi`, `zod` — supplied by the host
application.
- `@suaveplan/api-plugin-auth` — used by `requireAuth()` route gate
when `requireAuthForRoutes: true` (default).