<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Colonel Oleksii]]></title><description><![CDATA[Advanced TypeScript blog - frontend, react and more]]></description><link>https://colonel.shnyra.com/</link><image><url>https://colonel.shnyra.com/favicon.png</url><title>Colonel Oleksii</title><link>https://colonel.shnyra.com/</link></image><generator>Ghost 4.35</generator><lastBuildDate>Sun, 25 May 2025 15:40:07 GMT</lastBuildDate><atom:link href="https://colonel.shnyra.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[2025 Safari is low 60 FPS browser still - so what?]]></title><description><![CDATA[<p>While AI industry is booming around the world of May 2025, Apple still cannot figure out how to make their products high-performant as competitors do. Safari performance issues, unfortunately, gives me a vibe of Internet Explorer from old days - Microsoft wasted years to support slow and ugly IE.</p><p>IE</p>]]></description><link>https://colonel.shnyra.com/2025-safari-is-low-fps-browser-still/</link><guid isPermaLink="false">6829e0290cbe8700011975f6</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Sun, 18 May 2025 16:00:20 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2025/05/safari-is-low-fps-browser.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2025/05/safari-is-low-fps-browser.png" alt="2025 Safari is low 60 FPS browser still - so what?"><p>While AI industry is booming around the world of May 2025, Apple still cannot figure out how to make their products high-performant as competitors do. Safari performance issues, unfortunately, gives me a vibe of Internet Explorer from old days - Microsoft wasted years to support slow and ugly IE.</p><p>IE was the biggest limiting factor to write cross-browser complex UI apps. Now we have this situation caused by Apple Safari &#x1F926;&#x1F3FB;&#x200D;&#x2642;&#xFE0F;. Maybe Apple will use AI to finally fix their Safari source code, so it won&apos;t end up abandoned as IE - will see.</p><h1 id="live-check-fps-of-your-device">Live check FPS of your device</h1><blockquote>Live demo to test FPS of your current browser. Check yourself how Safari is a slow browser, and see how other browsers like Chrome on macOS do render things at 120 FPS.</blockquote><p>Let&apos;s measure two things:</p><ul><li>RequestAnimationFrame (RAF)</li><li>and scroll speed (measured during a scroll, so scroll slowly to see changes)</li></ul><!--kg-card-begin: html--><div>Your RAF speed: <strong id="fps-for-raf">-</strong> FPS</div>
<div>Your scroll speed: <strong id="fps-for-scroll">-</strong> FPS</div>

<script>
const createFPSMeter = () => {
    const MAX_DELTAS = 30;
    let prevTime = Date.now();
    const deltas = [];
    const getFPS = () => (1e3 / ((deltas.reduce((a, v) => a + v, 0)) / deltas.length));
    const tick = () => {
        const now = Date.now();
        const delta = now - prevTime;
        prevTime = now;
        deltas.push(delta);
        if (deltas.length > MAX_DELTAS) {
            deltas.shift();
        }
    };
    return { tick, getFPS };
};

const elementForRaf = document.getElementById('fps-for-raf');
const elementForScroll = document.getElementById('fps-for-scroll');

const rafMeter = createFPSMeter();
const rafLoop = () => {
    requestAnimationFrame(rafLoop);
    rafMeter.tick();
    elementForRaf.textContent = rafMeter.getFPS().toFixed(0);
};
requestAnimationFrame(rafLoop);

const scrollMeter = createFPSMeter();
const onScroll = () => {
    scrollMeter.tick();
    elementForScroll.textContent = scrollMeter.getFPS().toFixed(0);
};
document.addEventListener('scroll', onScroll);
</script>
<!--kg-card-end: html--><blockquote>See HTML source code of this block to see details about how it is measured</blockquote><h1 id="safari-browser-is-limited-to-only-60-fps-%F0%9F%A6%A5">Safari browser is limited to only 60 FPS &#x1F9A5;</h1><p>Wether it is modern iOS Pro, Pro Max or even Safari on MacBook Pro - you cannot get JavaScript based computation more frequently than 60 FPS, meaning:</p><ul><li>WebGL FPS limited to 60 FPS</li><li>JS animations limited to 60 FPS (for example scroll based like parallax)</li><li>iOS and iPad devices have this issue for all installed browsers, not only Safari</li><li>JS animations look mis-synchronized (for example page scrolled or swiped and JS animations lag behind and throttle)</li></ul><p>Checkout this prominent difference on example of WebGL for Safari and Chrome for the same MacBook Pro with M1 Pro Apple Silicon chip.</p><blockquote>Link to check WebGL FPS yourself is <a href="https://webglsamples.org/aquarium/aquarium.html">https://webglsamples.org/aquarium/aquarium.html</a></blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2025/05/image-6.png" class="kg-image" alt="2025 Safari is low 60 FPS browser still - so what?" loading="lazy" width="2000" height="1463" srcset="https://colonel.shnyra.com/content/images/size/w600/2025/05/image-6.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2025/05/image-6.png 1000w, https://colonel.shnyra.com/content/images/size/w1600/2025/05/image-6.png 1600w, https://colonel.shnyra.com/content/images/size/w2400/2025/05/image-6.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Safari on macOS MacBook Pro with M Pro Silicon gives only 60 FPS in WebGL rendering</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2025/05/image-5.png" class="kg-image" alt="2025 Safari is low 60 FPS browser still - so what?" loading="lazy" width="2000" height="1468" srcset="https://colonel.shnyra.com/content/images/size/w600/2025/05/image-5.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2025/05/image-5.png 1000w, https://colonel.shnyra.com/content/images/size/w1600/2025/05/image-5.png 1600w, https://colonel.shnyra.com/content/images/size/w2400/2025/05/image-5.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Chrome on macOS MacBook Pro with M Pro Silicon gives 120 FPS in WebGL rendering</figcaption></figure><p>Results for 10K fish rendering scene:</p><ul><li>Safari renders only 60 FPS - not smooth, even for a single fish rendering</li><li>Chrome easily renders 120 FPS</li></ul><h2 id="parallax-and-similar-scroll-based-animations-will-not-go-faster-than-60-fps-on-safari">Parallax and similar scroll-based animations will not go faster than 60 FPS on Safari</h2><p>Because scroll event and other gestures will not trigger JS handler faster than 60 times per second, unfortunately. So, you cannot render smooth JS based animations on Safari.</p><p>Use JavaScript just to toggle CSS classes, so they cause CSS animations which are fast.</p><h1 id="macbook-chrome-renders-120-fps-while-safari-60-fps-or-less-%F0%9F%90%8C">MacBook Chrome renders 120 FPS while Safari 60 FPS or less &#x1F40C;</h1><p>Yes, even on modern MacBook Pro with Apple Silicon onboard Safari still limited to 60 FPS in best-case scenario. And when you give it a little work, for example to mutate DOM elements styles or content, FPS on Safari will easily drop to 30 and even 19 FPS.</p><h1 id="mobile-ios-chrome-is-actually-safari-under-the-hood-and-therefore-it-is-slow">Mobile iOS Chrome is actually Safari under the hood and therefore it is slow</h1><p>Apple did a strange thing for iOS and iPad - they forced all browser apps to be based on their slow Safari. That means that browsers like Chrome on iOS is slow as Safari because it is Safari under the hood and therefore limited to only 60 FPS or even lower when there is a load on JS - for example during scroll or text selection FPS will drop to lower than 60 FPS, and could be quickly as low as 19 FPS.</p><h1 id="ironically-apple-needs-high-fps-for-their-own-landing-pages-%F0%9F%A4%A3">Ironically, Apple needs high FPS for their own landing pages &#x1F923;</h1><p>When you check official Apple landing pages you will see a lot of JS-based animations there, mostly depending on scroll position.</p><p>Such animations require high FPS to look good and Safari is still not good browser in 2025 to run JS animations fast.</p><h1 id="css-will-change-rule-to-the-rescue-partially-but-still-limited-to-60-fps-on-safari-%E2%9C%85">CSS will-change rule to the rescue (partially, but still limited to 60 FPS on Safari) &#x2705;</h1><p>If you still want to give your Safari and iOS users better animations, checkout CSS property <code>will-change</code> at MDN <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/will-change">https://developer.mozilla.org/en-US/docs/Web/CSS/will-change</a></p><blockquote><strong>Warning: </strong><code>will-change</code> is intended to be used as a last resort, in order to try to deal with existing performance problems. It should not be used to anticipate performance problems.</blockquote><p>In general, <code>will-change</code> property let&apos;s your browser give more resources to run animations on specific elements, which also costs your device more memory and in general this CSS property should be avoided until really necessary (see MDN page details for that).</p><p>If you inspect Apple landing page for MacBook Air you will notice <code>js-will-change</code> CSS class which enables <code>will-change: transform,opacity</code> rule for their animated element.</p><p>This trick makes Safari animation smoother (not full 120 FPS though), with higher FPS rate, while other browser engines just normally do smooth animations for all elements even without this CSS force optimization rule.</p><figure class="kg-card kg-image-card"><img src="https://colonel.shnyra.com/content/images/2025/05/image.png" class="kg-image" alt="2025 Safari is low 60 FPS browser still - so what?" loading="lazy" width="2000" height="1397" srcset="https://colonel.shnyra.com/content/images/size/w600/2025/05/image.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2025/05/image.png 1000w, https://colonel.shnyra.com/content/images/size/w1600/2025/05/image.png 1600w, https://colonel.shnyra.com/content/images/size/w2400/2025/05/image.png 2400w" sizes="(min-width: 720px) 720px"></figure><h1 id="how-to-detect-safari-based-browser-engine-%F0%9F%92%A1">How to detect Safari based browser engine &#x1F4A1;</h1><p>To avoid JS based heavy animation you need to detect Safari browser or iOS device.</p><p>Here is a simple TypeScript util to do this:</p><pre><code class="language-ts">// read more at https://colonel.shnyra.com/2025-safari-is-low-fps-browser-still/
export const checkIsFPSLimitedBrowser = () =&gt; {
  const isIOS = /iPad|iPhone|iPod/i.test(navigator.userAgent);
  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);

  // iOS or Safari limited to 60 FPS and even lower
  return isIOS || isSafari;
};
</code></pre><h1 id="conclusion">Conclusion</h1><ul><li>Avoid heavy JS based animations for Safari because they will look ugly, choppy</li><li>Detect Safari-based browser engine to disable heavy JS based animations there</li><li>Try to use <code>will-change</code> CSS property as the last resort to get better animation in Safari</li><li>Use mostly CSS animations, as they are fastest 120 FPS, GPU accelerated where needed</li><li>Use lightweight JS based animations for Safari - just to toggle CSS classes or to update CSS properties to trigger CSS animations for smooth experience</li></ul>]]></content:encoded></item><item><title><![CDATA[TypeScript autocomplete with arbitrary strings allowed]]></title><description><![CDATA[<p>Here is a TypeScript trick to keep IDE intellisense suggestions for known options but still allow arbitrary strings to be provided without any compiler errors.</p><p>As the main screenshot of this article shows, the solutions is as simple as that:</p><pre><code class="language-ts">type Color = &apos;red&apos; | &apos;blue&apos; | &apos;other&</code></pre>]]></description><link>https://colonel.shnyra.com/typescript-trick/</link><guid isPermaLink="false">67b31a980cbe870001197541</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Mon, 17 Feb 2025 11:38:13 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2025/02/typescript-autocomplete-with-arbitrary-strings.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2025/02/typescript-autocomplete-with-arbitrary-strings.png" alt="TypeScript autocomplete with arbitrary strings allowed"><p>Here is a TypeScript trick to keep IDE intellisense suggestions for known options but still allow arbitrary strings to be provided without any compiler errors.</p><p>As the main screenshot of this article shows, the solutions is as simple as that:</p><pre><code class="language-ts">type Color = &apos;red&apos; | &apos;blue&apos; | &apos;other&apos; | (string &amp; {});

let color: Color = &apos;custom-is-fine&apos;;

color = &apos;red&apos;;

