<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Summarity</title>
        <description>Tech, thoughts, tinkering.</description>
        <link>/</link>
        <atom:link href="/feed.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Tue, 10 Dec 2024 12:18:20 +0000</pubDate>
        <lastBuildDate>Tue, 10 Dec 2024 12:18:20 +0000</lastBuildDate>
        <generator>Jekyll v4.3.1</generator>
        
            <item>
                <title>Creating Alpine.js Components in Nim</title>
                <description>&lt;p&gt;Nowadays, I’m building more and more services using the MASH stack: &lt;a href=&quot;https://github.com/guzba/mummy&quot; target=&quot;_blank&quot;&gt;Mummy&lt;/a&gt;, &lt;a href=&quot;https://alpinejs.dev/&quot; target=&quot;_blank&quot;&gt;Alpine.js&lt;/a&gt;, &lt;a href=&quot;https://sqlite.org/index.html&quot; target=&quot;_blank&quot;&gt;SQLite&lt;/a&gt;, and &lt;a href=&quot;https://htmx.org/&quot; target=&quot;_blank&quot;&gt;htmx&lt;/a&gt;. This stack provides a performant, pleasant, and straightforward way to develop web applications.&lt;/p&gt;

&lt;p&gt;In my first attempt, &lt;a href=&quot;https://summarity.com/findsight&quot; target=&quot;_blank&quot;&gt;Findsight&lt;/a&gt;, the frontend HTML was generated using server-side-rendered &lt;a href=&quot;https://github.com/karaxnim/karax&quot; target=&quot;_blank&quot;&gt;karax&lt;/a&gt;. Note that this was not utilizing the karax reactive framework, but its &lt;a href=&quot;https://github.com/karaxnim/karax?tab=readme-ov-file#server-side-html-rendering&quot; target=&quot;_blank&quot;&gt;DOM-builder DSL&lt;/a&gt;. However, the Alpine component code, state management, various app startup code, and htmx event handlers were all written using inline JS, which proved to be unwieldy as my projects grew.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how to use Nim as a full stack language for type safety and maintainability. We’ll build a complete example of frontend and backend code, which will demonstrate the important concepts to keep in mind when using Nim with Alpine.&lt;/p&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#alpine-primer&quot; id=&quot;markdown-toc-alpine-primer&quot;&gt;Alpine Primer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#frontend&quot; id=&quot;markdown-toc-frontend&quot;&gt;Frontend&lt;/a&gt;    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#project-structure&quot; id=&quot;markdown-toc-project-structure&quot;&gt;Project Structure&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#component-contract&quot; id=&quot;markdown-toc-component-contract&quot;&gt;Component Contract&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#alpine-abstraction&quot; id=&quot;markdown-toc-alpine-abstraction&quot;&gt;Alpine Abstraction&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#component-implementation-state&quot; id=&quot;markdown-toc-component-implementation-state&quot;&gt;Component Implementation: State&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#component-implementation-binding&quot; id=&quot;markdown-toc-component-implementation-binding&quot;&gt;Component Implementation: Binding&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#building-the-frontend&quot; id=&quot;markdown-toc-building-the-frontend&quot;&gt;Building the Frontend&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#backend&quot; id=&quot;markdown-toc-backend&quot;&gt;Backend&lt;/a&gt;    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#statically-including-the-frontend-code&quot; id=&quot;markdown-toc-statically-including-the-frontend-code&quot;&gt;Statically including the Frontend Code&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#rendering-the-html&quot; id=&quot;markdown-toc-rendering-the-html&quot;&gt;Rendering the HTML&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#serve-it&quot; id=&quot;markdown-toc-serve-it&quot;&gt;Serve it&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;alpine-primer&quot;&gt;Alpine Primer&lt;/h2&gt;

&lt;p&gt;Consider a single binding:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x-show=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Content&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s fine, as we’re including the component property &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt; by itself and there’s no active JS code in here. However, even a tiny component store violates this by including a JS object:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x-data=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{ open: false }&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or, staying with the same example, an event binding that contains code:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x-on:click=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;open = true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Expand&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Side note: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-on&lt;/code&gt; can be shortened to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These are smaller examples, but in a sufficiently complex app, the complexity of these little inline snippets grows with it.&lt;/p&gt;

&lt;p&gt;Fortunately, Alpine provides a way to extract the component (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-data&lt;/code&gt;) into a standalone JS object. Here’s an example straight from the documentation:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x-data=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dropdown&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x-on:click=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;toggle&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
 
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x-show=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
 
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;alpine:init&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;Alpine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dropdown&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 
            &lt;span class=&quot;nx&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;open&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And &lt;a href=&quot;https://alpinejs.dev/globals/alpine-data#encapsulating-directives-with-x-bind&quot; target=&quot;_blank&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-bind&lt;/code&gt;&lt;/a&gt; enables the same for any template directive. Both approaches combined keep the HTML clean.&lt;/p&gt;

&lt;p&gt;But now we’d have to write JavaScript, and I’d like to stay in Nim. Nim does have a JS backend, and there are just a few steps needed to create those components in pure Nim.&lt;/p&gt;

&lt;h2 id=&quot;frontend&quot;&gt;Frontend&lt;/h2&gt;

&lt;p&gt;First create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frontend&lt;/code&gt; directory. We’ll put all client side code in here, which is going to be compiled to JS from Nim, and then later minified and optimized.&lt;/p&gt;

&lt;h3 id=&quot;project-structure&quot;&gt;Project Structure&lt;/h3&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.
├── frontend
│   ├── alpine.nim
│   ├── app.nimble
│   ├── app.nim
│   ├── components.nim
│   └── nim.cfg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Nim config &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nim.cfg&lt;/code&gt; sets our backend, which is picked up by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nimsuggest&lt;/code&gt; in VS Code to provide backend-relevant error checks:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;backend:js
-d:release
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It also forces a release build. Nim-compiled JS, depending on the libraries used, is extremely verbose and the debugging info that is generated is less useful in JS code. By compiling in release mode, we end up with more readable JS.&lt;/p&gt;

&lt;p&gt;The resulting JS is still too large to ship directly to users, so we need to optimize it. For this, I’m using &lt;a href=&quot;https://terser.org/&quot; target=&quot;_blank&quot;&gt;Terser&lt;/a&gt;, which is the best standalone optimizing minifier (&lt;a href=&quot;https://esbuild.github.io/api/#minify-considerations&quot; target=&quot;_blank&quot;&gt;esbuild&lt;/a&gt; and the &lt;a href=&quot;https://github.com/google/closure-compiler#readme&quot; target=&quot;_blank&quot;&gt;Google Closure Compiler&lt;/a&gt; both have similar functionality, but are a less effective). Install it system wide via:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; terser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx&lt;/code&gt; to invoke it later without prior installation.&lt;/p&gt;

