<?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>Cat Mutilation Successful!! 🛸 Welcome New Members!! 🐈🎉</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Sun, 15 Nov 2026 10:01:42 +0000</pubDate>
      <link>https://dev.to/necoz/cat-mutilation-successful-welcome-new-members-4bj4</link>
      <guid>https://dev.to/necoz/cat-mutilation-successful-welcome-new-members-4bj4</guid>
      <description>&lt;p&gt;Hoi hoi! 👋&lt;/p&gt;

&lt;p&gt;Last week, we launched the Necoz organization here on DEV!&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__15923"&gt;
  &lt;a href="/necoz" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" alt="necoz image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;Necoz&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;
        Necoz is a friendly open-source community building small, simple, and useful tools for developers. 🐈 

Everyone is welcome, from first-time contributors to experienced engineers. 

Let's build something awesome together! 😸
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;And guess what?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;28 cats have already joined us!!!&lt;/strong&gt; 🙀🎉&lt;/p&gt;

&lt;p&gt;YAYYYYY!!! 💚&lt;/p&gt;

&lt;p&gt;A huge thank you to everyone who joined!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/johnnylemonny"&gt;@johnnylemonny&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hanzla"&gt;@hanzla&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/star123"&gt;@star123&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pjdeveloper896"&gt;@pjdeveloper896&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/mestonecold"&gt;@mestonecold&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/leob"&gt;@leob&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/gtref"&gt;@gtref&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/technogamerz"&gt;@technogamerz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/fiwon123"&gt;@fiwon123&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/vignesh-j"&gt;@vignesh-j&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/akhourianmolkumar"&gt;@akhourianmolkumar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/francistrdev"&gt;@francistrdev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hadil"&gt;@hadil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/earlgreyhot1701d"&gt;@earlgreyhot1701d&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/dj29"&gt;@dj29&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/codingwithjiro"&gt;@codingwithjiro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hemapriya_kanagala"&gt;@hemapriya_kanagala&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sizzlebop"&gt;@sizzlebop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/glnurltn"&gt;@glnurltn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/neithergalax"&gt;@neithergalax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/devstackhub"&gt;@devstackhub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aditya_sorathiya_069252f4"&gt;@aditya_sorathiya_069252f4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/heytechomaima"&gt;@heytechomaima&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koda2026"&gt;@koda2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/tyleruploads"&gt;@tyleruploads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/daniel_ng"&gt;@daniel_ng&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/madamherta"&gt;@madamherta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/antonioprosperi2"&gt;@antonioprosperi2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Apparently, DEV only allows up to 10 mentions per article, so I've listed everyone using profile links instead. 🙀💚&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But now that we've captured all these cats, &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where are we going to take them?&lt;/strong&gt; 👀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hehehe... We're building something behind the scenes. Stay tuned! 🐈🛸&lt;/p&gt;




&lt;h2&gt;
  
  
  🏆 New Contributors, Good Cats 😺😺😺
&lt;/h2&gt;

&lt;p&gt;This week, we've had some awesome contributors joining our projects!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GOOD CATS!!!&lt;/strong&gt; 😻🎉&lt;/p&gt;

&lt;p&gt;Since this is our first weekly report, we're also celebrating everyone who contributed before this week!&lt;/p&gt;

&lt;p&gt;So far, &lt;strong&gt;35 amazing cats have contributed 71 merged pull requests&lt;/strong&gt; across our projects! 🐈🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  🧩 is-kit
&lt;/h3&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/necoz-oss" rel="noopener noreferrer"&gt;
        necoz-oss
      &lt;/a&gt; / &lt;a href="https://github.com/necoz-oss/is-kit" rel="noopener noreferrer"&gt;
        is-kit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Build small guards. Compose them.  A lightweight, zero-dependency toolkit for building reusable TypeScript type guards that compose, refine, and preserve 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/necoz-oss/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%2Fnecoz-oss%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/8cc43754ebcdd0c0ac8d9222f18957e9656512259bfa259e345d90ff8acb171b/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/f9fa92623c81bee37a79ed68e720160662c03afe98fe28f4fcfc6414624e3c93/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/ebb24a40859d0c83b9520eceb670a086cc8cb9b3f7d624c4c71ab734b155c140/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f69732d6b69742e737667" alt="npm downloads"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/necoz-oss/is-kit/blob/main/LICENSE" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/91624d742b8f13be37666bd49187f115b02d0c15e0c50b535dfe7a4c82152968/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;&lt;code&gt;is-kit&lt;/code&gt; is not just a collection of &lt;code&gt;isX&lt;/code&gt; helpers. Its main focus is composing
small runtime checks into reusable guards while preserving useful TypeScript
narrowing.&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;, refine properties on values you already know about, and keep
&lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow. Use it at runtime
boundaries when needed, without requiring a schema-first workflow.&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;Use &lt;code&gt;refineKey&lt;/code&gt; to narrow a &lt;strong&gt;child property&lt;/strong&gt; while preserving its parent type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections when that is useful&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;a href="https://is-kit.dev/" 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%2Fnecoz-oss%2Fis-kit%2FHEAD%2Fdocs%2Fpublic%2Fdocument_button.png" alt="Documentation Site" width="240"&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;Best…&lt;/p&gt;
&lt;/blockquote&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/necoz-oss/is-kit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Aaqibhafeezkhan" rel="noopener noreferrer"&gt;Aaqibhafeezkhan&lt;/a&gt; — Improved &lt;code&gt;oneOf&lt;/code&gt; evaluation and short-circuit tests. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/385" rel="noopener noreferrer"&gt;#385&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/AmirAbaris" rel="noopener noreferrer"&gt;AmirAbaris&lt;/a&gt; — Strengthened &lt;code&gt;refineKey&lt;/code&gt; type-level tests. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/372" rel="noopener noreferrer"&gt;#372&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/BoteroAlejandro11" rel="noopener noreferrer"&gt;BoteroAlejandro11&lt;/a&gt; — Improved &lt;code&gt;mapOf&lt;/code&gt; short-circuit tests. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/416" rel="noopener noreferrer"&gt;#416&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Doomguy-BFG9000" rel="noopener noreferrer"&gt;Doomguy-BFG9000&lt;/a&gt; — Improved lazy predicate caching tests. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/392" rel="noopener noreferrer"&gt;#392&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/GyulianoDev" rel="noopener noreferrer"&gt;GyulianoDev&lt;/a&gt; — Improved array guard short-circuit tests. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/414" rel="noopener noreferrer"&gt;#414&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Pratham2511" rel="noopener noreferrer"&gt;Pratham2511&lt;/a&gt; — Expanded sparse array and parsing edge-case coverage. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/393" rel="noopener noreferrer"&gt;#393&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/is-kit/pull/384" rel="noopener noreferrer"&gt;#384&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/crims0n" rel="noopener noreferrer"&gt;crims0n&lt;/a&gt; — Improved &lt;code&gt;predicateToRefine&lt;/code&gt; test coverage. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/386" rel="noopener noreferrer"&gt;#386&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/diyaryavuz" rel="noopener noreferrer"&gt;diyaryavuz&lt;/a&gt; — Expanded refinement and &lt;code&gt;tupleOf&lt;/code&gt; edge-case tests. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/397" rel="noopener noreferrer"&gt;#397&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/is-kit/pull/377" rel="noopener noreferrer"&gt;#377&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/guhcostan" rel="noopener noreferrer"&gt;guhcostan&lt;/a&gt; — Improved &lt;code&gt;safeParse&lt;/code&gt; documentation. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/370" rel="noopener noreferrer"&gt;#370&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nuriadevs" rel="noopener noreferrer"&gt;nuriadevs&lt;/a&gt; — Clarified &lt;code&gt;isNumber&lt;/code&gt; documentation. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/371" rel="noopener noreferrer"&gt;#371&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/painbaba" rel="noopener noreferrer"&gt;painbaba&lt;/a&gt; — Fixed literal narrowing behavior in &lt;code&gt;oneOfValues&lt;/code&gt;. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/400" rel="noopener noreferrer"&gt;#400&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/psjdeveloper" rel="noopener noreferrer"&gt;psjdeveloper&lt;/a&gt; — Improved &lt;code&gt;define&lt;/code&gt; predicate coverage. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/394" rel="noopener noreferrer"&gt;#394&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/saimskywalker" rel="noopener noreferrer"&gt;saimskywalker&lt;/a&gt; — Fixed an edge case in &lt;code&gt;isPlainObject&lt;/code&gt;. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/406" rel="noopener noreferrer"&gt;#406&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/srijavuppala" rel="noopener noreferrer"&gt;srijavuppala&lt;/a&gt; — Strengthened &lt;code&gt;assert&lt;/code&gt; runtime guard tests. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/417" rel="noopener noreferrer"&gt;#417&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tridev-tej" rel="noopener noreferrer"&gt;tridev-tej&lt;/a&gt; — Improved &lt;code&gt;safeJsonParse&lt;/code&gt; error propagation tests. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/431" rel="noopener noreferrer"&gt;#431&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tylrcc" rel="noopener noreferrer"&gt;tylrcc&lt;/a&gt; — Improved runtime guard tests and compatibility documentation. (&lt;a href="https://github.com/necoz-oss/is-kit/pull/426" rel="noopener noreferrer"&gt;#426&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/is-kit/pull/425" rel="noopener noreferrer"&gt;#425&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/is-kit/pull/424" rel="noopener noreferrer"&gt;#424&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/is-kit/pull/423" rel="noopener noreferrer"&gt;#423&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/is-kit/pull/422" rel="noopener noreferrer"&gt;#422&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤖 changelog-bot
&lt;/h3&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/necoz-oss" rel="noopener noreferrer"&gt;
        necoz-oss
      &lt;/a&gt; / &lt;a href="https://github.com/necoz-oss/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/necoz-oss/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%2Fnecoz-oss%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/necoz-oss/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%2Fnecoz-oss%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;p&gt;The GitHub repository is &lt;a href="https://github.com/necoz-oss/changelog-bot" rel="noopener noreferrer"&gt;necoz-oss/changelog-bot&lt;/a&gt;. The npm package name is still &lt;code&gt;@nyaomaru/changelog-bot&lt;/code&gt;. In workflows, use &lt;code&gt;necoz-oss/changelog-bot&lt;/code&gt;. GitHub Actions does not follow repository redirects for actions or reusable workflows, so keep &lt;code&gt;uses:&lt;/code&gt; on the new owner. Web and git URLs for &lt;code&gt;nyaomaru/changelog-bot&lt;/code&gt; still redirect.&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…&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/necoz-oss/changelog-bot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/19algorythms" rel="noopener noreferrer"&gt;19algorythms&lt;/a&gt; — Added a Jest coverage script. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/299" rel="noopener noreferrer"&gt;#299&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Anantha-K" rel="noopener noreferrer"&gt;Anantha-K&lt;/a&gt; — Fixed release version handling during changelog generation. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/251" rel="noopener noreferrer"&gt;#251&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/GyulianoDev" rel="noopener noreferrer"&gt;GyulianoDev&lt;/a&gt; — Improved CLI error messages. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/307" rel="noopener noreferrer"&gt;#307&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/KingEmma7" rel="noopener noreferrer"&gt;KingEmma7&lt;/a&gt; — Improved runtime validation using shared guards. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/265" rel="noopener noreferrer"&gt;#265&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Mayank-jaiswal13" rel="noopener noreferrer"&gt;Mayank-jaiswal13&lt;/a&gt; — Refactored PR lookup logic and improved test maintainability. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/327" rel="noopener noreferrer"&gt;#327&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/changelog-bot/pull/306" rel="noopener noreferrer"&gt;#306&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/MayurK-cmd" rel="noopener noreferrer"&gt;MayurK-cmd&lt;/a&gt; — Expanded compare link generation tests. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/326" rel="noopener noreferrer"&gt;#326&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Prashaxt" rel="noopener noreferrer"&gt;Prashaxt&lt;/a&gt; — Added JSON schema support and improved GitHub API tests. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/288" rel="noopener noreferrer"&gt;#288&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/changelog-bot/pull/274" rel="noopener noreferrer"&gt;#274&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Satorino01" rel="noopener noreferrer"&gt;Satorino01&lt;/a&gt; — Expanded test coverage for repeated compare link updates. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/348" rel="noopener noreferrer"&gt;#348&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/WebWizard5243" rel="noopener noreferrer"&gt;WebWizard5243&lt;/a&gt; — Reduced duplication in metadata diagnostics. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/325" rel="noopener noreferrer"&gt;#325&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dbdefault" rel="noopener noreferrer"&gt;dbdefault&lt;/a&gt; — Simplified runtime validation using is-kit predicates, fixed PR linking inside fenced code blocks, and expanded title lookup and metadata fallback test coverage. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/352" rel="noopener noreferrer"&gt;#352&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/changelog-bot/pull/351" rel="noopener noreferrer"&gt;#351&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/changelog-bot/pull/349" rel="noopener noreferrer"&gt;#349&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/changelog-bot/pull/345" rel="noopener noreferrer"&gt;#345&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/changelog-bot/pull/342" rel="noopener noreferrer"&gt;#342&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/enidchen0125" rel="noopener noreferrer"&gt;enidchen0125&lt;/a&gt; — Improved repository configuration validation. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/339" rel="noopener noreferrer"&gt;#339&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/erSpell" rel="noopener noreferrer"&gt;erSpell&lt;/a&gt; — Improved WHY extractor test coverage. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/276" rel="noopener noreferrer"&gt;#276&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jay23606" rel="noopener noreferrer"&gt;jay23606&lt;/a&gt; — Improved changelog file error handling. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/272" rel="noopener noreferrer"&gt;#272&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/johnnylemonny" rel="noopener noreferrer"&gt;johnnylemonny&lt;/a&gt; — Contributed extensively to WHY extraction, evaluation, GitHub metadata handling, output delivery, Grok support, security, cross-platform compatibility, and much more! (&lt;a href="https://github.com/necoz-oss/changelog-bot/pulls?q=is%3Apr+is%3Amerged+author%3Ajohnnylemonny" rel="noopener noreferrer"&gt;View contributions&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mangomac" rel="noopener noreferrer"&gt;mangomac&lt;/a&gt; — Expanded release Markdown test coverage. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/275" rel="noopener noreferrer"&gt;#275&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/noahkhomer18" rel="noopener noreferrer"&gt;noahkhomer18&lt;/a&gt; — Fixed duplicate changelog bullet handling. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/324" rel="noopener noreferrer"&gt;#324&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sorathiya903" rel="noopener noreferrer"&gt;sorathiya903&lt;/a&gt; — Added practical output destination examples. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/309" rel="noopener noreferrer"&gt;#309&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tylrcc" rel="noopener noreferrer"&gt;tylrcc&lt;/a&gt; — Reduced code duplication across metadata, changelog utilities, and diagnostics. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/318" rel="noopener noreferrer"&gt;#318&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/changelog-bot/pull/317" rel="noopener noreferrer"&gt;#317&lt;/a&gt;, &lt;a href="https://github.com/necoz-oss/changelog-bot/pull/316" rel="noopener noreferrer"&gt;#316&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/xryanlee" rel="noopener noreferrer"&gt;xryanlee&lt;/a&gt; — Improved documentation on how to claim an issue. (&lt;a href="https://github.com/necoz-oss/changelog-bot/pull/330" rel="noopener noreferrer"&gt;#330&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🦀 type-tune
&lt;/h3&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/necoz-oss" rel="noopener noreferrer"&gt;
        necoz-oss
      &lt;/a&gt; / &lt;a href="https://github.com/necoz-oss/type-tune" rel="noopener noreferrer"&gt;
        type-tune
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Rust-powered CLI to find shared TypeScript types and move them safely. 🦀
    &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;type-tune&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/necoz-oss/type-tune/docs/public/animation/motion-logo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnecoz-oss%2Ftype-tune%2FHEAD%2Fdocs%2Fpublic%2Fanimation%2Fmotion-logo.gif" width="400" alt="Animated type-tune logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/actions/workflows/test.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/necoz-oss/type-tune/actions/workflows/test.yml/badge.svg" alt="Test"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/actions/workflows/docs.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/necoz-oss/type-tune/actions/workflows/docs.yml/badge.svg" alt="Docs"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3f66e5c1e948d61ed1824c4de00ca0cfdf12d13a6e274045433807519130c96e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="MIT License"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Give shared TypeScript types a better home. 😸&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;Scan. Review. Move. Validate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;type-tune&lt;/strong&gt; is a Rust-powered CLI that finds shared TypeScript &lt;code&gt;type&lt;/code&gt; aliases and &lt;code&gt;interface&lt;/code&gt; declarations and helps move them without manually chasing imports.&lt;/p&gt;

&lt;p&gt;It creates a reviewable plan, updates supported type imports and re-exports, and validates the rewrite with TypeScript. &lt;strong&gt;You decide what moves and where.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Guides for installation, the first scan, and the review workflow live in the &lt;a href="https://type-tune-nine.vercel.app/" rel="nofollow noopener noreferrer"&gt;official documentation&lt;/a&gt;. Releases are listed on &lt;a href="https://github.com/necoz-oss/type-tune/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;, and the package is &lt;a href="https://www.npmjs.com/package/type-tune" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;type-tune&lt;/code&gt; on npm&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why type-tune?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Types often start next to a component or utility, then end up shared across a project. Moving them later means tracking imports, local dependencies, and barrels by hand.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discover&lt;/strong&gt; shared types with deterministic candidate IDs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review&lt;/strong&gt; &lt;code&gt;plan.md&lt;/code&gt; and choose the destination before making changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite&lt;/strong&gt; supported type-only imports, aliases, and simple barrels while keeping compatibility re-exports at the original…&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/necoz-oss/type-tune" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ViviBeri" rel="noopener noreferrer"&gt;ViviBeri&lt;/a&gt; — Improved test coverage for aliased mixed imports through barrel re-exports. (&lt;a href="https://github.com/necoz-oss/type-tune/pull/23" rel="noopener noreferrer"&gt;#23&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you so much for your contributions! 💚&lt;/p&gt;

&lt;p&gt;Remember, no contribution is too small!&lt;/p&gt;

&lt;p&gt;Code, tests, documentation, bug reports, ideas, or even a simple question. Everything is welcome!&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Necoz Website
&lt;/h3&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;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/effessdev" rel="noopener noreferrer"&gt;effessdev&lt;/a&gt; — Fixed text spacing and wrapping on desktop while preserving the mobile layout. (&lt;a href="https://github.com/nyaomaru/necoz/pull/23" rel="noopener noreferrer"&gt;#23&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even our little website is getting some love from our cats! 😻&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 From Discussion to Creation
&lt;/h3&gt;

&lt;p&gt;One of our recent community discussions has already turned into a new open-source project called &lt;strong&gt;safe-paste!&lt;/strong&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/necoz-oss" rel="noopener noreferrer"&gt;
        necoz-oss
      &lt;/a&gt; / &lt;a href="https://github.com/necoz-oss/safe-paste" rel="noopener noreferrer"&gt;
        safe-paste
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A lightweight, local-first browser extension for sensitive information before sharing. 😺🔎
    &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;safepaste&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;A lightweight, local-first browser extension for sensitive information before sharing. 😺🔎&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/necoz-oss/safe-paste" 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;It's a little tool designed to help prevent accidentally pasting sensitive information.&lt;/p&gt;

&lt;p&gt;We're still in the early stages of development, so if you're interested, feel free to jump in and help us build it together! 🐾&lt;/p&gt;

&lt;p&gt;You're also more than welcome to start your own discussions, share ideas, suggest new projects, or just chat with other cats!&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/orgs/necoz-oss/discussions" rel="noopener noreferrer"&gt;https://github.com/orgs/necoz-oss/discussions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Who knows? Your little idea might become our next open-source project! 👀&lt;/p&gt;

&lt;p&gt;Don't be shy! We're friendly cats. Probably. 😼&lt;/p&gt;




&lt;h2&gt;
  
  
  👑 Cat of the Necoz
&lt;/h2&gt;

&lt;p&gt;And our very first &lt;strong&gt;Cat of the Necoz&lt;/strong&gt; is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🥁 Drumroll, please...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🐈 &lt;a class="mentioned-user" href="https://dev.to/johnnylemonny"&gt;@johnnylemonny&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__cover"&gt;
          &lt;a href="https://github.com/johnnylemonny" 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%2Favatars.githubusercontent.com%2Fu%2F248761666%3Fv%3D4%3Fs%3D400" 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://github.com/johnnylemonny" rel="noopener noreferrer" class="c-link"&gt;
            johnnylemonny (John) · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Full‑stack developer creating fast, modern, and refined applications. I focus on simplicity, performance, and real value for users. Open to opportunities. - johnnylemonny
          &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%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;John has made an incredible number of contributions to &lt;code&gt;changelog-bot&lt;/code&gt;, helping us improve everything from new features to testing, reliability, and security.&lt;/p&gt;

&lt;p&gt;Thank you for all your amazing work, John!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You are officially a SUPER CAT!!!&lt;/strong&gt; 😻👑🎉&lt;/p&gt;

&lt;p&gt;Who will be our next Cat of the Necoz? 👀&lt;/p&gt;




&lt;h2&gt;
  
  
  🎮 Necoz Quests
&lt;/h2&gt;

&lt;p&gt;Looking for your next adventure?&lt;/p&gt;

&lt;p&gt;We've prepared some quests for curious cats! 🐾&lt;/p&gt;

&lt;h3&gt;
  
  
  🧩 is-kit
&lt;/h3&gt;

&lt;p&gt;A lightweight TypeScript runtime type validation library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🐾 Available Quests&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/434" rel="noopener noreferrer"&gt;#434 — Verify that hasKey and hasKeys do not read property values&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/435" rel="noopener noreferrer"&gt;#435 — Strengthen not runtime forwarding and error behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/436" rel="noopener noreferrer"&gt;#436 — Document oneOfValues equality and snapshot behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/437" rel="noopener noreferrer"&gt;#437 — Clarify safeJsonParse error boundaries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/438" rel="noopener noreferrer"&gt;#438 — Clarify equalsKey property and equality semantics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/439" rel="noopener noreferrer"&gt;#439 — Update outdated tsup references to tsdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/440" rel="noopener noreferrer"&gt;#440 — Document oneOf evaluation and short-circuit behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/441" rel="noopener noreferrer"&gt;#441 — Improve tupleOf documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/442" rel="noopener noreferrer"&gt;#442 — Strengthen guardIn runtime forwarding tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/443" rel="noopener noreferrer"&gt;#443 — Verify equals reference identity behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/is-kit/issues/444" rel="noopener noreferrer"&gt;#444 — Improve the pull request template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🚧 Current Major Quest — API Refinement&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're exploring ways to refine and simplify the public API to make is-kit more consistent, intuitive, and easier to use.&lt;/p&gt;

&lt;p&gt;We're also improving our documentation and test coverage, so there are plenty of small quests for first-time contributors!&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/necoz-oss/is-kit/issues" rel="noopener noreferrer"&gt;Explore more quests&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 changelog-bot
&lt;/h3&gt;

&lt;p&gt;An AI-powered tool that helps generate useful changelogs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🐾 Available Quests&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/changelog-bot/issues/353" rel="noopener noreferrer"&gt;#353 — Fix cross-platform path handling in tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/changelog-bot/issues/354" rel="noopener noreferrer"&gt;#354 — Preserve unknown Markdown category headings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/changelog-bot/issues/355" rel="noopener noreferrer"&gt;#355 — Improve concurrency regression tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/changelog-bot/issues/356" rel="noopener noreferrer"&gt;#356 — Expand sub-change suppression and deduplication tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/changelog-bot/issues/357" rel="noopener noreferrer"&gt;#357 — Expand dependency relocation regression tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🚧 Current Major Quest — Road to v1.0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're continuing our journey toward v1.0, including improvements to changelog generation, audience filtering, and WHY extraction.&lt;/p&gt;

&lt;p&gt;Step by step, we're getting closer to our first major release. 🚀&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/necoz-oss/changelog-bot/issues" rel="noopener noreferrer"&gt;Explore more quests&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🦀 type-tune
&lt;/h3&gt;

&lt;p&gt;A Rust-powered CLI that helps organize shared TypeScript types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🐾 Available Quests&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/type-tune/issues/18" rel="noopener noreferrer"&gt;#18 — Verify conditional, mapped, and generic type move safety&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/type-tune/issues/29" rel="noopener noreferrer"&gt;#29 — Investigate indirect Vue consumers during TypeScript-only rewrites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/type-tune/issues/32" rel="noopener noreferrer"&gt;#32 — Detect circular dependencies introduced by type moves&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🚧 Current Major Quest — Beyond TypeScript&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We've been making progress on Vue support and validating type-tune against real-world Vue and Nuxt projects!&lt;/p&gt;

&lt;p&gt;We're also exploring some exciting future features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/type-tune/issues/17" rel="noopener noreferrer"&gt;#17 — Detect structurally identical type declarations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/type-tune/issues/20" rel="noopener noreferrer"&gt;#20 — Support TypeScript in Svelte components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/necoz-oss/type-tune/issues/21" rel="noopener noreferrer"&gt;#21 — Support TypeScript in Astro components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Love Rust or TypeScript? There are plenty of adventures waiting for you! 🦀&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/necoz-oss/type-tune/issues" rel="noopener noreferrer"&gt;Explore more quests&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🐈 Before Starting Your Quest
&lt;/h3&gt;

&lt;p&gt;Found something you'd like to work on?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before you start working on an issue, please check whether another cat has already claimed it and leave a comment to let us know you'd like to work on it.&lt;/strong&gt; 🙏&lt;/p&gt;

&lt;p&gt;Something as simple as&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'd like to work on this issue!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If another cat is already working on it, please choose another quest or ask if you can help.&lt;/p&gt;

&lt;p&gt;This helps us avoid duplicate work and makes sure everyone's time and effort count. 💚&lt;/p&gt;

&lt;h3&gt;
  
  
  🌱 First contribution? No problem!
&lt;/h3&gt;

&lt;p&gt;Pick a quest, ask questions, or simply join the discussion. Every little step counts!&lt;/p&gt;

&lt;p&gt;Not sure where to start? Just leave a comment, and we'll help you find your first quest!&lt;/p&gt;

&lt;p&gt;There are plenty of adventures for cats of all experience levels! 🐾&lt;/p&gt;




&lt;h2&gt;
  
  
  📰 Necoz Articles
&lt;/h2&gt;

&lt;p&gt;Our cats have been busy writing too! 😸&lt;/p&gt;

&lt;p&gt;From technical deep dives and beginner-friendly tutorials to community discussions and mysterious UFO adventures, we've already seen some amazing articles!&lt;/p&gt;

&lt;p&gt;Let's check them out! 🐾&lt;/p&gt;

&lt;h3&gt;
  
  
  🐈 John
&lt;/h3&gt;

&lt;p&gt;A deep dive into why changelogs should explain the WHY behind changes, rather than simply repeating commit messages. 😺&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/stop-generating-changelogs-from-git-commits-how-i-fixed-the-why-problem-in-changelogbot-11n9" class="crayons-story__hidden-navigation-link"&gt;Stop Generating Changelogs from Git Commits: How I Fixed the 'WHY' Problem in ChangelogBot&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/stop-generating-changelogs-from-git-commits-how-i-fixed-the-why-problem-in-changelogbot-11n9" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;CI rate limits and AI context solved&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/johnnylemonny" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3868757%2F1f787f85-4ec3-4a2e-ba8b-097436abcbd2.gif" alt="johnnylemonny profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/johnnylemonny" class="crayons-story__secondary fw-medium m:hidden"&gt;
              𝗝𝗼𝗵𝗻
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                𝗝𝗼𝗵𝗻
                
                
              
              &lt;div id="story-author-preview-content-4964893" 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="/johnnylemonny" 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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3868757%2F1f787f85-4ec3-4a2e-ba8b-097436abcbd2.gif" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;𝗝𝗼𝗵𝗻&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/stop-generating-changelogs-from-git-commits-how-i-fixed-the-why-problem-in-changelogbot-11n9" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 3&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/necoz/stop-generating-changelogs-from-git-commits-how-i-fixed-the-why-problem-in-changelogbot-11n9" id="article-link-4964893"&gt;
          Stop Generating Changelogs from Git Commits: How I Fixed the 'WHY' Problem in ChangelogBot
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/github"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;github&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/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&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/necoz/stop-generating-changelogs-from-git-commits-how-i-fixed-the-why-problem-in-changelogbot-11n9" 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;20&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/necoz/stop-generating-changelogs-from-git-commits-how-i-fixed-the-why-problem-in-changelogbot-11n9#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              18&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;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 nyaomaru
&lt;/h3&gt;

