<?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: nyaomaru</title>
    <description>The latest articles on DEV Community by nyaomaru (@nyaomaru).</description>
    <link>https://dev.to/nyaomaru</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png</url>
      <title>DEV Community: nyaomaru</title>
      <link>https://dev.to/nyaomaru</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nyaomaru"/>
    <language>en</language>
    <item>
      <title>Is Learning DSA Boring? Let's Use DSA View View 👀👀 (Two Sum, Binary Search, and Bubble Sort)</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 19 Aug 2026 14:43:44 +0000</pubDate>
      <link>https://dev.to/nyaomaru/is-learning-dsa-boring-lets-use-dsa-view-view-two-sum-binary-search-and-bubble-sort-374o</link>
      <guid>https://dev.to/nyaomaru/is-learning-dsa-boring-lets-use-dsa-view-view-two-sum-binary-search-and-bubble-sort-374o</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I’m &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who dislikes crowded places, so I'm planning to take a quiet vacation in September. 🏝️&lt;/p&gt;

&lt;p&gt;Have you used &lt;strong&gt;DSA View View&lt;/strong&gt; already? 👀👀&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-story__hidden-navigation-link"&gt;I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View 👀👀)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Features a timeline to step backward mid-loop&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image" width="800" height="790"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
                
              
              &lt;div id="story-author-preview-content-4143027" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt="" width="800" height="790"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 15&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" id="article-link-4143027"&gt;
          I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View 👀👀)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dsa"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dsa&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;58&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              22&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;DSA View View allows you to understand DSA by visualizing how your implementation actually runs.&lt;/p&gt;

&lt;p&gt;But just introducing the tool is not enough.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can it actually help us understand DSA?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we'll walk through three classic problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two Sum&lt;/li&gt;
&lt;li&gt;Binary Search&lt;/li&gt;
&lt;li&gt;Bubble Sort&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll first understand the algorithm, and then see what is actually happening with &lt;strong&gt;DSA View View&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw6oxvyvuatcas2ogxgya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw6oxvyvuatcas2ogxgya.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's learn together! 😸&lt;/p&gt;




&lt;h2&gt;
  
  
  🗺️ Two Sum
&lt;/h2&gt;

&lt;p&gt;Let's start with a very famous problem.&lt;/p&gt;

&lt;p&gt;Given an array of numbers and a target value, find the indices of two numbers whose sum equals the target.&lt;/p&gt;

&lt;p&gt;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="nx"&gt;nums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The answer is&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="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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 + 7 = 9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Simple!&lt;/p&gt;

&lt;p&gt;So, how should we find them? 🤔&lt;/p&gt;
&lt;h3&gt;
  
  
  Brute Force
&lt;/h3&gt;

&lt;p&gt;The easiest approach is probably checking every possible pair.&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;twoSum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&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="nx"&gt;target&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&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;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;target&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;j&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This works.&lt;/p&gt;

&lt;p&gt;But if the array becomes large, we may need to compare a lot of pairs, right?&lt;/p&gt;

&lt;p&gt;The time complexity is &lt;em&gt;O(n²)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Can we avoid checking the same values again and again?&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Let's use a &lt;code&gt;Map&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;function&lt;/span&gt; &lt;span class="nf"&gt;twoSum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&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="nx"&gt;target&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="kr"&gt;number&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;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&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;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="kd"&gt;const&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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;need&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&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="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;need&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;seen&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;need&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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="nx"&gt;i&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important part is this 👇&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;need&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Instead of asking&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which two numbers should I combine?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;we ask&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What number do I need to complete the target?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's follow the example.&lt;/p&gt;

&lt;p&gt;At first&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current = 2
target = 9

need = 9 - 2
     = 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Have we already seen &lt;code&gt;7&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;So we remember &lt;code&gt;2&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seen = {
  2 → 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current = 7
target = 9

need = 9 - 7
     = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Have we already seen &lt;code&gt;2&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Yes! 👀👀&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seen = {
  2 → 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So&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;return&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Done!&lt;/p&gt;

&lt;p&gt;Because we only need to walk through the array once, the time complexity becomes.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time:  O(n)
Space: O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  👀 Let's View View It
&lt;/h3&gt;

&lt;p&gt;The implementation is quite small.&lt;/p&gt;

&lt;p&gt;But when I was first learning this pattern, this part still felt a little magical.&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;need&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Where did &lt;code&gt;need&lt;/code&gt; come from?&lt;/li&gt;
&lt;li&gt;What is inside &lt;code&gt;seen&lt;/code&gt; at this moment?&lt;/li&gt;
&lt;li&gt;Why does checking the previous values solve the problem?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly where visualization helps.&lt;/p&gt;

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


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://dsa-view-view.vercel.app/#s=j.eyJlIjoidHdvLXN1bSIsImwiOiJ0eXBlc2NyaXB0IiwibSI6InZlcmlmaWNhdGlvbiIsInYiOjF9" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;dsa-view-view.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;With DSA View View, we can move through the runtime one step at a time and inspect how the values change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2
↓
Need 7
↓
Remember 2
↓
7
↓
Need 2
↓
Found 2!
🎉
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now the &lt;code&gt;Map&lt;/code&gt; is not just some mysterious trick.&lt;/p&gt;

&lt;p&gt;We can actually follow the idea.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember what we have already seen, and check whether the value we need is there.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nice! 😸&lt;/p&gt;


&lt;h2&gt;
  
  
  🔍 Binary Search
&lt;/h2&gt;

&lt;p&gt;Next is &lt;strong&gt;Binary Search&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suppose we have this sorted array&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 3, 5, 7, 9, 11, 13]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And we want to find&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Of course, we could start from &lt;code&gt;1&lt;/code&gt; and check every number.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 → 3 → 5 → 7 → 9 → 11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That works.&lt;/p&gt;

&lt;p&gt;But Binary Search does something smarter.&lt;/p&gt;

&lt;p&gt;Instead of checking from the beginning, it checks the &lt;strong&gt;middle&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 3, 5, 7, 9, 11, 13]
          ↑
         mid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Our middle value is &lt;code&gt;7&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We are looking for &lt;code&gt;11&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11 &amp;gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because the array is sorted, we already know something very useful.&lt;/p&gt;

&lt;p&gt;Everything on the left side of &lt;code&gt;7&lt;/code&gt; is also smaller than &lt;code&gt;11&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, we don't need that half anymore. 👋&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 3, 5, 7, 9, 11, 13]
             └───────┘
               search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we check the middle of the remaining range.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[9, 11, 13]
     ↑
    mid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11 === 11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Found it! 🎉&lt;/p&gt;

&lt;p&gt;Here is the implementation.&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;binarySearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&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="nx"&gt;target&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="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;right&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;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;target&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;mid&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&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="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="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&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="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;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There are three important variables.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;left
right
mid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;They represent the current search range.&lt;/p&gt;

&lt;p&gt;For our example, they start like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;left = 0
right = 6
mid = 3

[1, 3, 5, 7, 9, 11, 13]
 ↑        ↑          ↑
left     mid       right
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because&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="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;we move &lt;code&gt;left&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="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 3, 5, 7, 9, 11, 13]
             ↑   ↑   ↑
           left mid right
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And &lt;code&gt;11&lt;/code&gt; is found.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Is Binary Search Fast?
&lt;/h3&gt;

&lt;p&gt;This is the interesting part.&lt;/p&gt;

&lt;p&gt;Each step removes about &lt;strong&gt;half of the remaining candidates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If there are 1,000 values, we don't necessarily need 1,000 checks.&lt;/p&gt;

&lt;p&gt;It becomes roughly&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1000
↓
500
↓
250
↓
125
↓
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's why Binary Search has&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time:  O(log n)
Space: O(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But there is one very important condition.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The data must be sorted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without sorted data, we cannot safely throw away half of the search range.&lt;/p&gt;
&lt;h3&gt;
  
  
  👀 Let's View View It
&lt;/h3&gt;

&lt;p&gt;Binary Search is one of the algorithms that made me want a visualization tool in the first place.&lt;/p&gt;

&lt;p&gt;The code itself is short&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="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;or&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="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Easy.&lt;/p&gt;

&lt;p&gt;But when learning it, I sometimes found myself thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait... which part are we searching now? 😿&lt;/p&gt;
&lt;/blockquote&gt;

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


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://dsa-view-view.vercel.app/#s=j.eyJlIjoiYmluYXJ5LXNlYXJjaCIsImwiOiJ0eXBlc2NyaXB0IiwibSI6InZlcmlmaWNhdGlvbiIsInYiOjF9" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;dsa-view-view.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;When we visualize &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;mid&lt;/code&gt;, and &lt;code&gt;right&lt;/code&gt;, the idea becomes much easier to follow.&lt;/p&gt;

&lt;p&gt;We are not randomly changing three numbers.&lt;/p&gt;

&lt;p&gt;We are continuously shrinking the search area.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;███████████████

       ↓

        ███████

       ↓

          ███

       ↓

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

&lt;/div&gt;


&lt;p&gt;That's Binary Search!&lt;/p&gt;

&lt;p&gt;Cut the unnecessary half.&lt;/p&gt;

&lt;p&gt;Then cut it again. And again. And again.&lt;/p&gt;

&lt;p&gt;Until we find the answer. ✂️😸&lt;/p&gt;


&lt;h2&gt;
  
  
  🫧 Bubble Sort
&lt;/h2&gt;

&lt;p&gt;Finally, let's sort something!&lt;/p&gt;

&lt;p&gt;Consider this array.&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="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We want.&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="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Bubble Sort repeatedly compares &lt;strong&gt;two neighboring values&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If they are in the wrong order, it swaps them.&lt;/p&gt;

&lt;p&gt;Let's look at the beginning.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[5, 1, 4, 2, 8]
 ↑  ↑
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Compare&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 &amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So swap them.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 5, 4, 2, 8]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 5, 4, 2, 8]
    ↑  ↑
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Again&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 &amp;gt; 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Swap!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 4, 5, 2, 8]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And continue.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 4, 5, 2, 8]
       ↑  ↑

5 &amp;gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Swap!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 4, 2, 5, 8]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Eventually, larger values move toward the end of the array.&lt;/p&gt;

&lt;p&gt;They kind of...&lt;/p&gt;

&lt;p&gt;bubble up. 🫧&lt;/p&gt;

&lt;p&gt;That's why it is called &lt;strong&gt;Bubble Sort&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a simple implementation:&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;bubbleSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;i&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&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;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;i&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;j&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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="nx"&gt;nums&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 repeatedly compare&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="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And&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="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and swap them when necessary.&lt;/p&gt;

&lt;p&gt;After one full pass, the largest remaining value reaches its correct position near the end.&lt;/p&gt;

&lt;p&gt;So on the next pass, we don't need to check that position again.&lt;/p&gt;

&lt;p&gt;That's why the inner loop contains.&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="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;i&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Complexity
&lt;/h3&gt;

&lt;p&gt;Bubble Sort isn't very fast for large arrays.&lt;/p&gt;

&lt;p&gt;Its time complexity is&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time:  O(n²)
Space: O(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So I probably won't suddenly replace production sorting with Bubble Sort tomorrow. 😸&lt;/p&gt;

&lt;p&gt;But as a learning example, I really like it.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because you can &lt;strong&gt;see the algorithm working&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  👀 Let's View View It
&lt;/h3&gt;

&lt;p&gt;This is probably the most visually satisfying one of the three.&lt;/p&gt;

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


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://dsa-view-view.vercel.app/#s=j.eyJlIjoiYnViYmxlLXNvcnQiLCJsIjoidHlwZXNjcmlwdCIsIm0iOiJ2ZXJpZmljYXRpb24iLCJ2IjoxfQ" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;dsa-view-view.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Instead of only reading&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;we can follow the values moving through the array.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[5, 1, 4, 2, 8]

 ↓ swap

[1, 5, 4, 2, 8]

    ↓ swap

[1, 4, 5, 2, 8]

       ↓ swap

[1, 4, 2, 5, 8]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then another pass begins.&lt;/p&gt;

&lt;p&gt;The code contains nested loops, indexes, comparisons, and swaps.&lt;/p&gt;

&lt;p&gt;But visually, the basic rule is extremely simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compare neighbors. If the left one is bigger, swap them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Repeat. Repeat. Repeat.&lt;/p&gt;

&lt;p&gt;Sorted! 🎉&lt;/p&gt;


&lt;h2&gt;
  
  
  🧠 What Did We Actually Learn?
&lt;/h2&gt;

&lt;p&gt;These three problems look quite different.&lt;/p&gt;

&lt;p&gt;But each one introduces a useful way of thinking.&lt;/p&gt;
&lt;h3&gt;
  
  
  Two Sum
&lt;/h3&gt;

&lt;p&gt;Remember information from previous steps.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Have I already seen what I need?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Binary Search
&lt;/h3&gt;

&lt;p&gt;Use what we already know to remove impossible candidates.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can I safely discard half of the search space?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Bubble Sort
&lt;/h3&gt;

&lt;p&gt;Break a larger problem into many small comparisons.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Are these two values in the correct order?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is one of the things I find interesting about learning DSA.&lt;/p&gt;

&lt;p&gt;At first, the implementation can look like a collection of indexes, loops, conditions, and mysterious variables.&lt;/p&gt;

&lt;p&gt;But behind the code, there is usually a much simpler idea.&lt;/p&gt;

&lt;p&gt;And sometimes I don't fully understand that idea just by staring at the code.&lt;/p&gt;

&lt;p&gt;I want to &lt;strong&gt;view it&lt;/strong&gt;. 👀👀&lt;/p&gt;


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

&lt;p&gt;In this article, we looked at three classic algorithms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two Sum with a &lt;code&gt;Map&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Binary Search&lt;/li&gt;
&lt;li&gt;Bubble Sort&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more importantly, we looked at &lt;strong&gt;how the data changes while they run&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I think this is where visualization can be especially useful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading the final implementation tells us &lt;strong&gt;what the code is&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Stepping through it helps us understand &lt;strong&gt;why it works&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's exactly why I built DSA View View.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://dsa-view-view.vercel.app" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;dsa-view-view.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;You can write or load a TypeScript implementation, run it with your own inputs, and move backward and forward through the runtime.&lt;/p&gt;

&lt;p&gt;If you are learning DSA too, try taking a problem you already solved and viewing it step by step.&lt;/p&gt;

&lt;p&gt;You may notice something you didn't notice when only reading the code. 👀&lt;/p&gt;

&lt;p&gt;And if there is a DSA problem you want me to cover next, please let me know in the comments!&lt;/p&gt;

&lt;p&gt;I still have many algorithms to learn myself. 😸&lt;/p&gt;

&lt;p&gt;Let's train our DSA muscles together! 💪&lt;/p&gt;

&lt;p&gt;If you like DSA View View, please give it a star ⭐&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;
        dsa-view-view
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DSA View View allows you to understand DSA to see the data flow. 👀👀 Of course, it's free.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nyaomaru/dsa-view-view/./public/readme-view-view.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Freadme-view-view.png" alt="DSA View View" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DSA View View&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;DSA View View turns TypeScript algorithm functions into step-by-step visual
stories. 👀👀&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-tv.gif" alt="DSA View View TV"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Write code, run it with structured inputs, and see the arrays, matrices
trees, lists, stacks, pointers, and return values move as the function executes.&lt;/p&gt;
&lt;p&gt;It is built for those moments when reading the code is not enough and you want
to &lt;em&gt;view&lt;/em&gt; why the answer changes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-demo.gif" alt="DSA View View demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Try It?&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;🧠 &lt;strong&gt;Step through real TypeScript&lt;/strong&gt;&lt;br&gt;
Paste or edit a function, validate it, then run the exact code in the browser.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🧩 &lt;strong&gt;Views that match the data&lt;/strong&gt;&lt;br&gt;
Arrays become bars, matrices become grids, trees become node graphs, linked lists become chains, and two-pointer area problems get their own visual view.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🌳 &lt;strong&gt;DSA-friendly inputs out of the box&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;TreeNode&lt;/code&gt;, &lt;code&gt;ListNode&lt;/code&gt;, &lt;code&gt;MinHeap&lt;/code&gt;, &lt;code&gt;MaxHeap&lt;/code&gt;, &lt;code&gt;PriorityQueue&lt;/code&gt;, nested arrays
matrices, strings, numbers, and class-style inputs are supported without
ceremony.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🔎 &lt;strong&gt;39 built-in examples&lt;/strong&gt;&lt;br&gt;
Search by name, browse…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;And my DSA View View has launched at TinyLaunch! 🚀 Please take a loot 👇&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://www.tinylaunch.com/launch/17804-dsa-view-view" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;tinylaunch.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;See you in the next article!&lt;/p&gt;

</description>
      <category>dsa</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>learning</category>
    </item>
    <item>
      <title>is-kit Reached 50 Stars ⭐ Here’s How We Use It in Production</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 12 Aug 2026 13:16:48 +0000</pubDate>
      <link>https://dev.to/nyaomaru/is-kit-reached-50-stars-heres-how-we-use-it-in-production-2i5b</link>
      <guid>https://dev.to/nyaomaru/is-kit-reached-50-stars-heres-how-we-use-it-in-production-2i5b</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who is trying to lose weight. 🐖🙀&lt;/p&gt;

&lt;p&gt;I maintain a type guard library, &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I spend an unreasonable amount of time asking&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“But what if this value is actually &lt;code&gt;unknown&lt;/code&gt;???”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recently, &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt; crossed &lt;strong&gt;50 GitHub stars&lt;/strong&gt; 🎉🎉🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomyd3e9bikp2q5l493gs.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A star is not a benchmark. And 50 stars do not suddenly make a library production-ready.&lt;/p&gt;

&lt;p&gt;But each one still means&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Someone found this idea useful.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That makes me very happy!! Each star gives me more motivation to keep improving the library!!!!&lt;/p&gt;

&lt;p&gt;There is also something more concrete I want to share&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is now used in a production TypeScript application serving more than 100,000 users. 🚀 &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article explains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What problem we had&lt;/li&gt;
&lt;li&gt;How we introduced &lt;code&gt;is-kit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;What actually changed&lt;/li&gt;
&lt;li&gt;Where it is used today&lt;/li&gt;
&lt;li&gt;What its practical advantages are&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 The Problem Was Not “Validation”
&lt;/h2&gt;

&lt;p&gt;The application already had many small checks 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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&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="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="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It also had user-defined type guards for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP client errors&lt;/li&gt;
&lt;li&gt;Status codes&lt;/li&gt;
&lt;li&gt;Literal unions&lt;/li&gt;
&lt;li&gt;Arrays&lt;/li&gt;
&lt;li&gt;Plain objects&lt;/li&gt;
&lt;li&gt;Values coming from JSON or API responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each check was reasonable by itself.&lt;/p&gt;

&lt;p&gt;The problem appeared when they started repeating.&lt;/p&gt;

&lt;p&gt;For example, several error guards had almost the same structure.&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;type&lt;/span&gt; &lt;span class="nx"&gt;HttpClientError&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;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;isHttpClientError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;response&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;data&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="p"&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;isUnauthorizedError&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;unknown&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;is&lt;/span&gt; &lt;span class="nx"&gt;HttpClientError&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;!!&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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="nx"&gt;HttpClientError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;isHttpClientError&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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="nx"&gt;HttpClientError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
  &lt;span class="p"&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;isValidationError&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;unknown&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;is&lt;/span&gt; &lt;span class="nx"&gt;HttpClientError&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;!!&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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="nx"&gt;HttpClientError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;isHttpClientError&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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="nx"&gt;HttpClientError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;422&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 works.&lt;/p&gt;

&lt;p&gt;But it has three practical problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The same base check is repeated&lt;/li&gt;
&lt;li&gt;Assertion casts appear inside every guard&lt;/li&gt;
&lt;li&gt;Adding another status means adding another copy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code was not broken.&lt;/p&gt;

&lt;p&gt;It was simply asking for a reusable abstraction. 🔧&lt;/p&gt;


&lt;h2&gt;
  
  
  🏃‍♂️ The Production Pattern
&lt;/h2&gt;

&lt;p&gt;We replaced the repeated checks with small composable guards.&lt;/p&gt;

&lt;p&gt;Here is a business-neutral version of the production pattern.&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;define&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;equalsKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;or&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;is-kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;HttpClientError&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;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;isHttpClientError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;code&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="nl"&gt;response&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;data&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="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;isHttpClientError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HttpClientError&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;value&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;equalsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isHttpClientError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;value&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;isHttpErrorWithStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;define&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HttpClientError&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;span class="nx"&gt;value&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;isHttpClientError&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="o"&gt;&amp;amp;&amp;amp;&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;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&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;isUnauthorizedError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isHttpErrorWithStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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;isValidationError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isHttpErrorWithStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasTimeoutCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HttpClientError&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;span class="nx"&gt;value&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;isHttpClientError&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="o"&gt;&amp;amp;&amp;amp;&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;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TIMEOUT&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;hasTimeoutMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HttpClientError&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;span class="nx"&gt;value&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;isHttpClientError&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="o"&gt;&amp;amp;&amp;amp;&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timed out&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isTimeoutError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasTimeoutCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasTimeoutMessage&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 a few important details here.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;define&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;define&amp;lt;T&amp;gt;&lt;/code&gt; turns a runtime boolean check into a reusable predicate.&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;isHttpErrorWithStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;define&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HttpClientError&lt;/span&gt;&lt;span class="o"&gt;&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;The responsibility is still ours. The runtime check must actually prove &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; cannot make an incorrect predicate correct.&lt;/p&gt;

&lt;p&gt;But it gives custom guards one consistent shape.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;equalsKey&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The base error is not plain JSON.&lt;/p&gt;

&lt;p&gt;It is an error instance with a marker property.&lt;/p&gt;

&lt;p&gt;So a plain-object schema is not the right abstraction here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;equalsKey("isHttpClientError", true)&lt;/code&gt; expresses exactly what we need,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This value owns this key, and its value is exactly &lt;code&gt;true&lt;/code&gt;.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;or&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A timeout can be detected in more than one way.&lt;/p&gt;

&lt;p&gt;Instead of creating another large conditional, we compose two reusable guards.&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;isTimeoutError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasTimeoutCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasTimeoutMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is the core idea of &lt;code&gt;is-kit&lt;/code&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build small guards, then compose them.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  ✨ What Actually Changed
&lt;/h2&gt;

&lt;p&gt;The first adoption refactor was not just 👇&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add is-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It changed the structure of the guard layer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observable result&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Error guards&lt;/td&gt;
&lt;td&gt;7 separate modules became 1 shared module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adoption diff&lt;/td&gt;
&lt;td&gt;335 lines added, 584 removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Net diff&lt;/td&gt;
&lt;td&gt;249 fewer lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct imports today&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;is-kit&lt;/code&gt; is isolated to 7 app helper modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App reach today&lt;/td&gt;
&lt;td&gt;Those helpers are consumed by 39 non-test source files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The diff includes rewritten tests and helper adapters, so &lt;strong&gt;249 fewer lines is not a claim that a library magically deletes code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is the measured result of that specific consolidation.&lt;/p&gt;

&lt;p&gt;The more important change is the shape.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;is-kit primitives
       ↓
app guard helpers
       ↓
features, routes, services, and UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The production application does not import &lt;code&gt;is-kit&lt;/code&gt; from every component.&lt;/p&gt;

&lt;p&gt;Instead, most call sites use application-owned helpers.&lt;/p&gt;


&lt;h2&gt;
  
  
  🤔 Why Keep an Application Boundary?
&lt;/h2&gt;

&lt;p&gt;For primitives, the application wraps or re-exports the library guards 👇&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;isNumber&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;isFiniteNumberGuard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isNumberPrimitive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isString&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;isStringGuard&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;is-kit&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;isString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isStringGuard&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;isNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isNumberPrimitive&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;isFiniteNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isFiniteNumberGuard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This looks like a small detail, but it is an important design choice.&lt;/p&gt;

&lt;p&gt;JavaScript has more than one useful meaning for “number”.&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;typeof&lt;/span&gt; &lt;span class="kc"&gt;NaN&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="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="kc"&gt;Infinity&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isNumber&lt;/code&gt; follows primitive &lt;code&gt;typeof&lt;/code&gt; semantics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isFiniteNumber&lt;/code&gt; rejects &lt;code&gt;NaN&lt;/code&gt; and &lt;code&gt;Infinity&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application owns those names. &lt;code&gt;is-kit&lt;/code&gt; provides the reusable implementation.&lt;/p&gt;

&lt;p&gt;This boundary also means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call sites do not depend on library naming decisions&lt;/li&gt;
&lt;li&gt;Semantics stay consistent across the app&lt;/li&gt;
&lt;li&gt;A future migration has one clear place to start&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how I prefer to introduce small libraries into large applications.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Adopt them behind a local vocabulary.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  😎 Other Real Usage Patterns
&lt;/h2&gt;

&lt;p&gt;The HTTP error guards are the largest example, but not the only one.&lt;/p&gt;
&lt;h3&gt;
  
  
  Arrays
&lt;/h3&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;arrayOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isNumberPrimitive&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;is-kit&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;isNumberArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;arrayOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNumberPrimitive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This replaces&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;isNumberArray&lt;/span&gt; &lt;span class="o"&gt;=&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;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;item&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Literal unions
&lt;/h3&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;oneOfValues&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;is-kit&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;VIEW_MODES&lt;/span&gt; &lt;span class="o"&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;compact&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;comfortable&lt;/span&gt;&lt;span class="dl"&gt;"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isViewMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;oneOfValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VIEW_MODES&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&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;unknown&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;isViewMode&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// "compact" | "comfortable"&lt;/span&gt;
  &lt;span class="nx"&gt;input&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;
  
  
  Nullish values
&lt;/h3&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;isNull&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isUndefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;or&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;is-kit&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;isNullish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNull&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isUndefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because this is a function, it can be reused directly.&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;definedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNullish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The current application uses the same idea for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error branching&lt;/li&gt;
&lt;li&gt;JSON and API-derived values&lt;/li&gt;
&lt;li&gt;Filtering nullable collections&lt;/li&gt;
&lt;li&gt;Literal-value checks&lt;/li&gt;
&lt;li&gt;UI values that may be strings or other renderable values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what production usage looks like in practice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;not one giant schema,&lt;br&gt;
but many small decisions at normal control-flow points.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🚀 The Practical Advantages
&lt;/h2&gt;

&lt;p&gt;After using it in the application, the advantages became clearer.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Incremental adoption
&lt;/h3&gt;

&lt;p&gt;We did not need to redesign the data layer.&lt;/p&gt;

&lt;p&gt;A check 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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;can become&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="nf"&gt;isString&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And later, if reuse becomes useful.&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="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Less assertion casting
&lt;/h3&gt;

&lt;p&gt;The old error guards repeatedly used&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="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HttpClientError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The composed version narrows once, then accesses the narrowed value normally&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="nf"&gt;isHttpClientError&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="o"&gt;&amp;amp;&amp;amp;&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;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Shared runtime semantics
&lt;/h3&gt;

&lt;p&gt;Questions like these now have explicit answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does “number” include &lt;code&gt;NaN&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does this object check accept class instances?&lt;/li&gt;
&lt;li&gt;Is this field optional, nullable, or both?&lt;/li&gt;
&lt;li&gt;Are two values compared with &lt;code&gt;===&lt;/code&gt; or &lt;code&gt;Object.is&lt;/code&gt; semantics?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benefit is not shorter syntax alone.&lt;/p&gt;

&lt;p&gt;It is fewer slightly-different answers across the codebase.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Normal TypeScript control flow
&lt;/h3&gt;

&lt;p&gt;The result is still a function.&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isValidationError&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;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;No parse result is required.&lt;/p&gt;

&lt;p&gt;No schema object has to travel through the application.&lt;/p&gt;

&lt;p&gt;That makes the guards easy to use in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;event handlers&lt;/li&gt;
&lt;li&gt;error boundaries&lt;/li&gt;
&lt;li&gt;utility functions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  5. Small dependency surface
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; has no runtime dependencies.&lt;/p&gt;

&lt;p&gt;That does not mean it has zero bundle cost.&lt;/p&gt;

&lt;p&gt;It means introducing it does not bring a tree of transitive runtime packages with it.&lt;/p&gt;


&lt;h2&gt;
  
  
  👮 It Became a Team Rule
&lt;/h2&gt;

&lt;p&gt;One sign of real adoption is that the library moved beyond individual preference.&lt;/p&gt;

&lt;p&gt;The production repository now has a contributor rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When combining &lt;code&gt;is-kit&lt;/code&gt; guards,&lt;br&gt;
prefer &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;andAll&lt;/code&gt;, &lt;code&gt;nullish&lt;/code&gt;, and related combinators&lt;br&gt;
instead of rebuilding the same composition with native operators.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isTextOrNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isNumberPrimitive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;instead of,&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;isTextOrNumber&lt;/span&gt; &lt;span class="o"&gt;=&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;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="nf"&gt;isString&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="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isNumberPrimitive&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Both can return the same boolean.&lt;/p&gt;

&lt;p&gt;But the first version is a named, reusable guard that can be passed around and composed again.&lt;/p&gt;

&lt;p&gt;This rule is also used by coding agents working in the repository.&lt;/p&gt;

&lt;p&gt;That matters because a tool is not truly adopted if every contributor, human or AI, invents a different style.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✖️ What We Cannot Claim
&lt;/h2&gt;

&lt;p&gt;I want to be careful here.&lt;/p&gt;

&lt;p&gt;We did not run a controlled study showing that &lt;code&gt;is-kit&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improved runtime performance&lt;/li&gt;
&lt;li&gt;Reduced production incidents&lt;/li&gt;
&lt;li&gt;Made every validation task easier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I will not claim those things.&lt;/p&gt;

&lt;p&gt;The effects we can actually see are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repeated guards were consolidated&lt;/li&gt;
&lt;li&gt;Assertion heavy checks became composable predicates&lt;/li&gt;
&lt;li&gt;Primitive semantics became centralized&lt;/li&gt;
&lt;li&gt;Application code gained reusable narrowing functions&lt;/li&gt;
&lt;li&gt;The pattern became part of the repository guidelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is primarily a &lt;strong&gt;maintainability and type-safety improvement&lt;/strong&gt;. 🏋️‍♂️&lt;/p&gt;


&lt;h2&gt;
  
  
  👀 Why Not Use a Schema Library?
&lt;/h2&gt;

&lt;p&gt;For these call sites, we did not need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rich validation error trees&lt;/li&gt;
&lt;li&gt;Data transformations&lt;/li&gt;
&lt;li&gt;A schema-first model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We needed&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Can this &lt;code&gt;unknown&lt;/code&gt; value safely enter this branch?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is exactly where a type guard fits.&lt;/p&gt;

&lt;p&gt;For forms, API contracts, or detailed validation errors, a schema library such as &lt;code&gt;Zod&lt;/code&gt; may still be the better tool.&lt;/p&gt;

&lt;p&gt;They solve different problems.&lt;/p&gt;


&lt;h2&gt;
  
  
  🎯 What 50 Stars Means to Me
&lt;/h2&gt;

&lt;p&gt;50 stars is small compared with the largest TypeScript libraries.&lt;/p&gt;

&lt;p&gt;But OSS does not become meaningful only after thousands of stars.&lt;/p&gt;

&lt;p&gt;For me, this milestone means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;People outside the project understand the idea&lt;/li&gt;
&lt;li&gt;The API is useful beyond a toy example&lt;/li&gt;
&lt;li&gt;The library is solving a real maintenance problem&lt;/li&gt;
&lt;li&gt;There is still a lot to improve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the production application gives the milestone some weight.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is not only being starred.&lt;/p&gt;

&lt;p&gt;It is currently helping real application code answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“What is this value, and can TypeScript trust it?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thank you to everyone who starred, tested, reported an issue, or simply looked at the repository.&lt;/p&gt;

&lt;p&gt;If small composable type guards fit your TypeScript style, give it a try 👇&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;
        is-kit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Build small guards. Compose them. Lightweight, zero-dependency TypeScript type guards for runtime validation and natural narrowing. Runtime-safe 🛡️, composable 🧩, and ergonomic ✨.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;is-kit&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/is-kit/main/docs/public/iskit_image.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fis-kit%2Fmain%2Fdocs%2Fpublic%2Fiskit_image.png" width="600" alt="is-kit logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://www.npmjs.com/package/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/cb829c46f855a6f184f255391ea5fe58bb6ed17112e34d4856acd61dc63179c2/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f69732d6b69742e737667" alt="npm version"&gt;
  &lt;/a&gt;
  &lt;a href="https://jsr.io/@nyaomaru/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/b6648f6bf32eb9dff2136ce8538ccb4cc6af537c1127292b5fb0de416f7a69b4/68747470733a2f2f696d672e736869656c64732e696f2f6a73722f762f406e79616f6d6172752f69732d6b6974" alt="JSR"&gt;
  &lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/9b30eeaac46ebced99b5e0dc290fd045bc34c738c66b5d95c4b6ee8fed358b34/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f69732d6b69742e737667" alt="npm downloads"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/nyaomaru/is-kit/blob/main/LICENSE" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/631cdf8c56ad84dd8657fe3371b93015b5474c517422f968ff4ec944bb28bf2f/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f69732d6b69742e7376673f73616e6974697a653d74727565" alt="License"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Build small guards. Compose them.&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a lightweight, zero-dependency toolkit for building reusable TypeScript &lt;strong&gt;type guards&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It helps you write small &lt;code&gt;isFoo&lt;/code&gt; functions, compose them into &lt;strong&gt;richer runtime checks&lt;/strong&gt;, and keep &lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime-safe&lt;/strong&gt; 🛡️, &lt;strong&gt;composable&lt;/strong&gt; 🧩, and &lt;strong&gt;ergonomic&lt;/strong&gt; ✨ without asking you to adopt a heavy schema workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build and reuse &lt;strong&gt;typed guards&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compose guards&lt;/strong&gt; with &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;not&lt;/code&gt;, &lt;code&gt;oneOf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse or assert&lt;/strong&gt; &lt;code&gt;unknown&lt;/code&gt; values without a large schema framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://is-kit-docs.vercel.app/" rel="nofollow noopener noreferrer"&gt;📚 Documentation Site&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Best for &lt;strong&gt;app-internal narrowing, filtering, and reusable guards&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🤔 Why use &lt;code&gt;is-kit&lt;/code&gt;?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Tired of rewriting the same &lt;code&gt;isFoo&lt;/code&gt; checks again and again?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a good fit when you want to:&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;write reusable &lt;code&gt;isX&lt;/code&gt;&lt;/strong&gt; functions instead of one-off inline checks&lt;/li&gt;

&lt;li&gt;keep runtime validation &lt;strong&gt;lightweight and dependency-free&lt;/strong&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;narrow values directly&lt;/strong&gt; in &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and other TypeScript…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Celebrating 10,000 Total Views on DEV! 🥳 Let’s Check the Stats 😸</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:54:19 +0000</pubDate>
      <link>https://dev.to/nyaomaru/celebrating-10000-total-views-on-dev-lets-check-the-stats-224e</link>
      <guid>https://dev.to/nyaomaru/celebrating-10000-total-views-on-dev-lets-check-the-stats-224e</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who recently watched a flock of geese march through Heerlen. 🪿🪿🪿&lt;/p&gt;

&lt;p&gt;Thank you for reading my articles!&lt;/p&gt;

&lt;p&gt;My total views on DEV have now passed &lt;strong&gt;10,000&lt;/strong&gt;! 🎉🎉🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqri4p5vmkbs6ijml340.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqri4p5vmkbs6ijml340.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm so happy that my articles have been read by so many developers.&lt;/p&gt;

&lt;p&gt;To celebrate this milestone, I opened my article dashboard and checked my overall stats and post rankings.&lt;/p&gt;

&lt;p&gt;Let's take a look together!&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 DEV Post Stats
&lt;/h2&gt;

&lt;p&gt;I have published &lt;strong&gt;28 posts&lt;/strong&gt; since 28 September 2025, excluding this one.&lt;/p&gt;

&lt;p&gt;Here are my overall stats:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8m00646uldrd45wb0sec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8m00646uldrd45wb0sec.png" alt="DEV dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the average stats per article 👇&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Average&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Views&lt;/td&gt;
&lt;td&gt;387.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactions&lt;/td&gt;
&lt;td&gt;24.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comments&lt;/td&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I'm honestly surprised by these results!&lt;/p&gt;

&lt;p&gt;I'll continue publishing more interesting posts. 💪&lt;/p&gt;




&lt;h2&gt;
  
  
  👑 Post Rankings
&lt;/h2&gt;

&lt;p&gt;In this article, I'll introduce my top posts in the following categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most Reactions&lt;/li&gt;
&lt;li&gt;Most Comments&lt;/li&gt;
&lt;li&gt;Most Views&lt;/li&gt;
&lt;li&gt;My Personal Recommendations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💖 Most Reactions
&lt;/h2&gt;

&lt;p&gt;The article with the most reactions is...&lt;/p&gt;

&lt;p&gt;Drrrrrr... 🥁&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which OpenAPI Codegen Should You Choose? openapi-typescript vs Hey API vs Orval vs Kubb&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/which-openapi-codegen-should-you-choose-openapi-typescript-vs-hey-api-vs-orval-vs-kubb-100p" class="crayons-story__hidden-navigation-link"&gt;Which OpenAPI Codegen Should You Choose? openapi-typescript vs hey-api vs Orval vs Kubb&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
                
              
              &lt;div id="story-author-preview-content-3688298" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/which-openapi-codegen-should-you-choose-openapi-typescript-vs-hey-api-vs-orval-vs-kubb-100p" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 20&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/which-openapi-codegen-should-you-choose-openapi-typescript-vs-hey-api-vs-orval-vs-kubb-100p" id="article-link-3688298"&gt;
          Which OpenAPI Codegen Should You Choose? openapi-typescript vs hey-api vs Orval vs Kubb
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/openapi"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;openapi&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/which-openapi-codegen-should-you-choose-openapi-typescript-vs-hey-api-vs-orval-vs-kubb-100p" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;60&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/which-openapi-codegen-should-you-choose-openapi-typescript-vs-hey-api-vs-orval-vs-kubb-100p#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              15&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            18 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;It received &lt;strong&gt;60 reactions&lt;/strong&gt;!! Thank you very very much. 🙏&lt;/p&gt;

&lt;p&gt;I'm especially proud of this result because this was the article I worked hardest on.&lt;/p&gt;

&lt;p&gt;I tested each library against a large production codebase, and we actually adopted Hey API in our system's CI pipeline.&lt;/p&gt;

&lt;p&gt;I also recently tried generating Faker data and mocks with Hey API, and both features were excellent.&lt;/p&gt;

&lt;p&gt;After that, I evaluated the libraries again. What interested me most was the impressive performance of Kubb v5, which is currently in beta.&lt;/p&gt;

&lt;p&gt;In my test, Kubb v5 was more than &lt;strong&gt;five times faster&lt;/strong&gt; than v4!&lt;/p&gt;

&lt;p&gt;That is some amazing performance tuning.&lt;/p&gt;

&lt;p&gt;We should keep an eye on how OpenAPI code generators continue to evolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  💬 Most Comments
&lt;/h2&gt;

&lt;p&gt;The article with the most comments is...&lt;/p&gt;

&lt;p&gt;Drrrrrr... 🥁&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View 👀👀)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-story__hidden-navigation-link"&gt;I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View 👀👀)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Features a timeline to step backward mid-loop&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
                
              
              &lt;div id="story-author-preview-content-4143027" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 15&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" id="article-link-4143027"&gt;
          I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View 👀👀)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dsa"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dsa&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;58&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              22&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;It received &lt;strong&gt;21 comments&lt;/strong&gt;!! Thank you very much. 🙏&lt;/p&gt;

