<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Elves Santos</title>
    <description>The latest articles on DEV Community by Elves Santos (@sirelves).</description>
    <link>https://dev.to/sirelves</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1901793%2F36015fd4-fda6-4ec2-afc2-19dadeae1746.jpeg</url>
      <title>DEV Community: Elves Santos</title>
      <link>https://dev.to/sirelves</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sirelves"/>
    <language>en</language>
    <item>
      <title>Velocidade de entrega e custo de manutenção pós IA</title>
      <dc:creator>Elves Santos</dc:creator>
      <pubDate>Thu, 27 Aug 2026 13:58:08 +0000</pubDate>
      <link>https://dev.to/he4rt/velocidade-de-entrega-e-custo-de-manutencao-pos-ia-5gei</link>
      <guid>https://dev.to/he4rt/velocidade-de-entrega-e-custo-de-manutencao-pos-ia-5gei</guid>
      <description>&lt;p&gt;Entregar ficou rápido demais. O problema é que manter continuou custando o mesmo preço de sempre.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tabela de Conteúdo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;O dia que a conta chegou&lt;/li&gt;
&lt;li&gt;A gente tá comemorando a parte barata&lt;/li&gt;
&lt;li&gt;Se isso quebrar às 3 da manhã, eu consigo debugar sem reabrir o chat?&lt;/li&gt;
&lt;li&gt;O gargalo só mudou de lugar&lt;/li&gt;
&lt;li&gt;O que eu faço hoje&lt;/li&gt;
&lt;li&gt;No fim das contas, o trabalho virou escolher&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  O dia que a conta chegou
&lt;/h2&gt;

&lt;p&gt;Vou começar confessando uma coisa.&lt;/p&gt;

&lt;p&gt;Teve uma feature esse ano que eu entreguei numa manhã. Uma manhã. CRUD de fórmulas de análise: o cliente queria configurar a própria conta de infestação em vez de pedir pra gente cravar no código. Migration, 4 rotas novas, uma tela de 949 linhas, e o melhor pedaço, que foi trocar 208 linhas de estratégia de cálculo hardcoded por 50 linhas chamando um avaliador de fórmula. 11h17 da manhã o PR do front tava aberto. 11h32 o do back. Fui almoçar me achando o cara.&lt;/p&gt;

&lt;p&gt;Duas semanas depois apareceram 3 tipos de análise novos. E alguém (eu) entrou no servidor e adicionou as 3 estratégias hardcoded de volta, no mapa que o motor genérico tinha aposentado 14 dias antes. O diff parte da versão antiga do arquivo. Quem escreveu aquilo não achou o caminho novo. Não entendeu que existia um caminho novo.&lt;/p&gt;

&lt;p&gt;27 dias depois eu voltei de novo, porque quando um tipo de análise não tinha fórmula o código fazia &lt;code&gt;throw new Error(JSON.stringify({...}))&lt;/code&gt;. Uma exceção com JSON dentro da mensagem, que o handler HTTP tinha que abrir pra saber o que dizer pro usuário. Consertar isso custou 35 linhas de código e 175 de teste. 5 vezes mais teste que código, escrevendo teste pra um caso que eu deveria ter pensado na manhã em que "entreguei".&lt;/p&gt;

&lt;p&gt;Hoje, 5 meses depois, o arquivo tem 268 linhas e ainda carrega um comentário meu: &lt;code&gt;// Keep for backwards compatibility with tests&lt;/code&gt;. Um mapa de 16 entradas que nenhuma linha de produção lê. Ele tá lá porque 14 asserções do teste dependem dele, e ninguém (eu) teve a tarde sobrando pra desmontar. O arquivo de teste tem 351 linhas. É maior que o serviço que ele testa.&lt;/p&gt;

&lt;p&gt;A manhã eu economizei. Os três retornos ao arquivo, não.&lt;/p&gt;

&lt;p&gt;Ó, e antes que alguém entenda errado: eu uso IA todo santo dia, em tudo. Não é papo de "IA é ruim" não. É outra coisa.&lt;/p&gt;

&lt;h2&gt;
  
  
  A gente tá comemorando a parte barata
&lt;/h2&gt;