color = &apos;&apos; // here will be suggestions shown to choose from &apos;red&apos; | &apos;blue&apos; | &apos;other&apos;</code></pre><p>All the magic is done by <code>(string &amp; {})</code> in our union of options.</p><p>By providing string as intersection with <code>&amp; {}</code> we keep this string as separate option of union instead of collapsing the whole union to just any string.</p><p>This way autocomplete works by suggesting exact known literal options.</p><p>It is important to say that without intersection, our IDE autocompletions won&apos;t know what to suggest to write because union collapsed to just string as on the screenshot below:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2025/02/image.png" class="kg-image" alt="TypeScript autocomplete with arbitrary strings allowed" loading="lazy" width="1136" height="766" srcset="https://colonel.shnyra.com/content/images/size/w600/2025/02/image.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2025/02/image.png 1000w, https://colonel.shnyra.com/content/images/2025/02/image.png 1136w" sizes="(min-width: 720px) 720px"><figcaption>autocomplete lost when union collapsed to arbitrary string</figcaption></figure><p>P.S. without string intersection in union, to open arbitrary suggestions for just a string press CMD+I in VS Code on Mac OS.</p><p>The trick was found during inspection of source code for cool <code>tsup</code> package right on this line - <a href="https://github.com/egoist/tsup/blob/bee8f42dc72c5b40c2946422fc632069dde2a55b/src/options.ts#L94">https://github.com/egoist/tsup/blob/bee8f42dc72c5b40c2946422fc632069dde2a55b/src/options.ts#L94</a> </p>]]></content:encoded></item><item><title><![CDATA[Auto dark mode images with CSS]]></title><description><![CDATA[<p>CSS-only: Automatic Dark Mode for Images &#x2B50;&#xFE0F;</p><p>Just two CSS properties can adapt a light image for Dark Mode.</p><p>In Tailwind, it takes only two classes:<br><code>invert hue-rotate-180</code> (<a href="https://play.tailwindcss.com/5738sBZybt">open in playground</a>)</p><p>1&#xFE0F;&#x20E3; The <strong>invert</strong> filter turns white into black but distorts other colors.<br>2&#xFE0F;&#x20E3; However,</p>]]></description><link>https://colonel.shnyra.com/auto-dark-mode-images-with-css/</link><guid isPermaLink="false">677e644b0cbe8700011974e7</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Wed, 08 Jan 2025 11:48:43 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2025/01/2025-01-08-12-41-06.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2025/01/2025-01-08-12-41-06.png" alt="Auto dark mode images with CSS"><p>CSS-only: Automatic Dark Mode for Images &#x2B50;&#xFE0F;</p><p>Just two CSS properties can adapt a light image for Dark Mode.</p><p>In Tailwind, it takes only two classes:<br><code>invert hue-rotate-180</code> (<a href="https://play.tailwindcss.com/5738sBZybt">open in playground</a>)</p><p>1&#xFE0F;&#x20E3; The <strong>invert</strong> filter turns white into black but distorts other colors.<br>2&#xFE0F;&#x20E3; However, <strong>hue-rotate</strong> shifts the hues to approximate the original colors:<br>&#x2192; red stays red<br>&#x2192; green stays green<br>&#x2192; blue stays blue<br>&#x2192; and so on.</p><p>By adjusting inversion percentage and hue rotation you can quickly fine tune your image to match dark mode the best.</p><p>With this trick you may not need to generate separate images for dark mode on your website. Just apply proper styles to existing images.</p><p>The trick inspired by <a href="https://v8.dev/blog/javascript-code-coverage">https://v8.dev/blog/javascript-code-coverage</a> images styles.</p>]]></content:encoded></item><item><title><![CDATA[Type safe monkey patching with TypeScript]]></title><description><![CDATA[<p>How to wrap any function without using <code>any</code> types to keep your code as strict as possible.</p><p>Our goal is to have a universal solution so it should:</p><ul><li>wrap function with zero or more arguments</li><li>preserve types of original function (infer arguments and return type)</li><li>should work under strict compiler</li></ul>]]></description><link>https://colonel.shnyra.com/type-safe-monkey-patching-with-typescript/</link><guid isPermaLink="false">669255c40cbe870001197432</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Sat, 13 Jul 2024 10:54:01 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2024/07/monkey-at-a-computer.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2024/07/monkey-at-a-computer.jpg" alt="Type safe monkey patching with TypeScript"><p>How to wrap any function without using <code>any</code> types to keep your code as strict as possible.</p><p>Our goal is to have a universal solution so it should:</p><ul><li>wrap function with zero or more arguments</li><li>preserve types of original function (infer arguments and return type)</li><li>should work under strict compiler options of TypeScript</li></ul><p>This will allow us to:</p><ul><li>create wrappers around other functions (e.g. log function results or dispatch analytics events)</li><li>accept other functions as arguments to call them inside</li><li>call other functions before or after target function</li><li>or other cases.</li></ul><h1 id="solution">Solution</h1><p>Look, no <code>any</code> and <code>as SomeType</code> lies to compiler &#x1F389;</p><pre><code class="language-ts">const wrap = &lt;F extends (...args: never) =&gt; unknown&gt;(f: F): F =&gt; f;