&lt;p&gt;I recently released &lt;strong&gt;DSA View View&lt;/strong&gt;, and this article introduces the project.&lt;/p&gt;

&lt;p&gt;It is still brand new, so please take a look!&lt;/p&gt;

&lt;p&gt;If you want to improve your software engineering skills, DSA View View may help you understand data structures and algorithms more clearly.&lt;/p&gt;

&lt;p&gt;The article covers questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why do we still need to learn DSA in the AI era?&lt;/li&gt;
&lt;li&gt;How can you use DSA View View?&lt;/li&gt;
&lt;li&gt;What should you study first?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the answers in the article.&lt;/p&gt;




&lt;h2&gt;
  
  
  👀 Most Views
&lt;/h2&gt;

&lt;p&gt;The article with the most views is...&lt;/p&gt;

&lt;p&gt;Drrrrrr... 🥁&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Let’s Learn Feature-Sliced Design (FSD)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/lets-learn-feature-sliced-design-fsd-15bb" class="crayons-story__hidden-navigation-link"&gt;Let’s Learn Feature-Sliced Design (FSD)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
                
              
              &lt;div id="story-author-preview-content-2872478" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/lets-learn-feature-sliced-design-fsd-15bb" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 28 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/lets-learn-feature-sliced-design-fsd-15bb" id="article-link-2872478"&gt;
          Let’s Learn Feature-Sliced Design (FSD)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/design"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;design&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/lets-learn-feature-sliced-design-fsd-15bb" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;9&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/lets-learn-feature-sliced-design-fsd-15bb#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;It has received more than &lt;strong&gt;1,900 views&lt;/strong&gt;! Thank you very much. 🙏&lt;/p&gt;

&lt;p&gt;Because this was my first post on DEV, simply being online longer may have helped it reach this number.&lt;/p&gt;

&lt;p&gt;However, I also think Feature-Sliced Design can be a useful architecture for AI-assisted development.&lt;/p&gt;

&lt;p&gt;The clear separation between layers, slices, and segments can make a codebase easier to understand and maintain.&lt;/p&gt;

&lt;p&gt;Of course, learning FSD requires some time and effort. However, AI tools can learn this structure, and humans can follow it too.&lt;/p&gt;

&lt;p&gt;If you are struggling to maintain a legacy codebase, FSD may be worth trying.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⭐ My Personal Recommendations
&lt;/h2&gt;

&lt;p&gt;This is a special section containing some of my personal favourite articles.&lt;/p&gt;

&lt;p&gt;One of my recommendations is...&lt;/p&gt;

&lt;p&gt;Drrrrrr... 🥁&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🧠 Understanding Variance in TypeScript &amp;amp; Flow: Covariant, Contravariant, Invariant, Bivariant&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/understanding-variance-in-typescript-flow-covariant-contravariant-invariant-bivariant-4fbi" class="crayons-story__hidden-navigation-link"&gt;🧠 Understanding Variance in TypeScript &amp;amp; Flow: Covariant, Contravariant, Invariant, Bivariant&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
                
              
              &lt;div id="story-author-preview-content-3059743" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/understanding-variance-in-typescript-flow-covariant-contravariant-invariant-bivariant-4fbi" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 26 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/understanding-variance-in-typescript-flow-covariant-contravariant-invariant-bivariant-4fbi" id="article-link-3059743"&gt;
          🧠 Understanding Variance in TypeScript &amp;amp; Flow: Covariant, Contravariant, Invariant, Bivariant
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/understanding-variance-in-typescript-flow-covariant-contravariant-invariant-bivariant-4fbi" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;40&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/understanding-variance-in-typescript-flow-covariant-contravariant-invariant-bivariant-4fbi#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              6&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;In this article, I tried to explain variance in TypeScript.&lt;/p&gt;

&lt;p&gt;I often forget the differences myself, so I wrote this article as a reference—and I still go back and read it. 😸&lt;/p&gt;

&lt;p&gt;If you are struggling to understand variance in TypeScript, please check it out!&lt;/p&gt;

&lt;p&gt;And here is one more recommendation.&lt;/p&gt;

&lt;p&gt;Drrrrrr... 🥁&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;is-kit vs Zod: A Practical Comparison from 3 Perspectives&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/is-kit-vs-zod-a-practical-comparison-from-3-perspectives-21eh" class="crayons-story__hidden-navigation-link"&gt;is-kit vs Zod: A Practical Comparison from 3 Perspectives&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
                
              
              &lt;div id="story-author-preview-content-3522623" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/is-kit-vs-zod-a-practical-comparison-from-3-perspectives-21eh" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 22&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/is-kit-vs-zod-a-practical-comparison-from-3-perspectives-21eh" id="article-link-3522623"&gt;
          is-kit vs Zod: A Practical Comparison from 3 Perspectives
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/is-kit-vs-zod-a-practical-comparison-from-3-perspectives-21eh" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;42&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/is-kit-vs-zod-a-practical-comparison-from-3-perspectives-21eh#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              7&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;What is &lt;strong&gt;is-kit&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;If that is what you are thinking, please read the article!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a lightweight and simple type guard kit.&lt;/p&gt;

&lt;p&gt;If you want an easy way to protect your code from unknown values, you may enjoy using &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Summary
&lt;/h2&gt;

&lt;p&gt;I'm really happy to publish this celebration article.&lt;/p&gt;

&lt;p&gt;My next milestone is &lt;strong&gt;30,000 total views&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;I'll continue publishing interesting articles about frontend development, TypeScript, architecture, developer tools, and my own projects.&lt;/p&gt;

&lt;p&gt;If any of these articles caught your attention, please take a look.&lt;/p&gt;

&lt;p&gt;I hope at least one of them helps you!&lt;/p&gt;

&lt;p&gt;See you in the next article!&lt;/p&gt;

</description>
      <category>devto</category>
      <category>writing</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How I Made JavaScript Execution Visual and Rewindable (DSA View View 👀👀)</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:42:13 +0000</pubDate>
      <link>https://dev.to/nyaomaru/how-i-made-javascript-execution-visual-and-rewindable-dsa-view-view--42k4</link>
      <guid>https://dev.to/nyaomaru/how-i-made-javascript-execution-visual-and-rewindable-dsa-view-view--42k4</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who was recently &lt;strong&gt;knocked off my feet by how good the zeetong (sole) from a Dutch fish shop was&lt;/strong&gt;. 🐟😸&lt;/p&gt;

&lt;p&gt;In the previous article, I explained how DSA View View takes the &lt;code&gt;TypeScript&lt;/code&gt; written by a user, analyzes the function or class signature, and generates a Verification form.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/how-i-built-the-editor-and-compile-pipeline-for-a-typescript-dsa-visualizer-dsa-view-view--530f" class="crayons-story__hidden-navigation-link"&gt;How I Built the Editor and Compile Pipeline for a TypeScript DSA Visualizer (DSA View View 👀👀)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
              
              &lt;div id="story-author-preview-content-4197911" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/how-i-built-the-editor-and-compile-pipeline-for-a-typescript-dsa-visualizer-dsa-view-view--530f" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 22&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/how-i-built-the-editor-and-compile-pipeline-for-a-typescript-dsa-visualizer-dsa-view-view--530f" id="article-link-4197911"&gt;
          How I Built the Editor and Compile Pipeline for a TypeScript DSA Visualizer (DSA View View 👀👀)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/how-i-built-the-editor-and-compile-pipeline-for-a-typescript-dsa-visualizer-dsa-view-view--530f" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;35&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/how-i-built-the-editor-and-compile-pipeline-for-a-typescript-dsa-visualizer-dsa-view-view--530f#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;This time, we are entering the heart of DSA View View: the &lt;strong&gt;Runtime&lt;/strong&gt; and &lt;strong&gt;Visualization&lt;/strong&gt; layers.&lt;/p&gt;

&lt;p&gt;If you call an ordinary JavaScript function, the direct result you get back is usually the final return value.&lt;/p&gt;

&lt;p&gt;But when I am studying DSA, that is not the part I really want to see.&lt;/p&gt;

&lt;p&gt;I want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happened in the middle of the loop?&lt;/li&gt;
&lt;li&gt;When did the pointer move?&lt;/li&gt;
&lt;li&gt;At which exact moment did the variable change?&lt;/li&gt;
&lt;li&gt;How deep did the recursion go?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, I want to see &lt;strong&gt;the journey to the answer&lt;/strong&gt;, not only the answer itself.&lt;/p&gt;

&lt;p&gt;JavaScript, unfortunately, does not provide an API that accepts:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Excuse me, could you move the current line one step backward?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So how does DSA View View rewind execution?&lt;/p&gt;

&lt;p&gt;Here is the answer up front. 😸&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before execution, DSA View View rewrites the code into a version that can record its intermediate state, then runs it until it completes or reaches a configured limit.&lt;/p&gt;

&lt;p&gt;The UI then plays those recorded states forward and backward like security-camera footage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we will look at that &lt;strong&gt;record-and-replay&lt;/strong&gt; system through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AST transformation with Babel&lt;/li&gt;
&lt;li&gt;Injecting &lt;code&gt;recordStep&lt;/code&gt; calls&lt;/li&gt;
&lt;li&gt;Preserving the original meaning of the code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ExecutionStep&lt;/code&gt; and snapshot patches&lt;/li&gt;
&lt;li&gt;Rewinding and jumping to variable changes&lt;/li&gt;
&lt;li&gt;Running code inside a Web Worker&lt;/li&gt;
&lt;li&gt;Detecting the appropriate Visualization&lt;/li&gt;
&lt;li&gt;Tests that protect the AST transformation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source code is available here 👇&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;
        dsa-view-view
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DSA View View allows you to understand DSA to see the data flow. 👀👀 Of course, it's free.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nyaomaru/dsa-view-view/./public/readme-view-view.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Freadme-view-view.png" alt="DSA View View" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DSA View View&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;DSA View View turns TypeScript algorithm functions into step-by-step visual
stories. 👀👀&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-tv.gif" alt="DSA View View TV"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Write code, run it with structured inputs, and see the arrays, matrices
trees, lists, stacks, pointers, and return values move as the function executes.&lt;/p&gt;
&lt;p&gt;It is built for those moments when reading the code is not enough and you want
to &lt;em&gt;view&lt;/em&gt; why the answer changes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-demo.gif" alt="DSA View View demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Try It?&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;🧠 &lt;strong&gt;Step through real TypeScript&lt;/strong&gt;&lt;br&gt;
Paste or edit a function, validate it, then run the exact code in the browser.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🧩 &lt;strong&gt;Views that match the data&lt;/strong&gt;&lt;br&gt;
Arrays become bars, matrices become grids, trees become node graphs, linked lists become chains, and two-pointer area problems get their own visual view.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🌳 &lt;strong&gt;DSA-friendly inputs out of the box&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;TreeNode&lt;/code&gt;, &lt;code&gt;ListNode&lt;/code&gt;, &lt;code&gt;MinHeap&lt;/code&gt;, &lt;code&gt;MaxHeap&lt;/code&gt;, &lt;code&gt;PriorityQueue&lt;/code&gt;, nested arrays
matrices, strings, numbers, and class-style inputs are supported without
ceremony.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🔎 &lt;strong&gt;39 built-in examples&lt;/strong&gt;&lt;br&gt;
Search by name, browse…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;All right, let’s open the Runtime and see what Mr. View has been watching. 👀👀&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 The Big Picture: Turning Execution into an Array of Steps
&lt;/h2&gt;

&lt;p&gt;The Runtime pipeline looks roughly like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User TypeScript
      ↓
Prepare common DSA classes
      ↓
Parse the code into an AST with Babel Parser
      ↓
Visitors inject recordStep(...)
      ↓
Transform TypeScript into JavaScript with Babel
      ↓
Execute it with new Function inside a Web Worker
      ↓
Generate ExecutionStep[]
      ↓
React moves currentStep forward and backward
      ↓
Display the Visualization that matches the trace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important design decision here is that &lt;strong&gt;the user’s code is not connected directly to a React component&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, &lt;code&gt;ExecutionStep[]&lt;/code&gt; acts as a shared recording format.&lt;/p&gt;

&lt;p&gt;That lets arrays, pointers, recursion, trees, heaps, and other structures all live on the same timeline.&lt;/p&gt;

&lt;p&gt;Let’s walk through how that works.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔭 Runtime: Planting Observation Points in the Code
&lt;/h2&gt;

&lt;p&gt;This is the heart of DSA View View. 💖&lt;/p&gt;

&lt;p&gt;The Runtime needs intermediate states, so it does not execute the user’s code exactly as written.&lt;/p&gt;

&lt;p&gt;First, Babel transforms the AST and injects calls to &lt;code&gt;recordStep&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Injecting &lt;code&gt;recordStep&lt;/code&gt; into the AST
&lt;/h3&gt;

&lt;p&gt;Suppose the user writes.&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;let&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&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;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;left&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DSA View View parses it into an AST and transforms it into something roughly like this.&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;let&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&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;recordStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;variable-declaration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;let left = 0&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;left&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;recordStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loop-iteration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;while (left &amp;lt; right)&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;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;recordStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assignment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left++&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;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;right&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;blockquote&gt;
&lt;p&gt;The second argument to &lt;code&gt;recordStep&lt;/code&gt; is the original source line number, not the execution-step number.&lt;br&gt;
Because line 2 is empty, the recorded lines are &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt;, and &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the real implementation, dedicated visitors handle AST nodes such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FunctionDeclaration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FunctionExpression&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ArrowFunctionExpression&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ClassMethod&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VariableDeclaration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AssignmentExpression&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UpdateExpression&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CallExpression&lt;/code&gt; nodes that mutate arrays&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;for&lt;/code&gt; / &lt;code&gt;while&lt;/code&gt; / &lt;code&gt;for...of&lt;/code&gt; / &lt;code&gt;for...in&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReturnStatement&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A step can therefore be recorded when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A function is entered&lt;/li&gt;
&lt;li&gt;A variable is declared&lt;/li&gt;
&lt;li&gt;A loop completes an iteration&lt;/li&gt;
&lt;li&gt;A value is assigned&lt;/li&gt;
&lt;li&gt;An array is mutated&lt;/li&gt;
&lt;li&gt;A function returns&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Current limitation&lt;/strong&gt;&lt;br&gt;
callback bodies inside methods such as &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;reduce&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and &lt;code&gt;forEach&lt;/code&gt; are intentionally excluded from instrumentation for now.&lt;/p&gt;

&lt;p&gt;The goal is not to trace every corner of JavaScript immediately. I am starting with patterns that are useful for DSA traces and can be instrumented without breaking the original behavior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Injecting the Code Is Easier Than Not Breaking It
&lt;/h3&gt;

&lt;p&gt;The hardest part of the AST transformation was not calling &lt;code&gt;recordStep&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The hard part was making sure that,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;after adding the recording logic, every original expression is still evaluated the same number of times and still returns the same value.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, if a return expression were evaluated once for recording and once for the actual return, its side effects would run twice.&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;return&lt;/span&gt; &lt;span class="nf"&gt;someFunction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the transformed code first stores the value in a temporary variable.&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;algorithmVisualizerReturnValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;someFunction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;recordStep&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="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&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="s2"&gt;return value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;algorithmVisualizerReturnValue&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 location&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;returnLocation&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;algorithmVisualizerReturnValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assignment expressions are another fun little trap.&lt;/p&gt;

&lt;p&gt;Consider this.&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transformed code must preserve all three facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;i++&lt;/code&gt; runs exactly once&lt;/li&gt;
&lt;li&gt;The pre-increment value of &lt;code&gt;i&lt;/code&gt; is used as the array index&lt;/li&gt;
&lt;li&gt;The assignment expression itself evaluates to &lt;code&gt;7&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DSA View View wraps the assignment in a synthetic arrow function, stores the result in a temporary variable, records the step, and returns the same value.&lt;/p&gt;

&lt;p&gt;Using an arrow function also avoids accidentally stealing the surrounding &lt;code&gt;this&lt;/code&gt; or &lt;code&gt;arguments&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;AST transformation cannot be "close enough".&lt;/p&gt;

&lt;p&gt;If adding more visible steps changes the answer, the visualizer has somehow become the least trustworthy character in the story.&lt;/p&gt;

&lt;p&gt;Not ideal.&lt;/p&gt;




&lt;h2&gt;
  
  
  🖍️ &lt;code&gt;ExecutionStep&lt;/code&gt;: Making Execution Replayable
&lt;/h2&gt;

&lt;p&gt;The Runtime records each observed state as an &lt;code&gt;ExecutionStep&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A shortened version of the real type looks like this.&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;type&lt;/span&gt; &lt;span class="nx"&gt;ExecutionStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stepNumber&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionStepType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;line&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;column&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;description&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="nl"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&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;callStack&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="nl"&gt;scope&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="nl"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;loopIteration&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;conditionResult&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;functionName&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="nl"&gt;heapTrace&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;HeapTraceSnapshot&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 Runtime currently generates steps including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;function-call&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;function-entry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;variable-declaration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assignment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;array-mutation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loop-iteration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;return&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;line&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; are used in the timeline.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;variables&lt;/code&gt; provides the data needed to reconstruct the UI at that moment.&lt;/p&gt;

&lt;p&gt;Recursive traces also use &lt;code&gt;callStack&lt;/code&gt;, while prepared heap implementations can attach &lt;code&gt;metadata.heapTrace&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Saving References Would Rewrite the Past
&lt;/h3&gt;

&lt;p&gt;Suppose an array reference were stored directly inside a step.&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;stack&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nf"&gt;recordStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* save stack */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&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 recorded step keeps the same reference, the earlier state will also appear as &lt;code&gt;[0]&lt;/code&gt; after &lt;code&gt;pop()&lt;/code&gt; runs.&lt;/p&gt;

&lt;p&gt;That is not a historical record.&lt;/p&gt;

&lt;p&gt;That is the past quietly editing itself.&lt;/p&gt;

&lt;p&gt;So values passed to &lt;code&gt;recordStep&lt;/code&gt; are deep-cloned at that moment.&lt;/p&gt;

&lt;p&gt;The Runtime normally uses &lt;code&gt;structuredClone&lt;/code&gt;. When it encounters values that need different handling, it falls back to a cloning implementation that supports cycles, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;RegExp&lt;/code&gt;, and other cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Delta Snapshot" Is Really More Like a Patch
&lt;/h3&gt;

&lt;p&gt;The implementation contains a name called &lt;code&gt;variableDeltas&lt;/code&gt;, but this deserves a more precise explanation.&lt;/p&gt;

&lt;p&gt;The current Runtime does &lt;strong&gt;not&lt;/strong&gt; compare every value with the previous state and store only the keys that truly changed.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The first step stores the initial state, including the inputs&lt;/li&gt;
&lt;li&gt;Later steps deep-clone the visible variables passed to that &lt;code&gt;recordStep&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When a full snapshot is needed, the patches are merged in order&lt;/li&gt;
&lt;li&gt;Reconstructed snapshots are cached&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, strictly speaking, these are closer to &lt;strong&gt;per-step patches&lt;/strong&gt; than minimal deltas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 0 patch: { left: 0, right: 5 }
Step 1 patch: { left: 1 }
Step 2 patch: { right: 4 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reconstructing them produces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 0 snapshot: { left: 0, right: 5 }
Step 1 snapshot: { left: 1, right: 5 }
Step 2 snapshot: { left: 1, right: 4 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;variables&lt;/code&gt; property of each &lt;code&gt;ExecutionStep&lt;/code&gt; is a getter.&lt;/p&gt;

&lt;p&gt;It starts from a previously cached snapshot and merges only the patches needed to reach the requested step.&lt;/p&gt;

&lt;p&gt;There is one more implementation detail worth mentioning.&lt;/p&gt;

&lt;p&gt;The Runtime sends state from the Worker to the UI with &lt;code&gt;postMessage&lt;/code&gt;. During that structured clone, reading the getter also materializes the snapshot.&lt;/p&gt;

&lt;p&gt;So the data does not remain a tiny delta forever and magically arrive on the main thread in that form.&lt;/p&gt;

&lt;p&gt;The design mainly reduces eager reconstruction and cloning &lt;strong&gt;while the execution is being recorded&lt;/strong&gt;. 📽️&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Can the Timeline Move Backward?
&lt;/h3&gt;

&lt;p&gt;DSA View View is &lt;strong&gt;not reversing JavaScript while it runs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Inside the Web Worker, it first rewrites the code into an instrumented version and records the execution.&lt;/p&gt;

&lt;p&gt;During playback, the main value that changes is &lt;code&gt;currentStep&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;type&lt;/span&gt; &lt;span class="nx"&gt;ExecutionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;currentStep&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;totalSteps&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;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionStep&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;isComplete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;returnValue&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="nl"&gt;error&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving forward displays the snapshot for &lt;code&gt;currentStep + 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Moving backward displays the snapshot for &lt;code&gt;currentStep - 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So rewinding means,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;selecting an earlier recorded frame, not undoing the execution itself&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once recording has finished, users can navigate without rerunning their code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pause&lt;/li&gt;
&lt;li&gt;Move one step forward or backward&lt;/li&gt;
&lt;li&gt;Jump to any step in the timeline&lt;/li&gt;
&lt;li&gt;Move to the first or final step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users can also select a variable and jump directly to the previous step where its value changed.&lt;/p&gt;

&lt;p&gt;That is especially useful for pointers such as &lt;code&gt;i&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt;, and &lt;code&gt;right&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No more clicking the Back button 47 times while whispering, "Where did you go wrong?" 😸&lt;/p&gt;

&lt;h3&gt;
  
  
  There Is a Generator, but It Does Not Suspend the User’s Code Line by Line
&lt;/h3&gt;

&lt;p&gt;The Runtime API contains a &lt;code&gt;Generator&amp;lt;ExecutionStep, ...&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That might make it look as if the user’s code itself is being paused line by line with &lt;code&gt;yield&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;After yielding the first &lt;code&gt;function-call&lt;/code&gt; step, the instrumented code runs once and accumulates steps inside the execution context.&lt;/p&gt;

&lt;p&gt;The generator then yields the already-recorded steps in order.&lt;/p&gt;

&lt;p&gt;In the path used by the UI, the Worker consumes the generator completely and returns the finished &lt;code&gt;ExecutionState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Separating &lt;strong&gt;execution&lt;/strong&gt; from &lt;strong&gt;playback&lt;/strong&gt; is what makes the rewind behavior relatively simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Web Worker: Do Not Take the UI Down with You
&lt;/h2&gt;

&lt;p&gt;User code runs inside a dedicated Web Worker instead of the main thread.&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;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./execution-worker.ts&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;meta&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;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;module&lt;/span&gt;&lt;span class="dl"&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;algorithm-execution&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An infinite loop or expensive computation on the main thread would freeze the entire page.&lt;/p&gt;

&lt;p&gt;By moving execution into a Worker:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The React UI stays on the main thread&lt;/li&gt;
&lt;li&gt;Starting a new run or leaving the page can terminate the Worker through an &lt;code&gt;AbortController&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Runtime also has limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execution timeout: 10 seconds&lt;/li&gt;
&lt;li&gt;Maximum recorded steps: 3,000&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a limit is reached, the Worker can be terminated or execution can stop early with a warning.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Worker Is Not a Security Sandbox
&lt;/h3&gt;

&lt;p&gt;A Web Worker does not have access to the page DOM, &lt;code&gt;localStorage&lt;/code&gt;, or &lt;code&gt;sessionStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;DSA View View also does not execute user code on a server.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;a Web Worker is not a security boundary that makes untrusted code safe.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The user code runs through &lt;code&gt;new Function&lt;/code&gt; in the Worker global scope.&lt;/p&gt;

&lt;p&gt;Web APIs available inside Workers, including &lt;code&gt;fetch&lt;/code&gt;, are not automatically disabled.&lt;/p&gt;

&lt;p&gt;The purpose of this architecture is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Isolate heavy execution from the main thread&lt;/li&gt;
&lt;li&gt;Terminate an execution unit on timeout or cancellation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is an execution boundary for responsiveness and control, not a magical prison for JavaScript.&lt;/p&gt;

&lt;p&gt;JavaScript has watched enough escape-room videos to know better.&lt;/p&gt;




&lt;h2&gt;
  
  
  👀 Visualization: Variables Alone Are Not Enough
&lt;/h2&gt;

&lt;p&gt;The Runtime returns an &lt;code&gt;ExecutionState&lt;/code&gt; and snapshots for each step.&lt;/p&gt;

&lt;p&gt;The next problem is deciding which View should display that trace.&lt;/p&gt;

&lt;p&gt;DSA View View does not ask AI,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey, does this look like Binary Search?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;on every run.&lt;/p&gt;

&lt;p&gt;Instead, visualization candidates are detected through logic based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variable names&lt;/li&gt;
&lt;li&gt;Variable types and data structures&lt;/li&gt;
&lt;li&gt;Whether arrays or matrices actually changed&lt;/li&gt;
&lt;li&gt;Pointers such as &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt;, and &lt;code&gt;mid&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The call stack&lt;/li&gt;
&lt;li&gt;Function-entry and return history&lt;/li&gt;
&lt;li&gt;Heap metadata&lt;/li&gt;
&lt;li&gt;Changes across multiple steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, displaying a Sort Graph whenever a &lt;code&gt;number[]&lt;/code&gt; exists would classify algorithms that only &lt;em&gt;read&lt;/em&gt; an input array as sorting algorithms.&lt;/p&gt;

&lt;p&gt;So the detection logic checks whether the numeric array actually changes between steps.&lt;/p&gt;

&lt;p&gt;It also looks for context such as &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;swap&lt;/code&gt;, &lt;code&gt;partition&lt;/code&gt;, and &lt;code&gt;pivot&lt;/code&gt; in descriptions or the call stack.&lt;/p&gt;

&lt;p&gt;Some representative mappings are 👇&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime state&lt;/th&gt;
&lt;th&gt;Displayed View&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A changing numeric array&lt;/td&gt;
&lt;td&gt;Sort Graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An array used as a stack&lt;/td&gt;
&lt;td&gt;Stack View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search using &lt;code&gt;left&lt;/code&gt; / &lt;code&gt;right&lt;/code&gt; / &lt;code&gt;mid&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Index View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sliding Window pointers&lt;/td&gt;
&lt;td&gt;Sliding Window View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A two-dimensional array&lt;/td&gt;
&lt;td&gt;Matrix View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DP arrays or rolling state&lt;/td&gt;
&lt;td&gt;DP View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TreeNode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tree Graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ListNode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List Graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recursive function entry and return&lt;/td&gt;
&lt;td&gt;Recursion Tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logic using &lt;code&gt;Map&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Map View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logic using both &lt;code&gt;MinHeap&lt;/code&gt; and &lt;code&gt;MaxHeap&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Heap View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Word Ladder-style BFS&lt;/td&gt;
&lt;td&gt;Word Ladder View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container / rain-water / histogram pointers&lt;/td&gt;
&lt;td&gt;Area View&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calculator character positions and sign stack&lt;/td&gt;
&lt;td&gt;Expression View&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When multiple candidate Views are found, DSA View View assigns priorities and selects a primary Visualization.&lt;/p&gt;

&lt;p&gt;For example, if heap state is available, Heap View takes priority over an ordinary array View.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualization Is Built from &lt;code&gt;ExecutionState&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Each Visualizer knows nothing about the user’s source code.&lt;/p&gt;

&lt;p&gt;It extracts what it needs from the current &lt;code&gt;ExecutionState&lt;/code&gt; and converts that data into View-specific state.&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BinarySearchVisualizer&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;indexState&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;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;,&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;currentStep&lt;/code&gt; changes, the same Visualizer receives a new state.&lt;/p&gt;

&lt;p&gt;CSS transitions and Framer Motion animate the difference, making pointers and nodes appear to move.&lt;/p&gt;

&lt;p&gt;This creates a clean separation of responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime creates a correct trace while preserving the original behavior&lt;/li&gt;
&lt;li&gt;Detection decides which View best represents the trace&lt;/li&gt;
&lt;li&gt;Visualization focuses on how to display that state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding a new visualization generally follows this path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect the target execution state&lt;/li&gt;
&lt;li&gt;Convert it into View-specific state&lt;/li&gt;
&lt;li&gt;Render it with a React component&lt;/li&gt;
&lt;li&gt;Add it to the primary-visualization selection&lt;/li&gt;
&lt;li&gt;Add the smallest reproducible code sample as a test&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🧪 Tests: Reducing the Number of "It Happened to Work" Cases
&lt;/h2&gt;

&lt;p&gt;Users can write code freely, which means they can also write it in ways I never expected.&lt;/p&gt;

&lt;p&gt;AST transformations are especially dangerous because fixing one syntax pattern can quietly break another.&lt;/p&gt;

&lt;p&gt;So when a real issue appears, I keep the smallest version of that code as a regression test.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arguments and local variables for each recursive call&lt;/li&gt;
&lt;li&gt;Postfix &lt;code&gt;i++&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;XOR assignment&lt;/li&gt;
&lt;li&gt;Nested array mutations&lt;/li&gt;
&lt;li&gt;Class constructors and &lt;code&gt;super()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TreeNode&lt;/code&gt; / &lt;code&gt;ListNode&lt;/code&gt; inputs&lt;/li&gt;
&lt;li&gt;Heap operations&lt;/li&gt;
&lt;li&gt;Binary Search and Sliding Window pointers&lt;/li&gt;
&lt;li&gt;Word Ladder BFS state&lt;/li&gt;
&lt;li&gt;Worker timeouts and step limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trusting human memory to say,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Surely this syntax still works."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is not a particularly strong testing strategy.&lt;/p&gt;

&lt;p&gt;Memory is surprisingly optimistic.&lt;/p&gt;

&lt;p&gt;And it would be a little embarrassing to build a tool for avoiding confusion in DSA, only to get lost inside the tool’s own behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Summary
&lt;/h2&gt;

&lt;p&gt;DSA View View is not simply turning an array into a bar chart.&lt;/p&gt;

&lt;p&gt;At a high level, it works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write TypeScript in Monaco&lt;/li&gt;
&lt;li&gt;Analyze the source code and signature with Babel&lt;/li&gt;
&lt;li&gt;Inject &lt;code&gt;recordStep&lt;/code&gt; into the AST&lt;/li&gt;
&lt;li&gt;Execute the code inside a Web Worker&lt;/li&gt;
&lt;li&gt;Record variable snapshots and the call stack&lt;/li&gt;
&lt;li&gt;Replay the steps with React&lt;/li&gt;
&lt;li&gt;Display a View matching the data structure&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most important step was converting the user’s code into an array of &lt;code&gt;ExecutionStep&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;Once execution becomes a sequence of steps, the same &lt;code&gt;ExecutionState&lt;/code&gt; can power:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pause&lt;/li&gt;
&lt;li&gt;Forward and backward navigation&lt;/li&gt;
&lt;li&gt;Timeline jumps&lt;/li&gt;
&lt;li&gt;Navigation to the previous variable change&lt;/li&gt;
&lt;li&gt;Reconstruction of recursion trees&lt;/li&gt;
&lt;li&gt;Data-structure visualizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key idea is "Do not try to reverse JavaScript itself. First, transform execution into a recordable domain model".&lt;/p&gt;

&lt;p&gt;Then,&lt;/p&gt;

&lt;p&gt;"Runtime focuses on producing a correct trace, while Visualization focuses on presenting that trace."&lt;/p&gt;

&lt;p&gt;Keeping Editor, Runtime, and Visualization independent from one another has been essential to making the tool extensible. Stable models such as &lt;code&gt;ExecutionStep&lt;/code&gt; and &lt;code&gt;ExecutionState&lt;/code&gt; connect these layers without coupling them directly.&lt;/p&gt;

&lt;p&gt;There are still unsupported algorithms and syntax patterns, so I would love to hear from you if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An implementation does not run&lt;/li&gt;
&lt;li&gt;You want another data structure visualized&lt;/li&gt;
&lt;li&gt;You think a trace would be clearer in a different View&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Issues and PRs are always welcome 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;https://github.com/nyaomaru/dsa-view-view&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s run our code and watch what it is doing inside together. 👀👀&lt;/p&gt;

&lt;p&gt;And if you enjoy the project, I would be very happy if you left a GitHub ⭐!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Built the Editor and Compile Pipeline for a TypeScript DSA Visualizer (DSA View View 👀👀)</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 22 Jul 2026 14:06:50 +0000</pubDate>
      <link>https://dev.to/nyaomaru/how-i-built-the-editor-and-compile-pipeline-for-a-typescript-dsa-visualizer-dsa-view-view--530f</link>
      <guid>https://dev.to/nyaomaru/how-i-built-the-editor-and-compile-pipeline-for-a-typescript-dsa-visualizer-dsa-view-view--530f</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer. Thanks to my daily Duolingo streak, Dutch is finally starting to sound slightly less like one extremely long word. Progress! 🦉&lt;/p&gt;

&lt;p&gt;In my previous article, I introduced &lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;strong&gt;DSA View View&lt;/strong&gt;&lt;/a&gt;, a tool for visualizing data structures and algorithm execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vae2g6ny3lopz5jzsmy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vae2g6ny3lopz5jzsmy.gif" alt="DSA View View fish"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-story__hidden-navigation-link"&gt;I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View 👀👀)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Features a timeline to step backward mid-loop&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
                
              
              &lt;div id="story-author-preview-content-4143027" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 15&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" id="article-link-4143027"&gt;
          I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View 👀👀)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dsa"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dsa&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;58&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              22&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;That article covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why I decided to study DSA again&lt;/li&gt;
