<?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: Nitin Kaushal</title>
    <description>The latest articles on DEV Community by Nitin Kaushal (@nitin_kaushal_ecaea25f90f).</description>
    <link>https://dev.to/nitin_kaushal_ecaea25f90f</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%2F1587289%2Fdb645f98-9495-4aad-919e-066eb504c239.jpg</url>
      <title>DEV Community: Nitin Kaushal</title>
      <link>https://dev.to/nitin_kaushal_ecaea25f90f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nitin_kaushal_ecaea25f90f"/>
    <language>en</language>
    <item>
      <title>Stop paying for LLM calls you already know will fail: Budget Guard + circuit breakers in TypeScript</title>
      <dc:creator>Nitin Kaushal</dc:creator>
      <pubDate>Thu, 22 Oct 2026 13:08:07 +0000</pubDate>
      <link>https://dev.to/nitin_kaushal_ecaea25f90f/stop-paying-for-llm-calls-you-already-know-will-fail-budget-guard-circuit-breakers-in-typescript-3je5</link>
      <guid>https://dev.to/nitin_kaushal_ecaea25f90f/stop-paying-for-llm-calls-you-already-know-will-fail-budget-guard-circuit-breakers-in-typescript-3je5</guid>
      <description>&lt;p&gt;Every Node.js app I've worked on ends up with the same hand-rolled code around its dependencies: a &lt;code&gt;for&lt;/code&gt; loop with &lt;code&gt;await sleep()&lt;/code&gt;, a &lt;code&gt;Promise.race&lt;/code&gt; for timeouts, and a comment that says &lt;code&gt;// TODO: circuit breaker&lt;/code&gt;. Then LLM providers showed up and added two new failure modes: 429s and 60-second hangs at peak hours, and a bill you only notice when the invoice arrives.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://github.com/nkcodedev/resili" rel="noopener noreferrer"&gt;Resili&lt;/a&gt;&lt;/strong&gt;, a TypeScript-first resilience toolkit for Node.js. It wraps an &lt;strong&gt;async function&lt;/strong&gt; (not a URL, not a request object) in typed policies, and it has separate packages for &lt;code&gt;fetch&lt;/code&gt;/axios/undici and for OpenAI, Anthropic and Gemini.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Status, upfront: Resili is a &lt;strong&gt;release candidate&lt;/strong&gt; (&lt;code&gt;@resili/core@0.2.0-rc.2&lt;/code&gt; on the npm &lt;code&gt;next&lt;/code&gt; tag). It is not 1.0. Pin exact versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frgq6dsyo2ovm0xhyuh9i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frgq6dsyo2ovm0xhyuh9i.gif" alt="Resili retry and circuit breaker demo" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: one pipeline around one function
&lt;/h2&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;resili&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="s2"&gt;@resili/core&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;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resili&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="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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&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="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;perAttemptMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;retry&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="s2"&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;jitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&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;circuitBreaker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minimumThroughput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;failureRateThreshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&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;response&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;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few deliberate choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You opt in to each policy.&lt;/strong&gt; A policy is installed only when you configure it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalid options throw when the client is built&lt;/strong&gt;, not at 3 a.m. on the first failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic order.&lt;/strong&gt; When enabled, policies run as
&lt;code&gt;Fallback â†’ Cache â†’ Retry â†’ Circuit Breaker â†’ Timeout â†’ Dedupe â†’ Hedge â†’ Rate Limiter â†’ Bulkhead â†’ Operation&lt;/code&gt;.
Retry outside timeout means each attempt gets its own time budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No surprise retries.&lt;/strong&gt; A plain &lt;code&gt;Error&lt;/code&gt; is not retried unless &lt;code&gt;retryOn&lt;/code&gt; returns true, and HTTP status codes aren't failures unless you opt in (a returned &lt;code&gt;503&lt;/code&gt; is still a successful function result).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;failureRateThreshold&lt;/code&gt; is a percentage.&lt;/strong&gt; &lt;code&gt;50&lt;/code&gt; means 50%, not &lt;code&gt;0.5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Core has zero runtime dependencies.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Circuit breaker: stop hammering a dead dependency
&lt;/h2&gt;

