<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hyeseong Kim</title>
    <description>The latest articles on DEV Community by Hyeseong Kim (@cometkim).</description>
    <link>https://dev.to/cometkim</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F89150%2Ffb5bee2c-4917-4154-b9ee-285bf58bdcab.jpg</url>
      <title>DEV Community: Hyeseong Kim</title>
      <link>https://dev.to/cometkim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cometkim"/>
    <language>en</language>
    <item>
      <title>Dealing with Unicode text, done right and better.</title>
      <dc:creator>Hyeseong Kim</dc:creator>
      <pubDate>Sun, 16 Jun 2024 02:59:00 +0000</pubDate>
      <link>https://dev.to/cometkim/dealing-with-unicode-string-done-right-and-better-2nei</link>
      <guid>https://dev.to/cometkim/dealing-with-unicode-string-done-right-and-better-2nei</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I created a library called "&lt;a href="https://github.com/cometkim/unicode-segmenter" rel="noopener noreferrer"&gt;unicode-segmenter&lt;/a&gt;" to handle Unicode grapheme clusters with good performance and reasonable bundle size. Check it out!&lt;/p&gt;

&lt;p&gt;This article is not just about promoting my library but also about the process of creating a high-quality library, including optimization and testing.&lt;/p&gt;

&lt;p&gt;Updated: &lt;strong&gt;unicode-segmenter is now &lt;a href="https://e18e.dev/" rel="noopener noreferrer"&gt;e18e&lt;/a&gt; recommendation!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Have you ever heard of the term "grapheme cluster"? If you're using non-English languages or supporting emoji in your service, you probably have.&lt;/p&gt;

&lt;p&gt;One key lesson from Unicode is that strings often require more space than they appear to.&lt;/p&gt;

&lt;p&gt;If we used UCS-4 (or UTF-32), it would be sufficient to represent every character ever invented, making it easy to count each character. However, this would waste a lot of space and isn't even enough.&lt;/p&gt;

&lt;p&gt;Therefore, most programming languages use more efficient encodings like UTF-8 or UTF-16. JavaScript uses UTF-16 to represent characters like "👋" as surrogate pairs (two characters). So in JavaScript, its &lt;code&gt;.length&lt;/code&gt; property is 2.&lt;/p&gt;

&lt;p&gt;Some characters can be even longer by using special characters called "joiners".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"🇰🇷" displays as 1, but has a &lt;code&gt;.length&lt;/code&gt; of 4&lt;/li&gt;
&lt;li&gt;"👩‍👩‍👦‍👦" displays as 1, but has a &lt;code&gt;.length&lt;/code&gt; of 11&lt;/li&gt;
&lt;li&gt;"अनुच्छेद" displays as 4, but has a &lt;code&gt;.length&lt;/code&gt; of 8&lt;/li&gt;
&lt;li&gt;Some &lt;a href="https://github.com/cometkim/unicode-segmenter/blob/657e31a7cdbaf64769528596d11e6df03e9ee1e7/test/grapheme.js#L103" rel="noopener noreferrer"&gt;complex characters&lt;/a&gt; displays as 6, but has a &lt;code&gt;.length&lt;/code&gt; of 75! 🤯&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A unit internally expressed as multiple characters but logically treated as one character is called a "Grapheme Cluster."&lt;/p&gt;

&lt;p&gt;Unicode® standardizes how to handle these grapheme clusters in &lt;a href="https://dev.toUnicode%C2%AE%20Standard%20Annex%20#29"&gt;UAX#29&lt;/a&gt;, the "Unicode Segmentation" rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Handling graphemes is crucial when creating custom inputs, text editors, or code analyzers that count characters.&lt;/p&gt;

&lt;p&gt;There is the Web's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter" rel="noopener noreferrer"&gt;&lt;code&gt;Intl.Segmenter&lt;/code&gt;&lt;/a&gt; API that supports the Unicode Segmentation standard. It's available not only in Web browsers like Chrome and Safari, but also in JS runtimes like Node.js, Deno, and Bun.&lt;/p&gt;

&lt;p&gt;However, it's &lt;a href="https://caniuse.com/mdn-javascript_builtins_intl_segmenter" rel="noopener noreferrer"&gt;so new&lt;/a&gt; that I couldn't use it for my company's app, &lt;a href="https://karrotmarket.com" rel="noopener noreferrer"&gt;Karrot&lt;/a&gt;. For more context, Karrot is a massive app with over 18M monthly active users in South Korea, so supporting users on older versions is essential. Given the update frequency of mobile users, versions like Chrome 87 and Safari 14.5 are still not sufficient.&lt;/p&gt;

&lt;p&gt;One day, my colleague was analyzing the bundle size of their production and reported an issue with our design system library as it seemed unexpectedly large.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzms9z03m1ohvy5qi6oar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzms9z03m1ohvy5qi6oar.png" alt="Bundle size of graphemer, stat size is 459.49 KB and parsed size is 89.08 KB" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was the "&lt;a href="https://www.npmjs.com/package/graphemer" rel="noopener noreferrer"&gt;graphemer&lt;/a&gt;" library inside, more than 95% of the size of the problematic input component, and is a large enough portion of the overall app that it's even visually noticeable.&lt;/p&gt;

&lt;p&gt;Today, graphemer is the most popular way to handle grapheme clusters in JavaScript. (20M weekly downloads on NPM !!)&lt;/p&gt;

&lt;p&gt;But graphemer is an old library and is no longer actively maintained. This may result in differences with the latest Unicode version (mostly in Hindi), and no ESM support.&lt;/p&gt;