&lt;li&gt;What DSA View View is&lt;/li&gt;
&lt;li&gt;How to use it&lt;/li&gt;
&lt;li&gt;How I plan to study with it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if this is your first time meeting &lt;strong&gt;Mr. View&lt;/strong&gt;, the previous article is a good place to start.&lt;/p&gt;

&lt;p&gt;This time, we're opening the hood.&lt;/p&gt;

&lt;p&gt;This is the first half of the implementation story, covering everything from the moment a user writes &lt;code&gt;TypeScript&lt;/code&gt; to the moment &lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;DSA View View&lt;/a&gt; generates an input form for that implementation.&lt;/p&gt;

&lt;p&gt;We'll mainly look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The overall architecture and FSD&lt;/li&gt;
&lt;li&gt;Runtime type guards with &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Monaco&lt;/code&gt; Editor&lt;/li&gt;
&lt;li&gt;Static &lt;code&gt;TypeScript&lt;/code&gt; analysis&lt;/li&gt;
&lt;li&gt;Compilation with &lt;code&gt;Babel&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extracting function signatures from the AST&lt;/li&gt;
&lt;li&gt;Generating the Verification form&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the source code here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;
        dsa-view-view
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DSA View View allows you to understand DSA to see the data flow. 👀👀 Of course, it's free.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nyaomaru/dsa-view-view/./public/readme-view-view.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Freadme-view-view.png" alt="DSA View View" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DSA View View&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;DSA View View turns TypeScript algorithm functions into step-by-step visual
stories. 👀👀&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-tv.gif" alt="DSA View View TV"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Write code, run it with structured inputs, and see the arrays, matrices
trees, lists, stacks, pointers, and return values move as the function executes.&lt;/p&gt;
&lt;p&gt;It is built for those moments when reading the code is not enough and you want
to &lt;em&gt;view&lt;/em&gt; why the answer changes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-demo.gif" alt="DSA View View demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Try It?&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;🧠 &lt;strong&gt;Step through real TypeScript&lt;/strong&gt;&lt;br&gt;
Paste or edit a function, validate it, then run the exact code in the browser.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🧩 &lt;strong&gt;Views that match the data&lt;/strong&gt;&lt;br&gt;
Arrays become bars, matrices become grids, trees become node graphs, linked lists become chains, and two-pointer area problems get their own visual view.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🌳 &lt;strong&gt;DSA-friendly inputs out of the box&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;TreeNode&lt;/code&gt;, &lt;code&gt;ListNode&lt;/code&gt;, &lt;code&gt;MinHeap&lt;/code&gt;, &lt;code&gt;MaxHeap&lt;/code&gt;, &lt;code&gt;PriorityQueue&lt;/code&gt;, nested arrays
matrices, strings, numbers, and class-style inputs are supported without
ceremony.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🔎 &lt;strong&gt;39 built-in examples&lt;/strong&gt;&lt;br&gt;
Search by name, browse…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;All right, let's take a look together!&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 Designing DSA View View
&lt;/h2&gt;

&lt;p&gt;DSA View View has four main responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Editor&lt;/li&gt;
&lt;li&gt;Compile&lt;/li&gt;
&lt;li&gt;Runtime&lt;/li&gt;
&lt;li&gt;Visualize&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a Verification step in the UI, where the user provides input values before the code is executed.&lt;/p&gt;

&lt;p&gt;The overall flow looks like below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwpqg19os1rtxlfqkidks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwpqg19os1rtxlfqkidks.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each part is already a little complicated on its own, and they interact with one another quite deeply.&lt;/p&gt;

&lt;p&gt;I could see during the design phase that a flat project structure would eventually leave future-me wandering around the repository like a lost hiker. 🌲&lt;/p&gt;

&lt;p&gt;So I decided to use &lt;strong&gt;FSD (Feature-Sliced Design)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I previously wrote about how I learned FSD here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/nyaomaru/lets-learn-feature-sliced-design-fsd-15bb"&gt;https://dev.to/nyaomaru/lets-learn-feature-sliced-design-fsd-15bb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simplified version of the project structure looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── app/                # App startup and global styles
├── pages/
│   └── main/           # Main page that composes the features
├── widgets/
│   ├── editor-panel/   # UI around the editor
│   ├── control-panel/  # Compile / Verification / Runtime controls
│   └── header/
├── features/
│   ├── code-editing/   # Monaco Editor, formatting, and parsing
│   ├── compilation/    # Compile behavior
│   ├── code-execution/ # AST transforms, execution, step recording, input forms
│   ├── visualization/  # Visualization detection and views
│   └── shareable-url/
├── entities/
│   ├── code/           # Code, types, and compile results
│   ├── execution/      # ExecutionState and ExecutionStep
│   ├── data-structure/ # TreeNode, ListNode, and friends
│   └── algorithm-example/
└── shared/
    ├── ui/             # Generic components such as Button, Card, and Dialog
    └── lib/            # Guards, cloning, serializers, and other utilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, &lt;code&gt;features/code-execution&lt;/code&gt; owns the behavior of executing code.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;code&gt;ExecutionState&lt;/code&gt; and &lt;code&gt;ExecutionStep&lt;/code&gt; describe domain concepts, so they live in &lt;code&gt;entities/execution&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;pages/main&lt;/code&gt; combines the individual features and coordinates the overall flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After Compile succeeds, extract the signature and move to Verification&lt;/li&gt;
&lt;li&gt;After receiving the inputs, start the Runtime&lt;/li&gt;
&lt;li&gt;When the Runtime step changes, highlight the corresponding line in the Editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each slice is consumed through a public API such as &lt;code&gt;index.ts&lt;/code&gt;, and dependencies are allowed to flow only from higher layers to lower layers.&lt;/p&gt;

&lt;p&gt;I also verify those dependency directions and public API boundaries with architecture tests.&lt;/p&gt;

&lt;p&gt;Drawing those boundaries helps prevent the classic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I changed the Runtime. Why is the Editor broken now?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A mystery nobody ordered.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛡️ &lt;code&gt;is-kit&lt;/code&gt;: Protecting the App from &lt;code&gt;unknown&lt;/code&gt; Hell
&lt;/h2&gt;

&lt;p&gt;FSD can organize dependency directions, but it cannot automatically make values entering the application safe.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;DSA View View&lt;/strong&gt;, mysterious &lt;code&gt;unknown&lt;/code&gt; values come flying in from many directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AST nodes returned by &lt;code&gt;Babel Parser&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Messages sent between &lt;code&gt;Web Worker&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;JSON restored from a shareable URL&lt;/li&gt;
&lt;li&gt;Runtime snapshots recorded from user code&lt;/li&gt;
&lt;li&gt;Arrays, trees, graphs, and heaps being inspected as visualization candidates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;TypeScript&lt;/code&gt; is wonderfully reliable at compile time.&lt;/p&gt;

&lt;p&gt;But at runtime, it does not interrogate a value and ask.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Are you &lt;em&gt;really&lt;/em&gt; an &lt;code&gt;ExecutionState&lt;/code&gt;, or are you just wearing its name tag?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where my type guard library, &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt;, comes in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt; is a small, zero-dependency library for building and composing reusable &lt;code&gt;TypeScript&lt;/code&gt; type guards.&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;and&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;arrayOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;hasKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;oneOfValues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;or&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;predicateToRefine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Guard&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;is-kit&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;isNumericString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;predicateToRefine&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&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="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&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="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;isNonArrayObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isArray&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;isNumericValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isNumericString&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;isNumericArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;arrayOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNumericValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small guards can be combined like LEGO bricks to create more complicated runtime checks.&lt;/p&gt;

&lt;p&gt;For example, the code that finds class methods in an AST defines a guard.&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;isMethodNodeType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;oneOfValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ClassMethod&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;MethodDefinition&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;hasType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hasKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&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;isMethodLikeNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Guard&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MethodLikeNode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;isObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MethodLikeNode&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;span class="nx"&gt;value&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;hasType&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isMethodNodeType&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="kd"&gt;type&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;After a value passes this guard, it is narrowed from a plain &lt;code&gt;unknown&lt;/code&gt; to &lt;code&gt;MethodLikeNode&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The guard can then be passed directly to &lt;code&gt;filter&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;members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;classNode&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isMethodLikeNode&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 extremely convenient.&lt;/p&gt;

&lt;p&gt;Without &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt;, checks like this would have multiplied across the repository:&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;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&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="nx"&gt;value&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&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;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eventually, slightly different versions of the same guard would appear everywhere.&lt;/p&gt;

&lt;p&gt;Six months later, I would be asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What is the difference between this &lt;code&gt;isObject&lt;/code&gt; and that &lt;code&gt;isObject&lt;/code&gt;?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And congratulations—we would have built a second maze inside the first maze.&lt;/p&gt;

&lt;p&gt;At the time of writing, shared guards are used from around &lt;strong&gt;69 files&lt;/strong&gt; under &lt;code&gt;src&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Monaco&lt;/code&gt; and &lt;code&gt;Babel&lt;/code&gt; are the stars on stage, &lt;code&gt;is-kit&lt;/code&gt; is the very serious bouncer standing at every entrance to the AST, Worker, share URL, Runtime, and Visualization layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mr. Unknown&lt;/strong&gt; is not getting in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ynmx4d0fqp4mpny8zga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ynmx4d0fqp4mpny8zga.png" alt="Mr. Unknown insect damage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also use &lt;code&gt;Zod&lt;/code&gt; in the Verification forms, but the responsibilities are different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;is-kit&lt;/code&gt;: Internal narrowing, filtering, and reusable runtime guards&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Zod&lt;/code&gt;: Input form schemas, validation messages, and user-facing errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of making one library solve every problem, &lt;code&gt;is-kit&lt;/code&gt; handles internal runtime safety while &lt;code&gt;Zod&lt;/code&gt; handles user input validation.&lt;/p&gt;

&lt;p&gt;I am very glad I built &lt;code&gt;is-kit&lt;/code&gt; before building this application. Fighting all those &lt;code&gt;unknown&lt;/code&gt; values with my bare hands would have made &lt;em&gt;me&lt;/em&gt; the first thing that needed visualizing.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✏️ Editor
&lt;/h2&gt;

&lt;p&gt;The Editor uses &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;Monaco Editor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Monaco is the browser-focused editor component also used by &lt;code&gt;VS Code&lt;/code&gt;, giving the application syntax highlighting, completion, and &lt;code&gt;TypeScript&lt;/code&gt; diagnostics.&lt;/p&gt;

&lt;p&gt;The downside is that Monaco is not exactly tiny.&lt;/p&gt;

&lt;p&gt;Loading everything immediately made the initial display heavier, so DSA View View first renders a lightweight &lt;code&gt;textarea&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;textarea&lt;/code&gt; is also used as the &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; fallback, preventing the editor area from becoming empty while Monaco is loading.&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;
  &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Code Editor"&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onChange&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;event&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;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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="na"&gt;onFocus&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onActivate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, DSA View View waits briefly and prepares Monaco with &lt;code&gt;requestIdleCallback&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the browser does not support &lt;code&gt;requestIdleCallback&lt;/code&gt;, loading starts normally. If the user focuses the &lt;code&gt;textarea&lt;/code&gt; before that, Monaco preparation begins immediately.&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;CodeEditor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&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;prepareCodeEditorModule&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kr"&gt;module&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;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CodeEditor&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;Once loading finishes, the lightweight editor is replaced by Monaco while preserving the same source code.&lt;/p&gt;

&lt;p&gt;It looks like an editor, but secretly it is still a &lt;code&gt;textarea&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A tiny editor body-double strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static &lt;code&gt;TypeScript&lt;/code&gt; Analysis
&lt;/h3&gt;

&lt;p&gt;Static analysis uses the &lt;code&gt;TypeScript Worker&lt;/code&gt; included with Monaco.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TypeScript Language Service&lt;/code&gt; runs in a separate Worker from the editor itself and returns type and syntax problems as markers.&lt;/p&gt;

&lt;p&gt;Those markers are converted into the application's &lt;code&gt;CompilationError&lt;/code&gt; model and used to control the Compile button and display errors.&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;type&lt;/span&gt; &lt;span class="nx"&gt;CompilationError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;line&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;column&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;message&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="nl"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Compile button is disabled only when an &lt;code&gt;error&lt;/code&gt; exists. Warnings alone do not prevent compilation.&lt;/p&gt;

&lt;p&gt;One important detail is that &lt;strong&gt;&lt;code&gt;Babel&lt;/code&gt; does not perform &lt;code&gt;TypeScript&lt;/code&gt; type checking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The responsibilities are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monaco's &lt;code&gt;TypeScript&lt;/code&gt; Worker: Diagnose types and syntax&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Babel&lt;/code&gt;: Transform &lt;code&gt;TypeScript&lt;/code&gt; syntax into &lt;code&gt;JavaScript&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping that distinction clear saved me from blaming Babel for crimes it did not commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registering DSA Classes in the Editor
&lt;/h3&gt;

&lt;p&gt;Many &lt;code&gt;LeetCode&lt;/code&gt; problems provide classes such as &lt;code&gt;TreeNode&lt;/code&gt; and &lt;code&gt;ListNode&lt;/code&gt; automatically.&lt;/p&gt;

&lt;p&gt;DSA View View also provides common classes so users do not have to declare them every time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TreeNode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ListNode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GraphNode&lt;/code&gt; / &lt;code&gt;_Node&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TrieNode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PriorityQueue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MinHeap&lt;/code&gt; / &lt;code&gt;MaxHeap&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Deque&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UnionFind&lt;/code&gt; / &lt;code&gt;DSU&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Counter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Monaco provides a mechanism called &lt;code&gt;setExtraLibs&lt;/code&gt;, which lets the application add these type definitions to the Editor:&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="nx"&gt;monaco&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typescript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typescriptDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setExtraLibs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ts:algorithm-visualizer-prepared-classes.ts&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;However, &lt;code&gt;setExtraLibs&lt;/code&gt; only supports type analysis inside the Editor. It does not magically create those classes at Runtime.&lt;/p&gt;

&lt;p&gt;So the Compile and Runtime pipelines also prepend the same class definitions to the user's code as a prelude.&lt;/p&gt;

&lt;p&gt;If the user declares a &lt;code&gt;class&lt;/code&gt;, &lt;code&gt;interface&lt;/code&gt;, or &lt;code&gt;type&lt;/code&gt; with the same name, DSA View View skips injecting the prepared definition.&lt;/p&gt;

&lt;p&gt;Imagine carefully writing your own &lt;code&gt;ListNode&lt;/code&gt;, only for another &lt;code&gt;ListNode&lt;/code&gt; to emerge from behind it.&lt;/p&gt;

&lt;p&gt;That is not type safety. That is a summer horror movie.&lt;/p&gt;

&lt;h3&gt;
  
  
  Formatting
&lt;/h3&gt;

&lt;p&gt;Formatting uses &lt;code&gt;Prettier&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Prettier core, &lt;code&gt;TypeScript&lt;/code&gt; plugin, and &lt;code&gt;Estree&lt;/code&gt; plugin are dynamically imported only when needed and registered as Monaco's &lt;code&gt;Document Formatting Provider&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Users can therefore format their code with &lt;code&gt;Command/Ctrl + S&lt;/code&gt; or &lt;code&gt;Shift + Alt + F&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  📌 From the Compile Button to Verification
&lt;/h2&gt;

&lt;p&gt;So far, I have described "Compile" as if it were one large operation.&lt;/p&gt;

&lt;p&gt;Internally, however, the FSD slice &lt;code&gt;features/compilation&lt;/code&gt; does not own the whole flow.&lt;/p&gt;

&lt;p&gt;When the user presses the Compile button, &lt;code&gt;pages/main&lt;/code&gt; coordinates these operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;features/compilation&lt;/code&gt; checks whether the &lt;code&gt;TypeScript&lt;/code&gt; can be transformed into &lt;code&gt;JavaScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The parser in &lt;code&gt;features/code-editing&lt;/code&gt; extracts the function or class signature&lt;/li&gt;
&lt;li&gt;If everything succeeds, the UI moves to Verification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/code-execution&lt;/code&gt; displays an input form matching that signature&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To the user, this feels like one continuous Compile step.&lt;/p&gt;

&lt;p&gt;Inside the application, each responsibility remains separate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transforming &lt;code&gt;TypeScript&lt;/code&gt; with Babel
&lt;/h3&gt;

&lt;p&gt;The application uses Babel to transform &lt;code&gt;TypeScript&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;prepared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prepareTypeScriptCodeWithClasses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="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="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prepared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;presets&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="s2"&gt;typescript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DSA View View runs entirely in the browser, so it is not invoking &lt;code&gt;tsc&lt;/code&gt; on a &lt;code&gt;Node.js&lt;/code&gt; server.&lt;/p&gt;

&lt;p&gt;Instead, it uses &lt;code&gt;@babel/standalone&lt;/code&gt; to transform &lt;code&gt;TypeScript&lt;/code&gt; syntax into &lt;code&gt;JavaScript&lt;/code&gt; in the browser.&lt;/p&gt;

&lt;p&gt;Before transformation, the application prepends any prepared classes that the user has not declared themselves.&lt;/p&gt;

&lt;p&gt;If the source contains imports for those prepared classes, they are removed because the browser execution environment cannot resolve those modules.&lt;/p&gt;

&lt;p&gt;Again, an important reminder:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Babel's &lt;code&gt;TypeScript&lt;/code&gt; preset primarily removes type information. It does not type-check the program.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Monaco handles type errors. Babel checks whether the syntax can be transformed.&lt;/p&gt;

&lt;p&gt;The generated &lt;code&gt;JavaScript&lt;/code&gt; from this stage is also not executed directly on the main thread.&lt;/p&gt;

&lt;p&gt;For the Runtime, the original source is instrumented separately and then executed inside a &lt;code&gt;Web Worker&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the fun part—and the topic of the next article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extracting Function Signatures from the AST
&lt;/h3&gt;

&lt;p&gt;After Compile succeeds, DSA View View needs to build an input form.&lt;/p&gt;

&lt;p&gt;Suppose the user writes this function:&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;trap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&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="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&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 source code is parsed into an AST with &lt;code&gt;Babel Parser&lt;/code&gt;, and DSA View View extracts information like this:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&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;trap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;parameters&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;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;height&lt;/span&gt;&lt;span class="dl"&gt;"&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;number-array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;returnType&lt;/span&gt;&lt;span class="p"&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="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 parser supports more than ordinary function declarations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Function declarations&lt;/li&gt;
&lt;li&gt;Arrow functions assigned to variables&lt;/li&gt;
&lt;li&gt;Function expressions assigned to variables&lt;/li&gt;
&lt;li&gt;LeetCode-style factory functions that return another function&lt;/li&gt;
&lt;li&gt;Class declarations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a class, the parser extracts the constructor and callable methods. &lt;code&gt;private&lt;/code&gt; methods are currently excluded.&lt;/p&gt;

&lt;p&gt;For class-design problems such as &lt;code&gt;LRUCache&lt;/code&gt; and &lt;code&gt;MedianFinder&lt;/code&gt;, it extracts the class name, constructor, method names, and arguments.&lt;/p&gt;

&lt;p&gt;When the source contains multiple helper classes, DSA View View avoids prioritizing prepared classes as solution candidates and compares the number of callable methods to select the likely design class.&lt;/p&gt;

&lt;p&gt;This selection is heuristic, so it cannot perfectly identify every possible class structure yet.&lt;/p&gt;

&lt;p&gt;There are always more creative ways to write JavaScript than I expect. JavaScript considers this a feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating the Verification Form
&lt;/h3&gt;

&lt;p&gt;For ordinary functions, the input form is built from the extracted signature using &lt;code&gt;React Hook Form&lt;/code&gt; and &lt;code&gt;Zod&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Supported input types include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;number&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;number[]&lt;/code&gt; / &lt;code&gt;string[]&lt;/code&gt; / &lt;code&gt;boolean[]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;number[][]&lt;/code&gt; / &lt;code&gt;string[][]&lt;/code&gt; / &lt;code&gt;boolean[][]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TreeNode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ListNode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GraphNode&lt;/code&gt; / &lt;code&gt;_Node&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The input UI and validation behavior change depending on the type.&lt;/p&gt;

&lt;p&gt;For example, a &lt;code&gt;number[]&lt;/code&gt; accepts both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1, 2, 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both formats are converted into a real array that can be passed to the Runtime.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;TreeNode&lt;/code&gt;, the application constructs a tree from a level-order array.&lt;/p&gt;

&lt;p&gt;When a problem has multiple &lt;code&gt;TreeNode&lt;/code&gt; parameters, users can also select a specific node from the first root.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;ListNode&lt;/code&gt;, users can build a linked list from an array or node-based input and specify the cycle position.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;GraphNode&lt;/code&gt;, a cyclic graph structure is constructed from a &lt;code&gt;LeetCode&lt;/code&gt;-style adjacency list.&lt;/p&gt;

&lt;p&gt;Class-design problems use a different UI.&lt;/p&gt;

&lt;p&gt;Instead of the normal parameter form powered by &lt;code&gt;React Hook Form&lt;/code&gt; and &lt;code&gt;Zod&lt;/code&gt;, DSA View View displays the same &lt;code&gt;Operations&lt;/code&gt; and &lt;code&gt;Arguments&lt;/code&gt; JSON format used by &lt;code&gt;LeetCode&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;MedianFinder&lt;/code&gt; uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Operations: ["MedianFinder", "addNum", "addNum", "findMedian"]
Arguments:  [[], [1], [2], []]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This form validates class-design-specific requirements, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The number of operations matches the number of argument lists&lt;/li&gt;
&lt;li&gt;The first operation matches the class name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the Compile button starts a chain of separate responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Babel verifies that transformation is possible&lt;/li&gt;
&lt;li&gt;AST analysis extracts the signature&lt;/li&gt;
&lt;li&gt;The Verification layer generates the appropriate input form&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/main&lt;/code&gt; connects the entire flow from the Editor to Verification&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we followed the path from writing &lt;code&gt;TypeScript&lt;/code&gt; in DSA View View to generating the inputs needed for execution.&lt;/p&gt;

&lt;p&gt;The broad flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Render a lightweight &lt;code&gt;textarea&lt;/code&gt; first&lt;/li&gt;
&lt;li&gt;Prepare Monaco Editor in the background&lt;/li&gt;
&lt;li&gt;Diagnose types and syntax with the &lt;code&gt;TypeScript&lt;/code&gt; Worker&lt;/li&gt;
&lt;li&gt;Use Babel to check whether the &lt;code&gt;TypeScript&lt;/code&gt; can be transformed into &lt;code&gt;JavaScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extract the function or class signature from the AST&lt;/li&gt;
&lt;li&gt;Generate a Verification form based on the detected types&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important design decision was not forcing the Editor, diagnostics, syntax transformation, and input UI into one giant operation.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt; safely narrows &lt;code&gt;unknown&lt;/code&gt; values from the AST, Workers, JSON, and runtime snapshots&lt;/li&gt;
&lt;li&gt;Monaco's &lt;code&gt;TypeScript&lt;/code&gt; Worker diagnoses types and syntax&lt;/li&gt;
&lt;li&gt;Babel transforms &lt;code&gt;TypeScript&lt;/code&gt; syntax into &lt;code&gt;JavaScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AST analysis extracts the signature needed by the Verification UI&lt;/li&gt;
&lt;li&gt;The Verification form validates inputs and converts them into Runtime values&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/main&lt;/code&gt; connects the separate steps and advances the UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping those responsibilities separate made the flow much easier to understand and extend.&lt;/p&gt;

&lt;p&gt;In the next article, we will finally enter the heart of DSA View View: the Runtime.&lt;/p&gt;

&lt;p&gt;That is where &lt;strong&gt;Mr. View&lt;/strong&gt; stops politely waiting beside the Editor and starts watching every move your variables make. 👀&lt;/p&gt;

&lt;p&gt;There are still unsupported patterns and types, so Issues and PRs are always welcome!&lt;/p&gt;

&lt;p&gt;And if you enjoy the project, I would be very happy if you left a GitHub ⭐!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;https://github.com/nyaomaru/dsa-view-view&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>react</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a Tool to Visualize DSA. Let’s Learn Together! (DSA View View 👀👀)</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 15 Jul 2026 12:04:25 +0000</pubDate>
      <link>https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo</link>
      <guid>https://dev.to/nyaomaru/i-built-a-tool-to-visualize-dsa-lets-learn-together-dsa-view-view--djo</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I’m &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer currently fighting the intense European heatwave by building DIY window screens here in the Netherlands. 🛠️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/data-science-collective/should-you-still-learn-to-code-in-2026-034685e17707" rel="noopener noreferrer"&gt;https://medium.com/data-science-collective/should-you-still-learn-to-code-in-2026-034685e17707&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As this article also discusses, I believe that &lt;strong&gt;improving our fundamental engineering skills is still essential in the age of AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine that AI makes an F1 car fully autonomous. 🏎️&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Would you let someone without even a driver’s license enter a race as its driver?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or imagine that AI completely automates maintenance during pit stops. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Would you put someone who knows absolutely nothing about cars, their parts, or their internal structure in charge of that AI?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI is certainly reducing the amount of code we need to write by hand. Typing quickly and memorizing every small piece of syntax or every method are becoming less important.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;can we confidently release a product without understanding its fundamental concepts or implementation?&lt;/strong&gt; If something goes wrong in production, can we take responsibility and fix it?&lt;/p&gt;

&lt;p&gt;My answer was &lt;strong&gt;no&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Building things faster is obviously a good thing. But if we build without understanding, we may simply produce fragile software with no long-term sustainability at a faster rate.&lt;/p&gt;

&lt;p&gt;The real value comes from building meaningful things quickly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Then, how can we develop the fundamental skills we still need in the AI era?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I believe one useful approach is learning &lt;strong&gt;DSA: Data Structures and Algorithms&lt;/strong&gt;. So I decided to start learning DSA again.&lt;/p&gt;

&lt;p&gt;However, while solving problems on &lt;code&gt;LeetCode&lt;/code&gt;, I repeatedly ran into the same problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to visualize the data flow and understand what is happening, but I still can’t clearly see it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I used the skills I have developed through frontend engineering and built a tool that visualizes the execution flow of DSA implementations! 🚀&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;
        dsa-view-view
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DSA View View allows you to understand DSA to see the data flow. 👀👀 Of course, it's free.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nyaomaru/dsa-view-view/./public/readme-view-view.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Freadme-view-view.png" alt="DSA View View" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DSA View View&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;DSA View View turns TypeScript algorithm functions into step-by-step visual
stories. 👀👀&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-tv.gif" alt="DSA View View TV"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Write code, run it with structured inputs, and see the arrays, matrices
trees, lists, stacks, pointers, and return values move as the function executes.&lt;/p&gt;
&lt;p&gt;It is built for those moments when reading the code is not enough and you want
to &lt;em&gt;view&lt;/em&gt; why the answer changes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-demo.gif" alt="DSA View View demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Try It?&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;🧠 &lt;strong&gt;Step through real TypeScript&lt;/strong&gt;&lt;br&gt;
Paste or edit a function, validate it, then run the exact code in the browser.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🧩 &lt;strong&gt;Views that match the data&lt;/strong&gt;&lt;br&gt;
Arrays become bars, matrices become grids, trees become node graphs, linked lists become chains, and two-pointer area problems get their own visual view.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🌳 &lt;strong&gt;DSA-friendly inputs out of the box&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;TreeNode&lt;/code&gt;, &lt;code&gt;ListNode&lt;/code&gt;, nested arrays, matrices, strings, numbers, and class
style inputs are supported without ceremony.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🔎 &lt;strong&gt;39 built-in examples&lt;/strong&gt;&lt;br&gt;
Search by name, browse by category, and jump into…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flt3slpuvq0bt9yzzjtkm.gif" alt="DSA View View demo" width="600" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, I’ll explain how &lt;strong&gt;DSA View View&lt;/strong&gt; works and how we can use it to study DSA.&lt;/p&gt;