&lt;p&gt;Pensa comigo no que a IA realmente barateou.&lt;/p&gt;

&lt;p&gt;Boilerplate, DTO, migration, aquele CRUD que você já fez 40 vezes na vida, o esqueleto de um módulo novo, entender uma lib que você nunca tinha aberto. Tudo isso ficou absurdamente mais barato e eu não quero de volta nem por decreto.&lt;/p&gt;

&lt;p&gt;Mas repara: tudo isso é o momento em que o código &lt;strong&gt;nasce&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;E software não passa a vida nascendo. Software passa a vida sendo lido, debugado, alterado às pressas na sexta e explicado pra alguém que entrou no time mês passado. Essa parte aí continua custando o mesmo tanto que custava em 2019. Talvez mais, porque agora tem mais código.&lt;/p&gt;

&lt;p&gt;Então quando você olha e fala "cara, eu tô 3x mais produtivo", pode ser verdade. Mas provavelmente você tá medindo só o pedaço que já era o mais rápido do processo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Se isso quebrar às 3 da manhã, eu consigo debugar sem reabrir o chat?
&lt;/h2&gt;

&lt;p&gt;Se eu pudesse deixar uma pergunta só desse artigo, seria o título dessa seção. Faz ela antes de mergear.&lt;/p&gt;

&lt;p&gt;Se a resposta for não, você não terminou. Você só empurrou.&lt;/p&gt;

&lt;p&gt;Tem uma versão mais dura dessa pergunta, que é: você consegue explicar por que aquilo tá daquele jeito pra uma pessoa que nunca viu esse código? Porque se você não consegue, quem tem ownership daquele arquivo não é você. E o modelo não vai estar de plantão com você no dia do incidente, isso eu garanto.&lt;/p&gt;

&lt;p&gt;De novo, não é pra escrever tudo na mão. É pra ler antes de aceitar. Ler de verdade, linha por linha, com o dedo na tela se precisar.&lt;/p&gt;

&lt;p&gt;Ler 200 linhas leva uns 10 minutos. Debugar 200 linhas que você nunca leu leva um dia. Essa conta nunca mudou e não vai mudar.&lt;/p&gt;

&lt;p&gt;E olha, tem um detalhe que eu demorei pra perceber: a IA quase nunca te devolve a solução mínima. Ela te devolve a completa. Com uma abstração que você não pediu, um parâmetro opcional "pra ficar flexível", uma interface pra um caso de uso que não existe no seu projeto. Você pediu uma função, voltou um framework.&lt;/p&gt;

&lt;p&gt;E cada uma dessas linhas é uma linha que alguém do time vai ter que ler algum dia. Ninguém escreve isso no card, mas tá lá.&lt;/p&gt;

&lt;h2&gt;
  
  
  O gargalo só mudou de lugar
&lt;/h2&gt;

&lt;p&gt;Essa aqui eu vi acontecer no time e demorei pra entender. E ela corta dos dois lados.&lt;/p&gt;

&lt;p&gt;O primeiro corte é no review. A gente aumentou muito a velocidade de quem escreve e não aumentou nada a de quem revisa. Continua sendo o mesmo cérebro humano, com as mesmas 24 horas, agora com PR de 40 arquivos caindo na fila.&lt;/p&gt;

&lt;p&gt;Aí acontece o que sempre acontece quando a fila cresce. O revisor começa a bater o olho no resumo das mudanças (o diff), ver que os testes passaram e mandar aquele "por mim tá bom" (o famoso LGTM, de &lt;em&gt;looks good to me&lt;/em&gt;). Todo mundo aqui já fez isso, inclusive eu. Não vamos fingir que não.&lt;/p&gt;

&lt;p&gt;Só que aí você tem código que ninguém escreveu de verdade sendo aprovado por ninguém de verdade, indo pra produção onde tem cliente pagando. E quando quebra, tem duas pessoas envolvidas e nenhuma das duas sabe o que aquilo faz.&lt;/p&gt;

&lt;p&gt;Passar o review pra IA também não resolve. Na minha opinião, ajuda a pegar coisa boba, tipo, ajuda mesmo. Mas o review não existe só pra achar bug; existe pra pelo menos duas pessoas do time saberem que aquele código existe.&lt;/p&gt;

