// Node.js 22+. Run: node idempotency-lab.mjs // In-memory teaching fixture. No email is sent; state resets on restart. import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; const ledger = new Map(); let queued = 0; export async function simulatedSend(key, payload, { loseResponse = false } = {}) { const hash = createHash('sha256').update(JSON.stringify(payload)).digest('hex'); const previous = ledger.get(key); if (previous) { if (previous.hash !== hash) throw new Error('409: idempotency conflict'); return previous.result; } const result = { id: `simulated-${++queued}`, status: 'queued', mode: 'demo' }; ledger.set(key, { hash, result }); // Commit before the response is lost. if (loseResponse) throw new Error('Connection closed after acceptance'); return result; } const payload = { to: 'reader@example.test', subject: 'Your receipt', text: 'Order 42' }; const key = 'receipt:order-42:v1'; // Persist with the business event, before sending. await assert.rejects(simulatedSend(key, payload, { loseResponse: true })); const recovered = await simulatedSend(key, payload); assert.equal(recovered.id, 'simulated-1'); assert.equal(queued, 1); await assert.rejects(simulatedSend(key, { ...payload, text: 'Changed' }), /409/); await Promise.all(Array.from({ length: 10 }, () => simulatedSend(key, payload))); assert.equal(queued, 1); console.log(JSON.stringify({ tests: 4, queued, result: recovered, sendsRealEmail: false }));