&lt;p&gt;Let’s take a look together!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;&lt;br&gt;
I may sound confident so far, but I’m definitely not a DSA expert. I’m still learning, just like many of you.&lt;/p&gt;

&lt;p&gt;In fact, I built this tool precisely because I was struggling. When I realized that I couldn’t immediately solve Binary Search, I started to panic a little. 😸&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🖥️ What Is DSA View View?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DSA View View&lt;/strong&gt; is a tool that executes functions and classes written in &lt;code&gt;TypeScript&lt;/code&gt; and lets you inspect how variables and return values change on each line through a runtime timeline.&lt;/p&gt;

&lt;p&gt;The easiest way to understand is to try it yourself!&lt;/p&gt;

&lt;p&gt;Open the tool and click the &lt;strong&gt;Run Demo&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frdur3bu1s24luupivhly.png" alt="Run Demo Button" width="798" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll quickly see how the workflow works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write your implementation&lt;/li&gt;
&lt;li&gt;Compile it&lt;/li&gt;
&lt;li&gt;Enter the input values&lt;/li&gt;
&lt;li&gt;Run it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s really it.&lt;/p&gt;

&lt;p&gt;Although I created it primarily for DSA problems, you can also use it to inspect and test smaller &lt;code&gt;TypeScript&lt;/code&gt; implementations.&lt;/p&gt;

&lt;p&gt;And, of course, it’s completely free!&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 What Can It Do?
&lt;/h2&gt;

&lt;p&gt;With DSA View View, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write DSA implementations in &lt;code&gt;TypeScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test implementations with different input values&lt;/li&gt;
&lt;li&gt;Move forward and backward through runtime steps&lt;/li&gt;
&lt;li&gt;Visualize supported data structures and algorithms&lt;/li&gt;
&lt;li&gt;Review LeetCode solutions and confirm your understanding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use it as a lightweight environment for executing functions or formatting code blocks for technical articles such as Dev.to 😸&lt;/p&gt;

&lt;p&gt;Feel free to experiment with it and find your own use cases!&lt;/p&gt;




&lt;h2&gt;
  
  
  🧑‍🏫 How to Use It
&lt;/h2&gt;

&lt;p&gt;The main workflow consists of three modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Editor&lt;/li&gt;
&lt;li&gt;Verification&lt;/li&gt;
&lt;li&gt;Runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s look at each one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Editor
&lt;/h2&gt;

&lt;p&gt;First, write your implementation!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg96n2pdp7atrfm0i9y9k.png" alt=" " width="799" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Editor is designed to feel similar to a basic IDE.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TypeScript&lt;/code&gt; diagnostics are available by default, so the editor can detect some errors while you write.&lt;/p&gt;

&lt;p&gt;You can also press &lt;code&gt;Cmd/Ctrl + S&lt;/code&gt; to format your code with Prettier, so feel free to write first and clean it up afterward.&lt;/p&gt;

&lt;p&gt;Common classes used in DSA problems, such as &lt;code&gt;TreeNode&lt;/code&gt; and &lt;code&gt;ListNode&lt;/code&gt;, are already available.&lt;/p&gt;

&lt;p&gt;You don’t need to declare or import them every time.&lt;/p&gt;

&lt;p&gt;You can also define your own classes or replace the provided definitions when necessary.&lt;/p&gt;

&lt;p&gt;Once your implementation is ready, click &lt;strong&gt;Compile Code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If compilation fails, the tool displays the relevant location and error message so that you can fix it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fss7ylft3mi1q5ur2sw1z.png" alt=" " width="800" height="752"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also an &lt;strong&gt;Example&lt;/strong&gt; menu containing several common implementations.&lt;/p&gt;

&lt;p&gt;For example, if you want to quickly inspect a Heap Sort implementation and see how it behaves, you can load the example instead of writing everything manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;p&gt;In Verification mode, you define the input values that should be passed to your function or class.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhz024r1f92790yj7bkrf.png" alt=" " width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter a test value for each argument.&lt;/p&gt;

&lt;p&gt;For arrays, both of the following formats are supported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1, 2, 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once your input values are ready, click &lt;strong&gt;Run&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Runtime
&lt;/h2&gt;

&lt;p&gt;Finally, let’s inspect the runtime!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bziyhlkont1v3m2odh7.png" alt=" " width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click &lt;strong&gt;Run&lt;/strong&gt; in Verification mode, your implementation is executed.&lt;/p&gt;

&lt;p&gt;Runtime mode then lets you play through the execution steps or move backward and forward manually.&lt;/p&gt;

&lt;p&gt;When the tool recognizes supported logic, &lt;strong&gt;an additional visualization is displayed in a modal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not every possible implementation is supported yet, of course. If something you need is missing, I would be happy to receive an issue or pull request!&lt;/p&gt;

&lt;p&gt;You can move backward and forward through the executed lines to inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which line is currently running&lt;/li&gt;
&lt;li&gt;How variables change&lt;/li&gt;
&lt;li&gt;Where values are returned&lt;/li&gt;
&lt;li&gt;How the execution reaches its final result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can return to the beginning, jump directly to the end, or move through the timeline one step at a time.&lt;/p&gt;

&lt;p&gt;The main execution process runs entirely in the browser.&lt;/p&gt;


&lt;h2&gt;
  
  
  Share 🐈
&lt;/h2&gt;

&lt;p&gt;DSA View View also includes a sharing feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvzdkh18relxhb1sfir94.png" alt="DSA View View Share button" width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can create a URL that preserves your implementation and share it with someone else.&lt;/p&gt;

&lt;p&gt;Use the &lt;strong&gt;Share&lt;/strong&gt; button at the top of the page to post it on social media or copy the link directly.&lt;/p&gt;

&lt;p&gt;Share your implementations, compare different solutions, and let’s learn together!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The shared URL contains your implementation. Do not paste private, confidential, or proprietary code into the tool before sharing it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  What Is That Thing in the Top-Right Corner? 👀👀
&lt;/h2&gt;

&lt;p&gt;Meet Mr. View.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4k6eygcli1kzrkr62qn7.gif" alt=" " width="510" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mr. View watches over your implementation.&lt;/p&gt;

&lt;p&gt;Nothing more.&lt;/p&gt;

&lt;p&gt;Nothing less.&lt;/p&gt;

&lt;p&gt;He is simply there to support you.&lt;/p&gt;

&lt;p&gt;When you need to concentrate, press the TV button to hide him. Press it again to bring him back.&lt;/p&gt;

&lt;p&gt;However, if Mr. View is enough to distract you, you may not be ready for an attack from ten simultaneous Slack notifications. 😸&lt;/p&gt;


&lt;h2&gt;
  
  
  ✏️ How Should We Study DSA?
&lt;/h2&gt;

&lt;p&gt;Now you know how to use DSA View View.&lt;/p&gt;

&lt;p&gt;The next question is probably&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which problems should I solve?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LeetCode is an obvious place to start, and the study plan from Tech Interview Handbook also provides a useful collection of practice problems. 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.techinterviewhandbook.org/coding-interview-study-plan/" rel="noopener noreferrer"&gt;https://www.techinterviewhandbook.org/coding-interview-study-plan/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try solving these problems with &lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;DSA View View&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once your implementation works, use the runtime timeline to inspect the data flow and understand exactly how the algorithm reaches its answer.&lt;/p&gt;

&lt;p&gt;You can also use tools such as ChatGPT, Codex, Claude, Claude Code, Gemini, and Grok to explore your solution further.&lt;/p&gt;

&lt;p&gt;Don’t limit yourself to asking&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this implementation correct?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Try asking questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What would happen if I rewrote this DFS solution using BFS?&lt;/li&gt;
&lt;li&gt;Could Dynamic Programming solve this problem?&lt;/li&gt;
&lt;li&gt;Are there any other reasonable approaches?&lt;/li&gt;
&lt;li&gt;What are the time and space complexities?&lt;/li&gt;
&lt;li&gt;Why is this approach better than the alternatives?&lt;/li&gt;
&lt;li&gt;Under which conditions would another approach be better?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI becomes much more useful for learning when it helps us compare and understand different approaches instead of only generating a final answer. 👍&lt;/p&gt;
&lt;h3&gt;
  
  
  Why TypeScript Instead of Python? 🐍
&lt;/h3&gt;

&lt;p&gt;The simple answer is that &lt;code&gt;TypeScript&lt;/code&gt; is my favorite language.&lt;/p&gt;

&lt;p&gt;I mainly work as a frontend engineer, so &lt;code&gt;TypeScript&lt;/code&gt; is also the language in which I feel most comfortable experimenting and learning.&lt;/p&gt;

&lt;p&gt;However, I designed the tool so that additional languages could be supported in the future.&lt;/p&gt;

&lt;p&gt;If there is enough demand for another language, please leave a comment or open an issue!&lt;/p&gt;


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

&lt;p&gt;AI is helping us write code faster and faster.&lt;/p&gt;

&lt;p&gt;However, understanding what happens inside an implementation remains an important engineering skill.&lt;/p&gt;

&lt;p&gt;In fact, I believe that this ability may become even more valuable as AI-generated code becomes increasingly common.&lt;/p&gt;

&lt;p&gt;I built DSA View View because I wanted a tool that could help me strengthen those fundamental skills from the ground up.&lt;/p&gt;

&lt;p&gt;To be honest, I still have a lot to learn about DSA.&lt;/p&gt;

&lt;p&gt;That is exactly why I hope we can learn together.&lt;/p&gt;

&lt;p&gt;Please leave a comment or open an issue if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want support for a particular problem or data structure&lt;/li&gt;
&lt;li&gt;You have an idea for a new visualization&lt;/li&gt;
&lt;li&gt;You find a bug&lt;/li&gt;
&lt;li&gt;You want support for another programming language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s train our DSA muscles together! 💪😸&lt;/p&gt;

&lt;p&gt;If you like it, please give a star ⭐&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;
        dsa-view-view
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DSA View View allows you to understand DSA to see the data flow. 👀👀 Of course, it's free.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nyaomaru/dsa-view-view/./public/readme-view-view.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Freadme-view-view.png" alt="DSA View View" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DSA View View&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;DSA View View turns TypeScript algorithm functions into step-by-step visual
stories. 👀👀&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-tv.gif" alt="DSA View View TV"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Write code, run it with structured inputs, and see the arrays, matrices
trees, lists, stacks, pointers, and return values move as the function executes.&lt;/p&gt;
&lt;p&gt;It is built for those moments when reading the code is not enough and you want
to &lt;em&gt;view&lt;/em&gt; why the answer changes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dsa-view-view.vercel.app/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fdsa-view-view%2FHEAD%2F.%2Fpublic%2Fdemo%2Fdsa-view-view-demo.gif" alt="DSA View View demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Try It?&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;🧠 &lt;strong&gt;Step through real TypeScript&lt;/strong&gt;&lt;br&gt;
Paste or edit a function, validate it, then run the exact code in the browser.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🧩 &lt;strong&gt;Views that match the data&lt;/strong&gt;&lt;br&gt;
Arrays become bars, matrices become grids, trees become node graphs, linked lists become chains, and two-pointer area problems get their own visual view.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🌳 &lt;strong&gt;DSA-friendly inputs out of the box&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;TreeNode&lt;/code&gt;, &lt;code&gt;ListNode&lt;/code&gt;, nested arrays, matrices, strings, numbers, and class
style inputs are supported without ceremony.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🔎 &lt;strong&gt;39 built-in examples&lt;/strong&gt;&lt;br&gt;
Search by name, browse by category, and jump into…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/dsa-view-view" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;And I also launched on ProductHunt! Come say hello and let me know what you think 😸 👇&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://www.producthunt.com/products/dsa-view-view" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;producthunt.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>showdev</category>
      <category>typescript</category>
      <category>dsa</category>
      <category>react</category>
    </item>
    <item>
      <title>Trying Declarative Partial Updates: A Future API for Replacing HTML Later</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 01 Jul 2026 12:42:05 +0000</pubDate>
      <link>https://dev.to/nyaomaru/trying-declarative-partial-updates-a-future-api-for-replacing-html-later-2g43</link>
      <guid>https://dev.to/nyaomaru/trying-declarative-partial-updates-a-future-api-for-replacing-html-later-2g43</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who was cheering for the World Cup during tropical nights in the Netherlands while nearly melting from heat exhaustion. 🫠⚽ (But yesterday was comfortable 😺)&lt;/p&gt;

&lt;p&gt;How do you usually build frontend applications?&lt;/p&gt;

&lt;p&gt;Do you build SPAs with &lt;code&gt;React&lt;/code&gt; / &lt;code&gt;Vue&lt;/code&gt; / &lt;code&gt;Svelte&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Do you use SSR / SSG / ISR frameworks like &lt;code&gt;Next&lt;/code&gt; / &lt;code&gt;Remix&lt;/code&gt; / &lt;code&gt;Nuxt&lt;/code&gt; / &lt;code&gt;Astro&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;There are many ways to build frontend applications, but today it is very common to update the DOM on the client side using JavaScript.&lt;/p&gt;

&lt;p&gt;Even when using UI libraries and frameworks like &lt;code&gt;React&lt;/code&gt; / &lt;code&gt;Vue&lt;/code&gt; / &lt;code&gt;Svelte&lt;/code&gt;, or SSR frameworks like &lt;code&gt;Next&lt;/code&gt; / &lt;code&gt;Remix&lt;/code&gt; / &lt;code&gt;Nuxt&lt;/code&gt; / &lt;code&gt;Astro&lt;/code&gt;, there are still many cases where part of the screen is eventually updated by client-side JavaScript.&lt;/p&gt;

&lt;p&gt;But is that really always the best answer?&lt;/p&gt;

&lt;p&gt;Are there cases where we are forcing JavaScript on the client to handle work that could naturally be handled on the server?&lt;/p&gt;

&lt;p&gt;In response to that kind of question, the Chrome team introduced an experimental API called DPU, or Declarative Partial Updates. 🚀&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://developer.chrome.com/blog/declarative-partial-updates?hl=en" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdeveloper.chrome.com%2Fstatic%2Fblog%2Fdeclarative-partial-updates%2Fimage%2Fthumbnail.png" height="450" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://developer.chrome.com/blog/declarative-partial-updates?hl=en" rel="noopener noreferrer" class="c-link"&gt;
            Declarative partial updates &amp;nbsp;|&amp;nbsp; Blog &amp;nbsp;|&amp;nbsp; Chrome for Developers
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn about new out-of-order streaming capabilities and the renewed HTML insertion and streaming methods available for testing from Chrome 148
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.gstatic.com%2Fdevrel-devsite%2Fprod%2Fv11431966d26d9f049ef61662c2b798f1cdee8af320f1ba0f77a43eee64301d60%2Fchrome%2Fimages%2Ffavicon.png" width="32" height="32"&gt;
          developer.chrome.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;In this article, I want to explore what Declarative Partial Updates are, what problems they try to solve, and why they might matter.&lt;/p&gt;

&lt;p&gt;I will focus especially on the out-of-order streaming mechanism that can replace parts of HTML later without writing client-side JavaScript for the replacement itself.&lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqjkwpwvwoitaac69eml0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqjkwpwvwoitaac69eml0.png" alt="nyaomaru soccer" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 What are Declarative Partial Updates?
&lt;/h2&gt;

&lt;p&gt;Declarative Partial Updates are, roughly speaking&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A mechanism where you declare part of your HTML as “this area will be replaced later”, and then update only that part with HTML that arrives later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, imagine you want to return the overall page HTML immediately, but some data is slower to fetch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The header is instant&lt;/li&gt;
&lt;li&gt;The layout is fast&lt;/li&gt;
&lt;li&gt;The user profile is also pretty fast&lt;/li&gt;
&lt;li&gt;But only the recommendations API is slow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In that case, we often show a loading state on the client, fetch data, and update the DOM with JavaScript. In &lt;code&gt;React&lt;/code&gt;, you might reach for something like &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With DPU, the server can first return HTML like this.&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;section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Recommendations&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"recommendations"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Loading recommendations...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The area surrounded by &lt;code&gt;&amp;lt;?start name="recommendations"&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;?end&amp;gt;&lt;/code&gt; is a &lt;strong&gt;region that can be replaced later&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then, when the recommendations HTML is ready on the server, it can stream HTML like this later.&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&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"recommendations"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Advanced CSS Layouts&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Modern HTML APIs&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Web Performance Basics&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;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At that point, the browser finds the region named &lt;code&gt;recommendations&lt;/code&gt; and replaces the loading UI with the contents of this &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Wait, does that feel a bit weird?&lt;/p&gt;

&lt;p&gt;Exactly. The wild part is that &lt;strong&gt;up to this point, we did not write any JavaScript for the replacement, and no framework is involved&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other words, you no longer need to write client-side JavaScript like this just to replace that area:&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;element&lt;/span&gt; &lt;span class="o"&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;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#recommendations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You stream HTML, and the browser understands where that HTML should go.&lt;/p&gt;

&lt;p&gt;That is the interesting part of Declarative Partial Updates.&lt;/p&gt;

&lt;p&gt;DPU also has &lt;code&gt;&amp;lt;?marker&amp;gt;&lt;/code&gt;, not only &lt;code&gt;&amp;lt;?start&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;?end&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;?marker&amp;gt;&lt;/code&gt; is basically a marker that says, “insert later HTML here.”&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;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;marker&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"placeholder"&lt;/span&gt;&lt;span class="nt"&gt;&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&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"placeholder"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Here is streamed content!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this case, the browser finds the marker named &lt;code&gt;placeholder&lt;/code&gt; and inserts the contents of the later &lt;code&gt;&amp;lt;template for="placeholder"&amp;gt;&lt;/code&gt; at that position.&lt;/p&gt;

&lt;p&gt;On the other hand, the &lt;code&gt;&amp;lt;?start&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;?end&amp;gt;&lt;/code&gt; pair used mainly in this article is for showing placeholder content, such as a loading UI, and replacing that entire range later.&lt;/p&gt;

&lt;p&gt;So, roughly&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;?marker&amp;gt;&lt;/code&gt; means “insert here”&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;?start&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;?end&amp;gt;&lt;/code&gt; means “replace this range”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That mental model should be enough to start.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;?marker&amp;gt;&lt;/code&gt; can also be used by leaving the same marker inside streamed content, which lets you append list items one by one. That is pretty interesting too.&lt;/p&gt;

&lt;p&gt;In the first half of this article, I focus mainly on replacing loading UI, so I mostly use &lt;code&gt;&amp;lt;?start&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;?end&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the later sample, I also try an example that uses &lt;code&gt;&amp;lt;?marker&amp;gt;&lt;/code&gt; to append asset links sequentially.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🤔 Why do we need Declarative Partial Updates?
&lt;/h2&gt;

&lt;p&gt;At this point, you might think&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait, frameworks like React or Astro can already do this, right?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes. They can.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;React&lt;/code&gt; / &lt;code&gt;Vue&lt;/code&gt; / &lt;code&gt;Svelte&lt;/code&gt; / &lt;code&gt;Next&lt;/code&gt; / &lt;code&gt;Remix&lt;/code&gt; / &lt;code&gt;Nuxt&lt;/code&gt; / &lt;code&gt;Astro&lt;/code&gt;, you can show loading states, receive API results, and update parts of the screen. Many of us do this every day.&lt;/p&gt;

&lt;p&gt;But wait a second. A lot of that is implemented by &lt;strong&gt;client-side JavaScript updating the DOM&lt;/strong&gt;, right?&lt;/p&gt;

&lt;p&gt;A common flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The server returns HTML&lt;/li&gt;
&lt;li&gt;The browser displays the HTML&lt;/li&gt;
&lt;li&gt;Client-side JavaScript is loaded&lt;/li&gt;
&lt;li&gt;JavaScript calls an API&lt;/li&gt;
&lt;li&gt;The API result is put into state&lt;/li&gt;
&lt;li&gt;The framework updates the DOM&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course, this is an extremely common frontend architecture today.&lt;/p&gt;

&lt;p&gt;But if part of the screen is “something the server can already produce as HTML”, do we really need to make client-side JavaScript do that work every time?&lt;/p&gt;

&lt;p&gt;For example, the recommendations HTML can be generated on the server like this:&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&amp;gt;&lt;/span&gt;Advanced CSS Layouts&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Modern HTML APIs&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Web Performance Basics&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;p&gt;If all we want to do is send that to the browser, it can feel a bit indirect to do this.&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/recommendations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setRecommendations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You fetch JSON, convert it to HTML on the client, update state, and then update the DOM.&lt;/p&gt;

&lt;p&gt;That is convenient, but it is also a bit roundabout. &lt;/p&gt;

&lt;p&gt;What makes DPU interesting is that it points in another direction&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the server can produce HTML, why not stream that HTML directly and let the browser insert it into the right place?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So DPU is &lt;strong&gt;not a replacement for &lt;code&gt;React&lt;/code&gt; / &lt;code&gt;Vue&lt;/code&gt; / &lt;code&gt;Svelte&lt;/code&gt;&lt;/strong&gt;. It is more like a lower-level browser primitive.&lt;/p&gt;

&lt;p&gt;It may allow the browser to understand, as part of HTML itself, some of the partial update work that frameworks currently handle.&lt;/p&gt;

&lt;p&gt;If that happens, the boundary between client-side responsibility and server-side responsibility becomes even more important.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What can be done with HTML?&lt;/li&gt;
&lt;li&gt;What should be done with JavaScript?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By redesigning these boundaries and declaring things in a way that fits the browser, we may be able to unlock browser capabilities that we are not fully using yet.&lt;/p&gt;

&lt;p&gt;But we should not misunderstand &lt;strong&gt;the client still has responsibilities&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This discussion is not about replacing state management, and interactive features still need JavaScript.&lt;/p&gt;

&lt;p&gt;For example, forms, modals, charts, and similar UI should still be managed on the client side, while static text or server-generated HTML based on slower API responses may be a better fit for HTML streaming.&lt;/p&gt;

&lt;p&gt;The responsibility split becomes more complex, but the UX may improve a lot. This is definitely something worth watching.&lt;/p&gt;


&lt;h2&gt;
  
  
  🏠 This may expand what SPAs can do
&lt;/h2&gt;

&lt;p&gt;DPU is &lt;strong&gt;not about rejecting SPAs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Personally, I see it as something that may expand the possibilities of SPAs.&lt;/p&gt;

&lt;p&gt;Until now, SPAs have often followed this mental model.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetch data as JSON
    ↓
Put it into client-side state
    ↓
Re-render components
    ↓
Update the DOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But if DPU and the new HTML insertion / streaming APIs become available, another option appears.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The server returns HTML
    ↓
HTML is streamed
    ↓
The browser inserts it into the existing DOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That means an SPA could potentially choose different update strategies for different parts of the UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch JSON and render on the client&lt;/li&gt;
&lt;li&gt;Generate HTML on the server and insert it directly&lt;/li&gt;
&lt;li&gt;Return only the shell initially, then stream slower parts later&lt;/li&gt;
&lt;li&gt;Perform page-transition-like updates with HTML partials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is really interesting.&lt;/p&gt;

&lt;p&gt;SPAs often carry the image of “doing everything with client-side JavaScript”, but they may become more hybrid in the future. The meaning of the word SPA itself may change.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State that belongs on the client stays on the client&lt;/li&gt;
&lt;li&gt;HTML that can be produced on the server is produced on the server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the browser does not merely display that HTML. It also understands &lt;strong&gt;where and how to insert it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If this direction continues, frontend architecture may become even more flexible and deeper than it is today.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 APIs for streaming HTML from JavaScript are also coming
&lt;/h2&gt;

&lt;p&gt;The interesting part of DPU is not only out-of-order streaming with &lt;code&gt;&amp;lt;template for="..."&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Chrome team is also proposing a new set of APIs for inserting and streaming HTML from JavaScript.&lt;/p&gt;

&lt;p&gt;Even today, there are several ways to dynamically insert HTML.&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="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertAdjacentHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beforeend&lt;/span&gt;&lt;span class="dl"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But they all have slightly different behavior.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it overwrite existing content?&lt;/li&gt;
&lt;li&gt;Does it append after the existing content?&lt;/li&gt;
&lt;li&gt;Are &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags executed?&lt;/li&gt;
&lt;li&gt;What happens with sanitization?&lt;/li&gt;
&lt;li&gt;How does it interact with Trusted Types?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a lot of subtle differences.&lt;/p&gt;

&lt;p&gt;Honestly, I do not think many developers can confidently explain all of those differences from memory. I definitely cannot.&lt;/p&gt;

&lt;p&gt;The new APIs try to make HTML insertion easier to understand with more consistent names.&lt;/p&gt;

&lt;p&gt;For example, static HTML insertion looks like this.&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;newHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;p&amp;gt;This is a new paragraph&amp;lt;/p&amp;gt;&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;contentElement&lt;/span&gt; &lt;span class="o"&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;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#content-to-update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;contentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newHTML&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 not an API that appends to the end, like &lt;code&gt;insertAdjacentHTML("beforeend", html)&lt;/code&gt;. It replaces the existing contents of &lt;code&gt;contentElement&lt;/code&gt; with &lt;code&gt;newHTML&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A useful way to understand the difference is&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;setHTML()&lt;/code&gt; is the safer API that runs through a sanitizer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setHTMLUnsafe()&lt;/code&gt; disables the sanitizer by default and lets you handle HTML more directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then comes the really interesting part of streaming versions.&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;contentElement&lt;/span&gt; &lt;span class="o"&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;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#content-to-update&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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/content.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeThrough&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;TextDecoderStream&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamHTMLUnsafe&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 pretty wild.&lt;/p&gt;

&lt;p&gt;It means we may be able to take HTML returned by &lt;code&gt;fetch()&lt;/code&gt; and stream it directly into an element &lt;strong&gt;without waiting for the entire response to finish downloading&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;SPAs have not benefited much from HTML streaming after the initial document load.&lt;/p&gt;

&lt;p&gt;The initial document can be streamed from the server, but later screen updates often become JSON fetch + client render.&lt;/p&gt;

&lt;p&gt;With APIs like this, SPA updates may also be able to benefit from HTML streaming.&lt;/p&gt;

&lt;p&gt;That is why I think this may expand the possibilities of SPAs.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚠️ “unsafe” does not mean “never use it”
&lt;/h2&gt;

&lt;p&gt;When you see a name like &lt;code&gt;streamHTMLUnsafe()&lt;/code&gt;, you might think&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unsafe!? That sounds terrifying.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I thought that too at first.&lt;/p&gt;

&lt;p&gt;But this &lt;code&gt;unsafe&lt;/code&gt; does not mean “you must never use it.” It is closer to a warning that &lt;strong&gt;the sanitizer is disabled by default, so you need to think carefully about whether the input is trusted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, it would be dangerous to stream user-generated HTML directly into the DOM.&lt;/p&gt;

&lt;p&gt;But if the HTML is generated by your own trusted server, there may be valid use cases.&lt;/p&gt;

&lt;p&gt;Of course, before using this in production, you need to think carefully about sanitizers, Trusted Types, script execution, browser support, and other security concerns.&lt;/p&gt;


&lt;h2&gt;
  
  
  ➕ What happens when DPU and the streaming APIs are combined?
&lt;/h2&gt;

&lt;p&gt;This is the most interesting part.&lt;/p&gt;

&lt;p&gt;DPU’s &lt;code&gt;&amp;lt;template for="..."&amp;gt;&lt;/code&gt; and the new HTML streaming APIs are &lt;strong&gt;separate APIs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But combining them makes the story even deeper.&lt;/p&gt;

&lt;p&gt;For example, imagine placing placeholders in an SPA screen.&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;main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"page-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"page-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Loading content...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, during a page update, the server streams HTML.&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&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"page-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Dashboard&lt;span class="nt"&gt;&amp;lt;/h1&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;template&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"page-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This is the dashboard content.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Instead of manually targeting the DOM with JavaScript.&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#page-title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;titleHTML&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;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#page-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contentHTML&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;the browser may be able to understand where &lt;code&gt;page-title&lt;/code&gt; and &lt;code&gt;page-content&lt;/code&gt; should be applied.&lt;/p&gt;

&lt;p&gt;In other words, even in SPA-style page transitions, something like this may become possible.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetch a new page's HTML partial
    ↓
Stream the HTML
    ↓
Update only the necessary places via template for
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Part of the work that frameworks currently handle with Virtual DOM, compilers, or fine-grained reactivity may be shifted to browser-native HTML streaming / partial updates.&lt;/p&gt;

&lt;p&gt;That makes DPU feel like more than just a small HTML API. It feels like something that could expand the design space of frontend architecture.&lt;/p&gt;

&lt;p&gt;Of course, this is still experimental. This is not a “let's all use this in production right now” kind of API.&lt;/p&gt;

&lt;p&gt;But the old assumption&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An SPA fetches JSON and renders on the client&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;may start to change.&lt;/p&gt;

&lt;p&gt;HTML may evolve from “a static document loaded once at the beginning” into “a UI unit that can be streamed, replaced, and updated.”&lt;/p&gt;

&lt;p&gt;That is pretty exciting, right?&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 Trying it with Node.js DPU samples
&lt;/h2&gt;

&lt;p&gt;So far, we have talked about the concept. I also built a small Node.js sample app to try this out.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/declarative-partial-updates-sample" rel="noopener noreferrer"&gt;
        declarative-partial-updates-sample
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample repository for DPU (Declarative Partial Updates)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Declarative Partial Updates sample&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A small Node.js sample app for trying Chrome's experimental Declarative Partial
Updates (DPU) feature.&lt;/p&gt;
&lt;p&gt;Images under &lt;code&gt;public/&lt;/code&gt; are served through the Node.js stream handler at &lt;code&gt;/public/...&lt;/code&gt;
then inserted from later DPU or HTML streaming chunks.&lt;/p&gt;
&lt;p&gt;Styles live in &lt;code&gt;public/styles.css&lt;/code&gt;. Shared timing and image constants live in
&lt;code&gt;features/shared.mjs&lt;/code&gt;, while public asset metadata and GitHub links live in
&lt;code&gt;features/public-assets.mjs&lt;/code&gt; so the sample behavior is easier to inspect and
adjust.&lt;/p&gt;
&lt;p&gt;The server is split by feature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;features/home/&lt;/code&gt;: DPU document streaming sample HTML and stream chunks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/hybrid-shell/&lt;/code&gt;: hybrid shell HTML and route partial HTML&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/set-apis/&lt;/code&gt;: static and streaming HTML insertion sample HTML&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/public-assets.mjs&lt;/code&gt;: public asset metadata and stream handler&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/layout/&lt;/code&gt;: shared document layout HTML&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/layout.mjs&lt;/code&gt;: layout template renderer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/shared.mjs&lt;/code&gt;: common helpers and demo timing constants&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public/html-stream.js&lt;/code&gt;: client-side static / streaming HTML insertion helper&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm start&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://localhost:3000/" rel="nofollow noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/declarative-partial-updates-sample" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is an experimental feature, so you need Chrome Canary (Chrome v148 or later) or a Chrome build that includes this feature, with the experimental flag enabled.&lt;/p&gt;

&lt;p&gt;Open the following URL in Chrome’s address bar:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chrome://flags/#enable-experimental-web-platform-features&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Enable it, click “Relaunch”, and then run the sample. This article assumes a supported build with this flag enabled.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2r0obwjpvcguchppde0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2r0obwjpvcguchppde0j.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The demo app has roughly three pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/              ... Document streaming
/hybrid-shell  ... Hybrid shell + streamed HTML route updates
/set-apis      ... Static / Streaming HTML APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first page, &lt;code&gt;/&lt;/code&gt;, demonstrates DPU out-of-order streaming.&lt;/p&gt;

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

&lt;p&gt;The server first returns an HTML shell, and slower server-rendered fragments are streamed later as &lt;code&gt;&amp;lt;template for="..."&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, the initial HTML contains a placeholder.&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;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"panel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Recommendations&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"recommendations"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Waiting for recommendations...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the browser shows the loading UI.&lt;/p&gt;

&lt;p&gt;Then, once the recommendations HTML is ready on the server, it streams a chunk.&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&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"recommendations"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Advanced CSS Layouts&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Modern HTML APIs&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Web Performance Basics&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;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser finds the DPU range named &lt;code&gt;recommendations&lt;/code&gt; and replaces the loading UI with the list.&lt;/p&gt;

&lt;p&gt;On this page, I stream multiple named regions at different timings, not only &lt;code&gt;recommendations&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hero image&lt;/li&gt;
&lt;li&gt;metric&lt;/li&gt;
&lt;li&gt;asset strip&lt;/li&gt;
&lt;li&gt;team activity&lt;/li&gt;
&lt;li&gt;streamed code block&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The asset strip also uses &lt;code&gt;&amp;lt;?marker&amp;gt;&lt;/code&gt;. The initial HTML has a marker.&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;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"panel asset-panel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Streamed public assets&lt;span class="nt"&gt;&amp;lt;/h2&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;"asset-grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;marker&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"asset-list"&lt;/span&gt;&lt;span class="nt"&gt;&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;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the server streams chunks one by one whenever each asset link becomes ready.&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&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"asset-list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- streamed asset link --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;marker&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"asset-list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By leaving the same marker again inside the streamed content, the next asset link can continue to be appended in the same place.&lt;/p&gt;