&lt;p&gt;Custom implementations for Unicode are not trivial in bundle size because they include Unicode data. Also, graphemer generates &lt;a href="https://github.com/flmnt/graphemer/blob/72f61c1/src/Graphemer.ts#L107" rel="noopener noreferrer"&gt;tons of if-else statements&lt;/a&gt; for performance reasons. (spoiler: it's not performant)&lt;/p&gt;

&lt;p&gt;Well, isn't there a better alternative?&lt;/p&gt;

&lt;h2&gt;
  
  
  Can WebAssembly (and Rust) save us?
&lt;/h2&gt;

&lt;p&gt;Probably YES!&lt;/p&gt;

&lt;p&gt;I already knew that there was a good quality library &lt;a href="https://github.com/unicode-rs/unicode-segmentation" rel="noopener noreferrer"&gt;unicode-segmentation&lt;/a&gt; in Rust, and Rust has a great WebAssembly toolchain called &lt;a href="https://github.com/rustwasm/wasm-bindgen" rel="noopener noreferrer"&gt;wasm-bindgen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So I created a simple binding using wasm-bindgen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;unicode_segmentation&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UnicodeSegmentation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;wasm_bindgen&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[wasm_bindgen]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="nf"&gt;.graphemes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[wasm_bindgen]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="nf"&gt;.graphemes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code generates a WASM binary with JavaScript binding. It was surprisingly easy.&lt;/p&gt;

&lt;p&gt;After a few manual optimizations, I got a WASM binary in 52KB size. When combined with the bindings, it totals around 55KB. Also, WASM binaries compress very well, coming in at a total of 23KB when compressed by gzip!&lt;/p&gt;

&lt;p&gt;This is already smaller than the parsed size of the graphemer library, and since WASM supports streaming compilation, it appeared to be a better option to me.&lt;/p&gt;

&lt;p&gt;However, feedback from my colleagues wasn’t positive. The reason was that the size was still too large, and using WebAssembly was not yet feasible for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  RIIJS (Rewrite It In JavaScript)
&lt;/h2&gt;

&lt;p&gt;Rust people have a cultural idiom called &lt;a href="https://transitiontech.ca/random/RIIR" rel="noopener noreferrer"&gt;RIIR&lt;/a&gt; (Rewrite It In Rust).&lt;/p&gt;

&lt;p&gt;Although WebAssembly is known for its performance, its size can sometimes be unacceptable in mobile apps, or the performance might not meet expectations due to binding overhead.&lt;/p&gt;

&lt;p&gt;A high-quality JavaScript library is still valuable, so I decided to do the opposite: Rewrite It in JavaScript.&lt;/p&gt;

&lt;p&gt;First, I reviewed the Rust library’s license. It's dual-licensed under MIT and Apache 2.0, I chose MIT.&lt;/p&gt;

&lt;p&gt;Then I began by modifying its code generator, which was written in Python, to produce JavaScript instead of Rust. Since I’m not familiar with Python code, this was the most time-consuming task.&lt;/p&gt;

&lt;p&gt;By utilizing my knowledge of Rust and JavaScript, I ported the implementation of its &lt;a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html" rel="noopener noreferrer"&gt;&lt;code&gt;Iterator&lt;/code&gt;&lt;/a&gt; trait into JavaScript’s &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols" rel="noopener noreferrer"&gt;iteration protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since JavaScript doesn't have a pattern-matching like Rust, it could be hard to replicate the same logic. I used the &lt;a href="https://rescript-lang.org/" rel="noopener noreferrer"&gt;ReScript&lt;/a&gt; compiler to maintain the original logic as much as possible. It made me able to port it confidently. Specifically &lt;a href="https://github.com/unicode-rs/unicode-segmentation/blob/dce3a34/src/grapheme.rs#L264" rel="noopener noreferrer"&gt;&lt;code&gt;check_pair&lt;/code&gt; function&lt;/a&gt; can be converted into &lt;a href="https://rescript-lang.org/try?version=v11.1.0&amp;amp;code=C4TwDgpgBAxghsKBeAUFKAfKABOBnACgAYBKKAcQGEB9AQQDsQ1Md8CBGMq6ygJWay5CAJi41KAe3rAAThIA2A1oQDMY6gFEAHsAj0AJkqEEALOu26DEfdQAKASxjAJAcxlwwAC0dG2AVnUAGV9CADYggDEQggB2IIA1aIAOBIAVaIBOdVsZCEgDaPZSChpeCBd7KTh5agBJA0cECRlCzhLqAGUwOBh7ehcAWTgZAGtC0Xb09EE2djV2xOnlDjN2gC0AdQApFBR5CER7PAAhCQBXA2GQZCgCeGBjiAAzZogAGlgEWifdGQ+ZI6SC7AD4QAC2EgAVvYPn0YAAjMhIAB8UAA3sw8AB3ezAGCeW73R4vXIfe7fX5kDFLAjcPgfbiBCJI1FPap4aAAek5FGOKiUtPEUlkCg+1BZUFkZy5PPIxxMArpvDFEqlMt5CppjIiKuQqLVUG5GoF1AZQukcnkqpk0sNsuOfhNZp4vGttqNcsdNNN7SZbvVnsVNECzsCErZ8g5dt5oSD1BDC3D7IDx1jWuDofiScjKbTLEF8czqWzUY9qbjgXizqzeqgEdL9piFarkxLKab6eoLe4NZRdeT0blHfzPedxdr9fbzdSY7bg+OSTjM9bE4HZcX3udFj0+jnZYyTvW2z39oPm-aXR6fUGwxGJ95GTgcZyeR3ur7k-nGXhKG5WAIPrcAwIBkPYTxQHC8KrjmX4wLcEIAG7WJKEhQPCEDyBIWIobAEhgt0ThQJhFQwCQcabFsW46Du1h2I4zhuB43ikbWACE4JQvY87sOwcZlBUVQ1PU+iNM4fztPxlT0NUdQNPczQSgCeBAtIAC0yIQvoBATEgSBQEQ3HCAyxxzIe4psZBsHGRkNnMAAvigdlAA" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After some test-fix loops, I got the first working version and the result was way better than expected. It was &lt;a href="https://github.com/cometkim/unicode-segmenter/tree/ac4c9ba8e55144233c43be97c50cc417f0388d7d#benchmark" rel="noopener noreferrer"&gt;3x smaller in size and 2.5x faster&lt;/a&gt; than the graphemer 😲&lt;/p&gt;

&lt;p&gt;(It was the initial result and even better now!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Reducing bundle size
&lt;/h2&gt;

&lt;p&gt;The major win of the library comes from choosing the simplest structure to store the Unicode data table. This wins in both size and performance. Not only that but there are also various techniques and efforts to achieve an even smaller size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using JavaScript generator
&lt;/h3&gt;

&lt;p&gt;JavaScript has a syntax for stateful logic known as "&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator" rel="noopener noreferrer"&gt;Generator&lt;/a&gt;". Generator is highly expressive, making them 40% smaller (1682 bytes into 1011 bytes) than an equivalent class with the same functionality!&lt;/p&gt;

&lt;p&gt;You can see both &lt;a href="https://github.com/cometkim/unicode-segmenter/blob/ac4c9ba8e55144233c43be97c50cc417f0388d7d/src/grapheme-class.js" rel="noopener noreferrer"&gt;class version&lt;/a&gt; and &lt;a href="https://github.com/cometkim/unicode-segmenter/blob/ac4c9ba8e55144233c43be97c50cc417f0388d7d/src/grapheme.js" rel="noopener noreferrer"&gt;generator version&lt;/a&gt; in old commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Considering compression
&lt;/h3&gt;

&lt;p&gt;JavaScript code is mostly transmitted with compression like gzip or brotli (zstd soon).&lt;/p&gt;

&lt;p&gt;The minified graphemer library is 89.08 KB, but after gzip compression, it reduces to 13.15 KB. This is because the library size is significantly inflated by a bunch of if-else statements, and compression algorithms excel at such repetitive stuff.&lt;/p&gt;

&lt;p&gt;unicode-segmenter aims to be small even before compression, but it also carefully considers the size after compression.&lt;/p&gt;

&lt;p&gt;For instance, when dealing with character data like &lt;code&gt;“\u{1F680}”&lt;/code&gt;, a minifier like Terser tries to unescape it to &lt;code&gt;"🚀"&lt;/code&gt;. Unescaping can reduce code size since escape sequences require additional characters. However, in the case of data tables with a significant number of escaped characters, gzip compression is highly effective, making it better not to unescape them. The results are summarized in this &lt;a href="https://github.com/cometkim/unicode-segmenter/pull/11" rel="noopener noreferrer"&gt;pull request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Afterward, It was switched to use int32 codepoint directly for performance reasons. And it compresses better.&lt;/p&gt;

&lt;p&gt;Even further, I compressed the number strings to Base36 (0~9, a~z), which is easily reversible with &lt;code&gt;toString&lt;/code&gt;/&lt;code&gt;parseInt&lt;/code&gt;, and still benefits from additional compression from gzip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;initUnicodeRangeBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bufferSize&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,9,a,,b,1,d,,e,h,3j,w,4p,,4t,, ...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// encoded Unicode ranges&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initUnicodeRangeBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This technique is inspired by &lt;a href="https://github.com/marijnh" rel="noopener noreferrer"&gt;Marijin&lt;/a&gt;'s code, the author of &lt;a href="https://codemirror.net/" rel="noopener noreferrer"&gt;CodeMirror&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling tree-shaking
&lt;/h3&gt;

&lt;p&gt;JavaScript production apps use bundlers. These tools can exclude unused modules from the final bundle or completely remove unused code through static analysis.&lt;/p&gt;

&lt;p&gt;The one goal of unicode-segmenter is to provide a complete polyfill for &lt;code&gt;Intl.Segmenter&lt;/code&gt;. However, if only grapheme segmentation is needed, it adopts a well-considered modular structure to ensure that unnecessary parts are not included.&lt;/p&gt;

&lt;p&gt;The exposed APIs have independent entries based on their respective topics.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;unicode-segmenter/grapheme&lt;/code&gt; (5.3 KB): Provides text segmentation by extended grapheme cluster&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unicode-segmenter/intl-polyfill&lt;/code&gt; (5.7 KB): Provide &lt;code&gt;Intl.Segmenter&lt;/code&gt; (currently grapheme only) polyfill&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unicode-segmenter/emoji&lt;/code&gt; (825 B): Provides emoji matchers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unicode-segmenter/general&lt;/code&gt; (4.3 KB): Provides alphanumeric matchers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unicode-segmenter/utils&lt;/code&gt; (353 B): Provide utilities for UTF-16 and Unicode code points.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Optimizing performance
&lt;/h2&gt;

&lt;p&gt;Thanks to the unicode-rs' excellent implementation I referenced, it was already 2.5x better than graphemer in its first version. It's mostly a hand-written binary search in a compressed Unicode data table. It's simple but very efficient.&lt;/p&gt;

&lt;p&gt;However, for even better results, it is important to fully consider the characteristics of the language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leverage the compiler
&lt;/h3&gt;

&lt;p&gt;As mentioned above, the ReScript compiler was very helpful in the initial porting process.&lt;/p&gt;

&lt;p&gt;I ended up &lt;a href="https://github.com/cometkim/unicode-segmenter/pull/33" rel="noopener noreferrer"&gt;rewriting it by hand&lt;/a&gt; for the best performance, but until then it allowed me to focus on other areas first, as it always ensures accuracy and sufficient efficiency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the optimal data type
&lt;/h3&gt;

&lt;p&gt;JavaScript engines also have a compilation process called JIT(Just-In-Time compilation). One well-known thing is that they are very efficient when dealing with 32-bit integers (aka SMI; small integers).&lt;/p&gt;

&lt;p&gt;A Unicode data is a list of ranges of Unicode code points, which are 32-bit integers. Therefore, I was able to make all operations internally based on SMI.&lt;/p&gt;

&lt;p&gt;Unlike Rust, where the compiler checks data types, in JavaScript, this responsibility falls entirely on the developer. I managed to do it with careful attention.&lt;/p&gt;

&lt;p&gt;As a result, performance more than &lt;strong&gt;doubled&lt;/strong&gt; in the &lt;a href="https://github.com/cometkim/unicode-segmenter/releases/tag/unicode-segmenter%400.2.0" rel="noopener noreferrer"&gt;second release&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explicit bindings
&lt;/h3&gt;

&lt;p&gt;When using generators or regular functions instead of classes, it is natural to implicitly capture external variables through "&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures" rel="noopener noreferrer"&gt;closures&lt;/a&gt;". However, implicit bindings can negatively impact optimization and garbage collection.&lt;/p&gt;

&lt;p&gt;To avoid this, simply hoisting functions resulted in an additional 10% improvement. (See &lt;a href="https://github.com/cometkim/unicode-segmenter/commit/7590438979dddd0ed5a073ead6b9f74d816d27e3" rel="noopener noreferrer"&gt;the change&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoiding copying and constructing structs
&lt;/h3&gt;

&lt;p&gt;When dealing with more than one piece of data, there’s a desire to handle it as a structure.&lt;/p&gt;

&lt;p&gt;However, JavaScript lacks efficient records/tuples, and both &lt;code&gt;Object&lt;/code&gt; and &lt;code&gt;Array&lt;/code&gt; always come with a cost. While this isn’t an issue for most applications, it can be critical in the micro-benchmarks of libraries.&lt;/p&gt;

&lt;p&gt;It is also very important to avoid implicit copying and unnecessary GCs. Pass references directly and perform the copy explicitly where it really needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// retained `cache` reference&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;searchGraphemeCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// update its values by explicit copying&lt;/span&gt;
      &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Continuous benchmarking and efforts!
&lt;/h3&gt;

&lt;p&gt;What helped getting faster mostly was the benchmark tool that was set up from the very beginning and the continuous measurements.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://github.com/evanwashere/mitata" rel="noopener noreferrer"&gt;mitata&lt;/a&gt; (pretiously &lt;a href="https://github.com/tinylibs/tinybench" rel="noopener noreferrer"&gt;tinybench&lt;/a&gt;). If you care about performance, I highly recommend you do it.&lt;/p&gt;

&lt;p&gt;However, poorly designed benchmarks can be as dangerous as having none. They might be completely invalidated by the compiler, too small to reflect real-world scenarios, or fail to identify scalability issues.&lt;/p&gt;

&lt;p&gt;To avoid these pitfalls, it is crucial to design benchmarks to be as close to the real world as possible. unicode-segmenter gets help from ChatGPT to construct tests that resemble real code!&lt;/p&gt;

&lt;p&gt;Benchmarks can yield very different results depending on the environment. Therefore, I run it not only in my local environment but also across various versions of Node.js and Bun, on various OS and CPU architectures, and I also configure them to run in browsers to verify performance in both Chrome and Safari. Through this process, I confirmed that the performance of &lt;code&gt;Intl.Segmenter&lt;/code&gt; has improved significantly in recent versions of Node.js, Chrome, and Safari. This is very exciting news!&lt;/p&gt;

&lt;p&gt;But efforts are still worth it since not everyone is using the latest version or ideal environment :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing strategy
&lt;/h2&gt;

&lt;p&gt;Testing is crucial. Since Unicode involves characters that might be unfamiliar, extensive testing was necessary.&lt;/p&gt;

&lt;p&gt;I set up initial test suites with &lt;a href="https://nodejs.org/api/test.html" rel="noopener noreferrer"&gt;Node.js test runner&lt;/a&gt; (It's great!) and manually wrote a few known cases, but it was never enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Property-based testing
&lt;/h3&gt;

&lt;p&gt;I tried a more precise method called "Property-based testing".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fast-check.dev/" rel="noopener noreferrer"&gt;fast-check&lt;/a&gt; is the most popular PBT tool in the JavaScript ecosystem. With fast-check, I can easily write test code that defines properties and verifies them based on automated inputs.&lt;/p&gt;

&lt;p&gt;Then, how is the verification done? The idea is to check if the results match those of &lt;code&gt;Intl.Segmenter&lt;/code&gt;, which is already available at the Node.js runtime!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphemeSegments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unicode string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Intl.Segmenter is available on Node.js!&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intlSegmenter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Segmenter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullUnicodeString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// assert through automated input&lt;/span&gt;
        &lt;span class="nf"&gt;assertObjectContaining&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nf"&gt;graphemeSegments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
          &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;intlSegmenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Through PBT, I found and fixed numerous bugs in the initial implementation. The first bug PBT identified was an empty string (&lt;code&gt;""&lt;/code&gt;). 😅 This is a surprisingly common mistake.&lt;/p&gt;

&lt;p&gt;I completely switched my primary testing method to PBT and set up additional tests to debug the counterexamples found.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating test suites
&lt;/h3&gt;

&lt;p&gt;Unicode provides &lt;a href="https://www.unicode.org/Public/15.1.0/ucd/auxiliary/GraphemeBreakTest.html" rel="noopener noreferrer"&gt;official test data&lt;/a&gt;. I used the data to generate test suites and check for spec compliance.&lt;/p&gt;

&lt;p&gt;During this process, I discovered that the Rust library I referenced had not yet implemented the “GB9c” rule, so I &lt;a href="https://github.com/cometkim/unicode-segmenter/pull/29" rel="noopener noreferrer"&gt;implemented it myself&lt;/a&gt;. (It seems they &lt;a href="https://github.com/unicode-rs/unicode-segmentation/pull/134" rel="noopener noreferrer"&gt;implemented it recently&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Production testing
&lt;/h3&gt;

&lt;p&gt;I understand that despite all these efforts, it might still be insufficient. Reality can sometimes be more extreme than theory.&lt;/p&gt;

&lt;p&gt;I tried to create migration PRs directly to major dependents of graphemer. I found a few issues related to implementation, and many others related to TypeScript configuration, sourcemap setup, etc.&lt;/p&gt;

&lt;p&gt;By supporting both large and small-scale projects, I was able to update the library and gain confidence in its final quality.&lt;/p&gt;

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

&lt;p&gt;As a result, my app achieved a smaller bundle size and better performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzpj9kqqi5b361nd6lk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzpj9kqqi5b361nd6lk8.png" alt="Bundle size of unicode-segmenter/grapheme, stat size is 42.83 KB,  parsed size is 17.3 KB, gzipped size is 9.15 KB" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Updated: It's now only 6.7 KB (parsed) and 3.4 KB (gzipped) in v0.14.4&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I also learned that what it does is not that simple. This should be a temporary alternative to &lt;code&gt;Intl.Segmenter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I recommend to use &lt;code&gt;Intl.Segmenter&lt;/code&gt; where possible. The unicode-segmenter library might become another graphemer after 10 years. Who knows!&lt;/p&gt;

&lt;p&gt;But if you are in a special environment where &lt;code&gt;Intl.Segmenter&lt;/code&gt; is not available, try &lt;a href="https://github.com/cometkim/unicode-segmenter" rel="noopener noreferrer"&gt;unicode-segmenter&lt;/a&gt;. It provides good performance in a reasonable size.&lt;/p&gt;

&lt;p&gt;Especially if you build something on the React Native and the &lt;a href="https://hermesengine.dev/" rel="noopener noreferrer"&gt;Hermes&lt;/a&gt; engine. unicode-segmenter supports it &lt;a href="https://github.com/cometkim/unicode-segmenter/pull/47" rel="noopener noreferrer"&gt;pretty well&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Also, there are more coming shortly&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It will soon provide a full &lt;code&gt;Intl.Segmenter&lt;/code&gt; polyfill, including word/sentence support. (&lt;a href="https://github.com/cometkim/unicode-segmenter/issues/25" rel="noopener noreferrer"&gt;issue #25&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;It will support more advanced patterns that &lt;code&gt;Intl.Segmenter&lt;/code&gt; doesn't, like backward iteration. (&lt;a href="https://github.com/cometkim/unicode-segmenter/issues/26" rel="noopener noreferrer"&gt;issue #26&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Are you struggling with the quality of the library in another area? Consider taking some time to investigate. We still have many opportunities for further improvement in the JavaScript ecosystem.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>unicode</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>When and Where to use ReScript? The ReScript happy path</title>
      <dc:creator>Hyeseong Kim</dc:creator>
      <pubDate>Fri, 24 Nov 2023 16:35:20 +0000</pubDate>
      <link>https://dev.to/cometkim/when-and-where-to-use-rescript-the-rescript-happy-path-47ni</link>
      <guid>https://dev.to/cometkim/when-and-where-to-use-rescript-the-rescript-happy-path-47ni</guid>
      <description>&lt;p&gt;Disclaimer: This article is opinionated, and it's assuming you've already looked into ReScript a bit. I wrote this to defend my judgment on using or not using ReScript in some projects. If you are curious more, visit the &lt;a href="https://forum.rescript-lang.org/"&gt;ReScript Forum&lt;/a&gt; and see more opinions.&lt;/p&gt;




&lt;p&gt;Are you ever hesitant about adopting ReScript, or have you tried it and been frustrated? I will give you a realistic guide for adopting ReScript in projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's ReScript?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rescript-lang.org/"&gt;ReScript&lt;/a&gt; is an language, that compiles to JavaScript, providing a superior type system, fast compilation, and type-checking. It offers best-in-class JavaScript interoperability compared to many similar languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the difference from TypeScript?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rescript-lang.org/docs/manual/latest/introduction#difference-vs-typescript"&gt;According to its official docs&lt;/a&gt;, ReScript and TypeScript have pretty different approaches to code.&lt;/p&gt;

&lt;p&gt;TypeScript's design aims to express code that is already written in JS. In a sense, it goes beyond the scope of a type system. As a result, TypeScript's type system is more powerful and flexible than any other type systems. It has the power to express even messy and ambiguous models.&lt;/p&gt;

&lt;p&gt;ReScript, on the other hand, aims to support clean data models. It has first-class ADT(Algebraic Data Type) support, useful for modeling, and pattern matching for expressing declarative logic, all of those based on its sound type system.&lt;/p&gt;

&lt;p&gt;Let me demonstrate a simple state machine in ReScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Counter.res&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;: &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Paused&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Start&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Pause&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Resume&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increase&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrease&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Reset&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Paused&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;: &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;, &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Compiler understand what type you want here, without additional annotations&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;, &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Paused&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;, &lt;span class="nc"&gt;Start&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Resume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;, &lt;span class="nc"&gt;Increase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;: &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;, &lt;span class="nc"&gt;Decrease&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;: &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;, &lt;span class="nc"&gt;Pause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Paused&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;: &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;, &lt;span class="nc"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beautiful, right?&lt;/p&gt;

&lt;p&gt;And the compiler is super smart so you can immediately noticed actually some unhandled patterns there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You forgot to handle a possible case here, for example: 
  (Running({count: _, _}), Start | Resume)
| (Paused(_), Pause | Increase | Decrease)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in some cases, a sound type system may also imply sacrificing some expressiveness to the model. In return for the features, you can't express everything you can express in JavaScript/TypeScript with ReScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  When ReScript hurts
&lt;/h2&gt;

&lt;p&gt;If you use &lt;code&gt;lodash/fp&lt;/code&gt; or &lt;a href="https://ramdajs.com/"&gt;Ramda.js&lt;/a&gt; to compose functions, or like using &lt;a href="https://github.com/gvergnaud/ts-pattern"&gt;ts-pattern&lt;/a&gt; to simulate pattern matching in TypeScript, you might also be interested in languages like ReScript.&lt;/p&gt;

&lt;p&gt;But adopting is often difficult.&lt;/p&gt;

&lt;p&gt;Because of the differences mentioned above, you may encounter challenges when trying to express your intentions, or you may feel overwhelmed when trying to bind frequently used external types.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rescript"&gt;&lt;code&gt;&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;beginPath&lt;/span&gt;: &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"beginPath"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;closePath&lt;/span&gt;: &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"closePath"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;: &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fill"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;stroke&lt;/span&gt;: &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stroke"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;clip&lt;/span&gt;: &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"clip"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;moveTo&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"moveTo"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;lineTo&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lineTo"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt;
&lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;quadraticCurveTo&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;cp1x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;cp1y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="s2"&gt;"quadraticCurveTo"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt;
&lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;bezierCurveTo&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;,
  ~&lt;span class="n"&gt;cp1x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;cp1y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;cp2x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;cp2y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bezierCurveTo"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt;
&lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;arcTo&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;x1&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y1&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;x2&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y2&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;r&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arcTo"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt;
&lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;,
  ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;r&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;startAngle&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;endAngle&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;,
  ~&lt;span class="n"&gt;anticw&lt;/span&gt;: &lt;span class="kt"&gt;bool&lt;/span&gt;,
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arc"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;rect&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;w&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;h&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rect"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;isPointInPath&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"isPointInPath"&lt;/span&gt;

&lt;span class="cm"&gt;/* Path2D */&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;path2d&lt;/span&gt;
&lt;span class="nd"&gt;@new&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;newPath2D&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;path2d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Path2D"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;fillPath2D&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="n"&gt;path2d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fill"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;strokePath2D&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="n"&gt;path2d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stroke"&lt;/span&gt;

&lt;span class="cm"&gt;/* Text */&lt;/span&gt;
&lt;span class="nd"&gt;@set&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"font"&lt;/span&gt;
&lt;span class="nd"&gt;@set&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;textAlign&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"textAlign"&lt;/span&gt;
&lt;span class="nd"&gt;@set&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;textBaseline&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"textBaseline"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt;
&lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;fillText&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="kt"&gt;string&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;maxWidth&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;?, &lt;span class="nd"&gt;@ignore&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="s2"&gt;"fillText"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt;
&lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;strokeText&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="kt"&gt;string&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;maxWidth&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;?, &lt;span class="nd"&gt;@ignore&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="s2"&gt;"strokeText"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;measureText&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;measureText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"measureText"&lt;/span&gt;
&lt;span class="nd"&gt;@get&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;: &lt;span class="n"&gt;measureText&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"width"&lt;/span&gt;

&lt;span class="cm"&gt;/* Rectangles */&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;fillRect&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;w&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;h&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fillRect"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;strokeRect&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;w&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;h&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"strokeRect"&lt;/span&gt;
&lt;span class="nd"&gt;@send&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;clearRect&lt;/span&gt;: &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;w&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;, ~&lt;span class="n"&gt;h&lt;/span&gt;: &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"clearRect"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is part of the &lt;a href="https://github.com/TheSpyder/rescript-webapi"&gt;rescript-webapi&lt;/a&gt; binding code to use the Canvas API.&lt;/p&gt;

&lt;p&gt;If your initial experience with ReScript involved dealing with a graphics library interface rather than your own application logic, you may have abandoned it quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The major concern
&lt;/h2&gt;

&lt;p&gt;You end up in a dilemma. Which is better: ReScript or TypeScript?&lt;/p&gt;

&lt;p&gt;You might consider the choice of language to be a significant concern; You might be concerned that opting for ReScript could lead to an irreversible decision, making it challenging for newcomers to understand the codebase or even hindering the hiring process.&lt;/p&gt;

&lt;p&gt;But for all your worries, language doesn't have as major an impact as you might think. What really matters is what you express in the language, &lt;strong&gt;your business&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your business is complicated, it will look complicated even if you express it in the most appropriate language, and if your business is niche, it will be difficult to hire new people even if you use the most popular language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the "clean room"
&lt;/h2&gt;

&lt;p&gt;When a business and the software represents it become complex, developers have a hard time keeping up with it.&lt;/p&gt;

&lt;p&gt;Even if TypeScript’s type system tolerates the situation, it doesn't help much. This is not because it is essentially a problem caused by language, but because the mixing different models and its expressions.&lt;/p&gt;

&lt;p&gt;You have to orchestrate models and dependencies well for deealing with growing complexity.&lt;/p&gt;

&lt;p&gt;Worth paying attention to the famous approach &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html"&gt;"Clean Architecture"&lt;/a&gt;. I'm not to say it's the answer, but it's worth noting that it isolates the business model and puts it to the "core" of the entire dependencies.&lt;/p&gt;

&lt;p&gt;In this approach, by definition, there are no external dependencies on the "core" module. Without worrying about the outside world, you can just model your business and build logic from scratch. This is one of the few places where you can adopt DSL(Domain Specific Language) with confidence.&lt;/p&gt;

&lt;p&gt;You may still use TypeScript here, but this is where ReScript really shines. In my experience, ReScript shows a few times better expressiveness than TypeScript when dealing with pure business logic.&lt;/p&gt;

&lt;p&gt;Let me show examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Entity_Organization.res&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;,
  &lt;span class="n"&gt;label&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;,
  &lt;span class="n"&gt;owner&lt;/span&gt;: &lt;span class="n"&gt;memberId&lt;/span&gt;,
  &lt;span class="n"&gt;members&lt;/span&gt;: &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;,
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Archived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Created&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;: &lt;span class="nn"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="n"&gt;by&lt;/span&gt;: &lt;span class="n"&gt;memberId&lt;/span&gt;, &lt;span class="n"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;, &lt;span class="n"&gt;label&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;MemberAdded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;: &lt;span class="nn"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="n"&gt;by&lt;/span&gt;: &lt;span class="kt"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &lt;span class="n"&gt;member&lt;/span&gt;: &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;MemberRemoved&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;: &lt;span class="nn"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="n"&gt;by&lt;/span&gt;: &lt;span class="kt"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &lt;span class="n"&gt;member&lt;/span&gt;: &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;MemberAlreadyJoined&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;: &lt;span class="kt"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &lt;span class="n"&gt;member&lt;/span&gt;: &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;CannotRemoveOwner&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;: &lt;span class="kt"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &lt;span class="n"&gt;ownerMember&lt;/span&gt;: &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;CannotRemoveSelf&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;: &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;CannotModifyArchived&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;: &lt;span class="kt"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;: &lt;span class="n"&gt;id&lt;/span&gt;,
  &lt;span class="n"&gt;seq&lt;/span&gt;: &lt;span class="kt"&gt;int&lt;/span&gt;,
  &lt;span class="n"&gt;events&lt;/span&gt;: &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;,
  &lt;span class="n"&gt;state&lt;/span&gt;: &lt;span class="kt"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;,
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;, ~&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;?, ~&lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;, &lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;,
  &lt;span class="n"&gt;seq&lt;/span&gt;,
  &lt;span class="n"&gt;events&lt;/span&gt;: &lt;span class="p"&gt;[]&lt;/span&gt;,
  &lt;span class="n"&gt;state&lt;/span&gt;,
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Logic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Abstract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Logic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Make&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logic&lt;/span&gt;: &lt;span class="nn"&gt;Logic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// bunch of patterns&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;: &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Archived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;, &lt;span class="nc"&gt;MemberAdded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CannotModifyArchived&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;

  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;: &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="p"&gt;})}&lt;/span&gt;, &lt;span class="nc"&gt;MemberAdded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MemberAlreadyJoined&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;, &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;

  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;: &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Acvite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;, &lt;span class="nc"&gt;MemberAdded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      ...&lt;span class="n"&gt;t&lt;/span&gt;,
      &lt;span class="n"&gt;events&lt;/span&gt;: &lt;span class="n"&gt;t&lt;/span&gt;.&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;,
      &lt;span class="n"&gt;state&lt;/span&gt;: &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        ...&lt;span class="n"&gt;state&lt;/span&gt;,
        &lt;span class="n"&gt;members&lt;/span&gt;: &lt;span class="n"&gt;state&lt;/span&gt;.&lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;,
      &lt;span class="p"&gt;}))&lt;/span&gt;,
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;addMember&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, ~&lt;span class="n"&gt;date&lt;/span&gt;, ~&lt;span class="n"&gt;by&lt;/span&gt;, ~&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MemberAdded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;,
    &lt;span class="n"&gt;by&lt;/span&gt;,
    &lt;span class="n"&gt;member&lt;/span&gt;,
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="n"&gt;logic&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Logic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;, &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Bonus, features as &lt;a href="https://rescript-lang.org/docs/manual/latest/function#labeled-arguments"&gt;labeled arguments&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system"&gt;HM type inference&lt;/a&gt; save you the hassle of declaring signatures of dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Service_Organization.res&lt;/span&gt;

