<?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: Eugene Yakhnenko</title>
    <description>The latest articles on DEV Community by Eugene Yakhnenko (@eugenioenko).</description>
    <link>https://dev.to/eugenioenko</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%2F1225296%2Ffaac6a57-1b68-4e94-a89a-c5bc28538079.jpeg</url>
      <title>DEV Community: Eugene Yakhnenko</title>
      <link>https://dev.to/eugenioenko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eugenioenko"/>
    <language>en</language>
    <item>
      <title>Why I Built an Identity Provider in Go and SQLite</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Thu, 26 Mar 2026 23:44:40 +0000</pubDate>
      <link>https://dev.to/eugenioenko/zero-ceremony-identity-why-i-built-a-single-binary-oidc-provider-in-go-12d1</link>
      <guid>https://dev.to/eugenioenko/zero-ceremony-identity-why-i-built-a-single-binary-oidc-provider-in-go-12d1</guid>
      <description>&lt;p&gt;When I set out to build &lt;a href="https://autentico.top/" rel="noopener noreferrer"&gt;Auténtico&lt;/a&gt;, my primary goal was to create a fully-featured OpenID Connect Identity Provider where &lt;strong&gt;operational simplicity was the first-class design principle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Identity infrastructure is notoriously complex. A typical self-hosted setup involves a database server, a cache tier like Redis, a worker queue, and the identity service itself. When I needed a lightweight OpenID Connect (OIDC) server to run on a small 2GB RAM VPS, I realized the existing landscape was either operationally exhausting or structurally flawed for my specific needs.&lt;/p&gt;

&lt;p&gt;This is the story of how (and why) I built &lt;strong&gt;Auténtico&lt;/strong&gt;, a self-contained, single-binary OIDC provider backed by SQLite that removes the ceremony from identity management.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Itch: Finding the Right Lightweight IdP
&lt;/h2&gt;

&lt;p&gt;My journey started because I was researching and implementing a frontend OIDC library for product needs at my company. That scratched an itch, and I evolved it into a functional backend OIDC protocol server in Go.&lt;/p&gt;

&lt;p&gt;Months later, when I needed a lightweight Identity Provider, I evaluated the popular options but quickly hit roadblocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Casdoor:&lt;/strong&gt; I didn't like how they treated private data. Their demo instances recycle accounts every 5 minutes, making it impossible to truly test account deletion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PocketId:&lt;/strong&gt; This is a fantastic tool, but it had a critical UX flaw for my needs: it is &lt;strong&gt;passkey-only&lt;/strong&gt; by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While passkeys are the future, the current ecosystem is heavily fragmented. If a user is on an older OS or a restrictive browser, a passkey-only IdP completely locks them out. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Antidote: Zero-Ceremony Architecture
&lt;/h2&gt;

&lt;p&gt;I decided to convert my OIDC protocol server into a full IdP, ensuring that every architectural decision was evaluated against a single question: &lt;strong&gt;does this reduce or increase the operational burden on the person running this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://autentico.top/" rel="noopener noreferrer"&gt;Auténtico&lt;/a&gt; removes the entire traditional identity stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single Binary:&lt;/strong&gt; The entire IdP runs as one Go binary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded SQLite:&lt;/strong&gt; There is no separate database server. The entire state lives in one file. Eliminating the external database removes connection pool tuning, credential rotation, and network partitions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No External Infrastructure:&lt;/strong&gt; No Redis, no Postgres, no message queues. Background cleanup goroutines automatically purge expired tokens, sessions, and auth codes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded UIs:&lt;/strong&gt; Both the Admin dashboard (React/Ant Design) and the user-facing Account UI (React/Tailwind) are compiled directly into the binary using &lt;code&gt;go:embed&lt;/code&gt;. There are zero separate frontend deployments.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Flexibility Over Dogma: Solving the Passkey Trap
&lt;/h2&gt;

&lt;p&gt;To solve the hardware and OS fragmentation issues I experienced with passkeys, I ensured Auténtico wouldn't trap operators into a single authentication path.&lt;/p&gt;

&lt;p&gt;Instead, Auténtico offers &lt;strong&gt;three distinct authentication modes&lt;/strong&gt; that are switchable at runtime without restarting the server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;password&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;password_and_passkey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;passkey_only&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you deploy &lt;code&gt;passkey_only&lt;/code&gt; and discover your users' specific browser combinations are failing, you can instantly flip a setting in the Admin UI to fall back to passwords. For robust security without passkeys, it includes standard fallback methods like &lt;strong&gt;TOTP&lt;/strong&gt; (with in-browser QR enrollment) and &lt;strong&gt;Email OTP&lt;/strong&gt;. For users with modern browsers, it fully supports hardware-backed &lt;strong&gt;FIDO2&lt;/strong&gt; authentication and even allows first-login registration in one seamless flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Deliberately Un-clever" Architecture &amp;amp; The AI Accelerator
&lt;/h2&gt;