&lt;p&gt;So HTML does not have to wait until everything is complete from top to bottom. &lt;strong&gt;Fragments can be streamed in later as soon as they are ready.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the really interesting part of DPU.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;template for&amp;gt;&lt;/code&gt; has several subtle rules, including constraints related to parent elements.&lt;/p&gt;

&lt;p&gt;This demo is a sample for exploring the direction of the API, but if you want to use it in production someday, you should carefully check the applicable range of &lt;code&gt;&amp;lt;template for&amp;gt;&lt;/code&gt;, DOM movement during streaming, Trusted Types, sanitizers, and related details.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🌓 Trying a hybrid shell style architecture
&lt;/h2&gt;

&lt;p&gt;Next, &lt;code&gt;/hybrid-shell&lt;/code&gt; demonstrates streaming HTML route updates inside a hybrid shell style architecture.&lt;/p&gt;

&lt;p&gt;This is not a “pure SPA where everything is rendered on the client”. It is closer to an architecture where &lt;strong&gt;the whole document is not reloaded, the URL changes through the History API, and only the route content inside the shell is updated&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep client-side state alive, while replacing only the route content with server-generated HTML.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, this page has a counter:&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;div&amp;gt;&lt;/span&gt;
  Counter: &lt;span class="nt"&gt;&amp;lt;strong&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"counter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"increment"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Increment&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This counter is managed by client-side JavaScript.&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&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="o"&gt;=&amp;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;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&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;textContent&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this is clearly a client-side responsibility.&lt;/p&gt;