// no args
// &#x2705; const f1: () =&gt; number
const f1 = wrap(() =&gt; 55);
// with args
// &#x2705; const f2: (msg: string, shift: number) =&gt; number
const f2 = wrap((msg: string, shift: number) =&gt; msg.length + shift);
</code></pre><p>This solution works with strict compiler options of <code>tsconfig.json</code></p><pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true,
  }
}</code></pre><p>TypeScript version is <code>5.5.3</code> and you can play with this code on <a href="https://www.typescriptlang.org/play/?#code/MYewdgzgLgBA7gJwIYAcYF4YB4BiMCmAHlPmACYQwAUAdHUggOYQBcMY+AbvggJQYA+GAFcwAazAg4YAVQBmbHL0WCYcgNwBYAFA6A9HvYgYDZvsOBQchihIsOQEY2VfuiFhhAWwBGPHTehq9hjwyChUzqoArJG8WtoG8ACWUAAWJkwQ5jBW-nYATE4eEIxs0AiJYIwANDAQKYlyUGzu3jwubp4+CH7gAXJ5wYio4UUltVDllTV1DU3snW2qozQANqSMqTAA1LX1jbE6PbYmkqk8AOqhwbgExKQU1HQ0pqzsXIuuIuKS0rIKMEoVJ8AN46GDgmAJQAy5DAAKIIBAgBAwAAqAE8UPgYAByWj0DLNd58VSiCRSMDYmCJSinEwQCCJRhgJBeNYwKDGKAYrHYnDYmhgiEJcG4p4vQncYmfUk-ClUyhIemM5msrEc9kpLG5ZAVWAgOTs7k4vk1LzCWC87GC8HC6wgYQrMgwHxU2xIMBQRJIEhOuDJNJIGBkBpyHikWAQYReLmY62Qwzg-V22w6j04vHPAlvSXtL5k6T8qh5ADMeTyvDjuVdJAQzJWl1QQMe+OYubkGZeB20EOTAV1PDrDZQA0wHazAAUGEgPPgaxBcAI22PW3EewhZ8Ja9WB0h66E4gBfOJAA">TS playground</a>.</p><h2 id="how-it-works">How it works</h2><ul><li>use generics function to infer types from arguments</li><li>constraint generic argument with <code>extends</code></li><li><code>args: never</code> is a trick here to allow path functions of different signatures with and without arguments</li><li><code>unknown</code> as return type so we actually don&apos;t restrict return type</li></ul><h1 id="but-caveats">But, caveats...</h1><p>Unfortunately, for more complex scenarios you may still need to mark your code with <code>@ts-ignore</code> or <code>@ts-expect-error</code> or <code>@eslint-disable-next-line</code> in cases like this:</p><pre><code class="language-ts">const anotherWrap = &lt;F extends (...args: never) =&gt; unknown&gt;(f: F): F =&gt; {
    // &#x274C; Error Type &apos;(...args: never) =&gt; unknown&apos; is not assignable to type &apos;F&apos;.
    //   &apos;(...args: never) =&gt; unknown&apos; is assignable to the constraint of type &apos;F&apos;, but &apos;F&apos;
    //   could be instantiated with a different subtype
    //   of constraint &apos;(...args: never) =&gt; unknown&apos;.(2322)
    const internalWrap: F = (...args) =&gt; f(...args);
    return internalWrap;
};
</code></pre><p>Because TypeScript compiler cannot safely match types compatibility.</p><p>Even if you explicitly tell the compiler it is completely the same whole function type or the same arguments type:</p><pre><code class="language-ts">// &#x274C; still cannot match proper type
const internalWrap: F = (...args) =&gt; f(...args);
const internalWrap2 = (...args: Parameters&lt;F&gt;) =&gt; f(...args);</code></pre><p>It is really a pity that such a simple idea like monkey patching cannot work safely in TypeScript and requires lies to compiler like ignoring errors or loosing type safety via <code>any</code> type.</p>]]></content:encoded></item><item><title><![CDATA[How to round numbers in JavaScript properly]]></title><description><![CDATA[<p>JavaScript as many other programming languages has real difficulties when it comes to rounding numbers on edge cases for floating numbers.</p><h1 id="tldr">TL;DR;</h1><p>Proper, elegant solution to round numbers in JavaScript using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON">Number.EPSILON</a> (MDN link) correction &#x1F913;:</p><pre><code class="language-ts">
const round = (num = 0, fractions = 0) =&gt; { 
  const k = Math.pow(10,</code></pre>]]></description><link>https://colonel.shnyra.com/how-to-round-numbers-in-javascript-properly/</link><guid isPermaLink="false">666d72d20cbe870001197317</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Sat, 15 Jun 2024 11:37:47 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2024/06/js-round-properly.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2024/06/js-round-properly.png" alt="How to round numbers in JavaScript properly"><p>JavaScript as many other programming languages has real difficulties when it comes to rounding numbers on edge cases for floating numbers.</p><h1 id="tldr">TL;DR;</h1><p>Proper, elegant solution to round numbers in JavaScript using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON">Number.EPSILON</a> (MDN link) correction &#x1F913;:</p><pre><code class="language-ts">
const round = (num = 0, fractions = 0) =&gt; { 
  const k = Math.pow(10, fractions); 
  const n = (num * k) * (1 + Number.EPSILON); 
  return Math.round(n) / k; 
}</code></pre><p>With this util you can properly handle edge cases like these:</p><pre><code class="language-ts">// custom round works properly
console.log(round(23.435, 1) === 23.4);// true
console.log(round(4.475, 2) === 4.48); // true
console.log(round(1.255, 2) === 1.26); // true
console.log(round(1.005, 2) === 1.01); // true
console.log(round(5.015, 2) === 5.02); // true

// native method fail to round
console.log(+23.435.toFixed(1) === 23.4);// true
console.log(+4.475.toFixed(2) === 4.48); // false
console.log(+1.255.toFixed(2) === 1.26); // false
console.log(+1.005.toFixed(2) === 1.01); // false
console.log(+5.015.toFixed(2) === 5.02); // false</code></pre><p>View full code on <a href="https://www.typescriptlang.org/play?#code/MYewdgzgLgBATiArmAJjAvDAFGRBbDGABgBoYAzOAQ2CgEtwJCiBKDAPhgG8BYAKBgxQkWAGtCAWSpQAFgDoADiADuWAIykK1Wg0gsA3P0HDoMMIRz4YAKhii2t9TADUMAHL4ARgFM4cgKIACgDKAJIAMgDybgZG8N5QiHDmUrJyCMgoOGwA9HaGfAC+-Pw5ecCI0CAEGagwyiBwokwKCAq+ADYAnvwmIB3ech0gAOZYtVkATADMcgAs0wCsZGps6OswM-MGZTBQcIjevYz9g8NjE1hz8wDsy5trG9dzABwGMLv7h8eQp0Oj4yQqHUckmi3uk0emDUoIAbO9Pgcjnw+gN-hcgVkYUQiBCoTBsat9B88l9kaizgDLos5EQ1HiMBsaURIcTEd8+KU8mBpHQAG7eGB4BIyEBochUOgdPYgeCYn4QP7nLDOLYLGlQEAAMToAA9vFj8WqdqSkQqlQDnM87nJNTr9VN8c83my8hKOhBySc0crnDCwRrtXqDVhIYzoXCEW6qB6vb8fZbsbjbUGHaH8YSoxQY57zQmxs5mfSU-aQ2H1phmaySdnY-wgA">TypeScript playground demo</a></p><h1 id="why-this-needed">Why this needed?</h1><p>Native method to round floating numbers in JavaScript is <code>.toFixed</code> which causes bugs on edge cases due to <a href="https://en.wikipedia.org/wiki/Round-off_error">Round-off error</a> (read on wikipedia).</p><p>The most prominent example of issue is when you add two simple number 0.1 and 0.2:</p><pre><code class="language-js">0.1 + 0.2 === 0.30000000000000004</code></pre><p>This example is so popular that there is even a website dedicated to showcase this problem - <a href="https://0.30000000000000004.com/">https://0.30000000000000004.com/</a></p><h2 id="why-this-can-be-a-problem">Why this can be a problem?</h2><p>Miscalculated numbers may have different bad impact:</p><ul><li>ranging from bad &quot;out of boundaries&quot; rendering of values on frontend (UI bugs)</li><li>to causing failed comparisons of numbers which may trigger wrong conditional branches to run actually unintended code (functional bugs)</li><li>and potentially cause invalid financial transactions processing with wrong amount of money distributed between participants</li></ul><p>Some effects may be noticed immediately, for example hash functions produce completely different results even when a single character out of long text is different (which is in our case may be a different small fraction of a number).</p><p>While other effects are not that obvious but may cause eventually wrong results due to accumulation of slight errors over time (imagine slightly different number multiplied millions of time in some algorithms).</p><h1 id="how-to-handle-advanced-cases">How to handle advanced cases</h1><p>For very complex and precise calculations with floating numbers you may need to use npm package like this - <a href="https://mikemcl.github.io/decimal.js/">https://mikemcl.github.io/decimal.js/</a></p>]]></content:encoded></item><item><title><![CDATA[React component with traits]]></title><description><![CDATA[<p>Trait means behaviour or some way of appearance if we talk about UI. For example a duck has traits to walk, swim, fly and others. Similarly, components in our programs have their own behaviour. Imagine component button <code>withConfirmationDialog</code> behaviour (trait) or a menu tab <code>withBadgeCounter</code> appearance (trait).</p><h2 id="composition-over-inheritance">Composition over inheritance</h2>]]></description><link>https://colonel.shnyra.com/react-component-with-traits/</link><guid isPermaLink="false">62b5ac98ca319a0001832102</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Thu, 21 Jul 2022 06:37:00 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2023/01/2023-01-11-23-50-57.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2023/01/2023-01-11-23-50-57.png" alt="React component with traits"><p>Trait means behaviour or some way of appearance if we talk about UI. For example a duck has traits to walk, swim, fly and others. Similarly, components in our programs have their own behaviour. Imagine component button <code>withConfirmationDialog</code> behaviour (trait) or a menu tab <code>withBadgeCounter</code> appearance (trait).</p><h2 id="composition-over-inheritance">Composition over inheritance</h2><p>To understand traits more, let&apos;s first remember classic simple idea of inheritance from OOP world - behaviour and properties of an object are inherited from its parent. That is why a puppy can walk and bark similarly to its parent dog.</p><p>In programming world, things are more agile and we don&apos;t need to inherit objects to reuse a specific behaviour. This better way of reusing behaviour is achieved through a <strong>composition of traits</strong>.</p><p>Idea of trait is to extract behaviour and properties from component and reuse where needed easily. So, we can take a fish and extend it with <code>walk</code> trait - and it will walk! In virtual world we don&apos;t need a parent to inherit from, just get a behaviour and apply to any needed component of a program.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2023/01/lbnhwacvtte41.jpg" class="kg-image" alt="React component with traits" loading="lazy" width="1080" height="1109" srcset="https://colonel.shnyra.com/content/images/size/w600/2023/01/lbnhwacvtte41.jpg 600w, https://colonel.shnyra.com/content/images/size/w1000/2023/01/lbnhwacvtte41.jpg 1000w, https://colonel.shnyra.com/content/images/2023/01/lbnhwacvtte41.jpg 1080w" sizes="(min-width: 720px) 720px"><figcaption>pigeon with a helicopter-flight trait</figcaption></figure><p>Adding new methods to objects is easy for JavaScript - just assign needed method to instance and voila. Some languages like Scala and PHP even made <strong>traits</strong> as part of language syntax.</p><h1 id="but-how-to-apply-traits-to-functional-component-of-react-first-approach">But how to apply traits to functional component of React? First approach</h1><p>In functional programming a trait can be expressed as a <strong>function wrapper</strong>.</p><p>Let&apos;s consider next example using <strong>builder pattern</strong>:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">export const MyComponent = withQA()
  .withDefinedUser()
  .withErrorBoundary({ name: &apos;CustomNameComponent&apos; })
  .withAuthorizationRequired() // show a sign in button for unathorized users instead of content below
  .render(({ currentUser }) =&gt; {
    return &lt;div&gt;Hello {currentUser.nickname}&lt;/div&gt;;
  });</code></pre><figcaption>builder pattern to specify functional component traits</figcaption></figure><p>Here is <code>MyComponent</code> is a simple React component that renders authorized user&apos;s nickname, but extended with few traits:</p><ol><li><code>withDefinedUser</code> - makes sure <code>currentUser</code> prop provided to component, so we don&apos;t need to use &quot;useCurrentUser&quot; hook inside.</li><li><code>withQA</code> - extends component with convenient debug tools in runtime.</li><li>and others</li></ol><p>Remember those old days, when <code>react-router</code> suggested to use <code>withRouter</code> ? That&apos;s another practical example of a component trait! And <code>withRouter</code> is called here a &quot;high order function&quot; as it takes other function as argument and returns another function.</p><p>While this approach may look concise it is actually a tricky and cumbersome to implement and sometimes hard to debug rendering properly when multiple layers of traits composed to a component. Especially heavy part is for TypeScript apps where we need to forward and extend lots of generics till the needed component&apos;s rendering content - that&apos;s why I let you figure out yourself how can this builder pattern can be coded in reality.</p><h1 id="hoc-is-the-way-to-go-the-winner">HOC is the way to go. The winner</h1><p>High order components in React works great to express traits composition. HOC is also a function wrapper actually but it leverages all the beauty of JSX syntax.</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">&lt;WithConfirmationDialog
  trigger={&lt;StyledButton&gt;logout&lt;/StyledButton&gt;}
  onConfirm={logout}
/&gt;
&lt;WithQA&gt;
  &lt;WithDefinedUser
    render={({ currentUser }) =&gt; (
      &lt;Hello greeting=&quot;Hello&quot; currentUser={currentUser} /&gt;
    )}
  /&gt;
&lt;/WithQA&gt;
&lt;Hello greeting=&quot;Hola&quot; /&gt;</code></pre><figcaption>HOC examples in React</figcaption></figure><p>Reasons to use HOC instead of builder pattern or other high order functions:</p><ul><li>Better readability due to JSX (especially when lots of arguments configure HOC behaviour)</li><li>Semantically proper tree in virtual DOM due to component names without hidden layers of intermediate components created by high order functions from first example</li><li>Idiomatic way for React to compose functionality of components</li><li>Easy to implement - just regular components with children</li></ul><p>Because of that, I <strong>mostly use HOC to compose traits</strong> on top of my base components while builder pattern is rarely used and just for simple extensions (usually because of existing code style for a particular component).</p>]]></content:encoded></item><item><title><![CDATA[Meta programming with TypeScript]]></title><description><![CDATA[<p>First, developers write programs. Then, they write code (programs) which produce programs (code) based on other code (programs) - this is meta programming.</p><p>The simplest example of meta programming in JavaScript I can think of is <code>Object.keys</code> - iterate through any object&apos;s keys and do something with</p>]]></description><link>https://colonel.shnyra.com/meta-programming-typescript/</link><guid isPermaLink="false">628b87cf1d98bb0001db841a</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Thu, 07 Jul 2022 12:38:00 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2023/01/iStock-1205321953.avif.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2023/01/iStock-1205321953.avif.png" alt="Meta programming with TypeScript"><p>First, developers write programs. Then, they write code (programs) which produce programs (code) based on other code (programs) - this is meta programming.</p><p>The simplest example of meta programming in JavaScript I can think of is <code>Object.keys</code> - iterate through any object&apos;s keys and do something with them. This allows to change behaviour of a program dynamically in runtime.</p><p>Therefore, with magical power of JavaScript we can analyze any object - check for object keys, their types, modify and assign new properties and methods to any object / class.</p><p>Thankfully, TypeScript brings clarity (again) to JavaScript magic so we can create type-safe magic-enabled apps.</p><h1 id="caseapi-silent-methods">Case - API silent methods</h1><p>My recent case for metaprogramming with TypeScript happened with the help of <code><a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types">key remapping in mapped types</a></code> .</p><p>For REST API endpoints I use classes with static methods like next:</p><pre><code class="language-ts">class ProfileAPI {
  getMyProfile() { ... }
  
  updateMyProfile() { ... }
}</code></pre><p>Using <code>axios</code> for networking, I created <code>interceptors</code> to catch <strong>error responses</strong> and show relevant error toasters (aka snackbar notifications) using nice and simple <a href="https://www.npmjs.com/package/react-toastify">react-toastify npm package</a>.</p><p>But I don&apos;t want to show for every method of API, because:</p><ol><li>I want to handle error differently, e.g. show response error right below button user clicked instead of toaster in top right corner</li><li>sometimes it is not needed, e.g. optional side-effect which runs on interval and not every request is critical.</li></ol><h2 id="solutionextend-api-with-silent-methods">Solution - extend API with silent methods</h2><p>With little help of JS magic, I turned my APIs into new classes with more methods:</p><pre><code class="language-ts">class ProfileAPI {
  getMyProfile() { ... }
  
  updateMyProfile() { ... }

  getMyProfileSilent() { ... }
  
  updateMyProfileSilent() { ... }
}</code></pre><p>Methods with <code>Silent</code> suffixes are added automatically and have the same behaviour as original methods which you can then extend as you need via <strong><a href="https://en.wikipedia.org/wiki/Monkey_patch">monkey patching</a></strong>.</p><pre><code class="language-ts">type RemapKeysWithSuffix&lt;O extends object, Suffix extends string&gt; = {
  [K in keyof O as `${string &amp; K}${Suffix}`]: O[K]
};

// add special behaviour to new &quot;virtual&quot; methods during class build
const build = &lt;A extends object, Configs extends Record&lt;string, {}&gt;&gt;(api: new (config?: {}) =&gt; A, configs: Configs): A &amp; RemapKeysWithSuffix&lt;A, string &amp; keyof Configs&gt; =&gt; {
  const result: Record&lt;string, any&gt; = new api();
  Object.keys(configs).forEach(suffix =&gt; {
    const config = configs[suffix];
    const configuredApi: Record&lt;string, any&gt; = new api(config);
    Object.keys(result).forEach(key =&gt; {
      // monkey patch here
      result[key + suffix] = configuredApi[key];
    });
  });
  return result as any;
}

const DemoApi = build(class DemoApi {
  method() { }
  anotherMethod() { }
}, { Silent: {} });