&lt;p&gt;To make this work, the codebase had to be &lt;strong&gt;deliberately un-clever&lt;/strong&gt;. I designed a strict vertical-slice architecture where each package (like &lt;code&gt;pkg/login&lt;/code&gt; or &lt;code&gt;pkg/token&lt;/code&gt;) owns its exact slice of functionality with a predictable structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;model.go&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handler.go&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;service.go&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Database CRUD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This strictness had a massive secondary benefit: it created the &lt;strong&gt;perfect environment for AI&lt;/strong&gt;. Because I spent the time establishing this blueprint, I reached a tipping point where I could hand off the boilerplate. AI agents seamlessly followed the patterns to generate the CRUD operations and rapidly write over &lt;strong&gt;700 tests&lt;/strong&gt; (hitting &lt;strong&gt;80% coverage&lt;/strong&gt;) precisely because the architectural constraints were so rigid.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scale Ceiling (And Why It Doesn't Matter)
&lt;/h2&gt;

&lt;p&gt;The immediate pushback to this architecture is always: &lt;em&gt;"SQLite doesn't scale."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I am intentionally honest about the scale ceiling: SQLite serializes writes. Auténtico is &lt;strong&gt;not&lt;/strong&gt; designed for active-active multi-region deployments or massive enterprise horizontal scaling.&lt;/p&gt;

&lt;p&gt;However, let's look at the math:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;Error rate&lt;/th&gt;
&lt;th&gt;Login p95&lt;/th&gt;
&lt;th&gt;Token p95&lt;/th&gt;
&lt;th&gt;Assessment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20 VUs&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;86ms&lt;/td&gt;
&lt;td&gt;54ms&lt;/td&gt;
&lt;td&gt;Comfortable — imperceptible to users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 VUs&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;611ms&lt;/td&gt;
&lt;td&gt;647ms&lt;/td&gt;
&lt;td&gt;Supported — fully functional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500 VUs&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;3.36s&lt;/td&gt;
&lt;td&gt;3.89s&lt;/td&gt;
&lt;td&gt;Degraded — users feel the wait&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Performance tests with k6 show the system degrades gracefully via SQLite's busy timeout—queueing requests and adding latency rather than throwing errors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For most teams running internal tools, small-to-mid-sized apps, or self-hosted environments, &lt;strong&gt;trading infinite horizontal scaling for zero operational overhead is absolutely the right choice&lt;/strong&gt;.&lt;/p&gt;




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

&lt;p&gt;Operational simplicity does not mean protocol simplicity.&lt;/p&gt;

&lt;p&gt;Auténtico strictly enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OIDC Discovery&lt;/strong&gt; — publishes &lt;code&gt;/.well-known/openid-configuration&lt;/code&gt; so relying parties auto-configure without hardcoding endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWK Set&lt;/strong&gt; — exposes public signing keys at &lt;code&gt;/.well-known/jwks.json&lt;/code&gt; for independent token verification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RS256 JWT Signing&lt;/strong&gt; — asymmetric signing; the private key never leaves the IdP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth2/OIDC protocol&lt;/strong&gt;: ImplementsOIDC protocol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin UI&lt;/strong&gt;: For admins to manage clients, users and session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account UI&lt;/strong&gt;: For users to manage they profile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swagger OpenAPI docs&lt;/strong&gt;: Publishes api specs docs &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are a small team, an indie developer, or just someone who wants to deploy an Identity Provider without taking on a second job as a sysadmin, sometimes &lt;strong&gt;the best architecture is the one you barely have to think about&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://autentico.top/" rel="noopener noreferrer"&gt;Auténtico&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/eugenioenko/autentico" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>identity</category>
      <category>idp</category>
      <category>go</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>The Lightweight JavaScript Framework Renaissance of 2026</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Tue, 24 Mar 2026 01:43:52 +0000</pubDate>
      <link>https://dev.to/eugenioenko/the-lightweight-javascript-framework-renaissance-of-2026-4ee0</link>
      <guid>https://dev.to/eugenioenko/the-lightweight-javascript-framework-renaissance-of-2026-4ee0</guid>
      <description>&lt;h1&gt;
  
  
  Best JavaScript Frameworks in 2026: For AI and Humans
&lt;/h1&gt;

&lt;p&gt;The JavaScript framework landscape in 2026 looks different from what it did three years ago. Not because React disappeared or Vue lost relevance, but because something shifted in how code gets written. AI coding assistants now author a significant portion of frontend code. That changes the evaluation criteria in ways the existing framework rankings haven't caught up with yet.&lt;/p&gt;

&lt;p&gt;This article covers both the established giants and the growing category of lightweight libraries that are having a quiet renaissance. The goal is to help you pick the right tool given who, or what, will be writing most of your code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The New Evaluation Criteria
&lt;/h2&gt;

&lt;p&gt;The classic framework checklist covered performance, ecosystem, learning curve, and job market. Those still matter. But in 2026, two new questions belong on that list:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does this framework cost an AI to get right?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every framework has footguns. The question is whether those footguns require deep framework-specific knowledge to avoid, or whether they're the kind of mistakes any developer (human or AI) would catch on a first read. Frameworks with fewer implicit rules produce more reliable AI-generated code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you run it without a build pipeline?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For quick prototypes, internal tools, and AI-generated demos, the ability to drop a script tag and go is genuinely valuable. Not every project needs a bundler, and forcing one adds friction that compounds when an AI agent is setting up the environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Heavy Framework Tax
&lt;/h2&gt;

&lt;p&gt;React, Vue, Angular, and Svelte dominate the ecosystem. They dominate for real reasons: massive communities, mature tooling, rich ecosystems, and years of production hardening. None of what follows is an argument to abandon them.&lt;/p&gt;

&lt;p&gt;But they carry weight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React&lt;/strong&gt; requires understanding hooks ordering rules, &lt;code&gt;useEffect&lt;/code&gt; dependency arrays, stale closure behavior, and the distinction between controlled and uncontrolled components. These are not obvious from the surface syntax. AI agents generating React code make predictable, repeatable mistakes in all of these areas. The community has documented them extensively, which means LLMs have seen the patterns, but also means the footguns are well-established and hard to train away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vue 3&lt;/strong&gt; is more approachable. The Composition API is clean, and &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; reduces boilerplate significantly. The reactivity model is intuitive. But the template compiler is a black box, the distinction between &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;reactive&lt;/code&gt; trips up new users (human and AI alike), and the ecosystem split between Options API and Composition API adds cognitive overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Angular&lt;/strong&gt; is the most structured of the group. Structure helps, but Angular's DI system, decorators, zone.js, and now the signals migration mean there is a lot of framework-specific knowledge required before you can write idiomatic code. It remains the right choice for large enterprise teams where that structure is the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Svelte&lt;/strong&gt; compiles away at build time, which is elegant. But the compiler is the framework. You cannot use Svelte without a build step, the template syntax is non-standard HTML, and the reactivity model (especially the &lt;code&gt;$:&lt;/code&gt; syntax in Svelte 4 and the runes in Svelte 5) requires knowing Svelte specifically. An AI agent that hasn't seen enough Svelte in training will produce subtly wrong reactive code.&lt;/p&gt;

&lt;p&gt;None of this is fatal. Millions of applications run on these frameworks and will continue to. But there is a real cost, and it is higher when an AI is holding the pen.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Light Library Renaissance
&lt;/h2&gt;

&lt;p&gt;A different category of tools has been growing steadily: small, focused libraries that add reactivity and component structure on top of the browser's native model rather than replacing it. They tend to share a few traits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No build step required for core functionality&lt;/li&gt;
&lt;li&gt;Templates that stay close to HTML, or use standard JS tagged literals&lt;/li&gt;
&lt;li&gt;Signal-based or proxy-based reactivity with simple rules&lt;/li&gt;
&lt;li&gt;Minimal framework-specific concepts to learn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In 2026, this category is no longer a niche. It is a legitimate choice for a wide range of projects, and in many cases the better choice for AI-generated code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Arrow.js
&lt;/h2&gt;

&lt;p&gt;Arrow.js (&lt;code&gt;@arrow-js/core&lt;/code&gt;) is one of the most technically interesting entries in this space. It was built by Standard Agents and has an architecture that reads like a deliberate response to framework complexity.&lt;/p&gt;

&lt;p&gt;The core model is simple: reactive state is a plain object wrapped in &lt;code&gt;reactive()&lt;/code&gt;, and templates are JavaScript tagged literals using the &lt;code&gt;html&lt;/code&gt; tag.&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;reactive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&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;@arrow-js/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&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="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;Count: &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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
    &amp;lt;button @click="&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;+&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&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 things stand out here. Reactive expressions in templates are just arrow functions. Static values render once; functions are tracked and re-run when dependencies change. That distinction is explicit in the syntax, not hidden behind a compiler.&lt;/p&gt;

&lt;p&gt;The reactivity model is proxy-based rather than signal-based. This means you mutate properties directly (&lt;code&gt;state.count++&lt;/code&gt;), and array mutations like &lt;code&gt;.push()&lt;/code&gt; trigger updates without requiring a reassignment. For developers coming from plain JavaScript, this feels natural.&lt;/p&gt;

&lt;p&gt;Components are defined with &lt;code&gt;component()&lt;/code&gt;, a factory function that runs once per slot and returns a template. Local state, side effects, and cleanup all live inside the factory.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;props&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;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;clicks&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;button @click="&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;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clicks&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;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;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/button&amp;gt;`&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arrow's package ecosystem is notably complete. Beyond the core, it ships SSR with &lt;code&gt;@arrow-js/ssr&lt;/code&gt;, client-side hydration with &lt;code&gt;@arrow-js/hydrate&lt;/code&gt;, hydration boundary recovery, and a QuickJS/WASM sandbox (&lt;code&gt;@arrow-js/sandbox&lt;/code&gt;) for safely running user-authored Arrow code in the browser. That last one is unusual and speaks to an AI-native use case: letting agents generate and execute code without granting them access to the host page.&lt;/p&gt;

&lt;p&gt;The tradeoff is that templates live in JavaScript. The &lt;code&gt;html&lt;/code&gt; tagged literal approach means your markup is a JS string, not a file an HTML tool understands natively. That is a different philosophy from the HTML-first camp, and whether it is a feature or a limitation depends on the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kasper.js
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kasperjs.top" rel="noopener noreferrer"&gt;Kasper.js&lt;/a&gt; takes the opposite position on the templates-vs-JavaScript question. Templates are valid HTML. Directives are standard HTML attributes prefixed with &lt;code&gt;@&lt;/code&gt;. Any developer who knows HTML can read a Kasper template without knowing Kasper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Count: {{count.value}}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;"increment()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;+&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;if=&lt;/span&gt;&lt;span class="s"&gt;"count.value &amp;gt; 10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;You clicked a lot.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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;Component&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kasper-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&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="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reactivity model uses signals with explicit &lt;code&gt;.value&lt;/code&gt; reads and writes. This is more verbose than Arrow's proxy approach, but it makes reactive reads visible in both code and templates. An AI agent reading a Kasper template knows exactly which values are reactive and when they update.&lt;/p&gt;

&lt;p&gt;Components are classes. This is a deliberate choice for AI compatibility: classes have well-defined ownership, explicit methods, and a lifecycle that maps directly to familiar OOP patterns. There are no hook rules, no dependency arrays, no rules about where you can call what. &lt;code&gt;onMount&lt;/code&gt;, &lt;code&gt;onChanges&lt;/code&gt;, &lt;code&gt;onRender&lt;/code&gt;, &lt;code&gt;onDestroy&lt;/code&gt;: the lifecycle is what it says it is.&lt;/p&gt;

&lt;p&gt;Cleanup is handled through a single &lt;code&gt;AbortController&lt;/code&gt; that every component owns. &lt;code&gt;this.watch()&lt;/code&gt;, &lt;code&gt;this.effect()&lt;/code&gt;, &lt;code&gt;this.computed()&lt;/code&gt;, and all &lt;code&gt;@on:&lt;/code&gt; event listeners are released automatically when the component is destroyed. No &lt;code&gt;return () =&amp;gt; cleanup()&lt;/code&gt;. No forgetting to unsubscribe.&lt;/p&gt;

&lt;p&gt;The expression evaluator is worth noting: it is a custom recursive-descent parser, not &lt;code&gt;eval&lt;/code&gt; and not &lt;code&gt;new Function&lt;/code&gt;. This means Kasper templates work under strict Content Security Policies, which matters for enterprise and regulated environments. The parser is more capable than it might sound: it covers the full practical range of JavaScript expressions, including arrow functions, optional chaining, nullish coalescing, object and array literals with spread, &lt;code&gt;typeof&lt;/code&gt;, &lt;code&gt;instanceof&lt;/code&gt;, postfix and prefix operators, and a pipeline operator (&lt;code&gt;|&amp;gt;&lt;/code&gt;). The only meaningful gaps compared to full JavaScript are statement-level constructs like &lt;code&gt;async/await&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt; loops, and &lt;code&gt;switch&lt;/code&gt;, none of which belong in a template expression anyway.&lt;/p&gt;

&lt;p&gt;The no-build-step story is genuine. One CDN import (16KB gzipped) and you have signals, a router, slots, and lazy loading. The Vite plugin adds single-file &lt;code&gt;.kasper&lt;/code&gt; components on top, but it is optional.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Component&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.jsdelivr.net/npm/kasper-js/dist/kasper.min.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&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="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;button @on:click="count.value++"&amp;gt;{{count.value}}&amp;lt;/button&amp;gt;`&lt;/span&gt;

  &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&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="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Counter&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kasper also ships an &lt;code&gt;llms.txt&lt;/code&gt; at &lt;a href="https://kasperjs.top/llms.txt" rel="noopener noreferrer"&gt;kasperjs.top/llms.txt&lt;/a&gt;, a machine-readable reference file specifically for AI agents. It covers the full API surface in a compact format designed for context windows, which reflects where the framework sees the ecosystem going.&lt;/p&gt;




&lt;h2&gt;
  
  
  Others Worth Knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Alpine.js&lt;/strong&gt; is the simplest entry point in the light library category. Directives live directly on HTML elements as attributes (&lt;code&gt;x-data&lt;/code&gt;, &lt;code&gt;x-show&lt;/code&gt;, &lt;code&gt;x-on:click&lt;/code&gt;). There is no component system, no build step, and very little to learn. It is excellent for adding interactivity to server-rendered pages. It is not the right tool for building SPAs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lit&lt;/strong&gt; comes from Google and is built on Web Components. Templates use tagged literals like Arrow, and reactivity is property-based. Lit components are real custom elements, which means they work in any framework or no framework. The tradeoff is that Web Component conventions (shadow DOM, attribute reflection, property vs attribute distinctions) add complexity that pure library approaches avoid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solid.js&lt;/strong&gt; is a compiler-based framework like Svelte, but its output is fine-grained reactive updates with no virtual DOM. Performance is exceptional. The JSX surface looks like React, which helps with adoption, but the mental model is fundamentally different: components run once, and reactivity is tracked through signal reads. Solid is worth learning if performance is a primary concern and you are comfortable with a build step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Petite-Vue&lt;/strong&gt; is a distribution of Vue designed for progressive enhancement. It is small, requires no build step, and works well when you need Vue's template syntax on an existing server-rendered page. It is not a full SPA framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Build Required&lt;/th&gt;
&lt;th&gt;Reactivity&lt;/th&gt;
&lt;th&gt;Template Style&lt;/th&gt;
&lt;th&gt;AI-Friendly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Hooks&lt;/td&gt;
&lt;td&gt;JSX&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue 3&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;td&gt;Proxy/Ref&lt;/td&gt;
&lt;td&gt;HTML + compiler&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Angular&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Signals/Zone&lt;/td&gt;
&lt;td&gt;HTML + compiler&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Svelte 5&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Runes&lt;/td&gt;
&lt;td&gt;HTML + compiler&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arrow.js&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Proxy&lt;/td&gt;
&lt;td&gt;JS tagged literals&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kasper.js&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Signals&lt;/td&gt;
&lt;td&gt;Valid HTML&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpine.js&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Proxy&lt;/td&gt;
&lt;td&gt;Inline HTML&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lit&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Properties&lt;/td&gt;
&lt;td&gt;JS tagged literals&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solid&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Signals&lt;/td&gt;
&lt;td&gt;JSX&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How to Choose
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use React, Vue, or Angular&lt;/strong&gt; when you are joining an existing team, building a product that needs a large ecosystem, or hiring for a team that already knows the framework. The community, tooling, and hiring pool are real advantages that lightweight alternatives cannot match yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Svelte or Solid&lt;/strong&gt; when bundle size and runtime performance are primary constraints and you are comfortable with a compiler in the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Arrow.js&lt;/strong&gt; when you want the smallest possible runtime, prefer JavaScript-centric templates, need SSR with hydration out of the box, or are building tooling where the sandbox package is relevant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Kasper.js&lt;/strong&gt; when HTML-first templates matter (for readability, CSP compliance, or AI-generated code), when you want class-based components with automatic cleanup, or when a no-build-step option has real value for your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Alpine.js&lt;/strong&gt; when you have server-rendered HTML and want to add interactivity without touching the build pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Lit&lt;/strong&gt; when Web Components interoperability is a requirement.&lt;/p&gt;




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

&lt;p&gt;The right framework in 2026 is still context-dependent. The established four are not going anywhere, and for many teams they remain the correct answer.&lt;/p&gt;

&lt;p&gt;But the light library category has matured. Arrow.js and Kasper.js in particular are not toys or experiments: they are complete, well-tested solutions with clear architectural philosophies. They are simpler by design, not by omission. And in an era where AI agents write a growing share of frontend code, simpler-by-design has compounding returns.&lt;/p&gt;

&lt;p&gt;The best framework is the one your team, and your tools, can use correctly. In 2026, that calculation includes AI as a member of the team.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a JavaScript Framework (and Failing Twice at Reactivity)</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Mon, 23 Mar 2026 01:46:01 +0000</pubDate>
      <link>https://dev.to/eugenioenko/building-a-javascript-framework-and-failing-twice-at-reactivity-5aod</link>
      <guid>https://dev.to/eugenioenko/building-a-javascript-framework-and-failing-twice-at-reactivity-5aod</guid>
      <description>&lt;p&gt;About five years ago, I didn't set out to build a framework I'd use in production.&lt;/p&gt;

&lt;p&gt;I just wanted to understand them.&lt;/p&gt;

&lt;p&gt;I had already written parsers and interpreters before, so I knew the mechanics: tokenization, ASTs, execution. But frameworks felt different. They weren't just about parsing code; they were about state, updates, and keeping the UI in sync. Reactivity was the part I didn't understand. So I decided to build one from scratch.&lt;/p&gt;

&lt;p&gt;I started with the pieces I knew: a scanner, a parser, a JavaScript interpreter, and an HTML template parser. After a while, I had a working system: a small component model and a template engine that could render real views. It looked like a framework.&lt;/p&gt;

&lt;p&gt;But it was missing the one thing that actually makes a framework feel alive: reactivity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{count.value}}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;"count -= 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;"count += 1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;+&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Component&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;kasper-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&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="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Part That Failed Twice
&lt;/h2&gt;

&lt;p&gt;I tried implementing reactivity early on. It didn't work. I've tried with using Proxy, I tried just using a render() function, it was not clicking. There where other parts of the framework I struggled with as well, but reactivity left an imprint.&lt;/p&gt;

&lt;p&gt;Later, I tried again. This time I got something running; but it was fragile. It only worked for a single component. Child updates broke. State invalidation was inconsistent. It looked like reactivity, but you couldn't trust it.&lt;/p&gt;

&lt;p&gt;So I dropped it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Back to It
&lt;/h2&gt;

&lt;p&gt;The project sat dormant for a long time, almost abandoned. Other projects took over. Life moved on. After a few years away, I returned to it. This time, I approached it differently. Instead of trying to "add reactivity," I focused on correctness first, testing everything, and simplifying assumptions.&lt;/p&gt;

&lt;p&gt;With the help of AI agents, I rebuilt the system around signals.&lt;/p&gt;

&lt;p&gt;That changed everything.&lt;/p&gt;

&lt;p&gt;Once signals were in place, the architecture became much simpler: no virtual DOM, no diffing, direct updates to real DOM nodes, fine-grained reactivity. But the real breakthrough wasn't just the model. It was the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  600 Tests Later
&lt;/h2&gt;

&lt;p&gt;I started adding tests. Then more tests. Then hundreds more. With AI assistance, I reached 600+ test cases. At that point, something unexpected happened: the AI couldn't generate any new meaningful tests. Everything obvious and most non-obvious cases were already covered.&lt;/p&gt;

&lt;p&gt;The test were meaningful. It felt complete.&lt;/p&gt;

&lt;p&gt;But it wasn't. Of course it wasn't. Just because 600+ tests pass it doesn't mean your system has no bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Test Wasn't Tests
&lt;/h2&gt;

&lt;p&gt;The codebase looked solid. The tests passed. But there was still a problem: no one had actually used the framework to build real apps.&lt;/p&gt;

&lt;p&gt;So I tried something different. Instead of writing apps manually, I asked AI agents to build them.&lt;/p&gt;

&lt;p&gt;And they failed immediately.&lt;/p&gt;

&lt;p&gt;Not because the framework was broken; but because the AI didn't know how to use it. This was a surprising moment. The system worked, but it wasn't understandable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Missing Piece: Documentation for AI
&lt;/h2&gt;

&lt;p&gt;That's when I introduced something modern: &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A dedicated, structured specification designed for AI agents. Not marketing docs. Not tutorials. Just syntax, rules, constraints, and examples. Think of it as a "principal engineer version" of the API.&lt;/p&gt;

&lt;p&gt;Then I started a loop: give the AI the spec, ask it to build an app, observe where it fails, update the spec, repeat.&lt;/p&gt;

&lt;p&gt;After a few iterations, something remarkable happened. The AI started generating full apps on the first try: todo apps, CRUD interfaces, Kanban boards, tree views, infinite scroll, even Game of Life. All working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/eugenioenko/we-had-to-write-docs-for-ai-llmstxt-changed-everything-44f5"&gt;Article about my experience with llms.txt&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Surprising Insight About AI
&lt;/h2&gt;

&lt;p&gt;At one point, the AI became very confident about a "necessary" architectural change. It proposed a redesign that would require around 100 lines of changes. We tried it. It failed repeatedly.&lt;/p&gt;

&lt;p&gt;After stepping back and analyzing the problem, the real fix was 5 lines of code.&lt;/p&gt;

&lt;p&gt;That moment stuck with me. AI can be incredibly helpful but it can also confidently overcomplicate problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Tests Stop Helping
&lt;/h2&gt;

&lt;p&gt;With 600+ tests, the system looked stable. But once the AI started generating real applications, new edge cases appeared: subtle rendering issues, lifecycle timing problems, data edge cases that no unit test would have caught in isolation.&lt;/p&gt;