&lt;p&gt;On the other hand, route content is fetched from the server as HTML.&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;loadRoute&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;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&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;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setActiveRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&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;push&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;nextUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dashboard&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;/hybrid-shell/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/hybrid-shell/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;route&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="nx"&gt;nextUrl&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;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;`/partials/hybrid-shell/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&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="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceChildren&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streamHTMLUnsafe&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;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;response&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="nf"&gt;pipeThrough&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;TextDecoderStream&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamHTMLUnsafe&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;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&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;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceChildren&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does is not “fetch JSON and render components on the client.” Instead, &lt;strong&gt;it streams HTML generated by the server directly into the view&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The counter state remains on the client.&lt;/p&gt;

&lt;p&gt;That means we can split responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interactive state such as the counter belongs to the client&lt;/li&gt;
&lt;li&gt;Server-generated route body can be streamed as HTML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This feels close to the thinking behind island architecture in frameworks like &lt;code&gt;Astro&lt;/code&gt; / &lt;code&gt;Fresh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of making the client do everything, we separate &lt;strong&gt;HTML that can be returned statically or from the server&lt;/strong&gt; from &lt;strong&gt;interactive client state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Personally, I find this very interesting.&lt;/p&gt;

&lt;p&gt;At the very least, a shell with SPA-like navigation does not have to mean “everything is rendered by client-side JavaScript.” It may also expand into a hybrid shape that combines client state with streamed HTML.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;/hybrid-shell&lt;/code&gt; sample is &lt;strong&gt;not&lt;/strong&gt; a demo that replaces a range inside the shell using DPU’s &lt;code&gt;&amp;lt;?start&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;template for&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Chrome team article also introduces the possibility of SPA-style page loads by combining &lt;code&gt;streamHTMLUnsafe()&lt;/code&gt; and &lt;code&gt;&amp;lt;template for&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, in my local tests, I could not confirm that streaming &lt;code&gt;&amp;lt;template for="..."&amp;gt;&lt;/code&gt; into &lt;code&gt;streamHTMLUnsafe()&lt;/code&gt; in this shell architecture updates an existing DPU range.&lt;/p&gt;

&lt;p&gt;The SPA-style page load described in the official article seems to point toward an outline page with processing instructions, where the new page’s &lt;code&gt;&amp;lt;template for&amp;gt;&lt;/code&gt; elements are streamed toward the end of the HTML and slotted into those instructions.&lt;/p&gt;

&lt;p&gt;So in this &lt;code&gt;/hybrid-shell&lt;/code&gt; sample, I do not handle DPU range replacement. Instead, I use the HTML streaming insertion API to stream server-generated route partials directly into &lt;code&gt;#hybrid-shell-view&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎁 Trying Static / Streaming HTML APIs
&lt;/h2&gt;

&lt;p&gt;Finally, &lt;code&gt;/set-apis&lt;/code&gt; tries the new HTML insertion / streaming APIs separately from DPU’s out-of-order streaming.&lt;/p&gt;

&lt;p&gt;This page first checks whether the browser has the APIs.&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="nx"&gt;support&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  &amp;lt;ul class="list"&amp;gt;
    &amp;lt;li&amp;gt;setHTMLUnsafe: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setHTMLUnsafe&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;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;streamHTMLUnsafe: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streamHTMLUnsafe&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;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;
  &amp;lt;/ul&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;For static insertion, if &lt;code&gt;setHTMLUnsafe()&lt;/code&gt; is available, the demo uses it. Otherwise, it falls back to &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; and &lt;code&gt;replaceChildren()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setHTMLUnsafe&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;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;staticTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHTMLUnsafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markup&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;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;markup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;staticTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceChildren&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For streaming insertion, it pipes the fetched HTML response directly into &lt;code&gt;streamHTMLUnsafe()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/partials/set-apis/stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;streamTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceChildren&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streamHTMLUnsafe&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;Element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;response&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="nf"&gt;pipeThrough&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;TextDecoderStream&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamHTMLUnsafe&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;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this works, we can stream chunks into an existing element as they arrive, &lt;strong&gt;instead of waiting until the entire HTML response has downloaded and inserting it all at once&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Traditionally, SPAs could benefit from HTML streaming for the initial document, but subsequent screen updates often became JSON fetch + client render.&lt;/p&gt;

&lt;p&gt;With APIs like this, SPAs may be able to benefit from HTML streaming even for later updates.&lt;/p&gt;

&lt;p&gt;That is the exciting part of DPU and the new HTML streaming APIs.&lt;/p&gt;

&lt;p&gt;The static insertion partial is returned from &lt;code&gt;/partials/set-apis/static&lt;/code&gt;, and the streaming insertion partial is returned from &lt;code&gt;/partials/set-apis/stream&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Summary
&lt;/h2&gt;

&lt;p&gt;In this article, I looked at Declarative Partial Updates introduced by the Chrome team.&lt;/p&gt;

&lt;p&gt;Declarative Partial Updates are, roughly&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A mechanism where you declare part of your HTML as “this area will be replaced later”, and then update only that part with HTML that arrives later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course, DPU does not replace &lt;code&gt;React&lt;/code&gt; / &lt;code&gt;Vue&lt;/code&gt; / &lt;code&gt;Svelte&lt;/code&gt; / &lt;code&gt;Next&lt;/code&gt; / &lt;code&gt;Remix&lt;/code&gt; / &lt;code&gt;Nuxt&lt;/code&gt; / &lt;code&gt;Astro&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Interactive UI, client-side state management, forms, modals, charts, drag and drop, and similar features still need JavaScript.&lt;/p&gt;

&lt;p&gt;But on the other hand, there may be more cases where we do not need to handle everything with client-side JavaScript.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML that can be generated on the server should be generated on the server&lt;/li&gt;
&lt;li&gt;State that should be managed on the client should be managed on the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the browser may come to understand &lt;strong&gt;not only how to display HTML, but also where streamed HTML should be inserted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If this responsibility split progresses, frontend architecture may become more hybrid.&lt;/p&gt;

&lt;p&gt;Especially for SPAs, there may be another path beyond&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fetch JSON and render on the client&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, we may also be able to&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fetch / stream HTML partials and update via browser-native behavior&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I do not see this as the end of SPAs. I see it as something that may expand what SPAs can do.&lt;/p&gt;

&lt;p&gt;This is still experimental, so it is not an API to use in production right away.&lt;/p&gt;

&lt;p&gt;But the direction where HTML changes from “a document loaded once at the beginning” into “a UI unit that can be streamed, replaced, and updated” feels very important for the future of frontend development.&lt;/p&gt;

&lt;p&gt;DPU is definitely something to keep an eye on. 😺&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.chrome.com/blog/declarative-partial-updates?hl=en" rel="noopener noreferrer"&gt;https://developer.chrome.com/blog/declarative-partial-updates?hl=en&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/WICG/declarative-partial-updates" rel="noopener noreferrer"&gt;https://github.com/WICG/declarative-partial-updates&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/GoogleChromeLabs/template-for-polyfill" rel="noopener noreferrer"&gt;https://github.com/GoogleChromeLabs/template-for-polyfill&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/GoogleChromeLabs/html-setters-polyfill" rel="noopener noreferrer"&gt;https://github.com/GoogleChromeLabs/html-setters-polyfill&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/declarative-partial-updates-sample" rel="noopener noreferrer"&gt;https://github.com/nyaomaru/declarative-partial-updates-sample&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>browser</category>
      <category>node</category>
    </item>
    <item>
      <title>The Difference Between satisfies, as, and Generics in TypeScript</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 17 Jun 2026 12:43:50 +0000</pubDate>
      <link>https://dev.to/nyaomaru/the-difference-between-satisfies-as-and-generics-in-typescript-5f9f</link>
      <guid>https://dev.to/nyaomaru/the-difference-between-satisfies-as-and-generics-in-typescript-5f9f</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who recently obsessed with playing video game of "inscryption". 🃏&lt;/p&gt;

&lt;p&gt;In TypeScript, there are several syntaxes that look like they are "specifying a type."&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;as&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;satisfies&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;generics using &lt;code&gt;T extends xxx&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But these are not the same thing.&lt;/p&gt;

&lt;p&gt;Roughly speaking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;as&lt;/code&gt; asks TypeScript to &lt;strong&gt;trust you&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;satisfies&lt;/code&gt; asks TypeScript to &lt;strong&gt;check the value&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;generics let you &lt;strong&gt;encapsulate that check inside an API&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They all look type-related, but the responsibility lives in different places.&lt;/p&gt;

&lt;p&gt;You may see AI-generated code overusing &lt;code&gt;as&lt;/code&gt;, or codebases using &lt;code&gt;satisfies&lt;/code&gt; casually, or libraries using generics to enforce stricter APIs. These patterns appear quite often in real-world development.&lt;/p&gt;

&lt;p&gt;In this article, I want to explain the difference between &lt;code&gt;as&lt;/code&gt;, &lt;code&gt;satisfies&lt;/code&gt;, and generics not as "ways to specify a type," but as &lt;strong&gt;ways to decide where type responsibility should live&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's jump in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3kkusmu97rasjez8s9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3kkusmu97rasjez8s9s.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 &lt;code&gt;as&lt;/code&gt; asks TypeScript to trust you
&lt;/h2&gt;

&lt;p&gt;In TypeScript, &lt;code&gt;as&lt;/code&gt; lets you tell the compiler:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please treat this value as this type, please 😭&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, the runtime value below is still a &lt;code&gt;string&lt;/code&gt;, but TypeScript treats it as a &lt;code&gt;number&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;number&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "string"&lt;/span&gt;

&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// TypeScript thinks this is a number, so there is no compile-time error.&lt;/span&gt;
&lt;span class="c1"&gt;// But at runtime, this throws: "value.toFixed is not a function"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important point is 👇&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;as&lt;/code&gt; does not transform the runtime value.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So using it for external data can be very dangerous.&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;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&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;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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This does not check whether the parsed value is actually a &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It only tells TypeScript:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Trust me. This is a &lt;code&gt;User&lt;/code&gt;. Maybe.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, &lt;code&gt;as&lt;/code&gt; is for cases where &lt;strong&gt;a human knows something TypeScript cannot know&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is useful, but it does not guarantee anything by itself.&lt;/p&gt;

&lt;p&gt;If you write &lt;code&gt;as&lt;/code&gt;, &lt;strong&gt;the responsibility is on you.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🔍 &lt;code&gt;satisfies&lt;/code&gt; asks TypeScript to check the value
&lt;/h2&gt;

&lt;p&gt;Now let's look at &lt;code&gt;satisfies&lt;/code&gt;, which was introduced in TypeScript 4.9.&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;type&lt;/span&gt; &lt;span class="nx"&gt;RouteConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="na"&gt;path&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="nl"&gt;label&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="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;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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;about&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;About&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="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;RouteConfig&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, TypeScript checks whether the declared value matches the expected type structure.&lt;/p&gt;

&lt;p&gt;For example, if you add a property that does not exist on a route item, TypeScript reports an error.&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;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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;about&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;About&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;nyaomaru&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Type error: nyaomaru does not exist on the route item type 😼&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;RouteConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The interesting part is 👇&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Even though &lt;code&gt;routes&lt;/code&gt; is checked against &lt;code&gt;RouteConfig&lt;/code&gt;, the type of &lt;code&gt;routes&lt;/code&gt; itself is not collapsed into &lt;code&gt;RouteConfig&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;routes&lt;/code&gt; is checked as &lt;code&gt;RouteConfig&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the inferred type of &lt;code&gt;routes&lt;/code&gt; remains close to the actual object literal you wrote.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;keyof typeof routes&lt;/code&gt; becomes the union of the actual keys, not just &lt;code&gt;string&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;type&lt;/span&gt; &lt;span class="nx"&gt;RouteName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// "home" | "about"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In other words, &lt;code&gt;satisfies&lt;/code&gt; checks that a value satisfies a given type while preserving the original inference as much as possible.&lt;/p&gt;

&lt;p&gt;Compare that with a regular type annotation.&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;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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;about&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;About&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RouteName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When you annotate &lt;code&gt;routes&lt;/code&gt; as &lt;code&gt;RouteConfig&lt;/code&gt;, TypeScript treats it as &lt;code&gt;RouteConfig&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;RouteConfig&lt;/code&gt; is a &lt;code&gt;Record&amp;lt;string, ...&amp;gt;&lt;/code&gt;, &lt;code&gt;RouteName&lt;/code&gt; becomes &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So when you want to validate a value against a type while keeping the concrete information from the actual value, &lt;code&gt;satisfies&lt;/code&gt; is often a better fit.&lt;/p&gt;

&lt;p&gt;In short:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;satisfies&lt;/code&gt; checks that a value matches a type without unnecessarily widening the value's inferred type.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🎁 Generics encapsulate the check inside an API
&lt;/h2&gt;

&lt;p&gt;At this point, you might think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Okay, then I should just use &lt;code&gt;satisfies&lt;/code&gt; everywhere. 😹&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And yes, sometimes that works well.&lt;/p&gt;

&lt;p&gt;But writing &lt;code&gt;satisfies&lt;/code&gt; every time can be annoying.&lt;br&gt;
And if someone forgets to write it, the intended check disappears.&lt;/p&gt;

&lt;p&gt;So what if we move that check into a function?&lt;/p&gt;

&lt;p&gt;That is where generics come in.&lt;/p&gt;

&lt;p&gt;As a quick recap, we could write this&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;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;RouteConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But we can also wrap the pattern inside a function using generics.&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;defineRoutes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;const&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;RouteConfig&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;routes&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;routes&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;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineRoutes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&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;Nice and clean.&lt;/p&gt;

&lt;p&gt;Now we get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validation that the value matches &lt;code&gt;RouteConfig&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;preservation of the concrete key &lt;code&gt;"home"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;no need for users to write &lt;code&gt;satisfies&lt;/code&gt; every time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But then this appears&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;const&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;RouteConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What is going on here???&lt;/p&gt;

&lt;p&gt;Let's break it down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;T extends RouteConfig&lt;/code&gt; means: "&lt;code&gt;T&lt;/code&gt; must be assignable to &lt;code&gt;RouteConfig&lt;/code&gt;."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;const T&lt;/code&gt; asks TypeScript to infer object literals and array literals as narrowly as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here, &lt;code&gt;const&lt;/code&gt; is not the same as a &lt;code&gt;const&lt;/code&gt; variable declaration.&lt;/p&gt;

&lt;p&gt;It is not about whether a value can be reassigned.&lt;/p&gt;

&lt;p&gt;It is about making generic type parameter inference preserve literal information as much as possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mu565dfikencnwt9ym9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mu565dfikencnwt9ym9.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  📊 Comparing the three with config objects
&lt;/h2&gt;

&lt;p&gt;Here is the comparison so far.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Main responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;as Type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Asks TypeScript to believe the value is that type&lt;/td&gt;
&lt;td&gt;The person writing the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;satisfies Type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks whether the value matches the type&lt;/td&gt;
&lt;td&gt;The value declaration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;T extends Type&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Constrains what an API can accept&lt;/td&gt;
&lt;td&gt;The API designer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So, in casual terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;as&lt;/code&gt; means: "Trust me. 🙏"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;satisfies&lt;/code&gt; means: "Please check if this is correct. 🔍"&lt;/li&gt;
&lt;li&gt;generics mean: "Only pass values that fit this API. 🎁"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far, we have looked at config objects.&lt;/p&gt;

&lt;p&gt;But in real-world development, this difference becomes even more important when dealing with &lt;strong&gt;external data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;API responses, &lt;code&gt;localStorage&lt;/code&gt;, URL params, and form input cannot be protected by TypeScript types alone.&lt;/p&gt;

&lt;p&gt;For those values, we need runtime guards.&lt;/p&gt;

&lt;p&gt;And once we start writing runtime guards by hand, another problem appears 👇&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The existing TypeScript type and the handwritten schema can drift apart.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🤔 Thinking through &lt;code&gt;typedStruct&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This becomes easier to understand if we use a small validator API as an example.&lt;/p&gt;

&lt;p&gt;I'll use my type guard library, &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;
        is-kit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Lightweight, zero-dependency toolkit for building `isFoo` style type guards in TypeScript. Runtime-safe 🛡️, composable 🧩, and ergonomic ✨. npm -&amp;gt; https://www.npmjs.com/package/is-kit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;is-kit&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/is-kit/main/docs/public/iskit_image.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fis-kit%2Fmain%2Fdocs%2Fpublic%2Fiskit_image.png" width="600" alt="is-kit logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://www.npmjs.com/package/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/cb829c46f855a6f184f255391ea5fe58bb6ed17112e34d4856acd61dc63179c2/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f69732d6b69742e737667" alt="npm version"&gt;
  &lt;/a&gt;
  &lt;a href="https://jsr.io/@nyaomaru/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/b6648f6bf32eb9dff2136ce8538ccb4cc6af537c1127292b5fb0de416f7a69b4/68747470733a2f2f696d672e736869656c64732e696f2f6a73722f762f406e79616f6d6172752f69732d6b6974" alt="JSR"&gt;
  &lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/9b30eeaac46ebced99b5e0dc290fd045bc34c738c66b5d95c4b6ee8fed358b34/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f69732d6b69742e737667" alt="npm downloads"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/nyaomaru/is-kit/blob/main/LICENSE" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/631cdf8c56ad84dd8657fe3371b93015b5474c517422f968ff4ec944bb28bf2f/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f69732d6b69742e7376673f73616e6974697a653d74727565" alt="License"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a lightweight, zero-dependency toolkit for building reusable TypeScript &lt;strong&gt;type guards&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It helps you write small &lt;code&gt;isFoo&lt;/code&gt; functions, compose them into &lt;strong&gt;richer runtime checks&lt;/strong&gt;, and keep &lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime-safe&lt;/strong&gt; 🛡️, &lt;strong&gt;composable&lt;/strong&gt; 🧩, and &lt;strong&gt;ergonomic&lt;/strong&gt; ✨ without asking you to adopt a heavy schema workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build and reuse &lt;strong&gt;typed guards&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compose guards&lt;/strong&gt; with &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;not&lt;/code&gt;, &lt;code&gt;oneOf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse or assert&lt;/strong&gt; &lt;code&gt;unknown&lt;/code&gt; values without a large schema framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://is-kit-docs.vercel.app/" rel="nofollow noopener noreferrer"&gt;📚 Documentation Site&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Best for &lt;strong&gt;app-internal narrowing, filtering, and reusable guards&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🤔 Why use &lt;code&gt;is-kit&lt;/code&gt;?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Tired of rewriting the same &lt;code&gt;isFoo&lt;/code&gt; checks again and again?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a good fit when you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;write reusable &lt;code&gt;isX&lt;/code&gt;&lt;/strong&gt; functions instead of one-off inline checks&lt;/li&gt;
&lt;li&gt;keep runtime validation &lt;strong&gt;lightweight and dependency-free&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;narrow values directly&lt;/strong&gt; in &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and other TypeScript control flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;compose validation logic&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a lightweight library for building small type guards like &lt;code&gt;isString&lt;/code&gt; and &lt;code&gt;isNumber&lt;/code&gt;, then composing them with helpers like &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, and &lt;code&gt;struct&lt;/code&gt;, while still allowing TypeScript narrowing to work naturally.&lt;/p&gt;

&lt;p&gt;Suppose an external API returns a &lt;code&gt;User&lt;/code&gt; like this&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;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With a normal &lt;code&gt;struct&lt;/code&gt;, we can create a guard for that object shape.&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;struct&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;isString&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="nx"&gt;isString&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 useful.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;struct&lt;/code&gt; can infer the return type from the schema, so &lt;code&gt;isUser&lt;/code&gt; can validate a value that looks like a &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But there is one problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is easy for the schema and the existing type to drift apart.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, suppose the &lt;code&gt;User&lt;/code&gt; type already exists, but we accidentally write the wrong key in the schema.&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;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;struct&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;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isString&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 &lt;code&gt;struct&lt;/code&gt; call itself is valid.&lt;/p&gt;

&lt;p&gt;From the perspective of &lt;code&gt;struct&lt;/code&gt;, this is a perfectly valid schema for:&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="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;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;displayName&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So &lt;code&gt;struct&lt;/code&gt; is useful for creating a type from a schema.&lt;/p&gt;

&lt;p&gt;But it does not synchronize that schema with an existing &lt;code&gt;User&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;A common dangerous workaround is to use &lt;code&gt;as&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;struct&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;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Predicate&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is extremely dangerous. ⚠️⚠️⚠️&lt;/p&gt;

&lt;p&gt;The schema says &lt;code&gt;displayName&lt;/code&gt;, but we are telling TypeScript&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Trust me. This is a guard for &lt;code&gt;User&lt;/code&gt;. Please 🙏&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That &lt;code&gt;as&lt;/code&gt; is not a check.&lt;/p&gt;

&lt;p&gt;It is just a self-declaration.&lt;/p&gt;

&lt;p&gt;Now, &lt;code&gt;satisfies&lt;/code&gt; can improve this.&lt;/p&gt;

&lt;p&gt;If the library exposes a schema shape type, we could write something 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;userSchema&lt;/span&gt; &lt;span class="o"&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="nx"&gt;isString&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="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now TypeScript checks whether the schema corresponds to &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we write an unknown key or forget a required key, TypeScript can report an error.&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;userSchema&lt;/span&gt; &lt;span class="o"&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="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// name is missing, and displayName does not exist on User&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is better.&lt;/p&gt;

&lt;p&gt;But writing this every time is a bit annoying.&lt;/p&gt;

&lt;p&gt;Also, if the user forgets to write &lt;code&gt;satisfies TypedStructShape&amp;lt;User&amp;gt;&lt;/code&gt;, the schema and the type can drift apart again.&lt;/p&gt;

&lt;p&gt;So we can use generics to encapsulate this check inside the API.&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;typedStruct&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="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;isString&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="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this shape, users do not need to write &lt;code&gt;satisfies TypedStructShape&amp;lt;User&amp;gt;&lt;/code&gt; every time.&lt;/p&gt;

&lt;p&gt;Instead, the &lt;code&gt;typedStruct&amp;lt;User&amp;gt;()&lt;/code&gt; API itself checks 👇&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this schema correspond to &lt;code&gt;User&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, using the framing from earlier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we are not using &lt;code&gt;as&lt;/code&gt; to ask TypeScript to trust us&lt;/li&gt;
&lt;li&gt;we are not asking users to write &lt;code&gt;satisfies&lt;/code&gt; every time&lt;/li&gt;
&lt;li&gt;we are using generics to encapsulate the check inside an API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the core idea.&lt;/p&gt;




&lt;p&gt;Let's look a little more concretely.&lt;/p&gt;

&lt;p&gt;The implementation idea for &lt;code&gt;typedStruct&lt;/code&gt; looks like this:&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;type&lt;/span&gt; &lt;span class="nx"&gt;OptionalKeys&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// WHY: Optional keys accept an empty object when picked in isolation.&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&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;-&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Pick&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;K&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;K&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RequiredKeys&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="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&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;OptionalKeys&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;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Extract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequiredKeys&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="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Predicate&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&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="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Extract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OptionalKeys&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="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;OptionalSchemaField&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;Predicate&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]&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="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NoExtraKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Shape&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Shape&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;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TypedStructFields&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;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NoExtraKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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;&amp;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;typedStruct&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;object&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TypedStructFields&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&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;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Predicate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InferSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TypedStructFields&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// WHY: `typedStruct` keeps `struct` runtime behavior while using the target&lt;/span&gt;
    &lt;span class="c1"&gt;// type only to check that hand-written guard fields stay in sync.&lt;/span&gt;
    &lt;span class="nf"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&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 can ignore the small details for now.&lt;/p&gt;

&lt;p&gt;The important part is this:&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="nx"&gt;typedStruct&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;object&lt;/span&gt;&lt;span class="o"&gt;&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;First, &lt;code&gt;typedStruct&lt;/code&gt; receives the target type &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then this part:&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;means the actual schema &lt;code&gt;S&lt;/code&gt; passed by the user must correspond to &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But here is an important detail&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;S extends TypedStructShape&amp;lt;T&amp;gt;&lt;/code&gt; alone cannot completely prevent extra keys.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TypeScript uses structural typing.&lt;/p&gt;

&lt;p&gt;If a type has the required properties, it may still satisfy a constraint even if it also has additional properties.&lt;/p&gt;

&lt;p&gt;For example, if &lt;code&gt;User&lt;/code&gt; is &lt;code&gt;{ id: string; name: string }&lt;/code&gt;, then a schema like &lt;code&gt;{ id, name, displayName }&lt;/code&gt; has all required keys and one extra key.&lt;/p&gt;

&lt;p&gt;The constraint &lt;code&gt;S extends TypedStructShape&amp;lt;User&amp;gt;&lt;/code&gt; alone may still allow that.&lt;/p&gt;

&lt;p&gt;So we use &lt;code&gt;NoExtraKeys&lt;/code&gt; to make keys that do not exist in &lt;code&gt;TypedStructShape&amp;lt;T&amp;gt;&lt;/code&gt; become &lt;code&gt;never&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's look at how &lt;code&gt;TypedStructShape&amp;lt;T&amp;gt;&lt;/code&gt; works.&lt;/p&gt;

&lt;p&gt;It separates required keys and optional keys from &lt;code&gt;T&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;type&lt;/span&gt; &lt;span class="nx"&gt;OptionalKeys&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="o"&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;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&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;-&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Pick&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;K&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;K&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RequiredKeys&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="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&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;OptionalKeys&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;&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;&lt;code&gt;OptionalKeys&amp;lt;T&amp;gt;&lt;/code&gt; is used to pick optional properties like &lt;code&gt;name?: string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;TypedStructShape&amp;lt;T&amp;gt;&lt;/code&gt; treats the keys differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;required keys become &lt;code&gt;Predicate&amp;lt;T[K]&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;optional keys become &lt;code&gt;OptionalSchemaField&amp;lt;Predicate&amp;lt;T[K]&amp;gt;&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means if a key is optional in the target type, the schema must also mark it as optional using something like &lt;code&gt;optionalKey(...)&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;type&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Extract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequiredKeys&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="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Predicate&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&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="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Extract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OptionalKeys&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="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;OptionalSchemaField&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;Predicate&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here, &lt;code&gt;OptionalSchemaField&lt;/code&gt; is the internal type used to represent a schema field created by a helper like &lt;code&gt;optionalKey(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;TypedStructFields&amp;lt;T, S&amp;gt;&lt;/code&gt; prevents schema fields from having keys that do not exist in &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Roughly speaking, &lt;code&gt;NoExtraKeys&lt;/code&gt; takes keys that exist in &lt;code&gt;S&lt;/code&gt; but not in &lt;code&gt;Shape&lt;/code&gt;, and turns them into &lt;code&gt;never&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if &lt;code&gt;User&lt;/code&gt; does not have &lt;code&gt;displayName&lt;/code&gt;, but the schema includes &lt;code&gt;displayName&lt;/code&gt;, that field becomes &lt;code&gt;never&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since a normal guard cannot be assigned to &lt;code&gt;never&lt;/code&gt;, TypeScript rejects the extra key.&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;type&lt;/span&gt; &lt;span class="nx"&gt;NoExtraKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Shape&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;Exclude&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;Shape&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;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TypedStructFields&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;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NoExtraKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TypedStructShape&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;&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;So &lt;code&gt;typedStruct&lt;/code&gt; is not just a helper that adds a type to &lt;code&gt;struct&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is an API for making an existing object type and a handwritten runtime guard harder to drift apart.&lt;/p&gt;

&lt;p&gt;One important point 👇&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;typedStruct&lt;/code&gt; does not automatically generate a runtime validator.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TypeScript types do not exist at runtime.&lt;/p&gt;

&lt;p&gt;Also, we cannot automatically generate &lt;code&gt;isString&lt;/code&gt; or &lt;code&gt;isNumber&lt;/code&gt; from the &lt;code&gt;User&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;So the guard itself still has to be written by hand.&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;typedStruct&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="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;isString&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="nx"&gt;isString&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;But the API can check whether the handwritten schema is still in sync with &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's the tasty part of &lt;code&gt;typedStruct&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is especially useful when you already have API response types generated from something like OpenAPI.&lt;/p&gt;

&lt;p&gt;Here, imagine &lt;code&gt;ApiResponse&lt;/code&gt; is a generated response type.&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;type&lt;/span&gt; &lt;span class="nx"&gt;ArticleResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/articles/{id}&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;get&lt;/span&gt;&lt;span class="dl"&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;isArticleResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;typedStruct&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ArticleResponse&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;optionalKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isString&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;Here, &lt;code&gt;optionalKey(isString)&lt;/code&gt; means the object key itself may be missing.&lt;/p&gt;

&lt;p&gt;But if the key exists, the value must pass &lt;code&gt;isString&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So as a runtime check, this does not allow &lt;code&gt;{ summary: undefined }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;code&gt;summary&lt;/code&gt; exists, it must be a &lt;code&gt;string&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;if &lt;code&gt;summary&lt;/code&gt; does not exist, that is OK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a small but important distinction.&lt;/p&gt;

&lt;p&gt;The idea is not ❌&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The TypeScript property is optional, so I can omit the schema field.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, the idea is ✅&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The runtime guard should explicitly say that this key is optional.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That way, when the target type changes, the mismatch becomes easier to notice.&lt;/p&gt;

&lt;p&gt;Also, the compile-time key check in &lt;code&gt;typedStruct&lt;/code&gt; and the runtime check in &lt;code&gt;struct(..., { exact: true })&lt;/code&gt; are different things.&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;typedStruct&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="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="nx"&gt;isString&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="nx"&gt;isString&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;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;NoExtraKeys&lt;/code&gt; is a compile-time check that prevents the schema author from writing extra keys in the schema.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;code&gt;{ exact: true }&lt;/code&gt; is a runtime check that rejects actual runtime objects if they contain keys outside the schema.&lt;/p&gt;

&lt;p&gt;They may look similar, but they check different things.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NoExtraKeys&lt;/code&gt; checks mistakes in the handwritten schema&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{ exact: true }&lt;/code&gt; checks extra keys in runtime data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubximlmkrvqirlfv4owh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubximlmkrvqirlfv4owh.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is exactly the theme of the article.&lt;/p&gt;

&lt;p&gt;Instead of silencing TypeScript with &lt;code&gt;as&lt;/code&gt;, we take a &lt;code&gt;satisfies&lt;/code&gt;-like check and encapsulate it inside an API with generics.&lt;/p&gt;

&lt;p&gt;One more important design point 👇&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The return type is not directly forced to &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, it is inferred from the actual &lt;code&gt;fields&lt;/code&gt; passed to the function.&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="nx"&gt;Predicate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InferSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TypedStructFields&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&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;So &lt;code&gt;T&lt;/code&gt; is used to check whether the schema is in sync with the existing type.&lt;/p&gt;

&lt;p&gt;But the actual guard type is inferred from the schema, just like &lt;code&gt;struct&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In other words, we are not doing this&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;as&lt;/span&gt; &lt;span class="nx"&gt;Predicate&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Instead, the existing type synchronization is handled by the generic constraint, while the actual guard type is derived from the schema.&lt;/p&gt;


&lt;h2&gt;
  
  
  🎯 Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at the difference between &lt;code&gt;as&lt;/code&gt;, &lt;code&gt;satisfies&lt;/code&gt;, and generics through the lens of &lt;strong&gt;where type responsibility lives&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;Syntax&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Where the responsibility lives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;as Type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Asks TypeScript to trust you 🙏&lt;/td&gt;
&lt;td&gt;The person writing the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;satisfies Type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks whether a value matches a type 🔍&lt;/td&gt;
&lt;td&gt;The value declaration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;T extends Type&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Constrains what an API accepts 🎁&lt;/td&gt;
&lt;td&gt;The API designer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;as&lt;/code&gt; is not evil.&lt;/p&gt;

&lt;p&gt;Sometimes TypeScript cannot know something, and a human has to take responsibility for that missing information.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;as&lt;/code&gt; is not a check.&lt;/p&gt;

&lt;p&gt;When you use &lt;code&gt;as&lt;/code&gt; for external data or handwritten schemas, the code can look type-safe while skipping the actual validation.&lt;/p&gt;

&lt;p&gt;AI-generated code often papers over uncertainty with &lt;code&gt;as&lt;/code&gt;, so this is something worth watching for.&lt;/p&gt;

&lt;p&gt;When you really want to check whether a value has the right shape, first consider whether &lt;code&gt;satisfies&lt;/code&gt; can help.&lt;/p&gt;

&lt;p&gt;And when you find yourself repeating the same &lt;code&gt;satisfies&lt;/code&gt; pattern again and again, consider moving that constraint into an API with generics.&lt;/p&gt;

&lt;p&gt;Personally, I find this framing useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;as&lt;/code&gt; means: "Trust me."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;satisfies&lt;/code&gt; means: "Please check if this is correct."&lt;/li&gt;
&lt;li&gt;generics mean: "Only pass values that fit this API."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;typedStruct&lt;/code&gt; in &lt;code&gt;is-kit&lt;/code&gt; is a good example of this difference.&lt;/p&gt;

&lt;p&gt;When you want to keep an existing object type and a runtime guard in sync, do not silence TypeScript with &lt;code&gt;as&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, you can take a &lt;code&gt;satisfies&lt;/code&gt;-like check and encapsulate it inside an API using generics.&lt;/p&gt;

&lt;p&gt;This makes the safer way the natural way to use the API, and moves type responsibility closer to the API design.&lt;/p&gt;

&lt;p&gt;Happy TypeScript life. 🫰&lt;/p&gt;


&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;The user-defined type guard library used in this article is &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;
        is-kit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Lightweight, zero-dependency toolkit for building `isFoo` style type guards in TypeScript. Runtime-safe 🛡️, composable 🧩, and ergonomic ✨. npm -&amp;gt; https://www.npmjs.com/package/is-kit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;is-kit&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/is-kit/main/docs/public/iskit_image.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fis-kit%2Fmain%2Fdocs%2Fpublic%2Fiskit_image.png" width="600" alt="is-kit logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://www.npmjs.com/package/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/cb829c46f855a6f184f255391ea5fe58bb6ed17112e34d4856acd61dc63179c2/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f69732d6b69742e737667" alt="npm version"&gt;
  &lt;/a&gt;
  &lt;a href="https://jsr.io/@nyaomaru/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/b6648f6bf32eb9dff2136ce8538ccb4cc6af537c1127292b5fb0de416f7a69b4/68747470733a2f2f696d672e736869656c64732e696f2f6a73722f762f406e79616f6d6172752f69732d6b6974" alt="JSR"&gt;
  &lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/9b30eeaac46ebced99b5e0dc290fd045bc34c738c66b5d95c4b6ee8fed358b34/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f69732d6b69742e737667" alt="npm downloads"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/nyaomaru/is-kit/blob/main/LICENSE" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/631cdf8c56ad84dd8657fe3371b93015b5474c517422f968ff4ec944bb28bf2f/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f69732d6b69742e7376673f73616e6974697a653d74727565" alt="License"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a lightweight, zero-dependency toolkit for building reusable TypeScript &lt;strong&gt;type guards&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It helps you write small &lt;code&gt;isFoo&lt;/code&gt; functions, compose them into &lt;strong&gt;richer runtime checks&lt;/strong&gt;, and keep &lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime-safe&lt;/strong&gt; 🛡️, &lt;strong&gt;composable&lt;/strong&gt; 🧩, and &lt;strong&gt;ergonomic&lt;/strong&gt; ✨ without asking you to adopt a heavy schema workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build and reuse &lt;strong&gt;typed guards&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compose guards&lt;/strong&gt; with &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;not&lt;/code&gt;, &lt;code&gt;oneOf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse or assert&lt;/strong&gt; &lt;code&gt;unknown&lt;/code&gt; values without a large schema framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://is-kit-docs.vercel.app/" rel="nofollow noopener noreferrer"&gt;📚 Documentation Site&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Best for &lt;strong&gt;app-internal narrowing, filtering, and reusable guards&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🤔 Why use &lt;code&gt;is-kit&lt;/code&gt;?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Tired of rewriting the same &lt;code&gt;isFoo&lt;/code&gt; checks again and again?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a good fit when you want to:&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;write reusable &lt;code&gt;isX&lt;/code&gt;&lt;/strong&gt; functions instead of one-off inline checks&lt;/li&gt;

&lt;li&gt;keep runtime validation &lt;strong&gt;lightweight and dependency-free&lt;/strong&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;narrow values directly&lt;/strong&gt; in &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and other TypeScript control flow&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;compose validation logic&lt;/strong&gt;…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;code&gt;typedStruct&lt;/code&gt; was added in v1.8, so please give it a try. 😻&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>programming</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>CHANGELOG.md is for Both Humans and AI Now, So Let’s Automate It</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 03 Jun 2026 12:33:59 +0000</pubDate>
      <link>https://dev.to/nyaomaru/changelogmd-is-for-both-humans-and-ai-now-so-lets-automate-it-18dp</link>
      <guid>https://dev.to/nyaomaru/changelogmd-is-for-both-humans-and-ai-now-so-lets-automate-it-18dp</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I’m &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who recently discovered the deliciousness of a cheese whose name sounds a bit like Al Capone. 🧀&lt;/p&gt;

&lt;p&gt;Are you building personal projects?&lt;/p&gt;

&lt;p&gt;Or contributing to OSS?&lt;/p&gt;

&lt;p&gt;Or maybe you are busy playing &lt;strong&gt;Resident Evil 9 🧟 and CloverPit 🍀&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Whatever the case, I respect your admirable dedication as an engineer.&lt;/p&gt;

&lt;p&gt;Now, when managing OSS on GitHub, one file that becomes very important is &lt;code&gt;CHANGELOG.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why is it important?&lt;/p&gt;

&lt;p&gt;For example, when a library I use gets updated, the first thing I usually check is &lt;code&gt;CHANGELOG.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Recently, I have been experimenting with an AI triage workflow. For Dependabot updates, I feed release notes and &lt;code&gt;CHANGELOG.md&lt;/code&gt; into AI, then ask Claude Code to evaluate the risk and help with triage.&lt;/p&gt;

&lt;p&gt;This has been very useful. I do not have to check everything manually, and that makes me happy 😺&lt;/p&gt;

&lt;p&gt;In other words, &lt;code&gt;CHANGELOG.md&lt;/code&gt; is becoming a document that is not only read by humans, but also read by AI as input for dependency update decisions.&lt;/p&gt;

&lt;p&gt;So, &lt;code&gt;CHANGELOG.md&lt;/code&gt; will continue to be very useful in the coming era.&lt;/p&gt;

&lt;p&gt;But surprisingly, many projects do not maintain it well.&lt;/p&gt;

&lt;p&gt;For example, even some very useful React development tools sometimes ship silent releases without any release notes at all.&lt;/p&gt;

&lt;p&gt;When that happens, users have to dig through commit history and figure out things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What new features were added?&lt;/li&gt;
&lt;li&gt;What changed?&lt;/li&gt;
&lt;li&gt;Is it safe to upgrade?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, busy engineers do not have time for that. I am not that busy, but if I had that time, I would rather play &lt;strong&gt;CloverPit 🍀&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, to protect my precious game time, I built an OSS tool that automatically generates &lt;code&gt;CHANGELOG.md&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/changelog-bot" rel="noopener noreferrer"&gt;
        changelog-bot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Automatic create changelog with AI. 🤖 It provides CLI and github actions. 🚀  https://www.npmjs.com/package/&lt;a class="mentioned-user" href="https://dev.to/nyaomaru"&gt;@nyaomaru&lt;/a&gt;/changelog-bot
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/changelog-bot/main/public/ChangelogBot_logo02.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fchangelog-bot%2Fmain%2Fpublic%2FChangelogBot_logo02.png" alt="changelog-bot type logo" width="360"&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
    &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/changelog-bot/main/public/ChangelogBot_image.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fchangelog-bot%2Fmain%2Fpublic%2FChangelogBot_image.png" width="600px" alt="changelog-bot logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Releases should feel exciting, not tedious.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@nyaomaru/changelog-bot&lt;/code&gt; 🤖 turns your Git history and release notes into a polished changelog entry (and optional PR) in a single run. Drop it into CI, run it locally, or hand it to your release captain—either way, you ship with a crisp changelog and zero copy-paste fatigue.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why changelog-bot?&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated storytelling:&lt;/strong&gt; Combines commit history, PR titles, and release notes to produce human-ready changelog sections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM superpowers (optional):&lt;/strong&gt; Connect OpenAI or Anthropic for tone-aware summaries or skip API keys entirely and rely on a robust heuristic fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR-ready output:&lt;/strong&gt; Can open a pull request with updated changelog, compare links, and release notes already wired up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe defaults:&lt;/strong&gt; Detects duplicate versions, keeps compare links current, and won’t fail a release if AI is down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI-native:&lt;/strong&gt; Works as a GitHub Action, reusable workflow, or plain CLI—no fragile scripting required.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-alert markdown-alert-important"&gt;
&lt;p class="markdown-alert-title"&gt;Important&lt;/p&gt;
&lt;p&gt;This project is currently in its early stages…&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/changelog-bot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;In this article, I will walk through the changes from v0.1 and what I am planning next.&lt;/p&gt;

&lt;p&gt;Let’s take a look!&lt;/p&gt;




&lt;h2&gt;
  
  
  🤖 What is changelog-bot?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/changelog-bot" rel="noopener noreferrer"&gt;&lt;code&gt;changelog-bot&lt;/code&gt;&lt;/a&gt; is a CLI and GitHub Action that automatically creates a Pull Request to update &lt;code&gt;CHANGELOG.md&lt;/code&gt; based on Git history, Pull Requests, and GitHub Release Notes.&lt;/p&gt;

&lt;p&gt;What it does is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collect commits and PRs from the previous release to the current release&lt;/li&gt;
&lt;li&gt;Categorize changes into sections such as &lt;code&gt;Added&lt;/code&gt;, &lt;code&gt;Changed&lt;/code&gt;, &lt;code&gt;Fixed&lt;/code&gt;, &lt;code&gt;Docs&lt;/code&gt;, and &lt;code&gt;Chore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add a new release section to &lt;code&gt;CHANGELOG.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a Pull Request with that change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you configure an AI provider, it can use &lt;code&gt;OpenAI&lt;/code&gt;, &lt;code&gt;Anthropic&lt;/code&gt;, or &lt;code&gt;Gemini&lt;/code&gt; to generate a more readable changelog section.&lt;/p&gt;

&lt;p&gt;On the other hand, it also works with a fallback mode when no API key is provided.&lt;/p&gt;

&lt;p&gt;So it is totally fine even if you do not use AI.&lt;/p&gt;

&lt;p&gt;In other words, this is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A tool that can do nothing without AI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is more like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An automation tool that works better with AI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the key idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐾 What was available up to v0.1
&lt;/h2&gt;

&lt;p&gt;Up to v0.1, I focused on building the foundation of changelog-bot.&lt;/p&gt;

&lt;p&gt;It already supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI and GitHub Action usage&lt;/li&gt;
&lt;li&gt;Changelog generation from Git history&lt;/li&gt;
&lt;li&gt;Importing GitHub Release Notes&lt;/li&gt;
&lt;li&gt;OpenAI and Anthropic providers&lt;/li&gt;
&lt;li&gt;Heuristic fallback&lt;/li&gt;
&lt;li&gt;Dry-run mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, the basic experience was already there:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Automatically update &lt;code&gt;CHANGELOG.md&lt;/code&gt; during a release workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I also introduced it in this earlier article:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/generate-changelogmd-automatically-26g1" class="crayons-story__hidden-navigation-link"&gt;Generate CHANGELOG.md Automatically 🤖&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
              
              &lt;div id="story-author-preview-content-3072785" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/generate-changelogmd-automatically-26g1" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 30 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/generate-changelogmd-automatically-26g1" id="article-link-3072785"&gt;
          Generate CHANGELOG.md Automatically 🤖
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ci"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ci&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/generate-changelogmd-automatically-26g1" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;14&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/generate-changelogmd-automatically-26g1#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              3&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;But there were still some problems.&lt;/p&gt;

&lt;p&gt;In particular, even though users could choose an AI provider, there was still no strong way to adjust the language or writing style of the generated &lt;code&gt;CHANGELOG.md&lt;/code&gt; for each project.&lt;/p&gt;

&lt;p&gt;For example, there was no good way to pass project-specific rules such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Write it in Japanese.”&lt;/li&gt;
&lt;li&gt;“Group internal changes under Chore.”&lt;/li&gt;
&lt;li&gt;“Group dependency updates together.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the customization experience was still weak.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 v0.2: Gemini provider support
&lt;/h2&gt;

&lt;p&gt;In v0.2, I added &lt;strong&gt;Gemini&lt;/strong&gt; as a new LLM provider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcjt2918edvubhn3xz4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcjt2918edvubhn3xz4c.png" alt="Changelog-bot power up"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now changelog-bot supports these three providers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OpenAI&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Anthropic&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Gemini&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can specify Gemini from the CLI like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;changelog-bot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider&lt;/span&gt; gemini &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--release-tag&lt;/span&gt; HEAD &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--release-name&lt;/span&gt; 0.2.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also use it from GitHub Actions:&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;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GEMINI_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I also added &lt;strong&gt;dry-run diagnostics&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Provider: gemini
Model: gemini-3.5-flash
AI used: &lt;span class="nb"&gt;true
&lt;/span&gt;Fallback reasons: none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This makes it easier to check whether the AI provider was actually used, or whether changelog-bot fell back to the heuristic mode.&lt;/p&gt;

&lt;p&gt;It is a small feature, but I think this kind of observability is very important for CI tools.&lt;/p&gt;

&lt;p&gt;When something runs in CI, you want to quickly understand what actually happened.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 v0.3: Changelog customization
&lt;/h2&gt;

&lt;p&gt;In v0.3, I added customization support for changelog generation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa126nq69tgqofz9k1uux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa126nq69tgqofz9k1uux.png" alt="Changelog-bot note"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The newly added options are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--language&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specifies the language of the generated &lt;code&gt;CHANGELOG.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--instructions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Passes inline instructions to the AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--instructions-file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Passes instructions to the AI from a file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example, if you want to generate a changelog in Dutch, you can write:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;changelog-bot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider&lt;/span&gt; gemini &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--release-tag&lt;/span&gt; HEAD &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--release-name&lt;/span&gt; 0.3.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--language&lt;/span&gt; &lt;span class="nb"&gt;nl&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instructions&lt;/span&gt; &lt;span class="s2"&gt;"Schrijf kort en duidelijk in het Nederlands. Groepeer interne wijzigingen onder Chore."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also define project-specific rules in a file.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;changelog-bot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider&lt;/span&gt; openai &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--release-tag&lt;/span&gt; HEAD &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--release-name&lt;/span&gt; 0.3.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--language&lt;/span&gt; &lt;span class="nb"&gt;nl&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instructions-file&lt;/span&gt; .github/changelog-instructions.md &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For example, &lt;code&gt;.github/changelog-instructions.md&lt;/code&gt; can contain rules like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Write concise release notes in Dutch.

Rules:
&lt;span class="p"&gt;
-&lt;/span&gt; Focus on user-facing changes.
&lt;span class="p"&gt;-&lt;/span&gt; Group internal refactors under Chore.
&lt;span class="p"&gt;-&lt;/span&gt; Group dependency updates together.
&lt;span class="p"&gt;-&lt;/span&gt; Avoid mentioning commit hashes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this, changelog-bot is no longer just “a tool that generates changelogs with AI.”&lt;/p&gt;

&lt;p&gt;It is moving toward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A tool that can generate a &lt;code&gt;CHANGELOG.md&lt;/code&gt; that feels right for each project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also, when customization is enabled, changelog-bot does not simply use GitHub Release Notes as-is, even if they exist.&lt;/p&gt;

&lt;p&gt;Instead, it passes them to the model and lets the model rewrite them.&lt;/p&gt;

&lt;p&gt;This means GitHub Release Notes can still be used as source material, while the final output reflects the specified &lt;code&gt;language&lt;/code&gt; and &lt;code&gt;instructions&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  🥌 Roadmap toward v1
&lt;/h2&gt;

&lt;p&gt;With customization added in v0.3, the shape of v1 is becoming much clearer.&lt;/p&gt;

&lt;p&gt;Here are the main things I want to add before v1.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Config file support
&lt;/h3&gt;

&lt;p&gt;Passing CLI options every time is annoying, so I want to support config files.&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gemini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instructionsFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".github/changelog-instructions.md"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The goal is to make the same configuration usable from the CLI, GitHub Actions, and reusable workflows.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Choosing behavior when an AI provider fails
&lt;/h3&gt;

&lt;p&gt;Right now, changelog-bot falls back when an AI provider fails.&lt;/p&gt;

&lt;p&gt;This is convenient, but some projects may want the workflow to fail if AI generation fails.&lt;/p&gt;

&lt;p&gt;So I am considering options like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--fail-on-llm-error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--require-provider&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-ai&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since changelog-bot is intended to be used in CI, users should be able to choose between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Fail when AI generation fails”&lt;/li&gt;
&lt;li&gt;“Fallback and continue”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both behaviors are valid depending on the project.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. JSON report output
&lt;/h3&gt;

&lt;p&gt;Dry-run diagnostics already exist, but I also want to add a JSON report.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;changelog-bot &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="nt"&gt;--report-json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For example, it would be useful to output machine-readable information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provider&lt;/li&gt;
&lt;li&gt;model&lt;/li&gt;
&lt;li&gt;aiUsed&lt;/li&gt;
&lt;li&gt;fallbackReasons&lt;/li&gt;
&lt;li&gt;generated sections&lt;/li&gt;
&lt;li&gt;PR title&lt;/li&gt;
&lt;li&gt;labels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This should make it easier to integrate changelog-bot with other CI tools and bots.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Better changelog quality
&lt;/h3&gt;

&lt;p&gt;I also want to keep improving the quality of the generated changelog.&lt;/p&gt;

&lt;p&gt;Some areas I want to improve are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grouping dependency updates&lt;/li&gt;
&lt;li&gt;Detecting breaking changes&lt;/li&gt;
&lt;li&gt;Label-based include / exclude rules&lt;/li&gt;
&lt;li&gt;Custom section names&lt;/li&gt;
&lt;li&gt;Better duplicate detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Especially in OSS, what should be included in a changelog differs from project to project.&lt;/p&gt;

&lt;p&gt;I want changelog-bot to handle those differences better.&lt;/p&gt;


&lt;h2&gt;
  
  
  🎯 Summary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CHANGELOG.md&lt;/code&gt; is a very plain file, but for users, it is an important document.&lt;/p&gt;

&lt;p&gt;Recently, it is also becoming more common to let AI read it and use it as input for dependency update decisions.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;CHANGELOG.md&lt;/code&gt; should not just be generated somehow.&lt;/p&gt;

&lt;p&gt;It should be generated in a readable way that fits each project’s context.&lt;/p&gt;

&lt;p&gt;But writing it carefully by hand every time is honestly a pain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/changelog-bot" rel="noopener noreferrer"&gt;&lt;code&gt;changelog-bot&lt;/code&gt;&lt;/a&gt; is a tool for automating that process with Git history, Pull Requests, GitHub Release Notes, and &lt;strong&gt;AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In v0.2, I added Gemini support.&lt;/p&gt;

&lt;p&gt;In v0.3, I added customization through &lt;code&gt;language&lt;/code&gt;, &lt;code&gt;instructions&lt;/code&gt;, and &lt;code&gt;instructions file&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With these changes, changelog-bot is moving from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Just generating a changelog automatically&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generating a changelog that fits each project&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;v1 is getting closer.&lt;/p&gt;

&lt;p&gt;If you are interested, please give the repository a ⭐ and watch over it warmly!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/changelog-bot" rel="noopener noreferrer"&gt;
        changelog-bot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Automatic create changelog with AI. 🤖 It provides CLI and github actions. 🚀  https://www.npmjs.com/package/&lt;a class="mentioned-user" href="https://dev.to/nyaomaru"&gt;@nyaomaru&lt;/a&gt;/changelog-bot
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/changelog-bot/main/public/ChangelogBot_logo02.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fchangelog-bot%2Fmain%2Fpublic%2FChangelogBot_logo02.png" alt="changelog-bot type logo" width="360"&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
    &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/changelog-bot/main/public/ChangelogBot_image.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fchangelog-bot%2Fmain%2Fpublic%2FChangelogBot_image.png" width="600px" alt="changelog-bot logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Releases should feel exciting, not tedious.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@nyaomaru/changelog-bot&lt;/code&gt; 🤖 turns your Git history and release notes into a polished changelog entry (and optional PR) in a single run. Drop it into CI, run it locally, or hand it to your release captain—either way, you ship with a crisp changelog and zero copy-paste fatigue.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why changelog-bot?&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated storytelling:&lt;/strong&gt; Combines commit history, PR titles, and release notes to produce human-ready changelog sections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM superpowers (optional):&lt;/strong&gt; Connect OpenAI or Anthropic for tone-aware summaries or skip API keys entirely and rely on a robust heuristic fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR-ready output:&lt;/strong&gt; Can open a pull request with updated changelog, compare links, and release notes already wired up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe defaults:&lt;/strong&gt; Detects duplicate versions, keeps compare links current, and won’t fail a release if AI is down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI-native:&lt;/strong&gt; Works as a GitHub Action, reusable workflow, or plain CLI—no fragile scripting required.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-alert markdown-alert-important"&gt;
&lt;p class="markdown-alert-title"&gt;Important&lt;/p&gt;
&lt;p&gt;This project is currently in its early stages…&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/changelog-bot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Now let’s auto-generate our &lt;code&gt;CHANGELOG.md&lt;/code&gt; files and use the saved time to play some games! 🧟🍀&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>changelog</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Which OpenAPI Codegen Should You Choose? openapi-typescript vs hey-api vs Orval vs Kubb</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 20 May 2026 13:34:57 +0000</pubDate>
      <link>https://dev.to/nyaomaru/which-openapi-codegen-should-you-choose-openapi-typescript-vs-hey-api-vs-orval-vs-kubb-100p</link>
      <guid>https://dev.to/nyaomaru/which-openapi-codegen-should-you-choose-openapi-typescript-vs-hey-api-vs-orval-vs-kubb-100p</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who thought OpenAPI codegen would make API clients easier, but somehow ended up lost in a forest of generated files. 🌳🌳🌳&lt;/p&gt;

&lt;p&gt;Writing API clients by hand is painful.&lt;/p&gt;

&lt;p&gt;Usually, we want things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generated TypeScript types&lt;/li&gt;
&lt;li&gt;generated request functions&lt;/li&gt;
&lt;li&gt;maybe &lt;code&gt;TanStack Query&lt;/code&gt; hooks&lt;/li&gt;
&lt;li&gt;maybe &lt;code&gt;MSW&lt;/code&gt; mocks&lt;/li&gt;
&lt;li&gt;maybe even &lt;code&gt;Zod&lt;/code&gt; schemas&lt;/li&gt;
&lt;li&gt;and, if possible, everything growing automatically from OpenAPI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where OpenAPI codegen tools are useful.&lt;/p&gt;

&lt;p&gt;But after comparing several tools with a fairly large real-world schema, I realized something important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAPI codegen is not only about what a tool can generate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The generated output itself also matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many files it creates&lt;/li&gt;
&lt;li&gt;how fast generation is&lt;/li&gt;
&lt;li&gt;how friendly it is to linting&lt;/li&gt;
&lt;li&gt;how easy it is for the IDE to index&lt;/li&gt;
&lt;li&gt;how errors are modeled&lt;/li&gt;
&lt;li&gt;how authentication is handled&lt;/li&gt;
&lt;li&gt;how much generated code your team is willing to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, I compared the following four tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Version tested&lt;/th&gt;
&lt;th&gt;Main setup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v7.13.0&lt;/td&gt;
&lt;td&gt;types only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@hey-api/openapi-ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v0.96.1&lt;/td&gt;
&lt;td&gt;types + SDK + fetch client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v8.8.1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mode: "tags-split"&lt;/code&gt; / &lt;code&gt;client: "fetch"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v4.37.2&lt;/td&gt;
&lt;td&gt;client / types / schemas generated by plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frr61d7ozn3dwxht4s5jo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frr61d7ozn3dwxht4s5jo.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The OpenAPI schema used for this comparison was roughly this size. (It was generated by &lt;code&gt;drf-spectacular&lt;/code&gt;)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;openapi.yaml&lt;/code&gt; lines&lt;/td&gt;
&lt;td&gt;about 75,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;openapi.yaml&lt;/code&gt; size&lt;/td&gt;
&lt;td&gt;about 2 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;operations&lt;/td&gt;
&lt;td&gt;about 1,200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Some endpoint names, schema names, type names, and directory names in this article are simplified or replaced for publication. Also, these libraries move fast, so the generated output and issues described here are based on the versions tested at the time of writing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a small API, honestly, any of these tools may work fine.&lt;/p&gt;

&lt;p&gt;But at this scale, differences become very visible in generation speed, file count, lint behavior, enum handling, error models, and day-to-day maintainability.&lt;/p&gt;

&lt;p&gt;Let's walk through the comparison.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 TL;DR
&lt;/h2&gt;

&lt;p&gt;My conclusion is this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Tool I would choose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;You only need TypeScript types&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want operationId-based SDK functions and interceptors&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@hey-api/openapi-ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want &lt;code&gt;TanStack Query&lt;/code&gt;, &lt;code&gt;Zod&lt;/code&gt;, &lt;code&gt;MSW&lt;/code&gt;, or &lt;code&gt;Faker&lt;/code&gt; generated from OpenAPI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You value 1 operation = 1 file and plugin-based codegen architecture&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For my use case, &lt;strong&gt;hey-api felt the most practical&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The reasons were simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it generates functions from &lt;code&gt;operationId&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;arguments are consistently shaped like &lt;code&gt;{ query, path, body }&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;it has a result-style error model&lt;/li&gt;
&lt;li&gt;it supports axios-like interceptors&lt;/li&gt;
&lt;li&gt;the generated output is much lighter than Orval or Kubb&lt;/li&gt;
&lt;li&gt;it still leaves room for &lt;code&gt;TanStack Query&lt;/code&gt; and &lt;code&gt;Zod&lt;/code&gt; plugins later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, if your project already uses &lt;code&gt;openapi-typescript + openapi-fetch&lt;/code&gt; and you are happy with it, I do not think you need to migrate immediately.&lt;/p&gt;

&lt;p&gt;The difference is not huge enough to justify migration by itself.&lt;/p&gt;

&lt;p&gt;I would consider switching only when you start feeling pain around SDK functions, interceptors, result handling, mocks, generated hooks, or schema-driven tooling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this article, I will call &lt;code&gt;@hey-api/openapi-ts&lt;/code&gt; simply &lt;code&gt;hey-api&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🌓 Two families of OpenAPI codegen tools
&lt;/h2&gt;

&lt;p&gt;Before comparing each tool one by one, I think it is easier to divide them into two families.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;Family A: thin output, client-focused&lt;/th&gt;
&lt;th&gt;Family B: SDK-first, ecosystem generation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Representative tools&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;openapi-typescript&lt;/code&gt; / &lt;code&gt;hey-api&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Orval&lt;/code&gt; / &lt;code&gt;Kubb&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main purpose&lt;/td&gt;
&lt;td&gt;accurate types and lightweight runtime&lt;/td&gt;
&lt;td&gt;generated SDK + hooks + mocks + validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error model&lt;/td&gt;
&lt;td&gt;result-style &lt;code&gt;{ data, error, response }&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;wrapper / throw / client-dependent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output size&lt;/td&gt;
&lt;td&gt;small: types only or compact SDK&lt;/td&gt;
&lt;td&gt;large: tag-level or operation-level files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem features&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TanStack Query&lt;/code&gt; / &lt;code&gt;Zod&lt;/code&gt; depending on tool&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TanStack Query&lt;/code&gt; / &lt;code&gt;Zod&lt;/code&gt; / &lt;code&gt;MSW&lt;/code&gt; / &lt;code&gt;Faker&lt;/code&gt; / &lt;code&gt;SWR&lt;/code&gt; etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration style&lt;/td&gt;
&lt;td&gt;minimal&lt;/td&gt;
&lt;td&gt;codegen platform&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;openapi-typescript&lt;/code&gt; and &lt;code&gt;Kubb&lt;/code&gt; are both OpenAPI codegen tools, but they are not trying to solve the same problem.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;openapi-typescript&lt;/code&gt; mainly focuses on generating accurate TypeScript types.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Kubb&lt;/code&gt;, on the other hand, is closer to a codegen platform that can generate clients, types, schemas, mocks, and other assets in a structured way.&lt;/p&gt;

&lt;p&gt;Comparing them directly is a bit like comparing a kitchen knife with a full kitchen. 🧑‍🍳🔪&lt;/p&gt;

&lt;p&gt;Both are related to cooking, but they are not the same kind of thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏎️ Generation speed
&lt;/h2&gt;

&lt;p&gt;First, generation speed.&lt;/p&gt;

&lt;p&gt;I measured each command three times on &lt;code&gt;WSL2&lt;/code&gt; / &lt;code&gt;Node v24.14&lt;/code&gt; using &lt;code&gt;npx &amp;lt;cmd&amp;gt;&lt;/code&gt;. So the numbers include &lt;code&gt;npx&lt;/code&gt; startup overhead. (a custom-built PC)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;1st run&lt;/th&gt;
&lt;th&gt;2nd run&lt;/th&gt;
&lt;th&gt;3rd run&lt;/th&gt;
&lt;th&gt;Average&lt;/th&gt;
&lt;th&gt;Max RSS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.66 s&lt;/td&gt;
&lt;td&gt;1.50 s&lt;/td&gt;
&lt;td&gt;1.40 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;about 1.5 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;about 335 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@hey-api/openapi-ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8.74 s&lt;/td&gt;
&lt;td&gt;8.84 s&lt;/td&gt;
&lt;td&gt;6.43 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;about 8.0 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;about 495 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6.01 s&lt;/td&gt;
&lt;td&gt;6.12 s&lt;/td&gt;
&lt;td&gt;4.48 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;about 5.5 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;about 415 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;17.29 s&lt;/td&gt;
&lt;td&gt;19.82 s&lt;/td&gt;
&lt;td&gt;17.13 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;about 18.1 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;about 880 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Relative to &lt;code&gt;openapi-typescript&lt;/code&gt;, the difference looked like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Relative speed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;about 3.7x slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;about 5.3x slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;about 12x slower&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why openapi-typescript is so fast
&lt;/h3&gt;

&lt;p&gt;In this setup, &lt;code&gt;openapi-typescript&lt;/code&gt; only generates types into a single file.&lt;/p&gt;

&lt;p&gt;The workflow is basically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;read the schema&lt;/li&gt;
&lt;li&gt;convert it into TypeScript types&lt;/li&gt;
&lt;li&gt;write one file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That means very little file I/O.&lt;/p&gt;

&lt;p&gt;The generated output was just &lt;code&gt;types.gen.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At about 1.5 seconds, it is fast enough that running it in CI, pre-commit, or during local development barely feels like a problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Kubb is slower
&lt;/h3&gt;

&lt;p&gt;Kubb has a different philosophy.&lt;/p&gt;

&lt;p&gt;It splits things much more aggressively.&lt;/p&gt;

&lt;p&gt;In this test, I generated operation-level clients, operation-level types, and schemas.&lt;/p&gt;

&lt;p&gt;So the cost is not only computation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The number of files written to disk becomes a major factor.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The structure is clean from a bundling and organization perspective, but with around 1,200 operations, the file system cost becomes noticeable.&lt;/p&gt;

&lt;p&gt;For this kind of schema, I would not treat Kubb as something I casually run on every small change unless the generation target is narrowed.&lt;/p&gt;

&lt;p&gt;It feels more suitable for a workflow where generated files are committed, generated in CI, or carefully scoped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orval was faster than I expected
&lt;/h3&gt;

&lt;p&gt;I expected Orval to be slower.&lt;/p&gt;

&lt;p&gt;It can generate things like &lt;code&gt;TanStack Query&lt;/code&gt;, &lt;code&gt;Zod&lt;/code&gt;, and &lt;code&gt;MSW&lt;/code&gt;, so I assumed it would be heavier.&lt;/p&gt;

&lt;p&gt;But with &lt;code&gt;tags-split&lt;/code&gt;, the main functions are grouped by tag.&lt;/p&gt;

&lt;p&gt;In my schema, there were about 25 tags.&lt;/p&gt;

&lt;p&gt;So Orval was not generating one giant file, but it also was not generating one file per operation.&lt;/p&gt;

&lt;p&gt;That middle ground worked well for speed.&lt;/p&gt;

&lt;p&gt;However, this does not mean the output was small.&lt;/p&gt;

&lt;p&gt;It still generated more than 2,600 schema files.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗺️ Output size
&lt;/h2&gt;

&lt;p&gt;Next, generated output size and file count.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;File count&lt;/th&gt;
&lt;th&gt;Total size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/api/types.gen.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2.4 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@hey-api/openapi-ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/api/hey-api/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2.9 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/api/orval/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2,719&lt;/td&gt;
&lt;td&gt;14 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/api/kubb/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,877&lt;/td&gt;
&lt;td&gt;24 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Just from these numbers, the tools clearly live in different worlds.&lt;/p&gt;

&lt;p&gt;My rough feeling was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;openapi-typescript&lt;/code&gt;: one house 🏠&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hey-api&lt;/code&gt;: a small town 🏙️&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Orval&lt;/code&gt;: a small forest 🌲🌲&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Kubb&lt;/code&gt;: a deep forest 🌳🌳🌳&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;BTW, I love rural life so forest is better. 🌳&lt;/p&gt;

&lt;h3&gt;
  
  
  openapi-typescript
&lt;/h3&gt;

&lt;p&gt;The output was only &lt;code&gt;types.gen.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It contained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;paths&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;components&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;operations&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One file is simple and easy to understand.&lt;/p&gt;

&lt;p&gt;But a 2.4 MB TypeScript file can still be uncomfortable for IDE indexing.&lt;/p&gt;

&lt;p&gt;So even though the file count is tiny, the single file itself is large.&lt;/p&gt;

&lt;h3&gt;
  
  
  hey-api
&lt;/h3&gt;

&lt;p&gt;hey-api generated 16 files.&lt;/p&gt;

&lt;p&gt;The main files looked like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;types.gen.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;request / response types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sdk.gen.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;generated operation functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;index.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;barrel exports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;client.gen.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;default client instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;client/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fetch client implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;auth, serializers, utils, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;types.gen.ts&lt;/code&gt; was about 1.7 MB, and &lt;code&gt;sdk.gen.ts&lt;/code&gt; was about 750 KB.&lt;/p&gt;

&lt;p&gt;With 1,200 operations, the SDK file naturally becomes large.&lt;/p&gt;

&lt;p&gt;But compared with Orval and Kubb, the output still felt manageable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orval
&lt;/h3&gt;

&lt;p&gt;I used Orval with &lt;code&gt;tags-split&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The structure looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/api/orval/
├─ schemas/
│  ├─ ordersWriteRequest.ts
│  ├─ ordersListParams.ts
│  └─ ...
├─ order/
│  └─ order.ts
├─ product/
│  └─ product.ts
└─ ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Functions were grouped by tag.&lt;/p&gt;

&lt;p&gt;In this schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tag files: about 25&lt;/li&gt;
&lt;li&gt;schema files: more than 2,600&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The largest generated file was around 30,000 lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubb
&lt;/h3&gt;

&lt;p&gt;Kubb generated an even more split structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/api/kubb/
├─ types/
├─ clients/
├─ schemas/
└─ index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directory&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;types/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;operation-level types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clients/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;operation-level functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;schemas/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;individual schemas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;index.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;barrel exports&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The nice part is that the structure is close to 1 operation = 1 file.&lt;/p&gt;

&lt;p&gt;That makes grep easy and can be friendly to tree-shaking.&lt;/p&gt;

&lt;p&gt;But the tradeoff is real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;large PR diffs&lt;/li&gt;
&lt;li&gt;heavier IDE indexing&lt;/li&gt;
&lt;li&gt;slower clone and checkout&lt;/li&gt;
&lt;li&gt;more generated files to commit or ignore&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧪 Calling the same endpoint
&lt;/h2&gt;

&lt;p&gt;Now let's compare the actual call sites.&lt;/p&gt;

&lt;p&gt;Assume this endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It accepts common list query parameters like &lt;code&gt;page&lt;/code&gt; and &lt;code&gt;page_size&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  openapi-typescript
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;openapi-typescript&lt;/code&gt; only generates types, so I used it with &lt;code&gt;openapi-fetch&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;createClient&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;openapi-fetch&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;paths&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;./api/types.gen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;paths&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;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8000&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="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;response&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="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/orders&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;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;page_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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 important part is that the endpoint is specified as a path string:&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="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/orders&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 is simple and readable.&lt;/p&gt;

&lt;p&gt;IDE autocomplete also works well.&lt;/p&gt;

&lt;p&gt;But it is not operationId-based, so if the endpoint changes, rename refactoring is not as direct as function-based clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  hey-api
&lt;/h3&gt;

&lt;p&gt;hey-api generates SDK functions.&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;ordersList&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;./api/hey-api/sdk.gen&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="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;response&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="nf"&gt;ordersList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;page_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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 function name comes from &lt;code&gt;operationId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I like this because you can grep by function name and use IDE rename refactoring more naturally.&lt;/p&gt;

&lt;p&gt;The argument shape is also consistent:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This consistency becomes useful later when building wrappers, logging, retries, auth handling, or tracing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orval
&lt;/h3&gt;

&lt;p&gt;For Orval, I used &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;tags-split&lt;/code&gt;, and &lt;code&gt;useNamedParameters: true&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ordersList&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;./api/orval/orders/order&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="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;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&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="nf"&gt;ordersList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;page_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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 return value is 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="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;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;headers&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;At first glance, this is easy to use.&lt;/p&gt;

&lt;p&gt;But with the default fetch client, 4xx and 5xx responses are not thrown as errors.&lt;/p&gt;

&lt;p&gt;That point is very important in real applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubb
&lt;/h3&gt;

&lt;p&gt;Kubb generates operation-level functions.&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;ordersList&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;./api/kubb/clients/ordersList&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;data&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;ordersList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;page_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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;On success, it returns the response data directly.&lt;/p&gt;

&lt;p&gt;So the call site is very short.&lt;/p&gt;

&lt;p&gt;However, if you want to access response headers or status, the default return shape may not be enough.&lt;/p&gt;

&lt;p&gt;Also, path parameters and body arguments can become positional depending on the endpoint, which can make generic wrappers harder to design.&lt;/p&gt;

&lt;h2&gt;
  
  
  🖇️ Path params and body arguments
&lt;/h2&gt;

&lt;p&gt;A list endpoint does not show all differences, so let's compare path params and body arguments too.&lt;/p&gt;

&lt;h3&gt;
  
  
  GET by id
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// openapi-typescript&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;data&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="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/orders/{id}&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;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&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;123&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// hey-api&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;data&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="nf"&gt;ordersRetrieve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;path&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;123&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Orval&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;data&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="nf"&gt;ordersRetrieve&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;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Kubb&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="nf"&gt;ordersRetrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&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;Summary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Path param style&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;params.path&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ path: { id } }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ id }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;positional argument like &lt;code&gt;"123"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Kubb is short.&lt;/p&gt;

&lt;p&gt;But if an endpoint has multiple path params, positional arguments grow like &lt;code&gt;(id1, id2, ...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is partly a preference issue, but for large API clients I usually prefer named object arguments.&lt;/p&gt;

&lt;h3&gt;
  
  
  POST body
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// openapi-typescript&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/orders&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;body&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// hey-api&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ordersCreate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Orval&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ordersCreate&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Kubb&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ordersCreate&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Orval and Kubb feel natural when the body is the first argument.&lt;/p&gt;

&lt;p&gt;But for POST endpoints without a body, this can sometimes lead to explicit &lt;code&gt;undefined&lt;/code&gt; arguments.&lt;/p&gt;

&lt;p&gt;hey-api and openapi-typescript add one level with &lt;code&gt;{ body }&lt;/code&gt;, but the shape remains consistent with &lt;code&gt;query&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a large application, I personally prefer the consistency of hey-api.&lt;/p&gt;




&lt;h2&gt;
  
  
  💭 Function signature philosophy
&lt;/h2&gt;

&lt;p&gt;Here is the broader comparison of call signatures.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;endpoint selection&lt;/td&gt;
&lt;td&gt;URL string&lt;/td&gt;
&lt;td&gt;operationId function&lt;/td&gt;
&lt;td&gt;operationId function&lt;/td&gt;
&lt;td&gt;operationId function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;query&lt;/td&gt;
&lt;td&gt;&lt;code&gt;params.query&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ query }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;first argument&lt;/td&gt;
&lt;td&gt;first argument&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;params.path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ path }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{ id }&lt;/code&gt; or positional&lt;/td&gt;
&lt;td&gt;positional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;body&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ body }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ body }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;first argument&lt;/td&gt;
&lt;td&gt;first argument&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;headers&lt;/td&gt;
&lt;td&gt;options / middleware&lt;/td&gt;
&lt;td&gt;options / interceptor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RequestInit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cross-cutting behavior&lt;/td&gt;
&lt;td&gt;middleware&lt;/td&gt;
&lt;td&gt;interceptor&lt;/td&gt;
&lt;td&gt;mutator&lt;/td&gt;
&lt;td&gt;config / custom client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key strength of hey-api is that every endpoint moves toward the same shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;path&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="nx"&gt;headers&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;That makes cross-cutting behavior easier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;trace ids&lt;/li&gt;
&lt;li&gt;request ids&lt;/li&gt;
&lt;li&gt;custom headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Orval and Kubb often produce shorter call sites, but signatures can vary more by endpoint.&lt;/p&gt;

&lt;p&gt;In a small codebase, that is probably fine.&lt;/p&gt;

&lt;p&gt;In a 1,200-operation API, consistency matters a lot more.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Error model
&lt;/h2&gt;

&lt;p&gt;This was one of the most important differences.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Default behavior&lt;/th&gt;
&lt;th&gt;Can be changed?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;openapi-typescript&lt;/code&gt; + &lt;code&gt;openapi-fetch&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{ data, error, response }&lt;/code&gt; result union&lt;/td&gt;
&lt;td&gt;can throw via middleware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{ data, error, response }&lt;/code&gt; result union&lt;/td&gt;
&lt;td&gt;can throw with &lt;code&gt;throwOnError: true&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;wrapper like &lt;code&gt;{ data, status, headers }&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;can throw with custom fetch / mutator / config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;success returns &lt;code&gt;data&lt;/code&gt;, failure throws&lt;/td&gt;
&lt;td&gt;customizable via client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Result union in openapi-typescript and hey-api
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;openapi-fetch&lt;/code&gt; and hey-api, non-2xx responses can be represented in &lt;code&gt;error&lt;/code&gt; if the OpenAPI schema defines them.&lt;/p&gt;

&lt;p&gt;But in my &lt;code&gt;drf-spectacular&lt;/code&gt; schema, most non-2xx responses were not described.&lt;/p&gt;

&lt;p&gt;So in practice, &lt;code&gt;error&lt;/code&gt; was often close to &lt;code&gt;never&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means I still need a runtime check like this:&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;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/orders&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;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With hey-api, I can also opt into throwing behavior per call:&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;data&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="nf"&gt;ordersList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;throwOnError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like having both options.&lt;/p&gt;

&lt;p&gt;Sometimes I want a result-style response.&lt;/p&gt;

&lt;p&gt;Sometimes I want HTTP errors to throw immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orval's default fetch behavior needs attention
&lt;/h3&gt;

&lt;p&gt;With Orval's default fetch client, 4xx and 5xx responses do not throw automatically.&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ordersList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;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;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="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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 team forgets to check &lt;code&gt;status&lt;/code&gt;, it is possible to treat an error response as if it were success data.&lt;/p&gt;

&lt;p&gt;For example, the code may try to access &lt;code&gt;data.results&lt;/code&gt; and then fail at runtime because the actual response body was an error shape.&lt;/p&gt;

&lt;p&gt;This is easy to miss if you only look at TypeScript types.&lt;/p&gt;

&lt;p&gt;If I used Orval in production, I would choose one of these patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;always check &lt;code&gt;status&lt;/code&gt; at the call site&lt;/li&gt;
&lt;li&gt;configure the fetch client with &lt;code&gt;override.fetch.forceSuccessResponse: true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;override.mutator&lt;/code&gt; and write a custom fetch wrapper for auth, refresh, and errors&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A custom fetch may look like this:&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;// custom-fetch.ts&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;customFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;url&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&lt;/span&gt;&lt;span class="p"&gt;,&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;T&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Orval is powerful, but this behavior should be made explicit as a team rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubb's throw model
&lt;/h3&gt;

&lt;p&gt;Kubb is closer to a throw-based model by default.&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;data&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;ordersList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success, you get the data directly.&lt;/p&gt;

&lt;p&gt;On failure, it throws.&lt;/p&gt;

&lt;p&gt;This is pleasant in UI code.&lt;/p&gt;

&lt;p&gt;But if you need response metadata, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Retry-After&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ETag&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;rate limit headers&lt;/li&gt;
&lt;li&gt;pagination headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then the default return value may not be enough, and you may need a custom client or wrapper.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Authentication and interceptors
&lt;/h2&gt;

&lt;p&gt;Authentication handling also differs quite a bit.&lt;/p&gt;

&lt;h3&gt;
  
  
  openapi-fetch
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;openapi-fetch&lt;/code&gt; supports middleware.&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="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;onRequest&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;gt;&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getToken&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&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 you already use &lt;code&gt;openapi-typescript + openapi-fetch&lt;/code&gt;, this is clean and lightweight.&lt;/p&gt;

&lt;h3&gt;
  
  
  hey-api
&lt;/h3&gt;

&lt;p&gt;hey-api has axios-like interceptors.&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="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptors&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="nf"&gt;use&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="o"&gt;=&amp;gt;&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getToken&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&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 API feels familiar.&lt;/p&gt;

&lt;p&gt;For common headers, logging, trace ids, and 401 refresh flows, this style is easy to work with.&lt;/p&gt;

&lt;p&gt;Generated hey-api functions can also include &lt;code&gt;security&lt;/code&gt; metadata.&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;const&lt;/span&gt; &lt;span class="nx"&gt;ordersList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ThrowOnError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ordersListData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ThrowOnError&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;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ordersListResponses&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;ThrowOnError&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;security&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bearer&lt;/span&gt;&lt;span class="dl"&gt;"&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;http&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/orders&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;options&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 a little more to understand at first, but useful for authenticated APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orval
&lt;/h3&gt;

&lt;p&gt;Orval is closer to raw fetch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ordersList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getToken&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can pass headers per call.&lt;/p&gt;

&lt;p&gt;But in a real app, I would usually create a custom fetch with &lt;code&gt;override.mutator&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubb
&lt;/h3&gt;

&lt;p&gt;Kubb can configure the client or accept per-call config.&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="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers can also be passed per call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ordersList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it does not feel like axios-style interceptors are the default mental model.&lt;/p&gt;

&lt;p&gt;For things like 401 refresh, I would expect to wire my own client behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  🪛 TanStack Query, Zod, MSW, and Faker
&lt;/h2&gt;

&lt;p&gt;This is where the second family of tools becomes attractive.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Type generation&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fetch client&lt;/td&gt;
&lt;td&gt;external &lt;code&gt;openapi-fetch&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TanStack Query&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;plugin&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zod&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;plugin&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSW&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;limited / roadmap-dependent&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Faker&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SWR / Vue Query&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;limited / plugin-dependent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Orval has strong built-in support around this area.&lt;/p&gt;

&lt;p&gt;It can target tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TanStack Query&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SWR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Vue Query&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Zod&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MSW&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Faker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubb also has a strong plugin-based approach.&lt;/p&gt;

&lt;p&gt;hey-api is more focused around SDK generation, &lt;code&gt;TanStack Query&lt;/code&gt;, and &lt;code&gt;Zod&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If your goal is mainly API clients and types, &lt;code&gt;openapi-typescript&lt;/code&gt; or hey-api may be enough.&lt;/p&gt;

&lt;p&gt;If your goal is to generate a broader frontend ecosystem from OpenAPI, Orval or Kubb becomes more interesting.&lt;/p&gt;

&lt;p&gt;In my case, I did not need &lt;code&gt;MSW&lt;/code&gt; or &lt;code&gt;Faker&lt;/code&gt; yet.&lt;/p&gt;

&lt;p&gt;So Orval and Kubb felt a bit too much for the current problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤯 Enum issue: drf-spectacular BlankEnum
&lt;/h2&gt;

&lt;p&gt;One very real-world issue was enum generation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;drf-spectacular&lt;/code&gt; can represent nullable or blank enum values like this:&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;BlankEnum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;NullEnum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a field may use &lt;code&gt;anyOf&lt;/code&gt; like this:&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;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;anyOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/StatusEnum"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/BlankEnum"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/NullEnum"&lt;/span&gt;
  &lt;span class="na"&gt;nullable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intended TypeScript value is something 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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WAITING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RUNNING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILURE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generated BlankEnum
&lt;/h3&gt;

&lt;p&gt;The generated output differed by tool.&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;// openapi-typescript&lt;/span&gt;
&lt;span class="nx"&gt;BlankEnum&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// hey-api&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;BlankEnum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Orval&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BlankEnum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;BlankEnum&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;BlankEnum&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;BlankEnum&lt;/span&gt; &lt;span class="o"&gt;=&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="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;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Kubb&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;blankEnumEnum&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BlankEnumEnumKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;blankEnumEnum&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;blankEnumEnum&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;type&lt;/span&gt; &lt;span class="nx"&gt;BlankEnum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BlankEnumEnumKey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Kubb, the empty string disappeared, and the type effectively became &lt;code&gt;never&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens after anyOf composition
&lt;/h3&gt;

&lt;p&gt;As a result, only Kubb dropped &lt;code&gt;""&lt;/code&gt; from the final type.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Can represent &lt;code&gt;""&lt;/code&gt; in the type?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not just a small type detail.&lt;/p&gt;

&lt;p&gt;It can break real application code if fields like &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;file_type&lt;/code&gt;, or &lt;code&gt;update_type&lt;/code&gt; actually receive an empty string from the backend.&lt;/p&gt;

&lt;p&gt;Possible workarounds are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;patch &lt;code&gt;BlankEnum&lt;/code&gt; after generation&lt;/li&gt;
&lt;li&gt;cast affected fields to &lt;code&gt;string | null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;wait for an upstream fix&lt;/li&gt;
&lt;li&gt;avoid this schema shape if you control the backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use &lt;code&gt;drf-spectacular&lt;/code&gt; and have many &lt;code&gt;BlankEnum&lt;/code&gt; / &lt;code&gt;NullEnum&lt;/code&gt; patterns, I recommend checking this before adopting Kubb.&lt;/p&gt;




&lt;h2&gt;
  
  
  🥗 Body type reusability
&lt;/h2&gt;

&lt;p&gt;POST body types also differed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Body type style&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;components["schemas"]["ordersWriteRequest"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ordersWriteRequest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ordersWriteRequest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ordersCreateMutationRequest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Kubb generated endpoint-scoped wrapper types.&lt;/p&gt;

&lt;p&gt;This can be good because each operation is very explicit.&lt;/p&gt;

&lt;p&gt;But if you want to reuse the same schema in app-level code, it can be a little less convenient.&lt;/p&gt;

&lt;p&gt;For example, if a form wants to use &lt;code&gt;ordersWriteRequest&lt;/code&gt; as its submit data type, openapi-typescript, hey-api, and Orval feel more natural.&lt;/p&gt;

&lt;p&gt;With Kubb, I may need to alias the operation-scoped type:&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ordersCreateMutationRequest&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;./api/kubb/types/ordersCreate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OrderWriteRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ordersCreateMutationRequest&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 not wrong.&lt;/p&gt;

&lt;p&gt;It just depends on whether your app wants shared schema types or operation-scoped types.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚪 Lint behavior
&lt;/h2&gt;

&lt;p&gt;All four tools passed &lt;code&gt;tsc --noEmit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But linting showed differences.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;oxlint default&lt;/th&gt;
&lt;th&gt;oxlint + &lt;code&gt;no-explicit-any&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;eslint recommended&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1,549&lt;/td&gt;
&lt;td&gt;1,585&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  openapi-typescript / hey-api / Orval lint errors
&lt;/h3&gt;

&lt;p&gt;For the first three tools, the eslint errors were mainly &lt;code&gt;no-irregular-whitespace&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In my schema, some Django docstrings contained full-width spaces.&lt;/p&gt;

&lt;p&gt;Those comments were copied into generated code, and eslint complained.&lt;/p&gt;

&lt;p&gt;This is more about source docstrings and lint configuration than generated code quality.&lt;/p&gt;

&lt;p&gt;It can usually be handled by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;skipping comments&lt;/li&gt;
&lt;li&gt;excluding generated files from lint&lt;/li&gt;
&lt;li&gt;adding lint overrides for generated code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kubb lint errors
&lt;/h3&gt;

&lt;p&gt;Kubb produced many &lt;code&gt;no-explicit-any&lt;/code&gt; errors.&lt;/p&gt;

&lt;p&gt;Most were in the runtime client side.&lt;/p&gt;

&lt;p&gt;This does not mean Kubb is unusable.&lt;/p&gt;

&lt;p&gt;But if generated files are included in lint, Kubb almost certainly needs ignore rules or overrides.&lt;/p&gt;

&lt;p&gt;Before adopting it, I would decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;will generated files be committed?&lt;/li&gt;
&lt;li&gt;will generated files be linted?&lt;/li&gt;
&lt;li&gt;will generated files be checked in CI?&lt;/li&gt;
&lt;li&gt;which rules are disabled for generated code?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📝 Configuration notes
&lt;/h2&gt;

&lt;p&gt;Here are some issues I hit while setting up each tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  openapi-typescript
&lt;/h3&gt;

&lt;p&gt;This was the easiest setup.&lt;/p&gt;

&lt;p&gt;A single CLI command worked well.&lt;/p&gt;

&lt;p&gt;I did not hit any major configuration problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  hey-api
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;hey-api&lt;/code&gt; uses &lt;code&gt;openapi-ts.config.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I hit a few small points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;output.format: "prettier"&lt;/code&gt; aborts if Prettier is not installed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;runtimeConfigPath&lt;/code&gt; is emitted as an import path relative to &lt;code&gt;output.path&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I initially assumed &lt;code&gt;runtimeConfigPath&lt;/code&gt; was relative to the project root.&lt;/p&gt;

&lt;p&gt;That was easy to misunderstand.&lt;/p&gt;

&lt;p&gt;I shared this with the author, and the related PR was merged the next day.&lt;/p&gt;

&lt;p&gt;That fast response was a positive signal for adoption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orval
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Orval&lt;/code&gt; uses &lt;code&gt;orval.config.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It can split outputs by target, which is useful if you want to generate SDKs, Zod schemas, MSW mocks, and other assets separately.&lt;/p&gt;

&lt;p&gt;I did not hit major setup issues.&lt;/p&gt;

&lt;p&gt;But if you care about auth, refresh, or error handling, you should understand &lt;code&gt;override.mutator&lt;/code&gt; early.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubb
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Kubb&lt;/code&gt; configuration is plugin-based.&lt;/p&gt;

&lt;p&gt;Depending on your environment, generated imports with &lt;code&gt;.ts&lt;/code&gt; extensions may require this tsconfig setting:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowImportingTsExtensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not necessarily bad, but it should match your TypeScript and bundler policy.&lt;/p&gt;

&lt;p&gt;I had to adjust the setup a few times.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗨️ My impression of openapi-typescript
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;openapi-typescript&lt;/code&gt; is the thinnest and fastest option.&lt;/p&gt;

&lt;p&gt;It generates types and lets you choose the runtime layer yourself, often with &lt;code&gt;openapi-fetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What I liked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;very fast generation&lt;/li&gt;
&lt;li&gt;simple setup&lt;/li&gt;
&lt;li&gt;small generated output&lt;/li&gt;
&lt;li&gt;strong focus on type correctness&lt;/li&gt;
&lt;li&gt;runtime is separate and flexible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What may be missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no generated SDK functions&lt;/li&gt;
&lt;li&gt;endpoint calls are path-string based&lt;/li&gt;
&lt;li&gt;weaker fit for operationId-based rename refactoring&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TanStack Query&lt;/code&gt;, &lt;code&gt;Zod&lt;/code&gt;, and &lt;code&gt;MSW&lt;/code&gt; need separate handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you value simplicity, this is very strong.&lt;/p&gt;

&lt;p&gt;Especially if codegen runs often in local development or CI, the speed is a real advantage.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗨️ My impression of hey-api
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;hey-api&lt;/code&gt; felt like the best balance for my use case.&lt;/p&gt;

&lt;p&gt;It is thicker than &lt;code&gt;openapi-typescript&lt;/code&gt;, but much lighter than Orval or Kubb.&lt;/p&gt;

&lt;p&gt;What I liked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generated SDK functions&lt;/li&gt;
&lt;li&gt;consistent &lt;code&gt;{ query, path, body }&lt;/code&gt; arguments&lt;/li&gt;
&lt;li&gt;axios-like interceptors&lt;/li&gt;
&lt;li&gt;result model and &lt;code&gt;throwOnError&lt;/code&gt; option&lt;/li&gt;
&lt;li&gt;shared schema types are easy to use&lt;/li&gt;
&lt;li&gt;generated output remains manageable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weak points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generation takes longer than &lt;code&gt;openapi-typescript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sdk.gen.ts&lt;/code&gt; can become large&lt;/li&gt;
&lt;li&gt;weaker than Orval / Kubb for &lt;code&gt;MSW&lt;/code&gt; and &lt;code&gt;Faker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NullEnum&lt;/code&gt; may degrade into an empty enum in some cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my case, &lt;code&gt;NullEnum&lt;/code&gt; was not a major practical issue because nullable fields still carried &lt;code&gt;nullable: true&lt;/code&gt; through the composed type.&lt;/p&gt;

&lt;p&gt;More importantly, &lt;code&gt;BlankEnum&lt;/code&gt; preserved the empty string correctly.&lt;/p&gt;

&lt;p&gt;For my needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I wanted operationId functions&lt;/li&gt;
&lt;li&gt;I wanted interceptors&lt;/li&gt;
&lt;li&gt;I wanted result-style handling&lt;/li&gt;
&lt;li&gt;I did not need generated MSW or Faker yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So hey-api was the most practical choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗨️ My impression of Orval
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Orval&lt;/code&gt; is strong if you want OpenAPI to generate a broader frontend layer.&lt;/p&gt;

&lt;p&gt;What I liked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;supports &lt;code&gt;TanStack Query&lt;/code&gt;, &lt;code&gt;SWR&lt;/code&gt;, and &lt;code&gt;Vue Query&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;can generate &lt;code&gt;Zod&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;can generate &lt;code&gt;MSW&lt;/code&gt; and &lt;code&gt;Faker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;can split outputs by target&lt;/li&gt;
&lt;li&gt;faster than Kubb in this test&lt;/li&gt;
&lt;li&gt;shared body schemas are easy to reuse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things to watch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;default fetch client does not throw on 4xx / 5xx&lt;/li&gt;
&lt;li&gt;auth and refresh usually require a mutator&lt;/li&gt;
&lt;li&gt;file count can be large&lt;/li&gt;
&lt;li&gt;tag files can become large&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NullEnum&lt;/code&gt; may degrade into an empty const&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only want an API client, Orval may feel too thick.&lt;/p&gt;

&lt;p&gt;But if you want generated hooks, validation schemas, mocks, and test data, Orval becomes much more attractive.&lt;/p&gt;

&lt;p&gt;If I moved deeper into the ecosystem-generation side, I would probably evaluate Orval first.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗨️ My impression of Kubb
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Kubb&lt;/code&gt; has a strong philosophy.&lt;/p&gt;

&lt;p&gt;What I liked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;close to 1 operation = 1 file&lt;/li&gt;
&lt;li&gt;friendly to grep and physical file navigation&lt;/li&gt;
&lt;li&gt;structure can be tree-shake friendly&lt;/li&gt;
&lt;li&gt;clear plugin-based architecture&lt;/li&gt;
&lt;li&gt;can expand into &lt;code&gt;MSW&lt;/code&gt;, &lt;code&gt;Faker&lt;/code&gt;, &lt;code&gt;TanStack Query&lt;/code&gt;, &lt;code&gt;Zod&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;gives strong control over the codegen pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What felt heavy in this schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slow generation&lt;/li&gt;
&lt;li&gt;3,877 files / 24 MB output&lt;/li&gt;
&lt;li&gt;many lint errors from &lt;code&gt;any&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;possible &lt;code&gt;.ts&lt;/code&gt; import extension requirements&lt;/li&gt;
&lt;li&gt;positional path params make wrappers harder&lt;/li&gt;
&lt;li&gt;body types are endpoint-scoped&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BlankEnum&lt;/code&gt; became &lt;code&gt;never&lt;/code&gt; and lost the empty string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Kubb&lt;/code&gt; is not a bad tool.&lt;/p&gt;

&lt;p&gt;Actually, I like its design philosophy.&lt;/p&gt;

&lt;p&gt;But if your main goal is simply "I want a type-safe API client for a normal frontend app," it may be too heavy as the first option.&lt;/p&gt;

&lt;p&gt;I would choose Kubb when I have a clear reason, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I strongly want 1 operation = 1 file&lt;/li&gt;
&lt;li&gt;I want to design my own codegen pipeline&lt;/li&gt;
&lt;li&gt;I want plugin-level control&lt;/li&gt;
&lt;li&gt;I want to unify clients, schemas, mocks, and test data through plugins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The potential is high, but it needs commitment.&lt;/p&gt;




&lt;h2&gt;
  
  
  📓 Summary table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Family&lt;/td&gt;
&lt;td&gt;A: type-focused&lt;/td&gt;
&lt;td&gt;A: lightweight SDK&lt;/td&gt;
&lt;td&gt;B: frontend asset generation&lt;/td&gt;
&lt;td&gt;B: plugin/codegen platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation time&lt;/td&gt;
&lt;td&gt;1.5s&lt;/td&gt;
&lt;td&gt;8.0s&lt;/td&gt;
&lt;td&gt;5.5s&lt;/td&gt;
&lt;td&gt;18.1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output files&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2,719&lt;/td&gt;
&lt;td&gt;3,877&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output size&lt;/td&gt;
&lt;td&gt;2.4 MB&lt;/td&gt;
&lt;td&gt;2.9 MB&lt;/td&gt;
&lt;td&gt;14 MB&lt;/td&gt;
&lt;td&gt;24 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generates functions&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Function split&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;one SDK file&lt;/td&gt;
&lt;td&gt;one file per tag&lt;/td&gt;
&lt;td&gt;one file per operation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error model&lt;/td&gt;
&lt;td&gt;result union&lt;/td&gt;
&lt;td&gt;result union / throw&lt;/td&gt;
&lt;td&gt;success wrapper&lt;/td&gt;
&lt;td&gt;throw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client object&lt;/td&gt;
&lt;td&gt;external library&lt;/td&gt;
&lt;td&gt;generated client&lt;/td&gt;
&lt;td&gt;raw fetch / mutator&lt;/td&gt;
&lt;td&gt;generated client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interceptor&lt;/td&gt;
&lt;td&gt;openapi-fetch middleware&lt;/td&gt;
&lt;td&gt;axios-like&lt;/td&gt;
&lt;td&gt;mutator&lt;/td&gt;
&lt;td&gt;config / custom client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path params&lt;/td&gt;
&lt;td&gt;&lt;code&gt;params.path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ path: { id } }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ id }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;positional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Body type&lt;/td&gt;
&lt;td&gt;shared schema&lt;/td&gt;
&lt;td&gt;shared schema&lt;/td&gt;
&lt;td&gt;shared schema&lt;/td&gt;
&lt;td&gt;endpoint-scoped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TanStack Query&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;plugin&lt;/td&gt;
&lt;td&gt;built-in support&lt;/td&gt;
&lt;td&gt;plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zod&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;plugin&lt;/td&gt;
&lt;td&gt;built-in support&lt;/td&gt;
&lt;td&gt;plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSW / Faker&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;td&gt;strong&lt;/td&gt;
&lt;td&gt;strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lint&lt;/td&gt;
&lt;td&gt;minor adjustment&lt;/td&gt;
&lt;td&gt;minor adjustment&lt;/td&gt;
&lt;td&gt;minor adjustment&lt;/td&gt;
&lt;td&gt;likely needs ignore/override&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;drf-spectacular BlankEnum&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;needs care&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;thin and safe&lt;/td&gt;
&lt;td&gt;SDK + interceptors&lt;/td&gt;
&lt;td&gt;generate frontend assets&lt;/td&gt;
&lt;td&gt;design codegen pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🤔 How I would choose
&lt;/h2&gt;

&lt;p&gt;After this comparison, I would choose like this.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with openapi-typescript
&lt;/h3&gt;

&lt;p&gt;If your project already works well with &lt;code&gt;openapi-typescript + openapi-fetch&lt;/code&gt;, I would stay there.&lt;/p&gt;

&lt;p&gt;It is fast, thin, and easy to understand.&lt;/p&gt;

&lt;p&gt;A small wrapper like &lt;code&gt;src/api/client.ts&lt;/code&gt; plus middleware-based auth may be enough.&lt;/p&gt;

&lt;p&gt;There is no need to move to heavier codegen unless you have a concrete pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Move to hey-api when SDK functions become useful
&lt;/h3&gt;

&lt;p&gt;The next step is usually wanting things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call APIs by operationId functions instead of URL strings&lt;/li&gt;
&lt;li&gt;grep by function name&lt;/li&gt;
&lt;li&gt;use rename refactoring&lt;/li&gt;
&lt;li&gt;handle auth and refresh with interceptors&lt;/li&gt;
&lt;li&gt;work with consistent endpoint options&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that stage, &lt;code&gt;hey-api&lt;/code&gt; is a very good fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use Orval when OpenAPI becomes the source of frontend assets
&lt;/h3&gt;

&lt;p&gt;If OpenAPI becomes more than API client generation, &lt;code&gt;Orval&lt;/code&gt; becomes strong.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generated &lt;code&gt;TanStack Query&lt;/code&gt; hooks&lt;/li&gt;
&lt;li&gt;generated &lt;code&gt;Zod&lt;/code&gt; schemas&lt;/li&gt;
&lt;li&gt;generated &lt;code&gt;MSW&lt;/code&gt; mocks&lt;/li&gt;
&lt;li&gt;generated fake data&lt;/li&gt;
&lt;li&gt;multiple generation targets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where Orval's value becomes much clearer.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Choose Kubb when you love the architecture
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Kubb&lt;/code&gt; is powerful.&lt;/p&gt;

&lt;p&gt;But at this schema size, it was also heavy.&lt;/p&gt;

&lt;p&gt;I would choose it when I strongly care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 operation = 1 file&lt;/li&gt;
&lt;li&gt;plugin architecture&lt;/li&gt;
&lt;li&gt;tree-shake-friendly structure&lt;/li&gt;
&lt;li&gt;codegen pipeline control&lt;/li&gt;
&lt;li&gt;generating many kinds of artifacts consistently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It may not be my first choice for a normal frontend app, but it is very interesting if you want to design codegen itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Final conclusion
&lt;/h2&gt;

&lt;p&gt;OpenAPI codegen should not be chosen only by asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which tool has the most features?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much generated code are we willing to own?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My final choice looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;I only want types&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openapi-typescript&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I want SDK functions and interceptors&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hey-api&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I want to generate frontend assets together&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Orval&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I want to control codegen structure and plugins&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Kubb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For my current project, &lt;code&gt;hey-api&lt;/code&gt; had the best balance of SDK functions, interceptors, result handling, and output size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi70tk9kuqwxr09haz4hg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi70tk9kuqwxr09haz4hg.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The more useful codegen becomes, the bigger the generated forest becomes.&lt;/p&gt;

&lt;p&gt;So the important question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who maintains that forest, how much of it, and with what team rules?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the real OpenAPI codegen decision.&lt;/p&gt;

&lt;p&gt;What does your team generate from OpenAPI?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only types?&lt;/li&gt;
&lt;li&gt;SDK functions?&lt;/li&gt;
&lt;li&gt;Hooks?&lt;/li&gt;
&lt;li&gt;Mocks?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;or the whole forest? 🌳🌳🌳&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>openapi</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why I Didn’t Let AI Handle My Scroll Animation: Astro, React, and TypeScript Architecture</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Thu, 07 May 2026 12:41:31 +0000</pubDate>
      <link>https://dev.to/nyaomaru/why-i-didnt-let-ai-handle-my-scroll-animation-astro-react-and-typescript-architecture-37k2</link>
      <guid>https://dev.to/nyaomaru/why-i-didnt-let-ai-handle-my-scroll-animation-astro-react-and-typescript-architecture-37k2</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;I'm &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who once panicked because I triggered a fire alarm while roasting chashu at home in the Netherlands. 🐖&lt;/p&gt;

&lt;p&gt;This time, I want to write about how I built the corporate website for &lt;strong&gt;Necoz B.V.&lt;/strong&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://necoz.co/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnecoz.co%2Fnecoz_ogp.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://necoz.co/" rel="noopener noreferrer" class="c-link"&gt;
            Necoz | Software Development Studio in Amsterdam
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Necoz B.V. is a software development studio based in Amsterdam, the Netherlands. We build well-designed systems, clean architecture, and scalable web applications.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnecoz.co%2Ffavicon.png"&gt;
          necoz.co
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The site is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Astro&lt;/code&gt; for the base structure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;React&lt;/code&gt; only where interactive UI is needed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TypeScript&lt;/code&gt; for the main animation control&lt;/li&gt;
&lt;li&gt;responsive behavior for both desktop and mobile&lt;/li&gt;
&lt;li&gt;virtual scrolling to control the scroll amount itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because this is a corporate website, I did not want to break SEO.&lt;/p&gt;

&lt;p&gt;But I also wanted the motion to feel good.&lt;/p&gt;

&lt;p&gt;And I did not want the experience to fall apart on mobile.&lt;/p&gt;

&lt;p&gt;When you take these requirements seriously, the important question is not only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which technology should I use?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which responsibility should belong to which layer?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the end, the hardest part was the animation.&lt;/p&gt;

&lt;p&gt;AI was useful. It helped me move fast. It gave me rough ideas and initial implementations.&lt;/p&gt;

&lt;p&gt;But the animation it produced was not something I could use as-is.&lt;/p&gt;

&lt;p&gt;This article is about why, and how I ended up designing the animation system.&lt;/p&gt;

&lt;p&gt;The repository is here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/necoz" rel="noopener noreferrer"&gt;
        necoz
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Official Necoz B.V. website with custom scroll animations.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;necoz&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/necoz/main/public/necoz_ogp.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fnecoz%2Fmain%2Fpublic%2Fnecoz_ogp.png" alt="necoz logo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Necoz B.V. website built with Astro, React islands, and custom scroll / walker animation logic.&lt;/p&gt;
&lt;p&gt;Scroll down and enjoy the animations!&lt;/p&gt;
&lt;p&gt;You can check it on mobile too 😸&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Project Structure&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The project is organized like this:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;/
├── public/                     # Static files served as-is
├── src/
│   ├── components/
│   │   ├── home/               # Homepage sections
│   │   └── ui/                 # Reusable UI primitives and shared shells
│   │       ├── block/
│   │       ├── button/
│   │       ├── layout/
│   │       ├── mail/
│   │       ├── nyaomaru/
│   │       ├── scene/
│   │       ├── scroll/
│   │       └── text/
│   ├── layouts/                # Shared page layout shells
│   ├── lib/                    # Non-visual shared logic
│   │   ├── nyaomaru/           # Walker controller, scene logic, scene models
│   │   ├── math.ts
│   │   └── site-links.ts
│   ├── pages/                  # Route entrypoints
│   │   ├── index.astro
│   │   └── privacy-policy.astro
│&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/necoz" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Let's get into it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 Why I did not use AI-generated animation as-is
&lt;/h2&gt;

&lt;p&gt;AI-generated animation often looks nice at first glance.&lt;/p&gt;

&lt;p&gt;It usually has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;some movement&lt;/li&gt;
&lt;li&gt;some easing&lt;/li&gt;
&lt;li&gt;some visual atmosphere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But when I looked more carefully, I often felt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The direction is right, but the timing is wrong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the movement was too fast&lt;/li&gt;
&lt;li&gt;everything moved with the same rhythm&lt;/li&gt;
&lt;li&gt;elements did not stop where they should&lt;/li&gt;
&lt;li&gt;there was no pause&lt;/li&gt;
&lt;li&gt;there was no sense of timing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI can generate the rough idea of "how something moves".&lt;/p&gt;

&lt;p&gt;But in real animation, what matters is also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where it stops&lt;/li&gt;
&lt;li&gt;where it waits a little&lt;/li&gt;
&lt;li&gt;where one element reacts slightly later&lt;/li&gt;
&lt;li&gt;where the rhythm changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Animation is not just about moving from position A to position B.&lt;/p&gt;

&lt;p&gt;A small pause, a tiny delay, or a slightly different easing curve can completely change how it feels.&lt;/p&gt;

&lt;p&gt;I think this is one of the current gaps between AI and human judgment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI can produce motion quickly, but humans still have better sensitivity for timing and rhythm.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I used AI for rough drafts, structure, and experiments.&lt;/p&gt;

&lt;p&gt;But I adjusted the final timing by hand.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧑‍🚀 Why Astro?
&lt;/h2&gt;

&lt;p&gt;This site is a corporate website.&lt;/p&gt;

&lt;p&gt;So I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;real HTML content&lt;/li&gt;
&lt;li&gt;good SEO&lt;/li&gt;
&lt;li&gt;fast initial loading&lt;/li&gt;
&lt;li&gt;no unnecessary SPA architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I chose &lt;code&gt;Astro&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;https://astro.build/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The page structure is mainly built around &lt;code&gt;src/pages/index.astro&lt;/code&gt; and &lt;code&gt;src/layouts/Layout.astro&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The header, sections, footer, and base layout are handled by Astro.&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;Layout&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;{DEFAULT_TITLE}&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;{DEFAULT_DESCRIPTION}&lt;/span&gt; &lt;span class="na"&gt;canonicalPath=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"top"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Header&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;HeroIntro&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;WorkSection&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;StudioSection&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ContactSection&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;NyaomaruWalker&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Footer&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Layout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The layout also switches between native scrolling and virtual scrolling.&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;body&lt;/span&gt; &lt;span class="na"&gt;class:list=&lt;/span&gt;&lt;span class="s"&gt;{[virtualScroll&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;virtual-scroll-body&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;native-scroll-body&lt;/span&gt;&lt;span class="err"&gt;"]}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {virtualScroll ? &lt;span class="nt"&gt;&amp;lt;VirtualScroll&amp;gt;&amp;lt;slot&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&amp;lt;/VirtualScroll&amp;gt;&lt;/span&gt; : &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;}
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What I like about &lt;code&gt;Astro&lt;/code&gt; is that I do not have to turn the whole site into JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Astro&lt;/code&gt; lets me output clean HTML first, and then hydrate only the parts that need client-side behavior.&lt;/p&gt;

&lt;p&gt;For a corporate website, this is very useful.&lt;/p&gt;

&lt;p&gt;In this project, I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;solid page structure&lt;/li&gt;
&lt;li&gt;metadata and SEO&lt;/li&gt;
&lt;li&gt;client-side animation only where necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Astro&lt;/code&gt; did not make the animation better by itself.&lt;/p&gt;

&lt;p&gt;But it helped me separate structure and behavior cleanly.&lt;/p&gt;

&lt;p&gt;That separation was very important.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚖️ I used React, but not for everything
&lt;/h2&gt;

&lt;p&gt;The site also uses &lt;code&gt;React&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But it is not a full React app.&lt;/p&gt;

&lt;p&gt;The responsibility is divided like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Astro&lt;/code&gt; owns the page structure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;React&lt;/code&gt; owns small interactive UI parts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TypeScript&lt;/code&gt; owns the animation system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, the mail dialog is hydrated with &lt;code&gt;client:load&lt;/code&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"contact-links"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MailDialogTrigger&lt;/span&gt; &lt;span class="na"&gt;client:load&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SocialLinks&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The dialog trigger itself is a React component.&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MailDialogTrigger&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;isOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsOpen&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="kc"&gt;false&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;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;isOpen&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousOverflow&lt;/span&gt; &lt;span class="o"&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="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overflow&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="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&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="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="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="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;previousOverflow&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="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&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 a good use case for &lt;code&gt;React&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is local UI state.&lt;/p&gt;

&lt;p&gt;There is user interaction.&lt;/p&gt;

&lt;p&gt;There is a dialog.&lt;/p&gt;

&lt;p&gt;But that does not mean the entire website needs to become a React application.&lt;/p&gt;

&lt;p&gt;This was an important decision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using React does not mean everything has to be React.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;React is great for UI.&lt;/p&gt;

&lt;p&gt;But I did not want React to become responsible for the motion system.&lt;/p&gt;


&lt;h2&gt;
  
  
  🌊 Animation is controlled by TypeScript, not React state
&lt;/h2&gt;

&lt;p&gt;The core animation logic lives under &lt;code&gt;src/lib/nyaomaru/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It handles things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the walking character&lt;/li&gt;
&lt;li&gt;scene progression&lt;/li&gt;
&lt;li&gt;scroll-based state updates&lt;/li&gt;
&lt;li&gt;DOM measurement&lt;/li&gt;
&lt;li&gt;animation phases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of the main files are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;walker-controller.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls the main walker progression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;work-scene.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls the work section scene&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;studio-scene.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls the studio section scene&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;contact-scene.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls the contact section scene&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not a purely declarative animation system.&lt;/p&gt;

&lt;p&gt;It is much more direct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read the current scroll position&lt;/li&gt;
&lt;li&gt;determine the active scene&lt;/li&gt;
&lt;li&gt;calculate progress&lt;/li&gt;
&lt;li&gt;calculate the pose&lt;/li&gt;
&lt;li&gt;update the DOM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified version looks like this:&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;update&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="nx"&gt;scrollY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSceneScrollY&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;activeSceneState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getActiveSceneState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrollY&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;activeSceneState&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSceneProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrollY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeSceneState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snapshot&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;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeSceneState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;activeSceneState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`translate(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&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;requestUpdate&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="nf"&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rafId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;rafId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;update&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 flow is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;read the scroll value&lt;/li&gt;
&lt;li&gt;find the active scene&lt;/li&gt;
&lt;li&gt;calculate progress&lt;/li&gt;
&lt;li&gt;calculate the pose&lt;/li&gt;
&lt;li&gt;apply it to the DOM&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This was easier to tune than putting everything into React state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;React&lt;/code&gt; is strong for UI state.&lt;/p&gt;

&lt;p&gt;But animation timing often needs a thinner, more direct control layer.&lt;/p&gt;

&lt;p&gt;This does not mean React is bad for animation.&lt;/p&gt;

&lt;p&gt;It only means that, for this project, the responsibilities were different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React was for UI&lt;/li&gt;
&lt;li&gt;TypeScript was for motion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation worked well.&lt;/p&gt;


&lt;h2&gt;
  
  
  💎 Why I did not avoid direct DOM manipulation
&lt;/h2&gt;

&lt;p&gt;In modern frontend development, we often try to avoid direct DOM manipulation.&lt;/p&gt;

&lt;p&gt;Usually, I agree with that direction.&lt;/p&gt;

&lt;p&gt;Declarative UI is easier to reason about in many cases.&lt;/p&gt;

&lt;p&gt;But scroll-driven animation is a little different.&lt;/p&gt;

&lt;p&gt;Sometimes, the important state is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What state does this component have?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is actually visible on the screen right now?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this project, I needed to measure things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;getBoundingClientRect()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;viewport width&lt;/li&gt;
&lt;li&gt;which block is currently visible&lt;/li&gt;
&lt;li&gt;whether the desktop or mobile layout is active&lt;/li&gt;
&lt;li&gt;where the walker is currently positioned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was similar to what I learned while building a browser game before:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/building-a-browser-game-with-react-what-doesnt-work-well-run-away-from-work-1khf" class="crayons-story__hidden-navigation-link"&gt;Building a Browser Game with React: What Doesn’t work well (Run Away From Work)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" alt="nyaomaru profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nyaomaru" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nyaomaru
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nyaomaru
                
              
              &lt;div id="story-author-preview-content-3466423" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nyaomaru" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1436158%2Fa78bc4ed-7796-4760-baa5-06283c2404d6.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nyaomaru&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nyaomaru/building-a-browser-game-with-react-what-doesnt-work-well-run-away-from-work-1khf" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 8&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nyaomaru/building-a-browser-game-with-react-what-doesnt-work-well-run-away-from-work-1khf" id="article-link-3466423"&gt;
          Building a Browser Game with React: What Doesn’t work well (Run Away From Work)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamedev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamedev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nyaomaru/building-a-browser-game-with-react-what-doesnt-work-well-run-away-from-work-1khf" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;41&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nyaomaru/building-a-browser-game-with-react-what-doesnt-work-well-run-away-from-work-1khf#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              3&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The animation depends on the actual layout.&lt;/p&gt;

&lt;p&gt;So the DOM is not just an output target.&lt;/p&gt;

&lt;p&gt;It is also something the animation system has to observe.&lt;/p&gt;

&lt;p&gt;In this case, directly measuring the DOM made the intention clearer.&lt;/p&gt;

&lt;p&gt;It also avoided unnecessary indirection.&lt;/p&gt;

&lt;p&gt;So I did not avoid direct DOM manipulation completely.&lt;/p&gt;

&lt;p&gt;I used it only where it made sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  😿 Responsive design was not only a CSS problem
&lt;/h2&gt;

&lt;p&gt;The site also supports mobile.&lt;/p&gt;

&lt;p&gt;But responsive behavior here was not only about changing layout with CSS.&lt;/p&gt;

&lt;p&gt;For several sections, I have different block structures for desktop and mobile.&lt;/p&gt;

&lt;p&gt;For example, the hero, work, and studio sections have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;desktop blocks&lt;/li&gt;
&lt;li&gt;mobile blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The visible block changes depending on the viewport.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TypeScript&lt;/code&gt; animation logic also uses a &lt;code&gt;430px&lt;/code&gt; mobile breakpoint and changes things like scene progress, landing positions, and offsets.&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;workStack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isMobileViewport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;getVisibleElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-work-mobile-origin]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getVisibleElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-work-scene-display]&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;stackRect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;workStack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&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;walkerRect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&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;shotX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;walkerRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isMobileViewport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;MOBILE_SHOT_OFFSET_X&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SHOT_OFFSET_X&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The markup also separates desktop and mobile blocks.&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block block-three block-three--desktop"&lt;/span&gt;&lt;span class="nt"&gt;&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block block-three block-three--mobile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So responsive design in this project affected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM structure&lt;/li&gt;
&lt;li&gt;scroll progression&lt;/li&gt;
&lt;li&gt;animation targets&lt;/li&gt;
&lt;li&gt;movement distance&lt;/li&gt;
&lt;li&gt;position correction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I handled only the visual layout, the desktop version might feel good, but the mobile version would break quickly.&lt;/p&gt;

