Skip to content

Tutorial: Order Processing (Node.js)

Build an order processing pipeline that validates orders, charges payment and checks inventory in parallel, then parks until a shipping provider calls your webhook — resuming automatically to send the confirmation.

import { task, flow, branch, runDurableWorkflow, sendSignal, resumeWorkflow } from "sayiir";
interface Order {
orderId: string;
amount: number;
}
interface ShipmentEvent {
trackingNumber: string;
carrier: string;
}
const validateOrder = task("validate-order", (order: Order) => {
if (order.amount <= 0) throw new Error("Invalid amount");
return { ...order, validated: true as const };
});
const chargePayment = task("charge-payment", (order: Order) => {
return { ...order, paymentId: `pay_${order.orderId}`, charged: true as const };
}, { timeout: "30s", retry: { maxAttempts: 3, initialDelay: "1s", backoffMultiplier: 2.0 } });
const checkInventory = task("check-inventory", (order: Order) => {
return { ...order, inStock: true as const };
});
const workflow = flow<Order>("order-processing")
.then(validateOrder)
.fork([
branch("payment", chargePayment),
branch("inventory", checkInventory),
])
.join("finalize", ([payment, inventory]) => ({
orderId: payment.orderId,
paymentId: payment.paymentId,
inStock: inventory.inStock,
}))
.waitForSignal<ShipmentEvent>("shipping-wait", "shipment_dispatched", { timeout: "72h" })
.then("send-confirmation", (shipment) =>
`Order shipped via ${shipment.carrier}, tracking: ${shipment.trackingNumber}`)
.build();

The workflow parks at waitForSignal — no workers are held. When the shipping provider hits your webhook, deliver the payload as a signal:

// POST /webhooks/shipping handler
sendSignal(body.orderId, "shipment_dispatched", {
trackingNumber: body.trackingNumber,
carrier: body.carrier,
}, backend);
const result = resumeWorkflow(workflow, body.orderId, backend);

The full example includes a working HTTP server that receives the webhook and resumes the workflow.