&lt;p&gt;So I kept going. Built more apps (shopping cart, dashboards, editors, product listing, interractive tables with pagination), fed failures back into the system, and added more tests. Real usage found things that testing alone never would.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Made the Framework Stable
&lt;/h2&gt;

&lt;p&gt;Looking back, it wasn't one thing. It was the combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple reactive model (signals)&lt;/li&gt;
&lt;li&gt;Relentless testing (600+ cases)&lt;/li&gt;
&lt;li&gt;Real-world usage (apps, not just tests)&lt;/li&gt;
&lt;li&gt;AI as both a developer and a user&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Unexpected Win
&lt;/h2&gt;

&lt;p&gt;One design choice I made years ago turned out to be critical: the template syntax was valid HTML. Originally, this was just for better syntax highlighting. But later, it made the framework significantly more AI-friendly. No custom grammar. No ambiguity. Just HTML with extensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;If I started today, I would design the reactive model first, write tests earlier (a lot earlier), treat AI as a first-class user from day one, and create a machine-readable spec alongside human docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Ended Up
&lt;/h2&gt;

&lt;p&gt;After years of on-and-off work, multiple failures, and hundreds of tests, the framework is stable. Not because it's perfect, but because it survived repeated redesigns, real usage, and constant pressure from both humans and machines.&lt;/p&gt;

&lt;p&gt;Building the framework wasn't the hardest part. Making it correct, usable, and understandable for both humans and AI was the real challenge.&lt;/p&gt;

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

&lt;p&gt;Learn more about kasper.js at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kasperjs.top" rel="noopener noreferrer"&gt;kasperjs.top/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/eugenioenko/kasper-js" rel="noopener noreferrer"&gt;github.com/eugenioenko/kasper-js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>frameworks</category>
      <category>reactive</category>
      <category>parser</category>
    </item>
    <item>
      <title>We Had to Write Docs for AI: llms.txt Changed Everything</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Mon, 23 Mar 2026 01:37:14 +0000</pubDate>
      <link>https://dev.to/eugenioenko/we-had-to-write-docs-for-ai-llmstxt-changed-everything-44f5</link>
      <guid>https://dev.to/eugenioenko/we-had-to-write-docs-for-ai-llmstxt-changed-everything-44f5</guid>
      <description>&lt;p&gt;Most developers write documentation for humans.&lt;/p&gt;

&lt;p&gt;While building my JavaScript framework, I ran into a problem I didn't expect: the framework worked but AI couldn't use it. Not "wasn't perfect." Not "made small mistakes." It completely failed to build even basic apps correctly unless it had the source code of the framework available.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment Things Broke
&lt;/h2&gt;

&lt;p&gt;After years of work, I finally had a stable system: a custom scanner, parser, interpreter, a template engine with components, a signal-based reactivity system, and around 600 tests covering edge cases. I thought I was done.&lt;/p&gt;

&lt;p&gt;So I tried something simple: "Build a todo app using this framework."&lt;/p&gt;

&lt;p&gt;What I got back looked confident, but was completely wrong. Wrong syntax. Wrong mental model. Invented features that didn't exist.&lt;/p&gt;

&lt;p&gt;This wasn't a bug in the framework. It was a documentation failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  README Is Not Enough Anymore
&lt;/h2&gt;

&lt;p&gt;Traditional documentation is designed for humans: narrative explanations, gradual onboarding, examples mixed with storytelling.&lt;/p&gt;

&lt;p&gt;AI doesn't work like that. It doesn't "read" docs. It pattern-matches and guesses. So when the documentation is incomplete, ambiguous, or too prose-heavy, AI fills in the gaps. Confidently. Incorrectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: llms.txt
&lt;/h2&gt;

&lt;p&gt;The solution was simple in hindsight: treat AI like a strict compiler, not a reader.&lt;/p&gt;

&lt;p&gt;I created a new file: &lt;code&gt;llms.txt&lt;/code&gt;. Not marketing docs. Not tutorials. Just raw, explicit specification.&lt;/p&gt;

&lt;p&gt;The rules were strict:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No prose.&lt;/strong&gt; No storytelling, no explanations. Only syntax, rules, and constraints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No ambiguity.&lt;/strong&gt; There's a big difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use &lt;code&gt;@if&lt;/code&gt; for conditional rendering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;@if="condition"
&lt;span class="p"&gt;-&lt;/span&gt; condition must be a valid JS expression
&lt;span class="p"&gt;-&lt;/span&gt; evaluates to truthy/falsy
&lt;span class="p"&gt;-&lt;/span&gt; false removes node from DOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complete surface area.&lt;/strong&gt; All directives, template expressions, components, lifecycle hooks, signal behavior, everything explicitly defined. Nothing implied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minimal but real examples:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;each=&lt;/span&gt;&lt;span class="s"&gt;"item in items"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"item.id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ item.name }}&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reading the Docs + Source Code
&lt;/h2&gt;

&lt;p&gt;Even with &lt;code&gt;llms.txt&lt;/code&gt;, the AI couldn't just guess everything. It needed to read a lot of source code, inspect function signatures, understand how signals propagate, see how component lifecycle worked. Only then could it map the spec to the actual implementation and generate working apps.&lt;/p&gt;

&lt;p&gt;Building apps wasn't magic. It was AI + spec + code comprehension.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Iteration Loop
&lt;/h2&gt;

&lt;p&gt;I didn't just hand over the file and hope. The loop looked like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give AI the current &lt;code&gt;llms.txt&lt;/code&gt; and source access&lt;/li&gt;
&lt;li&gt;Ask it to build a real app (todo, kanban, etc.)&lt;/li&gt;
&lt;li&gt;Observe failures&lt;/li&gt;
&lt;li&gt;Fix the spec&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A few things became clear along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing features aren't always obvious.&lt;/strong&gt; At one point, AI kept trying to use &lt;code&gt;@keydown.enter&lt;/code&gt;. I had never documented it but the framework already supported it. The fix was to update the spec, not the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ambiguity is worse than missing features.&lt;/strong&gt; Undocumented features lead to confident guesses. Vaguely documented features lead to confident &lt;em&gt;wrong&lt;/em&gt; guesses. Explicit rules always win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI exposes your own blind spots.&lt;/strong&gt; It suggested massive architectural rewrites; redesign scope tracking, refactor core systems. All seemed convincing. The result: 100 lines of changes, none of which worked. The real fix? Five lines of code. AI can be very persuasive about the wrong solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  When It Finally Clicked
&lt;/h2&gt;

&lt;p&gt;After a few iterations of refining &lt;code&gt;llms.txt&lt;/code&gt; and reading source code, AI could reliably generate todo apps, Kanban boards, tree views, infinite scroll, and Game of Life (first try), fully working, following spec.&lt;/p&gt;

&lt;p&gt;Real apps also exposed edge cases that 600 unit tests never would: shopping carts, form wizards, markdown editors, live dashboards. The tests covered everything within a known model. Real usage kept expanding the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Types of Documentation
&lt;/h2&gt;

&lt;p&gt;There are now two distinct audiences for docs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human docs&lt;/strong&gt;: explain concepts, tell the story, teach mental models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI docs (&lt;code&gt;llms.txt&lt;/code&gt;)&lt;/strong&gt;: define rules, eliminate ambiguity, maximize correctness.&lt;/p&gt;

&lt;p&gt;Both are necessary. They serve completely different purposes and shouldn't be conflated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unexpected Payoff
&lt;/h2&gt;

&lt;p&gt;One design decision made early on turned out to help here too: the template syntax was valid HTML. This meant free syntax highlighting, editor support, and it turns out, AI-friendly defaults. The more your syntax looks like existing patterns, the less AI has to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The hardest part wasn't building the framework. It wasn't reactivity or performance.&lt;/p&gt;

&lt;p&gt;It was making the system understandable to something that doesn't actually understand.&lt;/p&gt;

&lt;p&gt;We're entering a world where humans write ideas and AI writes implementations. In that world, specification becomes the product. Not just a supplement to the code, the thing that makes the code usable at all.&lt;/p&gt;

&lt;p&gt;Learn more about kasper.js at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kasperjs.top" rel="noopener noreferrer"&gt;kasperjs.top&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kasperjs.top/guides/agents/" rel="noopener noreferrer"&gt;Using kasperjs with AI Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/eugenioenko/kasper-js" rel="noopener noreferrer"&gt;github.com/eugenioenko/kasper-js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;llmstxt.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>documentation</category>
      <category>javascript</category>
      <category>llms</category>
    </item>
    <item>
      <title>Adding Attribute-Based Access Control to a Real-Time Collaborative App with OpenTDF</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Fri, 20 Mar 2026 07:52:05 +0000</pubDate>
      <link>https://dev.to/eugenioenko/adding-attribute-based-access-control-to-a-real-time-collaborative-app-with-opentdf-76e</link>
      <guid>https://dev.to/eugenioenko/adding-attribute-based-access-control-to-a-real-time-collaborative-app-with-opentdf-76e</guid>
      <description>&lt;p&gt;I built &lt;a href="https://github.com/eugenioenko/skedoodle" rel="noopener noreferrer"&gt;Skedoodle&lt;/a&gt;, an open-source real-time collaborative sketching app. Think a lightweight Figma for doodling: multiple users connect over WebSocket, draw on a shared infinite canvas, and see each other's cursors move in real time. It's built with React, TypeScript, Two.js for vector graphics, and Zustand for state management, with an Express backend handling persistence and real-time sync.&lt;/p&gt;

&lt;p&gt;Building the interactive parts was the fun challenge. Throttled rendering at 60fps, path simplification algorithms to keep stroke data lean, touch support, pan and zoom on an infinite canvas, undo/redo that works across multiple collaborators. Skedoodle is a proper interactive app, not a toy demo.&lt;/p&gt;

&lt;p&gt;But it had a glaring gap: &lt;strong&gt;no authorization&lt;/strong&gt;. Authentication? Sure, users logged in via OIDC. But once you were in, you could access any sketch if you knew the ID. Think YouTube: every video is technically accessible if you have the link, even "unlisted" ones. Skedoodle had the same problem. There was no way to control who could see or edit what.&lt;/p&gt;

&lt;p&gt;I needed to fix this. And rather than hand-roll role checks and a collaborators table, I wanted to use a proper policy engine — one that could handle the simple case today and scale to more complex scenarios without rewriting everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Project Started
&lt;/h2&gt;

&lt;p&gt;This whole project started because I was working with an AI agent to generate an &lt;a href="https://opentdf.io/llms.txt" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt; for OpenTDF; a structured documentation file designed to give AI agents enough context to work with a platform. Once we had it, the obvious next step was to test it: take a real project with no authorization at all, point an agent at the &lt;code&gt;llms.txt&lt;/code&gt;, and see if it could build a correct ABAC integration from scratch.&lt;/p&gt;

&lt;p&gt;Skedoodle was the perfect candidate. A real collaborative app, with authentication but zero authorization. The experiment: could an AI agent, armed only with OpenTDF's &lt;code&gt;llms.txt&lt;/code&gt; and a description of the access model I wanted, deliver a working integration?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why OpenTDF
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opentdf.io/" rel="noopener noreferrer"&gt;OpenTDF&lt;/a&gt; is an open-source platform maintained by &lt;a href="https://www.virtru.com/" rel="noopener noreferrer"&gt;Virtru&lt;/a&gt; that provides attribute-based access control (ABAC) alongside end-to-end encryption via the &lt;a href="https://github.com/opentdf/spec" rel="noopener noreferrer"&gt;Trusted Data Format&lt;/a&gt; specification.&lt;/p&gt;

&lt;p&gt;What drew me in was how &lt;strong&gt;lightweight the authorization integration is&lt;/strong&gt;. OpenTDF is known for its encryption capabilities, but the ABAC engine stands entirely on its own. You don't need to encrypt anything to use it. You define policies, and the platform makes access decisions. That's exactly what I needed: a centralized policy engine that could answer "does this user have access to this sketch?" based on attributes rather than hardcoded role checks.&lt;/p&gt;