&lt;p&gt;For scroll-driven animation, responsive design is not only layout adjustment.&lt;/p&gt;

&lt;p&gt;It is also motion redesign.&lt;/p&gt;




&lt;h2&gt;
  
  
  📏 Why I used virtual scroll
&lt;/h2&gt;

&lt;p&gt;One of the most important parts of this site is virtual scrolling.&lt;/p&gt;

&lt;p&gt;The layout uses &lt;code&gt;VirtualScroll.astro&lt;/code&gt;, and inside that layer, a custom scroll controller runs.&lt;/p&gt;

&lt;p&gt;The goal was not just to make scrolling look fancy.&lt;/p&gt;

&lt;p&gt;The real goal was to separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visual scroll&lt;/li&gt;
&lt;li&gt;scene scroll&lt;/li&gt;
&lt;li&gt;scrollbar behavior&lt;/li&gt;
&lt;li&gt;smooth scroll following&lt;/li&gt;
&lt;li&gt;animation progression&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s4wn0ozo2q1thg9pfbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s4wn0ozo2q1thg9pfbw.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the implementation, I separate &lt;code&gt;visualScrollY&lt;/code&gt; and &lt;code&gt;sceneScrollY&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;applyScroll&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="nx"&gt;visualScrollY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getVisualScrollY&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;sceneScrollY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSceneScrollY&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;visualScrollY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;setScrollState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sceneScrollY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;visualScrollY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&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;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;necoz:virtual-scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scroll values are read like this:&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;const&lt;/span&gt; &lt;span class="nx"&gt;getVisualScrollY&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__necozScrollY&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollY&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;getSceneScrollY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;visualScrollY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getVisualScrollY&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__necozSceneScrollY&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;visualScrollY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation was very important.&lt;/p&gt;