&lt;p&gt;Retries help with blips. When a dependency is actually down, retries make it worse. Here is the circuit-breaker half of the demo in the GIF (from &lt;a href="https://github.com/nkcodedev/resili/blob/main/examples/stackblitz/demo.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;examples/stackblitz/demo.mjs&lt;/code&gt;&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;CircuitOpenError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resili&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="s2"&gt;@resili/core&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;guarded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resili&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="o"&gt;=&amp;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;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upstream is down&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;circuitBreaker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;minimumThroughput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;failureRateThreshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resetTimeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&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;build&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;guarded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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="c1"&gt;// real failure, circuit opens&lt;/span&gt;

&lt;span class="k"&gt;try&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;guarded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                          &lt;span class="c1"&gt;// never reaches the function&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;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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;CircuitOpenError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ERR_CIRCUIT_OPEN&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second call fails fast with &lt;code&gt;ERR_CIRCUIT_OPEN&lt;/code&gt; without touching the dependency. You can watch retries happen too: &lt;code&gt;recovered.on("RetryStarted", (event) =&amp;gt; console.log(event.delayMs, event.attemptNumber))&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it in the browser, no API key:&lt;/strong&gt; &lt;a href="https://stackblitz.com/fork/github/nkcodedev/resili/tree/main/examples/stackblitz?file=index.mjs&amp;amp;title=Resili" rel="noopener noreferrer"&gt;Open in StackBlitz&lt;/a&gt;, then run &lt;code&gt;node index.mjs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same policies, but for LLM calls
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@resili/llm&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; an AI SDK. It doesn't read API keys or construct vendor clients. You pass in your own client, and the adapter wraps it:&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="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&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;createLlmClient&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="s2"&gt;@resili/llm&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;createOpenAiProvider&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="s2"&gt;@resili/llm-openai&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;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLlmClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createOpenAiProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;client&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;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4.1-mini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4.1-mini&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;perAttemptMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&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;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;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;baseDelayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&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="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;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Explain backpressure in one sentence.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details I care about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Adapters disable the SDK's own retries&lt;/strong&gt;, so you don't get retries stacked on top of retries (3 Ã— 3 = 9 calls).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming is pull-through.&lt;/strong&gt; The provider call starts when you iterate, and the first non-empty text delta &lt;em&gt;commits&lt;/em&gt; the stream, so a later retry can't append a second answer to the one your user is already reading.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Anthropic (&lt;code&gt;@resili/llm-anthropic&lt;/code&gt;) and Gemini (&lt;code&gt;@resili/llm-gemini&lt;/code&gt;) work the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget Guard: reject expensive calls before they happen
&lt;/h2&gt;

&lt;p&gt;This is the feature I wanted most. You give &lt;code&gt;createLlmClient&lt;/code&gt; a price table and a budget. Resili ships &lt;strong&gt;no model prices&lt;/strong&gt;, so you own the numbers. From &lt;a href="https://github.com/nkcodedev/resili/blob/main/examples/budget-guard/example.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;examples/budget-guard/example.mjs&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;LlmBudgetExceededError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createLlmClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createPricingResolver&lt;/span&gt;&lt;span class="p"&gt;,&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="s2"&gt;@resili/llm&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;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLlmClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// any provider, e.g. createOpenAiProvider(...) or defineProvider(...)&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example-small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createPricingResolver&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example-small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputPerMillionTokensUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outputPerMillionTokensUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="na"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxCostPerRequestUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="p"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This text is not sent to a vendor.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;estimatedInputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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;estimatedOutputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;LlmBudgetExceededError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limitKind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attemptedMicroUsd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limitMicroUsd&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;The provider is &lt;strong&gt;never called&lt;/strong&gt;. Run it offline with &lt;code&gt;node examples/budget-guard/example.mjs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To be precise about what it guarantees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;maxCostPerRequestUsd&lt;/code&gt; is checked &lt;strong&gt;before&lt;/strong&gt; the call, using &lt;strong&gt;estimated&lt;/strong&gt; tokens. It's not a hard ceiling: real output can exceed the estimate, and the actual cost is recorded afterward.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxAccumulatedCostUsd&lt;/code&gt; limits recorded spend plus in-flight reservations for a scope, which is how you catch the runaway agent loop.&lt;/li&gt;
&lt;li&gt;The default ledger is &lt;strong&gt;process-local memory&lt;/strong&gt;. Multi-instance deployments need their own shared store.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it compares (honestly)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Resili&lt;/th&gt;
&lt;th&gt;cockatiel&lt;/th&gt;
&lt;th&gt;opossum&lt;/th&gt;
&lt;th&gt;p-retry&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retry&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeout&lt;/td&gt;
&lt;td&gt;policy&lt;/td&gt;
&lt;td&gt;policy&lt;/td&gt;
&lt;td&gt;breaker option&lt;/td&gt;
&lt;td&gt;&lt;code&gt;p-timeout&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circuit breaker&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulkhead&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting&lt;/td&gt;
&lt;td&gt;âœ…&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP adapters&lt;/td&gt;
&lt;td&gt;fetch/axios/undici&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM adapters + budget&lt;/td&gt;
&lt;td&gt;OpenAI/Anthropic/Gemini&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;td&gt;â€”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(From each project's own docs as of 2026-10-21. Cockatiel and opossum also document fallback.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to pick something else:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You only need retries? &lt;strong&gt;p-retry&lt;/strong&gt; is tiny and battle-tested.&lt;/li&gt;
&lt;li&gt;You only need a circuit breaker with a mature ecosystem? &lt;strong&gt;opossum&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You want a proven policy toolkit and don't care about HTTP/LLM adapters? &lt;strong&gt;cockatiel&lt;/strong&gt; is excellent and was a big inspiration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resili makes sense when you want &lt;strong&gt;one typed pipeline&lt;/strong&gt; across HTTP and LLM calls, plus &lt;strong&gt;cost controls&lt;/strong&gt; that run before the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it and tell me what's wrong
&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; @resili/core@0.2.0-rc.2 @resili/llm@0.1.0-rc.2 @resili/llm-openai@0.1.0-rc.2 openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;â­ Repo: &lt;a href="https://github.com/nkcodedev/resili" rel="noopener noreferrer"&gt;https://github.com/nkcodedev/resili&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ðŸ“š Docs: &lt;a href="https://resili.nkcode.dev/index.html" rel="noopener noreferrer"&gt;https://resili.nkcode.dev/index.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;â–¶ï¸ Playground: &lt;a href="https://stackblitz.com/fork/github/nkcodedev/resili/tree/main/examples/stackblitz?file=index.mjs&amp;amp;title=Resili" rel="noopener noreferrer"&gt;StackBlitz&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd especially like feedback on the policy-ordering defaults, the Budget Guard API, and which provider or adapter should come next. Issues and PRs are welcome.&lt;/p&gt;

&lt;p&gt;â€” Nitin Kaushal, &lt;a href="https://nkcode.dev" rel="noopener noreferrer"&gt;NK CODE DEV&lt;/a&gt; Â· Instagram &lt;a href="https://instagram.com/nk.cloud.dev" rel="noopener noreferrer"&gt;@nk.cloud.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Stop writing retry logic everywhere.</title>
      <dc:creator>Nitin Kaushal</dc:creator>
      <pubDate>Thu, 02 Jul 2026 05:48:04 +0000</pubDate>
      <link>https://dev.to/nitin_kaushal_ecaea25f90f/stop-writing-retry-logic-everywhere-55j9</link>
      <guid>https://dev.to/nitin_kaushal_ecaea25f90f/stop-writing-retry-logic-everywhere-55j9</guid>
      <description>&lt;p&gt;Every service ends up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry&lt;/li&gt;
&lt;li&gt;timeout&lt;/li&gt;
&lt;li&gt;circuit breaker&lt;/li&gt;
&lt;li&gt;fallback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…implemented differently every time.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Resili&lt;/strong&gt; — a TypeScript-first resilience toolkit 👇&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;resili&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="s2"&gt;@resili/core&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resili&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retry&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;circuitBreaker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minimumThroughput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript-first • Fluent API • Production-ready&lt;/p&gt;

&lt;p&gt;📦 &lt;a href="https://github.com/nkcodedev/resili" rel="noopener noreferrer"&gt;https://github.com/nkcodedev/resili&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback from backend/dev folks 👇&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
