<?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>Forem: Grafikui</title>
    <description>The latest articles on Forem by Grafikui (@grafikui).</description>
    <link>https://forem.com/grafikui</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3233613%2Fd1d8257e-fdaa-44c9-9cb7-18cb7c301e53.jpeg</url>
      <title>Forem: Grafikui</title>
      <link>https://forem.com/grafikui</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/grafikui"/>
    <language>en</language>
    <item>
      <title>How I added carbon tracking to GitHub Actions (now with AWS, Azure, and GCP)</title>
      <dc:creator>Grafikui</dc:creator>
      <pubDate>Fri, 20 Mar 2026 18:07:57 +0000</pubDate>
      <link>https://forem.com/grafikui/how-i-added-carbon-tracking-to-github-actions-and-why-devops-should-care-194n</link>
      <guid>https://forem.com/grafikui/how-i-added-carbon-tracking-to-github-actions-and-why-devops-should-care-194n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Updated March 2026:&lt;/strong&gt; v0.5.x adds multi-cloud support (AWS + Azure + GCP), Scope 3 embodied carbon, water consumption tracking, inline PR suggestions, and policy budgets. Original post covered AWS-only v0.2.x.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Most carbon tracking tools are dashboards built for sustainability officers. They pull three-month-old billing data and produce a PDF. Engineers never see it.&lt;/p&gt;

&lt;p&gt;This post is about shifting that left — into the pull request, before infrastructure is provisioned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with post-hoc measurement
&lt;/h2&gt;

&lt;p&gt;By the time AWS's Carbon Footprint Tool tells you that your &lt;code&gt;us-east-1&lt;/code&gt; cluster emits twice as much as &lt;code&gt;us-west-2&lt;/code&gt; would have, it has been running for a quarter. The architecture is locked in, the carbon is burnt.&lt;/p&gt;

&lt;p&gt;The only intervention point that changes what gets deployed is the PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GreenOps CLI does
&lt;/h2&gt;

&lt;p&gt;It's a GitHub Action that reads a &lt;code&gt;terraform show -json&lt;/code&gt; plan output and posts a carbon and cost diff directly on the PR — across AWS, Azure, and GCP.&lt;/p&gt;

&lt;p&gt;Here's what a developer sees on a multi-cloud plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;🌱 GreenOps Infrastructure Impact

Metric                     | Monthly Total
Scope 2 — Operational CO2e | 12.27kg
Scope 3 — Embodied CO2e    | 3.13kg
Total Lifecycle CO2e       | 15.40kg
Water Consumption          | 12.8L
Infrastructure Cost        | $210.97/month

Potential Scope 2 Savings: -11.88kg CO2e/month (96.8%) | -$13.14/month
💡 Found 3 optimization recommendations.

Resource                           | Instance        | Region      | Scope 2 | Action
aws_instance.web                   | m5.large        | us-east-1   | 4.31kg  | UPGRADE
azurerm_linux_virtual_machine.api  | Standard_D2s_v3 | eastus      | 4.24kg  | UPGRADE
google_compute_instance.worker     | n2-standard-2   | us-central1 | 3.71kg  | UPGRADE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No cloud credentials required. No outbound network calls from the CLI. The emission factors are a static JSON file you can read in ten minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the math works
&lt;/h2&gt;

&lt;p&gt;Three dimensions are tracked per resource.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope 2 — Operational (CPU power × grid intensity):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;W = W_idle + (W_max - W_idle) × utilisation
energy_kwh = W × PUE × 730h / 1000
co2e_g = energy_kwh × grid_intensity_gco2e_per_kwh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PUE differs by provider: AWS 1.13, Azure 1.125, GCP 1.10.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope 3 — Embodied (hardware manufacturing, prorated):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;embodied_g/month = (1,200,000g / 35,040h / 48 vCPUs) × vcpus × 730h
ARM discount: × 0.80  [Graviton, Ampere Altra, T2A]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Water consumption (data centre cooling):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;water_litres = (W × 730h / 1000) × WUE_litres_per_kwh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;m5.large&lt;/code&gt; in &lt;code&gt;us-east-1&lt;/code&gt; at 50% utilisation, Scope 2 produces 4,313.57g CO2e/month. That value is asserted in &lt;code&gt;engine.test.ts&lt;/code&gt;. You can verify it yourself from &lt;code&gt;factors.json&lt;/code&gt; in under five minutes.&lt;/p&gt;

