Review an inventory reservation saga

src/inventory-saga-types.tsTypeScript
@@ -0,0 +1,72 @@
1+export type ReservationState = "pending" | "confirmed" | "expired" | "canceled";
2+
3+export interface Reservation {
4+ id: string;
5+ orderId: string;
6+ sku: string;
7+ quantity: number;
8+ state: ReservationState;
9+ expiresAt: Date;
10+}
11+
12+export interface ReserveInventoryCommand {
13+ messageId: string;
14+ orderId: string;
15+ sku: string;
16+ quantity: number;
17+ expiresAt: Date;
18+}
19+
20+export interface OutboxEvent {
21+ id: string;
22+ type: "ReservationCreated" | "ReservationConfirmed" | "ReservationExpired" | "ReservationCanceled";
23+ reservationId: string;
24+ occurredAt: Date;
25+}
26+
27+export interface InventoryTransaction {
28+ decrementStock(sku: string, quantity: number): Promise<void>;
29+ decrementStockIfAvailable(sku: string, quantity: number): Promise<boolean>;
30+ incrementStock(sku: string, quantity: number): Promise<void>;
31+ insertReservation(reservation: Reservation): Promise<void>;
32+ setReservationState(id: string, state: ReservationState): Promise<void>;
33+ transitionReservation(
34+ id: string,
35+ from: readonly ReservationState[],
36+ to: ReservationState,
37+ ): Promise<boolean>;
38+ insertOutbox(event: OutboxEvent): Promise<void>;
39+ markInboxProcessed(messageId: string): Promise<void>;
40+}
41+
42+export interface SagaDependencies {
43+ database: {
44+ transaction<T>(work: (transaction: InventoryTransaction) => Promise<T>): Promise<T>;
45+ };
46+ inventory: { available(sku: string): Promise<number> };
47+ reservations: {
48+ get(id: string): Promise<Reservation | undefined>;
49+ listPendingExpiredAt(now: Date): Promise<readonly Reservation[]>;
50+ };
51+ inbox: {
52+ has(messageId: string): Promise<boolean>;
53+ markProcessed(messageId: string): Promise<void>;
54+ };
55+ outbox: { insert(event: OutboxEvent): Promise<void> };
56+ newId(): string;
57+ now(): Date;
58+}
59+
60+export interface EventBroker {
61+ publish(input: {
62+ event: OutboxEvent;
63+ idempotencyKey: string;
64+ }): Promise<void>;
65+}
66+
67+export interface PublishDependencies {
68+ broker: EventBroker;
69+ markDelivered(eventId: string): Promise<void>;
70+ sleep(milliseconds: number): Promise<void>;
71+ newId(): string;
72+}