&lt;p&gt;Now add it as a build task to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.nimble&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strformat&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;           &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;js&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;requires&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;nim &amp;gt;= 2.0.0&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;after&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;terser --compress hoist_funs,keep_fargs=false,unsafe --mangle toplevel {bin[0]}.js -o {bin[0]}.dist.js&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Other than the default terser options, we enable:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;additional compression options
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hoist_funs&lt;/code&gt;: hoists function declarations&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keep_fargs=false&lt;/code&gt;: discards unused function arguments (breaks code which relies on Function.length)&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt;: enables all &lt;a href=&quot;https://terser.org/docs/miscellaneous/#the-unsafe-compress-option&quot; target=&quot;_blank&quot;&gt;unsafe optimizations&lt;/a&gt; (which aren’t actually that unsafe)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;global name mangling (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toplevel&lt;/code&gt;) which enables terser to mangle some of the support functions that Nim emits for e.g. string formatting which would otherwise have quite long gensym names&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These options are optimized for this use case and will be different if you’re working on a Nim-JS project that isn’t Alpine. For example, you wouldn’t use global name mangling for JS libraries. Since Alpine component code depends on object properties and methods, they’re unaffected by the mangling algorithm.&lt;/p&gt;

&lt;h3 id=&quot;component-contract&quot;&gt;Component Contract&lt;/h3&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;components.nim&lt;/code&gt;, let’s define the type interface for both the component state (used with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alpine.data&lt;/code&gt;) and the behavior (used with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-bind&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dom&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;object&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;destroy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cstring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Interval&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cint&lt;/span&gt;
    
  &lt;span class=&quot;n&quot;&gt;DropButton&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;object&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;button&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exportc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;['&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;']&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.}:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exportc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;[':&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;']&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.}:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To break down what’s going on here:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std/dom&lt;/code&gt; gives us access to a lot of FFI types for interacting with the browser’s object implementations.&lt;/p&gt;

&lt;p&gt;The object types are declared as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ref&lt;/code&gt;, which is the default for JavaScript. Without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ref&lt;/code&gt;, Nim will use copy semantics, and emit large helper functions for deep-copying, which are not required in this case.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DropDown&lt;/code&gt; holds the component state:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;destroy&lt;/code&gt; are object methods (in the JS sense) that are invoked by Alpine when the component is created and destroyed&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt; will hold the current open/collapsed state&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toggle&lt;/code&gt; is the method that will change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt; state, and also demos parameter passing to object methods&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timer&lt;/code&gt; demos a component property that refers to a native browser type, in this case a time interval&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;counter&lt;/code&gt; is another property that will be used later to explain object property access from within methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DropButton&lt;/code&gt; holds the binding:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; is an Alpine attribute that determines the DOM type of the bound element&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@click&lt;/code&gt; (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-on:click&lt;/code&gt;) binds to the event (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-on&lt;/code&gt;) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;click&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:disabled&lt;/code&gt; is a direct attribute binding that maps the given DOM element attribute value to a component method/property&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may notice the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exportc&lt;/code&gt; pragmas on the last two properties. This is a necessary workaround for Nim’s name mangling. While some identifiers in Nim can be escaped with &lt;code&gt;`name`&lt;/code&gt;, it’s not nearly as flexible as JS’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;['name']&lt;/code&gt;, so we have to cheat a bit with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exportc&lt;/code&gt;’s built-in string formatting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@click&lt;/code&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;['$1']&quot;&lt;/code&gt; format produces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;['@click']&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disabled&lt;/code&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[':$1']&lt;/code&gt; format produces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[':disabled']&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;alpine-abstraction&quot;&gt;Alpine Abstraction&lt;/h3&gt;