&lt;p&gt;O segundo corte é no seu preço, e esse é o que mais me incomoda.&lt;/p&gt;

&lt;p&gt;No dia em que você entrega em 2 horas uma coisa que levaria 2 dias, o prazo daquela coisa virou 2 horas. Pra sempre. Ninguém volta atrás e pergunta "opa, mas quanto disso foi porque a IA acertou de primeira e quanto foi sorte?". A régua sobe e ela não desce nunca mais.&lt;/p&gt;

&lt;p&gt;Aí chega o problema chato de verdade, aquele que não tem resposta pronta em lugar nenhum, que exige você sentar e pensar por 3 dias. E agora você tá devendo uma velocidade que na real você nunca teve.&lt;/p&gt;

&lt;p&gt;Se você é PJ e cobra por escopo então, pior ainda. Você acabou de deflacionar seu próprio preço e ficou com a manutenção no colo de graça. Eu entreguei numa manhã e voltei naquele arquivo 3 vezes depois. Cobrei a manhã. As 3 voltas eu paguei do meu bolso, com o meu tempo.&lt;/p&gt;

&lt;p&gt;E é aí que eu fico travado: como é que eu continuo usando isso, que é bom demais pra abrir mão, sem entregar de graça o controle do que entra no código e o preço do meu trabalho?&lt;/p&gt;

&lt;h2&gt;
  
  
  O que eu faço hoje
&lt;/h2&gt;

&lt;p&gt;Hoje eu atuo como desenvolvedor PJ, e eu elaborei um processo de pensamento pra me ajudar a lidar com situações como essa. Esse processo, como estou chamando, foi resultado de muitos erros que eu cometi durante o uso de IA no meu trabalho.&lt;/p&gt;

&lt;p&gt;Eu descrevo o problema, não o código. Antes eu pedia "escreve uma função que faz X" e recebia a tal solução completa. Hoje eu falo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;o que já existe no projeto,&lt;/li&gt;
&lt;li&gt;qual a restrição,&lt;/li&gt;
&lt;li&gt;e principalmente o que eu &lt;strong&gt;não&lt;/strong&gt; quero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cortou muita abstração aleatória. E continuo:&lt;/p&gt;

&lt;p&gt;Eu peço a versão pequena, explicitamente. "Faz o mais simples que resolve, sem generalizar". Você não faz ideia do tanto de código que some quando você pede isso.&lt;/p&gt;

&lt;p&gt;A descrição do PR eu escrevo eu mesmo, do zero. O "como" pode vir gerado, tudo bem. O "por quê" é a única parte que não dá pra regenerar depois, e é justamente a que o meu eu de daqui a 6 meses vai precisar.&lt;/p&gt;

&lt;p&gt;Se saiu um PR monstro, eu quebro. Mesmo dando trabalho. O tamanho que cabe na cabeça de uma pessoa não mudou porque a IA chegou.&lt;/p&gt;

&lt;p&gt;E teste eu concentro onde dói. Caso feliz a IA cobre sozinha e é justamente o que menos quebra. O que quebra é falha de rede, payload torto, aquele dado legado que só existe naquele cliente antigo. É lá que mora o prejuízo.&lt;/p&gt;

&lt;p&gt;Sobre o preço, mudou uma coisa também: eu parei de estimar código e comecei a estimar problema resolvido. Não é a manhã que eu vou passar escrevendo, é o mês em que aquilo vai ficar de pé.&lt;/p&gt;

&lt;h2&gt;
  
  
  No fim das contas, o trabalho virou escolher
&lt;/h2&gt;

&lt;p&gt;Eu não sei se essa é a leitura certa, é o que eu enxerguei até agora mexendo com isso no dia a dia.&lt;/p&gt;

&lt;p&gt;Mas acho que a IA não acabou com o trabalho de engenharia, ela acabou com a parte mecânica dele. E ao acabar com a parte mecânica, ela deixou exposta a parte que a gente sempre conseguiu empurrar com a barriga: entender o sistema, decidir com critério, e sustentar aquilo em produção depois.&lt;/p&gt;

