# `events` _(no submodules)_ ## Purpose The saga orchestrator publishes every step + instance transition onto the host's `EventBus`. Sibling plugins use those events to drive UI updates, audit, and observability. This module pins both the type-level `SagaEventMap` shape (so a misspelt event name is a compile error at every subscribe site) and the Zod schemas every emit goes through (so an internal bug in the orchestrator cannot produce a wire-corrupt payload). The `SagaEventEmitter` bundles those validations into eight typed methods — one per event — that the orchestrator calls instead of hand-writing `bus.emit(...)` calls scattered across modules. ## Features - Typed `SagaEventMap` covering every step lifecycle event (`saga.step.requested`, `.completed`, `.failed`, `.compensated`) and every instance lifecycle event (`.started`, `.completed`, `.compensated`, `.compensation_failed`). - Strict Zod schemas (`z.object(...).strict()`) for every payload — extra fields are rejected, missing fields are rejected. - `SagaEventEmitter` with one method per event so callers can't mix the wrong payload with the wrong event name. - Schemas exported so external consumers (audit sinks, web hooks, log shippers) can re-validate inbound payloads. ## Usage: Basic ```ts import { EventBus } from "@suaveplan/events"; import { SagaEventEmitter, type SagaEventMap, } from "@suaveplan/api-saga/events/events"; const bus = new EventBus(); const emitter = new SagaEventEmitter(bus); bus.on("saga.step.completed", async (payload) => { // payload.output is typed as Readonly> console.log(`step ${payload.stepName} produced`, payload.output); }); emitter.emitStepCompleted({ sagaId: "s_1", sagaName: "checkout", stepName: "charge-card", stepIndex: 0, attempt: 1, tenantId: "acme", stepState: "completed", at: "2026-05-26T00:00:00.000Z", output: { chargeId: "ch_1" }, }); ``` ## Usage: Advanced The schemas are publicly exported so a downstream component (e.g. a webhook plugin that forwards saga events outside the host) can apply the same validation to inbound payloads it receives from network: ```ts import { sagaStepFailedEventSchema, sagaCompensationFailedEventSchema, } from "@suaveplan/api-saga/events/events"; router.post("/webhook/saga", async (req) => { const json = await req.json(); if ("failedStep" in json) { const parsed = sagaCompensationFailedEventSchema.parse(json); alertOnCall(parsed.sagaId, parsed.failedStep); } else { const parsed = sagaStepFailedEventSchema.parse(json); log.warn("step failed", parsed); } }); ``` ## API Reference - `SagaEventMap` — `Record` type pinned to the eight documented event names. - `SagaEventEmitter` — wrapper around `EventBus`: - `emitStepRequested(payload)`, - `emitStepCompleted(payload)`, - `emitStepFailed(payload)`, - `emitStepCompensated(payload)`, - `emitInstanceStarted(payload)`, - `emitInstanceCompleted(payload)`, - `emitInstanceCompensated(payload)`, - `emitInstanceCompensationFailed(payload)`. - Zod schemas: `sagaStepEventSchema`, `sagaStepCompletedEventSchema`, `sagaStepFailedEventSchema`, `sagaStepCompensatedEventSchema`, `sagaInstanceEventSchema`, `sagaCompensationFailedEventSchema`. ## Implementation Notes The emitter validates *outbound* payloads before pushing them onto the bus. This is deliberate — `bus.emit` is fire-and-forget; if the emitter trusted the caller and pushed a bad payload, every listener would see corrupt data with no callback to recover. By failing at the emit site the bug surfaces inside the orchestrator path that produced the payload, not in a downstream consumer that has no visibility into which path produced what.