&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;addMemberToOrganization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// Just name of dependencies, let the compiler work for you.&lt;/span&gt;
  ~&lt;span class="n"&gt;findMember&lt;/span&gt;,
  ~&lt;span class="n"&gt;findOrganization&lt;/span&gt;,
  ~&lt;span class="n"&gt;memberId&lt;/span&gt;,
  ~&lt;span class="n"&gt;organizationId&lt;/span&gt;,
  ~&lt;span class="n"&gt;by&lt;/span&gt;,
  ~&lt;span class="n"&gt;date&lt;/span&gt;,
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="nn"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all2&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;findMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;, &lt;span class="n"&gt;findOrganization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;, &lt;span class="nc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Organization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;~&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;.&lt;span class="nn"&gt;Member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;, ~&lt;span class="n"&gt;by&lt;/span&gt;, ~&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;,
      &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;joinToOrganization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;~&lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;organization&lt;/span&gt;.&lt;span class="n"&gt;id&lt;/span&gt;, ~&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;,
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;, &lt;span class="nc"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;"organization"&lt;/span&gt;: &lt;span class="n"&gt;organization&lt;/span&gt;, &lt;span class="s2"&gt;"member"&lt;/span&gt;: &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;organizationResult&lt;/span&gt;, &lt;span class="n"&gt;memberResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;#AggregatedError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="s2"&gt;"member"&lt;/span&gt;: &lt;span class="n"&gt;memberResult&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;someError&lt;/span&gt;,
          &lt;span class="s2"&gt;"organization"&lt;/span&gt;: &lt;span class="n"&gt;organizationResult&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;someError&lt;/span&gt;,
        &lt;span class="p"&gt;})&lt;/span&gt;,
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;, &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nc"&gt;#InvalidParameter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s2"&gt;"member"&lt;/span&gt;: &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;.&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;,
        &lt;span class="s2"&gt;"organization"&lt;/span&gt;: &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;organization&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;organization&lt;/span&gt;.&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;,
      &lt;span class="p"&gt;})&lt;/span&gt;,
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;exception&lt;/span&gt; &lt;span class="nn"&gt;Js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Exn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;exn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;#IOError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;"exn"&lt;/span&gt;: &lt;span class="kt"&gt;exn&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See? There is no need to declare types such as &lt;code&gt;interface AddMemberToOrganizationDeps { ... }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is very convinient on React components too&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rescript"&gt;&lt;code&gt;&lt;span class="nd"&gt;@genType&lt;/span&gt;
&lt;span class="nd"&gt;@react.component&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;~&lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... useSomthing&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By just adding labels &lt;code&gt;findOrganization&lt;/code&gt; or &lt;code&gt;onClick&lt;/code&gt; and using it, the ReScript compiler infers actual types from context.&lt;/p&gt;