&lt;p&gt;The ABAC model is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You define &lt;strong&gt;namespaces&lt;/strong&gt; and &lt;strong&gt;attributes&lt;/strong&gt; (e.g., &lt;code&gt;https://skedoodle.com/attr/sketch-access&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Each attribute has &lt;strong&gt;values&lt;/strong&gt; and a &lt;strong&gt;rule&lt;/strong&gt; (AnyOf, AllOf, or Hierarchy)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subject mappings&lt;/strong&gt; connect identity provider claims to attribute entitlements&lt;/li&gt;
&lt;li&gt;When someone requests access, the platform evaluates their entitlements against the resource's required attributes and returns &lt;strong&gt;permit or deny&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No SDKs to embed, no agents to deploy. It's a JSON API you call. Your app manages the data, OpenTDF manages the policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Access Model
&lt;/h2&gt;

&lt;p&gt;What I wanted was straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner&lt;/strong&gt; creates a sketch and always has full access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Owner can invite&lt;/strong&gt; other users by username&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Owner can remove&lt;/strong&gt; any collaborator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborators&lt;/strong&gt; can draw on the sketch and can leave voluntarily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborators cannot&lt;/strong&gt; remove other collaborators or the owner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No public access&lt;/strong&gt; — every sketch requires an explicit ABAC grant. Read-only public sharing could be layered on later as a separate attribute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple enough for users to understand, but it needs proper enforcement at every layer: REST API, WebSocket connections, and the real-time command stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building It with an AI Agent
&lt;/h2&gt;

&lt;p&gt;I used Claude Code as my coding agent. The agent fetched OpenTDF's &lt;code&gt;llms.txt&lt;/code&gt; at runtime, which gave it the architectural overview, API reference, Connect RPC URL patterns, protobuf enum values, and curl examples it needed to understand the platform.&lt;/p&gt;

&lt;p&gt;The agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the docs and &lt;strong&gt;correctly chose ABAC authorization over full TDF encryption&lt;/strong&gt;, understanding that per-command encryption would be impractical for real-time collaboration&lt;/li&gt;
&lt;li&gt;Designed an attribute scheme (one attribute value per sketch, AnyOf rule) that maps cleanly to the sharing model&lt;/li&gt;
&lt;li&gt;Built the entire integration: REST API, WebSocket authorization, OpenTDF service with subject mapping lifecycle, and client UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;llms.txt&lt;/code&gt; gave the agent enough context to use the right API patterns without guessing — the correct RPC URL format, the exact enum values for condition operators and boolean types, the entity identifier structure for &lt;code&gt;GetDecisions&lt;/code&gt;. I described the access model I wanted, and it delivered a working integration.&lt;/p&gt;

&lt;p&gt;The ongoing iteration — refining the architecture, debugging access issues, removing redundant layers — was also done collaboratively with the agent, with &lt;code&gt;llms.txt&lt;/code&gt; as the shared reference for how OpenTDF's APIs work. When we hit an issue where ABAC returned PERMIT but the app still denied access, the agent was able to trace the problem because it understood the full authorization flow from the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Integration Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ABAC as the Single Source of Truth
&lt;/h3&gt;

&lt;p&gt;There's no &lt;code&gt;collaborators&lt;/code&gt; table in the database. OpenTDF is the &lt;strong&gt;sole authority&lt;/strong&gt; for access control. The database stores sketches, commands, and users. Who has access to what is entirely managed through OpenTDF subject mappings.&lt;/p&gt;

&lt;p&gt;This is a deliberate design choice. Instead of maintaining a local access control table and keeping it in sync with a policy engine, the application delegates all authorization to OpenTDF. The only local concept of "role" is ownership: the &lt;code&gt;Sketch&lt;/code&gt; table has an &lt;code&gt;ownerId&lt;/code&gt; field. Everything else — who can access which sketch, whether a given user is permitted — comes from ABAC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policy Structure
&lt;/h3&gt;

&lt;p&gt;On server startup, the service registers Skedoodle's policy structure with OpenTDF:&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;Namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://skedoodle.com&lt;/span&gt;
&lt;span class="na"&gt;Attribute: sketch-access (rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AnyOf)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each sketch gets its own attribute value. Subject mappings are actively managed as part of the application lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sketch created&lt;/strong&gt; → register an attribute value, create a subject mapping for the owner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborator invited&lt;/strong&gt; → create a subject mapping linking the user's username to the sketch's attribute value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborator removed&lt;/strong&gt; → delete the subject mapping&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access check&lt;/strong&gt; → call &lt;code&gt;GetDecisions&lt;/code&gt; to verify the user has a valid entitlement&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Sharing Workflow
&lt;/h3&gt;

&lt;p&gt;Three endpoints handle collaboration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST   /api/sketches/:id/collaborators           Owner invites by username
DELETE /api/sketches/:id/collaborators/:username  Owner removes, or user leaves
GET    /api/sketches/:id/collaborators            List who has access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an owner invites a collaborator, the app creates a subject mapping in OpenTDF:&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;result&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;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;policy.subjectmapping.SubjectMappingService&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CreateSubjectMapping&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;attributeValueId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;valueId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;newSubjectConditionSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;subjectSets&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;conditionGroups&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;booleanOperator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONDITION_BOOLEAN_TYPE_ENUM_OR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;conditions&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;subjectExternalSelectorValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBJECT_MAPPING_OPERATOR_ENUM_IN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;subjectExternalValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;username&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="p"&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="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;This tells the platform: when a user's Keycloak &lt;code&gt;.username&lt;/code&gt; matches, grant them the sketch's attribute value entitlement.&lt;/p&gt;

&lt;p&gt;Listing collaborators queries &lt;code&gt;ListSubjectMappings&lt;/code&gt; and filters for mappings that match the sketch's attribute value. Removing a collaborator deletes the mapping. There's no local state to keep in sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Checks
&lt;/h3&gt;

&lt;p&gt;Every protected operation — loading a sketch, fetching commands, joining a WebSocket room, saving commands — calls &lt;code&gt;GetDecisions&lt;/code&gt;:&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;result&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;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authorization.AuthorizationService&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GetDecisions&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;decisionRequests&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;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;entityChains&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&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;resourceAttributes&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;attributeValueFqns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;`https://skedoodle.com/attr/sketch-access/value/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sketchId&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;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="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowed&lt;/span&gt; &lt;span class="o"&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;decisionResponses&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="nx"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DECISION_PERMIT&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;If the platform denies access or is unreachable, the request is rejected. This is a deliberate choice — ABAC is the single source of truth, so there's no stale local copy to fall back to. In a production deployment where availability is critical, you'd want to run OpenTDF with redundancy, or introduce a short-lived decision cache as a buffer. For Skedoodle, fail-closed is the right tradeoff: denying access temporarily is better than granting it incorrectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebSocket Enforcement
&lt;/h3&gt;

&lt;p&gt;Real-time collaboration adds a wrinkle. You can't call a policy service on every brush stroke. The approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Authorize on join&lt;/strong&gt;: call &lt;code&gt;GetDecisions&lt;/code&gt; when a user connects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce at the room level&lt;/strong&gt;: owners and collaborators can draw, the role is set once at join time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kick on revocation&lt;/strong&gt;: when access is removed via the API, immediately disconnect the user
&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="c1"&gt;// When an owner removes a collaborator&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mappingId&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;opentdfService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findSubjectMappingId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetUsername&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sketchId&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;mappingId&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;opentdfService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteSubjectMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mappingId&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;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rooms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sketchId&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;room&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kickClientByUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetUsername&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 client handles revocation gracefully with a dialog explaining what happened and options to go back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing Sketches from ABAC
&lt;/h3&gt;

&lt;p&gt;To show a user their sketches, the app queries both the database and OpenTDF in parallel:&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ownedSketches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;abacSketchIds&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sketch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ownerId&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;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;opentdfService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listSketchIdsForUser&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;username&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;Owned sketches come from the database. Shared sketches come from OpenTDF by iterating subject mappings and extracting sketch IDs from attribute value FQNs. The two lists are merged, deduped, and returned with roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Shows About ABAC
&lt;/h2&gt;

&lt;p&gt;This integration replaced what would typically be a &lt;code&gt;collaborators&lt;/code&gt; join table, a set of role-checking queries, and manual sync logic — with a handful of API calls to a policy engine.&lt;/p&gt;

&lt;p&gt;Where ABAC gets interesting is what happens next. Today Skedoodle's access model is simple: per-sketch, per-user grants. But the same infrastructure supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mapping team membership to sketch access (subject mappings based on group claims instead of individual usernames)&lt;/li&gt;
&lt;li&gt;Classification-based access (new attributes with AllOf or Hierarchy rules)&lt;/li&gt;
&lt;li&gt;Cross-organization sharing (attribute values scoped to external identity providers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These would be &lt;strong&gt;policy changes&lt;/strong&gt; — new attributes, new subject mappings — not application code changes. The &lt;code&gt;checkAccess()&lt;/code&gt; call stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Timeline
&lt;/h2&gt;

&lt;p&gt;The entire integration took &lt;strong&gt;one afternoon&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Switch identity provider to Keycloak&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create Keycloak client + test users&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collaborator API + OpenTDF subject mapping lifecycle&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSocket authorization + kick-on-revoke&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client UI (share dialog, access denied, role badges)&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenTDF ABAC service integration&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging and polish&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The OpenTDF integration itself was the smallest piece. Most of the work was building the sharing UX and enforcing access at the WebSocket layer. OpenTDF slotted in cleanly because it's designed to be an authorization service you call, not a framework you restructure your app around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ABAC can be your single source of truth for access control.&lt;/strong&gt; Instead of maintaining a collaborators table and keeping it in sync with a policy engine, Skedoodle delegates all authorization to OpenTDF. The application code doesn't contain access control logic beyond "ask OpenTDF and respect the answer."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The integration surface is small.&lt;/strong&gt; Six API operations cover the entire authorization model, callable from any language with plain &lt;code&gt;fetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-time apps need smart enforcement points.&lt;/strong&gt; You can't call a policy service on every WebSocket message. Authorize on connect, enforce roles at the room level, and handle revocation proactively by kicking disconnected users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;llms.txt&lt;/code&gt; makes AI-assisted integration practical.&lt;/strong&gt; The agent built a working ABAC integration from documentation alone. Structured, machine-readable docs lower the barrier to adoption — not just for AI agents, but for any developer exploring a new platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ABAC scales where RBAC doesn't.&lt;/strong&gt; Roles are fine until you need to express "users in department X with clearance level Y can access resources tagged with classification Z." That sentence maps directly to ABAC attributes. Trying to model it with roles leads to an explosion of role combinations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://opentdf.io/" rel="noopener noreferrer"&gt;OpenTDF&lt;/a&gt; integration lives in a dedicated fork: &lt;a href="https://github.com/eugenioenko/skedoodle-opentdf" rel="noopener noreferrer"&gt;skedoodle-opentdf&lt;/a&gt;. It includes everything you need to run the full stack locally.&lt;/p&gt;

&lt;p&gt;If you're building an app that needs access control beyond basic ownership — especially if you want centralized policy management or the flexibility to evolve your authorization model over time — ABAC with &lt;a href="https://opentdf.io/" rel="noopener noreferrer"&gt;OpenTDF&lt;/a&gt; is worth a look.&lt;/p&gt;

</description>
      <category>abac</category>
      <category>opentdf</category>
      <category>authorization</category>
    </item>
    <item>
      <title>Kneel Before Zod!</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Fri, 16 Jan 2026 02:56:00 +0000</pubDate>
      <link>https://dev.to/eugenioenko/kneel-before-zod-5edm</link>
      <guid>https://dev.to/eugenioenko/kneel-before-zod-5edm</guid>
      <description>&lt;p&gt;TypeScript has changed the game for JavaScript developers by adding static type checking, but it doesn’t automatically handle data validation. Especially when dealing with external sources like APIs or user inputs.&lt;br&gt;
Lets break down the challenges of data validation in TypeScript, explores possible solutions, and takes a closer look at Zod, a powerful validation library.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Data Validation Matters in TypeScript
&lt;/h2&gt;

&lt;p&gt;Data validation is all about making sure the data you receive is in the right format and contains the right information. This is especially important when handling external data, like API responses, user input or data from local storage. When you define types in TypeScript, they help during development, but they don’t actually enforce anything at runtime. So even if you expect an API to return a certain structure, TypeScript won’t stop it from giving you something completely different.&lt;br&gt;
You've probably experienced this issue tons of times with errors like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VM228:1 Uncaught TypeError: Cannot read properties of undefined (reading 'something')&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Compile-Time vs. Runtime-Time Gap
&lt;/h2&gt;

&lt;p&gt;One of the biggest challenges in TypeScript data validation is the difference between what TypeScript checks at compile time and what actually happens at runtime. For example, when you fetch data from an API, TypeScript assumes it matches your type definitions, but in reality, there’s no guarantee.&lt;br&gt;
Same issue when reading from localStorage. Even when &lt;code&gt;JSON.parse()&lt;/code&gt; succeeds, there's no guarantee that the data has the shape you're expecting.&lt;br&gt;
This gap means that without extra validation, your app could end up working with incorrect or unexpected data.&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchUser&lt;/span&gt; &lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;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;response&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="s2"&gt;`/api/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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;response&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// But nothing ensures data actually matches User interface&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;retrieveUser&lt;/span&gt; &lt;span class="o"&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;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&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;user&lt;/span&gt; &lt;span class="o"&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;parse&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// But nothing ensures data actually matches User interface&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;API interfaces are contracts, and usually this is not an issue, specially if you are also the maintainer of the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solutions for TypeScript Data Validation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Type Guards and Assertion Functions
&lt;/h3&gt;

&lt;p&gt;TypeScript's built-in type guards provide a simple validation mechanism:&lt;br&gt;
&lt;a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards" rel="noopener noreferrer"&gt;Type Guards Docs&lt;/a&gt;&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;function&lt;/span&gt; &lt;span class="nf"&gt;isUser&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;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="k"&gt;typeof&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;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="k"&gt;typeof&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;username&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="k"&gt;typeof&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;email&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processUser&lt;/span&gt; &lt;span class="o"&gt;=&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;unknown&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isUser&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="c1"&gt;// TypeScript knows data is User here&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;Invalid user data&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach works but becomes unwieldy for complex objects, requiring manual implementation of validation logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zod as a Solution for TypeScript Validation
&lt;/h2&gt;

&lt;p&gt;Zod is a TypeScript-first schema validation library with static type inference. It allows defining schemas that validate data at runtime while automatically inferring TypeScript types.&lt;br&gt;
&lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod Docs&lt;/a&gt;&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;z&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;zod&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;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Extract the inferred type&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&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;// { id: string; email: string }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The retrieve from local storage function would look like:&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;retrieveUser&lt;/span&gt; &lt;span class="o"&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;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&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;user&lt;/span&gt; &lt;span class="o"&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;parse&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validatedUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&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;validatedUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// User matches the type&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;h2&gt;
  
  
  Pros of Zod
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TypeScript-First Design
&lt;/h3&gt;

&lt;p&gt;Zod was built specifically for TypeScript, resulting in excellent type inference and integration with TypeScript's type system. This enables catching type errors during development rather than at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema-to-Type Inference
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;z.infer&amp;lt;typeof schema&amp;gt;&lt;/code&gt; pattern allows extracting TypeScript types directly from validation schemas, ensuring perfect alignment between validation and types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comprehensive Schema Options
&lt;/h3&gt;

&lt;p&gt;Zod supports a wide range of validation options, from simple primitives to complex structures including objects, arrays, tuples, unions, and even functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handy Zod utility: validateSchemaOrThrow
&lt;/h2&gt;

&lt;p&gt;Here is a handy utility for validating schemas. It will attempt to validate the schema.&lt;br&gt;
When it succeeds it returns the validated data. It will re-throw the combined zod errors when data is invalid.&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;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ZodRawShape&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;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateSchemaOrThrow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ZodRawShape&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZodObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="kr"&gt;any&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZodObject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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="s2"&gt;parse&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&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="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;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&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;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&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;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;issue&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&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="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;return&lt;/span&gt; &lt;span class="nx"&gt;parsed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how it would end up being used in a framework route for example:&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;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&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;request&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateSchemaOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LoginSchema&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authUser&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;loginUserOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&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;NextResponse&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;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&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="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="kr"&gt;any&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;error&lt;/span&gt; &lt;span class="o"&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="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected login error&lt;/span&gt;&lt;span class="dl"&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;NextResponse&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;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More info at
&lt;/h2&gt;

&lt;p&gt;&lt;a href="[https://zod.dev/]"&gt;https://zod.dev/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>zod</category>
      <category>react</category>
      <category>typescript</category>
      <category>validation</category>
    </item>
    <item>
      <title>Handling Tech Debt while Shipping Features</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Fri, 16 Jan 2026 02:54:13 +0000</pubDate>
      <link>https://dev.to/eugenioenko/handling-tech-debt-while-delivering-features-1g6k</link>
      <guid>https://dev.to/eugenioenko/handling-tech-debt-while-delivering-features-1g6k</guid>
      <description>&lt;p&gt;Picture this: You're halfway through building that exciting new feature everyone's been asking for. You're in the zone. The code is flowing. And then... you discover a bug. Not in your new code—in the old system your feature depends on. What do you do? Fix it now? File a ticket and move on? Pretend you didn't see it? Is it actually a bug or is it a bug in your understanding of the requirements?&lt;/p&gt;

&lt;p&gt;If you've been there (and honestly, who hasn't?), you know this moment of choice happens constantly during development. The reality of building software isn't a clean, linear path from requirements to deployment. It's more like exploring a house where opening one door reveals three more doors you didn't know existed, and sometimes those doors are stuck.&lt;/p&gt;

