-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path01-basic-checkout.ts
More file actions
122 lines (113 loc) · 3.51 KB
/
Copy path01-basic-checkout.ts
File metadata and controls
122 lines (113 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
* Basic Checkout Workflow
*
* A simple e-commerce checkout demonstrating the core saga pattern:
* - Multiple steps with compensation
* - Automatic rollback on failure
* - Type-safe step definitions
*/
import * as restate from "@restatedev/restate-sdk";
import { createSagaWorkflow, createSagaStep, StepResponse, InferServiceType } from "../src/index.js";
// Step 1: Reserve inventory
const reserveInventory = createSagaStep<
{ productId: string; quantity: number },
{ reservationId: string },
{ reservationId: string }
>({
name: "ReserveInventory",
run: async ({ input }) => {
// In production: call inventory service
const reservationId = `res_${Date.now()}`;
console.log(`Reserved ${input.quantity} of ${input.productId}`);
return new StepResponse({ reservationId }, { reservationId });
},
compensate: async (data) => {
// Release the reservation (use type guard for union type)
if ("reservationId" in data) {
console.log(`Released reservation ${data.reservationId}`);
}
},
});
// Step 2: Charge payment
const chargePayment = createSagaStep<
{ amount: number; currency: string },
{ paymentId: string },
{ paymentId: string; amount: number }
>({
name: "ChargePayment",
run: async ({ input }) => {
// In production: call payment gateway
const paymentId = `pay_${Date.now()}`;
console.log(`Charged ${input.amount} ${input.currency}`);
return new StepResponse({ paymentId }, { paymentId, amount: input.amount });
},
compensate: async (data) => {
// Refund the payment (use type guard for union type)
if ("paymentId" in data) {
console.log(`Refunded ${data.amount} for payment ${data.paymentId}`);
}
},
});
// Step 3: Create shipment
const createShipment = createSagaStep<
{ orderId: string; address: string },
{ trackingNumber: string },
{ trackingNumber: string }
>({
name: "CreateShipment",
run: async ({ input }) => {
// Simulate occasional failures
if (input.address === "FAIL") {
return StepResponse.permanentFailure("Invalid shipping address", {
trackingNumber: "",
});
}
const trackingNumber = `TRACK_${Date.now()}`;
console.log(`Created shipment to ${input.address}`);
return new StepResponse({ trackingNumber }, { trackingNumber });
},
compensate: async (data) => {
if ("trackingNumber" in data && data.trackingNumber) {
console.log(`Cancelled shipment ${data.trackingNumber}`);
}
},
});
// Checkout workflow
export const checkoutWorkflow = createSagaWorkflow(
"CheckoutWorkflow",
async (
saga,
input: {
productId: string;
quantity: number;
amount: number;
currency: string;
address: string;
},
) => {
// Step 1: Reserve inventory
const inventory = await reserveInventory(saga, {
productId: input.productId,
quantity: input.quantity,
});
// Step 2: Charge payment
const payment = await chargePayment(saga, {
amount: input.amount,
currency: input.currency,
});
// Step 3: Create shipment
// If this fails, payment and inventory are automatically rolled back
const shipment = await createShipment(saga, {
orderId: `order_${Date.now()}`,
address: input.address,
});
return {
reservationId: inventory.reservationId,
paymentId: payment.paymentId,
trackingNumber: shipment.trackingNumber,
};
},
);
export type CheckoutWorkflow = InferServiceType<typeof checkoutWorkflow>;
// Start the service
restate.endpoint().bind(checkoutWorkflow).listen(9080);