first commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# `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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user