DemoApi.anotherMethodSilent();
</code></pre><h1 id="casetype-safe-query-for-database">Case - type safe query for database</h1><p>Another prominent case I used metaprogramming for is related to <strong><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy</a>.</strong></p><p>On my backend project, which is written with a great framework <a href="https://nestjs.com/">NestJs</a> I use <a href="https://typeorm.io/">TypeORM</a> query builder technique to get complex data joins from PostgreSQL database while preserving type safety like next:</p><pre><code class="language-ts">const subSelect = this.repo
    .createQueryBuilder(safe.Alias.sub2Submission)
    .select(safe.sub2Submission.createdDate)
    .where(`${safe.sub2Submission.participantId} = ${safe.subSubmission.participantId}`)
    .groupBy(safe.sub2Submission.score)
    .addGroupBy(safe.sub2Submission.createdDate)
    .orderBy(safe.sub2Submission.score, &apos;DESC&apos;)
    .addOrderBy(safe.sub2Submission.createdDate, &apos;ASC&apos;)
    .limit(1)
    .getSql();</code></pre><p>Now, all &quot;raw queries&quot; are used only through special <code>safe</code> object with properties generated in runtime and only for allowed value.</p><h2 id="solutionproxy-query-generator">Solution - Proxy query generator</h2><p>With the next little util I can create string generators backed by type safety of TypeScript:</p><pre><code class="language-ts">export const createSafePropertyGetter = &lt;T&gt;(prefix?: string): Record&lt;keyof T, string&gt; =&gt; {
  return new Proxy({} as any, {
    get(_, prop: string) {
      if (typeof prop === &apos;symbol&apos;) {
        throw new Error(&apos;try to use safe.Alias.desiredEntity&apos;);
      }
      return prefix ? prefix + &apos;.&apos; + prop : prop;
    },
  }) as any;
};</code></pre><p>So, let&apos;s say I have entity (database table) called <code>profile</code>, we do next:</p><pre><code class="language-ts">// define list of entities
type allEntityNames =
  | &apos;profile&apos;
  | &apos;file&apos;
  | &apos;member&apos;
  | &apos;friend&apos;;
export const Alias = createSafePropertyGetter&lt;Record&lt;allEntityNames, string&gt;&gt;();

// create namespace to conveniently group all entities in one object
namespace nsSafe {
  export const profile = createSafePropertyGetter&lt;ProfileEntity&gt;(Alias.profile);
}

export const safe = {
  Alias,
  ...nsSafe,
} as const;

// now use it everywhere needed
safe.Alias.profile; // =&gt; &apos;profile&apos;
safe.friend.invitedById; // =&gt; &apos;friend.invitedById&apos;</code></pre><p>Benefits of this approach is all entities names and their properties are type safe, <strong>inferred from entity interfaces and type aliases</strong> and type checked during compilation, which in turn means:</p><ul><li>No typos</li><li>IDE Suggestions for available properties</li><li>Code is linked through project files, I can find all references to any property in query builder</li><li>Easy to refactor and maintain</li><li>Strict and clear pattern to follow across the team</li></ul><h1 id="read-more">Read more</h1><p>As part of metaprogramming, I encourage you to experiment with <a href="https://www.typescriptlang.org/docs/handbook/decorators.html">TypeScript decorators</a> which is still in experimental mode (TC39 <a href="https://github.com/tc39/proposal-decorators">stage 2 proposal</a>) after so many years, though. But I like this feature and hope they will make decorators available not only for class methods, but for plain arrow functions.</p><p>For me personally, metaprogramming is like magic because ability to program change itself in runtime based on inspection of its own code and data is a great origin story for Artificial Intelligence to appear - but that&apos;s another big topic for future &#x1F609;</p>]]></content:encoded></item><item><title><![CDATA[Universal identity type function]]></title><description><![CDATA[<p>As per nowadays trends, TypeScript is a smart language which can understand types of static code without explicit indication of type thanks to its <a href="https://www.typescriptlang.org/docs/handbook/type-inference.html">type inference</a> ability.</p><p>Type inference is really one of the best thing about this language, I use it every day I write a code. And strongly</p>]]></description><link>https://colonel.shnyra.com/universal-identity-type-function/</link><guid isPermaLink="false">62e38a2bca319a000183210d</guid><category><![CDATA[utils]]></category><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Thu, 30 Jun 2022 08:38:00 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2022/07/2022-07-29-11-36-03.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2022/07/2022-07-29-11-36-03.png" alt="Universal identity type function"><p>As per nowadays trends, TypeScript is a smart language which can understand types of static code without explicit indication of type thanks to its <a href="https://www.typescriptlang.org/docs/handbook/type-inference.html">type inference</a> ability.</p><p>Type inference is really one of the best thing about this language, I use it every day I write a code. And strongly recommend you to understand <code>where and why</code> TypeScript can infer types and where cannot - so you have to explicitly mark appropriate type in this case.</p><h1 id="problem-to-solve">Problem to solve</h1><p>Need a way to enforce object literal structure while inferring exact properties of object as literals instead of general types &quot;string&quot; or &quot;number&quot;.</p><h1 id="solution">Solution</h1><p>Universal identity function - <code>a function that returns exactly what it receives</code> -</p><pre><code class="language-ts">// src/utils/type.util.ts
export const inferIdentity = &lt;BaseType,&gt;() =&gt; &lt;T extends BaseType&gt;(value: T): T =&gt; value;</code></pre><p>Usage demo:</p><pre><code class="language-ts">interface CustomRoute {
    path: string;
}

const homeRoute = inferIdentity&lt;CustomRoute&gt;()({
    path: &apos;/&apos;,
} as const);

const otherRoute = inferIdentity&lt;CustomRoute&gt;()({
    pathTypo: &apos;/other&apos;,
//  ^ Error: Object literal may only specify known properties, and &apos;pathTypo&apos; does not exist in type &apos;CustomRoute&apos;.(2345)
} as const);</code></pre><p>If some type is widely used, you can reuse identity easily:</p><pre><code class="language-ts">export const identityRoute = inferIdentity&lt;CustomRoute&gt;();

// reuse across the app
const anotherRoute = identityRoute({
    path: &apos;/another&apos;,
} as const);</code></pre><p>Note, we name new util as <code>identityRoute</code> instead of <code>inferIdentityRoute</code> because function is identity function now:</p><ul><li><code>identityRoute</code> is not a high order function anymore, but a simple function.</li><li><code>inferIdentity</code> returns identity function instead of its argument, like identity function would do.</li></ul><h1 id="how-it-works">How it works?</h1><p>Because of a type closure. Similar to runtime JavaScript closure, we can capture generic type for its child function.</p><p>Therefore, actually, the &quot;universal identity function&quot; is not an identity function but a <code>high order function that returns an identity function</code> and type closure helps to constraint generic type needed.</p><h1 id="why-it-is-useful">Why it is useful?</h1><p>To explain reasoning here, let&apos;s start with a simple scenario.</p><p>Imagine you need an object literal config to use across the app:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">export const config = {
  timeout: 1e3,
}

// and the related type is
const config: {
    timeout: number;
}</code></pre><figcaption>config literal with a one second timeout</figcaption></figure><p>Now, we want to use our great type inference in some places to understand exact timeout value defined. Currently <code>timeout</code> is any <code>number</code> , so we add <code>as const</code> to keep exact values of properties:</p><pre><code class="language-ts">const config = {
  timeout: 1e3,
} as const;

// and the related type is
const config: {
    readonly timeout: 1000;
}</code></pre><p>Cool, now we can infer types of whole config, or its properties and further transform them via <a href="https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#handbook-content">mapped types</a> and constraint code with specific union literals, for instance.</p><p>Such code is actually enough for a small demo app, but in reality production apps:</p><ul><li>Have much bigger object literals</li><li>Hard to remember which exact properties are available for every object literal</li><li>Need a reliable way (compile error) to avoid typos in object properties</li></ul><p>For that, interfaces and type aliases comes to the rescue:</p><pre><code class="language-ts">const config: IConfig = {
  timeout: 1e3,
}</code></pre><p>Which is great - TypeScript now suggests us which properties are available for us to configure, and will raise a compile error in case of typo or wrong type defined for a property.</p><p>Only one thing left for it to be perfect - we need to know exact values of object literals instead of abstract <code>IConfig</code> interface.</p><p>For that, you come up with an idea of identity function:</p><pre><code class="language-ts">const configIdentity = &lt;T extends { timeout: number }&gt;(config: T): T =&gt; config;

const config = configIdentity({
    timeout: 1e3,
} as const);

// and the related type is
const config: {
    readonly timeout: 1000;
}</code></pre><p>Awesome, now you can create all the identity functions for all the object literals and even primitive types to make your codebase constrained and obvious.</p><p>Or you may use a single <code>universal identity function</code> shown at solution section of this article &#x1F609;.</p>]]></content:encoded></item><item><title><![CDATA[How to fix npm packages]]></title><description><![CDATA[<p>Web development is full of external dependencies and frontend developers and NodeJs developers are especially depend on <code>node_modules</code> which we know are the heaviest objects in the universe.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2022/06/image.png" class="kg-image" alt loading="lazy" width="990" height="712" srcset="https://colonel.shnyra.com/content/images/size/w600/2022/06/image.png 600w, https://colonel.shnyra.com/content/images/2022/06/image.png 990w" sizes="(min-width: 720px) 720px"><figcaption>node_modules are heaviest objects in the universe</figcaption></figure><p>Recently I faced with a critical issue with one of my code packages</p>]]></description><link>https://colonel.shnyra.com/how-to-fix-npm-packages/</link><guid isPermaLink="false">62aee133ca319a0001831e82</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Sun, 19 Jun 2022 10:20:32 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2022/06/2022-06-19-12-17-25.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2022/06/2022-06-19-12-17-25.png" alt="How to fix npm packages"><p>Web development is full of external dependencies and frontend developers and NodeJs developers are especially depend on <code>node_modules</code> which we know are the heaviest objects in the universe.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2022/06/image.png" class="kg-image" alt="How to fix npm packages" loading="lazy" width="990" height="712" srcset="https://colonel.shnyra.com/content/images/size/w600/2022/06/image.png 600w, https://colonel.shnyra.com/content/images/2022/06/image.png 990w" sizes="(min-width: 720px) 720px"><figcaption>node_modules are heaviest objects in the universe</figcaption></figure><p>Recently I faced with a critical issue with one of my code packages - <code><a href="https://nextjs.org/">NextJs</a></code> caused a compiler error, so I was unable to build my app with TypeScript.</p><p>Since <code>NextJs</code> is a core dependency of my project, I cannot replace it with something else, so I have to find a workaround on fixing npm package in a quick way. Issue details will be explained below, but first let&apos;s define <strong>general goals on the fix:</strong></p><ul><li>Solution has to be explicit, universal and elegant</li><li>Solution has to be <code>git tracked</code></li><li>Without Pull Request on GitHub repo (no time to wait for maintainers attention and approval)</li><li>Without forking GitHub repo to create my own version of package (I will not maintain this package as good as maintainers of official NextJs framework)</li><li>Solution has to be easy maintained after package version upgrade</li></ul><h1 id="patch-package-to-the-rescue">Patch-package to the rescue!</h1><p>Hopefully, some good developers from a community already took care about my case - <code><a href="https://www.npmjs.com/package/patch-package">patch-package</a></code> is an npm package which patches source code of any package as if I would directly commit changes to package while still git ignoring node_modules.</p><figure class="kg-card kg-image-card"><img src="https://colonel.shnyra.com/content/images/2022/06/image-3.png" class="kg-image" alt="How to fix npm packages" loading="lazy" width="844" height="490" srcset="https://colonel.shnyra.com/content/images/size/w600/2022/06/image-3.png 600w, https://colonel.shnyra.com/content/images/2022/06/image-3.png 844w" sizes="(min-width: 720px) 720px"></figure><h2 id="solution-demo">Solution demo</h2><p>Solution was very simple and fully aligned with general goals defined abode.</p><p>Screenshot below shows a full needed changes to my package to build the app without compiler errors. (if you ignore description comments, solution is just a few lines of code).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2022/06/image-1.png" class="kg-image" alt="How to fix npm packages" loading="lazy" width="1956" height="982" srcset="https://colonel.shnyra.com/content/images/size/w600/2022/06/image-1.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2022/06/image-1.png 1000w, https://colonel.shnyra.com/content/images/size/w1600/2022/06/image-1.png 1600w, https://colonel.shnyra.com/content/images/2022/06/image-1.png 1956w" sizes="(min-width: 720px) 720px"><figcaption>fix NextJs global.d.ts via patch-package</figcaption></figure><h1 id="issue-use-case">Issue use case</h1><p>In my situation, the issue was with <a href="https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-d-ts.html">global typings <code>.d.ts</code></a> files placed deeply inside git ignored source code of npm dependency - typings of CSS modules are defined in an incompatible way with my <code><a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#handbook-content">tsconfig.json</a></code> restrictions.</p><p>Original NextJs <code>global.d.ts</code> file makes all classes imports from <code>*.module.scss</code> in form of string record:</p><pre><code class="language-ts">const classes: { readonly [key: string]: string }</code></pre><p>What is wrong with it? It is weakly typed - you can refer to any arbitrary class name in your code and by default TypeScript compiler will be fine with that allowing potential runtime bugs, especially in future:</p><pre><code class="language-ts">import style from &apos;./index.module.scss&apos;;

