# Change: events-generics-and-interceptor-replace ## Why `@suaveplan/events` shipped at 0.0.0 with 100% coverage but the implementation drifts from the spec in four places that matter for downstream `events-schema`, `events-otel`, `events-metrics`, and `events-transport`: 1. `Interceptor` is non-generic — every interceptor sees `EventEnvelope` even when the bus is `EventBus`. Forces unsafe casts in every adapter and undermines the typed-bus value proposition. 2. Spec scenario "Interceptor can replace payload" is unimplemented — `Next` takes no args, so an interceptor cannot pass a replacement envelope down the chain. `compose` already plumbs `env` through `run(index, env)`, only the public type prevents it. 3. Sync `emit()` silently drops rejections from listeners that return Promises — they become unhandled rejections instead of landing in `EmitResult.errors`. 4. `Listener` is duplicated in `idempotent.ts` instead of importing from the bus types — a drift trap. These are correctness fixes, not new features. Public-surface-compatible: existing call sites keep working because the new generic parameter has a default and the new `Next` envelope arg is optional. ## What Changes ### `Interceptor` becomes `Interceptor` ```ts type Interceptor = ( envelope: EventEnvelope, next: Next ) => Promise; ``` `EventBus.use(interceptor: Interceptor)` narrows to the bus's map. Existing untyped interceptors keep working through the default. ### `Next` accepts an optional replacement envelope ```ts type Next = (envelope?: EventEnvelope) => Promise; ``` Calling `next()` advances with the current envelope. Calling `next(replacement)` advances with the replacement; downstream listeners observe `replacement.payload`. ### Sync `emit` captures async rejections When a listener returns a thenable, `emit` attaches a `.catch()` that wraps the rejection as `EventsError("EVENTS.LISTENER.THREW")` and pushes it onto a deferred-error channel exposed via `EmitResult.deferredErrors: Promise`. Synchronous throws still land in `errors[]` as before. This is additive — `errors` semantics unchanged. ### Sync `emit` interceptor behavior — documented bypass Sync `emit` does NOT run async interceptors. Document this explicitly in `event-bus.md` and surface a runtime warning hook (`onSyncEmitWithInterceptors?: (count) => void`) on `EventBusOptions` so adapters can detect mixed usage. Consumers wanting interceptor coverage use `emitAsync`. ### `idempotent` reuses `Listener` from bus types Drop the local `Listener` declaration; import from `../bus/event-bus.types.js`. ## Impact - `@suaveplan/events`: minor (semver-additive within 0.0.x) - Unblocks proper typing in `events-schema`, `events-otel`, `events-metrics`, `events-transport` - No external consumers yet — published only to Verdaccio at 0.0.0