Files
skills/plugins/document-navigator/docs/networking/events/events/README.md
admin 9bf56a806b Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 01:10:00 +01:00

11 KiB

@suaveplan/events - Typed event-bus substrate: pub/sub with deterministic FIFO, interceptor API, bounded-queue backpressure, idempotent listeners, Zod-validated envelopes, passive observability adapter.

Summary

Typed event-bus substrate for Suaveplan. Pub/sub with deterministic FIFO delivery, interceptor API, bounded-queue backpressure, idempotent listeners, Zod-validated envelopes, and a passive observability adapter.

Documentation tree

- [event-bus](./src/bus/event-bus.md) — _(no submodules)_- [clock](./src/clock/default-clock.md) — _(no submodules)_- [default-clock](./src/clock/default-clock.md) — _(no submodules)_- [envelope](./src/envelope/envelope.md) — _(no submodules)_- [envelope](./src/envelope/envelope.md) — _(no submodules)_- [codes](./src/errors/codes.md) — `EVENTS_CODES` is the frozen registry of every failure code `@suaveplan/events` can raise. All codes share the `EVENTS.…

Install

bun add @suaveplan/events

Why

Downstream packages (events-metrics, events-otel, events-schema, events-transport) attach through bus.use(interceptor) or withObservability(bus, adapter) — the core never couples to any of them.

Usage

import { EventBus } from "@suaveplan/events";

type Map = {
    "user.signup": { id: string; email: string };
    "order.paid": { orderId: string; amount: number };
};

const bus = new EventBus<Map>();
bus.on("user.signup", ({ email }) => sendWelcome(email));
bus.emit("user.signup", { id: "u1", email: "alice@example.com" });

Interceptor

bus.use(async (envelope, next) => {
    const start = performance.now();
    const result = await next();
    histogram.record(performance.now() - start, { type: envelope.type });
    return result;
});

Backpressure

import { BoundedQueue } from "@suaveplan/events";

const q = new BoundedQueue<Order>({ capacity: 1000, policy: "block" });
await q.enqueue(order, signal);

Idempotency

import { idempotent } from "@suaveplan/events";

bus.on(
    "order.paid",
    idempotent(handlePayment, { keyOf: (o) => o.orderId })
);

Error codes

EVENTS.LISTENER.THREW
EVENTS.INTERCEPTOR.THREW
EVENTS.QUEUE.OVERFLOW
EVENTS.QUEUE.ABORTED
EVENTS.QUEUE.INVALID_CAPACITY
EVENTS.EMIT.TIMED_OUT
EVENTS.EMIT.ABORTED

All surface as EventsError extends SuaveplanError<EventsCode> from @suaveplan/error.

Foundation rules honoured

  • Runtime deps: @suaveplan/error, tslib, zod (all catalog-pinned)
  • Zero Node / browser built-ins in src/ (guarded)
  • Date.now / crypto.randomUUID live only in clock/default-clock.ts and id/id-generator.ts (guarded)
  • sideEffects: false, per-module subpath exports
  • 100% statement / branch / function / line coverage

Testing

bunx turbo lint typecheck test --filter=@suaveplan/events
bunx turbo test:coverage --filter=@suaveplan/events