&lt;p&gt;Then add &lt;a href="https://rescript-lang.org/docs/gentype/latest/introduction"&gt;&lt;code&gt;@genType&lt;/code&gt;&lt;/a&gt;. The ReScript compiler will generate correct TypeScript definitions for you. So you can use the &lt;code&gt;Service_Organization&lt;/code&gt; module written in ReScript on your GraphQL server written in TypeScript, using &lt;a href="https://pothos-graphql.dev"&gt;Pothos&lt;/a&gt;, &lt;a href="https://www.prisma.io"&gt;Prisma&lt;/a&gt;, whatever you want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;OrganizationService&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;core/Service_Organization.gen.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutationFields&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;addMemberToOrganization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AddMemberToOrganizationOutputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AddMemberToOrganizationInputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;authScopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;activeMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;OrganizationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMemberToOrganization&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;findMember&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findMember&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;findOrganization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findOrganization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentMember&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// handle errors&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// ... other works&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publishAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very basic &lt;a href="https://en.wikipedia.org/wiki/Inversion_of_control"&gt;inversion of control&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's nice that you don't need additonal decorators or container like &lt;a href="https://nestjs.com/"&gt;NestJS&lt;/a&gt; for dependency injection. Everything is plain function and it can be checked at compile time by ReScript and TypeScript.&lt;/p&gt;

