// File: tasks/saga/source.js // Express.js payment saga source bundle. const TRANSITIONS = { "Idle:Reserve": "Reserved", "Reserved:Authorize": "Authorized", "Authorized:Capture": "Captured", "Captured:Settle": "Settled", "Reserved:CompensateReserve": "Compensated", "Authorized:CompensateAuthorize": "Compensating", "Compensating:CompensateReserve": "Compensated", "Captured:CompensateCapture": "Compensating", }; function transition(state, event) { if (event === "Fail") { return "Failed"; } return TRANSITIONS[`${state}:${event}`] ?? state; } function runSaga(events) { return events.reduce(transition, "Idle"); } function isCharged(state) { return state === "Captured" || state === "Settled"; } const HAPPY_PATH = ["Reserve", "Authorize", "Capture", "Settle"]; const express = require("express"); const app = express(); app.use(express.json()); const sagaStore = {}; app.post("/saga/start", (request, response) => { const sagaId = `saga_${Date.now()}`; sagaStore[sagaId] = { state: "Idle", history: [] }; response.json({ sagaId, state: "Idle" }); }); app.post("/saga/:sagaId/event", (request, response) => { const { sagaId } = request.params; const { event } = request.body; if (!sagaStore[sagaId]) { return response.status(404).json({ error: "Not found" }); } const saga = sagaStore[sagaId]; const nextState = transition(saga.state, event); saga.history.push({ from: saga.state, event, to: nextState }); saga.state = nextState; response.json({ sagaId, state: nextState, charged: isCharged(nextState) }); }); app.get("/saga/:sagaId", (request, response) => { const saga = sagaStore[request.params.sagaId]; if (!saga) { return response.status(404).json({ error: "Not found" }); } response.json({ state: saga.state, history: saga.history }); });