&lt;p&gt;Quem só digitava rápido perdeu vantagem. Quem consegue olhar pra 200 linhas geradas e falar "isso aqui tá errado, e o motivo é esse" ficou mais valioso, não menos. E repara no que essa pessoa tá fazendo de verdade: ela avaliou as opções e escolheu qual desvantagem ela topa carregar. O mercado chama isso de trade-off, e é literalmente o trabalho que sobrou pra gente. Generalizar agora e pagar complexidade, ou hardcodar hoje e pagar depois. Não existe escolha sem conta pra pagar, existe escolha feita com os olhos abertos e escolha feita no automático.&lt;/p&gt;

&lt;p&gt;Velocidade de entrega é fácil de mostrar em daily. Custo de manutenção fica escondido até o dia que ele te acha.&lt;/p&gt;

&lt;p&gt;Então gera, sim, gera bastante. Só lê o que você gerou antes de assinar embaixo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Da comunidade para a comunidade.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Bebe água e a gente se vê no próximo artigo.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>braziliandevs</category>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The 4 classic bugs every multi-service Node.js app hits (and how to stop reinventing the wheel)</title>
      <dc:creator>Elves Santos</dc:creator>
      <pubDate>Wed, 22 Apr 2026 02:26:00 +0000</pubDate>
      <link>https://dev.to/sirelves/the-4-classic-bugs-every-multi-service-nodejs-app-hits-and-how-to-stop-reinventing-the-wheel-4og7</link>
      <guid>https://dev.to/sirelves/the-4-classic-bugs-every-multi-service-nodejs-app-hits-and-how-to-stop-reinventing-the-wheel-4og7</guid>
      <description>&lt;p&gt;Every time I read a post-mortem from a different team, at least one of these four bugs is in it.&lt;/p&gt;

&lt;p&gt;Customer charged twice. Inventory locked because payment crashed. We hammered the Twilio API in a loop until we got banned. Or, the 3 a.m. classic, a pod died mid-flow and the retry ran everything from scratch, including the step that had already gone out.&lt;/p&gt;

&lt;p&gt;Always the same four. And always someone reinventing (badly) the wheel to fix them.&lt;/p&gt;

&lt;p&gt;This post is about those four bugs, why they keep showing up, and the library I wrote to stop reinventing them. It is called &lt;code&gt;kompensa&lt;/code&gt;. It is on npm, TypeScript-native, 20 KB, zero runtime dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: the customer was charged twice
&lt;/h2&gt;

&lt;p&gt;The most expensive scenario. Your checkout endpoint starts processing the order, the charge goes out to Stripe, but before the response comes back the client loses its connection. The mobile app has no idea if it went through, so it retries. Your API receives the POST again. And charges again.&lt;/p&gt;

&lt;p&gt;Everyone's first fix: add a &lt;code&gt;processed_requests&lt;/code&gt; table with a hash of the request body as a unique key, check it at the top of the handler. Works, until someone does an &lt;code&gt;UPDATE&lt;/code&gt; on the customer's order without reprocessing payment and you realize the wrong key was in the wrong table. Or until someone adds a timestamp field to the request and every retry becomes a "new" request.&lt;/p&gt;

&lt;p&gt;The right way is to make idempotency a first-class concept in your flow, not a side table, but the identifier that drives all behavior.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;kompensa&lt;/code&gt; you pass an &lt;code&gt;idempotencyKey&lt;/code&gt; when you execute the flow. The first time, the flow runs for real. The second time with the same key, it returns the cached result without touching any side effect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createFlow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kompensa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createFlow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// First call: Stripe is hit.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ord_42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ord_42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Second call with the same key: returns result1, Stripe is NOT called.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ord_42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ord_42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule: the key is a business identifier the client controls. The Stripe &lt;code&gt;Idempotency-Key&lt;/code&gt; header works. The &lt;code&gt;order_id&lt;/code&gt; works. &lt;code&gt;Date.now()&lt;/code&gt; does NOT work, because every retry generates a new key and you are back to square one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 2: the inventory got stuck because the payment crashed
&lt;/h2&gt;

&lt;p&gt;The second most expensive bug, and the one that floods the support Slack.&lt;/p&gt;