&lt;p&gt;These advantages can be applied regardless of whether it is backend or frontend.&lt;/p&gt;

&lt;p&gt;There are several architectural patterns for UI applications. It's not an exact classification, but I categorize it into Elm style (aka the &lt;a href="https://guide.elm-lang.org/architecture/"&gt;Elm Architecture&lt;/a&gt;) and Cycle.js (aka Reactive programming or Data-flow graph) style.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://elm-lang.org/"&gt;Elm&lt;/a&gt; is actually a language very similar to ReScript (as the same &lt;a href="https://en.wikipedia.org/wiki/ML_(programming_language)"&gt;ML family&lt;/a&gt;), and the background to its success was that it provided this architectural pattern from the very beginning. Elm sandboxes the core UI logic, and interaction with the system (e.g. DOM APIs) is provided through the Elm runtime.&lt;/p&gt;

&lt;p&gt;You can choose a similar architectural pattern for your UI application. Use ReScript at its core, and use TypeScript to build your own Elm runtime.&lt;/p&gt;

&lt;p&gt;Or you may be more familiar with &lt;a href="https://redux.js.org/introduction/getting-started/"&gt;Redux&lt;/a&gt;. In Redux, all dirty works is handled by middleware. Then the rest of the UI can be written in ReScript without any inconvenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a general-purpose language
&lt;/h2&gt;