&lt;p&gt;The announcement that started our little adventure here on DEV! 🎉🎉🎉&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/necoz-has-a-new-home-on-dev-3454" class="crayons-story__hidden-navigation-link"&gt;Necoz Has a New Home on DEV! 🐈🎉&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/necoz-has-a-new-home-on-dev-3454" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Open invite for contributors to join the new org&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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-4989886" 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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/necoz-has-a-new-home-on-dev-3454" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 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/necoz/necoz-has-a-new-home-on-dev-3454" id="article-link-4989886"&gt;
          Necoz Has a New Home on DEV! 🐈🎉
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/news"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;news&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devto"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devto&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/community"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;community&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/organization"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;organization&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/necoz/necoz-has-a-new-home-on-dev-3454" 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;179&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;span class="crayons-story__favorited"&gt;
              &lt;span class="favorited-marker"&gt;
                &lt;span&gt;
                  

                &lt;/span&gt;
                &lt;span class="hidden"&gt;
                  

                &lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;a href="https://dev.to/necoz/necoz-has-a-new-home-on-dev-3454#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              119&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;
            1 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 Hanzla Baig
&lt;/h3&gt;

&lt;p&gt;A wonderful reminder that even the smallest open-source contributions can make a meaningful difference.&lt;/p&gt;

&lt;p&gt;This is exactly what Necoz is all about! 😸💚&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/small-contributions-matter-too-2j4" class="crayons-story__hidden-navigation-link"&gt;Small Contributions Matter Too 🐈&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/small-contributions-matter-too-2j4" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Highlights the open-source community Necoz&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/hanzla" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png" alt="hanzla profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/hanzla" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hanzla Baig
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hanzla Baig
                
                
              
              &lt;div id="story-author-preview-content-4999918" 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="/hanzla" 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%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hanzla Baig&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/small-contributions-matter-too-2j4" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 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/necoz/small-contributions-matter-too-2j4" id="article-link-4999918"&gt;
          Small Contributions Matter Too 🐈
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&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/github"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;github&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/necoz"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;necoz&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/necoz/small-contributions-matter-too-2j4" 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;73&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/necoz/small-contributions-matter-too-2j4#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              27&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;
            2 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 Hanzla Baig
&lt;/h3&gt;

&lt;p&gt;What if DEV had a dedicated space for developers to launch, share, and discover new projects? An interesting community discussion! 👍&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/what-if-dev-had-a-launch-system-like-product-hunt-but-for-developers-1bfo" class="crayons-story__hidden-navigation-link"&gt;What if DEV had a launch system, like Product Hunt but for developers?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/what-if-dev-had-a-launch-system-like-product-hunt-but-for-developers-1bfo" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Community weighs risks of spam versus discovery&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/hanzla" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png" alt="hanzla profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/hanzla" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hanzla Baig
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hanzla Baig
                
                
              
              &lt;div id="story-author-preview-content-5011864" 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="/hanzla" 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%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hanzla Baig&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/what-if-dev-had-a-launch-system-like-product-hunt-but-for-developers-1bfo" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 10&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/necoz/what-if-dev-had-a-launch-system-like-product-hunt-but-for-developers-1bfo" id="article-link-5011864"&gt;
          What if DEV had a launch system, like Product Hunt but for developers?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " 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/startup"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;startup&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/necoz/what-if-dev-had-a-launch-system-like-product-hunt-but-for-developers-1bfo" 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/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/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;89&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/necoz/what-if-dev-had-a-launch-system-like-product-hunt-but-for-developers-1bfo#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              32&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;
            1 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 Sᴛᴀʀ⭐
&lt;/h3&gt;

&lt;p&gt;A lovely reminder that building your very first project is still something special, even in the age of AI. 😸&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/ai-can-write-code-but-the-magic-of-a-first-project-is-unmatched-30c4" class="crayons-story__hidden-navigation-link"&gt;AI can write code, but the magic of a first project is unmatched! 🗽&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/ai-can-write-code-but-the-magic-of-a-first-project-is-unmatched-30c4" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Community praises the buggy first project&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/star123" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F4190689%2Fa5769091-dbf8-41b9-ad15-609006a4d8ce.jpg" alt="star123 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/star123" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Sᴛᴀʀ⭐
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Sᴛᴀʀ⭐
                
                
              
              &lt;div id="story-author-preview-content-5016771" 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="/star123" 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%2F4190689%2Fa5769091-dbf8-41b9-ad15-609006a4d8ce.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Sᴛᴀʀ⭐&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/ai-can-write-code-but-the-magic-of-a-first-project-is-unmatched-30c4" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 11&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/necoz/ai-can-write-code-but-the-magic-of-a-first-project-is-unmatched-30c4" id="article-link-5016771"&gt;
          AI can write code, but the magic of a first project is unmatched! 🗽
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/html"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;html&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/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/nicoz"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nicoz&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/necoz/ai-can-write-code-but-the-magic-of-a-first-project-is-unmatched-30c4" 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;57&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/necoz/ai-can-write-code-but-the-magic-of-a-first-project-is-unmatched-30c4#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              42&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;
            1 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 nyaomaru
&lt;/h3&gt;

&lt;p&gt;Introducing type-tune, our new Rust-powered CLI for organizing shared TypeScript types! 🦀 (Actually, I'm allergic to crabs 🙀)&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/meet-type-tune-give-your-typescript-types-a-better-home-1p6l" class="crayons-story__hidden-navigation-link"&gt;Meet type-tune! 🦀 Give Your TypeScript Types a Better Home! 😸&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/meet-type-tune-give-your-typescript-types-a-better-home-1p6l" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Addresses type clutter from AI-generated code&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/nyaomaru" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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-4999208" 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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/meet-type-tune-give-your-typescript-types-a-better-home-1p6l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 11&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/necoz/meet-type-tune-give-your-typescript-types-a-better-home-1p6l" id="article-link-4999208"&gt;
          Meet type-tune! 🦀 Give Your TypeScript Types a Better Home! 😸
        &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/rust"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rust&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/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/necoz/meet-type-tune-give-your-typescript-types-a-better-home-1p6l" 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;109&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/necoz/meet-type-tune-give-your-typescript-types-a-better-home-1p6l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              36&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;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 Prasoon Jadon
&lt;/h3&gt;

&lt;p&gt;A beginner-friendly guide to making your first open-source contribution, covering the workflow from getting started to submitting a pull request. 😸&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/from-user-to-contributor-a-beginners-guide-to-open-source-workflow-44h3" class="crayons-story__hidden-navigation-link"&gt;From User to Contributor: A Beginner's Guide to Open Source Workflow 🐱&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/pjdeveloper896" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F3206128%2F984414e9-d286-41d3-b4af-d2a0a45a0bcb.jpg" alt="pjdeveloper896 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pjdeveloper896" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Prasoon  Jadon 
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Prasoon  Jadon 
                
                
              
              &lt;div id="story-author-preview-content-5020744" 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="/pjdeveloper896" 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%2F3206128%2F984414e9-d286-41d3-b4af-d2a0a45a0bcb.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Prasoon  Jadon &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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/from-user-to-contributor-a-beginners-guide-to-open-source-workflow-44h3" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 12&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/necoz/from-user-to-contributor-a-beginners-guide-to-open-source-workflow-44h3" id="article-link-5020744"&gt;
          From User to Contributor: A Beginner's Guide to Open Source Workflow 🐱
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&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/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&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;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/necoz/from-user-to-contributor-a-beginners-guide-to-open-source-workflow-44h3" 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;33&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/necoz/from-user-to-contributor-a-beginners-guide-to-open-source-workflow-44h3#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              8&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 class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 Sᴛᴀʀ⭐
&lt;/h3&gt;

&lt;p&gt;Becoming a DEV Trusted Member at just 10 years old!&lt;/p&gt;

&lt;p&gt;Congratulations, Star! 😻🎉&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/a-10-years-old-developer-who-has-become-a-devto-trusted-member-49c8" class="crayons-story__hidden-navigation-link"&gt;A 10 years old developer who has become a DEV.to Trusted Member! 😱&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/a-10-years-old-developer-who-has-become-a-devto-trusted-member-49c8" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Overcame discouragement from doubting classmates&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/star123" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F4190689%2Fa5769091-dbf8-41b9-ad15-609006a4d8ce.jpg" alt="star123 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/star123" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Sᴛᴀʀ⭐
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Sᴛᴀʀ⭐
                
                
              
              &lt;div id="story-author-preview-content-5022229" 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="/star123" 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%2F4190689%2Fa5769091-dbf8-41b9-ad15-609006a4d8ce.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Sᴛᴀʀ⭐&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/a-10-years-old-developer-who-has-become-a-devto-trusted-member-49c8" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 12&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/necoz/a-10-years-old-developer-who-has-become-a-devto-trusted-member-49c8" id="article-link-5022229"&gt;
          A 10 years old developer who has become a DEV.to Trusted Member! 😱
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/trusted"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;trusted&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;a class="crayons-tag  crayons-tag--monochrome " href="/t/devto"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devto&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/necoz/a-10-years-old-developer-who-has-become-a-devto-trusted-member-49c8" 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/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;96&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;span class="crayons-story__favorited"&gt;
              &lt;span class="favorited-marker"&gt;
                &lt;span&gt;
                  

                &lt;/span&gt;
                &lt;span class="hidden"&gt;
                  

                &lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;a href="https://dev.to/necoz/a-10-years-old-developer-who-has-become-a-devto-trusted-member-49c8#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              48&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 class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 John
&lt;/h3&gt;

&lt;p&gt;An interesting technical deep dive into the hidden context costs of MCP servers and tool-heavy AI agents. 😼🤖&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/your-mcp-servers-are-silently-draining-your-context-window-the-hidden-cost-of-tool-heavy-ai-agents-fkl" class="crayons-story__hidden-navigation-link"&gt;Your MCP Servers Are Silently Draining Your Context Window: The Hidden Cost of Tool-Heavy AI Agents&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/your-mcp-servers-are-silently-draining-your-context-window-the-hidden-cost-of-tool-heavy-ai-agents-fkl" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Schema bloat silently burns tokens&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/johnnylemonny" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3868757%2F1f787f85-4ec3-4a2e-ba8b-097436abcbd2.gif" alt="johnnylemonny profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/johnnylemonny" class="crayons-story__secondary fw-medium m:hidden"&gt;
              𝗝𝗼𝗵𝗻
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                𝗝𝗼𝗵𝗻
                
                
              
              &lt;div id="story-author-preview-content-5017672" 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="/johnnylemonny" 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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3868757%2F1f787f85-4ec3-4a2e-ba8b-097436abcbd2.gif" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;𝗝𝗼𝗵𝗻&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/your-mcp-servers-are-silently-draining-your-context-window-the-hidden-cost-of-tool-heavy-ai-agents-fkl" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 12&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/necoz/your-mcp-servers-are-silently-draining-your-context-window-the-hidden-cost-of-tool-heavy-ai-agents-fkl" id="article-link-5017672"&gt;
          Your MCP Servers Are Silently Draining Your Context Window: The Hidden Cost of Tool-Heavy AI Agents
        &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/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/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/necoz/your-mcp-servers-are-silently-draining-your-context-window-the-hidden-cost-of-tool-heavy-ai-agents-fkl" 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;54&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;span class="crayons-story__favorited"&gt;
              &lt;span class="favorited-marker"&gt;
                &lt;span&gt;
                  

                &lt;/span&gt;
                &lt;span class="hidden"&gt;
                  

                &lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;a href="https://dev.to/necoz/your-mcp-servers-are-silently-draining-your-context-window-the-hidden-cost-of-tool-heavy-ai-agents-fkl#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              47&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 class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 Shruti Shahi
&lt;/h3&gt;

&lt;p&gt;A humorous and relatable story about the experience of opening LinkedIn as a developer. 😸&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/a-day-in-the-life-of-someone-who-just-wanted-to-open-linkedin-in-peace-2be" class="crayons-story__hidden-navigation-link"&gt;A Day in the Life of Someone Who Just Wanted to Open LinkedIn in Peace&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/a-day-in-the-life-of-someone-who-just-wanted-to-open-linkedin-in-peace-2be" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Relatable community replies to platform burnout&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/mestonecold" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F4239261%2Fc6f3916f-858d-40a4-acc8-09a0cedc086c.jpg" alt="mestonecold profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mestonecold" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Shruti Shahi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Shruti Shahi
                
                
              
              &lt;div id="story-author-preview-content-5024729" 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="/mestonecold" 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%2F4239261%2Fc6f3916f-858d-40a4-acc8-09a0cedc086c.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Shruti Shahi&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/a-day-in-the-life-of-someone-who-just-wanted-to-open-linkedin-in-peace-2be" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 12&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/necoz/a-day-in-the-life-of-someone-who-just-wanted-to-open-linkedin-in-peace-2be" id="article-link-5024729"&gt;
          A Day in the Life of Someone Who Just Wanted to Open LinkedIn in Peace
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/watercooler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;watercooler&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/career"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;career&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/socialmedia"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;socialmedia&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/necoz/a-day-in-the-life-of-someone-who-just-wanted-to-open-linkedin-in-peace-2be" 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;34&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/necoz/a-day-in-the-life-of-someone-who-just-wanted-to-open-linkedin-in-peace-2be#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              12&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;
            5 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 Sᴛᴀʀ⭐
&lt;/h3&gt;

&lt;p&gt;Learning Python loops inside the Necoz UFO?!&lt;/p&gt;

&lt;p&gt;Even our mysterious spaceship is becoming a programming classroom! 😹🛸&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/necoz-the-ufo-kingdom-python-loops-hd1" class="crayons-story__hidden-navigation-link"&gt;🛸 Necoz: The UFO Kingdom Python Loops 🐱&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/necoz/necoz-the-ufo-kingdom-python-loops-hd1" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Story format makes Python fun&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/star123" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F4190689%2Fa5769091-dbf8-41b9-ad15-609006a4d8ce.jpg" alt="star123 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/star123" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Sᴛᴀʀ⭐
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Sᴛᴀʀ⭐
                
                
              
              &lt;div id="story-author-preview-content-5029900" 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="/star123" 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%2F4190689%2Fa5769091-dbf8-41b9-ad15-609006a4d8ce.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Sᴛᴀʀ⭐&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/necoz-the-ufo-kingdom-python-loops-hd1" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 13&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/necoz/necoz-the-ufo-kingdom-python-loops-hd1" id="article-link-5029900"&gt;
          🛸 Necoz: The UFO Kingdom Python Loops 🐱
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&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/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/necoz/necoz-the-ufo-kingdom-python-loops-hd1" 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;71&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/necoz/necoz-the-ufo-kingdom-python-loops-hd1#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              40&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;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;


&lt;h3&gt;
  
  
  🐈 Hanzla Baig
&lt;/h3&gt;

&lt;p&gt;A useful improvement to DEV itself! You can now respond to organization invitations directly from your notifications. 😽&lt;/p&gt;

&lt;p&gt;Congratulations on getting your PR merged! 🎉&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/necoz/you-can-now-accept-or-decline-dev-organization-invitations-from-notifications-1oa" class="crayons-story__hidden-navigation-link"&gt;You Can Now Accept or Decline DEV Organization Invitations From Notifications Page! 🎉&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 class="crayons-logo crayons-logo--l" href="/necoz"&gt;
            &lt;img alt="Necoz logo" 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%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/hanzla" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png" alt="hanzla profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/hanzla" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hanzla Baig
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hanzla Baig
                
                
              
              &lt;div id="story-author-preview-content-5035147" 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="/hanzla" 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%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hanzla Baig&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;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/necoz" class="crayons-story__secondary fw-medium"&gt;Necoz&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/necoz/you-can-now-accept-or-decline-dev-organization-invitations-from-notifications-1oa" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 14&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/necoz/you-can-now-accept-or-decline-dev-organization-invitations-from-notifications-1oa" id="article-link-5035147"&gt;
          You Can Now Accept or Decline DEV Organization Invitations From Notifications Page! 🎉
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/news"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;news&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/forem"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;forem&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devto"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devto&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/necoz/you-can-now-accept-or-decline-dev-organization-invitations-from-notifications-1oa" 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;87&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/necoz/you-can-now-accept-or-decline-dev-organization-invitations-from-notifications-1oa#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              9&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;
            2 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

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


&lt;/div&gt;





&lt;p&gt;Thank you to all our amazing writers! 😻💚&lt;/p&gt;

&lt;p&gt;Want to publish your own article under Necoz?&lt;/p&gt;

&lt;p&gt;Tutorials, technical deep dives, personal experiences, project announcements, or even stories about cats getting abducted by UFOs 🛸&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything is welcome!&lt;/strong&gt; 😹&lt;/p&gt;

&lt;p&gt;Let's keep learning, sharing, and building together!&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__15923"&gt;
  &lt;a href="/necoz" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" alt="necoz image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;Necoz&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;
        Necoz is a friendly open-source community building small, simple, and useful tools for developers. 🐈 

Everyone is welcome, from first-time contributors to experienced engineers. 

Let's build something awesome together! 😸
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  🔮 What's Next?
&lt;/h2&gt;

&lt;p&gt;Starting now, I'll be sharing little Necoz updates and events &lt;strong&gt;every Sunday!&lt;/strong&gt; 🐈&lt;/p&gt;

&lt;p&gt;New members, contributors, quests, articles, and whatever strange things our cats have been doing!&lt;/p&gt;

&lt;p&gt;And the week after next 👇&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;We're planning a little bring-your-own-something event!&lt;/strong&gt; 👀🎉&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What will everyone bring?&lt;/p&gt;

&lt;p&gt;You'll find out soon!&lt;/p&gt;

&lt;p&gt;Thanks for being part of Necoz! 💚&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy coding! And stay a good cat!&lt;/strong&gt; 🐈&lt;/p&gt;




&lt;p&gt;Want to join our growing family of cats? 🐈&lt;/p&gt;

&lt;p&gt;Feel free to leave a comment below!&lt;/p&gt;

&lt;p&gt;There's always room for another cat in our little UFO. 🛸💚&lt;/p&gt;

</description>
      <category>necoz</category>
      <category>opensource</category>
      <category>community</category>
      <category>github</category>
    </item>
    <item>
      <title>Meet type-tune! 🦀 Give Your TypeScript Types a Better Home! 😸</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 11 Nov 2026 17:19:22 +0000</pubDate>
      <link>https://dev.to/necoz/meet-type-tune-give-your-typescript-types-a-better-home-1p6l</link>
      <guid>https://dev.to/necoz/meet-type-tune-give-your-typescript-types-a-better-home-1p6l</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 actually allergic to crabs! 🙀🦀&lt;/p&gt;

&lt;p&gt;Today, I have another exciting announcement!&lt;/p&gt;

&lt;p&gt;I've just released a new open-source tool called &lt;strong&gt;type-tune&lt;/strong&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/necoz-oss" rel="noopener noreferrer"&gt;
        necoz-oss
      &lt;/a&gt; / &lt;a href="https://github.com/necoz-oss/type-tune" rel="noopener noreferrer"&gt;
        type-tune
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Rust-powered CLI to find shared TypeScript types and move them safely. 🦀
    &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;type-tune&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/necoz-oss/type-tune/docs/public/animation/motion-logo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnecoz-oss%2Ftype-tune%2FHEAD%2Fdocs%2Fpublic%2Fanimation%2Fmotion-logo.gif" width="400" alt="Animated type-tune logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/actions/workflows/test.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/necoz-oss/type-tune/actions/workflows/test.yml/badge.svg" alt="Test"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/actions/workflows/docs.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/necoz-oss/type-tune/actions/workflows/docs.yml/badge.svg" alt="Docs"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="MIT License"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Give shared TypeScript types a better home. 😸&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;Scan. Review. Move. Validate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;type-tune&lt;/strong&gt; is a Rust-powered CLI that finds shared TypeScript &lt;code&gt;type&lt;/code&gt; aliases and &lt;code&gt;interface&lt;/code&gt; declarations and helps move them without manually chasing imports.&lt;/p&gt;

&lt;p&gt;It creates a reviewable plan, updates supported type imports and re-exports, and validates the rewrite with TypeScript. &lt;strong&gt;You decide what moves and where.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Guides for installation, the first scan, and the review workflow live in the &lt;a href="https://type-tune-nine.vercel.app/" rel="nofollow noopener noreferrer"&gt;official documentation&lt;/a&gt;. Releases are listed on &lt;a href="https://github.com/necoz-oss/type-tune/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;, and the package is &lt;a href="https://www.npmjs.com/package/type-tune" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;type-tune&lt;/code&gt; on npm&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why type-tune?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Types often start next to a component or utility, then end up shared across a project. Moving them later means tracking imports, local dependencies, and barrels by hand.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discover&lt;/strong&gt; shared types with deterministic candidate IDs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review&lt;/strong&gt; &lt;code&gt;plan.md&lt;/code&gt; and choose the destination before making changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite&lt;/strong&gt; supported type-only imports, aliases, and simple barrels while keeping compatibility re-exports at the original…&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/necoz-oss/type-tune" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&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://type-tune-docs.vercel.app/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;type-tune-docs.vercel.app&lt;/span&gt;
          

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


&lt;p&gt;It's a small CLI tool built with Rust 🦀 that helps you find shared TypeScript types and move them to a better place.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Scan. Review. Move. Validate.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds simple, right?&lt;/p&gt;

&lt;p&gt;Well, that's exactly the idea! 😸&lt;/p&gt;


&lt;h2&gt;
  
  
  🐈 What Is type-tune?
&lt;/h2&gt;

&lt;p&gt;If you've worked on a large TypeScript project, you've probably used tools like &lt;a href="https://knip.dev/" rel="noopener noreferrer"&gt;Knip&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Knip&lt;/code&gt; is awesome! 🎉&lt;/p&gt;

&lt;p&gt;It helps us find unused files, exports, and dependencies, making it easier to clean up our codebases.&lt;/p&gt;

&lt;p&gt;But there's another problem I've been thinking about.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What about types that are still being used, but aren't organized very well?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TypeScript types are incredibly reusable.&lt;/p&gt;

&lt;p&gt;A type declared in one component might eventually be needed by another component, an API module, or even an entire feature.&lt;/p&gt;

&lt;p&gt;But in practice, we don't always pay much attention to maintaining our type declarations.&lt;/p&gt;

&lt;p&gt;For example, imagine we have 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;// src/features/order/OrderPage.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;OrderSummary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&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;At first, &lt;code&gt;OrderSummary&lt;/code&gt; might only be used by &lt;code&gt;OrderPage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But as the feature grows, other files start importing it.&lt;/p&gt;

&lt;p&gt;And sometimes, developers &lt;strong&gt;end up declaring similar types again&lt;/strong&gt; because they don't realize something reusable already exists.&lt;/p&gt;

&lt;p&gt;Eventually, we might have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Types scattered across components and utilities.&lt;/li&gt;
&lt;li&gt;Similar type declarations repeated within the same feature.&lt;/li&gt;
&lt;li&gt;Shared types living in files that don't really belong to them.&lt;/li&gt;
&lt;li&gt;Imports that make refactoring unnecessarily complicated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything still works!&lt;/p&gt;

&lt;p&gt;But is it easy to maintain?&lt;/p&gt;

&lt;p&gt;Hmm, not really. 😹&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;That's where type-tune comes in!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;type-tune finds exported types and interfaces that are already shared across multiple files and helps relocate them to more appropriate locations.&lt;/p&gt;

&lt;p&gt;It can also update supported imports and preserve compatibility re-exports, so we don't have to manually chase references around the project.&lt;/p&gt;

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

&lt;p&gt;Unlike &lt;code&gt;Knip&lt;/code&gt;, which focuses on unused code, type-tune focuses on &lt;strong&gt;organizing types that are actually being used&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They're complementary tools!&lt;/p&gt;

&lt;p&gt;One helps us remove what we don't need. The other helps us organize what we still need. 😸&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Why Did I Build It?
&lt;/h2&gt;

&lt;p&gt;This idea actually came from my own experience.&lt;/p&gt;

&lt;p&gt;I was working on refactoring a production codebase with &lt;strong&gt;more than 300,000 lines of code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And one thing kept bothering me.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;We weren't making very good use of our existing types.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even within the same feature, types that could have been shared were sometimes declared separately.&lt;/p&gt;

&lt;p&gt;Some types had gradually become shared dependencies, but their locations still reflected where they were originally created.&lt;/p&gt;

&lt;p&gt;As the codebase grew, maintaining those relationships became increasingly difficult.&lt;/p&gt;

&lt;p&gt;And there was something else I noticed.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 Types Are Even More Valuable in the AI Era
&lt;/h3&gt;

&lt;p&gt;I've been working with AI-assisted development quite a lot recently.&lt;/p&gt;

&lt;p&gt;And through that experience, I've come to appreciate TypeScript's type system even more.&lt;/p&gt;

&lt;p&gt;Types aren't just there to catch mistakes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;They can also act as useful guardrails for AI coding agents.&lt;/strong&gt; 🛤️&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well-defined types help communicate the structure of our applications, the relationships between data, and the contracts our code is expected to follow.&lt;/p&gt;

&lt;p&gt;In my experience, making those contracts clear is an important part of building an effective AI development workflow.&lt;/p&gt;

&lt;p&gt;But there's a catch!&lt;/p&gt;

&lt;p&gt;If our types are duplicated, scattered, or poorly maintained, we're not making full use of that information.&lt;/p&gt;

&lt;p&gt;So I started wondering&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why don't we have a tool that helps us maintain the structure of our types automatically?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I looked around, but couldn't find a tool that quite solved the problem I had in mind.&lt;/p&gt;

&lt;p&gt;So&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I decided to build one myself!&lt;/strong&gt; 🦀🎉&lt;/p&gt;

&lt;p&gt;YAY!!!&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 How to Use type-tune
&lt;/h2&gt;

&lt;p&gt;Getting started is pretty simple!&lt;/p&gt;

&lt;h3&gt;
  
  
  📦 Installation
&lt;/h3&gt;

&lt;p&gt;You can install type-tune from &lt;code&gt;npm&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm install -D type-tune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or with &lt;code&gt;pnpm&lt;/code&gt; (My favorite 😸)&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 -D type-tune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also try it without installing it into your project&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx type-tune scan --project tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The npm package requires Node.js v22 or later and runs a platform-specific native Rust binary.&lt;/p&gt;

&lt;p&gt;And since type-tune is written in Rust, you can also build and install it directly from GitHub using Cargo! 📦&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo install --git https://github.com/necoz-oss/type-tune --tag v1.0.0 type-tune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Rust source requires Rust 1.99 or later.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔍 Step 1. Find Shared Types
&lt;/h3&gt;

&lt;p&gt;First, scan your TypeScript project&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx type-tune scan --project tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;type-tune&lt;/code&gt; analyzes your project and finds candidates for relocation.&lt;/p&gt;

&lt;p&gt;By default, it looks for exported types and interfaces referenced by at least two other files.&lt;/p&gt;

&lt;p&gt;And don't worry!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scanning doesn't modify your code.&lt;/strong&gt; 😸&lt;/p&gt;
&lt;h3&gt;
  
  
  📋 Step 2. Create a Refactoring Plan
&lt;/h3&gt;

&lt;p&gt;Next, generate a plan&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx type-tune plan \
  --project tsconfig.json \
  --out .type-tune/plan.json \
  --markdown .type-tune/plan.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This creates a JSON plan and a Markdown file you can review.&lt;/p&gt;

&lt;p&gt;You can inspect which types were found, where they're currently declared, and where they could be moved.&lt;/p&gt;
&lt;h3&gt;
  
  
  🐈 Step 3. Review the Changes
&lt;/h3&gt;

&lt;p&gt;Here's an important part!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;type-tune doesn't decide to move everything automatically.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every candidate starts as &lt;code&gt;ignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can review the plan and choose which declarations should actually move.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx type-tune review \
  --markdown .type-tune/plan.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also edit the Markdown plan manually.&lt;/p&gt;

&lt;p&gt;I wanted the tool to automate the tedious work while keeping architectural decisions in the developer's hands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automation is useful. But control matters too!&lt;/strong&gt; 😸&lt;/p&gt;
&lt;h3&gt;
  
  
  🦀 Step 4. Apply the Changes
&lt;/h3&gt;

&lt;p&gt;Once you've reviewed your plan, you can preview the refactoring&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx type-tune apply \
  --plan .type-tune/plan.json \
  --markdown .type-tune/plan.md \
  --dry-run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;type-tune performs the supported rewrites, validates them with TypeScript, and restores the changed files after a normal dry-run.&lt;/p&gt;

&lt;p&gt;When you're happy with the plan, run&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx type-tune apply \
  --plan .type-tune/plan.json \
  --markdown .type-tune/plan.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it! 🎉&lt;/p&gt;

&lt;p&gt;For supported transformations, &lt;code&gt;type-tune&lt;/code&gt; handles moving declarations, updating type imports, and validating the result with the TypeScript compiler.&lt;/p&gt;

&lt;p&gt;Of course, you should still review the Git diff and run your project's own checks.&lt;/p&gt;
&lt;h3&gt;
  
  
  ✨ Want to Move Just One Type?
&lt;/h3&gt;

&lt;p&gt;There's also a direct extraction command!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx type-tune extract OrderSummary \
  --from src/features/order/OrderPage.tsx \
  --to src/features/order/types/order.ts \
  --project tsconfig.json \
  --dry-run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Pretty handy, right?&lt;/strong&gt; 😸&lt;/p&gt;


&lt;h2&gt;
  
  
  🐈 Let's Build type-tune Together!
&lt;/h2&gt;

&lt;p&gt;I released type-tune under &lt;strong&gt;Necoz&lt;/strong&gt; because I want to build it together with other developers! 😸💚&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://github.com/necoz-oss" 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%2Favatars.githubusercontent.com%2Fu%2F350971701%3Fs%3D280%26v%3D4" height="280" class="m-0" width="280"&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://github.com/necoz-oss" rel="noopener noreferrer" class="c-link"&gt;
            Necoz · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A friendly open-source community building small, simple, and useful tools together. 😸 - Necoz
          &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%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;And this time, with Rust! 🦀&lt;/p&gt;

&lt;p&gt;I believe open source becomes even more exciting when we share ideas, learn from each other, and build things together.&lt;/p&gt;

&lt;p&gt;So, if you have any ideas, suggestions, or want to contribute, &lt;strong&gt;I'd love to hear from you!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's grow type-tune together! 🐈&lt;/p&gt;




&lt;h2&gt;
  
  
  🌱 Just the Beginning!
&lt;/h2&gt;

&lt;p&gt;This is the first public release, &lt;strong&gt;v1&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;type-tune currently focuses on exported TypeScript &lt;code&gt;type&lt;/code&gt; aliases and &lt;code&gt;interface&lt;/code&gt; declarations in &lt;code&gt;.ts&lt;/code&gt; and &lt;code&gt;.tsx&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;It doesn't automatically merge duplicate type definitions or handle every possible TypeScript refactoring yet.&lt;/p&gt;

&lt;p&gt;But I think it's a useful first step toward making type maintenance easier.&lt;/p&gt;

&lt;p&gt;And there's still plenty of room to improve!&lt;/p&gt;

&lt;p&gt;We'd love to hear about the problems you've encountered while maintaining types in large TypeScript projects.&lt;/p&gt;

&lt;p&gt;Maybe you've run into the same challenges!&lt;/p&gt;

&lt;p&gt;Or maybe you've found a completely different approach.&lt;/p&gt;

&lt;p&gt;Either way, I'd love to learn from your experience! 💚&lt;/p&gt;




&lt;h2&gt;
  
  
  💚 Check It Out!
&lt;/h2&gt;

&lt;p&gt;type-tune is open source and available on GitHub!&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/necoz-oss" rel="noopener noreferrer"&gt;
        necoz-oss
      &lt;/a&gt; / &lt;a href="https://github.com/necoz-oss/type-tune" rel="noopener noreferrer"&gt;
        type-tune
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Rust-powered CLI to find shared TypeScript types and move them safely. 🦀
    &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;type-tune&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/necoz-oss/type-tune/docs/public/animation/motion-logo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnecoz-oss%2Ftype-tune%2FHEAD%2Fdocs%2Fpublic%2Fanimation%2Fmotion-logo.gif" width="400" alt="Animated type-tune logo"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/actions/workflows/test.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/necoz-oss/type-tune/actions/workflows/test.yml/badge.svg" alt="Test"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/actions/workflows/docs.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/necoz-oss/type-tune/actions/workflows/docs.yml/badge.svg" alt="Docs"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/necoz-oss/type-tune/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="MIT License"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Give shared TypeScript types a better home. 😸&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;Scan. Review. Move. Validate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;type-tune&lt;/strong&gt; is a Rust-powered CLI that finds shared TypeScript &lt;code&gt;type&lt;/code&gt; aliases and &lt;code&gt;interface&lt;/code&gt; declarations and helps move them without manually chasing imports.&lt;/p&gt;

&lt;p&gt;It creates a reviewable plan, updates supported type imports and re-exports, and validates the rewrite with TypeScript. &lt;strong&gt;You decide what moves and where.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Guides for installation, the first scan, and the review workflow live in the &lt;a href="https://type-tune-nine.vercel.app/" rel="nofollow noopener noreferrer"&gt;official documentation&lt;/a&gt;. Releases are listed on &lt;a href="https://github.com/necoz-oss/type-tune/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;, and the package is &lt;a href="https://www.npmjs.com/package/type-tune" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;type-tune&lt;/code&gt; on npm&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why type-tune?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Types often start next to a component or utility, then end up shared across a project. Moving them later means tracking imports, local dependencies, and barrels by hand.&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;Discover&lt;/strong&gt; shared types with deterministic candidate IDs.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Review&lt;/strong&gt; &lt;code&gt;plan.md&lt;/code&gt; and choose the destination before making changes.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Rewrite&lt;/strong&gt; supported type-only imports, aliases, and simple barrels while keeping compatibility re-exports at the original…&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/necoz-oss/type-tune" 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;If you find it interesting, &lt;strong&gt;please consider giving it a ⭐ on GitHub!&lt;/strong&gt; 😸&lt;/p&gt;

&lt;p&gt;And if you have any ideas, suggestions, or bugs to report, feel free to open an issue or contribute!&lt;/p&gt;

&lt;p&gt;Every bit of feedback helps make these little projects better.&lt;/p&gt;

&lt;h3&gt;
  
  
  🦀 Coming Up Next
&lt;/h3&gt;

&lt;p&gt;Oh, and one more thing!&lt;/p&gt;

&lt;p&gt;You might be wondering &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why did I choose Rust instead of TypeScript to build a TypeScript refactoring tool? Is nyaomaru a TypeScript lover, isn't it?&lt;/strong&gt; 🤔&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's actually an interesting story!&lt;/p&gt;

&lt;p&gt;I'll talk about the reasons behind that decision, what I learned while building it, and my experience using Rust in my next article!&lt;/p&gt;

&lt;p&gt;Stay tuned! 🦀&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy coding! 😸🙌&lt;/p&gt;




&lt;h2&gt;
  
  
  💚 Join Our Necoz Community!
&lt;/h2&gt;

&lt;p&gt;Oh, and one more thing! 😸&lt;/p&gt;

&lt;p&gt;We also have a &lt;strong&gt;Necoz community on DEV&lt;/strong&gt;! 🎉&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__15923"&gt;
  &lt;a href="/necoz" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" alt="necoz image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;Necoz&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;
        Necoz is a friendly open-source community building small, simple, and useful tools for developers. 🐈 

Everyone is welcome, from first-time contributors to experienced engineers. 

Let's build something awesome together! 😸
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Everyone is welcome, whether you're a developer, a writer, or just curious!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wanna join? Leave a comment below and I'll send you an invitation!&lt;/strong&gt; 💌&lt;/p&gt;

&lt;p&gt;No pressure, no obligations. Just come hang out with us! 🐈&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small. Simple. Useful. Built Together!&lt;/strong&gt; 💚&lt;/p&gt;

</description>
      <category>rust</category>
      <category>typescript</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Necoz Has a New Home on DEV! 🐈🎉</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Sun, 08 Nov 2026 11:50:29 +0000</pubDate>
      <link>https://dev.to/necoz/necoz-has-a-new-home-on-dev-3454</link>
      <guid>https://dev.to/necoz/necoz-has-a-new-home-on-dev-3454</guid>
      <description>&lt;p&gt;Hoi hoi! 👋&lt;/p&gt;

&lt;p&gt;A little update from Necoz! 😸&lt;/p&gt;

&lt;p&gt;We've created our own organization here on DEV!&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__15923"&gt;
  &lt;a href="/necoz" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15923%2F19042de3-8257-4cc5-b2c0-adebba8cbeaa.png" alt="necoz image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;Necoz&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/necoz" class="ltag__user__link"&gt;
        Necoz is a friendly open-source community building small, simple, and useful tools for developers. 🐈 

Everyone is welcome, from first-time contributors to experienced engineers. 

Let's build something awesome together! 😸
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Necoz started with a few small open-source projects, and now we're slowly building a community around them.&lt;/p&gt;

&lt;p&gt;This will be another place to share what we're building, what we're learning, and the little discoveries we make along the way.&lt;/p&gt;

&lt;p&gt;And of course, we'd love to see more contributors sharing their own experiences too! 💚&lt;/p&gt;

&lt;p&gt;Whether you're writing code, improving docs, fixing bugs, or just exploring, you're always welcome!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small. Simple. Useful. Built Together!&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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs1tfoter0wxpv9enxbh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs1tfoter0wxpv9enxbh.gif" alt="Necoz organization" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🐈 Wanna Join Necoz?
&lt;/h2&gt;

&lt;p&gt;We'd love to have you! 😸&lt;/p&gt;

&lt;p&gt;John has already joined us here on DEV! Thanks, &lt;a class="mentioned-user" href="https://dev.to/johnnylemonny"&gt;@johnnylemonny&lt;/a&gt; ! 🎉💚&lt;/p&gt;

&lt;p&gt;If you're interested in joining Necoz here on DEV, just leave a comment below and I'll send you an invitation! 💌&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everyone is welcome!&lt;/strong&gt; 🙌&lt;/p&gt;

&lt;p&gt;You don't need to write articles or contribute anything. Just joining us is more than enough!&lt;/p&gt;

&lt;p&gt;No pressure, no obligations. Let's grow this little community together, one cat at a time! 🐾&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://github.com/necoz-oss" 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%2Favatars.githubusercontent.com%2Fu%2F350971701%3Fs%3D280%26v%3D4" height="280" class="m-0" width="280"&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://github.com/necoz-oss" rel="noopener noreferrer" class="c-link"&gt;
            Necoz · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A friendly open-source community building small, simple, and useful tools together. 😸 - Necoz
          &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%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Stay a Good Cat! 🐱&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devto</category>
      <category>community</category>
      <category>organization</category>
      <category>news</category>
    </item>
    <item>
      <title>Meet Necoz! 😸 Small. Simple. Useful.</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 04 Nov 2026 16:17:07 +0000</pubDate>
      <link>https://dev.to/nyaomaru/meet-necoz-small-simple-useful-ppo</link>
      <guid>https://dev.to/nyaomaru/meet-necoz-small-simple-useful-ppo</guid>
      <description>&lt;p&gt;Hoi hoi! 👋&lt;/p&gt;

&lt;p&gt;I'm &lt;a class="mentioned-user" href="https://dev.to/nyaomaru"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who loves TypeScript and cats! 😸&lt;/p&gt;

&lt;p&gt;Today, I have some exciting news!&lt;/p&gt;

&lt;p&gt;I've created a new open-source organization called &lt;strong&gt;Necoz&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://github.com/necoz-oss" 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%2Favatars.githubusercontent.com%2Fu%2F350971701%3Fs%3D280%26v%3D4" height="280" class="m-0" width="280"&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://github.com/necoz-oss" rel="noopener noreferrer" class="c-link"&gt;
            Necoz · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A friendly open-source community building small, simple, and useful tools together. 😸 - Necoz
          &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%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Well, my projects started as little tools I built for myself.&lt;/p&gt;

&lt;p&gt;But over time, people started using them, reporting bugs, improving documentation, and contributing code.&lt;/p&gt;

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

&lt;p&gt;And I realized something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open source is much more fun when we build things together!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, I decided to give these projects a new home. 🏠&lt;/p&gt;

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




&lt;h2&gt;
  
  
  🐈 Small. Simple. Useful.
&lt;/h2&gt;

&lt;p&gt;This is the philosophy behind Necoz.&lt;/p&gt;

&lt;p&gt;We don't need to build something gigantic to make a difference.&lt;/p&gt;

&lt;p&gt;A tiny utility can solve a real problem.&lt;/p&gt;

&lt;p&gt;A small PR can make a project better.&lt;/p&gt;

&lt;p&gt;Even fixing one typo can help someone!&lt;/p&gt;

&lt;p&gt;That's what I love about open source. 😸&lt;/p&gt;




&lt;h2&gt;
  
  
  🌱 Our Three Principles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🌍 Open to Everyone
&lt;/h3&gt;

&lt;p&gt;First-time contributor?&lt;/p&gt;

&lt;p&gt;Experienced engineer?&lt;/p&gt;

&lt;p&gt;Just found a typo?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EVERYONE IS WELCOME!!! 🎉&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don't need to be an expert to contribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛠️ Build Something Useful
&lt;/h3&gt;

&lt;p&gt;Small tools. Simple solutions. Real problems.&lt;/p&gt;

&lt;p&gt;A project doesn't need a thousand features to be valuable.&lt;/p&gt;

&lt;p&gt;Sometimes one useful function is enough!&lt;/p&gt;

&lt;h3&gt;
  
  
  😸 Be a Good Cat
&lt;/h3&gt;

&lt;p&gt;Be kind. Be curious. Respect others.&lt;/p&gt;

&lt;p&gt;We can disagree about code, but we should always respect the people behind it.&lt;/p&gt;

&lt;p&gt;And don't be afraid to make mistakes.&lt;/p&gt;

&lt;p&gt;Even cats sometimes fall off tables! 😹&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Our Projects
&lt;/h2&gt;

&lt;p&gt;We're starting with two projects!&lt;/p&gt;

&lt;h3&gt;
  
  
  is-kit: A lightweight TypeScript toolkit for composing reusable type guards.
&lt;/h3&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/necoz-oss" rel="noopener noreferrer"&gt;
        necoz-oss
      &lt;/a&gt; / &lt;a href="https://github.com/necoz-oss/is-kit" rel="noopener noreferrer"&gt;
        is-kit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Build small guards. Compose them.  A lightweight, zero-dependency toolkit for building reusable TypeScript type guards that compose, refine, and preserve 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/necoz-oss/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%2Fnecoz-oss%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/necoz-oss/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;&lt;code&gt;is-kit&lt;/code&gt; is not just a collection of &lt;code&gt;isX&lt;/code&gt; helpers. Its main focus is composing
small runtime checks into reusable guards while preserving useful TypeScript
narrowing.&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;, refine properties on values you already know about, and keep
&lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow. Use it at runtime
boundaries when needed, without requiring a schema-first workflow.&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;Use &lt;code&gt;refineKey&lt;/code&gt; to narrow a &lt;strong&gt;child property&lt;/strong&gt; while preserving its parent type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections when that is useful&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;a href="https://is-kit.dev/" 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%2Fnecoz-oss%2Fis-kit%2FHEAD%2Fdocs%2Fpublic%2Fdocument_button.png" alt="Documentation Site" width="240"&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;Best…&lt;/p&gt;
&lt;/blockquote&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/necoz-oss/is-kit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  changelog-bot: A tool that helps generate useful changelogs from GitHub project history.
&lt;/h3&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/necoz-oss" rel="noopener noreferrer"&gt;
        necoz-oss
      &lt;/a&gt; / &lt;a href="https://github.com/necoz-oss/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/necoz-oss/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%2Fnecoz-oss%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/necoz-oss/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%2Fnecoz-oss%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;p&gt;The GitHub repository is &lt;a href="https://github.com/necoz-oss/changelog-bot" rel="noopener noreferrer"&gt;necoz-oss/changelog-bot&lt;/a&gt;. The npm package name is still &lt;code&gt;@nyaomaru/changelog-bot&lt;/code&gt;. In workflows, use &lt;code&gt;necoz-oss/changelog-bot&lt;/code&gt;. GitHub Actions does not follow repository redirects for actions or reusable workflows, so keep &lt;code&gt;uses:&lt;/code&gt; on the new owner. Web and git URLs for &lt;code&gt;nyaomaru/changelog-bot&lt;/code&gt; still redirect.&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…&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/necoz-oss/changelog-bot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Both started as personal projects.&lt;/p&gt;

&lt;p&gt;Now, they're part of Necoz!&lt;/p&gt;

&lt;p&gt;And hopefully, more little projects will join us someday. 😸&lt;/p&gt;


&lt;h2&gt;
  
  
  🎉 Welcome to Necoz, John!
&lt;/h2&gt;

&lt;p&gt;And guess what?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/johnnylemonny"&gt;@johnnylemonny&lt;/a&gt; has already joined Necoz!&lt;/strong&gt; 🐈🎉&lt;/p&gt;

&lt;p&gt;YAY!!! Our little cat family is growing! 😸&lt;/p&gt;

&lt;p&gt;I'm really happy to have him on board!&lt;/p&gt;

&lt;p&gt;Welcome, John! Let's build something awesome together! 🚀&lt;/p&gt;


&lt;h2&gt;
  
  
  ❤️ Let's Build Together!
&lt;/h2&gt;

&lt;p&gt;To everyone who has contributed to my projects so far...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;THANK YOU!!!&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;p&gt;Every Issue, PR, suggestion, and Star means a lot.&lt;/p&gt;

&lt;p&gt;I don't know how big Necoz will become.&lt;/p&gt;

&lt;p&gt;And that's okay!&lt;/p&gt;

&lt;p&gt;What matters is building useful things, learning together, and making open source a welcoming place.&lt;/p&gt;

&lt;p&gt;One small contribution at a time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐈 GitHub: &lt;a href="https://github.com/necoz-oss" rel="noopener noreferrer"&gt;https://github.com/necoz-oss&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 Website: &lt;a href="https://necoz.co/" rel="noopener noreferrer"&gt;https://necoz.co/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If something catches your interest, feel free to join us!&lt;/p&gt;

&lt;p&gt;And if you like our projects, a ⭐ would make these little cats very happy! 😸&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stay a Good Cat! 😸&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading! 🤲&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>software</category>
      <category>community</category>
    </item>
    <item>
      <title>Trick or Type Guard? Surviving `unknown` with is-kit 🎃</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Sat, 31 Oct 2026 15:16:52 +0000</pubDate>
      <link>https://dev.to/nyaomaru/trick-or-type-guard-surviving-unknown-with-is-kit-4oc2</link>
      <guid>https://dev.to/nyaomaru/trick-or-type-guard-surviving-unknown-with-is-kit-4oc2</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 scared of horror things 🙀&lt;/p&gt;

&lt;p&gt;Happy Halloween! 🎃👻&lt;/p&gt;

&lt;p&gt;Tonight, we're exploring a haunted TypeScript project.&lt;/p&gt;

&lt;p&gt;There are zombies 🧟, ghosts 👻, mysterious pumpkins 🎃, and a terrifying vampire 🧛 waiting for us.&lt;/p&gt;

&lt;p&gt;And they all have something to do with TypeScript types!&lt;/p&gt;

&lt;p&gt;Our mission?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Survive the night without losing type safety!&lt;/strong&gt; 😸&lt;/p&gt;

&lt;p&gt;Let's go!&lt;/p&gt;

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




&lt;h2&gt;
  
  
  🧟 Chapter 1: The Unknown Living Dead
&lt;/h2&gt;

&lt;p&gt;It's a dark and spooky night.&lt;/p&gt;

&lt;p&gt;Suddenly, something crawls out of the grave! 🧟&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;creature&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;=&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"message":"BRAAAINS!"}&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;Oh! An &lt;code&gt;unknown&lt;/code&gt; creature!&lt;/p&gt;