&lt;p&gt;The flow is: reserve stock, charge the card, issue an invoice, hand off to shipping. Suppose the reserve succeeds, the charge succeeds, but the invoice fails because the customer's tax ID came back invalid from the tax service.&lt;/p&gt;

&lt;p&gt;The request breaks, you return a 500. But the stock is reserved. And the card is debited.&lt;/p&gt;

&lt;p&gt;In the next 10 minutes you have two paths: either the customer complains (and you find out), or nobody complains and that SKU stays marked unavailable forever. On a normal day, this breaks your Black Friday.&lt;/p&gt;

&lt;p&gt;The solution to this kind of flow is the &lt;strong&gt;Saga pattern&lt;/strong&gt;. Each step has a semantic inverse. When a step fails, you walk backward through the steps that succeeded, running each inverse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createFlow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reserveStock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reservation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reservation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;issueInvoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;taxService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// no compensate: issuing the invoice is the last step, nothing to roll back&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;issueInvoice&lt;/code&gt; errors out, &lt;code&gt;stripe.refund&lt;/code&gt; runs automatically, then &lt;code&gt;inventory.release&lt;/code&gt; runs automatically, and the &lt;code&gt;FlowError&lt;/code&gt; thrown by &lt;code&gt;execute()&lt;/code&gt; carries the original error plus any compensation errors that happened along the way.&lt;/p&gt;