// no error by compiler &#x1F44E;
&lt;div className={style.nameWithTypo} /&gt;
// no error by compiler &#x1F44E;
&lt;div className={style.anotherTypoMissedHere} /&gt;
// no error by compiler &#x1F44E;
&lt;div className={style.previouslyDefinedButRemovedOrRenamedNow} /&gt;
</code></pre><p>Hopefully, we can make TypeScript compiler more stricter and force us to check all arbitrary members of objects via enabled option <code><a href="https://www.typescriptlang.org/tsconfig/#noUncheckedIndexedAccess">noUncheckedIndexedAccess</a></code> in <code><a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#handbook-content">tsconfig.json</a></code>. With this option, all our classes from module css files are potentially undefined.</p><p>So now, every object member checked by compiler and raise compile errors in cases like that:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import clsx from &apos;clsx&apos;;
import style from &apos;./style.module.scss&apos;;

    &lt;Button
      className={clsx({
        [style.autoMargin]: props.fullWidth,
//         &#x274C;  ^ A computed property name must be of type &apos;string&apos;, &apos;number&apos;, &apos;symbol&apos;, or &apos;any&apos;.ts(2464)
      })}
      /&gt;
      {children}
    &lt;/Button&gt;


// error is equivalent to
const demo = { [undefined]: true };
//           &#x274C; ^ A computed property name must be of type &apos;string&apos;, &apos;number&apos;, &apos;symbol&apos;, or &apos;any&apos;.ts(2464)</code></pre><figcaption>TypeScript compiler error when undefined value used as object key</figcaption></figure><p>In code above, <code>style.autoMargin</code> potentially <code>undefined</code> and therefore not allowed to be used as key for literal object.</p><p>Of course I do not want to check in runtime existence of my class name. Also I do not want to lie to my compiler via terrible <code><a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator">non-null assertion operator</a></code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">const panic = &lt;T extends unknown&gt;(value: T): NonNull&lt;T&gt; =&gt; {
  if (value === undefined || value === null) {
    throw new Error(&apos;value undefined&apos;);
  }
  return value;
}

// &#x1F44E; runtime check via panic looks like over engineering
&lt;div className={panic(style.autoMargin)} /&gt;
// &#x1F44E; non-null assertion is a lie to compiler, do not spoil the code
&lt;div className={style.autoMargin!} /&gt;
</code></pre><figcaption>Ugly workarounds on potentially undefined values</figcaption></figure><h2 id="so-the-solution-is-to-change-module-css-typings">So, the solution is to change module css typings</h2><pre><code class="language-ts">declare module &apos;*.module.scss&apos; {
  const classes: any
  export default classes
}</code></pre><p>Now we don&apos;t need to handle potentially undefined values &#x1F389;</p><h2 id="but-wait-your-classes-any-is-even-worse-than-a-string-record">But wait, your <code>classes: any</code> is even worse than a string record</h2><p>Not exactly, because I use other technique to keep my CSS modules names relevant - TypeScript plugin <code><a href="https://www.npmjs.com/package/typescript-plugin-css-modules">typescript-plugin-css-modules</a></code>.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text"><code><a href="https://www.npmjs.com/package/typescript-plugin-css-modules">typescript-plugin-css-modules</a></code> shows a fully typed object of CSS classes available</div></div><p>This way, my favourite <a href="https://code.visualstudio.com/">IDE VS Code</a> helps me to know relevant CSS classes and highlight issues like typos.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2022/06/image-2.png" class="kg-image" alt="How to fix npm packages" loading="lazy" width="1426" height="798" srcset="https://colonel.shnyra.com/content/images/size/w600/2022/06/image-2.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2022/06/image-2.png 1000w, https://colonel.shnyra.com/content/images/2022/06/image-2.png 1426w" sizes="(min-width: 720px) 720px"><figcaption>See CSS module classes typed</figcaption></figure><p>I believe this approach is a good balance between development convenience and type reliability. For something more important than classnames, I would rely on real compiler check (no lies via <code>:any</code> or <code>!</code> or <code>as SomeType</code>) or even runtime check like with a <code>panic</code> util above.</p>]]></content:encoded></item><item><title><![CDATA[WebGL shader renderer for awesome animations]]></title><description><![CDATA[<p></p><p>Nowadays, WebGL is a popular technology for developing engaging frontend web apps. I see more and more global companies like Stripe use WebGL to create awesome animations on their landing pages.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2022/05/image.png" class="kg-image" alt loading="lazy" width="2000" height="1160" srcset="https://colonel.shnyra.com/content/images/size/w600/2022/05/image.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2022/05/image.png 1000w, https://colonel.shnyra.com/content/images/size/w1600/2022/05/image.png 1600w, https://colonel.shnyra.com/content/images/size/w2400/2022/05/image.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Stripe landing with WebGL background</figcaption></figure><p>This background mesh gradient is smoothly animated &#x261D;&#xFE0F; (visit <a href="https://stripe.com/">https://stripe.com/</a> to</p>]]></description><link>https://colonel.shnyra.com/webgl-shader-renderer/</link><guid isPermaLink="false">627b93241d98bb0001db83ef</guid><category><![CDATA[webgl]]></category><category><![CDATA[shader]]></category><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Sun, 29 May 2022 18:25:00 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2022/05/2022-05-29-21-40-36.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2022/05/2022-05-29-21-40-36.png" alt="WebGL shader renderer for awesome animations"><p></p><p>Nowadays, WebGL is a popular technology for developing engaging frontend web apps. I see more and more global companies like Stripe use WebGL to create awesome animations on their landing pages.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2022/05/image.png" class="kg-image" alt="WebGL shader renderer for awesome animations" loading="lazy" width="2000" height="1160" srcset="https://colonel.shnyra.com/content/images/size/w600/2022/05/image.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2022/05/image.png 1000w, https://colonel.shnyra.com/content/images/size/w1600/2022/05/image.png 1600w, https://colonel.shnyra.com/content/images/size/w2400/2022/05/image.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Stripe landing with WebGL background</figcaption></figure><p>This background mesh gradient is smoothly animated &#x261D;&#xFE0F; (visit <a href="https://stripe.com/">https://stripe.com/</a> to see it in action)</p><p>Below you can see <strong>realtime rendered by your device</strong> running WebGL programs &#x1F447;&#x1F3FB;</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Click on animation to enter Full Screen mode if your device supports it</div></div><!--kg-card-begin: html--><!-- main styles for canvas containers -->

<style>
        .shader-demo {
          height: 300px;
          margin: 0 auto;
          width: 100%;
          max-width: 350px;
          cursor: pointer;
        }
        .canvas-info {
          position: relative;
          padding: 8px 0;
          text-align: center;
          color: rgb(38, 63, 88);
          cursor: pointer;
        }
        .canvas-info:hover {
          text-decoration: underline;
        }
        .canvas-desc pre {
          overflow: auto;
          flex: 1;
          max-height: 80vh;
        }
</style><!--kg-card-end: html--><h3 id="tubes-with-textures">Tubes with textures</h3><!--kg-card-begin: html--><div class="canvas-tubes shader-demo"></div>
<!--kg-card-end: html--><h3 id="glass">Glass</h3><!--kg-card-begin: html--><div class="canvas-glass shader-demo"></div>
<!--kg-card-end: html--><h3 id="matrix-with-the-flag-of-ukraine">Matrix with the flag of Ukraine</h3><!--kg-card-begin: html--><div class="canvas-matrix shader-demo"></div>
<!--kg-card-end: html--><h3 id="metaball">Metaball</h3><!--kg-card-begin: html--><div class="canvas-metaball shader-demo"></div>
<!--kg-card-end: html--><h3 id="helix">Helix</h3><!--kg-card-begin: html--><div class="canvas-helix shader-demo"></div>
<!--kg-card-end: html--><h1 id="how-to-create-such-animations">How to create such animations?</h1><p>Learning WebGL might seem an overwhelming because of new terminology, new approach to render things on a screen and a setup of WebGL is not straightforward comparing to usual <code>2D</code> canvas context.</p><p>Here are base steps you may do:</p><ul><li>Go through <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL">WebGL tutorial on MDN</a></li><li>Google about &quot;introduction to WebGL shaders&quot;</li><li>Install IDE extensions to to highlight and explain GLSL language (e.g. for VS Code, I recommend this one - <code>slevesque.shader</code>)</li><li>Search for YouTube videos</li><li>Feel overwhelmed &#x1F629;, rest for a while &#x1F60C;</li><li>Repeat steps multiple times &#x267B;&#xFE0F;</li><li>Finally, produce first meaningful WebGL programs &#x2705;</li></ul><h2 id="or-can-it-be-simpler">Or can it be simpler?</h2><p>Yes! <code>Focus only on <strong>WebGL fragment shaders</strong></code> without caring about <code>vertex shaders, WebGL API and other stuff</code> . All you need is to understand <code>fragment shaders</code> (written in a language called <code>glsl</code>).</p><p>Here are my favourite sources I recommend to go through:</p><ul><li><a href="https://thebookofshaders.com/">https://thebookofshaders.com/</a> - interactive online book with practical examples</li><li><a href="https://www.youtube.com/user/osakaandrew/videos">https://www.youtube.com/user/osakaandrew/videos</a> - step by step video to run WebGL program</li></ul><p>To simplify things for you, I have created an <code><a href="https://github.com/overshom/overshader">overshader</a></code> - a simple <strong>NPM package</strong> to render WebGL shaders without complex details behind the scene.</p><p>Animations above - which are called WebGL programs - are created with <code>overshader</code>.</p><p>To get started, just checkout <code>README.md</code> of my package - <a href="https://github.com/overshom/overshader">https://github.com/overshom/overshader</a></p><p>Explore source code (shaders) of examples - <a href="https://overshom.github.io/overshader/github-demo/index.html">https://overshom.github.io/overshader/github-demo/index.html</a></p><p><code>README.md</code> on GitHub describes how you can easily launch your own WebGL shader program which is as simple as:</p><pre><code class="language-ts">import { runWebglProgram } from &apos;overshader&apos;;