&lt;p&gt;Let's see what it wants.&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creature&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="c1"&gt;// ❌ 'creature' is of type 'unknown'.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;WHAT?! 🙀&lt;/p&gt;

&lt;p&gt;TypeScript won't let us!&lt;/p&gt;

&lt;p&gt;But why?&lt;/p&gt;

&lt;p&gt;Well, because we don't actually know what &lt;code&gt;creature&lt;/code&gt; is.&lt;/p&gt;

&lt;p&gt;It might be a zombie.&lt;br&gt;
It might be a ghost.&lt;br&gt;
It might even be a cute little cat. 😸&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;unknown&lt;/code&gt; means we haven't verified the value yet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;any&lt;/code&gt;, TypeScript won't let us access arbitrary properties on an &lt;code&gt;unknown&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;And that's actually a good thing!&lt;/p&gt;

&lt;p&gt;Let's try something simpler.&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="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;voice&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;=&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"BRAAAINS!"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;voice&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="c1"&gt;// BRAAAINS!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;We survived our first encounter!&lt;/p&gt;

&lt;p&gt;Of course, we could also use &lt;code&gt;typeof voice === 'string'&lt;/code&gt; here.&lt;/p&gt;

&lt;p&gt;For a single check, that's perfectly fine!&lt;/p&gt;

&lt;p&gt;But what happens when our monsters become bigger and more complicated?&lt;/p&gt;


&lt;h2&gt;
  
  
  🎃 Chapter 2: The Big Pumpkin of Unknown
&lt;/h2&gt;

&lt;p&gt;Look!&lt;/p&gt;

&lt;p&gt;A GIGANTIC PUMPKIN! 🎃&lt;/p&gt;

&lt;p&gt;And apparently, it's full of candy.&lt;/p&gt;

&lt;p&gt;But nobody knows what's really inside.&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;pumpkin&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;=&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="s2"&gt;`{
  "name": "Big Pumpkin",
  "candies": ["Chocolate", "Lollipop", "Cookie"],
  "spookyLevel": 99,
  "keeper": {
    "name": "cat",
    "friendly": true
  },
  "secret": "Meow!"
}`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's a pretty big object.&lt;/p&gt;

&lt;p&gt;Can we just grab the candy?&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pumpkin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candies&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ 'pumpkin' is of type 'unknown'.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not again! 😿&lt;/p&gt;

&lt;p&gt;We need to check what's inside before touching anything.&lt;/p&gt;

&lt;p&gt;Fortunately, we have &lt;code&gt;struct&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;arrayOf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isBoolean&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;isString&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="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;isPumpkin&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;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="na"&gt;candies&lt;/span&gt;&lt;span class="p"&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;isString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;spookyLevel&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;keeper&lt;/span&gt;&lt;span class="p"&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;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="na"&gt;friendly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isBoolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;secret&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;We composed several small guards into one big pumpkin guard.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isString&lt;/code&gt; checks strings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isNumber&lt;/code&gt; checks finite numbers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;arrayOf(isString)&lt;/code&gt; checks an array of strings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;struct&lt;/code&gt; checks nested object shapes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;optionalKey&lt;/code&gt; allows a property to be missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's open the pumpkin!&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;safeParse&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPumpkin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pumpkin&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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;candies&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ['Chocolate', 'Lollipop', 'Cookie']&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keeper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// cat 😸&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We found the candy!!! 🍬🎉&lt;/p&gt;

&lt;p&gt;When validation succeeds, &lt;code&gt;result.value&lt;/code&gt; has the type inferred from our guards.&lt;/p&gt;

&lt;p&gt;And when validation fails?&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;cursedPumpkin&lt;/span&gt; &lt;span class="o"&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;Cursed Pumpkin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;candies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100 candies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;spookyLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;keeper&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;cat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;friendly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="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;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPumpkin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cursedPumpkin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// { valid: false }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The candies were a string, not an array!&lt;/p&gt;

&lt;p&gt;Our guard rejected the cursed pumpkin.&lt;/p&gt;

&lt;p&gt;And here's the important part.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;safeParse&lt;/code&gt; doesn't magically fix or convert invalid values.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It simply tells us whether the value passed our guard.&lt;/p&gt;

&lt;p&gt;Also, &lt;code&gt;struct&lt;/code&gt; allows extra properties by default. If you need to reject additional own enumerable string keys, you can use &lt;code&gt;{ exact: true }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nice and simple!&lt;/p&gt;


&lt;h2&gt;
  
  
  🍬 Chapter 3: Trick or Type Guard!
&lt;/h2&gt;

&lt;p&gt;Ding dong! 🔔&lt;/p&gt;

&lt;p&gt;TRICK OR TREAT!!! 😸&lt;/p&gt;

&lt;p&gt;Someone hands us a mysterious gift.&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;receiveGift&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;return&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chocolate&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;spiders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&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;gift&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;=&lt;/span&gt; &lt;span class="nf"&gt;receiveGift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Wait.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is it candy?&lt;br&gt;
Or is it a box full of spiders?! 🕷️😱&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We need a way to check!&lt;/p&gt;

&lt;p&gt;And this time, we want to accept either a string or a number.&lt;/p&gt;

&lt;p&gt;Let's combine our guards with &lt;code&gt;or&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;isNumber&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;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;const&lt;/span&gt; &lt;span class="nx"&gt;isTreat&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;isNumber&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;trickOrTreat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gift&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="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;isTreat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gift&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// gift: string | number&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`🍬 Treat: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gift&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👻 Trick!&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;Now let's try 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="nf"&gt;trickOrTreat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chocolate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 🍬 Treat: Chocolate&lt;/span&gt;

&lt;span class="nf"&gt;trickOrTreat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 🍬 Treat: 100&lt;/span&gt;

&lt;span class="nf"&gt;trickOrTreat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;spiders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// 👻 Trick!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;YAY!!! 🍬&lt;/p&gt;

&lt;p&gt;No spiders for us today!&lt;/p&gt;

&lt;p&gt;This is one of my favorite things about &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;/p&gt;

&lt;p&gt;We don't need to write one giant validator.&lt;/p&gt;

&lt;p&gt;We can build small guards, combine them, and reuse them wherever we need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small guards. Simple composition. Useful TypeScript narrowing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the idea!&lt;/p&gt;

&lt;p&gt;But wait,&lt;br&gt;
Something is coming.&lt;br&gt;
Something TERRIFYING.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧛 Final Boss: The Vampire Called &lt;code&gt;any&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Suddenly, the lights go out.&lt;/p&gt;

&lt;p&gt;The door opens.&lt;/p&gt;

&lt;p&gt;A mysterious figure appears in the darkness.&lt;/p&gt;

&lt;p&gt;It's... &lt;strong&gt;COUNT ANY!!! 🧛💀&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most terrifying vampire in our TypeScript project! And he has a very dangerous power.&lt;/p&gt;

&lt;p&gt;He can make TypeScript stop checking our operations!&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;visitor&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;=&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"name":666}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A mysterious visitor has arrived.&lt;/p&gt;