&lt;p&gt;Counterintuitively, languages are more convenient when they represent a limited surface of a model. Although you may fear that it will be difficult to read and difficult to understand if the language varies. However, &lt;a href="https://toml.io"&gt;TOML&lt;/a&gt; code is actually more concise and easier to understand than TypeScript code for the "configuration" model&lt;/p&gt;

&lt;p&gt;Another example of how convenient a language can be is &lt;a href="https://mint-lang.com/"&gt;Mint&lt;/a&gt;. It represents declarative, component-based UI, styling, and state management in a very clean way.&lt;/p&gt;

&lt;p&gt;Some of the special-purpose languages are not that simple, some of them restrict which interpreter or runtime (VM) you can use. From the perspective of an expression and interpretation system, frameworks like &lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt; and &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt; are also able to be categorized as languages.&lt;/p&gt;

&lt;p&gt;Here, the advantages of TypeScript and ReScript are similar; "bring your own interpreter". Both have no restrictions other than specifying JavaScript as the target. And the &lt;a href="https://tinyclouds.org/javascript_containers"&gt;JavaScript is universal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;They can also be used for special purposes, like DSL by deliberately limiting their expressiveness.&lt;/p&gt;

&lt;p&gt;ReScript can be used to express &lt;a href="https://rescript-lang.org/docs/react/latest/components-and-props"&gt;React components&lt;/a&gt; with formatted type signature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gvergnaud/ts-pattern"&gt;ts-pattern&lt;/a&gt; can be called a (kinda) DSL and its interpreter for declarative logic (ReScript is obviously better for this). And so on.&lt;/p&gt;