&lt;p&gt;If everything depends directly on native scroll, it becomes harder to control the rhythm.&lt;/p&gt;

&lt;p&gt;For example, I wanted to adjust cases like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the page visually moved down, but the scene should wait a little more&lt;/li&gt;
&lt;li&gt;the footer area needs denser animation&lt;/li&gt;
&lt;li&gt;mobile should use a different scroll progression&lt;/li&gt;
&lt;li&gt;some scenes should feel slower or more deliberate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With virtual scroll, I can separate:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;how much the page visually moves&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;how much the animation scene progresses&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means I can design the time axis myself.&lt;/p&gt;

&lt;p&gt;This was the main reason I used virtual scroll.&lt;/p&gt;

&lt;p&gt;Not because it looks cool.&lt;/p&gt;

&lt;p&gt;But because I wanted control over animation time.&lt;/p&gt;

&lt;p&gt;For scroll-driven animation, scroll is not just movement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scroll is input.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if scroll is input, it needs to be designed carefully.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤖 Where AI was useful
&lt;/h2&gt;

&lt;p&gt;At this point, it might sound like I do not trust AI.&lt;/p&gt;

&lt;p&gt;That is not true.&lt;/p&gt;

&lt;p&gt;AI was very useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generating rough drafts&lt;/li&gt;
&lt;li&gt;trying implementation patterns&lt;/li&gt;
&lt;li&gt;splitting responsibilities&lt;/li&gt;
&lt;li&gt;getting something working quickly&lt;/li&gt;
&lt;li&gt;exploring ideas before committing to one direction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a tool for the first step, AI is excellent.&lt;/p&gt;

&lt;p&gt;But final animation tuning was different.&lt;/p&gt;

&lt;p&gt;For example, walking, falling, and landing phases were separated like this:&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;const&lt;/span&gt; &lt;span class="nx"&gt;lerp&lt;/span&gt; &lt;span class="o"&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="kr"&gt;number&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;progress&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="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&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;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;progress&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;progress&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;secondFallStart&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secondFallX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;landingX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secondFallProgress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secondRunY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;landingY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secondFallProgress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HERO_FALL_EASING_POWER&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;second-fall&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;secondRunStart&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstFallX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secondFallX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secondRunProgress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secondRunY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;second-run&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 kind of adjustment required a repeated loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;look at the animation&lt;/li&gt;
&lt;li&gt;feel that something is slightly wrong&lt;/li&gt;
&lt;li&gt;adjust a function or a number&lt;/li&gt;
&lt;li&gt;check it again&lt;/li&gt;
&lt;li&gt;repeat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI helped me move fast.&lt;/p&gt;

&lt;p&gt;But speed alone was not enough.&lt;/p&gt;

&lt;p&gt;For this kind of site, the character walks, the scene changes, and the rhythm follows the scroll.&lt;/p&gt;

&lt;p&gt;The final feeling had to be adjusted by hand.&lt;/p&gt;

&lt;p&gt;I think this is one of the areas where frontend engineers will still matter in the AI era.&lt;/p&gt;

&lt;p&gt;Not only writing code faster.&lt;/p&gt;

&lt;p&gt;But deciding whether the result actually feels good.&lt;/p&gt;




&lt;h2&gt;
  
  
  💖 What I learned from this architecture
&lt;/h2&gt;

&lt;p&gt;The main lessons from this project are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate HTML structure and animation behavior&lt;/li&gt;
&lt;li&gt;separate UI state and animation state&lt;/li&gt;
&lt;li&gt;treat scroll as input, not just movement&lt;/li&gt;
&lt;li&gt;responsive animation requires motion redesign, not only layout changes&lt;/li&gt;
&lt;li&gt;direct DOM measurement can be valid when the screen itself is part of the state&lt;/li&gt;
&lt;li&gt;AI is useful for speed, but timing and rhythm still need human judgment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zdm6v4aoj0j3t3uktwm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zdm6v4aoj0j3t3uktwm.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For scroll-driven animation, this kind of separation made the system much easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Summary
&lt;/h2&gt;

&lt;p&gt;For the Necoz B.V. website, I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Astro&lt;/code&gt; for page structure and initial HTML&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;React&lt;/code&gt; for small interactive UI parts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TypeScript&lt;/code&gt; for the main animation control&lt;/li&gt;
&lt;li&gt;direct DOM measurement where needed&lt;/li&gt;
&lt;li&gt;responsive motion logic for desktop and mobile&lt;/li&gt;
&lt;li&gt;virtual scroll to redesign scroll progression&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest benefit was that I could keep SEO and initial rendering stable while still keeping control over the motion system.&lt;/p&gt;

&lt;p&gt;AI helped a lot.&lt;/p&gt;

&lt;p&gt;But there is still a gap between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;animation that moves&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;animation that feels alive&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I did not let AI handle everything.&lt;/p&gt;

&lt;p&gt;I used it to move faster, organize ideas, and create rough versions.&lt;/p&gt;

&lt;p&gt;But I kept the final timing and rhythm in my own hands.&lt;/p&gt;

&lt;p&gt;In the end, I think what I was really building was not just a layout or an animation.&lt;/p&gt;

&lt;p&gt;I was designing time along the scroll.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;If you found this interesting, the repository is public.&lt;/p&gt;

&lt;p&gt;A star ⭐ would make me very happy 😸&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/necoz" rel="noopener noreferrer"&gt;https://github.com/nyaomaru/necoz&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>astro</category>
      <category>react</category>
    </item>
    <item>
      <title>Handling `unknown` in TypeScript… isn't it painful?</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Tue, 28 Apr 2026 13:09:54 +0000</pubDate>
      <link>https://dev.to/nyaomaru/handling-unknown-in-typescript-isnt-it-painful-4dec</link>
      <guid>https://dev.to/nyaomaru/handling-unknown-in-typescript-isnt-it-painful-4dec</guid>
      <description>&lt;p&gt;Hi there 👋&lt;/p&gt;

&lt;p&gt;I'm a frontend engineer based in the Netherlands, currently suffering from hay fever 😿&lt;/p&gt;

&lt;p&gt;API responses, form inputs, external data…&lt;/p&gt;

&lt;p&gt;In TypeScript, we often end up dealing with &lt;strong&gt;&lt;code&gt;unknown&lt;/code&gt;&lt;/strong&gt;,&lt;br&gt;
and handling it properly can become a real pain in day-to-day work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n1jxhr8qai9f63jr5mi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n1jxhr8qai9f63jr5mi.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, &lt;code&gt;unknown&lt;/code&gt; has a kind of &lt;strong&gt;gravitational pull like the universe&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But still…&lt;br&gt;
we &lt;em&gt;do&lt;/em&gt; want to handle types safely, right?&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/strong&gt; comes in a library for composing type guards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fah7dsw9wjzpexas5fbuz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fah7dsw9wjzpexas5fbuz.png" alt=" " width="800" height="572"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nyaomaru" rel="noopener noreferrer"&gt;
        nyaomaru
      &lt;/a&gt; / &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;
        is-kit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Lightweight, zero-dependency toolkit for building `isFoo` style type guards in TypeScript. Runtime-safe 🛡️, composable 🧩, and ergonomic ✨. npm -&amp;gt; https://www.npmjs.com/package/is-kit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;is-kit&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/nyaomaru/is-kit/main/docs/public/iskit_image.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnyaomaru%2Fis-kit%2Fmain%2Fdocs%2Fpublic%2Fiskit_image.png" width="600" alt="is-kit logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://www.npmjs.com/package/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/cb829c46f855a6f184f255391ea5fe58bb6ed17112e34d4856acd61dc63179c2/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f69732d6b69742e737667" alt="npm version"&gt;
  &lt;/a&gt;
  &lt;a href="https://jsr.io/@nyaomaru/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/b6648f6bf32eb9dff2136ce8538ccb4cc6af537c1127292b5fb0de416f7a69b4/68747470733a2f2f696d672e736869656c64732e696f2f6a73722f762f406e79616f6d6172752f69732d6b6974" alt="JSR"&gt;
  &lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/is-kit" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/9b30eeaac46ebced99b5e0dc290fd045bc34c738c66b5d95c4b6ee8fed358b34/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f69732d6b69742e737667" alt="npm downloads"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/nyaomaru/is-kit/blob/main/LICENSE" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/631cdf8c56ad84dd8657fe3371b93015b5474c517422f968ff4ec944bb28bf2f/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f69732d6b69742e7376673f73616e6974697a653d74727565" alt="License"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a lightweight, zero-dependency toolkit for building reusable TypeScript &lt;strong&gt;type guards&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It helps you write small &lt;code&gt;isFoo&lt;/code&gt; functions, compose them into &lt;strong&gt;richer runtime checks&lt;/strong&gt;, and keep &lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime-safe&lt;/strong&gt; 🛡️, &lt;strong&gt;composable&lt;/strong&gt; 🧩, and &lt;strong&gt;ergonomic&lt;/strong&gt; ✨ without asking you to adopt a heavy schema workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build and reuse &lt;strong&gt;typed guards&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compose guards&lt;/strong&gt; with &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;not&lt;/code&gt;, &lt;code&gt;oneOf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse or assert&lt;/strong&gt; &lt;code&gt;unknown&lt;/code&gt; values without a large schema framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://is-kit-docs.vercel.app/" rel="nofollow noopener noreferrer"&gt;📚 Documentation Site&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Best for &lt;strong&gt;app-internal narrowing, filtering, and reusable guards&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🤔 Why use &lt;code&gt;is-kit&lt;/code&gt;?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Tired of rewriting the same &lt;code&gt;isFoo&lt;/code&gt; checks again and again?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; is a good fit when you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;write reusable &lt;code&gt;isX&lt;/code&gt;&lt;/strong&gt; functions instead of one-off inline checks&lt;/li&gt;
&lt;li&gt;keep runtime validation &lt;strong&gt;lightweight and dependency-free&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;narrow values directly&lt;/strong&gt; in &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, and other TypeScript control flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;compose validation logic&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Libraries like &lt;code&gt;Zod&lt;/code&gt; take a &lt;strong&gt;schema-first approach&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In contrast, &lt;strong&gt;&lt;code&gt;is-kit&lt;/code&gt; focuses on narrowing types &lt;em&gt;within your existing code&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of “writing validation”, you can think of it as &lt;strong&gt;extending your everyday &lt;code&gt;if&lt;/code&gt; statements with type safety&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Zod&lt;/code&gt; is for &lt;strong&gt;boundaries (API / input)&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;is-kit&lt;/code&gt; is for &lt;strong&gt;inside your application logic&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  A simple example
&lt;/h2&gt;

&lt;p&gt;Imagine you need to check “strings with length ≤ 3” multiple times.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;is-kit&lt;/code&gt;, you can define it once and reuse it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isString&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;is-kit&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;isShortString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;span class="nx"&gt;value&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;isString&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="o"&gt;&amp;amp;&amp;amp;&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;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then use it like this:&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;isShortString&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;~/utils/is&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&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;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// before → repeating conditions every time&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;input&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="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// after → reusable guard&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;isShortString&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="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="nf"&gt;toUpperCase&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;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbsw247artoxxa6dbvb9b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbsw247artoxxa6dbvb9b.gif" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This style works seamlessly with&lt;br&gt;
&lt;code&gt;if&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, and other existing logic.&lt;/p&gt;


&lt;h2&gt;
  
  
  🐾 How &lt;code&gt;is-kit&lt;/code&gt; has evolved
&lt;/h2&gt;

&lt;p&gt;It’s been about 6 months since the &lt;code&gt;v1.0&lt;/code&gt; release.&lt;/p&gt;

&lt;p&gt;Back then, &lt;code&gt;is-kit&lt;/code&gt; started as a &lt;strong&gt;lightweight type guard library&lt;/strong&gt;&lt;br&gt;
with primitives like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;define&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;and&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;or&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;struct&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;arrayOf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since then, up to &lt;code&gt;v1.6&lt;/code&gt;, it has gradually evolved into something more practical:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;a toolkit for handling &lt;code&gt;unknown&lt;/code&gt; values in real-world applications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s look at 5 major improvements.&lt;/p&gt;




&lt;h2&gt;
  
  
  🪄 1. Distinguishing “missing” vs “undefined” in &lt;code&gt;struct&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A common situation in API responses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A key is missing entirely&lt;/li&gt;
&lt;li&gt;A key exists but its value is &lt;code&gt;undefined&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;em&gt;not the same&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;v1.5.0&lt;/code&gt;, &lt;code&gt;optionalKey(...)&lt;/code&gt; was introduced:&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;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;optionalKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;struct&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;is-kit&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;struct&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;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;optionalKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;optionalKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isString&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 allows you to express both cases explicitly.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔑 2. Key-based narrowing (&lt;code&gt;hasKey&lt;/code&gt;, &lt;code&gt;hasKeys&lt;/code&gt;, &lt;code&gt;narrowKeyTo&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;From &lt;code&gt;v1.1.13&lt;/code&gt; and &lt;code&gt;v1.4.0&lt;/code&gt;, key-based utilities were added:&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;hasKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;narrowKeyTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;oneOfValues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isNumber&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;is-kit&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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;struct&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;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;oneOfValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&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;guest&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;trial&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;hasRoleAndId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hasKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;role&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;id&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;byRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;narrowKeyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;role&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;isGuest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;byRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guest&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 lets you &lt;strong&gt;build on top of existing guards&lt;/strong&gt; instead of redefining everything.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 3. &lt;code&gt;assert&lt;/code&gt; for fail-fast validation
&lt;/h2&gt;

&lt;p&gt;Added in &lt;code&gt;v1.2.0&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isString&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;is-kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&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;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isString&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input must be a string&lt;/span&gt;&lt;span class="dl"&gt;"&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="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can now use guards in a &lt;strong&gt;fail-fast style&lt;/strong&gt; when needed.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✨ 4. Support for &lt;code&gt;Set&lt;/code&gt; and &lt;code&gt;Map&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;v1.6.0&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isNumber&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;is-kit&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;isTags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isString&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;isScores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not everything is an array in real-world apps —&lt;br&gt;
this expands coverage to common JS data structures.&lt;/p&gt;


&lt;h2&gt;
  
  
  🥏 5. Handling numeric edge cases
&lt;/h2&gt;

&lt;p&gt;From &lt;code&gt;v1.1.x&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isInteger&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isSafeInteger&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isPositive&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isNegative&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isNaN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isInfiniteNumber&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isZero&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These help you express &lt;strong&gt;“valid numbers” more precisely&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  🌟 More features
&lt;/h2&gt;

&lt;p&gt;There are more utilities available —&lt;br&gt;
feel free to explore the docs:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://is-kit-docs.vercel.app/en" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;is-kit-docs.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;






&lt;h2&gt;
  
  
  🎯 Summary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; started as a small, composable type guard library.&lt;/p&gt;

&lt;p&gt;Over time, it has evolved into:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;a practical toolkit for handling &lt;code&gt;unknown&lt;/code&gt; in application code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Key improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More expressive object schemas (&lt;code&gt;optionalKey&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Better key-based narrowing&lt;/li&gt;
&lt;li&gt;Fail-fast assertions&lt;/li&gt;
&lt;li&gt;Collection support (&lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Rich numeric guards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal of &lt;code&gt;is-kit&lt;/code&gt; is simple:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Make type-safe code feel natural to write&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you try it and have ideas or feedback, feel free to share!&lt;/p&gt;

&lt;p&gt;See you in the next post 👋&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;https://github.com/nyaomaru/is-kit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