&lt;p&gt;Normally, TypeScript protects us from accessing properties before we verify them.&lt;/p&gt;

&lt;p&gt;But Count Any has a trick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;THE CURSE OF &lt;code&gt;as any&lt;/code&gt;! 🧛&lt;/strong&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;vampire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;visitor&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The vampire ignores type safety!&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vampire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;No TypeScript error!&lt;/p&gt;

&lt;p&gt;Wait, What?! 🙀&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does that mean the code is safe?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At runtime&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: vampire.name.toUpperCase is not a function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;💀 GAME OVER 💀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Count Any didn't turn the number into a string.&lt;/p&gt;

&lt;p&gt;He just convinced TypeScript to stop checking it!&lt;/p&gt;

&lt;p&gt;That's what makes &lt;code&gt;any&lt;/code&gt; dangerous.&lt;/p&gt;

&lt;p&gt;It allows operations without requiring proof that they'll work.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;unknown&lt;/code&gt; behaves differently.&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;visitor&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;=&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"name":666}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ TypeScript stops us!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;TypeScript refuses to continue until we check the value.&lt;/p&gt;

&lt;p&gt;So how do we defeat Count Any?&lt;/p&gt;

&lt;p&gt;With a Type Guard, of course! 😸&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;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;isFriendlyGuest&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;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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isFriendlyGuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;visitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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="k"&gt;else&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🧄 Not tonight, Count Any!&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;And just like that&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;THE VAMPIRE HAS BEEN DEFEATED!&lt;/strong&gt; 🧄🎉&lt;/p&gt;

&lt;p&gt;Well, at least this particular cursed value has been rejected.&lt;/p&gt;

&lt;p&gt;A Type Guard checks the actual runtime value, while a type assertion only changes what TypeScript believes about it.&lt;/p&gt;

&lt;p&gt;One important detail, &lt;strong&gt;neither &lt;code&gt;unknown&lt;/code&gt; nor &lt;code&gt;any&lt;/code&gt; changes the value at runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They are TypeScript types.&lt;/p&gt;

&lt;p&gt;The difference is what the compiler allows us to do before verifying the value.&lt;/p&gt;

&lt;p&gt;And that's why &lt;code&gt;unknown&lt;/code&gt; is usually the safer choice for untrusted data.&lt;/p&gt;


&lt;h2&gt;
  
  
  😸 Epilogue: We Survived Halloween!
&lt;/h2&gt;

&lt;p&gt;What a night!&lt;/p&gt;

&lt;p&gt;We met the Unknown Living Dead, opened a gigantic pumpkin, collected some candy, and defeated Count Any!&lt;/p&gt;

&lt;p&gt;So, what did we learn?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Monster&lt;/th&gt;
&lt;th&gt;TypeScript lesson&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🧟 Unknown Living Dead&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;unknown&lt;/code&gt; prevents unchecked operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🎃 Big Pumpkin&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;struct&lt;/code&gt; validates object shapes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🍬 Trick or Treat&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;or&lt;/code&gt; combines reusable type guards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧛 Count Any&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;any&lt;/code&gt; bypasses normal type checking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Of course, &lt;code&gt;any&lt;/code&gt; isn't always evil.&lt;/p&gt;

&lt;p&gt;Sometimes it's useful when dealing with legacy code or difficult integrations.&lt;/p&gt;

&lt;p&gt;But when working with API responses, parsed JSON, and other untrusted values, keeping them as &lt;code&gt;unknown&lt;/code&gt; until validation is usually a much safer approach.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;is-kit&lt;/code&gt; makes those checks easy to compose and reuse!&lt;/p&gt;

&lt;p&gt;If you want to explore more, check out the official guide 👇&lt;/p&gt;

&lt;p&gt;📖 &lt;a href="https://is-kit.dev/guides/validate-unknown-without-schema-library" rel="noopener noreferrer"&gt;How to Validate unknown in TypeScript Without a Schema Library&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And remember&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real monster isn't &lt;code&gt;unknown&lt;/code&gt;. It's unchecked &lt;code&gt;any&lt;/code&gt;.&lt;/strong&gt; 🧛&lt;/p&gt;

&lt;p&gt;Happy Halloween!!! 🎃👻&lt;/p&gt;

&lt;p&gt;If you enjoyed this little TypeScript adventure, check out is-kit!&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.  A lightweight, zero-dependency toolkit for building reusable TypeScript type guards that compose, refine, and preserve 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;&lt;code&gt;is-kit&lt;/code&gt; is not just a collection of &lt;code&gt;isX&lt;/code&gt; helpers. Its main focus is composing
small runtime checks into reusable guards while preserving useful TypeScript
narrowing.&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;, refine properties on values you already know about, and keep
&lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow. Use it at runtime
boundaries when needed, without requiring a schema-first workflow.&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;Use &lt;code&gt;refineKey&lt;/code&gt; to narrow a &lt;strong&gt;child property&lt;/strong&gt; while preserving its parent type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections when that is useful&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;a href="https://is-kit.dev/" 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%2Fis-kit%2FHEAD%2Fdocs%2Fpublic%2Fdocument_button.png" alt="Documentation Site" width="240"&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;Best…&lt;/p&gt;
&lt;/blockquote&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;And if you like the project, a ⭐ would make this little cat very happy! 😸&lt;/p&gt;

&lt;p&gt;Thank you for reading! 🤲&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Compared 6 TypeScript Validation Libraries. Here's What Surprised Me 🤔</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 28 Oct 2026 15:37:53 +0000</pubDate>
      <link>https://dev.to/nyaomaru/i-compared-6-typescript-validation-libraries-heres-what-surprised-me-325g</link>
      <guid>https://dev.to/nyaomaru/i-compared-6-typescript-validation-libraries-heres-what-surprised-me-325g</guid>
      <description>&lt;p&gt;Hoi hoi! 👋 &amp;nbsp;&lt;/p&gt;

&lt;p&gt;I'm nyaomaru, a frontend engineer and OSS developer working mostly with TypeScript.&lt;/p&gt;

&lt;p&gt;When we need runtime validation in TypeScript, the conversation often turns into this 👇&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which library should I use?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/colinhacks/zod" rel="noopener noreferrer"&gt;&lt;code&gt;Zod&lt;/code&gt;&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/open-circle/valibot" rel="noopener noreferrer"&gt;&lt;code&gt;Valibot&lt;/code&gt;&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/arktypeio/arktype" rel="noopener noreferrer"&gt;&lt;code&gt;ArkType&lt;/code&gt;&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/samchon/typia" rel="noopener noreferrer"&gt;&lt;code&gt;typia&lt;/code&gt;&lt;/a&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or maybe, &lt;strong&gt;just write a type guard?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I maintain &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt;, so I think about this boundary quite a lot.&lt;/p&gt;

&lt;p&gt;And recently I started wondering&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are we comparing libraries that are actually solving the same problem?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, if all I want 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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;do I really need a schema system?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But if I want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structured validation errors&lt;/li&gt;
&lt;li&gt;transformations&lt;/li&gt;
&lt;li&gt;parsing&lt;/li&gt;
&lt;li&gt;schema metadata&lt;/li&gt;
&lt;li&gt;framework integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then a small boolean guard is probably not enough either.&lt;/p&gt;

&lt;p&gt;So instead of asking &lt;strong&gt;"Which validator is best?"&lt;/strong&gt;, I tried a different comparison.&lt;/p&gt;

&lt;p&gt;I implemented the same &lt;code&gt;User&lt;/code&gt; validation with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a handwritten guard with a TypeScript predicate&lt;/li&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;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/runtypes/runtypes" rel="noopener noreferrer"&gt;&lt;code&gt;Runtypes&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/open-circle/valibot" rel="noopener noreferrer"&gt;&lt;code&gt;Valibot&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/colinhacks/zod" rel="noopener noreferrer"&gt;&lt;code&gt;Zod&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/arktypeio/arktype" rel="noopener noreferrer"&gt;&lt;code&gt;ArkType&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/samchon/typia" rel="noopener noreferrer"&gt;&lt;code&gt;typia&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I compiled, tested them against the same fixtures, measured standalone browser bundle size, and ran a small steady-state runtime benchmark.&lt;/p&gt;

&lt;p&gt;The hardest part was not making seven validators compile.&lt;/p&gt;

&lt;p&gt;It was &lt;strong&gt;making seven validators mean the same thing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even “a string with at most 100 characters” can describe different runtime contracts.&lt;/p&gt;

&lt;p&gt;So, let's look at what each tool is actually buying us. 😺&lt;/p&gt;

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




&lt;h2&gt;
  
  
  🧪 The User we will validate
&lt;/h2&gt;

&lt;p&gt;Here is the shared 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;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="nl"&gt;age&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;role&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="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tags&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;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bio&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;active&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the runtime rules are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; must be a string&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; must contain 1–100 UTF-16 code units&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;age&lt;/code&gt; must be an integer from 18 to 120&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role&lt;/code&gt; must be exactly &lt;code&gt;"admin"&lt;/code&gt; or &lt;code&gt;"user"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tags&lt;/code&gt; must be an array of strings&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;profile.bio&lt;/code&gt; must be a string&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;profile.active&lt;/code&gt; must be a boolean&lt;/li&gt;
&lt;li&gt;extra keys are rejected at the root and inside &lt;code&gt;profile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;values are not coerced&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this comparison, the main input domain is &lt;strong&gt;ordinary JSON-decoded data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That matters because JavaScript can also give us strange things like inherited properties, sparse arrays, symbols, getters, and non-enumerable keys.&lt;/p&gt;

&lt;p&gt;We'll come back to those later.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. ✍️ Handwritten Guard — Sometimes a Guard Is Enough
&lt;/h2&gt;

&lt;p&gt;Let's start with the boring option, a handwritten JavaScript guard with a TypeScript predicate.&lt;/p&gt;

&lt;p&gt;No validation library.&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;isRecord&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="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="o"&gt;=&amp;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;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="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasExactKeys&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="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="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&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="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&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;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;keys&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;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;keys&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;key&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;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&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;key&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;isUser&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="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="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="nf"&gt;isRecord&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="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;hasExactKeys&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&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;age&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;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;tags&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;profile&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;return&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="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="nf"&gt;isRecord&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;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;hasExactKeys&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;profile&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;bio&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;active&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;return&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="k"&gt;return &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="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="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="nx"&gt;name&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;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;name&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;100&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInteger&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;age&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;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&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;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;120&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;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&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="o"&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;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&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;tags&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;tag&lt;/span&gt;&lt;span class="p"&gt;)&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;tag&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="o"&gt;&amp;amp;&amp;amp;&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="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bio&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="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="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&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;And then&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;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;isUser&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="nx"&gt;name&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;This is still very good.&lt;/p&gt;

&lt;p&gt;For one local boundary, a handwritten type guard has a huge advantage&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is no extra model to learn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is just JavaScript runtime checks plus a TypeScript predicate.&lt;/p&gt;

&lt;p&gt;But the cost starts to appear when your codebase grows 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;isUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;isProfile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;isRole&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;isNonEmptyString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;isAdultAge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;isStringArray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;isApiResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;isConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;isFeatureFlag&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you are not writing "one guard" anymore.&lt;/p&gt;

&lt;p&gt;You are maintaining your own tiny guard library.&lt;/p&gt;

&lt;p&gt;And this is where the next option starts to make sense. 👍&lt;/p&gt;




&lt;h2&gt;
  
  
  2. 🔧 is-kit — When Small Guards Become Reusable Building Blocks
&lt;/h2&gt;

&lt;p&gt;This is the problem I built &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt; around.&lt;/p&gt;

&lt;p&gt;I didn't want a schema-first workflow for every narrowing problem.&lt;/p&gt;

&lt;p&gt;I wanted small guards that stay small, but compose cleanly.&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;isBoolean&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;isString&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;predicateToRefine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;safeParse&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="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;isName&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="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="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;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;100&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAge&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;isNumber&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;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="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="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInteger&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="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&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;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;120&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isProfile&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bio&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;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isBoolean&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;span class="k"&gt;export&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="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;isName&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;isAge&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;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&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;isString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isProfile&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseUser&lt;/span&gt; &lt;span class="o"&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="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;safeParse&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="nx"&gt;input&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 pieces remain normal 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;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&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;isName&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;isAge&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="c1"&gt;// value is number&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 useful when &lt;strong&gt;your codebase already thinks in terms of predicates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And this is also where I would draw the line.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;safeParse()&lt;/code&gt; gives a small success/failure result around a guard.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; turn &lt;code&gt;is-kit&lt;/code&gt; into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a field-level issue system&lt;/li&gt;
&lt;li&gt;a transformation pipeline&lt;/li&gt;
&lt;li&gt;a schema metadata system&lt;/li&gt;
&lt;li&gt;an OpenAPI ecosystem&lt;/li&gt;
&lt;li&gt;a form integration layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those are the actual requirements, I would rather use a schema library than try to make a guard toolkit become one.&lt;/p&gt;

&lt;p&gt;One type-level detail also showed up in the verification,  &lt;code&gt;struct()&lt;/code&gt; and &lt;code&gt;arrayOf()&lt;/code&gt; infer readonly structures.&lt;/p&gt;

