Initial Commit
This commit is contained in:
113
packages/api-stack/api/api-saga/src/events/events.md
Normal file
113
packages/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.
|
||||
248
packages/api-stack/api/api-saga/src/events/events.test.ts
Normal file
248
packages/api-stack/api/api-saga/src/events/events.test.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import { EventBus } from "@suaveplan/events";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
SagaEventEmitter,
|
||||
sagaCompensationFailedEventSchema,
|
||||
sagaInstanceEventSchema,
|
||||
sagaStepCompensatedEventSchema,
|
||||
sagaStepCompletedEventSchema,
|
||||
sagaStepEventSchema,
|
||||
sagaStepFailedEventSchema,
|
||||
} from "./events.js";
|
||||
import type {
|
||||
SagaCompensationFailedEvent,
|
||||
SagaEventMap,
|
||||
SagaInstanceEvent,
|
||||
SagaStepCompensatedEvent,
|
||||
SagaStepCompletedEvent,
|
||||
SagaStepEvent,
|
||||
SagaStepFailedEvent,
|
||||
} from "./events.types.js";
|
||||
|
||||
function baseStep(): SagaStepEvent {
|
||||
return {
|
||||
sagaId: "s_1",
|
||||
sagaName: "checkout",
|
||||
stepName: "charge",
|
||||
stepIndex: 0,
|
||||
attempt: 1,
|
||||
tenantId: "acme",
|
||||
stepState: "in_flight",
|
||||
at: "2026-05-26T00:00:00.000Z",
|
||||
};
|
||||
}
|
||||
|
||||
function baseInstance(): SagaInstanceEvent {
|
||||
return {
|
||||
sagaId: "s_1",
|
||||
sagaName: "checkout",
|
||||
tenantId: "acme",
|
||||
state: "running",
|
||||
at: "2026-05-26T00:00:00.000Z",
|
||||
};
|
||||
}
|
||||
|
||||
describe("events — schemas", () => {
|
||||
it("step event schema accepts a valid payload", { timeout: 5000 }, () => {
|
||||
const parsed = sagaStepEventSchema.parse(baseStep());
|
||||
expect(parsed.stepName).toBe("charge");
|
||||
});
|
||||
|
||||
it("step event schema rejects extra fields (strict)", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const result = sagaStepEventSchema.safeParse({
|
||||
...baseStep(),
|
||||
extra: 1,
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("step-completed schema requires output", { timeout: 5000 }, () => {
|
||||
const payload: SagaStepCompletedEvent = {
|
||||
...baseStep(),
|
||||
stepState: "completed",
|
||||
output: { id: "x" },
|
||||
};
|
||||
expect(sagaStepCompletedEventSchema.parse(payload).output).toEqual({
|
||||
id: "x",
|
||||
});
|
||||
const bad = sagaStepCompletedEventSchema.safeParse(baseStep());
|
||||
expect(bad.success).toBe(false);
|
||||
});
|
||||
|
||||
it("step-failed schema requires code + message", { timeout: 5000 }, () => {
|
||||
const payload: SagaStepFailedEvent = {
|
||||
...baseStep(),
|
||||
stepState: "failed",
|
||||
code: "SAGA.STEP_TIMEOUT",
|
||||
message: "boom",
|
||||
};
|
||||
const parsed = sagaStepFailedEventSchema.parse(payload);
|
||||
expect(parsed.code).toBe("SAGA.STEP_TIMEOUT");
|
||||
});
|
||||
|
||||
it("step-compensated schema requires compensationAttempts ≥ 1", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const payload: SagaStepCompensatedEvent = {
|
||||
...baseStep(),
|
||||
stepState: "compensated",
|
||||
compensationAttempts: 1,
|
||||
};
|
||||
expect(
|
||||
sagaStepCompensatedEventSchema.parse(payload).compensationAttempts
|
||||
).toBe(1);
|
||||
expect(
|
||||
sagaStepCompensatedEventSchema.safeParse({
|
||||
...payload,
|
||||
compensationAttempts: 0,
|
||||
}).success
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("instance schema accepts a valid payload", { timeout: 5000 }, () => {
|
||||
const parsed = sagaInstanceEventSchema.parse(baseInstance());
|
||||
expect(parsed.state).toBe("running");
|
||||
});
|
||||
|
||||
it("compensation-failed schema requires failedStep + attempts", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const payload: SagaCompensationFailedEvent = {
|
||||
...baseInstance(),
|
||||
state: "compensation_failed",
|
||||
failedStep: "release",
|
||||
compensationAttempts: 5,
|
||||
};
|
||||
expect(
|
||||
sagaCompensationFailedEventSchema.parse(payload).failedStep
|
||||
).toBe("release");
|
||||
expect(
|
||||
sagaCompensationFailedEventSchema.safeParse(baseInstance()).success
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("events — emitter", () => {
|
||||
function setup(): {
|
||||
bus: EventBus<SagaEventMap>;
|
||||
emitter: SagaEventEmitter;
|
||||
} {
|
||||
const bus = new EventBus<SagaEventMap>();
|
||||
return { bus, emitter: new SagaEventEmitter(bus) };
|
||||
}
|
||||
|
||||
it("emitStepRequested pushes onto the bus", { timeout: 5000 }, () => {
|
||||
const { bus, emitter } = setup();
|
||||
const seen: SagaStepEvent[] = [];
|
||||
bus.on("saga.step.requested", (p) => {
|
||||
seen.push(p);
|
||||
});
|
||||
emitter.emitStepRequested(baseStep());
|
||||
expect(seen).toHaveLength(1);
|
||||
expect(seen[0]?.stepName).toBe("charge");
|
||||
});
|
||||
|
||||
it("emitStepCompleted requires output", { timeout: 5000 }, () => {
|
||||
const { bus, emitter } = setup();
|
||||
const seen: SagaStepCompletedEvent[] = [];
|
||||
bus.on("saga.step.completed", (p) => {
|
||||
seen.push(p);
|
||||
});
|
||||
emitter.emitStepCompleted({
|
||||
...baseStep(),
|
||||
stepState: "completed",
|
||||
output: { ok: true },
|
||||
});
|
||||
expect(seen[0]?.output).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it("emitStepFailed validates code + message", { timeout: 5000 }, () => {
|
||||
const { bus, emitter } = setup();
|
||||
const seen: SagaStepFailedEvent[] = [];
|
||||
bus.on("saga.step.failed", (p) => {
|
||||
seen.push(p);
|
||||
});
|
||||
emitter.emitStepFailed({
|
||||
...baseStep(),
|
||||
stepState: "failed",
|
||||
code: "SAGA.STEP_TIMEOUT",
|
||||
message: "x",
|
||||
});
|
||||
expect(seen[0]?.code).toBe("SAGA.STEP_TIMEOUT");
|
||||
});
|
||||
|
||||
it("emitStepCompensated pushes onto the bus", { timeout: 5000 }, () => {
|
||||
const { bus, emitter } = setup();
|
||||
const seen: SagaStepCompensatedEvent[] = [];
|
||||
bus.on("saga.step.compensated", (p) => {
|
||||
seen.push(p);
|
||||
});
|
||||
emitter.emitStepCompensated({
|
||||
...baseStep(),
|
||||
stepState: "compensated",
|
||||
compensationAttempts: 2,
|
||||
});
|
||||
expect(seen[0]?.compensationAttempts).toBe(2);
|
||||
});
|
||||
|
||||
it("emitInstanceStarted/Completed/Compensated emit the right channel", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const { bus, emitter } = setup();
|
||||
const started: SagaInstanceEvent[] = [];
|
||||
const completed: SagaInstanceEvent[] = [];
|
||||
const compensated: SagaInstanceEvent[] = [];
|
||||
bus.on("saga.instance.started", (p) => {
|
||||
started.push(p);
|
||||
});
|
||||
bus.on("saga.instance.completed", (p) => {
|
||||
completed.push(p);
|
||||
});
|
||||
bus.on("saga.instance.compensated", (p) => {
|
||||
compensated.push(p);
|
||||
});
|
||||
emitter.emitInstanceStarted(baseInstance());
|
||||
emitter.emitInstanceCompleted({
|
||||
...baseInstance(),
|
||||
state: "completed",
|
||||
});
|
||||
emitter.emitInstanceCompensated({
|
||||
...baseInstance(),
|
||||
state: "compensated",
|
||||
});
|
||||
expect(started).toHaveLength(1);
|
||||
expect(completed).toHaveLength(1);
|
||||
expect(compensated).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("emitInstanceCompensationFailed pushes onto the bus", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const { bus, emitter } = setup();
|
||||
const seen: SagaCompensationFailedEvent[] = [];
|
||||
bus.on("saga.instance.compensation_failed", (p) => {
|
||||
seen.push(p);
|
||||
});
|
||||
emitter.emitInstanceCompensationFailed({
|
||||
...baseInstance(),
|
||||
state: "compensation_failed",
|
||||
failedStep: "release",
|
||||
compensationAttempts: 5,
|
||||
});
|
||||
expect(seen[0]?.failedStep).toBe("release");
|
||||
});
|
||||
|
||||
it("emit rejects a malformed payload before pushing", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const { emitter } = setup();
|
||||
expect(() =>
|
||||
emitter.emitStepRequested({
|
||||
...baseStep(),
|
||||
stepIndex: -1,
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
149
packages/api-stack/api/api-saga/src/events/events.ts
Normal file
149
packages/api-stack/api/api-saga/src/events/events.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import type { EventBus } from "@suaveplan/events";
|
||||
import z from "zod";
|
||||
import type {
|
||||
SagaCompensationFailedEvent,
|
||||
SagaEventMap,
|
||||
SagaInstanceEvent,
|
||||
SagaStepCompensatedEvent,
|
||||
SagaStepCompletedEvent,
|
||||
SagaStepEvent,
|
||||
SagaStepFailedEvent,
|
||||
} from "./events.types.js";
|
||||
|
||||
const stepStateSchema = z.enum([
|
||||
"pending",
|
||||
"in_flight",
|
||||
"completed",
|
||||
"failed",
|
||||
"compensating",
|
||||
"compensated",
|
||||
]);
|
||||
|
||||
const instanceStateSchema = z.enum([
|
||||
"running",
|
||||
"completed",
|
||||
"compensating",
|
||||
"compensated",
|
||||
"compensation_failed",
|
||||
"cancelled",
|
||||
]);
|
||||
|
||||
const baseStepShape = {
|
||||
sagaId: z.string().min(1),
|
||||
sagaName: z.string().min(1),
|
||||
stepName: z.string().min(1),
|
||||
stepIndex: z.number().int().min(0),
|
||||
attempt: z.number().int().min(1),
|
||||
tenantId: z.string().min(1),
|
||||
stepState: stepStateSchema,
|
||||
at: z.string().min(1),
|
||||
};
|
||||
|
||||
/** Zod schema for `saga.step.requested` / lifecycle step payload. */
|
||||
export const sagaStepEventSchema = z.object(baseStepShape).strict();
|
||||
|
||||
/** Zod schema for `saga.step.completed`. */
|
||||
export const sagaStepCompletedEventSchema = z
|
||||
.object({
|
||||
...baseStepShape,
|
||||
output: z.record(z.string(), z.unknown()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
/** Zod schema for `saga.step.failed`. */
|
||||
export const sagaStepFailedEventSchema = z
|
||||
.object({
|
||||
...baseStepShape,
|
||||
code: z.string().min(1),
|
||||
message: z.string().min(1),
|
||||
})
|
||||
.strict();
|
||||
|
||||
/** Zod schema for `saga.step.compensated`. */
|
||||
export const sagaStepCompensatedEventSchema = z
|
||||
.object({
|
||||
...baseStepShape,
|
||||
compensationAttempts: z.number().int().min(1),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const baseInstanceShape = {
|
||||
sagaId: z.string().min(1),
|
||||
sagaName: z.string().min(1),
|
||||
tenantId: z.string().min(1),
|
||||
state: instanceStateSchema,
|
||||
at: z.string().min(1),
|
||||
};
|
||||
|
||||
/** Zod schema for `saga.instance.*` non-failure events. */
|
||||
export const sagaInstanceEventSchema = z.object(baseInstanceShape).strict();
|
||||
|
||||
/** Zod schema for `saga.instance.compensation_failed`. */
|
||||
export const sagaCompensationFailedEventSchema = z
|
||||
.object({
|
||||
...baseInstanceShape,
|
||||
failedStep: z.string().min(1),
|
||||
compensationAttempts: z.number().int().min(1),
|
||||
})
|
||||
.strict();
|
||||
|
||||
/**
|
||||
* Bound emitter helpers. The orchestrator constructs one instance per
|
||||
* host and uses these typed methods so a misspelt event name is a
|
||||
* compile error and a misshapen payload is rejected at the boundary.
|
||||
*
|
||||
* Every emit validates the payload via the matching Zod schema before
|
||||
* pushing it onto the bus so a bug in one orchestrator path cannot
|
||||
* silently produce wire-corrupt events.
|
||||
*/
|
||||
export class SagaEventEmitter {
|
||||
public constructor(private readonly bus: EventBus<SagaEventMap>) {}
|
||||
|
||||
public emitStepRequested(payload: SagaStepEvent): void {
|
||||
const parsed = sagaStepEventSchema.parse(payload);
|
||||
this.bus.emit("saga.step.requested", parsed as SagaStepEvent);
|
||||
}
|
||||
|
||||
public emitStepCompleted(payload: SagaStepCompletedEvent): void {
|
||||
const parsed = sagaStepCompletedEventSchema.parse(payload);
|
||||
this.bus.emit("saga.step.completed", parsed as SagaStepCompletedEvent);
|
||||
}
|
||||
|
||||
public emitStepFailed(payload: SagaStepFailedEvent): void {
|
||||
const parsed = sagaStepFailedEventSchema.parse(payload);
|
||||
this.bus.emit("saga.step.failed", parsed as SagaStepFailedEvent);
|
||||
}
|
||||
|
||||
public emitStepCompensated(payload: SagaStepCompensatedEvent): void {
|
||||
const parsed = sagaStepCompensatedEventSchema.parse(payload);
|
||||
this.bus.emit(
|
||||
"saga.step.compensated",
|
||||
parsed as SagaStepCompensatedEvent
|
||||
);
|
||||
}
|
||||
|
||||
public emitInstanceStarted(payload: SagaInstanceEvent): void {
|
||||
const parsed = sagaInstanceEventSchema.parse(payload);
|
||||
this.bus.emit("saga.instance.started", parsed as SagaInstanceEvent);
|
||||
}
|
||||
|
||||
public emitInstanceCompleted(payload: SagaInstanceEvent): void {
|
||||
const parsed = sagaInstanceEventSchema.parse(payload);
|
||||
this.bus.emit("saga.instance.completed", parsed as SagaInstanceEvent);
|
||||
}
|
||||
|
||||
public emitInstanceCompensated(payload: SagaInstanceEvent): void {
|
||||
const parsed = sagaInstanceEventSchema.parse(payload);
|
||||
this.bus.emit("saga.instance.compensated", parsed as SagaInstanceEvent);
|
||||
}
|
||||
|
||||
public emitInstanceCompensationFailed(
|
||||
payload: SagaCompensationFailedEvent
|
||||
): void {
|
||||
const parsed = sagaCompensationFailedEventSchema.parse(payload);
|
||||
this.bus.emit(
|
||||
"saga.instance.compensation_failed",
|
||||
parsed as SagaCompensationFailedEvent
|
||||
);
|
||||
}
|
||||
}
|
||||
65
packages/api-stack/api/api-saga/src/events/events.types.ts
Normal file
65
packages/api-stack/api/api-saga/src/events/events.types.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type {
|
||||
SagaInstanceState,
|
||||
SagaStepState,
|
||||
} from "../state/state-machine.types.js";
|
||||
|
||||
/** Shape of a step-level event payload. */
|
||||
export interface SagaStepEvent {
|
||||
readonly sagaId: string;
|
||||
readonly sagaName: string;
|
||||
readonly stepName: string;
|
||||
readonly stepIndex: number;
|
||||
readonly attempt: number;
|
||||
readonly tenantId: string;
|
||||
readonly stepState: SagaStepState;
|
||||
readonly at: string;
|
||||
}
|
||||
|
||||
/** Step-success specialisation carrying the produced output. */
|
||||
export interface SagaStepCompletedEvent extends SagaStepEvent {
|
||||
readonly output: Readonly<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
/** Step-failure specialisation carrying the cause + code. */
|
||||
export interface SagaStepFailedEvent extends SagaStepEvent {
|
||||
readonly code: string;
|
||||
readonly message: string;
|
||||
}
|
||||
|
||||
/** Compensation outcome for a previously-completed step. */
|
||||
export interface SagaStepCompensatedEvent extends SagaStepEvent {
|
||||
readonly compensationAttempts: number;
|
||||
}
|
||||
|
||||
/** Instance-lifetime payload. */
|
||||
export interface SagaInstanceEvent {
|
||||
readonly sagaId: string;
|
||||
readonly sagaName: string;
|
||||
readonly tenantId: string;
|
||||
readonly state: SagaInstanceState;
|
||||
readonly at: string;
|
||||
}
|
||||
|
||||
/** Compensation-failed terminal payload. */
|
||||
export interface SagaCompensationFailedEvent extends SagaInstanceEvent {
|
||||
readonly failedStep: string;
|
||||
readonly compensationAttempts: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed event map the orchestrator emits onto the host's
|
||||
* `EventBus<SagaEventMap>`. The host wires its global event bus so saga
|
||||
* payloads land on `bus.on("saga.step.requested", ...)`.
|
||||
*/
|
||||
export type SagaEventMap = {
|
||||
"saga.step.requested": SagaStepEvent;
|
||||
"saga.step.completed": SagaStepCompletedEvent;
|
||||
"saga.step.failed": SagaStepFailedEvent;
|
||||
"saga.step.compensated": SagaStepCompensatedEvent;
|
||||
"saga.instance.started": SagaInstanceEvent;
|
||||
"saga.instance.completed": SagaInstanceEvent;
|
||||
"saga.instance.compensated": SagaInstanceEvent;
|
||||
"saga.instance.compensation_failed": SagaCompensationFailedEvent;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
export type SagaEventName = keyof SagaEventMap;
|
||||
19
packages/api-stack/api/api-saga/src/events/index.ts
Normal file
19
packages/api-stack/api/api-saga/src/events/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export {
|
||||
SagaEventEmitter,
|
||||
sagaCompensationFailedEventSchema,
|
||||
sagaInstanceEventSchema,
|
||||
sagaStepCompensatedEventSchema,
|
||||
sagaStepCompletedEventSchema,
|
||||
sagaStepEventSchema,
|
||||
sagaStepFailedEventSchema,
|
||||
} from "./events.js";
|
||||
export type {
|
||||
SagaCompensationFailedEvent,
|
||||
SagaEventMap,
|
||||
SagaEventName,
|
||||
SagaInstanceEvent,
|
||||
SagaStepCompensatedEvent,
|
||||
SagaStepCompletedEvent,
|
||||
SagaStepEvent,
|
||||
SagaStepFailedEvent,
|
||||
} from "./events.types.js";
|
||||
Reference in New Issue
Block a user