runWebglProgram({
    canvasContainer: &apos;.responsive-canvas-container&apos;,
});</code></pre><ul><li>canvasContainer - simple div, inside which canvas with WebGL program will be inserted</li><li>by default, my package renders a simple program so you can easily see it is working</li></ul><p>Feel free to fork or just clone my repo and launch it locally - it is fast and convenient to work with inside IDE like VS Code.</p><h1 id="benefits-of-using-webgl">Benefits of using WebGL</h1><ul><li>Leverage <code>GPU</code> processor of your device to run animations at <strong>impressive 60Hz or even 120Hz</strong> (device dependent, of course) (usual Canvas 2D context will not provide such a fast rendering for any complex animation)</li><li>Control every pixel rendered on a screen - create <strong>very complex and mind blowing rendering scenes</strong></li><li>Improve your math skills (a shader program is full of vectors, matrices, dot products, sin and cos and lots of other stuff)</li></ul><h1 id="debug-webgl">Debug WebGL</h1><p>Short answer - there is no actual debug tools in browser yet for WebGL like <code>console.log</code> or <code>Chrome Dev Inspector</code>, no breakpoints, what we can do instead is to use canvas rendered pixels itself as our inspector.</p><p>For that purposes, I have written a simple fragment shader debugger - it renders red, green and blue channels for <code>vec3</code> vectors and has vertical lines which indicate value of every vector component ( <code>r,g,b</code> or <code>x,y,z</code> if you like)</p><!--kg-card-begin: html--><div class="canvas-debug shader-demo"></div>
<!--kg-card-end: html--><p>This way &#x261D;&#xFE0F; you can render your values (vectors) and see their value from a range <code>(-&#x221E;, -2, -1, 0, 1, 2, &#x221E;+)</code> </p><p>How it works:</p><ul><li>Very first vertical line on the left - means value is <code>-2</code></li><li>Second vertical line - means value is <code>-1</code></li><li>Center vertical line - means value is <code>zero</code></li><li>Next line - means value is <code>1</code></li><li>And the last line - the very right one - means value is <code>2</code></li></ul><h2 id="simpler-debugging">Simpler debugging</h2><p>The simplest debug you can make is just to output a high-contrast color on a screen, like red:</p><pre><code class="language-glsl">gl_FragColor = vec4(1., .0, .0, 1.);</code></pre><!--kg-card-begin: html--><script type="module">
// @ts-check
import { runWebglProgram } from "https://cdn.skypack.dev/pin/overshader@v1.0.3-uPo4BBgULuOHWH21z2tf/mode=imports,min/optimized/overshader.js";

// handle different path for localhost development
const rootBase = window.location.hostname === "localhost" ? "../.." : "";

/** @type {<K extends keyof HTMLElementTagNameMap>(tagName: K,classes?: string[]) => HTMLElementTagNameMap[K]} */
const cel = (type, classes = []) => {
  const desc = document.createElement(type);
  classes.forEach((c) => desc.classList.add(c));
  return desc;
};

/**
 * @type {typeof runWebglProgram}
 */
const myRunWebglProgram = (config) => {
  if (typeof config.canvasContainer !== "string") {
    return;
  }
  const div = document.querySelector(config.canvasContainer);
  if (!div || !(div instanceof HTMLDivElement)) {
    return;
  }
  let fullscreen = false;
  div.onclick = async () => {
    if (fullscreen) {
      await document.exitFullscreen();
      fullscreen = false;
      return;
    }
    await div.requestFullscreen();
    fullscreen = true;
  };

  const info = cel("div", ["canvas-info"]);
  div.after(info);

  const loadSource = async () => {
    const url = config.fragmentShaderURL;
    if (!url) {
      return;
    }
    const res = await fetch(url);
    const text = await res.text();
    const name = url.split("/").pop() || url;
    const desc = cel("div", ["canvas-desc"]);
    const pre = cel("pre");
    const code = cel("code", ["hljs", "language-glsl"]);
    pre.append(code);
    desc.append(pre);
    // const infoBottom = cel("div", ["canvas-info"]);
    // div.after(infoBottom);
    let mounted = false;
    const renderSub = () => {
      if (mounted) {
        info.textContent = `close source for ${name}`;
      } else {
        info.textContent = `view source for ${name}`;
      }
    };
    const toggle = () => {
      if (mounted) {
        desc.remove();
        mounted = false;
      } else {
        info.after(desc);
        mounted = true;
        window.hljs && window.hljs.highlightAll();
      }
      renderSub();
    };
    renderSub();
    info.onclick = () => toggle();
    code.textContent = `${text}`;
  };
  loadSource();

  const controls = runWebglProgram(config);
  return controls;
};

myRunWebglProgram({
  canvasContainer: ".canvas-tubes",
  fragmentShaderURL: `${rootBase}/assets/shaders/tubes.glsl`,
  textures: [`${rootBase}/assets/textures/texture-soil.jpeg`],
  // lower resolution for Retina screen for better performance
  devicePixelRatio: 1,
});

myRunWebglProgram({
  canvasContainer: ".canvas-glass",
  fragmentShaderURL: `${rootBase}/assets/shaders/glass.glsl`,
  contextAttributes: {
    alpha: false,
  },
});

myRunWebglProgram({
  canvasContainer: ".canvas-matrix",
  fragmentShaderURL: `${rootBase}/assets/shaders/matrix.glsl`,
  textures: [
    `${rootBase}/assets/textures/texture-font.png`,
    `${rootBase}/assets/textures/texture-noise.png`,
  ],
  initialTimeShift: Math.random() * 1e3 * 1,
});

myRunWebglProgram({
  canvasContainer: ".canvas-metaball",
  fragmentShaderURL: `${rootBase}/assets/shaders/metaball.glsl`,
});

myRunWebglProgram({
  canvasContainer: ".canvas-helix",
  fragmentShaderURL: `${rootBase}/assets/shaders/helix.glsl`,
  contextAttributes: {
    alpha: true,
    premultipliedAlpha: false,
  },
});

myRunWebglProgram({
  canvasContainer: ".canvas-debug",
  fragmentShaderURL: `${rootBase}/assets/shaders/debug.glsl`,
  onFirstRender(controls) {
    const upd = () => {
      controls.setUniform3fv("u_value", [
        Math.random() * 4 - 2,
        Math.random() * 4 - 2,
        Math.random() * 4 - 2,
      ]);
    };
    setInterval(upd, 500);
    upd();
  },
});

</script><!--kg-card-end: html--><h1 id="get-inspired-by-shadertoy">Get inspired by Shadertoy</h1><p>To uncover awesome approaches of using fragment shaders - visit <a href="https://www.shadertoy.com/">Shadertoy</a> - a playground and collection of interactive fragment shaders.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://colonel.shnyra.com/content/images/2022/05/image-1.png" class="kg-image" alt="WebGL shader renderer for awesome animations" loading="lazy" width="2000" height="1163" srcset="https://colonel.shnyra.com/content/images/size/w600/2022/05/image-1.png 600w, https://colonel.shnyra.com/content/images/size/w1000/2022/05/image-1.png 1000w, https://colonel.shnyra.com/content/images/size/w1600/2022/05/image-1.png 1600w, https://colonel.shnyra.com/content/images/size/w2400/2022/05/image-1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Shadertoy homepage</figcaption></figure><h3 id="note-fragment-shaders-are-not-efficient-to-render-everything">NOTE: fragment shaders are not efficient to render everything.</h3><p>The best rendering performance (FPS) for complex scenes will be a combination of dozens different fragment and vertex shaders applied to different rendering objects, scenes, with properly optimized 3D models (exported from other tools like Blender) and other stuff.</p><h3 id="once-you-are-good-with-fragment-shaders-i-recommend-you-to-go-further-and-learn-libraries-like-threejs">Once you are good with fragment shaders, I recommend you to go further and learn libraries like THREE.Js</h3><ul><li>You can play online with their editor at here - <a href="https://threejs.org/editor/">https://threejs.org/editor/</a></li></ul><p>I hope now you will find an opportunity to integrate WebGL shaders into your apps and make the web even more beautiful and interactive place.</p><p></p><!--kg-card-begin: html--><script type="module">
// highlight syntax for glsl
import glsl from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.5.0/build/es/languages/glsl.min.js';
hljs.registerLanguage('glsl', glsl);
</script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Make enum and literal compatible]]></title><description><![CDATA[<p>In TypeScript, enum can be represented <a href="https://www.typescriptlang.org/docs/handbook/enums.html">in multiple forms</a>:</p><ul><li>Numeric enums (&quot;default&quot;)</li><li>String enums (where key represents a literal string)</li><li>Heterogeneous enums (mix of numbers and strings, some of them are computed in runtime)</li></ul><p>Among these multiple forms I believe <strong>string enums</strong> are the most used in web</p>]]></description><link>https://colonel.shnyra.com/make-enum-and-literal-compatible/</link><guid isPermaLink="false">626ccaea056828000127a8e6</guid><category><![CDATA[utils]]></category><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Thu, 05 May 2022 06:45:56 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2022/05/2022-05-04-18-29-22.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2022/05/2022-05-04-18-29-22.png" alt="Make enum and literal compatible"><p>In TypeScript, enum can be represented <a href="https://www.typescriptlang.org/docs/handbook/enums.html">in multiple forms</a>:</p><ul><li>Numeric enums (&quot;default&quot;)</li><li>String enums (where key represents a literal string)</li><li>Heterogeneous enums (mix of numbers and strings, some of them are computed in runtime)</li></ul><p>Among these multiple forms I believe <strong>string enums</strong> are the most used in web apps development because of their human readability. Unfortunately, string enums are not &quot;default&quot; version of enums in TypeScript , so whenever you need to use enums in a human readable form you need to explicitly define values for every member of enum. This comes with an awkward incompatibility between enums as described below.</p><h2 id="issue-demo">Issue demo</h2><figure class="kg-card kg-code-card"><pre><code class="language-ts">enum ERole {
    USER = &apos;USER&apos;,
    ADMIN = &apos;ADMIN&apos;,
}

enum ERoleBeta {
    USER = &apos;USER&apos;,
    ADMIN = &apos;ADMIN&apos;,
}

// &#x1F926;&#x1F3FB;&#x200D;&#x2642;&#xFE0F; nightmare &#x1F926;&#x1F3FB;&#x200D;&#x2642;&#xFE0F;
const role: ERole.ADMIN = ERoleBeta.ADMIN;
// &#x274C; ^ Type &apos;ERoleBeta.ADMIN&apos; is not assignable to type &apos;ERole.ADMIN&apos;.(2322)
</code></pre><figcaption>different enums are incompatible</figcaption></figure><p>Code above in runtime (in JavaScript) will be equivalent to <code>const role = &apos;USER&apos;</code> and we know TypeScript marked variable <code>role</code> as of type <code>ERole.ADMIN</code> which value is literal string <code>&apos;USER&apos;</code>. Nevertheless, TypeScript compiler does not allow such assignment because <strong>different enums are not compatible even if their values are the same.</strong></p><figure class="kg-card kg-code-card"><pre><code class="language-ts">// &#x1F926;&#x1F3FB;&#x200D;&#x2642;&#xFE0F; nightmare &#x1F926;&#x1F3FB;&#x200D;&#x2642;&#xFE0F;
const role: ERole.ADMIN = &apos;ADMIN&apos;;
// &#x274C; ^ Type &apos;&quot;ADMIN&quot;&apos; is not assignable to type &apos;ERole.ADMIN&apos;.(2322)
</code></pre><figcaption>literal string is not assignable to string enum</figcaption></figure><p>Also, you cannot assign literal string to enum type.</p><p>All these restrictions make sense if we would use <strong>number enums, or mixed enums. </strong>But if we conventionally agree enums to be only <strong>string enums</strong> there should be a better way to write our code!</p><h2 id="solution-goals">Solution goals</h2><figure class="kg-card kg-code-card"><pre><code class="language-ts">let assignableToEnum: EBaseRole;
// &#x2705; compatible with self values
assignableToEnum = EBaseRole.USER;
// &#x2705; compatible with literals
assignableToEnum = &apos;USER&apos;;
// &#x2705; compatible with other enums for same values
assignableToEnum = EAdvancedRole.USER;

let assignableToLiteral: &apos;USER&apos;;
// &#x2705; compatible with all enums same values
assignableToLiteral = EBaseRole.USER;
assignableToLiteral = EAdvancedRole.USER;