&lt;p&gt;So the runtime shape matches our &lt;code&gt;User&lt;/code&gt;, but &lt;code&gt;readonly string[]&lt;/code&gt; is not assignable to mutable &lt;code&gt;string[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That difference is real, and the type tests make it explicit instead of hiding it with a cast. 🔧&lt;/p&gt;




&lt;h2&gt;
  
  
  3. 🏃 Runtypes — A Validator Object, Not Just a Predicate
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Runtypes&lt;/code&gt; moves one level further.&lt;/p&gt;

&lt;p&gt;Instead of just a boolean predicate, you get a runtime validator object with several ways to consume 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="nb"&gt;Array&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;RtArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Boolean&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;RtBoolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Number&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;RtNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;RtObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;RtString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Union&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;runtypes&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;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RtString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withConstraint&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="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;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;100&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="s2"&gt;name length must be 1..100&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;Age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RtNumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withConstraint&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="p"&gt;(&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;isInteger&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="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&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;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;120&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="s2"&gt;age must be an integer in 18..120&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;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RtObject&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;RtString&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;Name&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;Age&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="nc"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Literal&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="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RtArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RtString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RtObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RtString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RtBoolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;exact&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;isUser&lt;/span&gt; &lt;span class="o"&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="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="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;guard&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkUser&lt;/span&gt; &lt;span class="o"&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="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="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseUser&lt;/span&gt; &lt;span class="o"&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="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="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;inspectUser&lt;/span&gt; &lt;span class="o"&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="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="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feels different from a guard toolkit.&lt;/p&gt;

&lt;p&gt;A predicate mainly answers&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this value a &lt;code&gt;User&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;code&gt;Runtype&lt;/code&gt; can also answer that question, but the same object can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;narrow with &lt;code&gt;guard&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;throw with &lt;code&gt;check&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;parse with &lt;code&gt;parse&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;expose structured failure data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, &lt;code&gt;check()&lt;/code&gt; and &lt;code&gt;parse()&lt;/code&gt; should not be treated as synonyms.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Runtypes&lt;/code&gt; supports parser behavior, and object handling can differ between validation and parsing contexts.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of runtime machinery you do not have in a plain predicate. 🏃&lt;/p&gt;




&lt;h2&gt;
  
  
  4. 🤖 Valibot — Schema Validation With a Modular API
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Valibot&lt;/code&gt; is where the comparison starts looking more "schema-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;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;v&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;valibot&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;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictObject&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;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minLength&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="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&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;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maxValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&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="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;picklist&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;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&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;export&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;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="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserSchema&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseUser&lt;/span&gt; &lt;span class="o"&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="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="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserSchema&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema validation&lt;/li&gt;
&lt;li&gt;type inference&lt;/li&gt;
&lt;li&gt;structured issues&lt;/li&gt;
&lt;li&gt;parsing APIs&lt;/li&gt;
&lt;li&gt;transformation pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the API is still function-based and modular.&lt;/p&gt;

&lt;p&gt;That is a big part of &lt;code&gt;Valibot&lt;/code&gt;'s design.&lt;/p&gt;

&lt;p&gt;Instead of every capability living as methods on large schema objects, you compose independent functions.&lt;/p&gt;

&lt;p&gt;That makes tree shaking a first-class part of the normal API.&lt;/p&gt;

&lt;p&gt;And unlike a pure schema-only mental model, &lt;code&gt;Valibot&lt;/code&gt; also exposes &lt;code&gt;v.is()&lt;/code&gt; as a type guard.&lt;/p&gt;

&lt;p&gt;So schema validation and ordinary TypeScript narrowing are not mutually exclusive.&lt;/p&gt;

&lt;p&gt;One caveat: &lt;code&gt;v.is()&lt;/code&gt; is the guard path. It does not expose validation issues or apply transformations like parsing does. 🤖&lt;/p&gt;




&lt;h2&gt;
  
  
  5. 💎 Zod — When the Schema Is Part of a Bigger Ecosystem
&lt;/h2&gt;

&lt;p&gt;Now &lt;code&gt;Zod&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="o"&gt;*&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refine&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="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;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name must contain 1..100 UTF-16 code units&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;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&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;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseUser&lt;/span&gt; &lt;span class="o"&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="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="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// This schema has no transforms, so its output type is safe for the input too.&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;isUser&lt;/span&gt; &lt;span class="o"&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="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;is&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="nf"&gt;parseUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;success&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;Zod&lt;/code&gt; does not expose a built-in &lt;code&gt;is()&lt;/code&gt; method, but this adapter can narrow &lt;code&gt;input&lt;/code&gt; because this schema has no transforms. With a transforming schema, &lt;code&gt;safeParse().data&lt;/code&gt; is the safe typed value: the original input and output types may differ.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Zod&lt;/code&gt; is not interesting because it can validate a &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All of these libraries can do that.&lt;/p&gt;

&lt;p&gt;The real value is everything around the schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;form integrations&lt;/li&gt;
&lt;li&gt;API tooling&lt;/li&gt;
&lt;li&gt;schema conversion&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;li&gt;codecs&lt;/li&gt;
&lt;li&gt;OpenAPI-related tooling&lt;/li&gt;
&lt;li&gt;framework integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your schema is already becoming part of application infrastructure, that ecosystem matters. 💎&lt;/p&gt;

&lt;p&gt;There is one important detail in this sample though.&lt;/p&gt;

&lt;p&gt;I did &lt;strong&gt;not&lt;/strong&gt; use 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;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&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;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Because our contract says&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1–100 UTF-16 code units&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Zod&lt;/code&gt;'s built-in string length checks use Unicode code points, so this sample uses &lt;code&gt;.refine()&lt;/code&gt; with JavaScript's &lt;code&gt;string.length&lt;/code&gt; to preserve the stated UTF-16 contract. The shared emoji fixture demonstrates why below.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about bundle size?
&lt;/h3&gt;

&lt;p&gt;It is too easy to write&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Valibot&lt;/code&gt; is small, &lt;code&gt;Zod&lt;/code&gt; is large.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is not a good 2026 comparison anymore.&lt;/p&gt;

&lt;p&gt;Zod also has &lt;strong&gt;Zod Mini&lt;/strong&gt;, which uses a more functional, tree-shakable API.&lt;/p&gt;

&lt;p&gt;The bundle table later in this article measures &lt;strong&gt;regular Zod&lt;/strong&gt;, not Zod Mini.&lt;/p&gt;

&lt;p&gt;So it should not be read as &lt;code&gt;Valibot&lt;/code&gt; versus the smallest possible &lt;code&gt;Zod&lt;/code&gt; setup.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Zod&lt;/code&gt; can also compile optimized validation logic at runtime with &lt;code&gt;z.compile()&lt;/code&gt; and &lt;code&gt;new Function&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I did not enable it here. &lt;/p&gt;

&lt;p&gt;That makes this distinction more useful than the broad label “generated validation”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;runtime schema compilation
  vs
build-time generation from TypeScript types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction becomes important when we get to &lt;code&gt;typia&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. 🚢 ArkType — What If the Runtime Schema Looked Like TypeScript?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ArkType&lt;/code&gt; has a very different authoring model.&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="kd"&gt;type&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;arktype&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;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;type&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;string&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;0 &amp;lt; string &amp;lt;= 100&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;18 &amp;lt;= number.integer &amp;lt;= 120&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'admin' | 'user'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&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="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="p"&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="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;onDeepUndeclaredKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reject&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;isUser&lt;/span&gt; &lt;span class="o"&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="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="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allows&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseUser&lt;/span&gt; &lt;span class="o"&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="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="nc"&gt;UserSchema&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ArkErrors&lt;/span&gt; &lt;span class="o"&gt;=&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;errors&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 this comparison&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;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vs&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;18 &amp;lt;= number.integer &amp;lt;= 120&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;Neither is "better".&lt;/p&gt;

&lt;p&gt;They are different ways to express the runtime contract.&lt;/p&gt;

&lt;p&gt;If your team already thinks naturally in TypeScript-like expressions, &lt;code&gt;ArkType&lt;/code&gt; is very readable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;allows()&lt;/code&gt; also works as a type guard.&lt;/p&gt;

&lt;p&gt;And calling the schema directly gives you validated output or an error collection.&lt;/p&gt;

&lt;p&gt;Another interesting part: &lt;code&gt;ArkType&lt;/code&gt; can precompile optimized traversal logic at runtime when the environment allows &lt;code&gt;new Function&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;ArkType&lt;/code&gt; is already doing specialization. 🚢&lt;/p&gt;

&lt;p&gt;But it is still not the same architecture as &lt;code&gt;typia&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ArkType&lt;/code&gt; starts from a runtime type representation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;typia&lt;/code&gt; starts from TypeScript types during the build.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. 🚀 typia — Generate the Validator During the Build
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;typia&lt;/code&gt; is the outlier in this comparison.&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;typia&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;tags&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;typia&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;Utf16Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TagBase&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf16Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;target&lt;/span&gt;&lt;span class="p"&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="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;validate&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.length &amp;gt;= 1 &amp;amp;&amp;amp; $input.length &amp;lt;= 100&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="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Utf16Name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;age&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;amp;&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Type&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;uint32&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Minimum&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Maximum&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;120&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;role&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="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tags&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;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bio&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;active&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="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;isUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;typia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createEquals&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;typia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createValidateEquals&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 not a runtime schema object.&lt;/p&gt;

&lt;p&gt;The source of truth is the TypeScript type plus &lt;code&gt;typia&lt;/code&gt;'s tags.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;typia&lt;/code&gt;'s build transform turns&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;typia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createEquals&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;into specialized validation code.&lt;/p&gt;

&lt;p&gt;That means there is no general runtime schema interpreter walking a &lt;code&gt;User&lt;/code&gt; schema object.&lt;/p&gt;

&lt;p&gt;But there is a trade-off&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You need build integration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The current official setup uses TypeScript 7 and &lt;code&gt;ttsc&lt;/code&gt; / typia's transform integration.&lt;/p&gt;

&lt;p&gt;Stock &lt;code&gt;tsc&lt;/code&gt; alone does not run the transform.&lt;/p&gt;

&lt;p&gt;In my verification workspace, the first &lt;code&gt;typia&lt;/code&gt; build also had to compile a Go source plugin and took several minutes.&lt;/p&gt;

&lt;p&gt;That setup cost is not part of the runtime benchmark, but it is absolutely part of the adoption decision.&lt;/p&gt;

&lt;p&gt;Also, &lt;code&gt;typia&lt;/code&gt;'s built-in &lt;code&gt;MinLength&lt;/code&gt; / &lt;code&gt;MaxLength&lt;/code&gt; did not match our UTF-16 contract.&lt;/p&gt;

&lt;p&gt;So I used a custom tag for &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Again, same-looking validation rule, different semantics.  🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  😹 Wait, Even "String Length" Was Different?
&lt;/h2&gt;

&lt;p&gt;This was the first test that made me stop.&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;😀&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;51&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="c1"&gt;// 102&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript's &lt;code&gt;string.length&lt;/code&gt; counts UTF-16 code units.&lt;/p&gt;

&lt;p&gt;So with our contract&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maximum = 100 UTF-16 code units
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this should fail.&lt;/p&gt;

&lt;p&gt;And it did fail in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;handwritten guard&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Runtypes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Valibot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ArkType&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the original &lt;code&gt;Zod&lt;/code&gt; and &lt;code&gt;typia&lt;/code&gt; samples accepted it.&lt;/p&gt;

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

&lt;p&gt;Because their &lt;strong&gt;built-in length constraints count Unicode code points instead&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For this string&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;51 emoji
= 51 code points
= 102 UTF-16 code units
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither behavior is wrong.&lt;/p&gt;

&lt;p&gt;They are just different contracts.&lt;/p&gt;

&lt;p&gt;And neither one necessarily matches "what a human sees" either, because grapheme clusters make Unicode even more fun. 😹&lt;/p&gt;

&lt;p&gt;So I changed the &lt;code&gt;Zod&lt;/code&gt; sample to &lt;code&gt;.refine()&lt;/code&gt; and &lt;code&gt;typia&lt;/code&gt; to a custom tag.&lt;/p&gt;

&lt;p&gt;That was probably the most useful lesson in the whole comparison 👇&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Equivalent-looking schemas are not automatically equivalent runtime contracts.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  So What Are We Actually Comparing?
&lt;/h2&gt;

&lt;p&gt;At this point, these no longer look like seven interchangeable libraries.&lt;/p&gt;

&lt;p&gt;They are different runtime models.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Source of truth &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Natural success API &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Structured failures &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Transformation model &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Compilation / build requirement &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Handwritten guard&lt;/td&gt;
&lt;td&gt;Type + handwritten check &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Predicate &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Manual &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Manual &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;None &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;is-kit &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Composed guards &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Predicate / &lt;code&gt;safeParse&lt;/code&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Basic valid/invalid result&lt;/td&gt;
&lt;td&gt;Guard-oriented &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;None &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtypes&lt;/td&gt;
&lt;td&gt;Runtype object &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;guard&lt;/code&gt; / &lt;code&gt;check&lt;/code&gt; / &lt;code&gt;parse&lt;/code&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Yes &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Parsers &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;None &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valibot &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Schema functions &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;is&lt;/code&gt; / &lt;code&gt;safeParse&lt;/code&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Yes &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Schema pipelines &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;None &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zod &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Schema objects &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;parse&lt;/code&gt; / &lt;code&gt;safeParse&lt;/code&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Yes &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Transforms / codecs &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;No build transform; optional runtime &lt;code&gt;z.compile()&lt;/code&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ArkType &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Runtime type expressions &amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;allows&lt;/code&gt; / invocation &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Yes &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Morphs &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;No build transform; runtime precompilation when available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;typia &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;TypeScript types and tags&lt;/td&gt;
&lt;td&gt;Generated predicates / validation results&lt;/td&gt;
&lt;td&gt;With validation APIs &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Build-generated functions&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Build transform required&lt;/strong&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is why I no longer like thinking about them as a simple upgrade ladder.&lt;/p&gt;

&lt;p&gt;A schema library is not "a better type guard".&lt;br&gt;
A generated validator is not "a better schema".&lt;/p&gt;

&lt;p&gt;They solve overlapping problems at different layers.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✅ What Actually Passed?
&lt;/h2&gt;

&lt;p&gt;The complete harness, lockfile, fixtures, probes, and generated result files live in &lt;a href="https://github.com/nyaomaru/typescript-validator-comparison/tree/b26350d01fd18cb77d55a95e2f3b112657f0d305" rel="noopener noreferrer"&gt;&lt;code&gt;typescript-validator-comparison&lt;/code&gt;&lt;/a&gt;. The article links to that immutable commit, rather than to a moving branch.&lt;/p&gt;

&lt;p&gt;To reproduce the recorded snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nyaomaru/typescript-validator-comparison.git
&lt;span class="nb"&gt;cd &lt;/span&gt;typescript-validator-comparison
git checkout &lt;span class="nt"&gt;--detach&lt;/span&gt; b26350d01fd18cb77d55a95e2f3b112657f0d305
corepack pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;
corepack pnpm verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pnpm verify&lt;/code&gt; runs lint, strict type checking, fixtures, bundle measurement, benchmarks, JavaScript-only probes, and report generation. The result tables below are that run's saved snapshot, reruns on another machine will naturally produce different timing numbers.&lt;/p&gt;

&lt;p&gt;I pinned the versions and ran the same fixture matrix against all seven implementations.&lt;/p&gt;

&lt;p&gt;Environment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date: &amp;nbsp; &amp;nbsp; &amp;nbsp; 2026-10-23
Node: &amp;nbsp; &amp;nbsp; &amp;nbsp; 24.22.1
TypeScript: 7.0.2
pnpm: &amp;nbsp; &amp;nbsp; &amp;nbsp; 11.22.0
ttsc: &amp;nbsp; &amp;nbsp; &amp;nbsp; 0.31.3
tsbuild: &amp;nbsp; &amp;nbsp;0.28.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Packages&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;is-kit &amp;nbsp;&lt;/td&gt;
&lt;td&gt;1.15.3 &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;runtypes&lt;/td&gt;
&lt;td&gt;7.0.5 &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;valibot &amp;nbsp;&lt;/td&gt;
&lt;td&gt;1.5.0 &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zod &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;4.6.5 &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;arktype &amp;nbsp;&lt;/td&gt;
&lt;td&gt;2.2.8 &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;typia &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;15.1.0 &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shared matrix contains 35 JSON fixtures.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;valid users&lt;/li&gt;
&lt;li&gt;age boundaries&lt;/li&gt;
&lt;li&gt;name boundaries&lt;/li&gt;
&lt;li&gt;emoji length cases&lt;/li&gt;
&lt;li&gt;wrong primitive types&lt;/li&gt;
&lt;li&gt;missing fields&lt;/li&gt;
&lt;li&gt;invalid roles&lt;/li&gt;
&lt;li&gt;invalid array elements&lt;/li&gt;
&lt;li&gt;extra root keys&lt;/li&gt;
&lt;li&gt;extra nested keys&lt;/li&gt;
&lt;li&gt;non-object roots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All seven matched the expected result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;35 fixtures
× 7 validators
= 245 boolean checks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 245 passed.&lt;/p&gt;

&lt;p&gt;The harness also checks input mutation, richer APIs, strict TypeScript compilation, lint, builds, narrowing, and bundled output behavior.&lt;/p&gt;

&lt;p&gt;So the implementations agree for the JSON-shaped contract in this article—not necessarily for every JavaScript value.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 One More Surprise: "Exact" Is Not Always Exact
&lt;/h2&gt;

&lt;p&gt;I also tested values outside the normal JSON-shaped input domain. The four probes in the table mean exactly 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;sparseTags&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;valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tags&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;Array&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="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inheritedFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valid&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;extraSymbol&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;valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extra&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extraUndefined&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;valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;extra&lt;/span&gt;&lt;span class="p"&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;The repository runs nine more probes, including non-enumerable properties and a throwing getter. Here is what happened for these four&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Probe &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Native&lt;/th&gt;
&lt;th&gt;is-kit&lt;/th&gt;
&lt;th&gt;Runtypes&lt;/th&gt;
&lt;th&gt;Valibot&lt;/th&gt;
&lt;th&gt;Zod &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;ArkType&lt;/th&gt;
&lt;th&gt;typia &amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sparse &lt;code&gt;tags&lt;/code&gt; array &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;td&gt;Accept &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Reject &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;td&gt;Reject &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All fields inherited &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;td&gt;Reject &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;td&gt;Accept &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra symbol property &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;td&gt;Reject &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;td&gt;Reject &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra key with value &lt;code&gt;undefined&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;td&gt;Reject &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Reject &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Reject&lt;/td&gt;
&lt;td&gt;Reject &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All seven accepted the extra non-enumerable property I tested.&lt;/p&gt;

&lt;p&gt;All seven threw when reading a getter that throws.&lt;/p&gt;

&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; mean one library is "correct" and the others are broken.&lt;/p&gt;

&lt;p&gt;It means&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Exact object validation" needs a scope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, my native guard uses&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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&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;That only sees enumerable string keys.&lt;/p&gt;

&lt;p&gt;And 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;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;skips sparse array holes.&lt;/p&gt;

&lt;p&gt;That is perfectly reasonable for JSON data.&lt;/p&gt;

&lt;p&gt;It is not a universal policy for every weird object JavaScript can create.&lt;/p&gt;

&lt;p&gt;This is another reason I think validator comparisons should state the expected input domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Bundle Size
&lt;/h2&gt;

&lt;p&gt;Now the fun table. 😺&lt;/p&gt;

&lt;p&gt;Each browser-targeted entry exports only &lt;code&gt;validate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All of them were bundled with the same esbuild settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser platform&lt;/li&gt;
&lt;li&gt;ESM&lt;/li&gt;
&lt;li&gt;ES2022&lt;/li&gt;
&lt;li&gt;minification&lt;/li&gt;
&lt;li&gt;tree shaking&lt;/li&gt;
&lt;li&gt;legal comments removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I measured raw, gzip, and Brotli output.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Validator&lt;/th&gt;
&lt;th&gt;Raw bytes&lt;/th&gt;
&lt;th&gt;gzip bytes&lt;/th&gt;
&lt;th&gt;Brotli bytes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;handwritten guard&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 588&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;337&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;295&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;is-kit &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; 3,421&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;1,530&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1,423&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;runtypes &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp;16,667&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;5,188&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;4,657&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;valibot &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; 5,722&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;1,853&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1,675&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zod &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp;89,888&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; 26,212&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 23,331&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;arktype &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; 155,549&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; 47,603&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 41,956&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;typia &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 772&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;403&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;339&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few important caveats.&lt;/p&gt;

&lt;h3&gt;
  
  
  This is standalone cost
&lt;/h3&gt;

&lt;p&gt;If your app already ships Zod, the marginal cost of one more schema is not 26 KB gzip.&lt;/p&gt;

&lt;p&gt;This table measures isolated entry bundles.&lt;/p&gt;

&lt;h3&gt;
  
  
  This is regular Zod
&lt;/h3&gt;

&lt;p&gt;Not &lt;code&gt;Zod&lt;/code&gt; Mini.&lt;/p&gt;

&lt;p&gt;So this table does &lt;strong&gt;not&lt;/strong&gt; answer&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Valibot&lt;/code&gt; vs &lt;code&gt;Zod&lt;/code&gt; Mini?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Generated does not mean zero bytes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;typia&lt;/code&gt;'s final guard bundle was only 772 bytes raw in this test.&lt;/p&gt;

&lt;p&gt;But it still shipped generated code and a uint32 helper.&lt;/p&gt;

&lt;p&gt;"Generated validator" does not mean "free validator".&lt;/p&gt;

&lt;p&gt;Still, the architectural difference is very visible here.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Runtime
&lt;/h2&gt;

&lt;p&gt;I almost did not include runtime numbers because validator benchmarks get misleading very quickly.&lt;br&gt;
But since the workspace already had the same contract and fixtures, I ran a small steady-state benchmark.&lt;/p&gt;

&lt;p&gt;Three cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;valid user&lt;/li&gt;
&lt;li&gt;wrong &lt;code&gt;id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;wrong nested &lt;code&gt;profile.active&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each library ran in a separate Node process.&lt;/p&gt;

&lt;p&gt;Each case used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20,000 warmup calls&lt;/li&gt;
&lt;li&gt;9 measured samples&lt;/li&gt;
&lt;li&gt;50,000 calls per sample&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Schemas and validators were created outside the measured loop.&lt;/p&gt;

&lt;p&gt;Results are median nanoseconds per call, with min–max in parentheses.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Validator&lt;/th&gt;
&lt;th&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Valid&lt;/th&gt;
&lt;th&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Wrong &lt;code&gt;id&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;&amp;nbsp; Wrong &lt;code&gt;profile.active&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;handwritten guard&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;79.9 (77.3–114.1)&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 73.6 (72.1–84.9)&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;80.8 (79.0–96.3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;is-kit &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;232.0 (225.7–257.5)&lt;/td&gt;
&lt;td&gt;34.5 (33.1–38.9)&lt;/td&gt;
&lt;td&gt;210.3 (203.6–218.1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;runtypes &amp;nbsp;&lt;/td&gt;
&lt;td&gt;5804.0 (5425.5–6298.0)&lt;/td&gt;
&lt;td&gt;9221.8 (8443.2–11063.2)&lt;/td&gt;
&lt;td&gt;10101.2 (9287.6–10503.5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;valibot &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp;951.2 (425.2–1782.7)&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; 200.2 (96.1–422.3)&lt;/td&gt;
&lt;td&gt;&amp;nbsp; 1112.1 (648.4–1538.1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zod &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; 612.6 (351.3–1203.5)&lt;/td&gt;
&lt;td&gt;1491.6 (862.9–2824.6)&lt;/td&gt;
&lt;td&gt;2208.5 (1592.4–4757.4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;arktype &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp;291.5 (278.1–614.4)&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 9.6 (6.6–17.7)&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 12.0 (11.3–37.6)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;typia &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;21.9 (20.5–46.1)&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2.0 (1.9–6.4)&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 11.5 (10.8–17.2)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Please do &lt;strong&gt;not&lt;/strong&gt; read this as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;typia is the fastest validator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a microbenchmark.&lt;/p&gt;

&lt;p&gt;The same small object shapes are validated repeatedly, so V8 can optimize very aggressively.&lt;/p&gt;

&lt;p&gt;I would not interpret a &lt;code&gt;2.0 ns&lt;/code&gt; result as a portable "cost of validation" in a real application.&lt;/p&gt;

&lt;p&gt;The success APIs are also different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;handwritten guard uses a predicate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;is-kit&lt;/code&gt; uses a predicate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Runtypes&lt;/code&gt; uses &lt;code&gt;.guard()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Valibot&lt;/code&gt; uses &lt;code&gt;is()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Zod&lt;/code&gt; uses &lt;code&gt;safeParse(...).success&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ArkType&lt;/code&gt; uses &lt;code&gt;allows()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;typia&lt;/code&gt; uses a generated equals guard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those operations do not all perform exactly the same work.&lt;/p&gt;

&lt;p&gt;What the benchmark &lt;em&gt;does&lt;/em&gt; show is that architecture and failure path matter.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the handwritten guard pays for exact-key checks before rejecting the wrong &lt;code&gt;id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ArkType's invalid paths were much cheaper than its valid path&lt;/li&gt;
&lt;li&gt;typia's generated guard had very low steady-state overhead here&lt;/li&gt;
&lt;li&gt;Runtypes' richer validation machinery costs more in this particular hot loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is useful.&lt;/p&gt;

&lt;p&gt;A universal speed ranking is not.&lt;/p&gt;


&lt;h2&gt;
  
  
  So... Which One Would I Use? 🤔
&lt;/h2&gt;

&lt;p&gt;After doing all of this, my decision tree is actually pretty simple.&lt;/p&gt;
&lt;h3&gt;
  
  
  Just one local narrowing check?
&lt;/h3&gt;

&lt;p&gt;Use a handwritten 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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;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;// input is User&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependency is a feature when the problem is small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lots of reusable guards?
&lt;/h3&gt;

&lt;p&gt;This is where I use &lt;code&gt;is-kit&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;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;isName&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;isAge&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="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 problem is not "I need a schema".&lt;/p&gt;

&lt;p&gt;The problem is "I keep rebuilding the same predicates and composition helpers".&lt;/p&gt;

&lt;h3&gt;
  
  
  Want a validator object?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Runtypes&lt;/code&gt; is interesting when you like having one runtime object that can: 🏃&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;guard
check
parse
inspect
assert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Want schema validation with a modular API?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Valibot&lt;/code&gt;. 🤖&lt;/p&gt;

&lt;p&gt;Especially if client bundle size and tree shaking are important parts of the design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want schemas tied into a bigger ecosystem?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Zod&lt;/code&gt;. 💎&lt;/p&gt;

&lt;p&gt;That is probably its strongest reason to exist in many applications.&lt;/p&gt;

&lt;p&gt;And if bundle size is a concern, evaluate Zod Mini separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want the schema syntax to feel like TypeScript?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ArkType&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;18 &amp;lt;= number.integer &amp;lt;= 120&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;is a very different authoring experience from chained schema methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want TypeScript types transformed into validators during the build?
&lt;/h3&gt;

&lt;p&gt;typia.&lt;br&gt;
But I would treat the build integration as part of the decision, not a footnote.&lt;/p&gt;




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

&lt;p&gt;These tools overlap, but they live at different layers.&lt;/p&gt;

&lt;p&gt;The hard part is not making seven validators compile. It is making seven validators mean the same thing.&lt;/p&gt;

&lt;p&gt;So before comparing package size or benchmark screenshots, ask what the boundary needs to return and what runtime machinery is worth carrying.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use the smallest model that still represents your boundary well.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then, which one will you use? &lt;/p&gt;

&lt;p&gt;Thank you for reading! 🤲&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>is-kit Reached 100 Stars ⭐🎉 Here’s What Changed Since 50</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 21 Oct 2026 15:08:25 +0000</pubDate>
      <link>https://dev.to/nyaomaru/is-kit-reached-100-stars-heres-what-changed-since-50-49pf</link>
      <guid>https://dev.to/nyaomaru/is-kit-reached-100-stars-heres-what-changed-since-50-49pf</guid>
      <description>&lt;p&gt;Hoi hoi!&lt;/p&gt;

&lt;p&gt;YAY! &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt; just reached &lt;strong&gt;100 GitHub stars⭐&lt;/strong&gt; 🎉🎉🎉&lt;/p&gt;

&lt;p&gt;When it reached 50 stars, I wrote about how we use it in a production TypeScript application serving more than 100,000 users. 👇&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nyaomaru/is-kit-reached-50-stars-heres-how-we-use-it-in-production-2i5b" class="crayons-story__hidden-navigation-link"&gt;is-kit Reached 50 Stars ⭐ Here’s How We Use It in Production&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/nyaomaru/is-kit-reached-50-stars-heres-how-we-use-it-in-production-2i5b" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Real-world scale for type safety&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-4352154" 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/is-kit-reached-50-stars-heres-how-we-use-it-in-production-2i5b" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 12&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-reached-50-stars-heres-how-we-use-it-in-production-2i5b" id="article-link-4352154"&gt;
          is-kit Reached 50 Stars ⭐ Here’s How We Use It in Production
        &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/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/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/is-kit-reached-50-stars-heres-how-we-use-it-in-production-2i5b" 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="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;97&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-reached-50-stars-heres-how-we-use-it-in-production-2i5b#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              21&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;I could write the same story again. But that would be boring. 😹&lt;/p&gt;

&lt;p&gt;A lot has happened since then.&lt;/p&gt;

&lt;p&gt;The library gained &lt;strong&gt;new APIs&lt;/strong&gt;, some existing APIs &lt;strong&gt;became stricter&lt;/strong&gt;, I started &lt;strong&gt;using it in more of my own OSS projects&lt;/strong&gt;, and &lt;strong&gt;one real-world use case even exposed a type inference edge case in&lt;/strong&gt; &lt;a href="https://github.com/Effect-TS/effect" rel="noopener noreferrer"&gt;&lt;code&gt;Effect&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So this time, instead of explaining the production adoption again, I want to look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What changed between 50 and 100 stars&lt;/li&gt;
&lt;li&gt;Some of the most important APIs added since then&lt;/li&gt;
&lt;li&gt;How I am actually using &lt;code&gt;is-kit&lt;/code&gt; outside the original production application&lt;/li&gt;
&lt;li&gt;A real bug report involving &lt;code&gt;Effect.catchIf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;What 100 stars means to me now&lt;/li&gt;
&lt;/ul&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%2Fx3mfwqekgn0yxaip3cds.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%2Fx3mfwqekgn0yxaip3cds.png" alt="is-kit 100 stars" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⭐ First, What Did 50 Stars Mean?
&lt;/h2&gt;

&lt;p&gt;At 50 stars, the biggest thing I wanted to show was that &lt;a href="https://github.com/nyaomaru/is-kit" rel="noopener noreferrer"&gt;&lt;code&gt;is-kit&lt;/code&gt;&lt;/a&gt; was not only a toy project.&lt;/p&gt;

&lt;p&gt;It was already being used in a real production application. 🚀&lt;/p&gt;

&lt;p&gt;The application had repeated runtime 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;and several larger user-defined type guards for things like HTTP errors, literal unions, arrays, API values, and plain objects.&lt;/p&gt;

&lt;p&gt;The interesting part was &lt;strong&gt;not&lt;/strong&gt; that &lt;code&gt;is-kit&lt;/code&gt; made those checks shorter.&lt;br&gt;
The interesting part was that it gave them one &lt;strong&gt;reusable shape&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Small guards could be composed.&lt;/p&gt;

&lt;p&gt;Application-owned helpers could sit between the library and feature code. And the result could still participate in normal TypeScript control flow.&lt;/p&gt;

&lt;p&gt;That part has not changed.&lt;/p&gt;

&lt;p&gt;But since 50 stars, the library itself has moved quite a bit. 👀&lt;/p&gt;


&lt;h2&gt;
  
  
  🔑 Property Refinement Became a First-Class Use Case
&lt;/h2&gt;

&lt;p&gt;One thing I kept running into was this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I already have a good type guard.&lt;br&gt;
I just want to apply it to one property.&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Maybe I already know how to validate a 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="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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But now I want to express&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This object has a &lt;code&gt;name&lt;/code&gt; property, and that property is a string."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds simple, but there are actually a few different cases.&lt;/p&gt;

&lt;p&gt;A required property is &lt;strong&gt;not the same as an optional property&lt;/strong&gt;.&lt;br&gt;
An array index is &lt;strong&gt;not the same as an object property&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And an optional property that becomes known to be defined should &lt;strong&gt;narrow differently from one that is still optional&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;is-kit&lt;/code&gt; added three property refinement helpers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;refineKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refineDefinedKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refineIndex&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is simple&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Take a type guard you already trust and lift it onto a property or array element.&lt;/strong&gt;&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="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;refineKey&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;hasStringName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&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;p&gt;Now the string guard is reusable at a deeper level instead of being rewritten inside another custom predicate.&lt;/p&gt;

&lt;p&gt;This is a small API, but I like it because it follows the same design goal as the rest of &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do not build one giant validator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build small checks that can be reused in normal TypeScript code&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔍 Then &lt;code&gt;discriminatedUnion&lt;/code&gt; Got Much More Serious
&lt;/h2&gt;

&lt;p&gt;The biggest addition since the 50-star article is probably the exhaustive discriminated union API introduced in &lt;code&gt;v1.15.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Consider a common TypeScript 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;Result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&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;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At runtime, validating this is not difficult.&lt;/p&gt;

&lt;p&gt;You can check &lt;code&gt;ok&lt;/code&gt;, then validate the corresponding fields.&lt;/p&gt;

&lt;p&gt;But there is another question&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can TypeScript verify that the validator itself covers the whole union?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where &lt;code&gt;discriminatedUnion&lt;/code&gt; comes in.&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;discriminatedUnion&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;oneOfValues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;typedStruct&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;Result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&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;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultUnion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;discriminatedUnion&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Result&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resultUnion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&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="kc"&gt;true&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="na"&gt;true&lt;/span&gt;&lt;span class="p"&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="nb"&gt;Extract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&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="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()({&lt;/span&gt;
    &lt;span class="na"&gt;ok&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;value&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="na"&gt;false&lt;/span&gt;&lt;span class="p"&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="nb"&gt;Extract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&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="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()({&lt;/span&gt;
    &lt;span class="na"&gt;ok&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The runtime behavior is useful.&lt;/p&gt;

&lt;p&gt;But the more interesting part is the compile-time contract.&lt;/p&gt;

&lt;p&gt;The builder checks things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing discriminant branches&lt;/li&gt;
&lt;li&gt;Extra branches&lt;/li&gt;
&lt;li&gt;Incompatible branch guards&lt;/li&gt;
&lt;li&gt;Incomplete discriminant tuples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if the union changes, the guard cannot silently stay incomplete.&lt;/p&gt;

&lt;p&gt;That matters because one of the easiest ways for a hand-written type guard to become dangerous is drift.&lt;/p&gt;

&lt;p&gt;The TypeScript type evolves.&lt;br&gt;
The runtime guard does not.&lt;br&gt;
And the compiler cannot normally tell you that the two are no longer aligned.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;discriminatedUnion&lt;/code&gt; is one attempt to make that class of mistake harder.&lt;/p&gt;


&lt;h2&gt;
  
  
  💪 The Library Also Became Stricter
&lt;/h2&gt;

&lt;p&gt;Not every improvement between 50 and 100 stars was a new feature.&lt;/p&gt;

&lt;p&gt;A lot of work was about making existing behavior more precise.&lt;/p&gt;

&lt;p&gt;For example, recent releases included fixes around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;recordOf&lt;/code&gt; literal-key narrowing&lt;/li&gt;
&lt;li&gt;native &lt;code&gt;Error&lt;/code&gt; brand checks&lt;/li&gt;
&lt;li&gt;array guards with overridden iterators&lt;/li&gt;
&lt;li&gt;exact &lt;code&gt;struct&lt;/code&gt; validation&lt;/li&gt;
&lt;li&gt;symbol-keyed struct schemas&lt;/li&gt;
&lt;li&gt;cross-realm struct schemas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There was also more package-level testing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; now checks published-package behavior from both ESM and CommonJS TypeScript consumers, and also has &lt;code&gt;Deno&lt;/code&gt; / &lt;code&gt;JSR&lt;/code&gt; smoke coverage.&lt;/p&gt;

&lt;p&gt;This part is less exciting to show in a code snippet.&lt;/p&gt;

&lt;p&gt;But for a small library, I think it is important.&lt;/p&gt;

&lt;p&gt;Making sure they behave correctly in weird JavaScript and TypeScript environments is where things become real.&lt;/p&gt;


&lt;h2&gt;
  
  
  🤖 I Started Using &lt;code&gt;is-kit&lt;/code&gt; in Other OSS Too
&lt;/h2&gt;

&lt;p&gt;Since reaching 50 stars, I've also started using &lt;code&gt;is-kit&lt;/code&gt; more naturally in other OSS projects I maintain.&lt;/p&gt;

&lt;p&gt;For example, in my changelog tooling of &lt;a href="https://github.com/nyaomaru/changelog-bot" rel="noopener noreferrer"&gt;&lt;code&gt;changelog-bot&lt;/code&gt;&lt;/a&gt;, I use &lt;code&gt;define&lt;/code&gt; to keep small predicates reusable and composable instead of turning every runtime check into an isolated helper.&lt;/p&gt;

&lt;p&gt;A simplified example 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="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;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;CHANGELOG_SECTIONS&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;Added&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;Changed&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;Fixed&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;Removed&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;isChangelogSection&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;CHANGELOG_SECTIONS&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="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="nx"&gt;CHANGELOG_SECTIONS&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 exact code is not the important part.&lt;/p&gt;

&lt;p&gt;What changed for me is that I no longer think&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I should use my library here."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I just reach for the guard I need.&lt;/p&gt;

&lt;p&gt;A library starts becoming useful to its maintainer when it stops feeling like a demo of itself.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 Then an &lt;code&gt;is-kit&lt;/code&gt; Guard Ran Into an Effect Type Bug
&lt;/h2&gt;

&lt;p&gt;This was one of the most interesting things that happened recently.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;is-kit&lt;/code&gt; user was using a type guard with Effect's &lt;code&gt;catchIf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually, the refinement looked 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="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;SomeError&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;That is completely normal for &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In fact, &lt;code&gt;unknown&lt;/code&gt; is exactly where the library is designed to start.&lt;/p&gt;

&lt;p&gt;But with &lt;code&gt;Effect.catchIf&lt;/code&gt;, that refinement could affect generic inference in an unexpected way.&lt;/p&gt;

&lt;p&gt;The error channel could become widened to &lt;code&gt;unknown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reported case resulted in this Effect PR 👏 And merged 🚀&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/Effect-TS/effect/pull/9252" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        fix(effect): Made catchIf NoInfer Refinement's first arg with generic Err param
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#9252&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/nikelborm" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F29357907%3Fv%3D4" alt="nikelborm avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/nikelborm" rel="noopener noreferrer"&gt;nikelborm&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/Effect-TS/effect/pull/9252" rel="noopener noreferrer"&gt;&lt;time&gt;Oct 15, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Type&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Refactor&lt;/li&gt;
&lt;li&gt;[ ] Feature&lt;/li&gt;
&lt;li&gt;[x] Bug Fix&lt;/li&gt;
&lt;li&gt;[ ] Optimization&lt;/li&gt;
&lt;li&gt;[ ] Documentation Update&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Description&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;When passing &lt;code&gt;(arg: unknown) =&amp;gt; arg is SomeError&lt;/code&gt; to some &lt;code&gt;catchIf&lt;/code&gt;s, it turned the error channel into &lt;code&gt;unknown&lt;/code&gt;. This PR makes only &lt;code&gt;self&lt;/code&gt;'s Error slot influence the types, regardless of the type taken by refinement.&lt;/p&gt;
&lt;p&gt;Validation: the Channel, Effect, and Stream type-test files pass on TypeScript 5.9.3 and 6.0.3, and &lt;code&gt;pnpm check&lt;/code&gt; passes. Temporarily reverting the implementation reproduces unknown error-channel widening in Channel data-last usage and in unrelated-refinement cases across all three APIs. The matching data-first refinement cases also pass without the fix. No tests were changed during maintainer validation.&lt;/p&gt;
&lt;p&gt;Closes EFF-2233&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Effect-TS/effect/pull/9252" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;The relevant type originally looked 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="nx"&gt;refinement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Predicate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Refinement&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EB&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;and the fix changed it to&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;refinement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Predicate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Refinement&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NoInfer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;E&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;EB&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 important idea is that the &lt;strong&gt;refinement's input type should not be allowed to redefine the error type already established by the &lt;code&gt;Effect&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The PR also adds type tests for cases 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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;catchIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;mixedEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;AiError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;AiError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&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;Without the fix, the refinement input could participate in inference and widen the error channel.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;NoInfer&lt;/code&gt;, the original error type remains the source of truth.&lt;/p&gt;

&lt;p&gt;The same pattern was fixed for &lt;code&gt;Channel&lt;/code&gt; and &lt;code&gt;Stream&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;There was also a &lt;code&gt;Bluesky&lt;/code&gt; post about the issue&lt;/p&gt;


&lt;blockquote class="bluesky-embed"&gt;
&lt;p&gt;Not like the lib itself caught it, but when I created a reusable operator:

export const isNotFoundError = and(
  isObject,
  refineDefinedKey('reason', equalsKey('_tag', 'NotFound')),
)

I noticed my streams having unknown in the error channel

github.com/Effect-TS/ef...&lt;/p&gt;— &lt;a href="https://bsky.app/profile/did:plc:uloc6bgqlunyjpqh2odxrqar?ref_src=embed" rel="noopener noreferrer"&gt;Evangeline Rome 🌵🦎🌱🐢🐍🐸 🥦 (@evadeva.bsky.social)&lt;/a&gt; &lt;a href="https://bsky.app/profile/did:plc:uloc6bgqlunyjpqh2odxrqar/post/3mxwiqfeo3s2j?ref_src=embed" rel="noopener noreferrer"&gt;2026-10-15T16:38:16.787Z&lt;/a&gt;
&lt;/blockquote&gt;


&lt;p&gt;I really liked this bug because &lt;strong&gt;it came from an interaction I would probably never have discovered by testing &lt;code&gt;is-kit&lt;/code&gt; alone&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; did not "break Effect". And &lt;code&gt;Effect&lt;/code&gt; was not doing something absurd either.&lt;/p&gt;

&lt;p&gt;It was a subtle TypeScript inference interaction between a generic API and a refinement whose input was intentionally broad.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of edge case that appears when libraries start meeting each other in real code.&lt;/p&gt;


&lt;h2&gt;
  
  
  👍 This Is the Kind of Feedback I Wanted
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;is-kit&lt;/code&gt; reached 50 stars, I wrote&lt;/p&gt;

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

&lt;p&gt;That was already enough to make me happy.&lt;/p&gt;

&lt;p&gt;100 stars still does not mean the library is huge.&lt;/p&gt;

&lt;p&gt;A star is still not a benchmark.&lt;br&gt;
It is still not proof of correctness.&lt;br&gt;
And it definitely does not mean every TypeScript project should install &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But something has changed.&lt;/p&gt;

&lt;p&gt;People are not only starring the repository anymore.&lt;/p&gt;

&lt;p&gt;They are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using the library&lt;/li&gt;
&lt;li&gt;Asking questions&lt;/li&gt;
&lt;li&gt;Finding edge cases&lt;/li&gt;
&lt;li&gt;Reporting bugs&lt;/li&gt;
&lt;li&gt;Comparing it with other validation approaches&lt;/li&gt;
&lt;li&gt;Using its guards with other TypeScript libraries&lt;/li&gt;
&lt;li&gt;Making me reconsider APIs I thought were already finished&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is much more valuable than the number itself. 💖&lt;/p&gt;


&lt;h2&gt;
  
  
  💬 The Project Is Also Becoming Easier to Explain
&lt;/h2&gt;

&lt;p&gt;Another thing changed between 50 and 100 stars.&lt;/p&gt;

&lt;p&gt;I became much clearer about what &lt;code&gt;is-kit&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; trying to replace every schema library.&lt;br&gt;
It is &lt;strong&gt;not&lt;/strong&gt; trying to turn every runtime value into a schema-first workflow.&lt;br&gt;
And it is &lt;strong&gt;not&lt;/strong&gt; trying to hide normal TypeScript control flow.&lt;/p&gt;

&lt;p&gt;The core question is still very small&lt;/p&gt;

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

&lt;p&gt;Sometimes a schema library is the right answer.&lt;/p&gt;

&lt;p&gt;For example, if I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rich validation errors&lt;/li&gt;
&lt;li&gt;Transformations&lt;/li&gt;
&lt;li&gt;Form validation&lt;/li&gt;
&lt;li&gt;A contract shared across systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I may want &lt;code&gt;Zod&lt;/code&gt;, &lt;code&gt;Valibot&lt;/code&gt;, &lt;code&gt;ArkType&lt;/code&gt;, or something else.&lt;/p&gt;

&lt;p&gt;But sometimes I already have normal TypeScript code and I only need a reusable narrowing function.&lt;/p&gt;

&lt;p&gt;That is where I want &lt;code&gt;is-kit&lt;/code&gt; to be 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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&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;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Handle HTTP client errors&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="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;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isHttpClientError&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;isMaybeNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nullish&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;Small runtime decisions.&lt;br&gt;
Normal functions.&lt;br&gt;
Normal TypeScript.&lt;/p&gt;


&lt;h2&gt;
  
  
  🐱 What 100 Stars Means to Me
&lt;/h2&gt;

&lt;p&gt;100 stars is still small compared with the largest TypeScript OSS projects.&lt;/p&gt;

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

&lt;p&gt;What matters to me is that the project is becoming more real in ways that have nothing to do with the star counter.&lt;/p&gt;

&lt;p&gt;Since the 50-star milestone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New APIs were added&lt;/li&gt;
&lt;li&gt;Existing APIs became stricter&lt;/li&gt;
&lt;li&gt;Runtime edge cases were fixed&lt;/li&gt;
&lt;li&gt;Package compatibility testing improved&lt;/li&gt;
&lt;li&gt;I started using &lt;code&gt;is-kit&lt;/code&gt; naturally in other OSS&lt;/li&gt;
&lt;li&gt;External usage produced feedback I could not generate alone&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;unknown&lt;/code&gt;-based refinement even helped expose an inference edge case in Effect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last part is probably my favorite. &lt;/p&gt;

&lt;p&gt;Because the goal was never to build a library that only looks nice in its own &lt;code&gt;README&lt;/code&gt;!!!&lt;/p&gt;

&lt;p&gt;The goal is &lt;strong&gt;to make reusable runtime narrowing work naturally inside real TypeScript code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And now I am starting to see that happen outside my own repository too.&lt;/p&gt;

&lt;p&gt;Thank you to everyone who starred the project, tried it, reported something, asked a question, opened an issue, or simply read the code. 🙏&lt;/p&gt;

&lt;p&gt;There is still a lot I want to improve.&lt;/p&gt;

&lt;p&gt;But 100 feels pretty nice. 😸⭐&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.  A lightweight, zero-dependency toolkit for building reusable TypeScript type guards that compose, refine, and preserve 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;&lt;code&gt;is-kit&lt;/code&gt; is not just a collection of &lt;code&gt;isX&lt;/code&gt; helpers. Its main focus is composing
small runtime checks into reusable guards while preserving useful TypeScript
narrowing.&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;, refine properties on values you already know about, and keep
&lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow. Use it at runtime
boundaries when needed, without requiring a schema-first workflow.&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;Use &lt;code&gt;refineKey&lt;/code&gt; to narrow a &lt;strong&gt;child property&lt;/strong&gt; while preserving its parent type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections when that is useful&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;a href="https://is-kit.dev/" 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%2Fis-kit%2FHEAD%2Fdocs%2Fpublic%2Fdocument_button.png" alt="Documentation Site" width="240"&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;Best…&lt;/p&gt;
&lt;/blockquote&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;Thank you for reading! 🤲&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>How Do You Validate `unknown` in TypeScript Without a Schema Library? 🛡️</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 14 Oct 2026 15:35:25 +0000</pubDate>
      <link>https://dev.to/nyaomaru/how-do-you-validate-unknown-in-typescript-without-a-schema-library-1a11</link>
      <guid>https://dev.to/nyaomaru/how-do-you-validate-unknown-in-typescript-without-a-schema-library-1a11</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 obsessed with gamba. 😸🦐&lt;/p&gt;

&lt;p&gt;Recently, I was thinking about a very common TypeScript problem.&lt;/p&gt;

&lt;p&gt;Suppose some value comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an API response&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSON.parse&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;local storage&lt;/li&gt;
&lt;li&gt;a message payload&lt;/li&gt;
&lt;li&gt;third-party code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What type should it have before we actually verify it?&lt;/p&gt;

&lt;p&gt;Probably 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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That part is easy.&lt;/p&gt;

&lt;p&gt;The interesting question is what comes next.&lt;/p&gt;

&lt;p&gt;How do we safely turn that &lt;code&gt;unknown&lt;/code&gt; into something useful?&lt;/p&gt;

&lt;p&gt;And do we really need a full schema library every time?&lt;/p&gt;

&lt;p&gt;Let's look at it! 👀&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://is-kit.dev/guides/validate-unknown-without-schema-library" 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/https%3A%2F%2Fis-kit.dev%2Fiskit_guide4.gif" height="336" 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://is-kit.dev/guides/validate-unknown-without-schema-library" rel="noopener noreferrer" class="c-link"&gt;
            Validate unknown in TypeScript Without a Schema Library | is-kit
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Validate unknown TypeScript values with small composable type guards and safeParse, without adopting a schema-first validation workflow.
          &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%2Fis-kit.dev%2Fiskit_favicon.png" width="192" height="192"&gt;
          is-kit.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;






&lt;h2&gt;
  
  
  🧱 &lt;code&gt;unknown&lt;/code&gt; Is Doing Its Job
&lt;/h2&gt;

&lt;p&gt;Suppose we have&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;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="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;TypeScript refuses to let us access &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At first this can feel annoying.&lt;/p&gt;

&lt;p&gt;But TypeScript is actually telling us something important&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We have a value.&lt;/p&gt;

&lt;p&gt;We do not know what it is yet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is exactly what an untrusted boundary value should mean.&lt;/p&gt;

&lt;p&gt;An HTTP response does not become a &lt;code&gt;User&lt;/code&gt; just because our application expects a &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Neither does parsed JSON.&lt;/p&gt;

&lt;p&gt;So I like to think of &lt;code&gt;unknown&lt;/code&gt; like this&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;unknown&lt;/code&gt; is not missing type information.&lt;/p&gt;

&lt;p&gt;It is an honest description of a value we have not verified yet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problem is not &lt;code&gt;unknown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem is how we establish trust.&lt;/p&gt;


&lt;h2&gt;
  
  
  😈 The Easy Escape: &lt;code&gt;as User&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There is a very short way out.&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="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="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;input&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;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;TypeScript is happy.&lt;/p&gt;

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

&lt;p&gt;Except, &lt;strong&gt;nothing was actually checked&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This still works&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;input&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&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="mi"&gt;123&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;unknown&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;input&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;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// 💥 Runtime error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;as User&lt;/code&gt; changes what the compiler believes.&lt;/p&gt;

&lt;p&gt;It does not inspect the runtime value.&lt;br&gt;
It does not add missing properties.&lt;br&gt;
It does not turn &lt;code&gt;123&lt;/code&gt; into a string.&lt;/p&gt;

&lt;p&gt;So if runtime trust matters, a type assertion is not validation.&lt;/p&gt;

&lt;p&gt;We need an actual runtime check.&lt;/p&gt;


&lt;h2&gt;
  
  
  🛡️ A Type Guard Connects Runtime and TypeScript
&lt;/h2&gt;

&lt;p&gt;Let's start small.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;is-kit&lt;/code&gt;, we can build a guard 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;isString&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="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;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="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;member&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;Now we can pass &lt;code&gt;unknown&lt;/code&gt; directly to 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="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;isUser&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="nx"&gt;name&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="c1"&gt;// input:&lt;/span&gt;
  &lt;span class="c1"&gt;// Readonly&amp;lt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//   id: string;&lt;/span&gt;
  &lt;span class="c1"&gt;//   name: string;&lt;/span&gt;
  &lt;span class="c1"&gt;//   role: "admin" | "member";&lt;/span&gt;
  &lt;span class="c1"&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 the part I like.&lt;/p&gt;

&lt;p&gt;The same check does two jobs&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;runtime
→ does this value actually match?

TypeScript
→ what is this value after the check succeeds?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is no separate schema definition and no type assertion.&lt;/p&gt;

&lt;p&gt;We just compose small runtime predicates.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧩 The Shape Can Grow Naturally
&lt;/h2&gt;

&lt;p&gt;Real payloads are usually a little more complicated.&lt;/p&gt;

&lt;p&gt;Maybe a user has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an optional profile&lt;/li&gt;
&lt;li&gt;a nullable bio&lt;/li&gt;
&lt;li&gt;a list of tags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can keep composing 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;arrayOf&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;nullable&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;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="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;isProfile&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;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="na"&gt;bio&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;nullable&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="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;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;member&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;profile&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;isProfile&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&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;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;Then&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;isUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tags&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But&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;isUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tags&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="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each piece is still just a guard.&lt;/p&gt;

&lt;p&gt;That means we can test it independently, reuse it somewhere else, or compose it into something larger.&lt;/p&gt;

&lt;p&gt;There is also an important difference here&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;optionalKey&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;means&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the property may be absent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;while&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;nullable&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;means&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the value may be null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those are different runtime contracts, so I prefer keeping them separate.&lt;/p&gt;


&lt;h2&gt;
  
  
  📦 What Does &lt;code&gt;safeParse&lt;/code&gt; Add?
&lt;/h2&gt;

&lt;p&gt;A direct guard is often enough.&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;isUser&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="nf"&gt;renderUser&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But sometimes I want the validation result itself to move through the program.&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="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;safeParse&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="nx"&gt;input&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="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="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;safeParse&lt;/code&gt; gives us a small tagged result&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;valid&lt;/span&gt;&lt;span class="p"&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;/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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;valid&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So TypeScript can narrow it with a normal branch.&lt;/p&gt;

&lt;p&gt;The important part is what &lt;code&gt;safeParse&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; do.&lt;/p&gt;

&lt;p&gt;It does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clone the value&lt;/li&gt;
&lt;li&gt;coerce the value&lt;/li&gt;
&lt;li&gt;generate a new object&lt;/li&gt;
&lt;li&gt;transform invalid data into valid data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On success, it returns the same value after the guard has accepted it.&lt;/p&gt;

&lt;p&gt;So I think of the difference 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;guard
→ boolean + narrowing

safeParse
→ tagged result + validated value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Same runtime rule.&lt;/p&gt;

&lt;p&gt;Different control-flow shape.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧃 What About JSON?
&lt;/h2&gt;

&lt;p&gt;This is one of the most common boundaries.&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="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;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The awkward part is that &lt;code&gt;JSON.parse&lt;/code&gt; returns &lt;code&gt;any&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So now the unsafe value can spread through the program before we validate it.&lt;/p&gt;

&lt;p&gt;Instead, we can keep the boundary small&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;safeJsonParse&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;body&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="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;safeJsonParse&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;isUser&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;renderUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Conceptually, the flow is&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JSON text
   ↓
decode
   ↓
unknown
   ↓
guard
   ↓
validated value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Invalid JSON and guard failures both produce&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;valid&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And again, nothing is coerced just to make the value pass.&lt;/p&gt;

&lt;p&gt;I like this because the unsafe part stays right at the decode boundary.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚪 Should Extra Keys Be Allowed?
&lt;/h2&gt;

&lt;p&gt;There is another small design choice that appears at object boundaries.&lt;/p&gt;

&lt;p&gt;Suppose we expect&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;username&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;password&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;Should this also be accepted?&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;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neko&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;admin&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;With &lt;code&gt;struct&lt;/code&gt;, extra keys are allowed by default.&lt;/p&gt;

&lt;p&gt;That is often useful for API responses because a server may add a new field without breaking an older client.&lt;/p&gt;

&lt;p&gt;But sometimes we really want a closed object shape.&lt;/p&gt;

&lt;p&gt;Then we can use&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;isCredentials&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&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;Now&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;isCredentials&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neko&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;while&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;isCredentials&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neko&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important part is not that one mode is always better.&lt;/p&gt;

&lt;p&gt;It is that the boundary should express the contract we actually want.&lt;/p&gt;


&lt;h2&gt;
  
  
  🤔 So Do We Still Need Schema Libraries?
&lt;/h2&gt;

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

&lt;p&gt;Sometimes absolutely.&lt;/p&gt;

&lt;p&gt;A type guard toolkit and a schema library overlap a little, but they are not trying to solve exactly the same problem.&lt;/p&gt;

&lt;p&gt;For example, a schema library is probably the better choice when we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structured validation errors with field paths&lt;/li&gt;
&lt;li&gt;multiple validation issues at once&lt;/li&gt;
&lt;li&gt;coercion&lt;/li&gt;
&lt;li&gt;default values&lt;/li&gt;
&lt;li&gt;transformations&lt;/li&gt;
&lt;li&gt;schema-generated types or external artifacts&lt;/li&gt;
&lt;li&gt;deep integration with a form or API framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are larger validation workflows.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; asks a narrower question&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does this value satisfy this runtime predicate?

If yes, what can TypeScript safely narrow it to?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That narrower model is sometimes exactly what I want.&lt;/p&gt;

&lt;p&gt;For example&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Good fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inline &lt;code&gt;typeof&lt;/code&gt; checks&lt;/td&gt;
&lt;td&gt;One small local condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type guards&lt;/td&gt;
&lt;td&gt;Reusable boolean validation and narrowing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema library&lt;/td&gt;
&lt;td&gt;Rich validation, transformation, and schema workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So I don't think the question is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Type guards or schema libraries—which one wins?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more useful question is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the smallest validation model that solves this boundary?&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🎯 The Important Part
&lt;/h2&gt;

&lt;p&gt;I started with a simple value&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The tempting shortcut was&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&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;But that only changes TypeScript's belief.&lt;/p&gt;

&lt;p&gt;It does not establish runtime trust.&lt;/p&gt;

&lt;p&gt;A runtime guard gives us a much more useful flow&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unknown
   ↓
runtime check
   ↓
narrowed value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And when we want a result-oriented API&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unknown
   ↓
guard
   ↓
safeParse
   ↓
{ valid: true, value }
or
{ valid: false }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;No assertion pretending the check already happened.&lt;/p&gt;

&lt;p&gt;Just small runtime rules that TypeScript can understand too. 😸&lt;/p&gt;

&lt;p&gt;If you want to see the full guide, including JSON boundaries, exact objects, and custom domain refinements:&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://is-kit.dev/guides/validate-unknown-without-schema-library" 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/https%3A%2F%2Fis-kit.dev%2Fiskit_guide4.gif" height="336" 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://is-kit.dev/guides/validate-unknown-without-schema-library" rel="noopener noreferrer" class="c-link"&gt;
            Validate unknown in TypeScript Without a Schema Library | is-kit
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Validate unknown TypeScript values with small composable type guards and safeParse, without adopting a schema-first validation workflow.
          &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%2Fis-kit.dev%2Fiskit_favicon.png" width="192" height="192"&gt;
          is-kit.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;And if you want to explore &lt;code&gt;is-kit&lt;/code&gt; itself:&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.  A lightweight, zero-dependency toolkit for building reusable TypeScript type guards that compose, refine, and preserve 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;&lt;code&gt;is-kit&lt;/code&gt; is not just a collection of &lt;code&gt;isX&lt;/code&gt; helpers. Its main focus is composing
small runtime checks into reusable guards while preserving useful TypeScript
narrowing.&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;, refine properties on values you already know about, and keep
&lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow. Use it at runtime
boundaries when needed, without requiring a schema-first workflow.&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;Use &lt;code&gt;refineKey&lt;/code&gt; to narrow a &lt;strong&gt;child property&lt;/strong&gt; while preserving its parent type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections when that is useful&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;a href="https://is-kit.dev/" 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%2Fis-kit%2FHEAD%2Fdocs%2Fpublic%2Fdocument_button.png" alt="Documentation Site" width="240"&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;Best…&lt;/p&gt;
&lt;/blockquote&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;If it looks useful, a ⭐ on GitHub is always very welcome!&lt;/p&gt;

&lt;p&gt;Thanks for reading! 🙌&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>How Do We Extract the “Why” from a PR into a CHANGELOG with Jev? 🤔</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Sat, 10 Oct 2026 15:49:58 +0000</pubDate>
      <link>https://dev.to/nyaomaru/how-do-we-extract-the-why-from-a-pr-into-a-changelog-with-jev-52pk</link>
      <guid>https://dev.to/nyaomaru/how-do-we-extract-the-why-from-a-pr-into-a-changelog-with-jev-52pk</guid>
      <description>&lt;p&gt;Hoi hoi! 👋&lt;/p&gt;

&lt;p&gt;I'm &lt;a class="mentioned-user" href="https://dev.to/nyaomaru"&gt;@nyaomaru&lt;/a&gt;, a frontend engineer currently building an AI-assisted CHANGELOG generator and experimenting with Jev. 😸&lt;/p&gt;

&lt;p&gt;Recently, I have been thinking about one problem.&lt;/p&gt;

&lt;p&gt;How can we extract this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Why&lt;/span&gt;

The previous implementation could leave stale release data after an update.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;from a pull request and put it into a CHANGELOG?&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### Fixed&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Fix cache invalidation

  Why: The previous implementation could leave stale release data after an update.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Getting the &lt;strong&gt;WHAT&lt;/strong&gt; is relatively easy.&lt;br&gt;
Getting the &lt;strong&gt;WHY&lt;/strong&gt; is much harder.&lt;br&gt;
And I didn't want AI to simply invent a nice-looking reason.&lt;/p&gt;

&lt;p&gt;Let's look at how &lt;code&gt;changelog-bot&lt;/code&gt; currently approaches this! 👀&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;h2&gt;
  
  
  📝 WHAT Is Easy. WHY Is Hard.
&lt;/h2&gt;

&lt;p&gt;Suppose a PR contains.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Description&lt;/span&gt;

Extract shared runtime configuration into ProviderBase.

Update OpenAI, Anthropic, and Gemini adapters to use the shared implementation.

This reduces duplicated setup logic and makes adding new providers easier.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first two sentences mainly describe &lt;strong&gt;WHAT&lt;/strong&gt; and &lt;strong&gt;HOW&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But this one is different&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This reduces duplicated setup logic and makes adding new providers easier.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is much closer to &lt;strong&gt;WHY&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We could simply give the whole PR to an LLM and ask&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Why was this change made?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But then the model may summarize, paraphrase, or infer a reason that was never explicitly written.&lt;/p&gt;

&lt;p&gt;For a CHANGELOG, I wanted something stricter.&lt;/p&gt;

&lt;p&gt;Maybe WHY extraction is not a generation problem.&lt;/p&gt;

&lt;p&gt;Maybe it is a &lt;strong&gt;selection problem&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✂️ First, Find Possible WHY Candidates
&lt;/h2&gt;

&lt;p&gt;Before calling a model, &lt;code&gt;changelog-bot&lt;/code&gt; preprocesses the PR description.&lt;/p&gt;

&lt;p&gt;It looks for signals such as&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;STRONG_CANONICAL_SECTION_NAMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;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;why&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;reason&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;because&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;motivation&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;context&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;background&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;problem&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;rationale&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;RATIONALE_MARKER_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;why|because|so that|in order to|reason|rationale|motivation|to avoid|to prevent|context|problem&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;|,&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*so&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/i&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 removes template noise such as checkboxes, placeholders, and irrelevant fields.&lt;/p&gt;

&lt;p&gt;Then it builds small candidate snippets&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;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildWhyCandidateSnippets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sections&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;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxCharsPerPr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasTargetSection&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 instead of sending a large PR body, we might get&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Extract shared runtime configuration into ProviderBase.&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;This reduces duplicated setup logic and makes adding new providers easier.&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;Now the problem is much smaller.&lt;/p&gt;

&lt;p&gt;Which candidate is actually the WHY?&lt;/p&gt;


&lt;h2&gt;
  
  
  🧠 Asking Jev Two Questions
&lt;/h2&gt;

&lt;p&gt;This is where I started experimenting with &lt;a href="https://typesafe.ai/" rel="noopener noreferrer"&gt;&lt;code&gt;Jev&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I don't ask &lt;code&gt;Jev&lt;/code&gt; to write a WHY sentence.&lt;/p&gt;

&lt;p&gt;Instead, &lt;strong&gt;each candidate is evaluated with two questions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First&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;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Does the target candidate explicitly state why this changelog change was made?&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;Basically&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this actually a reason?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But that alone is not enough.&lt;/p&gt;

&lt;p&gt;Imagine&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We changed the cache invalidation logic.

The README was updated because the previous explanation was confusing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The second sentence definitely contains a reason.&lt;/p&gt;

&lt;p&gt;But it explains the README change, not the cache invalidation change.&lt;/p&gt;

&lt;p&gt;So we also ask&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;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Does the target candidate state a reason that applies to the identified changelog change?&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;Now each candidate has two signals&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;probability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;explicitWhyProbability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;relevanceProbability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;changeRelevanceProbability&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 the real question becomes&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is this a reason?

AND

Is it the reason for this change?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎯 Selecting the Candidate
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;changelog-bot&lt;/code&gt; compares the candidate probabilities&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;bestCandidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;candidateProbabilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidate&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;combinedWhyCandidateProbability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&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;combinedWhyCandidateProbability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;best&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 both minimum thresholds must pass&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;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;selectedCandidate&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;bestCandidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;probability&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;JEV_MIN_EXPLICIT_WHY_PROBABILITY&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;bestCandidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relevanceProbability&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;JEV_MIN_CHANGE_RELEVANCE_PROBABILITY&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 candidate does not pass&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No WHY is added.&lt;/p&gt;

&lt;p&gt;And I think that is important.&lt;/p&gt;

&lt;p&gt;Sometimes the correct answer is&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;There is no reliable WHY in this PR.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is better than inventing one.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔒 Keep the Original PR Text
&lt;/h2&gt;

&lt;p&gt;This is probably my favourite part.&lt;/p&gt;

&lt;p&gt;When a candidate is accepted&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;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;prNumber&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;prNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;why&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectedCandidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mappedConfidence&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;Notice&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;why&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectedCandidate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The final WHY is the original PR text.&lt;/p&gt;

&lt;p&gt;Not newly generated prose.&lt;/p&gt;

&lt;p&gt;So the pipeline becomes&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR description
      ↓
candidate extraction
      ↓
Jev evaluates candidates
      ↓
select candidate
      ↓
original source text
      ↓
CHANGELOG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;Jev&lt;/code&gt; decides.&lt;/p&gt;

&lt;p&gt;The PR author still provides the sentence. 😸&lt;/p&gt;


&lt;h2&gt;
  
  
  🧱 Where Does WHY Fit in the v1 Pipeline?
&lt;/h2&gt;

&lt;p&gt;While working on WHY extraction, I have also been redesigning the &lt;code&gt;changelog-bot&lt;/code&gt; pipeline for v1.&lt;/p&gt;

&lt;p&gt;The main idea is &lt;strong&gt;deterministic-first&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A complete CHANGELOG should exist even when AI is disabled or fails.&lt;/p&gt;

&lt;p&gt;So AI should enrich the result, not own the result.&lt;/p&gt;

&lt;p&gt;The target v1 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;source changes
      ↓
ReleaseChange[]
      ↓
deterministic classification
      ↓
complete ReleaseDraft
      ↓
optional editorial enrichment
      +
optional WHY enrichment
      ↓
ReleaseResult
      ↓
deterministic renderer
      ↓
CHANGELOG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;WHY extraction is therefore an independent optional enrichment stage.&lt;/p&gt;

&lt;p&gt;Conceptually&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReleaseDraft / ReleaseChange[]
      ↓
find eligible WHY targets
      ↓
collect PR rationale candidates
      ↓
LLM or Jev
      ↓
trust / confidence validation
      ↓
attach structured WHY notes
      ↓
ReleaseResult
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One important change here is that the WHY engine does &lt;strong&gt;not&lt;/strong&gt; need to control the final Markdown.&lt;/p&gt;

&lt;p&gt;Today, some of the pipeline still works by finding targets from generated Markdown and inserting WHY text afterward.&lt;/p&gt;

&lt;p&gt;For v1, I want to remove that boundary.&lt;/p&gt;

&lt;p&gt;Instead&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;prNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;why&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectedCandidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;confidence&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;becomes structured release data first.&lt;/p&gt;

&lt;p&gt;Then one deterministic renderer decides how it appears&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; Fix cache invalidation
&lt;span class="p"&gt;  -&lt;/span&gt; Why: The previous implementation could leave stale release data after an update.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So the responsibility becomes clearer&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHY engine
→ find the evidence

Renderer
→ format the evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is also part of a larger rule I am using for the v1 migration&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deterministic code owns the CHANGELOG structure. AI only enriches it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Phases 1–4 of this migration are already implemented, and the structured rendering and enrichment boundary is part of the next phase.&lt;/p&gt;

&lt;p&gt;You can see the full v1 design here 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nyaomaru/changelog-bot/blob/main/v1-design.md" rel="noopener noreferrer"&gt;https://github.com/nyaomaru/changelog-bot/blob/main/v1-design.md&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 But What Threshold Should We Use?
&lt;/h2&gt;

&lt;p&gt;Of course, once we have probabilities, another question appears.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.50?
0.60?
0.80?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I didn't want to pick one only because it felt reasonable.&lt;/p&gt;

&lt;p&gt;So I created an evaluation harness with an initial 14-case corpus.&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;candidates&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;Separate changelog execution into input-resolution and output-finalization phases.&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;This simplifies the main orchestration flow and runs independent GitHub lookups concurrently.&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;expectedSelectedCandidateIndex&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;We also need negative examples&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;candidates&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;Extract shared provider runtime configuration into ProviderBase.&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;Refactor OpenAI, Anthropic, and Gemini adapters to inherit it.&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;expectedSelectedCandidateIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is plenty of implementation information.&lt;/p&gt;

&lt;p&gt;But no strong explicit WHY.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;null&lt;/code&gt; is the correct result.&lt;/p&gt;


&lt;h2&gt;
  
  
  🤝 Then John Joined the Project
&lt;/h2&gt;

&lt;p&gt;While I was working on this experiment, &lt;a class="mentioned-user" href="https://dev.to/johnnylemonny"&gt;@johnnylemonny&lt;/a&gt; joined the project as a contributor. 🎉&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://github.com/johnnylemonny" 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%2Favatars.githubusercontent.com%2Fu%2F248761666%3Fv%3D4%3Fs%3D400" height="460" class="m-0" width="460"&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://github.com/johnnylemonny" rel="noopener noreferrer" class="c-link"&gt;
            johnnylemonny (John) · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Full‑stack developer creating fast, modern, and refined applications. I focus on simplicity, performance, and real value for users. Open to opportunities. - johnnylemonny
          &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%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;John proposed expanding the small benchmark into roughly 50 cases covering&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explicit Rationale
Implementation-Only
Template &amp;amp; Noise
Multilingual PRs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I liked the idea, but I wanted one rule to stay clear&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ground truth should be independent of the current Jev threshold.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the benchmark first defines whether a candidate is actually valid.&lt;/p&gt;

&lt;p&gt;Then we evaluate different thresholds afterward.&lt;/p&gt;

&lt;p&gt;John implemented the expanded corpus in PR &lt;a href="https://github.com/nyaomaru/changelog-bot/pull/212" rel="noopener noreferrer"&gt;#212&lt;/a&gt;. 🙌&lt;/p&gt;

&lt;p&gt;The benchmark grew from 14 cases to 50, including tricky negative cases.&lt;/p&gt;

&lt;p&gt;He also added finer threshold sweeps&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="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can compare precision, recall, and F1 instead of saying&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;0.8 feels safe! 👍&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's measure it instead. 😸&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 The Current Pipeline
&lt;/h2&gt;

&lt;p&gt;Today, WHY extraction roughly 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;Pull Request
     ↓
Normalize PR body
     ↓
Extract WHY candidates
     ↓
Remove noise
     ↓
Local trust check
     ↓
Jev:
  explicit rationale?
  relevant to this change?
     ↓
Apply threshold
     ↓
Candidate or none
     ↓
Original PR text
     ↓
CHANGELOG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are quite a few steps for one small &lt;code&gt;Why:&lt;/code&gt; line. 😂&lt;/p&gt;

&lt;p&gt;But that is what made this problem interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 What I Learned
&lt;/h2&gt;

&lt;p&gt;When I started this feature, I thought the main question was&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How can AI write a good reason?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I think the more interesting question is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How can we find evidence that the reason already exists?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So my current approach is simple&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use deterministic code to find evidence.

Use Jev to evaluate the evidence.

Keep the original evidence as the output.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And thanks to John's contributions, we now have a much better way to test whether that approach actually works.&lt;/p&gt;

&lt;p&gt;Open source is fun when someone looks at your experiment and says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if we test this properly?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and then builds it with you. 🙌&lt;/p&gt;

&lt;p&gt;&lt;code&gt;changelog-bot&lt;/code&gt; is still evolving, and Jev support is still experimental.&lt;/p&gt;

&lt;p&gt;I'm curious to see where this approach goes next!&lt;/p&gt;

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

&lt;p&gt;Thanks for reading!&lt;/p&gt;

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

</description>
      <category>opensource</category>
      <category>githubactions</category>
      <category>ai</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Migrating a Real TypeScript OSS Library from tsup to tsdown 🔧</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 07 Oct 2026 15:59:28 +0000</pubDate>
      <link>https://dev.to/nyaomaru/migrating-a-real-typescript-oss-library-from-tsup-to-tsdown-5b80</link>
      <guid>https://dev.to/nyaomaru/migrating-a-real-typescript-oss-library-from-tsup-to-tsdown-5b80</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The config migration was small. Preserving the package contract was the interesting part.&lt;/p&gt;
&lt;/blockquote&gt;

&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 enjoyed looking for fungus this season. 😸🍄&lt;/p&gt;

&lt;p&gt;Recently, I migrated its build setup from &lt;a href="https://github.com/egoist/tsup" rel="noopener noreferrer"&gt;&lt;code&gt;tsup&lt;/code&gt;&lt;/a&gt; to &lt;a href="https://github.com/rolldown/tsdown" rel="noopener noreferrer"&gt;&lt;code&gt;tsdown&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At first, I expected this to be a very small task.&lt;/p&gt;

&lt;p&gt;Just remove &lt;code&gt;tsup&lt;/code&gt;.&lt;br&gt;
Just install &lt;code&gt;tsdown&lt;/code&gt;.&lt;br&gt;
Just change the config.&lt;br&gt;
Run the build.&lt;br&gt;
Done! 🎉&lt;/p&gt;

&lt;p&gt;And honestly...&lt;/p&gt;

&lt;p&gt;The config migration &lt;strong&gt;was&lt;/strong&gt; small.&lt;/p&gt;

&lt;p&gt;But there was one interesting problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The first &lt;code&gt;tsdown&lt;/code&gt; build succeeded while silently changing files that were already part of the package's public contract.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So this article isn't really an introduction to &lt;code&gt;tsdown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, I want to show what happened when I migrated a real published TypeScript library from &lt;code&gt;tsup&lt;/code&gt; to &lt;code&gt;tsdown&lt;/code&gt;, what actually changed, and how I verified that consumers would still receive the same package.&lt;/p&gt;

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


&lt;h2&gt;
  
  
  🤔 Why Migrate a Package That Already Builds Correctly?
&lt;/h2&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 zero-dependency TypeScript library for runtime type guards.&lt;/p&gt;

&lt;p&gt;Its build setup was already pretty boring.&lt;/p&gt;

&lt;p&gt;And boring build systems are good. 😸&lt;/p&gt;

&lt;p&gt;The package had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one entry point&lt;/li&gt;
&lt;li&gt;ESM and CJS outputs&lt;/li&gt;
&lt;li&gt;bundled declaration files&lt;/li&gt;
&lt;li&gt;explicit &lt;code&gt;package.json&lt;/code&gt; exports&lt;/li&gt;
&lt;li&gt;a declaration banner&lt;/li&gt;
&lt;li&gt;a packed-package smoke test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before the migration, it used&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&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;Package&lt;/td&gt;
&lt;td&gt;&lt;code&gt;is-kit@1.14.2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundler&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tsup@8.5.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/index.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ESM + CJS + bundled declarations&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;&lt;code&gt;esnext&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I wasn't trying to solve a broken build.&lt;/p&gt;

&lt;p&gt;The motivation was mostly maintenance.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tsdown&lt;/code&gt; is built around Rolldown, has an active ecosystem, and is explicitly designed as a migration path for projects currently using &lt;code&gt;tsup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the question wasn't&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can &lt;code&gt;tsdown&lt;/code&gt; build this library?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I move the build setup to &lt;code&gt;tsdown&lt;/code&gt; without changing what existing consumers receive?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a much more useful question for a published package.&lt;/p&gt;


&lt;h2&gt;
  
  
  📦 The Package Contract I Needed to Preserve
&lt;/h2&gt;

&lt;p&gt;For an application, changing an output filename may not matter much.&lt;/p&gt;

&lt;p&gt;For a library, it can be a breaking change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; already exposes files through explicit package exports.&lt;/p&gt;

&lt;p&gt;The important paths were effectively&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;"exports"&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;"."&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;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.mjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.js"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;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;So I wanted the migration to preserve&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/index.mjs
dist/index.js
dist/index.d.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;along with the existing ESM/CJS runtime behavior, declaration compatibility, export set, and declaration banner.&lt;/p&gt;

&lt;p&gt;In other words&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The source code was not the contract here. The packed npm package was.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction became important very quickly.&lt;/p&gt;


&lt;h2&gt;
  
  
  🛠️ The Migration Looked Almost Trivial
&lt;/h2&gt;

&lt;p&gt;I replaced &lt;code&gt;tsup&lt;/code&gt; with &lt;code&gt;tsdown&lt;/code&gt; and replaced&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tsup.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;with&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tsdown.config.mts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I intentionally used &lt;code&gt;.mts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The package itself is not &lt;code&gt;"type": "module"&lt;/code&gt;, and using an ESM-specific config extension avoids having Node reinterpret the config file and emit related warnings.&lt;/p&gt;

&lt;p&gt;Most of the important options mapped almost 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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&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;tsdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;format&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;esm&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;cjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;dts&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="na"&gt;clean&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="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;banner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dtsBanner&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 declaration banner also stayed dts-only with &lt;code&gt;banner: { dts: dtsBanner }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So far, everything looked easy.&lt;/p&gt;

&lt;p&gt;Then I ran the build.&lt;/p&gt;

&lt;p&gt;It passed.&lt;/p&gt;

&lt;p&gt;And the package contract was wrong. 🙀&lt;/p&gt;


&lt;h2&gt;
  
  
  💥 The First Build Succeeded and Changed My Filenames
&lt;/h2&gt;

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

&lt;p&gt;Before the migration, the important generated 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;Build&lt;/th&gt;
&lt;th&gt;Main generated files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tsup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;index.mjs&lt;/code&gt;, &lt;code&gt;index.js&lt;/code&gt;, &lt;code&gt;index.d.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;default &lt;code&gt;tsdown&lt;/code&gt; migration&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;index.mjs&lt;/code&gt;, &lt;code&gt;index.cjs&lt;/code&gt;, &lt;code&gt;index.d.mts&lt;/code&gt;, &lt;code&gt;index.d.cts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;tsdown&lt;/code&gt; build completed successfully with exit code &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But my existing &lt;code&gt;package.json&lt;/code&gt; still expected&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require → ./dist/index.js
types   → ./dist/index.d.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those files no longer matched the generated output.&lt;/p&gt;

&lt;p&gt;If I had stopped at&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and published the package.&lt;/p&gt;

&lt;p&gt;CJS consumers and TypeScript resolution could have been broken.&lt;/p&gt;

&lt;p&gt;This was the most important lesson from the migration&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build success ≠ package contract preserved.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A bundler only knows whether it successfully produced its output.&lt;/p&gt;

&lt;p&gt;It doesn't automatically mean that output still matches every promise your already-published package makes.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔧 Fixing the Public Contract with &lt;code&gt;outExtensions&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://tsdown.dev/guide/migrate-from-tsup" rel="noopener noreferrer"&gt;&lt;code&gt;tsdown&lt;/code&gt; migration guide&lt;/a&gt; explicitly calls out the rename from &lt;code&gt;outExtension&lt;/code&gt; to &lt;code&gt;outExtensions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;is-kit&lt;/code&gt;, I used it to preserve the existing filenames&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;outExtensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;format&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;dts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.d.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.d.mts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.mjs&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;Now the relevant output became&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/index.mjs
dist/index.js
dist/index.d.mts
dist/index.d.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and the existing exports continued to resolve correctly.&lt;/p&gt;

&lt;p&gt;I don't really consider the original output behavior a &lt;code&gt;tsdown&lt;/code&gt; bug.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tsdown&lt;/code&gt; chooses extensions based on the package type and output format to avoid ambiguous module interpretation.&lt;/p&gt;

&lt;p&gt;That's reasonable.&lt;/p&gt;

&lt;p&gt;But for an existing package, &lt;strong&gt;reasonable new defaults are still changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If consumers already depend on your filenames, those filenames are part of your compatibility surface.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 Verifying the Published Package with a Smoke Test
&lt;/h2&gt;

&lt;p&gt;This is where an existing &lt;strong&gt;packed-package smoke test&lt;/strong&gt; in &lt;code&gt;is-kit&lt;/code&gt; helped a lot.&lt;/p&gt;

&lt;p&gt;I already had a &lt;code&gt;pnpm test:package&lt;/code&gt; command that builds the package, runs &lt;code&gt;npm pack&lt;/code&gt;, installs the generated tarball into a temporary project, and verifies it from a real consumer's perspective.&lt;/p&gt;

&lt;p&gt;Instead of testing 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/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;it tests this&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-1.14.2.tgz
        ↓
temporary consumer
        ↓
npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A library can work perfectly inside its own repository while still publishing a broken package, so this smoke test exercises the artifact that users would actually install.&lt;/p&gt;

&lt;p&gt;After the migration, I expanded the test to verify more of the package contract 👇&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;exports["."].import&lt;/code&gt; → &lt;code&gt;./dist/index.mjs&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;exports["."].require&lt;/code&gt; → &lt;code&gt;./dist/index.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;exports["."].types&lt;/code&gt; → &lt;code&gt;./dist/index.d.ts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime dependencies&lt;/td&gt;
&lt;td&gt;✅ 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESM exports&lt;/td&gt;
&lt;td&gt;✅ same 83 exports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CJS exports&lt;/td&gt;
&lt;td&gt;✅ same 83 exports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Declaration banner&lt;/td&gt;
&lt;td&gt;✅ preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESM runtime import&lt;/td&gt;
&lt;td&gt;✅ pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CJS runtime require&lt;/td&gt;
&lt;td&gt;✅ pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packed TypeScript consumer&lt;/td&gt;
&lt;td&gt;✅ pass&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I also installed the packed package into temporary consumer projects using TypeScript v5.7 through v7.0.&lt;/p&gt;

&lt;p&gt;All of them successfully resolved and consumed the generated declarations.&lt;/p&gt;

&lt;p&gt;This isn't testing whether each TypeScript version can generate declarations through &lt;code&gt;tsdown&lt;/code&gt;. It's testing the artifact my users actually install.&lt;/p&gt;

&lt;p&gt;So if a future build change accidentally alters an extension, export path, declaration file, or runtime behavior, &lt;strong&gt;the smoke test should catch it before publishing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a library migration like this, that is much more meaningful than simply asserting "build exited successfully".&lt;/p&gt;


&lt;h2&gt;
  
  
  📏 The Output Actually Got Bigger
&lt;/h2&gt;

&lt;p&gt;I was also curious about artifact size.&lt;/p&gt;

&lt;p&gt;This produced a result I didn't expect.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;&lt;code&gt;tsup&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;tsdown&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JS + dts total&lt;/td&gt;
&lt;td&gt;116,503 B&lt;/td&gt;
&lt;td&gt;144,007 B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+23.6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESM JavaScript&lt;/td&gt;
&lt;td&gt;15,787 B&lt;/td&gt;
&lt;td&gt;29,410 B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+86.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CJS JavaScript&lt;/td&gt;
&lt;td&gt;19,318 B&lt;/td&gt;
&lt;td&gt;31,155 B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+61.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dts, one format&lt;/td&gt;
&lt;td&gt;40,699 B&lt;/td&gt;
&lt;td&gt;41,721 B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+2.5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;npm pack&lt;/code&gt; tarball&lt;/td&gt;
&lt;td&gt;37,226 B&lt;/td&gt;
&lt;td&gt;42,366 B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+13.8%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In this configuration, the &lt;code&gt;tsdown&lt;/code&gt; output retained more comments and region markers than the previous &lt;code&gt;tsup&lt;/code&gt; output.&lt;/p&gt;

&lt;p&gt;So the raw JavaScript became noticeably larger.&lt;/p&gt;

&lt;p&gt;Compression reduced the difference in the actual &lt;code&gt;npm tarball&lt;/code&gt;, but didn't remove it.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;is-kit&lt;/code&gt;, this isn't particularly concerning.&lt;/p&gt;

&lt;p&gt;It is a small zero-dependency utility library, and we're talking about a few kilobytes in the final tarball.&lt;/p&gt;

&lt;p&gt;But it was still a useful reminder&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A faster or newer bundler does not automatically mean a smaller artifact.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If package size is a strict constraint for your library, I would compare the generated files and decide on your minification strategy before migrating.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⏱️ What About Build Performance?
&lt;/h2&gt;

&lt;p&gt;I also measured wall-clock build time.&lt;/p&gt;

&lt;p&gt;The old &lt;code&gt;tsup&lt;/code&gt; build&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.50 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The new &lt;code&gt;tsdown&lt;/code&gt; build across three runs&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.22 s
1.17 s
1.18 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Looking only at those numbers, it would be very tempting to say&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;tsdown&lt;/code&gt; made the build faster! 🚀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But I don't think this benchmark supports that conclusion.&lt;/p&gt;

&lt;p&gt;I only have one measured &lt;code&gt;tsup&lt;/code&gt; run in this comparison.&lt;/p&gt;

&lt;p&gt;Also, this is a tiny single-entry library where declaration generation represents a large part of the build.&lt;/p&gt;

&lt;p&gt;The bundlers' own timing output was roughly&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tsup
JavaScript: ~22 ms
dts:        ~707 ms

tsdown
complete:   ~717–755 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The wall-clock result was better, but with only one &lt;code&gt;tsup&lt;/code&gt; sample and declaration generation dominating this tiny library, I don't think this is enough evidence to claim a meaningful performance improvement.&lt;/p&gt;

&lt;p&gt;But no, I'm not migrating because I saved around 300 ms.&lt;/p&gt;


&lt;h2&gt;
  
  
  🐱 Was the Migration Actually Small?
&lt;/h2&gt;

&lt;p&gt;For &lt;code&gt;is-kit&lt;/code&gt;, yes.&lt;/p&gt;

&lt;p&gt;There were no source-code changes.&lt;/p&gt;

&lt;p&gt;The migration was basically limited to&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependency
config
task documentation
package smoke assertions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important options were almost one-to-one.&lt;/p&gt;

&lt;p&gt;But I think it's important to explain &lt;strong&gt;why&lt;/strong&gt; it stayed small.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; has&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one entry&lt;/li&gt;
&lt;li&gt;zero runtime dependencies&lt;/li&gt;
&lt;li&gt;no bundler plugins&lt;/li&gt;
&lt;li&gt;no CSS pipeline&lt;/li&gt;
&lt;li&gt;no complicated code splitting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and, most importantly, it already had a way to test the packed package as a consumer.&lt;/p&gt;

&lt;p&gt;The migration becomes more interesting if your package relies on custom plugins, unusual entries, CSS processing, exact source maps, generated exports, strict byte-size limits, or older Node build environments.&lt;/p&gt;

&lt;p&gt;There is also a build-environment requirement worth checking.&lt;/p&gt;

&lt;p&gt;For the version tested here&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tsdown 0.23.0
Rolldown 1.2.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;the relevant Node engine requirement is&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^22.18.0 || ^24.11.0 || &amp;gt;=26.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;My CI uses&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Node 22.22.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;so that was fine.&lt;/p&gt;

&lt;p&gt;But if contributors still build your library with Node v20 or an older Node v22 version, this is something you need to solve before migrating.&lt;/p&gt;

&lt;p&gt;That doesn't necessarily mean your &lt;strong&gt;library consumers&lt;/strong&gt; must use Node v22.&lt;/p&gt;

&lt;p&gt;It's a build-tool requirement.&lt;/p&gt;

&lt;p&gt;Still, contributor and CI environments are part of the migration cost too.    😿&lt;/p&gt;


&lt;h2&gt;
  
  
  🎯 So, Was Moving from tsup to tsdown Worth It?
&lt;/h2&gt;

&lt;p&gt;For &lt;code&gt;is-kit&lt;/code&gt;, I think yes.&lt;/p&gt;

&lt;p&gt;But not because of performance.&lt;/p&gt;

&lt;p&gt;The migration preserved the package contract across ESM, CJS, declarations, exports, and the packed-package consumer tests.&lt;/p&gt;

&lt;p&gt;The configuration change was small and reviewable.&lt;/p&gt;

&lt;p&gt;My existing Node environment satisfies the new build requirement.&lt;/p&gt;

&lt;p&gt;And the build setup is now aligned with the actively evolving Rolldown ecosystem.&lt;/p&gt;

&lt;p&gt;The cost was also real&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;artifact size increased&lt;/li&gt;
&lt;li&gt;Node build requirements increased&lt;/li&gt;
&lt;li&gt;output extensions needed explicit configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I wouldn't describe this as&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everyone using tsup should migrate immediately because tsdown is faster!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not what this experiment showed.&lt;/p&gt;

&lt;p&gt;For me, the better conclusion is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your project already meets the Node requirements and you want to move your build setup toward the Rolldown ecosystem, migrating a small library from tsup to tsdown can be a very reasonable change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But verify the package you publish.&lt;/p&gt;

&lt;p&gt;Not only the source.&lt;br&gt;
Not only the config.&lt;br&gt;
And definitely not only the green build message. 😸&lt;/p&gt;

&lt;p&gt;Because the most interesting bug in this migration happened when the build said everything was fine.&lt;/p&gt;



&lt;p&gt;If you'd like to see the package from this article in a real project, &lt;code&gt;is-kit&lt;/code&gt; is open source.&lt;/p&gt;

&lt;p&gt;It's a lightweight, zero-dependency TypeScript type guard library focused on runtime validation and safe narrowing.&lt;/p&gt;

&lt;p&gt;If it looks useful for your project, I'd be happy if you gave it a try and a ⭐ on GitHub is always appreciated.&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.  A lightweight, zero-dependency toolkit for building reusable TypeScript type guards that compose, refine, and preserve 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;&lt;code&gt;is-kit&lt;/code&gt; is not just a collection of &lt;code&gt;isX&lt;/code&gt; helpers. Its main focus is composing
small runtime checks into reusable guards while preserving useful TypeScript
narrowing.&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;, refine properties on values you already know about, and keep
&lt;strong&gt;TypeScript narrowing&lt;/strong&gt; natural inside regular control flow. Use it at runtime
boundaries when needed, without requiring a schema-first workflow.&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;Use &lt;code&gt;refineKey&lt;/code&gt; to narrow a &lt;strong&gt;child property&lt;/strong&gt; while preserving its parent type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate object&lt;/strong&gt; shapes and collections when that is useful&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;a href="https://is-kit.dev/" 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%2Fis-kit%2FHEAD%2Fdocs%2Fpublic%2Fdocument_button.png" alt="Documentation Site" width="240"&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;Best…&lt;/p&gt;
&lt;/blockquote&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;Issues and PRs are welcome too. 😸&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>TypeScript Compiler API: Preserving Child Node Narrowing in Reusable Type Guards 🔧</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 30 Sep 2026 14:34:27 +0000</pubDate>
      <link>https://dev.to/nyaomaru/typescript-compiler-api-preserving-child-node-narrowing-in-reusable-type-guards-4pgh</link>
      <guid>https://dev.to/nyaomaru/typescript-compiler-api-preserving-child-node-narrowing-in-reusable-type-guards-4pgh</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 exploring new possibilities with &lt;code&gt;Jev&lt;/code&gt; 😸 (I'm also curious about "Decisions API" from OpenAI)&lt;/p&gt;

&lt;p&gt;Recently, I was looking into the TypeScript Compiler API and asking a pretty specific question&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can reusable type guards preserve not only the AST node type, but also a narrowed child property?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first, I thought this might be a Compiler API-specific problem.&lt;/p&gt;

&lt;p&gt;Then I reproduced the same pattern with plain TypeScript objects.&lt;/p&gt;

&lt;p&gt;That changed the way I looked at it.&lt;/p&gt;

&lt;p&gt;The interesting problem wasn't really the AST.&lt;/p&gt;

&lt;p&gt;It was &lt;strong&gt;property refinement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's look at it! 👀&lt;/p&gt;

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




&lt;h2&gt;
  
  
  🌲 A Very Common Compiler API Pattern
&lt;/h2&gt;

&lt;p&gt;Suppose we have a broad &lt;code&gt;ts.Node&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&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 to know two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this a &lt;code&gt;CallExpression&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Is its &lt;code&gt;expression&lt;/code&gt; an &lt;code&gt;Identifier&lt;/code&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inline, this is easy.&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// node: ts.CallExpression&lt;/span&gt;
  &lt;span class="c1"&gt;// node.expression: ts.Identifier&lt;/span&gt;
  &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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;TypeScript understands the control flow perfectly.&lt;/p&gt;

&lt;p&gt;Nothing special is needed.&lt;/p&gt;

&lt;p&gt;And honestly, if this check appears only once, I would probably leave it exactly like this.&lt;/p&gt;


&lt;h2&gt;
  
  
  🤔 What If We Want to Reuse That Shape?
&lt;/h2&gt;

&lt;p&gt;Now suppose the same AST shape appears in several places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a visitor&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;find&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;another transformation&lt;/li&gt;
&lt;li&gt;another lint rule&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then giving the check a name starts to make sense.&lt;/p&gt;

&lt;p&gt;Since TypeScript v5.5, simple functions can often infer a type predicate automatically.&lt;/p&gt;

&lt;p&gt;But this compound parent-plus-child check is different.&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;isCallWithIdentifierExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// inferred:&lt;/span&gt;
&lt;span class="c1"&gt;// (node: ts.Node) =&amp;gt; boolean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So if we want the extracted predicate to preserve both facts, we have to describe &lt;strong&gt;the refined type explicitly&lt;/strong&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;isCallWithIdentifierExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CallExpression&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;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Identifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&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. Then we can 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="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&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;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodes&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;isCallWithIdentifierExpression&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// calls:&lt;/span&gt;
&lt;span class="c1"&gt;// Array&amp;lt;&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;nbsp; ts.CallExpression &amp;amp; {&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;nbsp; &amp;nbsp; expression: ts.Identifier;&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;nbsp; }&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;So what's the problem?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There isn't really a runtime problem.&lt;/p&gt;

&lt;p&gt;The annoying part is that we had to manually describe 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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CallExpression&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="na"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Identifier&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 we already performed those exact runtime checks.&lt;/p&gt;

&lt;p&gt;It would be nice if we could compose the checks and let the type follow them. 😸&lt;/p&gt;


&lt;h2&gt;
  
  
  🧩 Refining the Parent and Child Together
&lt;/h2&gt;

&lt;p&gt;This is where I ended up using &lt;code&gt;refineKey&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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;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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;and&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refineKey&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;isCallWithIdentifierExpression&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIdentifier&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's it.&lt;/p&gt;

&lt;p&gt;Now&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&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;isCallWithIdentifierExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// node:&lt;/span&gt;
  &lt;span class="c1"&gt;// ts.CallExpression &amp;amp; {&lt;/span&gt;
  &lt;span class="c1"&gt;// &amp;nbsp; expression: ts.Identifier;&lt;/span&gt;
  &lt;span class="c1"&gt;// }&lt;/span&gt;
  &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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 interesting part is the relationship between the two checks.&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;narrows the parent.&lt;/p&gt;

&lt;p&gt;Then&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;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIdentifier&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;within the composed narrowing chain, checks one property on the parent already narrowed by &lt;code&gt;ts.isCallExpression&lt;/code&gt; and preserves the checked child type.&lt;/p&gt;

&lt;p&gt;So the idea is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check the child once at runtime, then carry that same fact back to the parent type.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  😸 This Turned Out Not to Be an AST Problem
&lt;/h2&gt;

&lt;p&gt;This was the part that surprised me during the research.&lt;/p&gt;

&lt;p&gt;I originally thought I was investigating a TypeScript Compiler API gap.&lt;/p&gt;

&lt;p&gt;But the same shape appears with ordinary objects too.&lt;/p&gt;

&lt;p&gt;Conceptually, the pattern is just&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;Parent&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;span class="nx"&gt;Parent&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RefinedChild&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Compiler API is simply a really good stress test for it because AST code contains this pattern everywhere.&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 plaintext"&gt;&lt;code&gt;CallExpression
&amp;nbsp; → expression
&amp;nbsp; → Identifier
&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 plaintext"&gt;&lt;code&gt;VariableDeclaration
&amp;nbsp; → initializer?
&amp;nbsp; → CallExpression
&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 plaintext"&gt;&lt;code&gt;CallExpression
&amp;nbsp; → arguments[0]
&amp;nbsp; → StringLiteral
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So I don't think of &lt;code&gt;refineKey&lt;/code&gt; as a Compiler API helper.&lt;/p&gt;

&lt;p&gt;The Compiler API is just an advanced example of a more generic composition problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔗 Compiler API Guards Already Compose Well
&lt;/h2&gt;

&lt;p&gt;Another thing I wanted to avoid was wrapping TypeScript's existing predicates unnecessarily.&lt;/p&gt;

&lt;p&gt;The Compiler API already provides excellent 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="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isStringLiteral&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIdentifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isClassDeclaration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We should reuse them.&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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;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;const&lt;/span&gt; &lt;span class="nx"&gt;isStringLike&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isStringLiteral&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;　&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isNoSubstitutionTemplateLiteral&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;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&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;strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodes&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;isStringLike&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// strings:&lt;/span&gt;
&lt;span class="c1"&gt;// (&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;nbsp; | ts.StringLiteral&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;nbsp; | ts.NoSubstitutionTemplateLiteral&lt;/span&gt;
&lt;span class="c1"&gt;// )[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is no reason for &lt;code&gt;is-kit&lt;/code&gt; to create its own&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;isTsStringLiteral&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;isTsIdentifier&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;isTsCallExpression&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That would just duplicate the Compiler API.&lt;/p&gt;

&lt;p&gt;The useful part is composition.&lt;/p&gt;


&lt;h2&gt;
  
  
  ♻️ Reuse the Same Guard in &lt;code&gt;find&lt;/code&gt; and Visitors
&lt;/h2&gt;

&lt;p&gt;This becomes more useful when a refined shape appears in several contexts.&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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;and&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refineKey&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;isIdentifierNamedJsxAttribute&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isJsxAttribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIdentifier&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 use it with &lt;code&gt;find&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="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JsxAttributeLike&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;attribute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isIdentifierNamedJsxAttribute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// attribute:&lt;/span&gt;
&lt;span class="c1"&gt;// (&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;nbsp; ts.JsxAttribute &amp;amp; {&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;nbsp; &amp;nbsp; name: ts.Identifier;&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;nbsp; }&lt;/span&gt;
&lt;span class="c1"&gt;// ) | undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The same guard works in a visitor&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;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isIdentifierNamedJsxAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// node:&lt;/span&gt;
    &lt;span class="c1"&gt;// ts.JsxAttribute &amp;amp; {&lt;/span&gt;
    &lt;span class="c1"&gt;// &amp;nbsp; name: ts.Identifier;&lt;/span&gt;
    &lt;span class="c1"&gt;// }&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEachChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;visit&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's the point where extracting the guard starts earning its keep.&lt;/p&gt;

&lt;p&gt;The runtime rule and the TypeScript narrowing travel together.&lt;/p&gt;


&lt;h2&gt;
  
  
  🫥 Optional Children Are a Different Contract
&lt;/h2&gt;

&lt;p&gt;AST nodes contain lots of optional properties.&lt;/p&gt;

&lt;p&gt;For example, a &lt;code&gt;VariableDeclaration&lt;/code&gt; may or may not have an initializer.&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;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializer&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 slightly different from refining a required property.&lt;/p&gt;

&lt;p&gt;We don't just want&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Refine &lt;code&gt;initializer&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We want&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Require &lt;code&gt;initializer&lt;/code&gt; to exist, then refine it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For that case, &lt;code&gt;is-kit&lt;/code&gt; has &lt;code&gt;refineDefinedKey&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;refineDefinedKey&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;hasCallInitializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;refineDefinedKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;initializer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCallExpression&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 typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VariableDeclaration&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;hasCallInitializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// declaration.initializer: ts.CallExpression&lt;/span&gt;
  &lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&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;Inside the branch, &lt;code&gt;initializer&lt;/code&gt; is both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;present&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;ts.CallExpression&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A missing initializer returns &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An explicitly &lt;code&gt;undefined&lt;/code&gt; initializer also returns &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I like keeping this separate from &lt;code&gt;refineKey&lt;/code&gt; because absence is runtime behavior, not just a TypeScript annotation.&lt;/p&gt;


&lt;h2&gt;
  
  
  📦 Arrays Have the Same Problem
&lt;/h2&gt;

&lt;p&gt;AST arrays introduce another small issue.&lt;/p&gt;

&lt;p&gt;Suppose we want a call whose first argument is a string literal.&lt;/p&gt;

&lt;p&gt;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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;looks simple, but at runtime the array can be empty.&lt;/p&gt;

&lt;p&gt;So we want to prove two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;index &lt;code&gt;0&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;the value is a &lt;code&gt;StringLiteral&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can compose that too&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;and&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refineIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refineKey&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;isCallWithStringFirstArgument&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arguments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;refineIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isStringLiteral&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&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&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;isCallWithStringFirstArgument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// node: ts.CallExpression&lt;/span&gt;
  &lt;span class="c1"&gt;// node.arguments[0]: ts.StringLiteral&lt;/span&gt;
  &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&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;Now index &lt;code&gt;0&lt;/code&gt; is known to exist and to be a &lt;code&gt;ts.StringLiteral&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Again, this isn't really an AST-specific idea.&lt;/p&gt;

&lt;p&gt;It's just&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Refine one checked location and preserve that fact.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🪆 Nested Checks Can Stay Composable
&lt;/h2&gt;

&lt;p&gt;These refinements can also be nested.&lt;/p&gt;

&lt;p&gt;Suppose we want a function-like declaration whose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;body&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;body&lt;/code&gt; is a block&lt;/li&gt;
&lt;li&gt;first statement exists&lt;/li&gt;
&lt;li&gt;first statement is a return statement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can build the pieces separately.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;and&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refineDefinedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refineIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refineKey&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;isBlockStartingWithReturn&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statements&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;refineIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isReturnStatement&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;hasBodyStartingWithReturn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;refineDefinedKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isBlockStartingWithReturn&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&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functionLike&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FunctionLikeDeclaration&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;hasBodyStartingWithReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;functionLike&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// functionLike.body: ts.Block&lt;/span&gt;
  &lt;span class="c1"&gt;// functionLike.body.statements[0]: ts.ReturnStatement&lt;/span&gt;
  &lt;span class="nx"&gt;functionLike&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;statements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;expression&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;Each step proves one thing.&lt;/p&gt;

&lt;p&gt;There is no path string like&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;body.statements[0]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and no special AST DSL.&lt;/p&gt;

&lt;p&gt;It's just small guards composed together.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔒 Why Only One Concrete Key or Index?
&lt;/h2&gt;

&lt;p&gt;There is an important limitation here.&lt;/p&gt;

&lt;p&gt;A successful lookup proves one concrete location.&lt;/p&gt;

&lt;p&gt;If we check&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;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&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;we proved something about&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;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We did &lt;strong&gt;not&lt;/strong&gt; prove that every property from some wider key domain passed the same test.&lt;/p&gt;

&lt;p&gt;That's why the refinement helpers intentionally work with one concrete key or index.&lt;/p&gt;

&lt;p&gt;Broad key unions and similar multi-location claims would make the resulting type much easier to overstate.&lt;/p&gt;

&lt;p&gt;I would rather make the API slightly less magical than let one runtime lookup claim more than it actually checked.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 What About TypeScript 7?
&lt;/h2&gt;

&lt;p&gt;This research became especially interesting because TypeScript v7 changed the Compiler API landscape.&lt;/p&gt;

&lt;p&gt;The examples in this section were verified against TypeScript v7.0.2.&lt;/p&gt;

&lt;p&gt;As of TypeScript v7.0.2, AST types and predicates are exposed through&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;typescript/unstable/ast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So the same composition style can be used there&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ast&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;typescript/unstable/ast&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;and&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refineKey&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;isCallWithIdentifierExpression&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;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIdentifier&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;One thing I specifically investigated was whether TypeScript v7 made these &lt;code&gt;isX&lt;/code&gt; checks unnecessary through &lt;code&gt;kind&lt;/code&gt; narrowing.&lt;/p&gt;

&lt;p&gt;For a genuine discriminated union, TypeScript can absolutely narrow from a literal discriminant.&lt;/p&gt;

&lt;p&gt;But the broad AST &lt;code&gt;Node&lt;/code&gt; currently exposed by the TypeScript v7 AST surface is not that kind of closed discriminated union.&lt;/p&gt;

&lt;p&gt;So with a broad AST node, the &lt;code&gt;isX&lt;/code&gt; predicates still matter.&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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ast&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;typescript/unstable/ast&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;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SyntaxKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CallExpression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// broad ast.Node does not automatically&lt;/span&gt;
  &lt;span class="c1"&gt;// expose CallExpression properties here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;A custom AST type modeled as a discriminated union can behave differently.&lt;/p&gt;

&lt;p&gt;That doesn't mean TypeScript v7's broad &lt;code&gt;Node&lt;/code&gt; currently behaves the same way.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Not Make &lt;code&gt;Node&lt;/code&gt; a Closed Union?
&lt;/h3&gt;

&lt;p&gt;After I posted about this, &lt;a href="https://bsky.app/profile/jakebailey.dev/post/3mwpa3wwmjs2d" rel="noopener noreferrer"&gt;Jake Bailey&lt;/a&gt; gave a wonderfully concise answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;because it's slow 😞&lt;/p&gt;
&lt;/blockquote&gt;


&lt;blockquote class="bluesky-embed"&gt;
&lt;p&gt;because it's slow 😞&lt;/p&gt;— &lt;a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite?ref_src=embed" rel="noopener noreferrer"&gt;Jake Bailey (@jakebailey.dev)&lt;/a&gt; &lt;a href="https://bsky.app/profile/did:plc:4eukmtg5kmyjmp6qw3xkpite/post/3mwpa3wwmjs2d?ref_src=embed" rel="noopener noreferrer"&gt;2026-09-30T01:49:50.060Z&lt;/a&gt;
&lt;/blockquote&gt;


&lt;p&gt;That makes the trade-off much easier to understand.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;Node&lt;/code&gt; were one closed discriminated union containing every AST node type, &lt;code&gt;kind&lt;/code&gt; could potentially give us stronger narrowing and exhaustive checks.&lt;/p&gt;

&lt;p&gt;For example, with a closed union, we can use the familiar &lt;code&gt;never&lt;/code&gt; 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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// handle every known kind...&lt;/span&gt;

  &lt;span class="nl"&gt;default&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="na"&gt;exhaustive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&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;When a new variant is added, that &lt;code&gt;never&lt;/code&gt; check can fail at compile time and tell us that our handling is no longer exhaustive.&lt;/p&gt;

&lt;p&gt;But that stronger type-level model is not free.&lt;/p&gt;

&lt;p&gt;The cost is type-checking performance, a very large closed union gives the checker more work to do.&lt;/p&gt;

&lt;p&gt;So the broad shape of &lt;code&gt;ast.Node&lt;/code&gt; is not simply a missing narrowing feature.&lt;/p&gt;

&lt;p&gt;There is a real trade-off here&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;stronger compile-time exhaustiveness vs. type-checking performance&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that also helps explain why explicit predicates like &lt;code&gt;ast.isCallExpression()&lt;/code&gt; still have an important role.&lt;/p&gt;

&lt;p&gt;There is one more TypeScript 7-specific detail worth keeping in mind.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;unstable&lt;/code&gt; part of&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;typescript/unstable/ast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;is also important.&lt;/p&gt;

&lt;p&gt;I wouldn't build documentation promises around a still-evolving API surface.&lt;/p&gt;

&lt;p&gt;The composition pattern is generic.&lt;/p&gt;

&lt;p&gt;The exact TypeScript v7 integration can evolve with TypeScript itself.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✋ You Probably Don't Need This for Every AST Check
&lt;/h2&gt;

&lt;p&gt;This is also important.&lt;/p&gt;

&lt;p&gt;If I have one local condition&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isReturnStatement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// node: ts.ReturnStatement&lt;/span&gt;
  &lt;span class="c1"&gt;// node.expression: ts.Expression&lt;/span&gt;
  &lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expression&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 would leave it inline.&lt;/p&gt;

&lt;p&gt;Really.&lt;/p&gt;

&lt;p&gt;Turning it into&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;isReturnWithExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;just because we &lt;em&gt;can&lt;/em&gt; doesn't automatically make the code better.&lt;/p&gt;

&lt;p&gt;I think the useful split is&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Prefer &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One local branch &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;ts.isX&lt;/code&gt; checks &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeated AST shape &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;td&gt;Named reusable guard &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project already uses &lt;code&gt;is-kit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;refineKey&lt;/code&gt;, &lt;code&gt;refineDefinedKey&lt;/code&gt;, &lt;code&gt;refineIndex&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The goal isn't&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace every &lt;code&gt;ts.isX&lt;/code&gt; condition with &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When a runtime fact becomes reusable vocabulary, keep the narrowing reusable too.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🚫 What This Does Not Try to Do
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;is-kit&lt;/code&gt; doesn't try to become a Compiler API framework.&lt;/p&gt;

&lt;p&gt;It does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wrap individual Compiler API functions&lt;/li&gt;
&lt;li&gt;validate complete AST node shapes&lt;/li&gt;
&lt;li&gt;control AST traversal&lt;/li&gt;
&lt;li&gt;detect AST cycles&lt;/li&gt;
&lt;li&gt;add a TypeScript runtime dependency&lt;/li&gt;
&lt;li&gt;require TypeScript as a peer dependency&lt;/li&gt;
&lt;li&gt;replace clear one-off inline checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Compiler API is simply a demanding real-world example of generic property refinement.&lt;/p&gt;

&lt;p&gt;That's the boundary I want to keep.&lt;/p&gt;


&lt;h2&gt;
  
  
  🎯 The Important Part
&lt;/h2&gt;

&lt;p&gt;I started this research thinking&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Maybe the TypeScript Compiler API needs some special handling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What I found was more general.&lt;/p&gt;

&lt;p&gt;The recurring problem was&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;narrow parent
&amp;nbsp; &amp;nbsp; ↓
check child
&amp;nbsp; &amp;nbsp; ↓
preserve both facts
&amp;nbsp; &amp;nbsp; ↓
reuse the predicate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's useful for AST nodes, but it isn't really about AST nodes.&lt;/p&gt;

&lt;p&gt;So my current mental model is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use native type guards for the actual runtime knowledge&lt;/li&gt;
&lt;li&gt;keep one-off conditions inline&lt;/li&gt;
&lt;li&gt;compose a named guard when the same checked shape becomes reusable&lt;/li&gt;
&lt;li&gt;preserve child refinement on the parent instead of rewriting intersection types by hand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the Compiler API, that can look like&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCallWithIdentifierExpression&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;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCallExpression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;refineKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIdentifier&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;Small runtime checks.&lt;br&gt;
Small reusable pieces.&lt;br&gt;
And TypeScript keeps the facts we actually checked. 😸&lt;/p&gt;

&lt;p&gt;I also wrote a more detailed guide with examples for required children, optional children, array indices, nested AST shapes, and TypeScript 7&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced Property Refinement with the TypeScript Compiler API&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://is-kit.dev/guides/typescript-compiler-api" 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/https%3A%2F%2Fis-kit.dev%2Fiskit_guide3.gif" height="336" 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://is-kit.dev/guides/typescript-compiler-api" rel="noopener noreferrer" class="c-link"&gt;
            Advanced Property Refinement with the TypeScript Compiler API | is-kit
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Use TypeScript Compiler API guards as an advanced example of reusable property refinement across broad AST node types.
          &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%2Fis-kit.dev%2Fiskit_favicon.png" width="192" height="192"&gt;
          is-kit.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;And if you want to explore &lt;code&gt;is-kit&lt;/code&gt; itself&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.dev/" rel="nofollow noopener noreferrer"&gt;📚 Documentation Site&lt;/a&gt; ·
&lt;a href="https://is-kit.dev/guides" rel="nofollow noopener noreferrer"&gt;🧭 Practical Guides&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;…&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;If it looks useful, a ⭐ on GitHub is always very welcome!&lt;/p&gt;

&lt;p&gt;Thanks for reading! 🙌&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Your Type Guard Can Silently Drift from Your TypeScript Type 🔧</title>
      <dc:creator>nyaomaru</dc:creator>
      <pubDate>Wed, 23 Sep 2026 14:23:57 +0000</pubDate>
      <link>https://dev.to/nyaomaru/your-type-guard-can-silently-drift-from-your-typescript-type-o57</link>
      <guid>https://dev.to/nyaomaru/your-type-guard-can-silently-drift-from-your-typescript-type-o57</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 just back from a short vacation on Texel, a small island in the Netherlands. 😸🏝️&lt;/p&gt;

&lt;p&gt;Today, let's talk about a type guard that looks completely safe.&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="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="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="c1"&gt;// runtime checks...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Looks good, right?&lt;/p&gt;

&lt;p&gt;TypeScript knows that when &lt;code&gt;isUser(value)&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, the value is a &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But there's a small problem&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TypeScript trusts that promise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It doesn't prove that your runtime checks actually validate every field in &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And that's where type guards can slowly drift away from the types they claim to protect.&lt;/p&gt;

&lt;p&gt;Let's take a look! 👀&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%2Fhrkfnbo16jlm9snlvwzu.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%2Fhrkfnbo16jlm9snlvwzu.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🕳️ A Type Guard Can Become Outdated Without an Error
&lt;/h2&gt;

&lt;p&gt;Imagine we start with this 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;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;And a hand-written type 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;isUser&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="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="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;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;||&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="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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So far, everything matches.&lt;/p&gt;

&lt;p&gt;Later, we update &lt;code&gt;User&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;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="nl"&gt;role&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="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&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;But we forget to update the 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;isUser&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="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="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;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;||&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="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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is no &lt;code&gt;role&lt;/code&gt; check.&lt;/p&gt;

&lt;p&gt;But this still compiles. 😿&lt;/p&gt;


&lt;h2&gt;
  
  
  🧠 Why Doesn't TypeScript Catch This?
&lt;/h2&gt;

&lt;p&gt;Because 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="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="nx"&gt;User&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;is a user-defined type predicate.&lt;/p&gt;

&lt;p&gt;You are telling TypeScript&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Trust me. If this function returns &lt;code&gt;true&lt;/code&gt;, the value is a &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TypeScript can check whether the declared predicate type itself makes sense.&lt;/p&gt;

&lt;p&gt;But it cannot generally prove that arbitrary runtime logic actually validates every part of that type.&lt;/p&gt;

&lt;p&gt;So this is possible&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="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="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;Terrible guard.&lt;/p&gt;

&lt;p&gt;Perfectly valid TypeScript. 😹&lt;/p&gt;

&lt;p&gt;The return type is a contract written by us, not a proof generated from the function body.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔄 This Becomes a Maintenance Problem
&lt;/h2&gt;

&lt;p&gt;The annoying part isn't writing the guard once.&lt;/p&gt;

&lt;p&gt;It's keeping these two things synchronized over time&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeScript type
      ↕
Runtime validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Types change.&lt;/p&gt;

&lt;p&gt;Properties get&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;added&lt;/li&gt;
&lt;li&gt;removed&lt;/li&gt;
&lt;li&gt;renamed&lt;/li&gt;
&lt;li&gt;made optional&lt;/li&gt;
&lt;li&gt;changed to another type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And every time that happens, we need to remember that some runtime guard somewhere may also need an update.&lt;/p&gt;

&lt;p&gt;If we forget, the compiler may not tell us.&lt;/p&gt;

&lt;p&gt;That's the kind of bug I really don't want to rely on memory to prevent.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✅ What If the Type Could Be the Contract?
&lt;/h2&gt;

&lt;p&gt;This is one of the reasons I added &lt;code&gt;typedStruct&lt;/code&gt; to &lt;code&gt;is-kit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Suppose the application type already exists:&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="nl"&gt;age&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can build the guard against that existing 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="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;isString&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;typedStruct&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="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="na"&gt;age&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;isNumber&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;Now the field map has a type-level relationship with &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At runtime, it still performs ordinary object validation.&lt;/p&gt;

&lt;p&gt;But at compile time, TypeScript can check whether the guards we declared match the object type they're supposed to follow.&lt;/p&gt;


&lt;h2&gt;
  
  
  💥 Now Drift Becomes Visible
&lt;/h2&gt;

&lt;p&gt;Let's add a field again&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="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="nl"&gt;role&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="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;age&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But forget to update the 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="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="na"&gt;age&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;isNumber&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="c1"&gt;// TypeScript error:&lt;/span&gt;
  &lt;span class="c1"&gt;// role is missing&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Nice.&lt;/p&gt;

&lt;p&gt;The runtime bug became a compile-time problem.&lt;/p&gt;

&lt;p&gt;The same thing happens if the guard uses an incompatible field 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="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;isString&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;optionalKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;typedStruct&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="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;isNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// TypeScript error:&lt;/span&gt;
  &lt;span class="c1"&gt;// User["name"] is string&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;member&lt;/span&gt;&lt;span class="dl"&gt;"&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="nf"&gt;optionalKey&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the part I care about most.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;typedStruct&lt;/code&gt; doesn't eliminate maintenance.&lt;/p&gt;

&lt;p&gt;It makes forgotten maintenance visible.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧩 Optional and Nullable Are Different
&lt;/h2&gt;

&lt;p&gt;Another place where object guards can get confusing is optional properties.&lt;/p&gt;

&lt;p&gt;Consider:&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;nickname&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There are two separate ideas here&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nickname may be absent
&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;nickname may exist with the value null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those are different runtime contracts.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;typedStruct&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;isString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nullable&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;typedStruct&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="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;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="nf"&gt;nullable&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;Now&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;isUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nf"&gt;isUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&lt;/span&gt;&lt;span class="dl"&gt;"&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nf"&gt;isUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Neko&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nf"&gt;isUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&lt;/span&gt;&lt;span class="dl"&gt;"&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="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I like keeping these two decisions explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;optionalKey(...)&lt;/code&gt; → the property may be absent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nullable(...)&lt;/code&gt; → the value may be &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They look similar at first, but they describe different things.&lt;/p&gt;


&lt;h2&gt;
  
  
  🌳 Nested Types Don't Need to Be Duplicated Either
&lt;/h2&gt;

&lt;p&gt;Now imagine a larger 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;Account&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="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="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&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="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;bio&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&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;We could manually copy the profile shape into another type.&lt;/p&gt;

&lt;p&gt;But that creates another thing that can drift.&lt;/p&gt;

&lt;p&gt;Instead, we can reference the type we already have&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;arrayOf&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;nullable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;typedStruct&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;isProfile&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="nb"&gt;NonNullable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&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="na"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nullable&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAccount&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;Account&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;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isProfile&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&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;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 the model I like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reuse the existing type at compile time. Compose small guards at runtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The application type remains the source we want the guard to follow.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔒 What About Extra Runtime Properties?
&lt;/h2&gt;

&lt;p&gt;There is another distinction worth making.&lt;/p&gt;

&lt;p&gt;These are two different questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does my &lt;strong&gt;guard definition&lt;/strong&gt; match the TypeScript type?&lt;/li&gt;
&lt;li&gt;Should a &lt;strong&gt;runtime object&lt;/strong&gt; be allowed to contain additional properties?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By default, an object can still have additional keys.&lt;/p&gt;

&lt;p&gt;If you want the runtime object shape to be closed as well, you can enable exact mode:&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;typedStruct&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;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;isExactUser&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;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then:&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;isExactUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&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;Ada&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="nf"&gt;isExactUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-1&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;Ada&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Whether extra properties should be rejected is a runtime policy decision.&lt;/p&gt;

&lt;p&gt;It shouldn't be confused with keeping the guard definition synchronized with the TypeScript type.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚖️ Which Should Be the Source of Truth?
&lt;/h2&gt;

&lt;p&gt;I don't think there is one correct validation style for every project.&lt;/p&gt;

&lt;p&gt;The important question is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What already owns the shape of this data?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Manual predicate
&lt;/h3&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;isSomething&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="nx"&gt;Something&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// custom logic&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Great when the validation is unusual or not primarily structural.&lt;/p&gt;
&lt;h3&gt;
  
  
  Guard-first
&lt;/h3&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;Useful when the guard itself should define the resulting type.&lt;/p&gt;
&lt;h3&gt;
  
  
  Type-first
&lt;/h3&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;Useful when &lt;code&gt;User&lt;/code&gt; already exists and the runtime guard needs to stay aligned with it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Schema-first
&lt;/h3&gt;

&lt;p&gt;A schema library or code generation may be the better source of truth when you need things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structured validation errors&lt;/li&gt;
&lt;li&gt;coercion&lt;/li&gt;
&lt;li&gt;transforms&lt;/li&gt;
&lt;li&gt;defaults&lt;/li&gt;
&lt;li&gt;generated artifacts&lt;/li&gt;
&lt;/ul&gt;

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

&lt;blockquote&gt;
&lt;p&gt;I don't think every boolean validation check needs to become a schema. 😸&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🚫 What &lt;code&gt;typedStruct&lt;/code&gt; Does Not Do
&lt;/h2&gt;

&lt;p&gt;There are some important boundaries.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;typedStruct&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; generate runtime validation from a TypeScript type.&lt;/p&gt;

&lt;p&gt;Types are erased at runtime, so you still need to declare the guards you want to execute.&lt;/p&gt;

&lt;p&gt;It also doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prove that every custom predicate is honest&lt;/li&gt;
&lt;li&gt;coerce values&lt;/li&gt;
&lt;li&gt;return rich structured validation errors&lt;/li&gt;
&lt;li&gt;replace schema-first workflows&lt;/li&gt;
&lt;li&gt;validate numeric or symbol properties as part of its string-keyed object contract&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's intentionally smaller than that.&lt;/p&gt;

&lt;p&gt;The goal is simply to create a typed bridge between&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the object type you already have&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the runtime guards you choose to run&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🎯 The Important Part
&lt;/h2&gt;

&lt;p&gt;The main point isn't really &lt;code&gt;typedStruct&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's this&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A type predicate is a promise, not a proof.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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="nx"&gt;value&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="nx"&gt;User&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;doesn't mean TypeScript inspected your implementation and proved that every &lt;code&gt;User&lt;/code&gt; field was validated.&lt;/p&gt;

&lt;p&gt;We made that promise.&lt;/p&gt;

&lt;p&gt;So when a TypeScript type is the source of truth, I think it's useful to make the runtime guard structurally depend on that type instead of relying on us to remember every future change.&lt;/p&gt;

&lt;p&gt;That's what I wanted &lt;code&gt;typedStruct&lt;/code&gt; to help with. 😸&lt;/p&gt;

&lt;p&gt;If your guard defines the type, use a guard-first approach.&lt;/p&gt;

&lt;p&gt;If an existing TypeScript type should define the contract, connect the guard to that type.&lt;/p&gt;

&lt;p&gt;And if you need rich parsing, transforms, coercion, or detailed errors, that's where a schema starts to earn its weight.&lt;/p&gt;

&lt;p&gt;I wrote a more complete guide about this on the is-kit documentation site:&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://is-kit.dev/guides/keep-type-guards-in-sync" 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/https%3A%2F%2Fis-kit.dev%2Fiskit_guide2.gif" height="336" 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://is-kit.dev/guides/keep-type-guards-in-sync" rel="noopener noreferrer" class="c-link"&gt;
            Keep Type Guards in Sync with TypeScript Types | is-kit
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Keep hand-written runtime guards aligned with existing string-keyed TypeScript object types using typedStruct and field-level checks.
          &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%2Fis-kit.dev%2Fiskit_favicon.png" width="192" height="192"&gt;
          is-kit.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;If you like small reusable TypeScript type guards, &lt;code&gt;is-kit&lt;/code&gt; is open source too!&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.dev/" rel="nofollow noopener noreferrer"&gt;📚 Documentation Site&lt;/a&gt; ·
&lt;a href="https://is-kit.dev/guides" rel="nofollow noopener noreferrer"&gt;🧭 Practical Guides&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;…&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;Thanks for reading! 🙌&lt;/p&gt;

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