&lt;p&gt;Let's talk about how to handle this reality without burning out, missing deadlines, or letting your codebase turn into a maintenance nightmare.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters More Than You Think
&lt;/h2&gt;

&lt;p&gt;Here's a sobering fact: when you switch from working on your feature to investigating that bug, it takes your brain about a good amount of time to fully get back into the zone afterward. Not the five minutes you hoped. For a team getting interrupted multiple times a day, that's anywhere from 10-20 hours of lost productivity every week.&lt;/p&gt;

&lt;p&gt;And it's not just about time. Studies show that interrupted tasks take twice as long to complete and contain twice as many errors. It's a vicious cycle: poor code quality from interrupted work creates new bugs, which create more interruptions, which create more poor code.&lt;/p&gt;

&lt;p&gt;But here's some good news: teams that handle these interruptions well don't eliminate them (that's impossible). They build systems to manage them efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Things First: Not Everything is Urgent
&lt;/h2&gt;

&lt;p&gt;The fastest way to chaos is treating every discovered issue like a five-alarm fire. Most things aren't. You need a simple way to decide what actually needs your attention right now.&lt;/p&gt;

&lt;p&gt;Here's a framework that works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P0/Critical&lt;/strong&gt;: System crashes, data loss, security breaches. Drop everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P1/High&lt;/strong&gt;: Significant features broken but workarounds exist. Handle this sprint or immediately after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P2/Medium&lt;/strong&gt;: Degraded experience but not blocking. Can wait until next sprint if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P3/Low&lt;/strong&gt;: Cosmetic issues, minor UX friction. Backlog material.&lt;/p&gt;

&lt;p&gt;The key word here is "actually." Is this &lt;em&gt;actually&lt;/em&gt; critical, or does it just feel urgent because you discovered it today?&lt;/p&gt;

&lt;p&gt;A helpful trick: use RICE scoring for the gray areas. Score each issue on Reach (how many users), Impact (how badly affected), Confidence (how sure you are), and Effort (how hard to fix). Then calculate: &lt;code&gt;(Reach × Impact × Confidence) / Effort&lt;/code&gt;. Higher scores win. This removes emotion from the decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan for the Unexpected (Because It Will Happen)
&lt;/h2&gt;

&lt;p&gt;Here's where some teams go wrong: they plan sprints as if nothing unexpected will happen. Every hour is allocated to planned work. When interruptions inevitably arrive, the sprint explodes.&lt;/p&gt;

&lt;p&gt;Successful teams build buffer into every sprint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;10-15% Corporate overhead&lt;/strong&gt;: Meetings, emails, ceremonies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;60-75% Planned work&lt;/strong&gt;: Your actual features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10-15% Unplanned work&lt;/strong&gt;: The buffer for surprises&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;"But that means we'll deliver less!"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I hear you saying. Actually, no. You'll deliver &lt;em&gt;more consistently&lt;/em&gt; because you're planning realistically. Some sprints, you'll have fewer interruptions and pull ahead. Others, you'll use the full buffer. Over time, it averages out—but without the constant feeling of failure.&lt;/p&gt;

&lt;p&gt;How much buffer do you need? It depends on the team, product, and environment: Track your actual interrupt load for a few sprints and adjust accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Superman Strategy: Protecting Focus Time
&lt;/h2&gt;

&lt;p&gt;For teams dealing with production systems or customer support, here's a game-changer: the &lt;strong&gt;Superman rotation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of spreading interrupts across everyone (death by a thousand distractions), one person handles all interrupts for a set period—a week, a sprint, whatever makes sense. Everyone else gets uninterrupted focus time.&lt;/p&gt;

&lt;p&gt;Yes, one person's productivity takes a hit. But the rest of the team's productivity &lt;em&gt;increases&lt;/em&gt;, and the net result is usually positive. Plus, the Superman builds deep knowledge of system issues and common problems.&lt;/p&gt;

&lt;p&gt;Keys to making this work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rotate fairly&lt;/strong&gt;: Nobody should be permanently on interrupt duty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide backup&lt;/strong&gt;: Have a secondary person for escalation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be realistic&lt;/strong&gt;: Junior developers might need help; that's okay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give them side work&lt;/strong&gt;: They can tackle documentation, tools, or admin tasks between interrupts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Turn Fires Into Fireproofing
&lt;/h2&gt;

&lt;p&gt;The difference between reactive and proactive teams isn't that proactive teams have fewer problems. It's that they prevent the same problem from happening twice.&lt;/p&gt;

&lt;p&gt;After any major issue, follow this pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fix the immediate problem&lt;/strong&gt; (the symptom)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conduct a quick Root Cause Analysis&lt;/strong&gt; within 24-48 hours: Why did this happen? Was it missing tests? Unclear requirements? Architecture gap?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a prevention artifact&lt;/strong&gt;: A runbook, automated test, monitoring rule, or architectural change&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track and prioritize improvements&lt;/strong&gt;: Work the highest-impact, lowest-effort ones into your tech debt time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example: Payment processing bug blocks the team. Don't just fix it, ask why your tests didn't catch it. Should there be integration tests? Add them. Document the scenario. Set up monitoring. Now it won't happen again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect Your Brain: Reduce Context Switching
&lt;/h2&gt;

&lt;p&gt;Even well-managed interrupts cause context switching. Here's how to minimize the damage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reserve focus time blocks&lt;/strong&gt;: Many teams reserve specific hours as interrupt-free. No meetings, no Slack questions (unless production is literally on fire). Make it a team norm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set response time expectations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interrupt now (call, DM): Production down, security breach&lt;/li&gt;
&lt;li&gt;Same-day response: Code reviews, sprint blockers (within 8 hours)&lt;/li&gt;
&lt;li&gt;Next-day response: General questions, non-urgent bugs (within 24 hours)&lt;/li&gt;
&lt;li&gt;Async only: Status updates, docs (no immediate response needed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limit work-in-progress&lt;/strong&gt;: One or two active items per developer, maximum. Finish before starting new work. It feels slower but actually speeds things up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Missing Requirements Problem
&lt;/h2&gt;

&lt;p&gt;Sometimes the "issue" isn't a bug—it's that you start building and realize the requirements were incomplete. This happens &lt;em&gt;constantly&lt;/em&gt;. If you are not breaking things, you are not solving hard problems, and incomplete requirements are part of that.&lt;/p&gt;

&lt;p&gt;Three-tier response:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Critical path clarification&lt;/strong&gt;: If it blocks current work, pause and clarify immediately with your product owner. This should be a 30-minute conversation, not a three-day delay.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scope decision&lt;/strong&gt;: Is this part of the current feature? If yes, add it. If no, capture it for later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document for next time&lt;/strong&gt;: Update your requirements template so this gap doesn't recur.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't fall into the false choice between "delay everything for perfect requirements" and "build something incomplete." Address blocking gaps now; defer the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure What Matters
&lt;/h2&gt;

&lt;p&gt;How do you know if your interrupt management is working? Track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cycle time&lt;/strong&gt;: How long from issue discovery to fix? Faster is better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment frequency&lt;/strong&gt;: Are you shipping consistently or sporadically?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bug escape rate&lt;/strong&gt;: What percentage of bugs reach production?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer satisfaction&lt;/strong&gt;: Survey your team on focus time and stress levels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these are trending wrong, your process needs adjustment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Traps to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Priority inflation&lt;/strong&gt;: If 50% of your issues are "P0 critical," your definitions are broken. Typically, 5-10% should be P0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating interrupts as planning failure&lt;/strong&gt;: They're not. They're inevitable in live software. The question is how you handle them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permanent interrupt duty&lt;/strong&gt;: Rotate fairly or you'll burn people out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping root cause analysis&lt;/strong&gt;: Fixing the 20th payment bug without understanding why they keep happening means you're firefighting forever. Take the time to prevent recurrence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Process creep&lt;/strong&gt;: Don't add so much overhead that the meetings about interrupts are worse than the interrupts themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Simple
&lt;/h2&gt;

&lt;p&gt;You don't need to implement everything at once. Here is a simple four-week plan to get started:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define your priority levels and share with the team&lt;/li&gt;
&lt;li&gt;Reserve 10-20% of your sprint for unplanned work&lt;/li&gt;
&lt;li&gt;Start a weekly 15-minute triage meeting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Week 2-3&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try Superman rotation if your team handles interrupts&lt;/li&gt;
&lt;li&gt;Protect time blocks as focus time&lt;/li&gt;
&lt;li&gt;Set max 2 active items per developer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Week 4+&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do root cause analysis on major issues&lt;/li&gt;
&lt;li&gt;Track your metrics&lt;/li&gt;
&lt;li&gt;Adjust based on what you learn&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Building software means dealing with unexpected issues. The question is not if unexpected issues will happen (they will), but rather when.&lt;/p&gt;

&lt;p&gt;Teams that excel at this aren't more talented or better equipped. They just accept reality and build around it: clear prioritization, reserved capacity, focused triage, root cause prevention, and protected focus time.&lt;/p&gt;

&lt;p&gt;Your codebase will never be perfect. There will always be tech debt, bugs, and surprises. But with the right system, you can ship features, maintain quality, and keep your team healthy.&lt;/p&gt;

&lt;p&gt;The best time to start was yesterday. The second-best time is right now.&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>discuss</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Custom HTTP Interceptors in HLS.js for Video Streaming</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Fri, 16 Jan 2026 02:52:32 +0000</pubDate>
      <link>https://dev.to/eugenioenko/custom-http-interceptors-in-hlsjs-for-video-streaming-236l</link>
      <guid>https://dev.to/eugenioenko/custom-http-interceptors-in-hlsjs-for-video-streaming-236l</guid>
      <description>&lt;h1&gt;
  
  
  Custom HTTP Interceptors in HLS.js for Video Streaming
&lt;/h1&gt;

&lt;p&gt;When building video streaming applications, you might need to intercept HTTP requests for authentication, custom decryption, or analytics during video playback. HLS.js makes this surprisingly straightforward with custom loaders. Let's explore what HLS.js is and how to implement HTTP interceptors for video fragment loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is HLS.js?
&lt;/h2&gt;

&lt;p&gt;HLS.js is a JavaScript library that enables HTTP Live Streaming (HLS) playback in browsers that don't natively support it. While Safari handles HLS natively, browsers like Chrome, Firefox, and Edge need HLS.js to parse &lt;code&gt;.m3u8&lt;/code&gt; playlists and stream video fragments seamlessly.&lt;/p&gt;

&lt;p&gt;The library handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parsing HLS manifests (&lt;code&gt;.m3u8&lt;/code&gt; files)&lt;/li&gt;
&lt;li&gt;Downloading and buffering video segments&lt;/li&gt;
&lt;li&gt;Adaptive bitrate switching based on network conditions&lt;/li&gt;
&lt;li&gt;Video playback coordination&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Install HLS.js via npm or pnpm:&lt;br&gt;
&lt;/p&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;hls.js
&lt;span class="c"&gt;# or&lt;/span&gt;
pnpm add hls.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;

&lt;p&gt;Here's a minimal React implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&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;react&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="nx"&gt;Hls&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;hls.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HlsPlayer&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;videoRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLVideoElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;playlistUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.m3u8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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;video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;Hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSupported&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;hls&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;Hls&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playlistUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&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="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;video&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;videoRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;controls&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing a Custom HTTP Interceptor
&lt;/h2&gt;

&lt;p&gt;The real power comes when you need to intercept fragment requests. &lt;br&gt;
Create a custom loader by extending &lt;code&gt;Hls.DefaultConfig.loader&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomLoader&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefaultConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Handle video fragments with custom logic&lt;/span&gt;
      &lt;span class="nf"&gt;fetchVideoFragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Use default loader for playlists&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callbacks&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="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;context.frag&lt;/code&gt; check distinguishes between playlist requests (handled by the default loader) and video fragment requests (where we apply custom logic).&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Fragment Fetcher
&lt;/h2&gt;

&lt;p&gt;Here's how to fetch fragments with custom handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchVideoFragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;response&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Add custom headers here if needed&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;Authorization&lt;/span&gt;&lt;span class="dl"&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;{{ Bearer token }}&lt;/span&gt;&lt;span class="dl"&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;X-Custom-Header&lt;/span&gt;&lt;span class="dl"&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;{{Custom value}}&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;buffer&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&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;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;byteLength&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;context&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="nx"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;code&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&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;as&lt;/span&gt; &lt;span class="nb"&gt;Error&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;networkError&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="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;context&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;This approach lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add authentication tokens to fragment requests&lt;/li&gt;
&lt;li&gt;Decrypt encrypted video segments&lt;/li&gt;
&lt;li&gt;Track loading performance metrics&lt;/li&gt;
&lt;li&gt;Implement custom error handling&lt;/li&gt;
&lt;li&gt;Apply transformations to video data before playback&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;Wire up the custom loader when initializing HLS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HlsPlayerHttpInterceptor&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;videoRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLVideoElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;playlistUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.m3u8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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;video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;Hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSupported&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;hls&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;Hls&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="c1"&gt;// uses the custom loader&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CustomLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playlistUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&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="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;video&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;videoRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;controls&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;Custom HTTP interceptors are particularly useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protected content&lt;/strong&gt;: Adding authentication headers to video fragment requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DRM workflows&lt;/strong&gt;: Decrypting video segments before playback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt;: Tracking fragment load times and network performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching strategies&lt;/strong&gt;: Implementing custom caching logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A/B testing&lt;/strong&gt;: Routing requests to different CDN endpoints&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hls</category>
      <category>react</category>
      <category>video</category>
      <category>streaming</category>
    </item>
    <item>
      <title>Shaking Trees in JavaScript Libraries</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Tue, 18 Mar 2025 03:51:58 +0000</pubDate>
      <link>https://dev.to/eugenioenko/shaking-trees-in-javascript-libraries-394e</link>
      <guid>https://dev.to/eugenioenko/shaking-trees-in-javascript-libraries-394e</guid>
      <description>&lt;h1&gt;
  
  
  Tree Shaking in JavaScript Libraries: Default vs. Named Exports
&lt;/h1&gt;

&lt;p&gt;Tree shaking is an optimization technique that eliminates unused code in JavaScript bundles, significantly reducing the size of applications that consume libraries. When developing JavaScript libraries, the export pattern can dramatically impact your consumers' ability to benefit from tree shaking. Let's explore this with some concrete examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Tree Shaking Limitations with Default Exports
&lt;/h2&gt;

&lt;p&gt;Default exports can prevent tree shaking in several patterns used in JavaScript libraries, resulting in bloated bundles for consumers. Let's review some of these patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Object Pattern Problem
&lt;/h3&gt;

&lt;p&gt;One of the most common anti-patterns that prevents tree shaking occurs when developers use default exports to export multiple functionalities as properties of a single object:&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="c1"&gt;// utils.js&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatMoney&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;formatCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;formatPhoneNumber&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When consumers import this module, even if they only need one function, they get the entire object:&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="c1"&gt;// consumer.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;utils&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;./utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formatDate&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;Date&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Only using formatDate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this scenario, the bundler cannot determine that &lt;code&gt;formatCurrency&lt;/code&gt; and &lt;code&gt;formatPhoneNumber&lt;/code&gt; are unused because the entire object is being imported as a single unit. This effectively breaks tree shaking, causing all functions to be included in the final bundle. It also makes it impossible for the consumer to only import &lt;code&gt;formatDate&lt;/code&gt; function because the whole object is exported.&lt;/p&gt;

&lt;h3&gt;
  
  
  Namespace Re-exports
&lt;/h3&gt;

&lt;p&gt;Another problematic pattern involves re-exporting namespace imports:&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="c1"&gt;// constants.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// namespaced-constants.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;constants&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;./constants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;constants&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// consumer.js&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;constants&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;./namespaced-constants&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;constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Only using foo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern prevents tree shaking because the bundler cannot analyze which properties of the re-exported namespace are actually used by consumers. Both &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt; will be included in the final bundle even though only &lt;code&gt;foo&lt;/code&gt; is used.&lt;/p&gt;

&lt;h3&gt;
  
  
  React Component Libraries
&lt;/h3&gt;

&lt;p&gt;This issue is particularly prevalent in React component libraries:&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="c1"&gt;// components.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&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;Input&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&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;Select&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Button&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;Select&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a consumer only needs one component, they still receive the entire library:&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="c1"&gt;// App.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Components&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;./components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Button&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;// Only using Button&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enabling Tree Shaking with Named Exports
&lt;/h2&gt;

&lt;p&gt;Named exports provide a great approach for library authors who want to enable effective tree shaking for their consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Individual Named Exports
&lt;/h3&gt;

&lt;p&gt;The most straightforward pattern uses individual named exports:&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="c1"&gt;// utils.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatCurrency&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consumers can then import only what they need:&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="c1"&gt;// consumer.js&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;formatDate&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;./utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;formatDate&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;Date&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this approach, bundlers can now analyze the import statements and determine that only &lt;code&gt;formatDate&lt;/code&gt; is used and avoid bundling the unused functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Effective Re-exports
&lt;/h3&gt;

&lt;p&gt;When aggregating exports from multiple files, using the &lt;code&gt;export *&lt;/code&gt; syntax provides better tree shaking than re-exporting namespace objects:&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="c1"&gt;// constants/index.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&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;./dateConstants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&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;./currencyConstants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&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;./validationConstants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// consumer.js&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;isoDateFormatter&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;./constants&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;This pattern allows bundlers to trace imports through multiple modules and include only the necessary code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component Library Example
&lt;/h3&gt;

&lt;p&gt;For React component libraries, named exports provide superior tree shaking:&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="c1"&gt;// components.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Select&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* implementation */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// App.js&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;Button&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;./components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;/&amp;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;Or when needing to combine multiple exports into single re-exporting module use &lt;code&gt;export *&lt;/code&gt; format:&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="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&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;./buttons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&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;./inputs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&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;./select&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;Modern bundlers like webpack and rollup can easily determine that only the &lt;code&gt;Button&lt;/code&gt; component is being used and exclude &lt;code&gt;Input&lt;/code&gt; and &lt;code&gt;Select&lt;/code&gt; from the final bundle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical Impact on Bundle Size
&lt;/h3&gt;

&lt;p&gt;The practical impact of these differences can be substantial in real-world applications. For example, a UI library with 50 components might be 500KB in total size. With proper tree shaking using named exports, an application that only uses 5 of those components might include just 50KB of code. Without tree shaking due to default exports, the entire 500KB library would be included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Use named exports for all public API components to enable effective tree shaking&lt;/li&gt;
&lt;li&gt;Avoid exporting objects with multiple properties as default exports&lt;/li&gt;
&lt;li&gt;When aggregating exports from multiple files, use &lt;code&gt;export *&lt;/code&gt; or individual named exports rather than namespace objects&lt;/li&gt;
&lt;li&gt;Consider adding ESLint rules to enforce these patterns across your codebase&lt;/li&gt;
&lt;li&gt;When using &lt;code&gt;export default&lt;/code&gt; make sure to export from different modules and not re-export from a single one. If you do need to re-export from single one, only do &lt;code&gt;export { default as Name}&lt;/code&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The choice between default and named exports is not merely stylistic, it has significant implications for the performance of applications that consume your library. Default exports, particularly when used to export multiple functionalities as a single object, can prevent effective tree shaking. In contrast, named exports enable bundlers to precisely identify and include only the code that's actually used.&lt;/p&gt;

&lt;p&gt;TLDR; Stick to named exports, they are clear winners when it comes to tree shaking capabilities.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Debouncing State in React</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Tue, 17 Sep 2024 17:06:29 +0000</pubDate>
      <link>https://dev.to/eugenioenko/debouncing-state-in-react-45f4</link>
      <guid>https://dev.to/eugenioenko/debouncing-state-in-react-45f4</guid>
      <description>&lt;p&gt;When building interactive components in react that depend on user input and specially keyboard input, it's common to end in a situation where an API call is made for every key pressed which leads to performance and ux issues. Too many unnecessary API calls are made and also it's not guaranteed that those calls are gonna return in order, so the results might not even be for the last searched value. A way to solve this problem is to debounce the user input which is what this article is gonna explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is debouncing?
&lt;/h2&gt;

&lt;p&gt;Debouncing ensures that a value is updated or a function is called only after a certain amount of time has passed since the last function call. The debounced value "lags" behind and is updated less frequently but it guarantees to get the latest value at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is useDeferredValue debouncing?
&lt;/h2&gt;

&lt;p&gt;No, useDeferredValue is not the same as debouncing. While both useDeferredValue and debouncing help to manage updates efficiently and improve performance, they serve different purposes and work in distinct ways.&lt;/p&gt;

&lt;p&gt;The purpose of &lt;a href="https://react.dev/reference/react/useDeferredValue" rel="noopener noreferrer"&gt;useDeferredValue&lt;/a&gt; is to defer a low-priority update so that higher-priority updates (such as visual UI updates) can be processed first. It allows React to prioritize rendering more critical parts of the UI, improving responsiveness, especially in cases where there are complex or expensive computations happening in the background.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unlike debouncing, it doesn’t require choosing any fixed delay. If the user’s device is fast (e.g. powerful laptop), the deferred re-render would happen almost immediately and wouldn’t be noticeable. If the user’s device is slow, the list would “lag behind” the input proportionally to how slow the device is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://react.dev/reference/react/useDeferredValue#how-is-deferring-a-value-different-from-debouncing-and-throttling" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Debouncing with lodash _.debounce
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://lodash.com/docs/#debounce" rel="noopener noreferrer"&gt;_.debounce&lt;/a&gt; creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.&lt;br&gt;
Using lodash’s debounce combined with useMemo is a good choice especially if lodash is already part of your project (stay tuned for a feature article exploring this combination)&lt;/p&gt;
&lt;h2&gt;
  
  
  Custom useDebouncedValue hook
&lt;/h2&gt;

&lt;p&gt;This hook takes a value and a delay, and it returns the debounced version of that value. If the value changes, the hook will wait for the specified delay before updating the debounced value. If the value changes again within the delay period, the timer resets, and the value is debounced once more.&lt;/p&gt;

&lt;p&gt;Here’s how you can implement it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Hook that provides a debounced state value and a setter function.
 * The state value only updates after the specified delay has passed without further changes.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useDebouncedState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initial&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="nx"&gt;timeInMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initial&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;timeoutRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;lastValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initial&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;setDebouncedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;timeoutRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;lastValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&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="nx"&gt;timeInMs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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="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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDebouncedValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&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;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State Management&lt;/strong&gt;: The hook uses useState to store the debounced value. Initially, it’s set to the passed-in value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Effect Hook&lt;/strong&gt;: Inside useEffect, a setTimeout is triggered, which will update the debouncedValue after the specified delay (in milliseconds).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup&lt;/strong&gt;: The return function in useEffect clears the timeout when either the value or the delay changes. This ensures that no outdated updates are applied, preventing memory leaks and keeping the debounce behavior stable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Usage Example
&lt;/h3&gt;

&lt;p&gt;Imagine you’re building a search bar that triggers an API request on every user input. To avoid making too many unnecessary requests while the user types, you can debounce the search term like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useDebouncedState&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;@/hooks/use-debounce&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;useEffect&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;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Books&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSearch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useDebouncedState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="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;search&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="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;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;em&gt;(note: it’s possible to use onChange instead of onInput but it does require for the user to focus out of the input to be triggered which in case of an autocomplete, this might not be desired)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;useDebouncedState hook has been designed in a way that it can be a “drop in” replacement for “useState” hook when throttling is required, so if you ever need debouncing I hope this article got you inspired and covered!&lt;br&gt;
If you ever needed to debounce in the past, what was your solution at that time? Leave a comment! &lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>debounce</category>
    </item>
    <item>
      <title>Silent Breaking Changes: A Developer’s Guide to Identifying Hidden Bugs</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Wed, 20 Dec 2023 05:57:36 +0000</pubDate>
      <link>https://dev.to/eugenioenko/identifying-silent-breaking-changes-1d13</link>
      <guid>https://dev.to/eugenioenko/identifying-silent-breaking-changes-1d13</guid>
      <description>&lt;p&gt;Here's something that's kept me up at night more than once in my career: you push a seemingly harmless change, tests pass green, code reviews look good, and then three days later you're in a war room trying to figure out why a microservice stopped responding. Welcome to the world of silent breaking changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Silent Breaking Changes?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;silent breaking change&lt;/strong&gt; is a backward-incompatible change that doesn't immediately scream at you through compilation errors or failing tests. Instead, it lurks in the shadows, only revealing itself through subtle runtime bugs, weird production behavior, or angry customer reports days (or weeks) after deployment.&lt;/p&gt;

&lt;p&gt;They're "silent" because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your code still compiles and builds successfully&lt;/li&gt;
&lt;li&gt;Automated tests do not catch the changed behavior&lt;/li&gt;
&lt;li&gt;Only specific consumers or edge cases are affected&lt;/li&gt;
&lt;li&gt;The impact can surface long after deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three General Categories
&lt;/h2&gt;

&lt;p&gt;Before we dive into where these issues hide, it helps to understand the fundamental types of silent breaking changes. Most fall into one of these three categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Algorithmic Changes
&lt;/h3&gt;

&lt;p&gt;Modifying the underlying logic or implementation without updating documentation, tests, or properly communicating the change. The method signature stays the same, but the way it computes results changes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example:&lt;/em&gt; Your &lt;code&gt;calculateShippingCost()&lt;/code&gt; function used to round prices up to the nearest dollar, but you "improved" it to use standard rounding (nearest). Suddenly, customers who budgeted for $15 shipping are getting charged $14, and your accounting system that expected whole numbers is confused.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Behavioral Changes
&lt;/h3&gt;

&lt;p&gt;Changes in input/output expectations, error handling, or side effects that produce undesired behavior. This includes changes to return value semantics, validation rules, or when exceptions are thrown.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example:&lt;/em&gt; Your authentication API used to throw a &lt;code&gt;405 Method Not Allowed&lt;/code&gt; for invalid credentials, and your tests only checked that an error was thrown. You switched to throwing a &lt;code&gt;401 Unauthorized&lt;/code&gt; to be more "RESTful." Now, client apps that specifically checked for a &lt;code&gt;405&lt;/code&gt; code break, even though your tests still pass, because they didn't verify the exact error code.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Side Effect Changes
&lt;/h3&gt;

&lt;p&gt;Modifications that impact global state, interactions with other systems, resource usage, dependencies, or introduce race conditions. These are particularly insidious because they affect things outside the immediate function call.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example:&lt;/em&gt; You optimize a data processing function to use async batch writes instead of synchronous single writes. The function still returns success, but now there's a delay before data appears in the database. Processes that immediately query for the data start failing intermittently.&lt;/p&gt;

&lt;p&gt;Now let's look at where these categories manifest across different layers of your stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Silent Breaking Changes Hide
&lt;/h2&gt;

&lt;p&gt;Silent breaking changes can lurk in any layer of your stack. Here are the most common culprits:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. API and Interface Changes
&lt;/h3&gt;

&lt;p&gt;Even when schemas appear unchanged, APIs can break consumers in surprising ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semantic shifts&lt;/strong&gt;: A field that used to mean "internal" or "external" suddenly gains new implications that require different client behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default behavior changes&lt;/strong&gt;: Switching from "replace entire list" to "merge" semantics on an update endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error model changes&lt;/strong&gt;: Altering HTTP status codes or error body formats breaks downstream error handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional → required fields&lt;/strong&gt;: Making previously optional fields required causes existing clients to fail validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering assumptions&lt;/strong&gt;: Changing default sort order, pagination, or filtering behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stricter validation&lt;/strong&gt;: Tightening rules to disallow characters that were previously accepted&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Database Schema Evolution
&lt;/h3&gt;

&lt;p&gt;Database changes are classic sources of silent breakage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type changes&lt;/strong&gt;: Converting an &lt;code&gt;int&lt;/code&gt; to &lt;code&gt;varchar&lt;/code&gt; or reinterpreting a status column's meaning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Renamed or dropped columns&lt;/strong&gt;: Queries may work initially through ORM caching, then fail under specific conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New constraints&lt;/strong&gt;: Adding &lt;code&gt;NOT NULL&lt;/code&gt;, unique, or foreign key constraints without aligning application code first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Big bang" migrations&lt;/strong&gt;: Attempting to change schema and code simultaneously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The industry-standard pattern to avoid this is &lt;strong&gt;expand-migrate-contract&lt;/strong&gt; (also called parallel change):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Expand&lt;/strong&gt;: Add new columns/tables alongside old ones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrate&lt;/strong&gt;: Dual-write, backfill data, gradually move reads to new schema&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract&lt;/strong&gt;: Remove old schema only after verifying no dependencies remain&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ensures backward compatibility at every step and makes rollbacks possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dependency Updates
&lt;/h3&gt;

&lt;p&gt;This one's particularly insidious. Research has found that roughly one-third of libraries claiming to follow semantic versioning introduced breaking changes in minor versions. For npm, studies running 75,913 test runs across 4,616 packages found at least 263 breaking changes from supposedly non-breaking dependency updates.&lt;br&gt;
I've seen this many times myself when bumping dependencies led to unexpected runtime errors in production, despite all tests passing. Or build failing, which is not silent, but still surprising when you expected a patch update to be safe.&lt;/p&gt;

&lt;p&gt;Common patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral changes&lt;/strong&gt;: Same method signature, different behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ABI incompatibility&lt;/strong&gt;: Binary layout or calling convention changes in C++/C libraries causing crashes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default configuration changes&lt;/strong&gt;: New defaults that alter runtime behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform shifts&lt;/strong&gt;: Upgrading runtime versions (JDK, Python, Node) that change edge case behaviors&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Microservices Contracts
&lt;/h3&gt;

&lt;p&gt;In distributed systems, contracts between services are a fertile ground for silent breakage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider shape changes&lt;/strong&gt;: Adding/removing fields or changing enum semantics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RPC drift&lt;/strong&gt;: Tightly coupled gRPC/IDL systems where stubs are regenerated but consumer code isn't updated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event/message evolution&lt;/strong&gt;: Changing message formats in queues without ensuring all consumers handle both versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side-effect changes&lt;/strong&gt;: Altering timeout behavior, retry logic, or cascading calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly why Consumer-Driven Contract testing (CDC) exists-to let consumers define their expectations and validate providers against them.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Machine Learning Models
&lt;/h3&gt;

&lt;p&gt;ML introduces a newer dimension of silent breaking changes that's often overlooked:&lt;/p&gt;

&lt;p&gt;A new model might have higher overall accuracy but introduce new errors in places where the old model was correct. If downstream systems were tuned around the old model's behavior, they can start failing unexpectedly.&lt;/p&gt;

&lt;p&gt;Researchers formalize this with metrics like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backward Trust Compatibility (BTC)&lt;/strong&gt;: Percentage of previously correct predictions that remain correct&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backward Error Compatibility (BEC)&lt;/strong&gt;: Fraction of new model's errors that overlap with old model's errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;High accuracy but low BTC/BEC is a silent breaking change for downstream systems that depend on your model.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Configuration and Feature Flags
&lt;/h3&gt;

&lt;p&gt;Operational changes can induce silent breakage without touching code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default flag flips&lt;/strong&gt;: Changing feature flag values that alter validation or filtering logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollout policy changes&lt;/strong&gt;: Routing traffic to new implementations without behavior parity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway configuration&lt;/strong&gt;: New rate limits, timeout settings, or routing rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are especially tricky because nothing about schemas, APIs, or binaries changes-only runtime behavior shifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Catch Silent Breaking Changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Static Diff-Based Checks
&lt;/h3&gt;

&lt;p&gt;Your first line of defense is comparing interfaces and schemas between versions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For REST/OpenAPI:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use tools like &lt;code&gt;oasdiff&lt;/code&gt; to compare OpenAPI definitions and classify changes by severity&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;oasdiff breaking&lt;/code&gt; in CI to fail builds on breaking changes&lt;/li&gt;
&lt;li&gt;Tools like Optic and Postman templates can enforce semantic diffs in your pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Protobuf/gRPC:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;buf breaking&lt;/code&gt; to ensure new &lt;code&gt;.proto&lt;/code&gt; definitions remain compatible&lt;/li&gt;
&lt;li&gt;Integrate with CI systems (GitHub Actions, GitLab, CircleCI) to catch issues in PRs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Language-specific checkers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt;: Tools like Modver diff modules and suggest version bumps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kotlin/Java&lt;/strong&gt;: Binary compatibility checkers compare public APIs between releases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These catch syntactic changes, but behavioral changes require testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Consumer-Driven Contract Testing
&lt;/h3&gt;

&lt;p&gt;CDC tools like Pact help ensure providers stay compatible with consumer expectations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Consumers write tests using a mock provider and generate a contract file&lt;/li&gt;
&lt;li&gt;Providers verify their implementation satisfies all published contracts&lt;/li&gt;
&lt;li&gt;If providers break any consumer's contract, CI fails&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This excels at catching silent breaking changes where the schema technically allows change, but consumers depend on specific behaviors.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Regression and Integration Testing
&lt;/h3&gt;

&lt;p&gt;Maintain regression suites that include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Critical use cases and negative paths&lt;/li&gt;
&lt;li&gt;Tests for data shape changes (database/schema)&lt;/li&gt;
&lt;li&gt;Integration points between services&lt;/li&gt;
&lt;li&gt;Dependency update validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One powerful technique: run your dependent packages' test suites across all minor/patch versions of dependencies. This is how researchers discovered those 263 breaking changes in npm. They ran tests and flagged when they went from passing to failing.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Runtime Monitoring
&lt;/h3&gt;

&lt;p&gt;Not all silent breaking changes can be caught pre-production. Mature teams use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API error monitoring&lt;/strong&gt;: Watch for spikes in 4xx/5xx rates by endpoint and client version&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business KPI tracking&lt;/strong&gt;: Monitor conversion rates, abandonment, and other metrics aligned to releases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic shadowing&lt;/strong&gt;: Route copies of production traffic to new versions and compare responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canary deployments&lt;/strong&gt;: Roll out to small percentages and watch for anomalies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For ML models specifically, measure BTC and BEC alongside accuracy to detect behavioral breaking changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The Human Element
&lt;/h3&gt;

&lt;p&gt;Don't underestimate thorough code reviews with explicit compatibility checklists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this change remove/rename any public API or field?&lt;/li&gt;
&lt;li&gt;Does it alter semantics, defaults, or error behavior?&lt;/li&gt;
&lt;li&gt;Are there documented consumers relying on the old contract?&lt;/li&gt;
&lt;li&gt;Have we tested with real consumer versions?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prevention: Building Compatibility Into Your Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Design-Time Compatibility Rules
&lt;/h3&gt;

&lt;p&gt;Define explicit rules for what's allowed within a major version:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generally safe (non-breaking):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding new optional fields or endpoints&lt;/li&gt;
&lt;li&gt;Adding enum values clients can ignore&lt;/li&gt;
&lt;li&gt;Adding optional query parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Forbidden (breaking within major version):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removing or renaming fields, methods, or endpoints&lt;/li&gt;
&lt;li&gt;Changing field types or wire formats&lt;/li&gt;
&lt;li&gt;Moving fields into different structures&lt;/li&gt;
&lt;li&gt;Making optional fields required&lt;/li&gt;
&lt;li&gt;Tightening validation that rejects previously valid inputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Document hidden contracts like ordering, error codes, and default behaviors that clients may depend on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Versioning Strategies
&lt;/h3&gt;

&lt;p&gt;While Semantic Versioning (SemVer) is popular, you need internal definitions that reflect reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MAJOR&lt;/strong&gt;: Incompatible API changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MINOR&lt;/strong&gt;: Backward-compatible new features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PATCH&lt;/strong&gt;: Backward-compatible bug fixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: Some bug fixes alter behavior that clients depend on. These might need to be treated as breaking.&lt;/p&gt;

&lt;p&gt;For microservices and APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use URI or header-based versioning (&lt;code&gt;/v1/...&lt;/code&gt;, &lt;code&gt;/v2/...&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Provide long deprecation windows&lt;/li&gt;
&lt;li&gt;Support parallel versions during migration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CI/CD Guardrails
&lt;/h3&gt;

&lt;p&gt;Automate compatibility checks in your pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;oasdiff breaking&lt;/code&gt; on OpenAPI specs&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;buf breaking&lt;/code&gt; on protobuf definitions&lt;/li&gt;
&lt;li&gt;Integrate CDC tests for critical microservice interactions&lt;/li&gt;
&lt;li&gt;Run regression tests with old client versions&lt;/li&gt;
&lt;li&gt;Execute dependent package test suites against new versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These should be release blockers, not warnings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation and Communication
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Release notes matter:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintain explicit backward-incompatible sections&lt;/li&gt;
&lt;li&gt;Document required actions for integrators&lt;/li&gt;
&lt;li&gt;Provide migration guides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deprecation policies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mark deprecated features clearly with timelines&lt;/li&gt;
&lt;li&gt;Provide tooling (lints, logs, warnings) to detect usage&lt;/li&gt;
&lt;li&gt;Give teams time to migrate before removal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Release candidate channels:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expose changes early so integrators can test&lt;/li&gt;
&lt;li&gt;Platforms like Shopify's REST Admin API do this well&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Organizational Patterns
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear ownership&lt;/strong&gt;: Assign accountability for APIs and schemas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture reviews&lt;/strong&gt;: Review high-risk changes before implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibility SLOs&lt;/strong&gt;: Make compatibility measurable (e.g., target BTC/BEC for ML models)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cultural values&lt;/strong&gt;: Make "no silent breaking changes" part of engineering principles&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Silent Breaking Changes Escape
&lt;/h2&gt;

&lt;p&gt;Even with strong prevention, some will reach production. Here's how to respond:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Detection and Triage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Correlate anomalies with recent changes&lt;/li&gt;
&lt;li&gt;Segment by client version or environment&lt;/li&gt;
&lt;li&gt;Reproduce using captured logs or traffic replay&lt;/li&gt;
&lt;li&gt;Run post-hoc API diffs to pinpoint contract divergence&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Containment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Roll back the deployment or flip feature flags&lt;/li&gt;
&lt;li&gt;Use circuit breakers to isolate failures&lt;/li&gt;
&lt;li&gt;Leverage canary/blue-green deployments to limit blast radius&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Remediation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fix or provide compatibility shims&lt;/li&gt;
&lt;li&gt;Notify impacted consumers with migration guides&lt;/li&gt;
&lt;li&gt;If SemVer promises were violated, treat it as a bug and release a fix&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Learn and Improve
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add regression tests for the broken scenario&lt;/li&gt;
&lt;li&gt;Strengthen contract tests or schema diffing&lt;/li&gt;
&lt;li&gt;Update compatibility guidelines and review checklists&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Practical Checklist
&lt;/h2&gt;

&lt;p&gt;Here's what I recommend to teams:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define what "breaking" means:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document per domain (APIs, DB, libraries, ML)&lt;/li&gt;
&lt;li&gt;Include behavioral aspects, not just types&lt;/li&gt;
&lt;li&gt;Make it accessible to all engineers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Build CI/CD guardrails:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API/schema diff tools on every change&lt;/li&gt;
&lt;li&gt;CDC tests for critical interactions&lt;/li&gt;
&lt;li&gt;Language-specific compatibility checkers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Apply safe evolution patterns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expand-migrate-contract for schema changes&lt;/li&gt;
&lt;li&gt;Additive-only API design&lt;/li&gt;
&lt;li&gt;Measure BTC/BEC for ML models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Monitor production:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error rates by endpoint and client version&lt;/li&gt;
&lt;li&gt;Business KPI dashboards aligned to releases&lt;/li&gt;
&lt;li&gt;Canary deployments with automatic rollback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Foster the right culture:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make compatibility a first-class concern&lt;/li&gt;
&lt;li&gt;Include compatibility reviews in design processes&lt;/li&gt;
&lt;li&gt;Reward teams that maintain strong compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Silent breaking changes are fundamentally about mismatched expectations between components over time. The more widely consumed your interfaces, the more costly these mismatches become.&lt;/p&gt;

&lt;p&gt;I've learned (sometimes the hard way) that treating compatibility as a managed engineering discipline rather than an afterthought is one of the highest-leverage investments you can make. It's not just about avoiding incidents: it's about maintaining trust with the teams and customers who depend on your systems.&lt;/p&gt;

&lt;p&gt;The good news? You don't have to implement everything at once. Start with the layer that's causing you the most pain, add some automated checks, and build from there. Your future self (and your on-call rotation) will thank you.&lt;/p&gt;

</description>
      <category>breaking</category>
      <category>changes</category>
      <category>silent</category>
      <category>concealed</category>
    </item>
    <item>
      <title>GraphQL in Your Ember App with Glimmer Apollo</title>
      <dc:creator>Eugene Yakhnenko</dc:creator>
      <pubDate>Tue, 05 Dec 2023 20:34:36 +0000</pubDate>
      <link>https://dev.to/eugenioenko/unleashing-graphql-in-your-ember-app-with-glimmer-apollo-3pee</link>
      <guid>https://dev.to/eugenioenko/unleashing-graphql-in-your-ember-app-with-glimmer-apollo-3pee</guid>
      <description>&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;Ember.js, a robust JavaScript framework for building ambitious web applications, has gained popularity for its convention over configuration approach and developer-friendly features. To enhance data fetching and management in Ember applications, integrating GraphQL is a powerful choice. In this article, we'll explore how to unleash the capabilities of GraphQL in your Ember app using &lt;a href="https://glimmer-apollo.com/"&gt;Glimmer Apollo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Starting new Ember Project
&lt;/h2&gt;

&lt;p&gt;We can create a new Ember project by using &lt;code&gt;ember-cli&lt;/code&gt;. If you don't have it already installed, its as simple as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn global add ember-cli
or 
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; ember-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once you have &lt;code&gt;ember-cli&lt;/code&gt; installed, to create a new Ember project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ember new my-project-name
&lt;span class="nb"&gt;cd &lt;/span&gt;my-project-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  2. Adding Typescript Support (optional)
&lt;/h2&gt;

&lt;p&gt;This step is not strictly necessary but without it, you won't get the benefit of type checking on your queries.&lt;br&gt;
Installing ember-cli-typescript allows to write ember component using typescript instead of javascript.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;--dev&lt;/span&gt; ember-cli-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  3. Installing Glimmer Apollo and Dependencies
&lt;/h2&gt;

&lt;p&gt;We will be using &lt;a href="https://glimmer-apollo.com/"&gt;Glimmer Apollo&lt;/a&gt; to integrate the Apollo Client. To install the necessary dependencies:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-dev&lt;/span&gt; glimmer-apollo @apollo/client graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Thats it, all the Glimmer Apollo dependencies are installed&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Configuring Apollo Client
&lt;/h2&gt;

&lt;p&gt;To be able to use Apollo Client, we need to configure it and create an instance of the client first. &lt;br&gt;
Create a script file in your project, in this example we are gonna be using &lt;code&gt;/app/config/apollo.ts&lt;/code&gt;&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="c1"&gt;// Import the setClient function from the "glimmer-apollo" package.&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;setClient&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;glimmer-apollo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Import necessary Apollo Client modules.&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;ApolloClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InMemoryCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createHttpLink&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;@apollo/client/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Define a function that sets up and configures the Apollo Client.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setupApolloClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create an HTTP link that points to the GraphQL server.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;httpLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHttpLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://graphqlzero.almansi.me/api&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;// Create an in-memory cache for Apollo Client to store data.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&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;InMemoryCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Create an instance of Apollo Client with the configured link and cache.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apolloClient&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;ApolloClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;httpLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Set the Apollo Client instance on the provided context object.&lt;/span&gt;
  &lt;span class="nf"&gt;setClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apolloClient&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 previous code imports the necessary modules from the "apollo/client/core" package, including the ApolloClient class, InMemoryCache, and createHttpLink function. The setupApolloClient function is then defined, which creates an HTTP link pointing to a GraphQL server (in this case, "&lt;a href="https://graphqlzero.almansi.me/api"&gt;https://graphqlzero.almansi.me/api&lt;/a&gt;" which is a mock graphql api), an in-memory cache for storing Apollo Client data, and finally, an instance of the Apollo Client with the configured link and cache. The created Apollo Client instance is then set on the provided context object using the setClient function from the "glimmer-apollo" package. &lt;/p&gt;

&lt;p&gt;Remember to update the &lt;code&gt;createHttpLink&lt;/code&gt; &lt;code&gt;uri&lt;/code&gt; parameter to the url of your Graphql api&lt;/p&gt;

&lt;p&gt;Finally, to  instantiate the previous initialization code, we can use Embers &lt;a href="https://guides.emberjs.com/release/applications/initializers/"&gt;Instance Initializer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Application instance initializers are run as an application instance is loaded. They provide a way to configure the initial state of your application, as well as to set up dependency injections that are local to the application instance.&lt;/p&gt;

&lt;p&gt;To create an Instance Initializer for our Apollo Client configuration you can execute:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ember generate instance-initializer apollo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or simple create a file &lt;code&gt;/app/instance-initializer/apollo.ts&lt;/code&gt;&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;setupApolloClient&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;../config/apollo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setupApolloClient&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;We set the &lt;code&gt;setupApolloClient&lt;/code&gt; function exported from previous step so that it is run once the application instance is loaded.&lt;/p&gt;

&lt;p&gt;And thats it, Glimmer Apollo is ready to be used!&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Adding 1st Graphql Query
&lt;/h2&gt;

&lt;p&gt;Lets create our first Graphql query and used in a component. We can do so by creating the following files.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app
└── components
    └── first-query
        ├── index.hbs
        ├── index.ts
        └── resources.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4.1 Defining Glimmer Apolllo Query in resources.ts
&lt;/h3&gt;

&lt;p&gt;The api we are using for this tutorial has quit a few queries defined, you can explore the schema here: &lt;a href="https://graphqlzero.almansi.me/api"&gt;https://graphqlzero.almansi.me/api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this tutorial we are gonna be fetching the a user name by his id. We can achieve that with the following query&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useUserByIdQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;UseQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;args&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;args&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;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return&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;return&lt;/span&gt; &lt;span class="nf"&gt;useQuery&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="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="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
      query UserById($id: ID!) {
        user(id: $id) {
          id
          name
        }
      }
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;args&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="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;There are three important parts to this code. &lt;/p&gt;

&lt;p&gt;The query section &lt;code&gt;query UserById($id: ID!) {&lt;/code&gt; where we define the query we want to execute. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;UseQuery&amp;lt;any, any&amp;gt;&lt;/code&gt; defines the type of the arguments the query receives and the type of data the query returns. For now  its &lt;code&gt;&amp;lt;any, any&amp;gt;&lt;/code&gt; but we will fix it in the step 5.&lt;/p&gt;
&lt;h3&gt;
  
  
  4.2 Using the Glimmer Apollo Query in our component class
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import Component from &lt;span class="s2"&gt;"@glimmer/component"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt; useUserByIdQuery &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s2"&gt;"./resources"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default class FirstQuery extends Component &lt;span class="o"&gt;{&lt;/span&gt;
  query &lt;span class="o"&gt;=&lt;/span&gt; useUserByIdQuery&lt;span class="o"&gt;(&lt;/span&gt;this, &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;({&lt;/span&gt;
    variables: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;id&lt;/span&gt;: 1,
    &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="o"&gt;}))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;useUserByIdQuery&lt;/code&gt; takes in two arguments. &lt;br&gt;
First is the context for the execution of the query. Usually this will be the instance of the component in which the query is used. Its required so that Glimmer Apollo can cleanup properly the resources after the component is destroyed.&lt;/p&gt;

&lt;p&gt;Second argument is a list of arguments to pass to the Apollo Client to modify how the query is executed. More &lt;a href="https://www.apollographql.com/docs/react/data/queries/"&gt;info here&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;We need to pass the Id of the user we want to fetch. We can do that by passing it as the &lt;code&gt;variables&lt;/code&gt; property of the arguments&lt;/p&gt;
&lt;h3&gt;
  
  
  4.3 Fetching the data in the template
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="k"&gt;{{#if&lt;/span&gt; &lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;loading&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
  loading...
&lt;span class="k"&gt;{{else&lt;/span&gt; &lt;span class="nv"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;{{else&lt;/span&gt; &lt;span class="nv"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;{{/if}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When we access the &lt;code&gt;loading&lt;/code&gt; or &lt;code&gt;data&lt;/code&gt; properties of the query, Glimmer Apollo will execute the query and fetch the data making it available in the &lt;code&gt;data&lt;/code&gt; property. And if there was an &lt;code&gt;error&lt;/code&gt; it will set the &lt;code&gt;error&lt;/code&gt; property of the query.&lt;/p&gt;

&lt;p&gt;Finally, to view this in action, use the &lt;code&gt;&amp;lt;FirstQuery /&amp;gt;&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;One thing to note is that the variables passed to the query are reactive. If the variable is a tracked variable, when its  value changes the query will execute again.&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;Component&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;@glimmer/component&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;tracked&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;@glimmer/tracking&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;useUserByIdQuery&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;./resources&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FirstQuery&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;tracked&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUserByIdQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&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="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 we change the value of &lt;code&gt;this.userId = 2&lt;/code&gt; the query will execute again and fetch the data for user with Id 2.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Adding Graphql Code Generator (optional)
&lt;/h2&gt;

&lt;p&gt;GraphQL Code Generator is a plugin-based tool that helps you get the best out of your GraphQL stack.&lt;/p&gt;

&lt;p&gt;From back-end to front-end, GraphQL Code Generator automates the generation of:&lt;/p&gt;

&lt;p&gt;Typed Queries, Mutations and, Subscriptions for React, Vue, Angular, Next.js, Svelte, Ember whether you are using Apollo Client, URQL or, React Query.&lt;br&gt;
Typed GraphQL resolvers, for any Node.js (GraphQL Yoga, GraphQL Modules, TypeGraphQL or Apollo) or Java GraphQL server.&lt;/p&gt;

&lt;p&gt;To add &lt;code&gt;graphql-codegen&lt;/code&gt; to our project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;--dev&lt;/span&gt; @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/named-operations-object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After the dependency is installed we need to initiate the code generator. This can be done by executing:&lt;/p&gt;

&lt;p&gt;Add the following config in the root of the project &lt;code&gt;codegen.yml&lt;/code&gt;&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;overwrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;afterAllFileWrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prettier --write&lt;/span&gt;
&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://graphqlzero.almansi.me/api'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;documents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;app/**/*.{graphql,ts}'&lt;/span&gt;
&lt;span class="na"&gt;generates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;types/graphqlzero-api/index.d.ts:&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;typescript&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;typescript-operations&lt;/span&gt;
  &lt;span class="na"&gt;app/utils/gql-operations.ts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;named-operations-object&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;identifierName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;namedOperations&lt;/span&gt;
  &lt;span class="na"&gt;tests/mocks/schema.graphql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;schema-ast&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The key properties of this config are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;schema&lt;/code&gt;: the url of the graphql-api schema&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;documents&lt;/code&gt;: the list of files that should be scanned for graphql queries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;plugins&lt;/code&gt;: sets the plugins used for the code generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you can generate the typescript types for your queries. And also a list of the queries and mutations by name which comes in handy for mock testing. To do that, execute &lt;code&gt;graphql-codegen&lt;/code&gt; by passing the config file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;graphql-codegen &lt;span class="nt"&gt;--config&lt;/span&gt; codegen.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also make it more accessible by adding this line in the scripts of &lt;code&gt;pacakge.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"codegen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"graphql-codegen --config codegen.yml"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After the graphql types are generated, we can now update our query to include the proper types. Lets revisit how the &lt;code&gt;resources.ts&lt;/code&gt; file looks like for our &lt;code&gt;FirstQuery&lt;/code&gt; component:&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;gql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useQuery&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;glimmer-apollo&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UseQuery&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;glimmer-apollo&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserByIdQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UserByIdQueryVariables&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;graphqlzero-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useUserByIdQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;UseQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserByIdQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UserByIdQueryVariables&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;args&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;args&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;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return&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;return&lt;/span&gt; &lt;span class="nf"&gt;useQuery&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="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="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
      query UserById($id: ID!) {
        user(id: $id) {
          id
          name
        }
      }
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;args&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="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 main difference is now we can use the proper type for our UseQuery &lt;code&gt;UseQuery&amp;lt;UserByIdQuery, UserByIdQueryVariables&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By integrating Glimmer Apollo into your Ember application, you can seamlessly incorporate GraphQL for efficient data fetching and management. This powerful combination enhances your development experience, providing flexibility and performance in handling complex data requirements.&lt;/p&gt;

&lt;p&gt;Copy of this project is available in &lt;a href="https://github.com/eugenioenko/ember-glimmer-apollo-starter"&gt;this repository&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag__replit"&gt;
  &lt;iframe height="550px" src="https://repl.it/@eugenioenko/ember-glimmer-apollo-starter?lite=true"&gt;&lt;/iframe&gt;
&lt;/div&gt;



</description>
      <category>ember</category>
      <category>graphql</category>
      <category>apollo</category>
      <category>glimmer</category>
    </item>
  </channel>
</rss>