&lt;p&gt;The full methodology — every formula, every assumption, every data source — is in &lt;code&gt;METHODOLOGY.md&lt;/code&gt; and MIT-licensed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding it to your workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate Terraform Plan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;terraform init&lt;/span&gt;
    &lt;span class="s"&gt;terraform plan -out=tfplan&lt;/span&gt;
    &lt;span class="s"&gt;terraform show -json tfplan &amp;gt; plan.json&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GreenOps Carbon Lint&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;omrdev1/greenops-cli@v0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;plan-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan.json&lt;/span&gt;
    &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works with AWS, Azure, and GCP plans — provider is detected automatically from resource types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional: inline suggestion comments
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;post-suggestions: true&lt;/code&gt;, GreenOps posts a one-click committable suggestion directly on the &lt;code&gt;instance_type&lt;/code&gt; / &lt;code&gt;size&lt;/code&gt; / &lt;code&gt;machine_type&lt;/code&gt; line in the PR diff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GreenOps Carbon Lint&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;omrdev1/greenops-cli@v0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;plan-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan.json&lt;/span&gt;
    &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;post-suggestions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The developer clicks "Commit suggestion" and the change is applied without leaving the PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional: policy budgets
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;.greenops.yml&lt;/code&gt; to your repo root to block merges that exceed a carbon or cost threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;budgets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;max_pr_co2e_increase_kg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;max_pr_cost_increase_usd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;
&lt;span class="na"&gt;fail_on_violation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fail_on_violation: true&lt;/code&gt; exits with code 1. The merge is blocked. The PR comment shows which constraint was breached and by how much.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;EC2, RDS, Azure VMs, and GCP Compute only. No Lambda, no ECS, no AKS.&lt;/li&gt;
&lt;li&gt;71 instance types across the three providers. Unsupported instances are flagged as &lt;code&gt;⚠ UNKNOWN&lt;/code&gt; rather than silently showing zero.&lt;/li&gt;
&lt;li&gt;Provider alias regions may not resolve — affected resources skip with a &lt;code&gt;known_after_apply&lt;/code&gt; reason.&lt;/li&gt;
&lt;li&gt;Annual average grid intensity. No real-time marginal emissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the above are tracked in open issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open source the methodology
&lt;/h2&gt;

&lt;p&gt;Every other tool in this space uses proprietary emission factors. That doesn't survive a CSRD audit where a compliance officer needs to trace exactly how a number was produced.&lt;/p&gt;

&lt;p&gt;If you disagree with an assumption, open a PR and change it.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/omrdev1/greenops-cli" rel="noopener noreferrer"&gt;github.com/omrdev1/greenops-cli&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>gcp</category>
      <category>aws</category>
      <category>devops</category>
    </item>
    <item>
      <title>Saga Engine Go: Type-Safe Distributed Transactions with Zero Infrastructure</title>
      <dc:creator>Grafikui</dc:creator>
      <pubDate>Tue, 27 Jan 2026 21:05:15 +0000</pubDate>
      <link>https://forem.com/grafikui/saga-engine-go-type-safe-distributed-transactions-with-zero-infrastructure-ke2</link>
      <guid>https://forem.com/grafikui/saga-engine-go-type-safe-distributed-transactions-with-zero-infrastructure-ke2</guid>
      <description>&lt;h3&gt;
  
  
  The Go port of Saga Engine. Compile-time step safety via generics, PostgreSQL persistence, and a 15-minute hard limit. No Temporal cluster required.
&lt;/h3&gt;




&lt;p&gt;After shipping &lt;a href="https://dev.to/grafikui/sagas-in-nodejs-without-the-heavy-lifting-introducing-saga-engine-2gf6"&gt;Saga Engine for Node.js&lt;/a&gt;, the most common request was a Go version. Not a wrapper. A native implementation that leverages what Go actually gives you: generics, context propagation, and compile-time safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saga Engine Go&lt;/strong&gt; is that implementation. Same guarantees. Different language idioms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Different from the Node.js Version
&lt;/h2&gt;