// &#x2705; error raised at compile time
let expectError: EBaseRole = EAdvancedRole.VIEWER;
//  ^ Type &apos;&quot;VIEWER&quot;&apos; is not assignable to type &apos;EBaseRole&apos;.(2322)
</code></pre><figcaption>make enum and literals compatible</figcaption></figure><p>&#x261D;&#xFE0F; Also, we want to avoid dirty lies to our compiler like <code>as unknown as ERole.ADMIN</code></p><h2 id="solution-implementation">Solution implementation</h2><figure class="kg-card kg-code-card"><pre><code class="language-ts">const LiteralEnum = &lt;T extends string&gt;(...values: T[]): {
    [K in T]: K;
} =&gt; {
    const res = {} as any;
    values.forEach(v =&gt; {
        res[v] = v;
    })
    return res;
}</code></pre><figcaption>LiteralEnum util to build string enums</figcaption></figure><p>Create util function to build our enums from literals arguments:</p><ul><li>To make sure our <strong>keys equal to values</strong></li><li>To emphasise the way we create enums</li><li>To easily search through a project which enums were created by our util</li></ul><p>P.S. I decided to call the util with capitalised character to highlight its specialty. We try to workaround native language inconveniences.</p><h3 id="the-important-trick">The important trick!</h3><figure class="kg-card kg-code-card"><pre><code class="language-ts">export const EBaseRole = LiteralEnum(
    &apos;USER&apos;,
    &apos;ADMIN&apos;,
);
export type EBaseRole = keyof typeof EBaseRole;
</code></pre><figcaption>present enum as union of literals</figcaption></figure><p>Now, when we create our enum, we <strong>define type of same name as the const</strong> to leverage <a href="https://www.typescriptlang.org/docs/handbook/declaration-merging.html#handbook-content">declaration merging technique of TypeScript</a>.</p><p>Now, TypeScript compiler can smartly recognise context and use either type of enum or enum value &#x1F9E0; &#x1F389;</p><h2 id="full-working-demo">Full working demo</h2><pre><code class="language-ts">const LiteralEnum = &lt;T extends string&gt;(...values: T[]): {
    [K in T]: K;
} =&gt; {
    const res = {} as any;
    values.forEach(v =&gt; {
        res[v] = v;
    })
    return res;
}

export const EBaseRole = LiteralEnum(
    &apos;USER&apos;,
    &apos;ADMIN&apos;,
);
// &#x2705; easy to convert to union
export type EBaseRole = keyof typeof EBaseRole;

export const EAdvancedRole = LiteralEnum(
    &apos;USER&apos;,
    &apos;ADMIN&apos;,
    &apos;VIEWER&apos;,
);
export type EAdvancedRole = keyof typeof EAdvancedRole;

let assignableToEnum: EBaseRole;
// &#x2705; compatible with self values
assignableToEnum = EBaseRole.USER;
// &#x2705; compatible with literals
assignableToEnum = &apos;USER&apos;;
// &#x2705; compatible with other enums for same values
assignableToEnum = EAdvancedRole.USER;

let assignableToLiteral: &apos;USER&apos;;
// &#x2705; compatible with all enums same values
assignableToLiteral = EBaseRole.USER;
assignableToLiteral = EAdvancedRole.USER;

// &#x2705; error raised at compile time
let expectError: EBaseRole = EAdvancedRole.VIEWER;
//  ^ Type &apos;&quot;VIEWER&quot;&apos; is not assignable to type &apos;EBaseRole&apos;.(2322)
</code></pre><p>You can try this in TypeScript playground:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.typescriptlang.org/play?#code/KYOwrgtgBAogSgewDbCgbwFBW1AqgZXigF4oByA+MgGixwEEARAWQEkA5E8pt9mjAL4YMoSLEQoAQsAAuAQ3R1slOFwqE4-HFB4c1uvrSEZM2gPRmogPg3AZLuAeDcDf+4FgCQEJkgeD+oIAJYBzABYyEHIATqi2jq5uSlAAxgggAM4yUMHIwABc4mkAdAZc8GnS8rksHADc0RZQgDLkUAB6UAAqAJ4ADqhkBVKyciW8ZFBeCZ4IyXIJCb4gcgBGKFAyCAttHV3AfRxk2QAUAEwAzLu7AJSCwqY4cYnJqSiZaxucpGQGZBXmlrUt7eQARAa-AZDEZjCZTWbzRbLH6dCTrV47A5HU7GK5JKAAGS8MmAwTkSBg4GgpAAPI0oMAAB44kAAE2GSWCXhAPgAfNtspyAG74sDABKZRoAbQAusdMhdsEKANKDEBNEWZaUVAQkVmKbRom78rhoVXjKByEDNd44HlIPkJbIAMwQwRgchifm2XLVGu0OFCCSFXJFXC5puwAlO2lCMjAwXlXpVwiprTtyS1sEk42AcK4WJxeIJRO20XUVFo2hepUMGGOFSqgFByCnjZoLJZXLm45JQsDeeIiSnx4KtlbJ1Pp0gAa2AzQQ1uhwAnA4SabSFS7PcT8XRMHotJ5IBiwFpQ8x2Nx+MJkDzxZUWhwJf6RavADVWDAAOqF8sVOMJqewDdbnd7tJcKO46TjIKwzuum5Gn+cKLigoKTD40xzMAjQICeED3Cmc4wRg1axAgECtHIMheMhUAAO7Yn4UBzkgk7mpaGDjAhSEoKh6H5Fh84oNkKiVpYNZxIRxGkfMlEyNRSCHtmCRMWCiEQihaFEmoF78VAgkEURJFkeJ1GjH4uIUkSwy2sENFyBAqAMfycksYp7EqaQEG-rucK8RosGyIa8msUpmZHkgmQFpo6macJOliVRhpIEgxmQAylnWbytnMeCyGoQF2acYOOR8XZ6VsQgWX4vkP5QW5eWecIeG4qk5l4kMu6GiuhFeJCXhWRgcEUt2wAxDIMDBPVmG5fMznldulU8Q+z7wOp9RNP2ZC-LNL5wICgzDCAow+fZZFQqBMIwFxcJbHshwnMI101RYd33Q9D1QLSXi0lA45gFAw47eRAD8uGWB9sRGvhIDNr28XQG2HbyjOUlZviwx6QshlQIZSCtFAM44oRSDEag8OBbJPXtl48QAGKpBA6GZAABgAJGgawCLT6kNCTMOUwRNNQL8Ki-FAAA+vMAsIpMU1THHPGpGDiyAXPU05WQ8flQA"><div class="kg-bookmark-content"><div class="kg-bookmark-title">TS Playground - An online editor for exploring TypeScript and JavaScript</div><div class="kg-bookmark-description">The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.typescriptlang.org/icons/icon-512x512.png?v=8944a05a8b601855de116c8a56d3b3ae" alt="Make enum and literal compatible"><span class="kg-bookmark-author">An online editor for exploring TypeScript and JavaScript</span></div></div><div class="kg-bookmark-thumbnail"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALgAAAAmCAYAAAB3X1H0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAApYSURBVHgB7VwJsBXFFb1PFCSIBHGJRg0hkagJoayoX4KGUEncIJRJKospU4SkTEXLfS+13MpCccMFRcUCF9xwKUXFHdFyQ6RcUBE3QPTrB76CiAIC4zl2j++++2d7/81Xa/6cqvPndU9335meO9237+35EuSHqQLgeFCQH34rJUo0gA2kRIkCo1TwEoVGqeAlCo1SwUsUGhtKiRLfccDZsCkOfwC3A7uCi8FHK5XKorS6pYLnADyArXHYCXwenf6JlMgF6Ffq57HgKWBPc/oI8LK0Ngqj4OgMvtk3gX3Mqc/BYVC8QOoA2vspDrdEnJqMti5W5f6NwwRxfbkE6X1w/gUpkQeowP+XBlCkEbwbOATcPOLcn8A7pT4cCf4qIn9G+APKvBEOp0u1H7cAzwL/KCUaAvp2EA7/NdnN4OviBrH1EXU2w+Dykc7rLCbKoVKHgnubb/8MRbt4amwsJfLAIeBGKj0FPBoK3Izn00PcgMZnxUHlQHHPqy+4o26ksyh4Ezpie3TOuxnL7wP+OK0Q2luFds/BzzHibMT54JlSIg/8zqTHULn5A8eVOKxE33MknwNu5cussI0U2U2oF3ubgP/IUgmdVhE3elR81oqk8ujs8TgMFvdSNCH9pJTIA1ub9OvSDhRZwZeBLSrNPTK9MtTbFfyNSr+WVgFKPQd8CFwiJfJCxaTXSjtQZBOFXpOHwH/59ABwL/DelHqHS9WuXg6+AjbFCgmCbcTNECHe91NoXHn2+a/FvUhbeFmLvZxHUHetKkszSduhC3B+DfK74/cI8Ac+7+4IORt4GbuJW3hTLmc1joT0IX8mKfCzGRfanKE4ojK9FHwRnI421qXU39HXZR/RZma/LAQfjhoMIu5Xoz/OrzF5vUz5LijTXzcpQX74VncTomxPcImquxAcYtp7IKWNbcEPVfk7wFNMGxeYOreb83+JabsXOApsCeJB2YNVnafVubXgIPYJuExXMnI2Af8Hzk+Q8wl4deAWaHF9sRP4bEIbc8EuEfU2BPcF5yTUXQ3eE9Qqo73fPLCqyCYKR5uZUmu7DQ2cfzsOw6S6YCEulRy8IpD5MxxuByeCWyYUpezeuqo5z3ZuEzdyRcn5IQ53gVeJ8yjEgQvig8EZqDMgoh26W9l3TQlt9Iiox7wrwGngLxLqMmYxHJyOOn/WTUjOKJqCW7vtC/A6lWbHHpRQ/xD1+w3wGWnrBqwLeIB8Qeii/L09Bb4nzjRZKtkeLiN6m8fI4XXeKm29Dwx0MfD0PGjNgp3BG1F3M9MOA1c6csj1DJX2fnBewrUyBsAXRz8Hmlz0dMwCPzDl+UJOgMykl6Eh0C5rkXwQei1W5thmvYjq+CvBM8T7TYG/okPP1rbuVxWDgKPVQJV1hbd3pUFcIk6RQvCloyKejvbf8bKpEHuIC0trgVpRqHicfVp8m4+LG8lDhaarcrAqz4DHNeBpkLPay6G9egJ4qlRnpgE+fYxPN3k5IWaDjM62hhl+FhypyjCPwa0jVNYqcDJ4UljX3+ffwIvE2eUEXy6aS7yPfcUNQsRSqUU/cX2nwbrTpRq9/lDcmqPmwvrkxB6+vY1zbDPzIjhoa4O/60cjnhuv8mn/jYioP1mV+RjcweePDmqR2QbH737gOnP+NP+go+6ha+AWkGH6KVP3C3D/iHpbBrVrB+L4GBmVwNnoGq1gX39+pDl3paT3Pe3uB0298Qnl9/P3EmI9ONSUsega0U4fc9/v2zJ5mijhana9fPdwg1TdTOyoUVrJArd6H6bKP4BR501pHDSHdB+/LS5gETktcMYAP5d4TMH5aRH5HP312oEzw0SJlkHZk/y1hOBIGJoJraYK1y39JRkcXfdSad7DWXGFcQ00dZ7SWeCe0gHgCPmq5INHxD1QjmBjJR9w2psljYNtzBU3HRN7g9uC4XZLLni+73/zBZ0g+WCISd8cmgvtxF0x+QNN+jltUljgHEdP2uU/Udm/FOdCZV8xuBXa4FRuelPYJ+NRd0FEk3QHdlfpuSj3gSSD+qL7J+0laheo4FtJPtjUH7vk2GYPyQH+gdKzMM5nfU/cy3hO4BaBf1fF6XV5TBqEnyF+ZLJnSvvBkTcukGSjfq9IOuwM1Zd/0Fd0Y9J7dLJU1wD07NB2Pwrn+BKcgHJ6BrDbGhZKOl4y6W2kA9CZvujhZp0FKj3SK+Eu4CCVP67erbUx4ODR3eSlBlcSwJllTcy5bia9StJhZxK93uF+Gi48l5syNO/o1uOIfqSyi+19xl2nxmcJ8nNDp1FwHzl7WGXRp8wp8j9S7QeWSYt0ZgVfEhvp6ymNIe7Fs8qa5GuPK/Nx+IMzHjhanEeFnhjr0aCrkmZoOPPZyG2W++xt0q3SAehs32SONukDpHZb7NQsn0FlgXdDLjbZu0vHoNmks/iVdzbpNntucA/zQPq1uYika1K7Vjn7cbHOkfc9U7V/kO4B282k35YOQKdScL9Auk9l8WuR0PYLvQt54jmTpnuum2RHJWO52Sa9B+T8PK5w4EL0u6osmjSx6wP02wrwKGm7I5OKz304DORos4TfTu6ZIJ8L+uEm+2lpHHSz1mR0xq/qtftMKxsXZnl0sgZ9yLrHObVPCnzMwILuOLCfzpJsoILrve50+40J3H5pK4MvDX35eoPYo+JHcJzfARwYI+c1c00MvHBUp1tyuspnv16AdranwhlSPgNCegbhovQJqR+rpXZWYf/WyCx6qD4K7MjmiPxJOS0uqxdTqbyMw/Umm1+fPO6DLQMCFww6ALxcnG9YP/hMIzjk0BQ6W2qVj379qWj3cHAXKi04StwM9k9Vjos9RnbD+AU9IjMDF/g6ENwucBvFuAPyfHNNdDGv9HXPkNqFI3ch0ht1ojhbni5a2uw3gzoIxWsem+TWTLjvT6Xt+oCDyu5e3mFF3k34dSQzouyZRg7Lxu3xaHck05/vDb4YZMdwVVdHMr/aTZhw/93AKUF9oPv0YNPO3hnrLg+c0uu6J/nrrAfjYu7HomtMuYsS2i70bsIkXCu1rrRpGA2WSgcA7dI7wa0BHfqljw8g0SN0ecYq3DtE3/Y1Uj84ch4HmdakO4/5ku3jBEY7L/XlG8G5Eh8fKNwHDwxehBE0ekPiQuLzA7fHosmXmZjQJu3LGSptV/tvmfOLIuRxhqBLkl4bfq3PKVRvw6VCcJfeheJ2MIZgtDFcvNHlmPg/VzhlQw7t22vFbdyiTB0E4r3yu1H+ew3++4t5Ec0wkknF50tJW1zb6tzoxW2/Y8ONYkY+TZWLAzebUz7/WQ+jpdqsoTl1h7gF/ewEs9AG2yK3gNA8gzxuVaCHbD+puihpLs3ixpu87M57IGwEmmOE8AbJB0PR5gwpGPx0ywfP6C9Hw3dS9qA0IoseE/q8+YIsSvraKKJuxdelkrei7jKpE4H7TJCeKrbV3J426pBFi4QenHVe1vryP1t9C+CmKnF7Y74JWZy+2/WtqB9dW6SB7c9og9HQ5fINwM8gNdsEyn++WaLQKBW8RKFRKniJQqNU8BKFRqngJQqNLwEZWErzngOVuwAAAABJRU5ErkJggg==" alt="Make enum and literal compatible"></div></a></figure><h2 id="did-you-know">Did you know?</h2><p>You can easily convert enum to <em>union of literals with the help of <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#handbook-content">template literal types</a></em>:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">enum ERole {
    USER = &apos;USER&apos;,
    ADMIN = &apos;ADMIN&apos;,
}

