first commit
This commit is contained in:
113
docs/api-stack/api/api-saga/src/events/events.md
Normal file
113
docs/api-stack/api/api-saga/src/events/events.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# `events`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The saga orchestrator publishes every step + instance transition onto
|
||||
the host's `EventBus<SagaEventMap>`. 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<SagaEventMap>();
|
||||
const emitter = new SagaEventEmitter(bus);
|
||||
|
||||
bus.on("saga.step.completed", async (payload) => {
|
||||
// payload.output is typed as Readonly<Record<string, unknown>>
|
||||
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<saga.event.name, payload>` type pinned to
|
||||
the eight documented event names.
|
||||
- `SagaEventEmitter` — wrapper around `EventBus<SagaEventMap>`:
|
||||
- `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.
|
||||
Reference in New Issue
Block a user