&lt;p&gt;This isn't a line-by-line port. Go changes the design in meaningful ways:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Node.js&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runtime validation&lt;/td&gt;
&lt;td&gt;Compile-time via &lt;code&gt;Step[T]&lt;/code&gt; generics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cancellation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AbortController&lt;/code&gt; (optional)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;context.Context&lt;/code&gt; (first-class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single-threaded event loop&lt;/td&gt;
&lt;td&gt;Goroutines + race detector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Step Results&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;any&lt;/code&gt; with runtime checks&lt;/td&gt;
&lt;td&gt;Generic type parameter &lt;code&gt;T&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Go version catches an entire class of bugs at compile time that the Node version can only catch at runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Core API: Generic Steps
&lt;/h2&gt;

&lt;p&gt;Every step is parameterized by its return type. No &lt;code&gt;interface{}&lt;/code&gt; casting. No runtime type assertions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Step 1: Reserve inventory (returns string)&lt;/span&gt;
    &lt;span class="n"&gt;reservationID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"reserve-inventory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StepDefinition&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;
        &lt;span class="n"&gt;IdempotencyKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"order-123-inv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&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="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Compensate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Step 2: Charge payment (returns string)&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"charge-payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StepDefinition&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;
        &lt;span class="n"&gt;IdempotencyKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"order-123-pay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&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="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Compensate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chargeID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chargeID&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;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compensate function receives the exact type returned by execute. If &lt;code&gt;Execute&lt;/code&gt; returns &lt;code&gt;(string, error)&lt;/code&gt;, &lt;code&gt;Compensate&lt;/code&gt; receives &lt;code&gt;string&lt;/code&gt;. The compiler enforces this.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Context Mandate
&lt;/h2&gt;

&lt;p&gt;Go's &lt;code&gt;context.Context&lt;/code&gt; is the mechanism for timeout enforcement. The engine cancels the context when deadlines are exceeded. This only works if your functions cooperate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// This respects cancellation&lt;/span&gt;
&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequestWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// This ignores cancellation (timeouts become lies)&lt;/span&gt;
&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&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 15-minute execution limit and per-step timeouts are enforced via context cancellation. If you pass &lt;code&gt;ctx&lt;/code&gt; to every I/O call, it works. If you don't, the engine has no way to interrupt your function.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Hard Guarantees
&lt;/h2&gt;

&lt;p&gt;Same as the Node.js version, enforced at the library level:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Guarantee&lt;/th&gt;
&lt;th&gt;Enforcement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Required. Returns &lt;code&gt;ErrIdempotencyRequired&lt;/code&gt; if keys are missing at transaction or step level.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Durability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;State committed to PostgreSQL before the next step executes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pg_try_advisory_lock&lt;/code&gt; prevents double-execution across processes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time-Boxed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15-minute hard limit, checked before every step.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Failed compensations move to &lt;code&gt;dead_letter&lt;/code&gt; for manual audit via &lt;code&gt;saga-admin&lt;/code&gt; CLI.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  4. The JSON Serialization Contract
&lt;/h2&gt;

&lt;p&gt;On crash recovery, step results are reconstructed from PostgreSQL via &lt;code&gt;json.Unmarshal&lt;/code&gt;. This means your result types must follow Go's JSON serialization rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Works: exported fields survive round-trip&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;OrderResult&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="s"&gt;`json:"amount"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Broken on resume: unexported fields are silently zeroed&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;badResult&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="c"&gt;// json.Unmarshal cannot see this&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a hard requirement, not a suggestion. If your step returns a struct with unexported fields, those fields will be zero-valued after a crash recovery. The saga will continue with corrupted state.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Error Handling the Go Way
&lt;/h2&gt;

&lt;p&gt;All errors support &lt;code&gt;errors.Is()&lt;/code&gt; and &lt;code&gt;errors.As()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrExecutionTimeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Workflow exceeded 15-minute limit&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;compErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompensationFailedError&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;compErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Compensation failed at step: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FailedStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Original error: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OriginalError&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;Seven sentinel errors, seven corresponding error types with structured fields. Standard Go error handling, no custom error-checking patterns to learn.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. PgBouncer Compatibility
&lt;/h2&gt;

&lt;p&gt;Advisory locks are session-scoped. This matters for connection pooling:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Connection Setup&lt;/th&gt;
&lt;th&gt;Compatible&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;*sql.DB&lt;/code&gt; (direct)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PgBouncer (session mode)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PgBouncer (transaction mode)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you run PgBouncer in transaction mode, lock ownership is lost between queries. The engine won't warn you. Your workflows will silently lose mutual exclusion.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Explicit Refusals
&lt;/h2&gt;

&lt;p&gt;Same philosophy as the Node.js version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No workflows &amp;gt; 15 minutes.&lt;/strong&gt; Use &lt;a href="https://temporal.io" rel="noopener noreferrer"&gt;Temporal&lt;/a&gt; for long-running processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No auto-recovery from dead letters.&lt;/strong&gt; If compensation fails, a human investigates. &lt;code&gt;saga-admin retry &amp;lt;id&amp;gt;&lt;/code&gt; is intentionally manual.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No distributed transactions.&lt;/strong&gt; Single-process, single-database. We coordinate side effects; we don't replace your DB's ACID properties.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. CLI: saga-admin
&lt;/h2&gt;

&lt;p&gt;Operational visibility without a dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build the CLI&lt;/span&gt;
go build &lt;span class="nt"&gt;-o&lt;/span&gt; saga-admin ./cmd/saga-admin

&lt;span class="c"&gt;# List dead letter workflows&lt;/span&gt;
saga-admin &lt;span class="nt"&gt;-db&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATABASE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; dead-letter

&lt;span class="c"&gt;# Investigate a specific workflow&lt;/span&gt;
saga-admin &lt;span class="nt"&gt;-db&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATABASE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; show order-123

&lt;span class="c"&gt;# Retry after fixing the root cause&lt;/span&gt;
saga-admin &lt;span class="nt"&gt;-db&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATABASE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; retry order-123

&lt;span class="c"&gt;# View aggregate stats&lt;/span&gt;
saga-admin &lt;span class="nt"&gt;-db&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATABASE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"database/sql"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="n"&gt;saga&lt;/span&gt; &lt;span class="s"&gt;"github.com/grafikui/saga-engine-go"&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"github.com/lib/pq"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPostgresStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPostgresLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransactionOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IdempotencyKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"order-123-v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"orderId"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"123"&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;Single PostgreSQL table. No migrations framework required. The schema is in the &lt;a href="https://github.com/grafikui/saga-engine-go#database-schema" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Saga Engine Go brings the same crash-resilient saga execution to the Go ecosystem. Type-safe generics, context-based cancellation, and a single PostgreSQL dependency.&lt;/p&gt;

&lt;p&gt;If you're already using the Node.js version, the Go port follows the same mental model. If you're new to Saga Engine, pick whichever runtime your services are built on.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/github.com/grafikui/saga-engine-go" rel="noopener noreferrer"&gt;Go package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/saga-engine" rel="noopener noreferrer"&gt;Node.js version&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/grafikui/saga-engine-go" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Star on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/grafikui/saga-engine-go" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://pkg.go.dev/github.com/grafikui/saga-engine-go" rel="noopener noreferrer"&gt;pkg.go.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>postgres</category>
      <category>microservices</category>
      <category>backend</category>
    </item>
    <item>
      <title>Sagas in Node.js Without the Heavy Lifting: Introducing Saga Engine</title>
      <dc:creator>Grafikui</dc:creator>
      <pubDate>Tue, 27 Jan 2026 15:34:15 +0000</pubDate>
      <link>https://forem.com/grafikui/sagas-in-nodejs-without-the-heavy-lifting-introducing-saga-engine-2gf6</link>
      <guid>https://forem.com/grafikui/sagas-in-nodejs-without-the-heavy-lifting-introducing-saga-engine-2gf6</guid>
      <description>&lt;p&gt;The Saga pattern is a lifesaver for data consistency across microservices, but implementation is often a choice between a mess of nested try/catch blocks or deploying a heavy orchestrator like Temporal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saga Engine&lt;/strong&gt; is a crash-resilient, Postgres-backed executor for Node.js. It provides the core benefits of a distributed transaction coordinator with &lt;strong&gt;zero additional infrastructure&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Logic: Clean, Atomic Workflows
&lt;/h2&gt;

&lt;p&gt;We focus on the "&lt;em&gt;No Magic&lt;/em&gt;" philosophy. You define steps; the engine guarantees execution or compensation.&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;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;t&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="c1"&gt;// Step 1: Reserve inventory&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;t&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;reserve-inventory&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;order-123-inv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&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;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;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;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Step 2: Process payment via internal gateway&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;t&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;process-payment&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;order-123-pay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&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;paymentGateway&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;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;ch&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;paymentGateway&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;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&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;&lt;strong&gt;What Happens on Failure?&lt;/strong&gt;&lt;br&gt;
If &lt;code&gt;process-payment&lt;/code&gt; fails:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;process-payment&lt;/code&gt; execution stops immediately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;reserve-inventory.compensate()&lt;/code&gt; is automatically triggered using the result from its &lt;code&gt;execute&lt;/code&gt; step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The transaction is marked as &lt;code&gt;failed&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  2. Hard Guarantees
&lt;/h2&gt;

&lt;p&gt;These aren't suggestions; they are enforced at the code level.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Guarantee&lt;/th&gt;
&lt;th&gt;Enforcement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Required. Throws &lt;code&gt;IdempotencyRequiredError&lt;/code&gt; if keys are missing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Durability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;State is persisted to Postgres before the next step starts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Postgres advisory locks prevent double-execution across pods.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time-Boxed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;15-minute hard limit&lt;/strong&gt;. Designed for high-integrity, short-lived workflows.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Failed compensations move to a &lt;code&gt;dead_letter&lt;/code&gt; state for manual audit.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  3. Explicit Refusals
&lt;/h2&gt;

&lt;p&gt;A professional tool is defined by its boundaries. &lt;strong&gt;Saga Engine&lt;/strong&gt; is not for everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Workflows &amp;gt; 15 Minutes:&lt;/strong&gt; Use Temporal. We provide the upgrade path, not the bloat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Auto-Recovery from Dead Letters:&lt;/strong&gt; If compensation fails, a human must investigate. Manual intervention via &lt;code&gt;npx saga-admin retry&lt;/code&gt; prevents cascading failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Distributed Transactions:&lt;/strong&gt; We coordinate side effects; we do not replace your DB's native ACID properties.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"bin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"saga-admin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bin/saga-admin.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. How It Compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Saga Engine&lt;/th&gt;
&lt;th&gt;Temporal&lt;/th&gt;
&lt;th&gt;BullMQ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infra&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Postgres Only&lt;/td&gt;
&lt;td&gt;Dedicated Cluster&lt;/td&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compensation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;compensate()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Manual try/catch&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning Curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 Minutes&lt;/td&gt;
&lt;td&gt;1-2 Weeks&lt;/td&gt;
&lt;td&gt;2 Days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API Orchestration&lt;/td&gt;
&lt;td&gt;Long-running/Complex&lt;/td&gt;
&lt;td&gt;Job Queues&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  5. Observability &amp;amp; Retries
&lt;/h2&gt;

&lt;p&gt;Plug into your existing stack with 13 built-in hooks:&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;tx&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;Transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-123&lt;/span&gt;&lt;span class="dl"&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="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onCompensationFailed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;alerting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Rollback failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&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="na"&gt;onDeadLetter&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;alerting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Manual intervention required: &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="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 a workflow enters &lt;code&gt;dead_letter&lt;/code&gt; due to a downstream service outage, fix the issue and use the CLI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx saga-admin retry order-123&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Start: Setup
&lt;/h2&gt;

&lt;p&gt;To keep your logic clean, move the initialization to your infrastructure layer.&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;Transaction&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="nx"&gt;PostgresLock&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;saga-engine&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;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&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;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="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="nx"&gt;pool&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;lock&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;PostgresLock&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="c1"&gt;// Use this 'tx' instance in your services&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Saga Engine v1.0.0 is production-ready. It fills the gap for teams that need saga semantics without the infrastructure tax.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions? Feedback? Found a bug?&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/Grafikui/saga_engine/issues" rel="noopener noreferrer"&gt;Open an issue on GitHub&lt;/a&gt; or drop a comment below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Grafikui/saga_engine" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Star on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/saga-engine" rel="noopener noreferrer"&gt;npm&lt;/a&gt; | &lt;a href="https://github.com/Grafikui/saga_engine" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://github.com/Grafikui/saga_engine/tree/main/docs" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>javascript</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