const unionFromEnum: `${ERole}`;
//    ^ unionFromEnum: &quot;USER&quot; | &quot;ADMIN&quot;

// &#x2705; 
unionFromEnum = &apos;USER&apos;;
// &#x2705; 
unionFromEnum = ERole.USER;
</code></pre><figcaption>interpolate enum to union of literals</figcaption></figure><p>This is a quick-trick for some hot fixes.</p><p>For usual development, I would suggest to stick with the <code>LiteralEnum</code> util we created today.</p>]]></content:encoded></item><item><title><![CDATA[Extract route params type (via Template Literal Types)]]></title><description><![CDATA[<p>TypeScript 4.1 introduced an awesome concept of <code><a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">template literal types</a></code> which allows us to parse literal types and extract their parts as types for further manipulations.</p><p>The most prominent use case of this approach for frontend is to get types of parameters from URL path.</p><h2 id="extractrouteparams-type">ExtractRouteParams type</h2><figure class="kg-card kg-code-card"><pre><code class="language-ts">type ExtractRouteParams&</code></pre></figure>]]></description><link>https://colonel.shnyra.com/extract-route-params-type/</link><guid isPermaLink="false">625cf77c4346aa00015c94d9</guid><category><![CDATA[utils]]></category><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Mon, 18 Apr 2022 09:32:07 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2022/05/2022-05-05-09-47-50.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2022/05/2022-05-05-09-47-50.png" alt="Extract route params type (via Template Literal Types)"><p>TypeScript 4.1 introduced an awesome concept of <code><a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">template literal types</a></code> which allows us to parse literal types and extract their parts as types for further manipulations.</p><p>The most prominent use case of this approach for frontend is to get types of parameters from URL path.</p><h2 id="extractrouteparams-type">ExtractRouteParams type</h2><figure class="kg-card kg-code-card"><pre><code class="language-ts">type ExtractRouteParams&lt;T extends string&gt; = string extends T
  ? {}
  : // we have start/:oneId/restOfUrl
  T extends `${string}:${infer Param}/${infer Rest}`
  ? { [k in Param | keyof ExtractRouteParams&lt;Rest&gt;]: string }
  : // we only have start/:oneId
  T extends `${string}:${infer Param}`
  ? { [k in Param]: string }
  : // nothing found
    {};
</code></pre><figcaption>base generic type ExtractRouteParams</figcaption></figure><p>This is basic implementation of generic type to infer params from URL according to param pattern <code>:param</code> which works good for any string length due to recursion.</p><h2 id="use-cases">Use cases</h2><h3 id="create-custom-react-hook-which-will-return-existing-params-for-current-url">Create custom React hook which will return existing params for current URL</h3><figure class="kg-card kg-code-card"><pre><code class="language-ts">// &#x2705; typed params infered from literal string
const { friendId, photoId } = useTypedParams&lt;&apos;/friends/:friendId/photos/:photoId&apos;&gt;();
</code></pre><figcaption>custom React hook useTypedParams</figcaption></figure><p>Usually, you will run this hook inside a component of a specific page like <code>useTypedParams&lt;Routes.somePage&gt;();</code> so generic can infer params from its type argument = literal string from app-wide constant of all routes.</p><h3 id="replace-url-params-to-build-link-for-navigation">Replace URL params to build link for navigation</h3><figure class="kg-card kg-code-card"><pre><code class="language-tsx">// &#x2705; typesafe replace params via object with properties
const url = buildLink(&apos;/friends/:friendId/photos/:photoId&apos;, { friendId, photoId });

&lt;Link to={url}&gt;view photo&lt;/Link&gt;</code></pre><figcaption>generate link from path and params</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Narrow types use cases]]></title><description><![CDATA[<p>TypeScript union types represent multiple types at the same time.</p><p>It is convenient to group types into unions to explain they share something in common, they have common purpose (think of <code>redux reducer</code> which receives an action object of many different shapes).</p><p>For TypeScript there is an awesome technique called</p>]]></description><link>https://colonel.shnyra.com/narrow-types-use-cases/</link><guid isPermaLink="false">61ed4d8e8b2c150001b731b7</guid><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Mon, 11 Apr 2022 11:44:00 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2022/05/2022-05-05-09-49-57.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2022/05/2022-05-05-09-49-57.png" alt="Narrow types use cases"><p>TypeScript union types represent multiple types at the same time.</p><p>It is convenient to group types into unions to explain they share something in common, they have common purpose (think of <code>redux reducer</code> which receives an action object of many different shapes).</p><p>For TypeScript there is an awesome technique called <code>type narrowing</code> </p><blockquote>Type narrowing - technique that allows a compiler (and developer via IDE) to understand which exact type (shape) is present inside code block.</blockquote><h2 id="how-to-narrow-types">How to narrow types</h2><p>Let&apos;s say we have union of two other types like below:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">type SignInAction = {
  type: &apos;sign-in&apos;;
  email: string;
  password: string;
};

type VerifyEmailAction = {
  type: &apos;verify-email&apos;;
  email: string;
  otp_code: string;
};

type Action = SignInAction | VerifyEmailAction;</code></pre><figcaption>union type example</figcaption></figure><blockquote>Now, we can narrow type of union via <code>runtime check</code>:</blockquote><figure class="kg-card kg-code-card"><pre><code class="language-ts">const handleAnyAction = (action: Action) =&gt; {
  if (action.type === &apos;sign-in&apos;) {
    // &#x2705; fully typed
    return action.password;
  }
  if (action.type === &apos;verify-email&apos;) {
    // &#x2705; fully typed
    return action.otp_code;
  }
};</code></pre><figcaption>runtime check to narrow types</figcaption></figure><blockquote>Also, we can narrow type <code>in types context only</code> via <code>built-in utility type Extract</code>:</blockquote><figure class="kg-card kg-code-card"><pre><code class="language-ts">// &#x2705; structure is same as for VerifyEmailAction
type ActionWithOtpCode = Extract&lt;Action, { otp_code: string }&gt;;</code></pre><figcaption>type narrowing via Extract</figcaption></figure><blockquote>Also, we create a <code>generic guard</code> which will safely narrow types like that:</blockquote><figure class="kg-card kg-code-card"><pre><code class="language-ts">const guardForActionType = &lt;T extends Action[&apos;type&apos;]&gt;(
  action: Action,
  type: T
): action is Extract&lt;Action, { type: typeof type }&gt; =&gt; action.type === type;

const handleEmailVerification = (action: Action) =&gt; {
  // makes sure that action is of type VerifyEmailAction
  if (guardForActionType(action, &apos;verify-email&apos;)) {
    // &#x2705; fully typed
    return action.otp_code;
  }
  throw new Error(&apos;invalid action provided&apos;);
};
</code></pre><figcaption>type narrowing via generic type guard</figcaption></figure><p>Type narrowing via generic guard is especially useful for complex types (shapes) where you need to check multiple fields.</p>]]></content:encoded></item><item><title><![CDATA[Handle async errors without nesting try / catch]]></title><description><![CDATA[<p>Concept of <code>async / await</code> keywords made code much more readable and maintainable in comparison of <code>callback hell</code> but still we need to handle errors for async operations. </p><p>Usual <code>try / catch</code> blocks will make a code nested, create new closure making variables created inside inaccessible outside of <code>try / catch</code>.</p><p>To avoid</p>]]></description><link>https://colonel.shnyra.com/handle-async-errors-without-try-catch/</link><guid isPermaLink="false">61ece4d29e2e4b00019a01c4</guid><category><![CDATA[utils]]></category><dc:creator><![CDATA[Oleksii Shnyra 🇺🇦]]></dc:creator><pubDate>Mon, 11 Apr 2022 07:59:00 GMT</pubDate><media:content url="https://colonel.shnyra.com/content/images/2022/05/2022-05-05-09-50-49.png" medium="image"/><content:encoded><![CDATA[<img src="https://colonel.shnyra.com/content/images/2022/05/2022-05-05-09-50-49.png" alt="Handle async errors without nesting try / catch"><p>Concept of <code>async / await</code> keywords made code much more readable and maintainable in comparison of <code>callback hell</code> but still we need to handle errors for async operations. </p><p>Usual <code>try / catch</code> blocks will make a code nested, create new closure making variables created inside inaccessible outside of <code>try / catch</code>.</p><p>To avoid new closure and make code flat (all rows are on the same level of depth) for <code>async / await</code> we can use a simple util <code>calm</code></p><h2 id="solution-goals">Solution goals</h2><figure class="kg-card kg-code-card"><pre><code class="language-ts">const [value, error] = await calm(fetch(&apos;https://api.my.app/endpoint&apos;));
if (error || !value) {
  // handle case
  return;
}

operate(value);</code></pre><figcaption>usage sample</figcaption></figure><h2 id="implementation">Implementation</h2><figure class="kg-card kg-code-card"><pre><code class="language-ts">type ThenArg&lt;T&gt; = T extends PromiseLike&lt;infer U&gt; ? U : T;

const calm = async &lt;P extends Promise&lt;any&gt;&gt;(p: P): Promise&lt;[ThenArg&lt;P&gt; | undefined, Error | undefined]&gt; =&gt; {
  try {
    const value = await p;
    return [value, undefined];
  } catch (error) {
    return [undefined, error];
  }
}</code></pre><figcaption>implementation details</figcaption></figure>]]></content:encoded></item></channel></rss>