&lt;p&gt;We’re almost ready to dive into the component implementation, but we need a few FFI helpers first. In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alpine.nim&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerComponent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cstring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
  &lt;span class=&quot;p&quot;&gt;{.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;importjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Alpine.data(#, #)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bindElement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cstring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;importjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Alpine.bind(#, #)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{.&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;(#).bind(#)&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typedesc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;importc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nodecl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.}:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registerComponent&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bindElement&lt;/code&gt; map to the respective Alpine functions&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env&lt;/code&gt; is a mapping of JavaScript’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bind&lt;/code&gt; function, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind&quot; target=&quot;_blank&quot;&gt;which allows a caller to set the inner &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;&lt;/a&gt; - this may be confusing at first but is required later as we get into closures over object instances from within methods&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; allows us to make Nim aware of the implicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; in a method, and give it a strong type to make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nimsuggest&lt;/code&gt; and compiler messages more helpful. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;importc&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inject&lt;/code&gt; ensure that the code emitted by Nim refers to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; literally&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;component-implementation-state&quot;&gt;Component Implementation: State&lt;/h3&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.nim&lt;/code&gt;, we’ll place the actual implementation, starting with the component state and methods:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;strformat&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpine&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;components&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dropdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;initialOpenState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;
      
      &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
          &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;inc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Counter is now at {this.counter}&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# explicit binding&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;1_000&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      
      &lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Component initialized and timer started&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;destroy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;clearInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initialOpenState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cstring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Parameter passed to toggle: {msg}&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s a lot more behavior in here than required for a normal dropdown, mostly because I want to show you how to implement other common patterns as well. If you understand what’s happening in this component, you’ll be able to create components of virtually any complexity.&lt;/p&gt;

&lt;p&gt;Let’s start with the proc signature:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dropdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;initialOpenState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alpine.data&lt;/code&gt; (see the example at the beginning of this article) expects a function, which &lt;em&gt;returns&lt;/em&gt; a component: a JS object with methods. This is that function. But Alpine also allows parameter passing, usually used for initial state. Here we receive a boolean that indicates the initial state of the dropdown and later copy this into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;All other properties of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dropdown&lt;/code&gt; component are methods. They’re declared inline for brevity, but that is not required. All of them open with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this Dropdown&lt;/code&gt;, which declares the Nim identifier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; with the given type.&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt;, on component creation, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timer&lt;/code&gt; property is initialized with a new interval timer using the JS function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setInterval&lt;/code&gt;. Here we have to be careful with scoping. We need to ensure that the inline proc passed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setInterval&lt;/code&gt; closes over the correct &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;. We can’t use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this Dropdown&lt;/code&gt; on its own in the timer proc, because it is not invoked from the Alpine environment, but from the browser timer callback. Instead, we need to explicitly close over the &lt;em&gt;outer&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt;. There are two ways to do this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env&lt;/code&gt; binding we declared before as shown here - this will create a new function that wraps the inner function in a closure environment with the supplied &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; value&lt;/li&gt;
  &lt;li&gt;declare a new variable in the method that the callback can close over&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the second variant, we’d have to add a line like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;var that = this&lt;/code&gt; below the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this Dropdown&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; proc body, and then refer to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;that&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this&lt;/code&gt; from the timer callback proc. Personally I consider &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env&lt;/code&gt; to be a cleaner way of achieving this.&lt;/p&gt;

&lt;p&gt;Other than that, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; method is simple: create a timer that keeps incrementing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;counter&lt;/code&gt; and print new values to the console (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo&lt;/code&gt; becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.log&lt;/code&gt;). &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;destroy&lt;/code&gt; ensures the timer is deregistered.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toggle&lt;/code&gt; is the core functionality of flipping the dropdown’s internal state. It also demos the passing of parameters to component methods.&lt;/p&gt;

&lt;h3 id=&quot;component-implementation-binding&quot;&gt;Component Implementation: Binding&lt;/h3&gt;

&lt;p&gt;The last thing we have to implement on the frontend is the binding, to keep the HTML as clean as possible:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dropbutton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropButton&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;DropButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;`:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;boo!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;`:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DropDown&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On a mouse click event, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toggle&lt;/code&gt; method is invoked with a parameter. After the counter reaches 11 (so after 10s), the toggle button will be disabled.&lt;/p&gt;

&lt;p&gt;Finally, we register the component:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alpine:init&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;proc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;registerComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dropdown&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dropdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bindElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dropbutton&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dropbutton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;building-the-frontend&quot;&gt;Building the Frontend&lt;/h3&gt;

&lt;p&gt;A simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nimble build&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frontend/&lt;/code&gt; now produces two files:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-rw-r--r--@ 1 turbo  staff    11K Dec 29 23:43 app.dist.js
-rw-r--r--@ 1 turbo  staff    34K Dec 21 18:32 app.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You may be surprised at how big the compiled artifact is. 34k for a simple dropdown?! What’s actually happened here though is that Nim has emitted standard library functions we’ve used, e.g. for string formatting. That will only happen once. As you add more functionality, those functions are re-used, so the output size doesn’t grow at this rate!&lt;/p&gt;

&lt;p&gt;You can also see Terser has done its job and the final artifact is only a third of the size and looks like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;){&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fromCharCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;;){&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)?&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// and so on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re size sensitive you should reduce the reliance on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std&lt;/code&gt; libraries and bundle JS libraries instead, that you then access via FFI. For example, if I remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strformat&lt;/code&gt; import and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo&lt;/code&gt; statements from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.nim&lt;/code&gt; module, we end up with a much smaller file:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-rw-r--r--@ 1 turbo  staff   3.0K Dec 29 23:49 app.dist.js
-rw-r--r--@ 1 turbo  staff   7.6K Dec 29 23:49 app.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;backend&quot;&gt;Backend&lt;/h2&gt;

&lt;p&gt;Our frontend project is now complete and we have to serve it. I’m not going to show you how to build a full MASH app, just the basics of SSR HTML that uses the component we built above.&lt;/p&gt;

&lt;p&gt;Here’s the project structure:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.
├── backend
│   └── server.nim
├── frontend
│   ...
└── server.nimble
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The nimble file will need to build the frontend first:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;srcDir&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;backend&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bin&lt;/span&gt;           &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;server&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;c&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;requires&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;nim &amp;gt;= 2.0.0&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;requires&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;karax#head&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;requires&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mummy &amp;gt;= 0.3.5&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;withDir&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;frontend&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;nimble build&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ll also pull in mummy and karax. The backend for this is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c&lt;/code&gt; (or any other native backend you want).&lt;/p&gt;

&lt;h3 id=&quot;statically-including-the-frontend-code&quot;&gt;Statically including the Frontend Code&lt;/h3&gt;

&lt;p&gt;I prefer to do this, you may also choose to read it from disk or serve it from a CDN:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;strutils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;strformat&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mummy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;routers&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;karax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;karaxdsl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vdom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vstyles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appJs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;./frontend/app.dist.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;readFile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;rendering-the-html&quot;&gt;Rendering the HTML&lt;/h3&gt;

&lt;p&gt;First, let’s build the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rHead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VNode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;buildHtml&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;charset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;viewport&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
      &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@[&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;width=device-width&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;s&quot;&gt;&quot;initial-scale=1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;s&quot;&gt;&quot;maximum-scale=1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;s&quot;&gt;&quot;user-scalable=0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http-equiv&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;X-UA-Compatible&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ie=edge&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;&quot;defer&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The various &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta&lt;/code&gt; directives are the minimum required to make sure our page renders fine on all device sizes with sensible defaults.&lt;/p&gt;

&lt;p&gt;We also import the Alpine release. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; attribute causes the browser to fetch the remote script immediately after parsing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, but delay execution until after the entire HTML has been parsed. This is important for our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; directives to fire deterministically. Usually boolean attributes are activated like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attrib = true&lt;/code&gt; in karax, but since it conflicts with the built-in Nim keyword we need to use the string version.&lt;/p&gt;

&lt;p&gt;Now let’s build the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VNode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;buildHtml&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tdiv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;x-data&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;dropdown(true)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;x-bind&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;dropbutton&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Toggle Me&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;tdiv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;x-show&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;I'm inside!&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;verbatim&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
      &amp;lt;script&amp;gt;
        {appJs}
      &amp;lt;/script&amp;gt;
    &quot;&quot;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A few notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tdiv&lt;/code&gt; is karax’ way of avoiding conflicts with the Nim operator &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;div&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-data&lt;/code&gt; is the component initializer, also passing the initial open state to our new dropdown&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-bind&lt;/code&gt; means the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;button&amp;gt;&lt;/code&gt; will inherit all behavior from the binding we created in Nim&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x-show&lt;/code&gt; ensures the inner paragraph is only shown when the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;open&lt;/code&gt; property is true (or truthy)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verbatim&lt;/code&gt; includes the bundled frontend code inline. Again, this is a personal preference (avoid another request), you don’t have to do this.&lt;/p&gt;

&lt;p&gt;Now emit the entire HTML file as a string:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vnode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buildHtml&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lang&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rHead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vnode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;serve-it&quot;&gt;Serve it&lt;/h3&gt;

&lt;p&gt;The very last thing to do is to serve this via mummy:&lt;/p&gt;

&lt;div class=&quot;language-nim highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;indexHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpHeaders&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;text/html; charset=utf-8&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Router&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indexHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Serving on http://localhost:8080&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;After &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nimble run&lt;/code&gt;, we can view the result at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:8080&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/demo-alpine-nim.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can download the complete example repo &lt;a href=&quot;https://github.com/turbo/nim-alpine-demo&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
                <pubDate>Wed, 27 Dec 2023 00:00:00 +0000</pubDate>
                <link>/nim-alpine</link>
                <guid isPermaLink="true">/nim-alpine</guid>
                
                <category>code</category>
                
                
            </item>
        
            <item>
                <title>Never Down: Implementing Auto Fallback in Caddy</title>
                <description>&lt;p&gt;I host lots of private (and a few public) projects behind &lt;a href=&quot;https://caddyserver.com/&quot; target=&quot;_blank&quot;&gt;Caddy&lt;/a&gt;. I do like its simplicity and obvious happy path. Something I did struggle with at the start of my journey with Caddy, and something I routinely forget again, is how to set up a fallback page. This is a good idea if there’s a single-instanced service that might be taken offline for a few moments due to scheduled or unscheduled maintenance. The solution is to create a second, static &lt;a href=&quot;https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#upstreams&quot; target=&quot;_blank&quot;&gt;upstream&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the Caddyfile for &lt;a href=&quot;https://summarity.com/findsight&quot; target=&quot;_blank&quot;&gt;Findsight&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:9999 {
	respond &quot;We'll be right back (usually within 3 minutes)!&quot;
}

www.findsight.ai {
	redir https://findsight.ai{uri}
}

findsight.ai {
	handle {
		reverse_proxy localhost:8080 :9999 {
			lb_policy first
			lb_try_duration 1s
			fail_duration 30s
			transport http {
				dial_timeout 500ms
			}
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s what’s happening:&lt;/p&gt;

&lt;p&gt;In normal operation, Caddy reverse proxies requests to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost:8080&lt;/code&gt; where the main Findsight app listens. The entry point is a domain (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;findsight.ai&lt;/code&gt;), and Caddy will automatically handle certificate provisioning and TLS termination. Additionally, there’s a request rewrite for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www&lt;/code&gt; subdomain.&lt;/p&gt;

&lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost:8080&lt;/code&gt; would be the only upstream defined and the server goes down (e.g. for database migration) Cloudflare, which is the CDN for Findsight, would display a generic “upstream dead” error and that’s not a good look. It leaves the user guessing: why is it down? When will it be back?&lt;/p&gt;

&lt;p&gt;We need to define a fallback. If Caddy considers an entry for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reverse_proxy&lt;/code&gt; to be dead, it moves on to the next one. That behavior is defined by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lb&lt;/code&gt; (&lt;a href=&quot;https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#load-balancing&quot; target=&quot;_blank&quot;&gt;load balancing&lt;/a&gt;) policy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;first&lt;/code&gt;. Dead upstreams remain dead for 30s. This duration can be tuned to taste, I chose it mainly to avoid requests hitting the server during start-up after a crash, when the port is reserved but requests won’t be processed yet. Now requests hit the internal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:9999&lt;/code&gt; upstream, which serves a &lt;a href=&quot;https://caddyserver.com/docs/caddyfile/directives/respond&quot; target=&quot;_blank&quot;&gt;static response&lt;/a&gt; directly from Caddy. Here one could alternatively display a full HTML page.&lt;/p&gt;
</description>
                <pubDate>Tue, 26 Dec 2023 00:00:00 +0000</pubDate>
                <link>/caddy-fallback</link>
                <guid isPermaLink="true">/caddy-fallback</guid>
                
                <category>code</category>
                
                
            </item>
        
            <item>
                <title>GitHub code scanning autofix demo</title>
                <description>&lt;p&gt;A 5 minute demo of GitHub code scanning and its new AI-powered automatic fix suggestions. Learn &lt;a href=&quot;https://docs.github.com/en/code-security/code-scanning/managing-code-scanning-alerts/about-autofix-for-codeql-code-scanning&quot; target=&quot;_blank&quot;&gt;more about this feature&lt;/a&gt; or &lt;a href=&quot;https://github.com/features/preview/security&quot; target=&quot;_blank&quot;&gt;request early access&lt;/a&gt;.&lt;/p&gt;
</description>
                <pubDate>Thu, 30 Nov 2023 00:00:00 +0000</pubDate>
                <link>/autofix-demo</link>
                <guid isPermaLink="true">/autofix-demo</guid>
                
                <category>code</category>
                
                <category>video</category>
                
                
            </item>
        
            <item>
                <title>Building a Hybrid/AI Search Engine for Non-fiction Content</title>
                <description>&lt;p&gt;Originally presented at an internal GitHub event, this talk takes you behind the scenes of &lt;a href=&quot;https://summarity.com/findsight&quot; target=&quot;_blank&quot;&gt;Findsight AI&lt;/a&gt; and it’s three-tier search architecture. Along the way you’ll learn how to implement search across traditional LER/NER tags, how to build a safe interface for SQLite’s FTS5 full text search, and how to optimize retrieval based on embeddings.&lt;/p&gt;
</description>
                <pubDate>Tue, 18 Apr 2023 00:00:00 +0000</pubDate>
                <link>/findsight-talk</link>
                <guid isPermaLink="true">/findsight-talk</guid>
                
                <category>code</category>
                
                <category>video</category>
                
                
            </item>
        
            <item>
                <title>Introducing: Findsight</title>
                <description>&lt;p&gt;I just launched Findsight AI, available for free on &lt;a href=&quot;https://findsight.ai&quot; target=&quot;_blank&quot;&gt;findsight.ai&lt;/a&gt;. Findsight helps you discover non-fiction sources like books, articles, papers, and more by comparing their core claims and ideas.&lt;/p&gt;

&lt;p&gt;Join more than 10,000 users, and &lt;strong&gt;try it right now on &lt;a href=&quot;https://findsight.ai&quot; target=&quot;_blank&quot;&gt;findsight.ai&lt;/a&gt;, no sign-up required!&lt;/strong&gt;&lt;/p&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#what-is-findsight&quot; id=&quot;markdown-toc-what-is-findsight&quot;&gt;What is Findsight?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#whats-unique-about-findsight&quot; id=&quot;markdown-toc-whats-unique-about-findsight&quot;&gt;What’s unique about Findsight?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-does-it-work&quot; id=&quot;markdown-toc-how-does-it-work&quot;&gt;How does it work?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#notes&quot; id=&quot;markdown-toc-notes&quot;&gt;Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-is-findsight&quot;&gt;What is Findsight?&lt;/h2&gt;

&lt;p&gt;Findsight is a search engine that lets you explore non-fiction content based on claims — the key points made by sources. You can discover and compare claims from various sources, see how authors tackle issues differently, and navigate to related claims to create your own learning journey. You can also find links to the original book or article. This approach is called syntopical reading.&lt;/p&gt;

&lt;p&gt;Findsight combines traditional text and tag search with AI-powered filters that let you find related claims, and even claims that answer questions.&lt;/p&gt;

&lt;p&gt;Here’s an example of a result:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/findsight-example-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Clicking on the title of a work expands this context view. In addition to the highlighted claims (the ones matching the search), you’ll get to see a few more claims surrounding it to get a sense of the narrative of the source. The real fun starts with the explore feature: click the arrow to jump from a claim to related claims. There’s always something new to learn by starting from &lt;em&gt;Random&lt;/em&gt; and following a new path.&lt;/p&gt;

&lt;p&gt;Findsight’s AI-powered filters enable you to find claims by meaning. Here’s an &lt;a href=&quot;https://findsight.ai/s/qqr-0gPxIzppKqiy9BNac&quot; target=&quot;_blank&quot;&gt;example of the “answer”&lt;/a&gt; filter identifying data that supports the user’s question:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/findsight-example-2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can find a detailed explanation of Findsight’s search capabilities in the &lt;em&gt;Search Guide&lt;/em&gt; on &lt;a href=&quot;https://findsight.ai&quot; target=&quot;_blank&quot;&gt;findsight.ai&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;whats-unique-about-findsight&quot;&gt;What’s unique about Findsight?&lt;/h2&gt;

&lt;p&gt;Generative AI has inspired exceptional knowledge retrieval tools, but Findsight’s core priorities make it unique:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Top-notch summarization&lt;/strong&gt;: Findsight employs cutting-edge large language models for high-quality summarization, extracting key claims while minimizing AI-generated inaccuracies — faithfully reflecting the source’s intent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Robust hybrid search&lt;/strong&gt;: With a custom search engine tailored for non-fiction, Findsight melds traditional text search and AI-powered retrieval to deliver highly relevant results in most cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discovery, not consumption&lt;/strong&gt;: Findsight isn’t about consumption; it’s a launchpad to &lt;em&gt;find insights&lt;/em&gt; and collect books or articles to explore further. Since a collection of claims can’t capture a source’s entire content, we limit the context returned in searches.&lt;/p&gt;

&lt;p&gt;That said, here’s a quick list of related services&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; that you can combine with Findsight to level up your learning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://elicit.org&quot; target=&quot;_blank&quot;&gt;Elicit&lt;/a&gt;: AI research assistant with a rich search interface for common attributes of scientific papers&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://shepherd.com&quot; target=&quot;_blank&quot;&gt;Shepherd&lt;/a&gt;: High-quality human-curated book suggestions with a sprinkle of AI-powered topic exploration&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://basmo.app&quot; target=&quot;_blank&quot;&gt;Basmo&lt;/a&gt;: Polished app experience for interacting with books using AI chatbots, and share knowledge with other users&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.librarian-ai.com&quot; target=&quot;_blank&quot;&gt;Librarian AI&lt;/a&gt;: Discover new books based on ones you already have&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/openai/chatgpt-retrieval-plugin&quot; target=&quot;_blank&quot;&gt;ChatGPT Retrieval&lt;/a&gt;: Early-stage project to enable knowledge retrieval with OpenAI’s ChatGPT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: Findsight/Summarity has no affiliation with any of the projects mentioned above. Follow links at your own risk. Not an endorsement.&lt;/p&gt;

&lt;h2 id=&quot;how-does-it-work&quot;&gt;How does it work?&lt;/h2&gt;

&lt;p&gt;This is a highly condensed overview of the tech stack behind Findsight - you don’t need to know this to use it. If you’re interested in the implementation details on any of the parts below, drop me an email and I’ll consider writing a separate post.&lt;/p&gt;

&lt;p&gt;Findsight’s AI features are powered by OpenAI’s GPT3, GPT3.5 completion models and Ada embeddings, combined with fine-tuned Azure LER/NER models.&lt;/p&gt;

&lt;p&gt;Beyond those services, Findsight is a depencency-free application written in &lt;a href=&quot;https://nim-lang.org&quot; target=&quot;_blank&quot;&gt;Nim&lt;/a&gt;. The larger components are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a custom OpenAI client with semantic caching and automatic &lt;a href=&quot;/hyde&quot; target=&quot;_blank&quot;&gt;HyDE&lt;/a&gt; prompting&lt;/li&gt;
  &lt;li&gt;an extraction module that automatically cleans and chunks input text, and adapts prompts and chunk size based on self-supervision&lt;/li&gt;
  &lt;li&gt;a custom KNN / vector-similarity index based on &lt;a href=&quot;https://proceedings.neurips.cc/paper_files/paper/2012/file/072b030ba126b2f4b2374f342be9ed44-Paper.pdf&quot; target=&quot;_blank&quot;&gt;SuperBit encoding&lt;/a&gt; and using &lt;a href=&quot;https://github.com/mratsim/weave&quot; target=&quot;_blank&quot;&gt;Weave&lt;/a&gt; for data-parallel execution&lt;/li&gt;
  &lt;li&gt;a hybrid search engine that uses three stages with content-dependent boosting:
    &lt;ul&gt;
      &lt;li&gt;Alpha: binary attribute filtering (e.g. tags)&lt;/li&gt;
      &lt;li&gt;Beta: BM25-ranked full-text search using a custom search syntax (SafeTS5) that compiles to provably safe (limited complexity) FTS5 queries (uses &lt;a href=&quot;https://github.com/zevv/npeg&quot; target=&quot;_blank&quot;&gt;NPEG&lt;/a&gt;)&lt;/li&gt;
      &lt;li&gt;Gamma: a ranked similarity search based on the KNN index, using potentially present Alpha and Beta stages as a pushdown filter&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;a book metadata engine that identifies and normalizes ISBN catalog data based on book content&lt;/li&gt;
  &lt;li&gt;a &lt;a href=&quot;https://github.com/guzba/mummy&quot; target=&quot;_blank&quot;&gt;mummy&lt;/a&gt;-based server using SQLite for persistent storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;If you are working on a related project, or spotted a mistake in this list, please get in touch (email in footer) &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
                <pubDate>Wed, 05 Apr 2023 00:00:00 +0000</pubDate>
                <link>/findsight</link>
                <guid isPermaLink="true">/findsight</guid>
                
                <category>Findsight AI</category>
                
                
            </item>
        
            <item>
                <title>SQLite-only Rate Limiting</title>
                <description>&lt;p&gt;Application-layer rate limiting is a good way to enforce client resource limits, while at the same time allowing you to place the rate limiting configuration and current statistics next to the application data.&lt;/p&gt;

&lt;p&gt;This approach should not be employed for defending against application layer or network layer (D)DoS attacks; rather, such defense should be pushed towards the edge, utilizing Web Application Firewalls or analogous policy agents that can drop suspicious traffic with minimal overhead.&lt;/p&gt;

&lt;p&gt;In contrast, this approach is more suitable for enforcing fair use limits or limits that are part of a license, which restricts the number of operations an actor can perform.&lt;/p&gt;

&lt;p&gt;SQLite is used to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;store the limits configuration, each with a maximum count per configurable time period&lt;/li&gt;
  &lt;li&gt;store instances of active counters for actors (we’ll use “visitors” for this example)&lt;/li&gt;
  &lt;li&gt;manage expiration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;interface&quot;&gt;Interface&lt;/h2&gt;

&lt;p&gt;The goal is that with a single query executed for every operation, rate limiting calculations are performed automatically:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visitor_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CONFLICT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DO&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;RETURNING&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resets_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;We inform the database that visitor &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; is attempting to perform an operation limited by limit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;If the limit is not yet reached, the current counter is incremented&lt;/li&gt;
  &lt;li&gt;If the limit is reached, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;denied&lt;/code&gt; will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; (SQLite has no booleans), and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resets_at&lt;/code&gt; is defined as the next UTC unix epoch timestamp after which the limit resets. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resets_at&lt;/code&gt; is not defined when the request isn’t denied&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows the counter to be incremented by other positive integer values, for example, if the “cost” between operations varies, which is useful for consumption-based enforcement.&lt;/p&gt;

&lt;h2 id=&quot;database-setup&quot;&gt;Database Setup&lt;/h2&gt;

&lt;h3 id=&quot;limits&quot;&gt;Limits&lt;/h3&gt;

&lt;p&gt;The schema for this is quite simple. First, we define a static configuration table for the limits:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limits&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ASC&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maximum&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maximum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;period_minutes&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;period_minutes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maximum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;STRICT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; uniquely identifies the limit, this should be the limited operation, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_message&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maximum&lt;/code&gt; is the maximum number of allowed operations within the period. This is inclusive, so a maximum of 5 allows 5 requests in a period and denies the 6th&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;period_minutes&lt;/code&gt; defines the time between counter resets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The period can alternatively be expressed in seconds, which is the highest usable time resolution in SQLite.&lt;/p&gt;

&lt;p&gt;To add a new limit:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- allow 5 messages per 2 minutes&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maximum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;period_minutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'send_message'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;counters&quot;&gt;Counters&lt;/h3&gt;

&lt;p&gt;Next, create a table for the counter instances. These will be lazily populated - if a visitor never executes a given operation, there’s no need to create a row for it:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ASC&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visitor_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;denied&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denied&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;-- set by trigger&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resets_at&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resets_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1680373723&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visitor_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;STRICT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note: this references another table &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;visitors&lt;/code&gt;, this may not be the case in your application. Use whichever actor identifier fits your model best.&lt;/p&gt;

&lt;p&gt;A sanity check is added to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resets_at&lt;/code&gt; to protect against logic bugs, using a reasonably recent timestamp and making it clear in the DDL that this column will be a unix epoch in seconds.&lt;/p&gt;

&lt;h3 id=&quot;triggers&quot;&gt;Triggers&lt;/h3&gt;

&lt;p&gt;To implement the add and reset logic, two triggers are required:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init_visitor_limit_counter&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;AFTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHEN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resets_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;BEGIN&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resets_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unixepoch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;period_minutes&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limits&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;denied&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This runs after the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT&lt;/code&gt; statement in our final interface query. If there wasn’t a counter for this operation and visitor, we initialize the counter, flag, and first reset timestamp.&lt;/p&gt;

&lt;p&gt;For any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT&lt;/code&gt;s after this, we need to maintain the flag and reset the counter if necessary using a second trigger:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;update_visitor_limit_counter_breaker&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;BEFORE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;BEGIN&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;-- trip breaker for non-expired&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denied&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maximum&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limits&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visitor_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visitor_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;-- or reset if expired&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resets_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unixepoch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;period_minutes&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limits&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;denied&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visitor_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visitor_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unixepoch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resets_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This runs before the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT&lt;/code&gt; to make sure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;denied&lt;/code&gt; flag that is returned is up to date. In our example, the 6th message should be denied, not the 7th.&lt;/p&gt;

&lt;h2 id=&quot;testing&quot;&gt;Testing&lt;/h2&gt;

&lt;p&gt;That’s it. We can test this now by running the same interface query repeatedly:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visitor_limits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;visitor_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CONFLICT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DO&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;RETURNING&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resets_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- send 6 messages in &amp;lt; 2 minutes:&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Run 1 returns: 0, NULL&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- Run 2, 3, 4, 5 return: 0, 1680441289&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- Run 6 returns: 1, 1680441289&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- ... wait for 2 minutes ...&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Run 7 returns: 0, 1680441422&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As the limit is reached, the counter will continue to increment; which can be leveraged to gain insights into the behavior of limited users. A minor overage is to be expected, but a drastic surge in requests beyond the set limit could signify a bug or the presence of a malicious bot.&lt;/p&gt;
</description>
                <pubDate>Sun, 02 Apr 2023 00:00:00 +0000</pubDate>
                <link>/sqlite-rate-limit</link>
                <guid isPermaLink="true">/sqlite-rate-limit</guid>
                
                <category>code</category>
                
                <category>data</category>
                
                
            </item>
        
            <item>
                <title>You're using the wrong Apple (Silicon) Cores</title>
                <description>&lt;p&gt;With the disruptive arrival of the Apple Silicon Macs, the industry saw a push away from TDP-be-damned space heater hardware to more sensible performance-per-watt targets. The Apple M1, M2 and its mobile counterparts all feature two main general purpose core types: performance and efficiency. Intel’s recent offerings are positively stuffed full of efficiency cores, and other players are following suit.&lt;/p&gt;

&lt;p&gt;That however poses a challenge for developers. It’s now harder than ever to &lt;a href=&quot;https://youtu.be/fHNmRkzxHWs?t=969&quot; target=&quot;_blank&quot;&gt;light up all those transistors&lt;/a&gt; and produce efficient software:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Race to sleep: The faster your software runs, the less power it consumes. […] We’re trying to actually engage the entire chip, the entire piece of hardware to do a computation, in order to do that computation as rapidly and effectively as possible.&lt;/p&gt;

  &lt;p&gt;&lt;cite&gt;– Chandler Carruth, “Efficiency with Algorithms, Performance with Data Structures”&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is pretty good advice. However, simply saturating all cores (or threads) your program &lt;em&gt;can&lt;/em&gt; access is no longer a good strategy, as modern silicon throws two wrenches in our works:&lt;/p&gt;

&lt;p&gt;First, chip design tends to capture common algorithms in hardware, rendering entire classes of general-purpose-CPU implementations obsolete. Whereas you might have previously used optimized libraries for matrix multiplication, or even GPGPU, you’re now better off turning that work over to system frameworks and have them schedule it on specialized cores within the main chip. On Apple Silicon, there are no less than four different tuned hardware implementations for this purpose: CPU-bound ARM NEON, Apple AMX (a semi-secret matrix instruction set), Apple Neural Engine (dedicated few-big cores), and the GPU itself. ANE implementations for deep learning inference outperform implementations using Apple Accelerate (NEON/AMX) by almost 300%!&lt;/p&gt;

&lt;p&gt;Matrix multiplication is still a pretty general use case though and more specific co-processors exist for media encoding and decoding, crypto, real-time image processing and more. But what if you do have a workload that isn’t a good fit for hardware acceleration? If your M1 Ultra reports 20 CPU cores, scheduling 20 threads should be able to use “the entire piece of hardware”?&lt;/p&gt;

&lt;p&gt;Sure enough, you can do that and you will see 100% utilization across all cores (let’s assume your workload is data-parallel and your algorithm is optimal, so no time wasted on IO and cache swaps). But taking our earlier example of the 20-core M1, your program will run virtually as fast using 20 threads as it would using 16. Why is that?&lt;/p&gt;

&lt;p&gt;The difference is caused by the efficiency cores. For normal software, which executes workloads that are both short-lived and small as well as longer-running intense workloads, using all threads isn’t a problem. The easy stuff can be scheduled on efficiency cores (e.g. keeping the UI responsive), while processing can be scheduled on the performance cores. That’s why e.g. Logic allows you to go all in:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/m1-perf-cores-logic.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But if all your threads are coordinating the same, intense workload, using both efficiency and performance cores will cause the clock-speed of performance cores to be dragged down. The consequences are almost non-existent gains of performance compared to just using the performance cores, and a risk of system lag as the system UI itself and background services are choked by the inappropriate load on the efficiency cores.&lt;/p&gt;

&lt;p&gt;Instead, your program should be aware of the number of cores available at the appropriate performance level. On macOS, this can be queried using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sysctl&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sysctl hw.perflevel0.logicalcpu_max
hw.perflevel0.logicalcpu_max: 8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perflevel0&lt;/code&gt; is “performance”, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perflevel1&lt;/code&gt; would be efficiency.&lt;/p&gt;
</description>
                <pubDate>Sun, 12 Mar 2023 00:00:00 +0000</pubDate>
                <link>/apple-silicon-perf</link>
                <guid isPermaLink="true">/apple-silicon-perf</guid>
                
                <category>code</category>
                
                
            </item>
        
            <item>
                <title>Hypothetical Embeddings Explained</title>
                <description>&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Wikipedia:Large_language_models&quot; target=&quot;_blank&quot;&gt;Large Language Models&lt;/a&gt; (LLM) can be combined with &lt;a href=&quot;https://en.wikipedia.org/wiki/Sentence_embedding&quot; target=&quot;_blank&quot;&gt;embeddings&lt;/a&gt; to create accurate knowledge retrieval systems. Hypothetical Document Embeddings, or HyDE&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, can be used to provide a question-answering interface for a given database of facts that feels like an LLM-powered chatbot, but without the risk of hallucinations.&lt;/p&gt;

&lt;p&gt;In LLM-only systems, such as search engines powered by &lt;a href=&quot;https://huggingface.co/blog/rlhf&quot; target=&quot;_blank&quot;&gt;RLHF&lt;/a&gt;-tuned models, there’s a risk of the core model confidently stating wrong facts or making assertions that go beyond or even contradict the available ground truths (e.g. a search index). This problem is more commonly known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)&quot; target=&quot;_blank&quot;&gt;hallucination&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are &lt;a href=&quot;https://cobusgreyling.medium.com/preventing-llm-hallucination-with-contextual-prompt-engineering-an-example-from-openai-7e7d58736162&quot; target=&quot;_blank&quot;&gt;several ways&lt;/a&gt; to engineer prompts to minimize this effect, and combined with &lt;a href=&quot;https://arxiv.org/abs/2210.11416&quot; target=&quot;_blank&quot;&gt;instruction fine tuning&lt;/a&gt; are generally successful in limiting hallucinations. In systems where fact retrieval is the primary use case though, this problem can be almost entirely eliminated.&lt;/p&gt;

&lt;p&gt;HyDE-powered search relies on embeddings, dense vector representations of (in this case) textual data. &lt;a href=&quot;https://platform.openai.com/docs/guides/embeddings&quot; target=&quot;_blank&quot;&gt;OpenAI embeddings&lt;/a&gt; for example, can effectively encode abstract qualities of short and long-form text (up to ~8,000 tokens per embedding) in ~1600-dimensional vectors. These vectors can then be stored in vector-similarity (VSS) indexes (e.g. &lt;a href=&quot;https://redis.io/docs/stack/search/reference/vectors/#querying-vector-fields&quot; target=&quot;_blank&quot;&gt;HNSW/RediSearch&lt;/a&gt;) that facilitate efficient VSS or &lt;a href=&quot;https://redis.io/docs/stack/search/reference/vectors/#hybrid-knn-queries&quot; target=&quot;_blank&quot;&gt;hybrid VSS and full-text queries&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, these systems are then limited to retrieving similar facts. A user interface could prompt the user for a fact, which is then embedded and used as a query vector to retrieve the nearest (most similar) facts from the index. This requires the user to already have a possible answer in mind, which is much less intuitive than asking a question.&lt;/p&gt;

&lt;p&gt;Instead of embedding the user input directly, a sufficiently trained LLM can be used to generated &lt;em&gt;hypothetical&lt;/em&gt; answers, which are in turn embedded and used for retrieval. The LLM will likely hallucinate details for its answers, but since these answers are not relayed to the user as fact, these errors are inconsequential. The embedding is much more sensitive to the structure and language used in the answers, rather than its correctness.&lt;/p&gt;

&lt;p&gt;An example prompt (here using LLaMa-65B) may look like this:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Three possible answers to the question 'what are the negative effects of the war on drugs?' are:
1.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;with an example output:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. Incarcerated people in the U.S. have exceeded two million and the great majority of the people in jail are there for drug related crimes. In many cases, these people don't get to go to school and learn the skills they need to lead healthy, productive lives.
2. People all over the world die daily, in violent confrontations, related to the trade of illegal drugs.
3. Millions of people, including children, are sick with diseases caused by using contaminated, illegal drugs. These people are spreading disease to others.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Depending on the performance of the embedding and the difference between the answers, one embedding per answer (then averaged) or one embedding for the entire answer list can be used. For non-fiction fact retrieval (excerpts from books), I found it helpful to include the question itself in the embedding for each HyDE, since this type of content often poses a question only to immediately answer it.&lt;/p&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;HyDE was first introduced in &lt;a href=&quot;https://arxiv.org/abs/2212.10496&quot; target=&quot;_blank&quot;&gt;Precise Zero-Shot Dense Retrieval without Relevance Labels&lt;/a&gt;, where InstructGPT had been fine tuned to generate HyDE. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
                <pubDate>Sat, 11 Mar 2023 00:00:00 +0000</pubDate>
                <link>/hyde</link>
                <guid isPermaLink="true">/hyde</guid>
                
                <category>data</category>
                
                <category>machine learning</category>
                
                
            </item>
        
            <item>
                <title>Nim 2.0 on Apple Silicon</title>
                <description>&lt;p&gt;The Nim 2.0 release candidate &lt;a href=&quot;https://nim-lang.org/blog/2022/12/21/version-20-rc.html&quot; target=&quot;_blank&quot;&gt;is out&lt;/a&gt;, and includes a whole host of amazing changes and additions. I had been holding off on exploring Nim because I was waiting for &lt;a href=&quot;https://nim-lang.org/blog/2020/12/08/introducing-orc.html&quot; target=&quot;_blank&quot;&gt;ORC&lt;/a&gt; and multi-threading to become the default. They have been available for a long time, but defaults matter - it’s what first time and first-time-in-a-while users experience. It forms their opinion of what the language is about.&lt;/p&gt;

&lt;p&gt;Installing Nim on Apple Silicon Macs is pretty straight forward. The official installer sets up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;choosenim&lt;/code&gt; and a default stable release:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl https://nim-lang.org/choosenim/init.sh &lt;span class=&quot;nt&quot;&gt;-sSf&lt;/span&gt; | sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, both Nim itself and the compiler output will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amd64&lt;/code&gt; (Intel) binaries. To go native, and to benefit from Nim 2 right now, more configuration is required.&lt;/p&gt;

&lt;h2 id=&quot;set-up-nim-2-natively&quot;&gt;Set up Nim 2 natively&lt;/h2&gt;

&lt;p&gt;We need a source build, and thankfully that’s easy:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/nim-lang/Nim
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;Nim
git checkout version-2-0
sh build_all.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yep, that’s it. We don’t have to mess with our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt; either. Instead, we can now point &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;choosenim&lt;/code&gt; at the new build:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;choosenim &amp;lt;location&amp;gt;/Nim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And confirm the architecture with:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ nim -v
Nim Compiler Version 1.9.1 [MacOSX: arm64]
Compiled at 2023-01-14
Copyright (c) 2006-2022 by Andreas Rumpf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(2.0 RC self-reports as 1.9.1 as of Jan 15 2023)&lt;/p&gt;

&lt;p&gt;To switch back, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;choosenim stable&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;native--universal-binaries&quot;&gt;Native &amp;amp; universal binaries&lt;/h2&gt;

&lt;p&gt;Building a project with this toolchain will produce an Intel binary. To get a full build on macOS, we have to follow &lt;a href=&quot;https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary&quot; target=&quot;_blank&quot;&gt;Apple’s guidelines&lt;/a&gt; and set the correct compiler and linker targets. In your project’s nimble (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nimble init&lt;/code&gt; if you don’t have one yet), add a new target:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;task release_clang, &quot;Build a production release (macOS)&quot;:
  --verbose
  --forceBuild:on
  --cc:clang
  --define:release
  --deepcopy:on
  --cpu:arm64
  --passC:&quot;-flto -target arm64-apple-macos11&quot; 
  --passL:&quot;-flto -target arm64-apple-macos11&quot;
  --hints:off
  --outdir:&quot;.&quot;
  setCommand &quot;c&quot;, &quot;hello.nim&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-flto&lt;/code&gt; is required because clang doesn’t like Nim’s default passing of LTO parameters&lt;/li&gt;
  &lt;li&gt;note the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cpu&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-target&lt;/code&gt; parameters&lt;/li&gt;
  &lt;li&gt;the rest are standard release flags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ORC and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;threads&lt;/code&gt; are default now, so no need to enable them here anymore. It’s a good idea to pin the Nim version in the nimble file, in this case to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;= 1.9.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nimble release_clang&lt;/code&gt; now gives us a nice native binary:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ file hello
hello: Mach-O 64-bit executable arm64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to build an Intel binary, add another task and change the target to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x86_64-apple-macos10.12&lt;/code&gt;. If you need a universal binary, build both the Intel and ARM targets first, then merge them using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lipo&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lipo &lt;span class=&quot;nt&quot;&gt;-create&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-output&lt;/span&gt; universal_app x86_app arm_app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;BTW: If you’re wondering what the cover image is about - “Nimm 2” (“take two”) is a popular brand of gummy candy in Germany.&lt;/em&gt;&lt;/p&gt;
</description>
                <pubDate>Sun, 15 Jan 2023 00:00:00 +0000</pubDate>
                <link>/nim-2-m1</link>
                <guid isPermaLink="true">/nim-2-m1</guid>
                
                <category>code</category>
                
                
            </item>
        
            <item>
                <title>Baking Vertex Animations into Alembic Files</title>
                <description>&lt;p&gt;This continues the series on baking dynamics. Last time, we looked at &lt;a href=&quot;/baking-fbx-dynamics&quot; target=&quot;_blank&quot;&gt;baking rigid body dynamics into FBX f-curves&lt;/a&gt;, for rendering in applications that don’t support dynamics (such as mo2).&lt;/p&gt;

&lt;p&gt;In that post, I mentioned that baking soft-body dynamics is a whole lot harder. There are several reasons for that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Soft body deformations rely on per-vertex transformations&lt;/li&gt;
  &lt;li&gt;Apart from Alembic, there are no standard ways of transporting point-based animations between 3D software&lt;/li&gt;
  &lt;li&gt;Even then, some simulations (e.g. fluids) may change the vertex and thus poly count, something barely any software can deal with&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This problem isn’t new, and the industry had come up with two methods to address this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Portable dynamics bake caches (take a look at the &lt;a href=&quot;/baking-fbx-dynamics/&quot; target=&quot;_blank&quot;&gt;previous post&lt;/a&gt; for an explanation of dynamics baking). This is a way of transporting the internal bake cache between applications without “rendering” the transformations to a mesh. Notable software includes e.g. &lt;a href=&quot;https://www.lightwave3d.com/chronosculpt/&quot; target=&quot;_blank&quot;&gt;ChronoSculpt&lt;/a&gt;, which can work with three major cache formats (incl. Alembic)&lt;/li&gt;
  &lt;li&gt;The brute force method: Bake a new mesh for every single frame. This is supported by many apps as “OBJ sequences” or “mesh sequences”, notably AE Element3D. As the name implies, the export format is a folder full of single OBJ files, one for each tick (a frame). The benefit of this method is the wide compatibility: everything supports OBJs (though not necessarily sequences), and changing poly counts are a non-issue since we exchange the entire mesh every tick.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So mesh sequences address a similar problem to Alembic, but how can we convert between them? I needed to do this recently, in order to transport soft-body physics baked in Cheetah to Marmoset Toolbag 4 for rendering. Cheetah supports nothing - it stores its bake cache in the main scene file but the format is undocumented, and there isn’t any support for exporting Alembics. Toolbag on the other hand only supports Alembic, but not mesh sequences.&lt;/p&gt;

&lt;p&gt;The first problem was easy enough to address, and I won’t go into much detail because you’re likely not using Cheetah and are here to learn how to do the conversion from sequences to Alembic. In short, there’s both an &lt;a href=&quot;https://www.cheetah3d.com/forum/index.php?threads/2110/post-14628&quot; target=&quot;_blank&quot;&gt;import&lt;/a&gt; and an &lt;a href=&quot;https://www.cheetah3d.com/forum/index.php?threads/2110/post-82310&quot; target=&quot;_blank&quot;&gt;export&lt;/a&gt; script for OBJ sequences for Cheetah, created by forum users.&lt;/p&gt;

&lt;p&gt;Baking and exporting leaves us with a folder full of OBJs:&lt;/p&gt;

&lt;div class=&quot;gallery-box&quot;&gt;
  &lt;div class=&quot;gallery gallery--post&quot;&gt;
    &lt;img src=&quot;https://avm-cdn.com/images/alembic/sec 1 1.png&quot; loading=&quot;lazy&quot; alt=&quot;Mountains&quot; /&gt;
    &lt;img src=&quot;https://avm-cdn.com/images/alembic/sec 1 2.png&quot; loading=&quot;lazy&quot; alt=&quot;Mountains&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now we need to convert those into one Alembic file. I’ve searched a while for a simple way to do this, but I’m afraid there isn’t one. We have to involve another piece of software, but thankfully it’s free: good old &lt;a href=&quot;https://www.blender.org/&quot; target=&quot;_blank&quot;&gt;Blender&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Users of Blender have tried to solve this problem in the past, and a few have had success with slightly abusing the &lt;a href=&quot;https://blenderartists.org/t/stop-motion-obj-obj-stl-ply-sequence-importer-v2-1-1/670105/86&quot; target=&quot;_blank&quot;&gt;Stop Motion OBJ&lt;/a&gt; plugin, though that workaround seems to no longer work in Blender 3.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href=&quot;https://www.artstation.com/vaticinator&quot; target=&quot;_blank&quot;&gt;Barnaba Mikułowski&lt;/a&gt;, who graciously provided a plugin-free conversion setup based on a geometry node network in Blender. I’ve adapted his original file to be a more generic template, and you can &lt;a href=&quot;//avm-cdn.com/files/obj-to-abc-empty.blend&quot;&gt;&lt;strong&gt;download it here (3 MB)&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Open the Blender scene (I used Blender 3, I recommend you do the same). Click to highlight the “Import Sequence Here” collection and enable visibility if required.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/alembic/1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Go to File -&amp;gt; Import -&amp;gt; Wavefront (.obj) – don’t use the legacy option&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/alembic/2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Navigate to your mesh sequence folder and click-shift-select all files that are part of the sequence (make sure the naming produces proper ordering!)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/alembic/3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Click import, you should now see all OBJs imported as children of the import collection, in proper order, and rendered in the viewport.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/alembic/4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To clean up the viewport, untick the box on the collection object to re-exclude it from the viewport. In the bake network, confirm the input is set to the import collection. Adjust the Blender animation timeline to fit your imported sequence (start and end frames can be set at the top right of the keyframe panel, and FPS can be set in the output properties which can be found behind the printer icon in the vertical icon bar on the right between the viewports and the inspector). Click the play button to preview your animation and check everything’s in order.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/alembic/5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now select (!) only the bake network in the object tree and go to File -&amp;gt; Export -&amp;gt; Alembic (.abc). Change the scene options if required, but make sure “Only: Selected Objects” is ticked!&lt;/p&gt;

&lt;div class=&quot;gallery-box&quot;&gt;
  &lt;div class=&quot;gallery gallery--post&quot;&gt;
    &lt;img src=&quot;https://avm-cdn.com/images/alembic/6.png&quot; loading=&quot;lazy&quot; alt=&quot;Mountains&quot; /&gt;
    &lt;img src=&quot;https://avm-cdn.com/images/alembic/7.png&quot; loading=&quot;lazy&quot; alt=&quot;Mountains&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now, the file can be imported into any package that supports Alembic:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;//avm-cdn.com/images/flag-waving-in-MT4-cropped.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</description>
                <pubDate>Sat, 31 Dec 2022 00:00:00 +0000</pubDate>
                <link>/bake-mesh-sequence-alembic</link>
                <guid isPermaLink="true">/bake-mesh-sequence-alembic</guid>
                
                <category>creative</category>
                
                
            </item>
        
    </channel>
</rss>