&lt;p&gt;They may not be the languages with the best representation of the model, but they can provide a level of representation that the developer is satisfied with.&lt;/p&gt;

&lt;p&gt;In my experience, the closer to the core, the more powerful ReScript is, and the further out, the more convenient TypeScript is. But why don't pick both?&lt;/p&gt;

&lt;h2&gt;
  
  
  When need quick and dirty
&lt;/h2&gt;

&lt;p&gt;Not all software has this kind of code architecture. Code architectures are difficult to maintain. If critical parts are not properly isolated, they can easily become messy.&lt;/p&gt;

&lt;p&gt;However, actually, in most cases, messily wired monolithic code is good enough. In these codebases, flexible languages win.&lt;/p&gt;

&lt;p&gt;Is ReScript flexible enough? Perhaps not (yet)&lt;/p&gt;

&lt;p&gt;But even lack flexibility, it can be sufficiently productive with a proper "framework" (it's like your own interpreter) for its purpose.&lt;/p&gt;

&lt;p&gt;IMO this is one of the missing pieces in the current ReScript ecosystem. Something like ReScript-first full-stack webapp framework (binding to Next.js is not sufficient)&lt;/p&gt;

&lt;p&gt;However, the ReScript language and ecosystem are still growing steadily, and I'm sure we will get there in the near future.&lt;/p&gt;

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

&lt;p&gt;You don't need to use ReScript for the whole of your project. The same goes for TypeScript. Where the language makes more sense, use it. The use of multiple languages in any complex project is more common than you might think.&lt;/p&gt;

&lt;p&gt;Before embracing a language like ReScript, first evaluate your domain model and the appropriate architecture. Try using ReScript in spaces that have as few dependencies as possible and deal with pure business logic. &lt;/p&gt;

&lt;p&gt;Not necessarily, but it makes you happier.&lt;/p&gt;

&lt;p&gt;As you become more comfortable and confident using ReScript, you can try ReScript with other external models as well. Help ReScript cover more by sharing it with the community.&lt;/p&gt;

</description>
      <category>rescript</category>
      <category>typescript</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Compressing GraphQL Global Node ID</title>
      <dc:creator>Hyeseong Kim</dc:creator>
      <pubDate>Mon, 10 Apr 2023 07:40:31 +0000</pubDate>
      <link>https://dev.to/cometkim/compressing-graphql-global-node-id-543c</link>
      <guid>https://dev.to/cometkim/compressing-graphql-global-node-id-543c</guid>
      <description>&lt;p&gt;You may be familiar with &lt;strong&gt;&lt;a href="https://graphql.org/learn/global-object-identification/"&gt;Global Object Identification&lt;/a&gt;(GOI)&lt;/strong&gt;, especially if you've used &lt;a href="https://relay.dev/"&gt;Relay&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;GOI is one of best practices to build a good GraphQL API, makes possible to interact with clients more efficiently.&lt;/p&gt;

&lt;p&gt;It only requires to implement this interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Typical implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// root =&amp;gt; {}&lt;/span&gt;
    &lt;span class="c1"&gt;// args =&amp;gt; { id: string }&lt;/span&gt;
    &lt;span class="c1"&gt;// &lt;/span&gt;
    &lt;span class="c1"&gt;// You need a way to specifing which type of node you should load.&lt;/span&gt;
    &lt;span class="c1"&gt;// ... But you only have args.id here.&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;__resolveType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// node is the return type of the `query.node` resolver above.&lt;/span&gt;
    &lt;span class="c1"&gt;// How can you determine the typename?&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should implement resolvers for make GOI work, but as you can see, there is only &lt;code&gt;id&lt;/code&gt; argument in the whole context.&lt;/p&gt;

&lt;p&gt;Generally, to load an entity, you need at least the typename and id (unless you're using a Graph DB).&lt;/p&gt;

&lt;p&gt;So you need to find out the entity type with only the ID, but the database does not support this (again, unless you're using a Graph DB).&lt;/p&gt;

&lt;p&gt;Instead, you can convert all ids in the GraphQL application layer to include typenames.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base64(`${typename}:${id}`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the result, when client request with that ID, your API can determine its typename by extracting from ID back.&lt;/p&gt;

&lt;p&gt;This is very typical GOI implementaion, as same as of &lt;a href="https://github.com/graphql/graphql-relay-js/blob/10fab5b/src/node/node.ts#L87-L93"&gt;graphql-relay's one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, this implementation has several drawbacks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It will be much longer than the original ID.&lt;/li&gt;
&lt;li&gt;Using Base64 string is not URL-safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those drawbacks make it difficult to be used in permalink URLs (e.g. &lt;code&gt;/posts/{node.id}&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;But it's just an implementation, not a constraint by definition. How to make a better implementation?&lt;/p&gt;

&lt;h2&gt;
  
  
  Compressing it!
&lt;/h2&gt;

&lt;p&gt;Technically, the previous ID compression can be represented as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[typename, id] -&amp;gt; CSV(':') -&amp;gt; Base64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change it to a better compression algorithm. Here is my suggestion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[version, dict(version, typename), id] -&amp;gt; CBOR -&amp;gt; Base64URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dict(version, typename)&lt;/code&gt;: since the typenames are well-known strings, it can be compressed to a short integer using a dictionary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;version&lt;/code&gt;: Using ID for permalink URLs means that can be permanently exposed. You have to maintain all versions of dictionaries you make.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cbor.io/"&gt;CBOR (Concise Binary Object Representation)&lt;/a&gt; is a codec for small JSON object, similar to MessagePack but in the internet standard.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc4648#section-5"&gt;Base64 URL&lt;/a&gt; is an alternative to Base64, use only URL-safe character set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How well does it compress than previous one?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toGlobalId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql-relay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;encode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cbor-x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;typename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1234567&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;toCompressedGlobalIdV1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;typename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;typename&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql-relay toGlobalId :&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toGlobalId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toCompressedGlobalIdV1   :&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toCompressedGlobalIdV1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// graphql-relay toGlobalId : VXNlcjoxMjM0NTY3&lt;/span&gt;
&lt;span class="c1"&gt;// toCompressedGlobalIdV1   : gwEBGgAS1oc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does pretty well if the original ID format is numeric. If the original ID format is a string like cuid or UUID, this has little effect, but it won't make it any longer than the old way, and still guarantee URL-safety.&lt;/p&gt;

&lt;p&gt;Your implementation is not necessarily identical to this one. To summarize the key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compress typenames using a dictionary&lt;/li&gt;
&lt;li&gt;Use Base64 URL, Base62, Base58 to get URL-safe string&lt;/li&gt;
&lt;li&gt;Keep original ID short&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Or, you can use a Graph DB in the first place 😉)&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>relay</category>
    </item>
  </channel>
</rss>