&lt;p&gt;The important part: if the compensation itself fails (the refund didn't go through), that error is &lt;strong&gt;collected&lt;/strong&gt;, not masked. You know both what broke and what couldn't be rolled back, which is the worst case, and needs to page a human immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 3: the dumb retry loop that hammered the external API until it got banned
&lt;/h2&gt;

&lt;p&gt;Less catastrophic, but it happens every week.&lt;/p&gt;

&lt;p&gt;Someone wrote &lt;code&gt;while (true) { try { ... } catch {} }&lt;/code&gt; around a call that sometimes fails. Or added a retry to axios/fetch without backoff. What this does: on a transient failure (503 from the provider, network timeout, 429 throttling), you bombard the upstream service in a loop until, two seconds later, you have either succeeded or been IP-banned.&lt;/p&gt;

&lt;p&gt;A correct retry has three ingredients:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Exponential backoff&lt;/strong&gt;: each attempt waits longer than the previous one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jitter&lt;/strong&gt;: a bit of randomness so all your pods do not retry at the same time (thundering herd).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transient vs. permanent distinction&lt;/strong&gt;: a 400 will never succeed, so do not retry it.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createFlow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PermanentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TransientError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kompensa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.stripe.com/v1/charges&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// respects timeout and cancellation&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TransientError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`stripe &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 400, 401, 402, 404: no point retrying&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PermanentError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`stripe rejected: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;initialDelayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxDelayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;jitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that you wait 200ms, 400ms, 800ms, 1.6s, 3.2s between attempts (with jitter randomizing each one). If it is a 400 from Stripe, zero retries. If it is a 503, retry with backoff up to the fifth attempt.&lt;/p&gt;

&lt;p&gt;The difference between this and &lt;code&gt;while(true) try catch&lt;/code&gt; is not academic: it is the difference between "your integration works under pressure" and "your IP got banned by Stripe and you need to open a ticket with them".&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 4: the pod crashed mid-flow and the retry ran everything again
&lt;/h2&gt;

&lt;p&gt;The subtlest one, and the most fatal when it happens.&lt;/p&gt;

&lt;p&gt;Your service instance went down after charging the card but before creating the shipment. The job queue (BullMQ, SQS, whatever) re-delivers the job. The worker loads the payload, runs the flow from the top, and charges the customer again.&lt;/p&gt;

&lt;p&gt;Idempotency from Bug 1 helps, but only if you have idempotency &lt;strong&gt;inside&lt;/strong&gt; the flow. If your steps are hand-rolled try/catch blocks, an idempotent charge does not save you from double-booking shipping.&lt;/p&gt;

&lt;p&gt;The correct fix is to &lt;strong&gt;persist the flow state at every transition&lt;/strong&gt;, and on retry, continue from where it stopped. Every successful step writes a record. If the process dies, the next execution looks at the record and skips the completed steps.&lt;/p&gt;

&lt;p&gt;In kompensa this is automatic once you plug in a durable adapter (Postgres or Redis):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createFlow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kompensa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PostgresStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kompensa/storage/postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PostgresStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureSchema&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// creates the kompensa_states table&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createFlow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CheckoutInput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reserveStock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reserveStock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;releaseStock&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="na"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refund&lt;/span&gt;       &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ship&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ship&lt;/span&gt;                                    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Worker processing a job&lt;/span&gt;
&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`order-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the worker crashes after &lt;code&gt;charge&lt;/code&gt; but before &lt;code&gt;ship&lt;/code&gt;, the next consumer that picks up the same job (with the same idempotencyKey) loads the persisted state, sees that &lt;code&gt;charge&lt;/code&gt; already finished as &lt;code&gt;success&lt;/code&gt;, and continues straight to &lt;code&gt;ship&lt;/code&gt;. No duplicate charge, no duplicate reservation.&lt;/p&gt;

&lt;p&gt;On top of that, the Postgres adapter holds a &lt;code&gt;pg_advisory_lock&lt;/code&gt; on the connection, so even if two workers race for the same job at the same time, only one passes the "load state" gate. The other one waits or fails fast (configurable), depending on your policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;The honest way to show this is a full checkout: reserve, charge, invoice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createFlow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FlowError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LockAcquisitionError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kompensa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PostgresStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kompensa/storage/postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PostgresStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureSchema&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createFlow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CheckoutInput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lockWaitMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// if another worker is already processing this key, fail fast&lt;/span&gt;
  &lt;span class="na"&gt;defaultRetry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;initialDelayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reserveStock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reservationId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;issueInvoice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;taxService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;missing key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;LockAcquisitionError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in progress, retry shortly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;FlowError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Everything already compensated. Client sees a clean business error.&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;failedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failedStep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty lines of endpoint and you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Idempotency (the client can retry freely).&lt;/li&gt;
&lt;li&gt;Exponential backoff with jitter on the payment step.&lt;/li&gt;
&lt;li&gt;Per-step timeout.&lt;/li&gt;
&lt;li&gt;Saga compensation on the inventory and card.&lt;/li&gt;
&lt;li&gt;Distributed lock, so you never have two workers on the same key.&lt;/li&gt;
&lt;li&gt;Resume after crash.&lt;/li&gt;
&lt;li&gt;Structured errors separating "another worker is processing this" from "the flow failed and was already rolled back".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare that with the nested try/catch version that usually lives there. Not the same language.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use this
&lt;/h2&gt;

&lt;p&gt;Being honest with myself and with you: kompensa has a deliberately small scope.&lt;/p&gt;

&lt;p&gt;If your workflow runs for days, has steps waiting for human approval, needs deterministic history replay, or has to coordinate across a fleet of workers with cross-workflow signals, you want &lt;strong&gt;Temporal&lt;/strong&gt; or &lt;strong&gt;AWS Step Functions&lt;/strong&gt;. Kompensa does not replace either of them. It lives inside your process, the flow runs in seconds to minutes, and it has no history, no replay, no separate worker service.&lt;/p&gt;

&lt;p&gt;It is the reliability layer inside your service, not an external orchestrator.&lt;/p&gt;

&lt;p&gt;For most checkouts, onboardings, order syncs, and payment flows I have seen, that is what you actually need. Temporal is overkill for 90 percent of cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;kompensa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is open on GitHub, fully TypeScript, zero runtime dependencies, ESM plus CJS. It ships with 73 tests (50 unit plus 23 integration against real Postgres 17 and Redis 7), including a crash simulation that uses &lt;code&gt;pg_terminate_backend&lt;/code&gt; to prove the lock releases when the worker dies.&lt;/p&gt;

&lt;p&gt;Feedback is welcome. MIT, at &lt;a href="https://github.com/sirelves/kompensa" rel="noopener noreferrer"&gt;github.com/sirelves/kompensa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Anyone who has ever lost a night debugging stuck inventory will understand.&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>node</category>
      <category>typescript</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
